Merge pull request #118035 from Repiteo/4.6.2-final

[4.6] Cherry-picks for the 4.6 branch (future 4.6.2) - 5th batch
This commit is contained in:
Thaddeus Crews
2026-03-31 12:46:16 -05:00
committed by GitHub
8 changed files with 273 additions and 163 deletions
+6 -6
View File
@@ -543,16 +543,16 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs.push_back(InputEventKey::create_reference(Key::SPACE | KeyModifierMask::CTRL));
default_builtin_cache.insert("ui_text_completion_query", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KeyModifierMask::SHIFT | Key::TAB));
inputs.push_back(InputEventKey::create_reference(KeyModifierMask::SHIFT | Key::ENTER));
inputs.push_back(InputEventKey::create_reference(KeyModifierMask::SHIFT | Key::KP_ENTER));
default_builtin_cache.insert("ui_text_completion_accept", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::TAB));
inputs.push_back(InputEventKey::create_reference(Key::ENTER));
inputs.push_back(InputEventKey::create_reference(Key::KP_ENTER));
default_builtin_cache.insert("ui_text_completion_accept", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KeyModifierMask::SHIFT | Key::TAB));
inputs.push_back(InputEventKey::create_reference(KeyModifierMask::SHIFT | Key::ENTER));
inputs.push_back(InputEventKey::create_reference(KeyModifierMask::SHIFT | Key::KP_ENTER));
default_builtin_cache.insert("ui_text_completion_replace", inputs);
// Newlines
@@ -9092,6 +9092,7 @@ void AnimationMarkerEdit::gui_input(const Ref<InputEvent> &p_event) {
float offset = (pos.x - timeline->get_name_limit()) / timeline->get_zoom_scale();
if (!read_only) {
bool selected = _try_select_at_ui_pos(pos, mb->is_command_or_control_pressed() || mb->is_shift_pressed(), false);
moving_selection_attempt = false;
menu->clear();
menu->add_icon_item(get_editor_theme_icon(SNAME("Key")), TTR("Insert Marker..."), MENU_KEY_INSERT);
+1 -1
View File
@@ -4442,7 +4442,7 @@ void EditorInspector::update_tree() {
bool found = false;
// Small hack for theme_overrides. They are listed under Control, but come from another class.
if (classname == "Control" && p.name.begins_with("theme_override_")) {
if ((classname == "Control" || classname == "Window") && p.name.begins_with("theme_override_")) {
classname = get_edited_object()->get_class();
}
@@ -356,6 +356,20 @@ namespace Godot.Bridge
}
}
}
// This method may be called before initialization.
if (NativeFuncs.godotsharp_dotnet_module_is_initialized().ToBool() && Engine.IsEditorHint())
{
if (_pathTypeBiMap.Paths.Count > 0)
{
Callable.From(() =>
{
string[] scriptPaths = _pathTypeBiMap.Paths.ToArray();
using godot_packed_string_array scriptPathsNative = Marshaling.ConvertSystemArrayToNativePackedStringArray(scriptPaths);
NativeFuncs.godotsharp_internal_editor_file_system_update_files(scriptPathsNative);
}).CallDeferred();
}
}
}
[UnmanagedCallersOnly]
@@ -67,6 +67,7 @@
#endif
#define WAYLAND_MAX_FRAME_TIME_US (1'000'000)
#define WINDOW_READY_TIMEOUT_MS (10'000)
String DisplayServerWayland::_get_app_id_from_context(Context p_context) {
String app_id;
@@ -144,6 +145,66 @@ void DisplayServerWayland::_dispatch_input_event(const Ref<InputEvent> &p_event)
}
}
void DisplayServerWayland::_delete_window(WindowID p_window_id) {
ERR_FAIL_COND(!windows.has(p_window_id));
WindowData &wd = windows[p_window_id];
ERR_FAIL_COND(!windows.has(wd.root_id));
WindowData &root_wd = windows[wd.root_id];
// NOTE: By the time the Wayland thread will send a `WINDOW_EVENT_MOUSE_EXIT`
// the window will be gone and the message will be discarded, confusing the
// engine. We thus have to send it ourselves.
if (wayland_thread.pointer_get_pointed_window_id() == p_window_id) {
_send_window_event(WINDOW_EVENT_MOUSE_EXIT, p_window_id);
}
// The XDG shell specification requires us to clear all popups in reverse order.
while (!root_wd.popup_stack.is_empty() && root_wd.popup_stack.back()->get() != p_window_id) {
_send_window_event(WINDOW_EVENT_FORCE_CLOSE, root_wd.popup_stack.back()->get());
}
if (root_wd.popup_stack.back() && root_wd.popup_stack.back()->get() == p_window_id) {
root_wd.popup_stack.pop_back();
}
if (popup_menu_list.back() && popup_menu_list.back()->get() == p_window_id) {
popup_menu_list.pop_back();
}
#ifdef ACCESSKIT_ENABLED
if (accessibility_driver) {
accessibility_driver->window_destroy(p_window_id);
}
#endif
if (wd.visible) {
#ifdef VULKAN_ENABLED
if (rendering_device) {
rendering_device->screen_free(p_window_id);
}
if (rendering_context) {
rendering_context->window_destroy(p_window_id);
}
#endif
#ifdef GLES3_ENABLED
if (egl_manager) {
egl_manager->window_destroy(p_window_id);
}
#endif
}
if (wd.created) {
wayland_thread.window_destroy(p_window_id);
}
windows.erase(p_window_id);
DEBUG_LOG_WAYLAND(vformat("Destroyed window %d", p_window_id));
}
void DisplayServerWayland::_update_window_rect(const Rect2i &p_rect, WindowID p_window_id) {
ERR_FAIL_COND(!windows.has(p_window_id));
@@ -765,7 +826,7 @@ void DisplayServerWayland::show_window(WindowID p_window_id) {
WindowData &wd = windows[p_window_id];
if (!wd.visible) {
if (!wd.created) {
DEBUG_LOG_WAYLAND(vformat("Showing window %d", p_window_id));
// Showing this window will reset its mode with whatever the compositor
// reports. We'll save the mode beforehand so that we can reapply it later.
@@ -795,7 +856,7 @@ void DisplayServerWayland::show_window(WindowID p_window_id) {
wayland_thread.window_set_borderless(p_window_id, window_get_flag(WINDOW_FLAG_BORDERLESS, p_window_id));
// Since it can't have a position. Let's tell the window node the news by
// the actual rect to it.
// sending the actual rect to it.
if (wd.rect_changed_callback.is_valid()) {
wd.rect_changed_callback.call(wd.rect);
}
@@ -812,6 +873,16 @@ void DisplayServerWayland::show_window(WindowID p_window_id) {
wayland_thread.window_create_popup(p_window_id, wd.parent_id, wd.rect);
}
wd.created = true;
bool ready = wayland_thread.window_wait_ready(p_window_id, WINDOW_READY_TIMEOUT_MS);
if (!ready) {
ERR_PRINT(vformat("Could not create window %d: timeout.", p_window_id));
_send_window_event(WINDOW_EVENT_FORCE_CLOSE, p_window_id);
return;
}
// NOTE: The XDG shell protocol is built in a way that causes the window to
// be immediately shown as soon as a valid buffer is assigned to it. Hence,
// the only acceptable way of implementing window showing is to move the
@@ -873,61 +944,9 @@ void DisplayServerWayland::show_window(WindowID p_window_id) {
void DisplayServerWayland::delete_sub_window(WindowID p_window_id) {
MutexLock mutex_lock(wayland_thread.mutex);
ERR_FAIL_COND(!windows.has(p_window_id));
WindowData &wd = windows[p_window_id];
ERR_FAIL_COND_MSG(p_window_id == MAIN_WINDOW_ID, "Main window can't be deleted");
ERR_FAIL_COND(!windows.has(wd.root_id));
WindowData &root_wd = windows[wd.root_id];
// NOTE: By the time the Wayland thread will send a `WINDOW_EVENT_MOUSE_EXIT`
// the window will be gone and the message will be discarded, confusing the
// engine. We thus have to send it ourselves.
if (wayland_thread.pointer_get_pointed_window_id() == p_window_id) {
_send_window_event(WINDOW_EVENT_MOUSE_EXIT, p_window_id);
}
// The XDG shell specification requires us to clear all popups in reverse order.
while (!root_wd.popup_stack.is_empty() && root_wd.popup_stack.back()->get() != p_window_id) {
_send_window_event(WINDOW_EVENT_FORCE_CLOSE, root_wd.popup_stack.back()->get());
}
if (root_wd.popup_stack.back() && root_wd.popup_stack.back()->get() == p_window_id) {
root_wd.popup_stack.pop_back();
}
if (popup_menu_list.back() && popup_menu_list.back()->get() == p_window_id) {
popup_menu_list.pop_back();
}
#ifdef ACCESSKIT_ENABLED
if (accessibility_driver) {
accessibility_driver->window_destroy(p_window_id);
}
#endif
if (wd.visible) {
#ifdef VULKAN_ENABLED
if (rendering_device) {
rendering_device->screen_free(p_window_id);
}
if (rendering_context) {
rendering_context->window_destroy(p_window_id);
}
#endif
#ifdef GLES3_ENABLED
if (egl_manager) {
egl_manager->window_destroy(p_window_id);
}
#endif
wayland_thread.window_destroy(p_window_id);
}
windows.erase(p_window_id);
DEBUG_LOG_WAYLAND(vformat("Destroyed window %d", p_window_id));
_delete_window(p_window_id);
}
DisplayServer::WindowID DisplayServerWayland::window_get_active_popup() const {
@@ -1029,7 +1048,7 @@ void DisplayServerWayland::window_set_title(const String &p_title, DisplayServer
wd.title = p_title;
if (wd.visible) {
if (wd.created) {
wayland_thread.window_set_title(p_window_id, wd.title);
}
}
@@ -1125,7 +1144,7 @@ void DisplayServerWayland::window_set_max_size(const Size2i p_size, DisplayServe
wd.max_size = p_size;
if (wd.visible) {
if (wd.created) {
wayland_thread.window_set_max_size(p_window_id, p_size);
}
}
@@ -1160,7 +1179,7 @@ void DisplayServerWayland::window_set_transient(WindowID p_window_id, WindowID p
// NOTE: Looks like live unparenting is not really practical unfortunately.
// See WaylandThread::window_set_parent for more info.
if (wd.visible) {
if (wd.created) {
wayland_thread.window_set_parent(p_window_id, p_parent);
}
}
@@ -1186,7 +1205,7 @@ void DisplayServerWayland::window_set_min_size(const Size2i p_size, DisplayServe
wd.min_size = p_size;
if (wd.visible) {
if (wd.created) {
wayland_thread.window_set_min_size(p_window_id, p_size);
}
}
@@ -1209,7 +1228,9 @@ void DisplayServerWayland::window_set_size(const Size2i p_size, DisplayServer::W
}
Size2i new_size = p_size;
if (wd.visible) {
new_size = p_size.maxi(1);
if (wd.created) {
new_size = wayland_thread.window_set_size(p_window_id, p_size);
}
@@ -1248,7 +1269,7 @@ void DisplayServerWayland::window_set_mode(WindowMode p_mode, DisplayServer::Win
ERR_FAIL_COND(!windows.has(p_window_id));
WindowData &wd = windows[p_window_id];
if (!wd.visible) {
if (!wd.created) {
return;
}
@@ -1261,7 +1282,7 @@ DisplayServer::WindowMode DisplayServerWayland::window_get_mode(DisplayServer::W
ERR_FAIL_COND_V(!windows.has(p_window_id), WINDOW_MODE_WINDOWED);
const WindowData &wd = windows[p_window_id];
if (!wd.visible) {
if (!wd.created) {
return WINDOW_MODE_WINDOWED;
}
@@ -1289,12 +1310,12 @@ void DisplayServerWayland::window_set_flag(WindowFlags p_flag, bool p_enabled, D
case WINDOW_FLAG_POPUP: {
ERR_FAIL_COND_MSG(p_window_id == MAIN_WINDOW_ID, "Main window can't be popup.");
ERR_FAIL_COND_MSG(wd.visible && (wd.flags & WINDOW_FLAG_POPUP_BIT) != p_enabled, "Popup flag can't changed while window is opened.");
ERR_FAIL_COND_MSG(wd.created && (wd.flags & WINDOW_FLAG_POPUP_BIT) != p_enabled, "Popup flag can't changed while window is opened.");
} break;
case WINDOW_FLAG_POPUP_WM_HINT: {
ERR_FAIL_COND_MSG(p_window_id == MAIN_WINDOW_ID, "Main window can't have popup hint.");
ERR_FAIL_COND_MSG(wd.visible && (wd.flags & WINDOW_FLAG_POPUP_WM_HINT_BIT) != p_enabled, "Popup hint can't changed while window is opened.");
ERR_FAIL_COND_MSG(wd.created && (wd.flags & WINDOW_FLAG_POPUP_WM_HINT_BIT) != p_enabled, "Popup hint can't changed while window is opened.");
} break;
default: {
@@ -2028,6 +2049,8 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win
String session_desk = OS::get_singleton()->get_environment("XDG_SESSION_DESKTOP").to_lower();
swap_cancel_ok = (current_desk.contains("kde") || session_desk.contains("kde") || current_desk.contains("lxqt") || session_desk.contains("lxqt"));
MutexLock mutex_lock(wayland_thread.mutex);
Error thread_err = wayland_thread.init();
if (thread_err != OK) {
@@ -2247,6 +2270,12 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win
show_window(MAIN_WINDOW_ID);
if (!windows.has(MAIN_WINDOW_ID) || !windows[MAIN_WINDOW_ID].visible) {
ERR_PRINT("Could not map the main window.");
r_error = ERR_CANT_CREATE;
return;
}
#ifdef RD_ENABLED
if (rendering_context) {
rendering_device = memnew(RenderingDevice);
@@ -2298,6 +2327,8 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win
}
DisplayServerWayland::~DisplayServerWayland() {
wayland_thread.mutex.lock();
if (native_menu) {
memdelete(native_menu);
native_menu = nullptr;
@@ -2321,10 +2352,13 @@ DisplayServerWayland::~DisplayServerWayland() {
}
for (WindowID &id : toplevels) {
delete_sub_window(id);
_delete_window(id);
}
windows.clear();
// The thread needs the mutex to clean up. We're not going to touch Wayland
// stuff anymore from now on anyways.
wayland_thread.mutex.unlock();
wayland_thread.destroy();
// Destroy all drivers.
@@ -92,6 +92,9 @@ class DisplayServerWayland : public DisplayServer {
struct wl_egl_window *wl_egl_window = nullptr;
#endif
// Whether a `WaylandThread` equivalent exists or not.
bool created = false;
// Flags whether we have allocated a buffer through the video drivers.
bool visible = false;
@@ -181,6 +184,7 @@ class DisplayServerWayland : public DisplayServer {
static void dispatch_input_events(const Ref<InputEvent> &p_event);
void _dispatch_input_event(const Ref<InputEvent> &p_event);
void _delete_window(WindowID p_window_id = MAIN_WINDOW_ID);
void _update_window_rect(const Rect2i &p_rect, WindowID p_window_id = MAIN_WINDOW_ID);
void try_suspend();
+137 -90
View File
@@ -197,6 +197,21 @@ Vector<uint8_t> WaylandThread::_wp_primary_selection_offer_read(struct wl_displa
return Vector<uint8_t>();
}
void WaylandThread::_wl_display_check_error(struct wl_display *wl_display) {
int werror = wl_display_get_error(wl_display);
if (werror) {
if (werror == EPROTO) {
struct wl_interface *wl_interface = nullptr;
uint32_t id = 0;
int error_code = wl_display_get_protocol_error(wl_display, (const struct wl_interface **)&wl_interface, &id);
CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id));
} else {
CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror));
}
}
}
Ref<InputEventKey> WaylandThread::_seat_state_get_key_event(SeatState *p_ss, xkb_keycode_t p_keycode, bool p_pressed) {
Ref<InputEventKey> event;
@@ -1342,6 +1357,8 @@ void WaylandThread::_xdg_surface_on_configure(void *data, struct xdg_surface *xd
WindowState *ws = (WindowState *)data;
ERR_FAIL_NULL(ws);
ws->ready = true;
DEBUG_LOG_WAYLAND_THREAD(vformat("xdg surface on configure rect %s", ws->rect));
}
@@ -1349,6 +1366,14 @@ void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel *
WindowState *ws = (WindowState *)data;
ERR_FAIL_NULL(ws);
if (width == 0) {
width = ws->rect.size.width;
}
if (height == 0) {
height = ws->rect.size.height;
}
// Expect the window to be in a plain state. It will get properly set if the
// compositor reports otherwise below.
ws->mode = DisplayServer::WINDOW_MODE_WINDOWED;
@@ -1404,9 +1429,7 @@ void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel *
}
}
if (width != 0 && height != 0) {
window_state_update_size(ws, width, height);
}
window_state_update_size(ws, width, height);
DEBUG_LOG_WAYLAND_THREAD(vformat("XDG toplevel on configure width %d height %d.", width, height));
}
@@ -1457,10 +1480,16 @@ void WaylandThread::_xdg_popup_on_configure(void *data, struct xdg_popup *xdg_po
WindowState *ws = (WindowState *)data;
ERR_FAIL_NULL(ws);
if (width != 0 && height != 0) {
window_state_update_size(ws, width, height);
if (width == 0) {
width = ws->rect.size.width;
}
if (height == 0) {
height = ws->rect.size.width;
}
window_state_update_size(ws, width, height);
WindowState *parent = ws->wayland_thread->window_get_state(ws->parent_id);
ERR_FAIL_NULL(parent);
@@ -1552,6 +1581,8 @@ void WaylandThread::libdecor_frame_on_configure(struct libdecor_frame *frame, st
WindowState *ws = (WindowState *)user_data;
ERR_FAIL_NULL(ws);
ws->ready = true;
int width = 0;
int height = 0;
@@ -3342,19 +3373,7 @@ void WaylandThread::_poll_events_thread(void *p_data) {
}
}
int werror = wl_display_get_error(data->wl_display);
if (werror) {
if (werror == EPROTO) {
struct wl_interface *wl_interface = nullptr;
uint32_t id = 0;
int error_code = wl_display_get_protocol_error(data->wl_display, (const struct wl_interface **)&wl_interface, &id);
CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id));
} else {
CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror));
}
}
_wl_display_check_error(data->wl_display);
wl_display_flush(data->wl_display);
@@ -3526,6 +3545,10 @@ double WaylandThread::window_state_get_scale_factor(const WindowState *p_ws) {
void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int p_height) {
ERR_FAIL_NULL(p_ws);
// Failsafe.
p_width = MAX(p_width, 1);
p_height = MAX(p_height, 1);
int preferred_buffer_scale = window_state_get_preferred_buffer_scale(p_ws);
bool using_fractional = p_ws->preferred_fractional_scale > 0;
@@ -3824,7 +3847,7 @@ void WaylandThread::window_create(DisplayServer::WindowID p_window_id, const Siz
ws.registry = &registry;
ws.wayland_thread = this;
ws.rect.size = p_size;
ws.rect.size = p_size.maxi(1);
ws.wl_surface = wl_compositor_create_surface(registry.wl_compositor);
wl_proxy_tag_godot((struct wl_proxy *)ws.wl_surface);
@@ -3898,11 +3921,6 @@ void WaylandThread::window_create(DisplayServer::WindowID p_window_id, const Siz
}
wl_surface_commit(ws.wl_surface);
// Wait for the surface to be configured before continuing.
wl_display_roundtrip(wl_display);
window_state_update_size(&ws, ws.rect.size.width, ws.rect.size.height);
}
void WaylandThread::window_create_popup(DisplayServer::WindowID p_window_id, DisplayServer::WindowID p_parent_id, Rect2i p_rect) {
@@ -3916,7 +3934,6 @@ void WaylandThread::window_create_popup(DisplayServer::WindowID p_window_id, Dis
p_rect.position = scale_vector2i(p_rect.position, 1.0 / parent_scale);
p_rect.size = scale_vector2i(p_rect.size, 1.0 / parent_scale);
// We manually scaled based on the parent. If we don't set the relevant fields,
// the resizing routines will get confused and scale once more.
ws.preferred_fractional_scale = parent.preferred_fractional_scale;
@@ -3987,9 +4004,6 @@ void WaylandThread::window_create_popup(DisplayServer::WindowID p_window_id, Dis
wl_callback_add_listener(ws.frame_callback, &frame_wl_callback_listener, &ws);
wl_surface_commit(ws.wl_surface);
// Wait for the surface to be configured before continuing.
wl_display_roundtrip(wl_display);
}
void WaylandThread::window_destroy(DisplayServer::WindowID p_window_id) {
@@ -4108,7 +4122,7 @@ Size2i WaylandThread::window_set_size(DisplayServer::WindowID p_window_id, const
window_state_update_size(&ws, new_size.width, new_size.height);
return scale_vector2i(new_size, window_scale);
return scale_vector2i(new_size, window_scale).maxi(1);
}
void WaylandThread::beep() const {
@@ -4374,8 +4388,8 @@ void WaylandThread::window_try_set_mode(DisplayServer::WindowID p_window_id, Dis
#endif // LIBDECOR_ENABLED
} break;
}
// Wait for a configure event and hope that something changed.
// Roundtrip and hope that something changed.
// TODO: Async?
wl_display_roundtrip(wl_display);
if (ws.mode != DisplayServer::WINDOW_MODE_WINDOWED) {
@@ -4841,6 +4855,7 @@ Error WaylandThread::init() {
wl_registry_add_listener(wl_registry, &wl_registry_listener, &registry);
// Wait for registry to get notified from the compositor.
// TODO: Async?
wl_display_roundtrip(wl_display);
ERR_FAIL_NULL_V_MSG(registry.wl_shm, ERR_UNAVAILABLE, "Can't obtain the Wayland shared memory global.");
@@ -4877,6 +4892,7 @@ Error WaylandThread::init() {
}
// Wait for seat capabilities.
// TODO: Async?
wl_display_roundtrip(wl_display);
#ifdef LIBDECOR_ENABLED
@@ -5299,6 +5315,41 @@ bool WaylandThread::get_reset_frame() {
return old_frame;
}
// Wraps around `wl_display_dispatch_pending`. Judging from the docs, this
// should work pretty much like `wl_display_dispatch_timeout`, which is only
// available on somewhat recent versions of libwayland.
//
// This implementation is NOT based on libwayland's code.
int WaylandThread::wait_events(int p_timeout_ms) {
struct pollfd poll_fd;
poll_fd.fd = wl_display_get_fd(wl_display);
poll_fd.events = POLLIN | POLLHUP;
while (wl_display_prepare_read(wl_display) != 0) {
// Event queue got already something.
return wl_display_dispatch_pending(wl_display);
}
wl_display_flush(wl_display);
// Wait for the event file descriptor to have new data.
poll(&poll_fd, 1, p_timeout_ms);
if (poll_fd.revents | POLLIN) {
// Load the queues with fresh new data.
wl_display_read_events(wl_display);
} else {
// Oh well... Stop signaling that we want to read.
wl_display_cancel_read(wl_display);
// We've got no new events :(
return 0;
}
// Let's try dispatching now...
return wl_display_dispatch_pending(wl_display);
}
// Dispatches events until a frame event is received, a window is reported as
// suspended or the timeout expires.
bool WaylandThread::wait_frame_suspend_ms(int p_timeout) {
@@ -5307,9 +5358,21 @@ bool WaylandThread::wait_frame_suspend_ms(int p_timeout) {
// `wl_display_prepare_read` and `wl_display_read`. This means, that it will
// basically be guaranteed to stay stuck in a "prepare read" state, where it
// will block any other attempt at reading the display fd, such as ours. The
// solution? Let's make sure the mutex is locked (it should) and unblock the
// main thread with a roundtrip!
// solution? Let's make sure the mutex is locked (it should)...
MutexLock mutex_lock(mutex);
if (is_suspended()) {
return false;
}
if (frame) {
frame = false;
return true;
}
ERR_FAIL_COND_V(Thread::get_caller_id() == events_thread.get_id(), false);
// ...and unblock the main thread with a roundtrip!
wl_display_roundtrip(wl_display);
if (is_suspended()) {
@@ -5325,65 +5388,12 @@ bool WaylandThread::wait_frame_suspend_ms(int p_timeout) {
return true;
}
struct pollfd poll_fd;
poll_fd.fd = wl_display_get_fd(wl_display);
poll_fd.events = POLLIN | POLLHUP;
for (int remaining_ms = p_timeout; remaining_ms > 0;) {
int begin_ms = OS::get_singleton()->get_ticks_msec();
int begin_ms = OS::get_singleton()->get_ticks_msec();
int remaining_ms = p_timeout;
wait_events(remaining_ms);
while (remaining_ms > 0) {
// Empty the event queue while it's full.
while (wl_display_prepare_read(wl_display) != 0) {
if (wl_display_dispatch_pending(wl_display) == -1) {
// Oh no. We'll check and handle any display error below.
break;
}
if (is_suspended()) {
return false;
}
if (frame) {
// We had a frame event in the queue :D
frame = false;
return true;
}
}
int werror = wl_display_get_error(wl_display);
if (werror) {
if (werror == EPROTO) {
struct wl_interface *wl_interface = nullptr;
uint32_t id = 0;
int error_code = wl_display_get_protocol_error(wl_display, (const struct wl_interface **)&wl_interface, &id);
CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id));
} else {
CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror));
}
}
wl_display_flush(wl_display);
// Wait for the event file descriptor to have new data.
poll(&poll_fd, 1, remaining_ms);
if (poll_fd.revents | POLLIN) {
// Load the queues with fresh new data.
wl_display_read_events(wl_display);
} else {
// Oh well... Stop signaling that we want to read.
wl_display_cancel_read(wl_display);
// We've got no new events :(
// We won't even bother with checking the frame flag.
return false;
}
// Let's try dispatching now...
wl_display_dispatch_pending(wl_display);
_wl_display_check_error(wl_display);
if (is_suspended()) {
return false;
@@ -5425,6 +5435,43 @@ bool WaylandThread::is_suspended() const {
return true;
}
bool WaylandThread::window_wait_ready(DisplayServer::WindowID p_window_id, int p_timeout_ms) {
MutexLock mutex_lock(mutex);
WindowState *ws = windows.getptr(p_window_id);
ERR_FAIL_NULL_V(ws, false);
if (ws->ready) {
return true;
}
// See _poll_events_thread.
ERR_FAIL_COND_V(Thread::get_caller_id() == events_thread.get_id(), ws->ready);
// Unlock the thread.
wl_display_roundtrip(wl_display);
if (ws->ready) {
return true;
}
for (int remaining_ms = p_timeout_ms; remaining_ms > 0;) {
int begin_ms = OS::get_singleton()->get_ticks_msec();
wait_events(remaining_ms);
_wl_display_check_error(wl_display);
if (ws->ready) {
return true;
}
remaining_ms -= OS::get_singleton()->get_ticks_msec() - begin_ms;
}
return false;
}
struct godot_embedding_compositor *WaylandThread::get_embedding_compositor() {
return registry.godot_embedding_compositor;
}
@@ -248,6 +248,8 @@ public:
Rect2i rect;
DisplayServer::WindowMode mode = DisplayServer::WINDOW_MODE_WINDOWED;
bool ready = false; // Is configured or otherwise ready to be mapped.
// Toplevel states.
bool maximized = false; // MUST obey configure size.
bool fullscreen = false; // Can be smaller than configure size.
@@ -1049,6 +1051,9 @@ private:
static Vector<uint8_t> _wl_data_offer_read(struct wl_display *wl_display, const char *p_mime, struct wl_data_offer *wl_data_offer);
static Vector<uint8_t> _wp_primary_selection_offer_read(struct wl_display *wl_display, const char *p_mime, struct zwp_primary_selection_offer_v1 *wp_primary_selection_offer);
// Crashes if there's an error in the display and crashes if so.
static void _wl_display_check_error(struct wl_display *wl_display);
static void _seat_state_set_current(WaylandThread::SeatState &p_ss);
static Ref<InputEventKey> _seat_state_get_key_event(SeatState *p_ss, xkb_keycode_t p_keycode, bool p_pressed);
static Ref<InputEventKey> _seat_state_get_unstuck_key_event(SeatState *p_ss, xkb_keycode_t p_keycode, bool p_pressed, Key p_key);
@@ -1183,6 +1188,9 @@ public:
void commit_surfaces();
// Returns handled events.
int wait_events(int p_timeout = -1);
void set_frame();
bool get_reset_frame();
bool wait_frame_suspend_ms(int p_timeout);
@@ -1192,6 +1200,8 @@ public:
bool window_is_suspended(DisplayServer::WindowID p_window_id) const;
bool is_suspended() const;
bool window_wait_ready(DisplayServer::WindowID p_window_id, int p_timeout_ms);
struct godot_embedding_compositor *get_embedding_compositor();
OS::ProcessID embedded_compositor_get_focused_pid();