mirror of
https://github.com/godotengine/godot.git
synced 2026-05-12 22:35:35 +00:00
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:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 = ®istry;
|
||||
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, ®istry);
|
||||
|
||||
// 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();
|
||||
|
||||
Reference in New Issue
Block a user