From dcb058a1edf6a2d29532b4fb05e9b216b518de6e Mon Sep 17 00:00:00 2001 From: Hilderin <81109165+Hilderin@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:57:23 -0400 Subject: [PATCH] Embedding game process in editor --- core/config/engine.cpp | 8 + core/config/engine.h | 3 + core/core_bind.cpp | 5 + core/core_bind.h | 2 + core/os/os.cpp | 2 + core/os/os.h | 1 + doc/classes/DisplayServer.xml | 3 + doc/classes/Engine.xml | 6 + editor/editor_node.cpp | 8 +- editor/editor_node.h | 4 + editor/editor_run.cpp | 7 + editor/editor_run.h | 5 + editor/gui/editor_run_bar.cpp | 4 + editor/gui/editor_run_bar.h | 1 + editor/gui/editor_scene_tabs.cpp | 10 + editor/icons/KeepAspect.svg | 1 + editor/plugins/embedded_process.cpp | 331 ++++++++++++++++ editor/plugins/embedded_process.h | 90 +++++ editor/plugins/game_view_plugin.cpp | 358 ++++++++++++++++- editor/plugins/game_view_plugin.h | 59 ++- editor/window_wrapper.cpp | 27 +- editor/window_wrapper.h | 2 + main/main.cpp | 50 ++- platform/android/display_server_android.cpp | 6 +- platform/android/display_server_android.h | 4 +- platform/ios/display_server_ios.h | 4 +- platform/ios/display_server_ios.mm | 6 +- .../wayland/display_server_wayland.cpp | 6 +- .../linuxbsd/wayland/display_server_wayland.h | 4 +- platform/linuxbsd/x11/display_server_x11.cpp | 372 ++++++++++++++++-- platform/linuxbsd/x11/display_server_x11.h | 24 +- platform/macos/display_server_macos.h | 4 +- platform/macos/display_server_macos.mm | 6 +- platform/web/display_server_web.cpp | 6 +- platform/web/display_server_web.h | 4 +- platform/windows/display_server_windows.cpp | 315 +++++++++++++-- platform/windows/display_server_windows.h | 23 +- scene/main/node.h | 1 + scene/main/window.cpp | 23 +- servers/display_server.cpp | 20 +- servers/display_server.h | 9 +- servers/display_server_headless.h | 2 +- tests/display_server_mock.h | 2 +- tests/test_main.cpp | 2 +- 44 files changed, 1697 insertions(+), 133 deletions(-) create mode 100644 editor/icons/KeepAspect.svg create mode 100644 editor/plugins/embedded_process.cpp create mode 100644 editor/plugins/embedded_process.h diff --git a/core/config/engine.cpp b/core/config/engine.cpp index aac048e93f7e..f4c6a459b7be 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -412,6 +412,14 @@ void Engine::set_freeze_time_scale(bool p_frozen) { freeze_time_scale = p_frozen; } +void Engine::set_embedded_in_editor(bool p_enabled) { + embedded_in_editor = p_enabled; +} + +bool Engine::is_embedded_in_editor() const { + return embedded_in_editor; +} + Engine::Engine() { singleton = this; } diff --git a/core/config/engine.h b/core/config/engine.h index b38412308ae7..7ecbc25cd0c6 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -87,6 +87,7 @@ class Engine { bool editor_hint = false; bool project_manager_hint = false; bool extension_reloading = false; + bool embedded_in_editor = false; bool _print_header = true; @@ -201,6 +202,8 @@ class Engine { bool notify_frame_server_synced(); void set_freeze_time_scale(bool p_frozen); + void set_embedded_in_editor(bool p_enabled); + bool is_embedded_in_editor() const; Engine(); virtual ~Engine(); diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 925551d933fe..f2704b26ea05 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -1897,6 +1897,10 @@ bool Engine::is_editor_hint() const { return ::Engine::get_singleton()->is_editor_hint(); } +bool Engine::is_embedded_in_editor() const { + return ::Engine::get_singleton()->is_embedded_in_editor(); +} + String Engine::get_write_movie_path() const { return ::Engine::get_singleton()->get_write_movie_path(); } @@ -1974,6 +1978,7 @@ void Engine::_bind_methods() { ClassDB::bind_method(D_METHOD("get_script_language", "index"), &Engine::get_script_language); ClassDB::bind_method(D_METHOD("is_editor_hint"), &Engine::is_editor_hint); + ClassDB::bind_method(D_METHOD("is_embedded_in_editor"), &Engine::is_embedded_in_editor); ClassDB::bind_method(D_METHOD("get_write_movie_path"), &Engine::get_write_movie_path); diff --git a/core/core_bind.h b/core/core_bind.h index d013e348bd87..96f693b7e42c 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -584,6 +584,8 @@ class Engine : public Object { void set_editor_hint(bool p_enabled); bool is_editor_hint() const; + bool is_embedded_in_editor() const; + // `set_write_movie_path()` is not exposed to the scripting API as changing it at run-time has no effect. String get_write_movie_path() const; diff --git a/core/os/os.cpp b/core/os/os.cpp index 59a0579ce36b..562e8cfba592 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -405,6 +405,8 @@ bool OS::has_feature(const String &p_feature) { return _in_editor; } else if (p_feature == "editor_runtime") { return !_in_editor; + } else if (p_feature == "embedded_in_editor") { + return _embedded_in_editor; } #else if (p_feature == "template") { diff --git a/core/os/os.h b/core/os/os.h index ffdb905abaf7..1ef5f7ab9a4b 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -63,6 +63,7 @@ class OS { bool _stderr_enabled = true; bool _writing_movie = false; bool _in_editor = false; + bool _embedded_in_editor = false; CompositeLogger *_logger = nullptr; diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index dafa86d42e01..0e7c4e3378d0 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -1895,6 +1895,9 @@ The display server supports all features of [constant FEATURE_NATIVE_DIALOG_FILE], with the added functionality of Options and native dialog file access to [code]res://[/code] and [code]user://[/code] paths. See [method file_dialog_show] and [method file_dialog_with_options_show]. [b]Windows, macOS, Linux (X11/Wayland)[/b] + + Display server supports embedding a window from another process. [b]Windows, Linux (X11)[/b] + Makes the mouse cursor visible if it is hidden. diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml index bba515705384..8f4bad60e1f5 100644 --- a/doc/classes/Engine.xml +++ b/doc/classes/Engine.xml @@ -254,6 +254,12 @@ [b]Note:[/b] To detect whether the script is running on an editor [i]build[/i] (such as when pressing [kbd]F5[/kbd]), use [method OS.has_feature] with the [code]"editor"[/code] argument instead. [code]OS.has_feature("editor")[/code] evaluate to [code]true[/code] both when the script is running in the editor and when running the project from the editor, but returns [code]false[/code] when run from an exported project. + + + + Returns [code]true[/code] if the engine is running embedded in the editor. This is useful to prevent attempting to update window mode or window flags that are not supported when running the project embedded in the editor. + + diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 87f1f1b8a021..43b292256935 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -755,7 +755,9 @@ void EditorNode::_notification(int p_what) { } // Set a low FPS cap to decrease CPU/GPU usage while the editor is unfocused. - OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(int(EDITOR_GET("interface/editor/unfocused_low_processor_mode_sleep_usec"))); + if (unfocused_low_processor_usage_mode_enabled) { + OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(int(EDITOR_GET("interface/editor/unfocused_low_processor_mode_sleep_usec"))); + } } break; case NOTIFICATION_WM_ABOUT: { @@ -6713,6 +6715,10 @@ int EditorNode::execute_and_show_output(const String &p_title, const String &p_p return eta.exitcode; } +void EditorNode::set_unfocused_low_processor_usage_mode_enabled(bool p_enabled) { + unfocused_low_processor_usage_mode_enabled = p_enabled; +} + EditorNode::EditorNode() { DEV_ASSERT(!singleton); singleton = this; diff --git a/editor/editor_node.h b/editor/editor_node.h index 4a283983c8d2..a9b43ac10dc6 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -474,6 +474,8 @@ class EditorNode : public Node { bool was_window_windowed_last = false; + bool unfocused_low_processor_usage_mode_enabled = true; + static EditorBuildCallback build_callbacks[MAX_BUILD_CALLBACKS]; static EditorPluginInitializeCallback plugin_init_callbacks[MAX_INIT_CALLBACKS]; static int build_callback_count; @@ -788,6 +790,8 @@ class EditorNode : public Node { HashMap get_modified_properties_for_node(Node *p_node, bool p_node_references_only); HashMap get_modified_properties_reference_to_nodes(Node *p_node, List &p_nodes_referenced_by); + void set_unfocused_low_processor_usage_mode_enabled(bool p_enabled); + struct AdditiveNodeEntry { Node *node = nullptr; NodePath parent; diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp index caed02ae58ef..2f1efb3ee363 100644 --- a/editor/editor_run.cpp +++ b/editor/editor_run.cpp @@ -229,6 +229,9 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie) { List instance_args(args); RunInstancesDialog::get_singleton()->get_argument_list_for_instance(i, instance_args); RunInstancesDialog::get_singleton()->apply_custom_features(i); + if (instance_starting_callback) { + instance_starting_callback(i, instance_args); + } if (OS::get_singleton()->is_stdout_verbose()) { print_line(vformat("Running: %s", exec)); @@ -281,6 +284,10 @@ void EditorRun::stop() { running_scene = ""; } +OS::ProcessID EditorRun::get_current_process() const { + return pids.front()->get(); +} + EditorRun::EditorRun() { status = STATUS_STOP; running_scene = ""; diff --git a/editor/editor_run.h b/editor/editor_run.h index bd6770ae3dbb..387d15246b63 100644 --- a/editor/editor_run.h +++ b/editor/editor_run.h @@ -33,6 +33,8 @@ #include "core/os/os.h" +typedef void (*EditorRunInstanceStarting)(int p_index, List &r_arguments); + class EditorRun { public: enum Status { @@ -48,6 +50,8 @@ class EditorRun { String running_scene; public: + inline static EditorRunInstanceStarting instance_starting_callback = nullptr; + Status get_status() const; String get_running_scene() const; @@ -58,6 +62,7 @@ class EditorRun { void stop_child_process(OS::ProcessID p_pid); bool has_child_process(OS::ProcessID p_pid) const; int get_child_process_count() const { return pids.size(); } + OS::ProcessID get_current_process() const; EditorRun(); }; diff --git a/editor/gui/editor_run_bar.cpp b/editor/gui/editor_run_bar.cpp index 64135c8d50ed..26127ed099c9 100644 --- a/editor/gui/editor_run_bar.cpp +++ b/editor/gui/editor_run_bar.cpp @@ -344,6 +344,10 @@ void EditorRunBar::stop_child_process(OS::ProcessID p_pid) { } } +OS::ProcessID EditorRunBar::get_current_process() const { + return editor_run.get_current_process(); +} + void EditorRunBar::set_movie_maker_enabled(bool p_enabled) { write_movie_button->set_pressed(p_enabled); } diff --git a/editor/gui/editor_run_bar.h b/editor/gui/editor_run_bar.h index d8238aa2c541..d342cfa96477 100644 --- a/editor/gui/editor_run_bar.h +++ b/editor/gui/editor_run_bar.h @@ -102,6 +102,7 @@ class EditorRunBar : public MarginContainer { OS::ProcessID has_child_process(OS::ProcessID p_pid) const; void stop_child_process(OS::ProcessID p_pid); + OS::ProcessID get_current_process() const; void set_movie_maker_enabled(bool p_enabled); bool is_movie_maker_enabled() const; diff --git a/editor/gui/editor_scene_tabs.cpp b/editor/gui/editor_scene_tabs.cpp index 5b42afdbe8b9..ea95b3c9fe85 100644 --- a/editor/gui/editor_scene_tabs.cpp +++ b/editor/gui/editor_scene_tabs.cpp @@ -30,11 +30,13 @@ #include "editor_scene_tabs.h" +#include "editor/editor_main_screen.h" #include "editor/editor_node.h" #include "editor/editor_resource_preview.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/gui/editor_run_bar.h" #include "editor/inspector_dock.h" #include "editor/themes/editor_scale.h" #include "scene/gui/box_container.h" @@ -90,6 +92,14 @@ void EditorSceneTabs::_scene_tab_hovered(int p_tab) { if (!bool(EDITOR_GET("interface/scene_tabs/show_thumbnail_on_hover"))) { return; } + + // Currently the tab previews are displayed under the running game process when embed. + // Right now, the easiest technique to fix that is to prevent displaying the tab preview + // when the user is in the Game View. + if (EditorNode::get_singleton()->get_editor_main_screen()->get_selected_index() == EditorMainScreen::EDITOR_GAME && EditorRunBar::get_singleton()->is_playing()) { + return; + } + int current_tab = scene_tabs->get_current_tab(); if (p_tab == current_tab || p_tab < 0) { diff --git a/editor/icons/KeepAspect.svg b/editor/icons/KeepAspect.svg new file mode 100644 index 000000000000..1cc831563fcb --- /dev/null +++ b/editor/icons/KeepAspect.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/plugins/embedded_process.cpp b/editor/plugins/embedded_process.cpp new file mode 100644 index 000000000000..1926e5beb378 --- /dev/null +++ b/editor/plugins/embedded_process.cpp @@ -0,0 +1,331 @@ +/**************************************************************************/ +/* embedded_process.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 "embedded_process.h" + +#include "editor/editor_string_names.h" +#include "scene/main/window.h" +#include "scene/resources/style_box_flat.h" +#include "scene/theme/theme_db.h" + +void EmbeddedProcess::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + window = get_window(); + } break; + case NOTIFICATION_PROCESS: { + _check_focused_process_id(); + _check_mouse_over(); + + // We need to detect when the control globally changes location or size on the screen. + // NOTIFICATION_RESIZED and NOTIFICATION_WM_POSITION_CHANGED are not enough to detect + // resized parent to siblings controls that can affect global position. + Rect2i new_global_rect = get_global_rect(); + if (last_global_rect != new_global_rect) { + last_global_rect = new_global_rect; + _queue_update_embedded_process(); + } + + } break; + case NOTIFICATION_DRAW: { + _draw(); + } break; + case NOTIFICATION_RESIZED: + case NOTIFICATION_VISIBILITY_CHANGED: + case NOTIFICATION_WM_POSITION_CHANGED: { + _queue_update_embedded_process(); + } break; + case NOTIFICATION_THEME_CHANGED: { + focus_style_box = get_theme_stylebox(SNAME("FocusViewport"), EditorStringName(EditorStyles)); + Ref focus_style_box_flat = focus_style_box; + if (focus_style_box_flat.is_valid()) { + margin_top_left = Point2i(focus_style_box_flat->get_corner_radius(CORNER_TOP_LEFT), focus_style_box_flat->get_corner_radius(CORNER_TOP_LEFT)); + margin_bottom_right = Point2i(focus_style_box_flat->get_corner_radius(CORNER_BOTTOM_RIGHT), focus_style_box_flat->get_corner_radius(CORNER_BOTTOM_RIGHT)); + } else if (focus_style_box.is_valid()) { + margin_top_left = Point2i(focus_style_box->get_margin(SIDE_LEFT), focus_style_box->get_margin(SIDE_TOP)); + margin_bottom_right = Point2i(focus_style_box->get_margin(SIDE_RIGHT), focus_style_box->get_margin(SIDE_BOTTOM)); + } else { + margin_top_left = Point2i(); + margin_bottom_right = Point2i(); + } + } break; + case NOTIFICATION_FOCUS_ENTER: { + _queue_update_embedded_process(); + } break; + case NOTIFICATION_APPLICATION_FOCUS_IN: { + application_has_focus = true; + if (embedded_process_was_focused) { + embedded_process_was_focused = false; + // Refocus the embedded process if it was focused when the application lost focus, + // but do not refocus if the embedded process is currently focused (indicating it just lost focus) + // or if the current window is a different popup or secondary window. + if (embedding_completed && current_process_id != focused_process_id && window && window->has_focus()) { + grab_focus(); + _queue_update_embedded_process(); + } + } + } break; + case NOTIFICATION_APPLICATION_FOCUS_OUT: { + application_has_focus = false; + embedded_process_was_focused = embedding_completed && current_process_id == focused_process_id; + } break; + } +} + +void EmbeddedProcess::set_window_size(const Size2i p_window_size) { + if (window_size != p_window_size) { + window_size = p_window_size; + _queue_update_embedded_process(); + } +} + +void EmbeddedProcess::set_keep_aspect(bool p_keep_aspect) { + if (keep_aspect != p_keep_aspect) { + keep_aspect = p_keep_aspect; + _queue_update_embedded_process(); + } +} + +Rect2i EmbeddedProcess::_get_global_embedded_window_rect() { + Rect2i control_rect = get_global_rect(); + control_rect = Rect2i(control_rect.position, control_rect.size.maxi(1)); + if (keep_aspect) { + Rect2i desired_rect = control_rect; + float ratio = MIN((float)control_rect.size.x / window_size.x, (float)control_rect.size.y / window_size.y); + desired_rect.size = Size2i(window_size.x * ratio, window_size.y * ratio).maxi(1); + desired_rect.position = Size2i(control_rect.position.x + ((control_rect.size.x - desired_rect.size.x) / 2), control_rect.position.y + ((control_rect.size.y - desired_rect.size.y) / 2)); + return desired_rect; + } else { + return control_rect; + } +} + +Rect2i EmbeddedProcess::get_screen_embedded_window_rect() { + Rect2i rect = _get_global_embedded_window_rect(); + if (window) { + rect.position += window->get_position(); + } + + // Removing margins to make space for the focus border style. + return Rect2i(rect.position.x + margin_top_left.x, rect.position.y + margin_top_left.y, MAX(rect.size.x - (margin_top_left.x + margin_bottom_right.x), 1), MAX(rect.size.y - (margin_top_left.y + margin_bottom_right.y), 1)); +} + +bool EmbeddedProcess::is_embedding_in_progress() { + return !timer_embedding->is_stopped(); +} + +bool EmbeddedProcess::is_embedding_completed() { + return embedding_completed; +} + +void EmbeddedProcess::embed_process(OS::ProcessID p_pid) { + if (!window) { + return; + } + + ERR_FAIL_COND_MSG(!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING), "Embedded process not supported by this display server."); + + if (current_process_id != 0) { + // Stop embedding the last process. + OS::get_singleton()->kill(current_process_id); + } + + reset(); + + current_process_id = p_pid; + start_embedding_time = OS::get_singleton()->get_ticks_msec(); + embedding_grab_focus = has_focus(); + set_process(true); + set_notify_transform(true); + + // Attempt to embed the process, but if it has just started and the window is not ready yet, + // we will retry in this case. + _try_embed_process(); +} + +void EmbeddedProcess::reset() { + if (current_process_id != 0 && embedding_completed) { + DisplayServer::get_singleton()->remove_embedded_process(current_process_id); + } + current_process_id = 0; + embedding_completed = false; + start_embedding_time = 0; + embedding_grab_focus = false; + timer_embedding->stop(); + set_process(false); + set_notify_transform(false); + queue_redraw(); +} + +void EmbeddedProcess::_try_embed_process() { + Error err = DisplayServer::get_singleton()->embed_process(window->get_window_id(), current_process_id, get_screen_embedded_window_rect(), is_visible_in_tree(), embedding_grab_focus); + if (err == OK) { + embedding_completed = true; + queue_redraw(); + emit_signal(SNAME("embedding_completed")); + } else if (err == ERR_DOES_NOT_EXIST) { + if (OS::get_singleton()->get_ticks_msec() - start_embedding_time >= (uint64_t)embedding_timeout) { + // Embedding process timed out. + reset(); + emit_signal(SNAME("embedding_failed")); + } else { + // Tries another shot. + timer_embedding->start(); + } + } else { + // Another unknown error. + reset(); + emit_signal(SNAME("embedding_failed")); + } +} + +bool EmbeddedProcess::_is_embedded_process_updatable() { + return window && current_process_id != 0 && embedding_completed; +} + +void EmbeddedProcess::_queue_update_embedded_process() { + if (updated_embedded_process_queued || !_is_embedded_process_updatable()) { + return; + } + + updated_embedded_process_queued = true; + + callable_mp(this, &EmbeddedProcess::_update_embedded_process).call_deferred(); +} + +void EmbeddedProcess::_update_embedded_process() { + updated_embedded_process_queued = false; + + if (!_is_embedded_process_updatable()) { + return; + } + + bool must_grab_focus = false; + bool focus = has_focus(); + if (last_updated_embedded_process_focused != focus) { + if (focus) { + must_grab_focus = true; + } + last_updated_embedded_process_focused = focus; + } + + DisplayServer::get_singleton()->embed_process(window->get_window_id(), current_process_id, get_screen_embedded_window_rect(), is_visible_in_tree(), must_grab_focus); + emit_signal(SNAME("embedded_process_updated")); +} + +void EmbeddedProcess::_timer_embedding_timeout() { + _try_embed_process(); +} + +void EmbeddedProcess::_draw() { + if (focused_process_id == current_process_id && has_focus() && focus_style_box.is_valid()) { + Size2 size = get_size(); + Rect2 r = Rect2(Point2(), size); + focus_style_box->draw(get_canvas_item(), r); + } +} + +void EmbeddedProcess::_check_mouse_over() { + // This method checks if the mouse is over the embedded process while the current application is focused. + // The goal is to give focus to the embedded process as soon as the mouse hovers over it, + // allowing the user to interact with it immediately without needing to click first. + if (!is_visible_in_tree() || !embedding_completed || !application_has_focus || !window || !window->has_focus() || Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) || Input::get_singleton()->is_mouse_button_pressed(MouseButton::RIGHT)) { + return; + } + + bool focused = has_focus(); + + // Not stealing focus from a textfield. + if (!focused && get_viewport()->gui_get_focus_owner() && get_viewport()->gui_get_focus_owner()->is_text_field()) { + return; + } + + Vector2 mouse_position = DisplayServer::get_singleton()->mouse_get_position(); + Rect2i window_rect = get_screen_embedded_window_rect(); + if (!window_rect.has_point(mouse_position)) { + return; + } + + // Don't grab the focus if mouse over another window. + DisplayServer::WindowID window_id_over = DisplayServer::get_singleton()->get_window_at_screen_position(mouse_position); + if (window_id_over > 0 && window_id_over != window->get_window_id()) { + return; + } + + // When we already have the focus and the user moves the mouse over the embedded process, + // we just need to refocus the process. + if (focused) { + _queue_update_embedded_process(); + } else { + grab_focus(); + queue_redraw(); + } +} + +void EmbeddedProcess::_check_focused_process_id() { + OS::ProcessID process_id = DisplayServer::get_singleton()->get_focused_process_id(); + if (process_id != focused_process_id) { + focused_process_id = process_id; + if (focused_process_id == current_process_id) { + // The embedded process got the focus. + if (has_focus()) { + // Redraw to updated the focus style. + queue_redraw(); + } else { + grab_focus(); + } + } else if (has_focus()) { + release_focus(); + } + } +} + +void EmbeddedProcess::_bind_methods() { + ADD_SIGNAL(MethodInfo("embedding_completed")); + ADD_SIGNAL(MethodInfo("embedding_failed")); + ADD_SIGNAL(MethodInfo("embedded_process_updated")); +} + +EmbeddedProcess::EmbeddedProcess() { + timer_embedding = memnew(Timer); + timer_embedding->set_wait_time(0.1); + timer_embedding->set_one_shot(true); + add_child(timer_embedding); + timer_embedding->connect("timeout", callable_mp(this, &EmbeddedProcess::_timer_embedding_timeout)); + set_focus_mode(FOCUS_ALL); +} + +EmbeddedProcess::~EmbeddedProcess() { + if (current_process_id != 0) { + // Stop embedding the last process. + OS::get_singleton()->kill(current_process_id); + reset(); + } +} diff --git a/editor/plugins/embedded_process.h b/editor/plugins/embedded_process.h new file mode 100644 index 000000000000..d4265dc899c3 --- /dev/null +++ b/editor/plugins/embedded_process.h @@ -0,0 +1,90 @@ +/**************************************************************************/ +/* embedded_process.h */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#ifndef EMBEDDED_PROCESS_H +#define EMBEDDED_PROCESS_H + +#include "scene/gui/control.h" + +class EmbeddedProcess : public Control { + GDCLASS(EmbeddedProcess, Control); + + bool application_has_focus = true; + bool embedded_process_was_focused = false; + OS::ProcessID focused_process_id = 0; + OS::ProcessID current_process_id = 0; + bool embedding_grab_focus = false; + bool embedding_completed = false; + uint64_t start_embedding_time = 0; + bool updated_embedded_process_queued = false; + bool last_updated_embedded_process_focused = false; + + Window *window = nullptr; + Timer *timer_embedding = nullptr; + + const int embedding_timeout = 45000; + + bool keep_aspect = false; + Size2i window_size; + Ref focus_style_box; + Point2i margin_top_left; + Point2i margin_bottom_right; + Rect2i last_global_rect; + + void _try_embed_process(); + void _queue_update_embedded_process(); + void _update_embedded_process(); + void _timer_embedding_timeout(); + void _draw(); + void _check_mouse_over(); + void _check_focused_process_id(); + bool _is_embedded_process_updatable(); + Rect2i _get_global_embedded_window_rect(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + void embed_process(OS::ProcessID p_pid); + void reset(); + + void set_window_size(const Size2i p_window_size); + void set_keep_aspect(bool p_keep_aspect); + + Rect2i get_screen_embedded_window_rect(); + bool is_embedding_in_progress(); + bool is_embedding_completed(); + + EmbeddedProcess(); + ~EmbeddedProcess(); +}; + +#endif // EMBEDDED_PROCESS_H diff --git a/editor/plugins/game_view_plugin.cpp b/editor/plugins/game_view_plugin.cpp index 5c1f81ee94e3..16579fc61efa 100644 --- a/editor/plugins/game_view_plugin.cpp +++ b/editor/plugins/game_view_plugin.cpp @@ -30,12 +30,20 @@ #include "game_view_plugin.h" +#include "core/config/project_settings.h" #include "core/debugger/debugger_marshalls.h" +#include "editor/debugger/editor_debugger_node.h" +#include "editor/editor_command_palette.h" +#include "editor/editor_interface.h" #include "editor/editor_main_screen.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/gui/editor_run_bar.h" +#include "editor/plugins/embedded_process.h" #include "editor/themes/editor_scale.h" +#include "editor/window_wrapper.h" #include "scene/gui/button.h" +#include "scene/gui/label.h" #include "scene/gui/menu_button.h" #include "scene/gui/panel.h" #include "scene/gui/separator.h" @@ -184,6 +192,79 @@ void GameView::_sessions_changed() { _update_debugger_buttons(); } +void GameView::_instance_starting_static(int p_idx, List &r_arguments) { + ERR_FAIL_NULL(singleton); + singleton->_instance_starting(p_idx, r_arguments); +} + +void GameView::_instance_starting(int p_idx, List &r_arguments) { + if (p_idx == 0 && embed_on_play && make_floating_on_play && !window_wrapper->get_window_enabled() && EditorNode::get_singleton()->is_multi_window_enabled()) { + window_wrapper->restore_window_from_saved_position(floating_window_rect, floating_window_screen, floating_window_screen_rect); + } + + _update_arguments_for_instance(p_idx, r_arguments); +} + +void GameView::_play_pressed() { + OS::ProcessID current_process_id = EditorRunBar::get_singleton()->get_current_process(); + if (current_process_id == 0) { + return; + } + + if (!window_wrapper->get_window_enabled()) { + screen_index_before_start = EditorNode::get_singleton()->get_editor_main_screen()->get_selected_index(); + } + + if (embed_on_play) { + // It's important to disable the low power mode when unfocused because otherwise + // the button in the editor are not responsive and if the user moves the mouse quickly, + // the mouse clicks are not registered. + EditorNode::get_singleton()->set_unfocused_low_processor_usage_mode_enabled(false); + _update_embed_window_size(); + if (!window_wrapper->get_window_enabled()) { + EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_GAME); + embedded_process->grab_focus(); + } + embedded_process->embed_process(current_process_id); + _update_ui(); + } +} + +void GameView::_stop_pressed() { + EditorNode::get_singleton()->set_unfocused_low_processor_usage_mode_enabled(true); + embedded_process->reset(); + _update_ui(); + + if (window_wrapper->get_window_enabled()) { + window_wrapper->set_window_enabled(false); + } + + if (screen_index_before_start >= 0 && EditorNode::get_singleton()->get_editor_main_screen()->get_selected_index() == EditorMainScreen::EDITOR_GAME) { + // We go back to the screen where the user was before starting the game. + EditorNode::get_singleton()->get_editor_main_screen()->select(screen_index_before_start); + } + + screen_index_before_start = -1; +} + +void GameView::_embedding_completed() { + _update_ui(); +} + +void GameView::_embedding_failed() { + state_label->set_text(TTR("Connection impossible to the game process.")); +} + +void GameView::_embedded_process_updated() { + const Rect2i game_rect = embedded_process->get_screen_embedded_window_rect(); + game_size_label->set_text(vformat("%dx%d", game_rect.size.x, game_rect.size.y)); +} + +void GameView::_project_settings_changed() { + // Update the window size and aspect ratio. + _update_embed_window_size(); +} + void GameView::_update_debugger_buttons() { bool empty = active_sessions == 0; @@ -229,6 +310,67 @@ void GameView::_select_mode_pressed(int p_option) { debugger->set_select_mode(mode); } +void GameView::_embed_options_menu_menu_id_pressed(int p_id) { + switch (p_id) { + case EMBED_RUN_GAME_EMBEDDED: { + embed_on_play = !embed_on_play; + EditorSettings::get_singleton()->set_project_metadata("game_view", "embed_on_play", embed_on_play); + } break; + case EMBED_MAKE_FLOATING_ON_PLAY: { + make_floating_on_play = !make_floating_on_play; + EditorSettings::get_singleton()->set_project_metadata("game_view", "make_floating_on_play", make_floating_on_play); + } break; + } + _update_embed_menu_options(); +} + +void GameView::_keep_aspect_button_pressed() { + embedded_process->set_keep_aspect(keep_aspect_button->is_pressed()); +} + +void GameView::_update_ui() { + bool show_game_size = false; + if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) { + state_label->set_text(TTR("Game embedding not available on your OS.")); + } else if (embedded_process->is_embedding_completed()) { + state_label->set_text(""); + show_game_size = true; + } else if (embedded_process->is_embedding_in_progress()) { + state_label->set_text(TTR("Game starting...")); + } else if (EditorRunBar::get_singleton()->is_playing()) { + state_label->set_text(TTR("Game running not embedded.")); + } else if (embed_on_play) { + state_label->set_text(TTR("Press play to start the game.")); + } else { + state_label->set_text(TTR("Embedding is disabled.")); + } + + game_size_label->set_visible(show_game_size); +} + +void GameView::_update_embed_menu_options() { + PopupMenu *menu = embed_options_menu->get_popup(); + menu->set_item_checked(menu->get_item_index(EMBED_RUN_GAME_EMBEDDED), embed_on_play); + menu->set_item_checked(menu->get_item_index(EMBED_MAKE_FLOATING_ON_PLAY), make_floating_on_play); + + // When embed is Off or in single window mode, Make floating is not available. + menu->set_item_disabled(menu->get_item_index(EMBED_MAKE_FLOATING_ON_PLAY), !embed_on_play || !EditorNode::get_singleton()->is_multi_window_enabled()); +} + +void GameView::_update_embed_window_size() { + Size2 window_size; + window_size.x = GLOBAL_GET("display/window/size/viewport_width"); + window_size.y = GLOBAL_GET("display/window/size/viewport_height"); + + Size2 desired_size; + desired_size.x = GLOBAL_GET("display/window/size/window_width_override"); + desired_size.y = GLOBAL_GET("display/window/size/window_height_override"); + if (desired_size.x > 0 && desired_size.y > 0) { + window_size = desired_size; + } + embedded_process->set_window_size(window_size); +} + void GameView::_hide_selection_toggled(bool p_pressed) { hide_selection->set_button_icon(get_editor_theme_icon(p_pressed ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible"))); @@ -287,10 +429,44 @@ void GameView::_notification(int p_what) { select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_button_icon(get_editor_theme_icon(SNAME("ListSelect"))); hide_selection->set_button_icon(get_editor_theme_icon(hide_selection->is_pressed() ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible"))); + keep_aspect_button->set_button_icon(get_editor_theme_icon(SNAME("KeepAspect"))); + embed_options_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl"))); camera_override_button->set_button_icon(get_editor_theme_icon(SNAME("Camera"))); camera_override_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl"))); } break; + + case NOTIFICATION_READY: { + if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) { + // Embedding available. + embed_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "embed_on_play", true); + make_floating_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "make_floating_on_play", true); + keep_aspect_button->set_pressed(EditorSettings::get_singleton()->get_project_metadata("game_view", "keep_aspect", true)); + _update_embed_menu_options(); + + EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &GameView::_play_pressed)); + EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &GameView::_stop_pressed)); + EditorRun::instance_starting_callback = _instance_starting_static; + + // Listen for project settings changes to update the window size and aspect ratio. + ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &GameView::_project_settings_changed)); + + embedded_process->set_keep_aspect(keep_aspect_button->is_pressed()); + } else { + // Embedding not available. + embedding_separator->hide(); + embed_options_menu->hide(); + keep_aspect_button->hide(); + keep_aspect_button->hide(); + } + + _update_ui(); + } break; + case NOTIFICATION_WM_POSITION_CHANGED: { + if (window_wrapper->get_window_enabled()) { + _update_floating_window_settings(); + } + } break; } } @@ -329,8 +505,85 @@ Dictionary GameView::get_state() const { return d; } -GameView::GameView(Ref p_debugger) { +void GameView::set_window_layout(Ref p_layout) { + floating_window_rect = p_layout->get_value("GameView", "floating_window_rect", Rect2i()); + floating_window_screen = p_layout->get_value("GameView", "floating_window_screen", -1); + floating_window_screen_rect = p_layout->get_value("GameView", "floating_window_screen_rect", Rect2i()); +} + +void GameView::get_window_layout(Ref p_layout) { + if (window_wrapper->get_window_enabled()) { + _update_floating_window_settings(); + } + + p_layout->set_value("GameView", "floating_window_rect", floating_window_rect); + p_layout->set_value("GameView", "floating_window_screen", floating_window_screen); + p_layout->set_value("GameView", "floating_window_screen_rect", floating_window_screen_rect); +} + +void GameView::_update_floating_window_settings() { + if (window_wrapper->get_window_enabled()) { + floating_window_rect = window_wrapper->get_window_rect(); + floating_window_screen = window_wrapper->get_window_screen(); + floating_window_screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(floating_window_screen); + } +} + +void GameView::_update_arguments_for_instance(int p_idx, List &r_arguments) { + if (p_idx != 0 || !embed_on_play || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) { + return; + } + + // Remove duplicates/unwanted parameters. + List::Element *E = r_arguments.front(); + while (E) { + List::Element *N = E->next(); + + //For these parameters, we need to also renove the value. + if (E->get() == "--position" || E->get() == "--resolution" || E->get() == "--screen") { + r_arguments.erase(E); + if (N) { + List::Element *V = N->next(); + r_arguments.erase(N); + N = V; + } + } else if (E->get() == "-f" || E->get() == "--fullscreen" || E->get() == "-m" || E->get() == "--maximized" || E->get() == "-t" || E->get() == "-always-on-top") { + r_arguments.erase(E); + } + + E = N; + } + + // Add the editor window's native ID so the started game can directly set it as its parent. + r_arguments.push_back("--wid"); + r_arguments.push_back(itos(DisplayServer::get_singleton()->window_get_native_handle(DisplayServer::WINDOW_HANDLE, get_window()->get_window_id()))); + + // Be sure to have the correct window size in the embedded_process control. + _update_embed_window_size(); + + Rect2i rect = embedded_process->get_screen_embedded_window_rect(); + r_arguments.push_back("--position"); + r_arguments.push_back(itos(rect.position.x) + "," + itos(rect.position.y)); + r_arguments.push_back("--resolution"); + r_arguments.push_back(itos(rect.size.x) + "x" + itos(rect.size.y)); +} + +void GameView::_window_before_closing() { + // Before the parent window closed, we close the embedded game. That prevents + // the embedded game to be seen without a parent window for a fraction of second. + if (EditorRunBar::get_singleton()->is_playing() && (embedded_process->is_embedding_completed() || embedded_process->is_embedding_in_progress())) { + embedded_process->reset(); + // Call deferred to prevent the _stop_pressed callback to be executed before the wrapper window + // actually closes. + callable_mp(EditorRunBar::get_singleton(), &EditorRunBar::stop_playing).call_deferred(); + } +} + +GameView::GameView(Ref p_debugger, WindowWrapper *p_wrapper) { + singleton = this; + debugger = p_debugger; + window_wrapper = p_wrapper; // Add some margin to the sides for better aesthetics. // This prevents the first button's hover/pressed effect from "touching" the panel's border, @@ -438,21 +691,86 @@ GameView::GameView(Ref p_debugger) { menu->set_item_checked(menu->get_item_index(CAMERA_MODE_INGAME), true); menu->add_radio_check_item(TTR("Manipulate From Editors"), CAMERA_MODE_EDITORS); - _update_debugger_buttons(); + embedding_separator = memnew(VSeparator); + main_menu_hbox->add_child(embedding_separator); + + keep_aspect_button = memnew(Button); + main_menu_hbox->add_child(keep_aspect_button); + keep_aspect_button->set_toggle_mode(true); + keep_aspect_button->set_theme_type_variation("FlatButton"); + keep_aspect_button->set_tooltip_text(TTR("Keep the aspect ratio of the embedded game.")); + keep_aspect_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_keep_aspect_button_pressed)); + + embed_options_menu = memnew(MenuButton); + main_menu_hbox->add_child(embed_options_menu); + embed_options_menu->set_flat(false); + embed_options_menu->set_theme_type_variation("FlatMenuButton"); + embed_options_menu->set_h_size_flags(SIZE_SHRINK_END); + embed_options_menu->set_tooltip_text(TTR("Embedding Options")); + + menu = embed_options_menu->get_popup(); + menu->connect(SceneStringName(id_pressed), callable_mp(this, &GameView::_embed_options_menu_menu_id_pressed)); + menu->add_check_item(TTR("Embed game on Next Play"), EMBED_RUN_GAME_EMBEDDED); + menu->add_check_item(TTR("Make Game Workspace floating on Next Play"), EMBED_MAKE_FLOATING_ON_PLAY); + + main_menu_hbox->add_spacer(); + + game_size_label = memnew(Label()); + main_menu_hbox->add_child(game_size_label); + game_size_label->hide(); panel = memnew(Panel); add_child(panel); panel->set_theme_type_variation("GamePanel"); panel->set_v_size_flags(SIZE_EXPAND_FILL); + embedded_process = memnew(EmbeddedProcess); + panel->add_child(embedded_process); + embedded_process->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + embedded_process->connect("embedding_failed", callable_mp(this, &GameView::_embedding_failed)); + embedded_process->connect("embedding_completed", callable_mp(this, &GameView::_embedding_completed)); + embedded_process->connect("embedded_process_updated", callable_mp(this, &GameView::_embedded_process_updated)); + embedded_process->set_custom_minimum_size(Size2i(100, 100)); + + state_label = memnew(Label()); + panel->add_child(state_label); + state_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + state_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); + state_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD); + state_label->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + + _update_debugger_buttons(); + p_debugger->connect("session_started", callable_mp(this, &GameView::_sessions_changed)); p_debugger->connect("session_stopped", callable_mp(this, &GameView::_sessions_changed)); + + p_wrapper->connect("window_before_closing", callable_mp(this, &GameView::_window_before_closing)); + p_wrapper->connect("window_size_changed", callable_mp(this, &GameView::_update_floating_window_settings)); } /////// void GameViewPlugin::make_visible(bool p_visible) { - game_view->set_visible(p_visible); + if (p_visible) { + window_wrapper->show(); + } else { + window_wrapper->hide(); + } +} + +void GameViewPlugin::selected_notify() { + if (window_wrapper->get_window_enabled()) { + window_wrapper->grab_window_focus(); + _focus_another_editor(); + } +} + +void GameViewPlugin::set_window_layout(Ref p_layout) { + game_view->set_window_layout(p_layout); +} + +void GameViewPlugin::get_window_layout(Ref p_layout) { + game_view->get_window_layout(p_layout); } void GameViewPlugin::set_state(const Dictionary &p_state) { @@ -467,20 +785,48 @@ void GameViewPlugin::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { add_debugger_plugin(debugger); + connect("main_screen_changed", callable_mp(this, &GameViewPlugin::_save_last_editor)); } break; case NOTIFICATION_EXIT_TREE: { remove_debugger_plugin(debugger); + disconnect("main_screen_changed", callable_mp(this, &GameViewPlugin::_save_last_editor)); } break; } } +void GameViewPlugin::_window_visibility_changed(bool p_visible) { + _focus_another_editor(); +} + +void GameViewPlugin::_save_last_editor(const String &p_editor) { + if (p_editor != get_name()) { + last_editor = p_editor; + } +} + +void GameViewPlugin::_focus_another_editor() { + if (window_wrapper->get_window_enabled()) { + ERR_FAIL_COND(last_editor.is_empty()); + EditorInterface::get_singleton()->set_main_screen_editor(last_editor); + } +} + GameViewPlugin::GameViewPlugin() { + window_wrapper = memnew(WindowWrapper); + window_wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), TTR("Game Workspace"))); + window_wrapper->set_margins_enabled(true); + debugger.instantiate(); - game_view = memnew(GameView(debugger)); + game_view = memnew(GameView(debugger, window_wrapper)); game_view->set_v_size_flags(Control::SIZE_EXPAND_FILL); - EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(game_view); - game_view->hide(); + + window_wrapper->set_wrapped_control(game_view, nullptr); + + EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(window_wrapper); + window_wrapper->set_v_size_flags(Control::SIZE_EXPAND_FILL); + window_wrapper->hide(); + window_wrapper->connect("window_visibility_changed", callable_mp(this, &GameViewPlugin::_window_visibility_changed)); } GameViewPlugin::~GameViewPlugin() { diff --git a/editor/plugins/game_view_plugin.h b/editor/plugins/game_view_plugin.h index f8701c3e76fd..338d03b389b2 100644 --- a/editor/plugins/game_view_plugin.h +++ b/editor/plugins/game_view_plugin.h @@ -37,6 +37,10 @@ #include "scene/debugger/scene_debugger.h" #include "scene/gui/box_container.h" +class EmbeddedProcess; +class VSeparator; +class WindowWrapper; + class GameViewDebugger : public EditorDebuggerPlugin { GDCLASS(GameViewDebugger, EditorDebuggerPlugin); @@ -82,11 +86,24 @@ class GameView : public VBoxContainer { CAMERA_RESET_3D, CAMERA_MODE_INGAME, CAMERA_MODE_EDITORS, + EMBED_RUN_GAME_EMBEDDED, + EMBED_MAKE_FLOATING_ON_PLAY, }; + inline static GameView *singleton = nullptr; + Ref debugger; + WindowWrapper *window_wrapper = nullptr; int active_sessions = 0; + int screen_index_before_start = -1; + + bool embed_on_play = true; + bool make_floating_on_play = true; + + Rect2i floating_window_rect; + int floating_window_screen = -1; + Rect2i floating_window_screen_rect; Button *suspend_button = nullptr; Button *next_frame_button = nullptr; @@ -99,7 +116,14 @@ class GameView : public VBoxContainer { Button *camera_override_button = nullptr; MenuButton *camera_override_menu = nullptr; + VSeparator *embedding_separator = nullptr; + Button *keep_aspect_button = nullptr; + MenuButton *embed_options_menu = nullptr; + Label *game_size_label = nullptr; + Panel *panel = nullptr; + EmbeddedProcess *embedded_process = nullptr; + Label *state_label = nullptr; void _sessions_changed(); @@ -109,12 +133,31 @@ class GameView : public VBoxContainer { void _node_type_pressed(int p_option); void _select_mode_pressed(int p_option); + void _embed_options_menu_menu_id_pressed(int p_id); + void _keep_aspect_button_pressed(); + + void _play_pressed(); + static void _instance_starting_static(int p_idx, List &r_arguments); + void _instance_starting(int p_idx, List &r_arguments); + void _stop_pressed(); + void _embedding_completed(); + void _embedding_failed(); + void _embedded_process_updated(); + void _project_settings_changed(); + + void _update_ui(); + void _update_embed_menu_options(); + void _update_embed_window_size(); + void _update_arguments_for_instance(int p_idx, List &r_arguments); void _hide_selection_toggled(bool p_pressed); void _camera_override_button_toggled(bool p_pressed); void _camera_override_menu_id_pressed(int p_id); + void _window_before_closing(); + void _update_floating_window_settings(); + protected: void _notification(int p_what); @@ -122,16 +165,26 @@ class GameView : public VBoxContainer { void set_state(const Dictionary &p_state); Dictionary get_state() const; - GameView(Ref p_debugger); + void set_window_layout(Ref p_layout); + void get_window_layout(Ref p_layout); + + GameView(Ref p_debugger, WindowWrapper *p_wrapper); }; class GameViewPlugin : public EditorPlugin { GDCLASS(GameViewPlugin, EditorPlugin); GameView *game_view = nullptr; + WindowWrapper *window_wrapper = nullptr; Ref debugger; + String last_editor; + + void _window_visibility_changed(bool p_visible); + void _save_last_editor(const String &p_editor); + void _focus_another_editor(); + protected: void _notification(int p_what); @@ -141,6 +194,10 @@ class GameViewPlugin : public EditorPlugin { virtual void edit(Object *p_object) override {} virtual bool handles(Object *p_object) const override { return false; } virtual void make_visible(bool p_visible) override; + virtual void selected_notify() override; + + virtual void set_window_layout(Ref p_layout) override; + virtual void get_window_layout(Ref p_layout) override; virtual void set_state(const Dictionary &p_state) override; virtual Dictionary get_state() const override; diff --git a/editor/window_wrapper.cpp b/editor/window_wrapper.cpp index 3256680416d2..97ecda9bc93b 100644 --- a/editor/window_wrapper.cpp +++ b/editor/window_wrapper.cpp @@ -101,6 +101,12 @@ void WindowWrapper::_set_window_enabled_with_rect(bool p_visible, const Rect2 p_ Node *parent = _get_wrapped_control_parent(); + // In the GameView plugin, we need to the the signal before the window is actually closed + // to prevent the embedded game to be seen the parent window for a fraction of a second. + if (!p_visible) { + emit_signal("window_before_closing"); + } + if (wrapped_control->get_parent() != parent) { // Move the control to the window. wrapped_control->reparent(parent, false); @@ -131,9 +137,15 @@ void WindowWrapper::_set_window_rect(const Rect2 p_rect) { } } +void WindowWrapper::_window_size_changed() { + emit_signal(SNAME("window_size_changed")); +} + void WindowWrapper::_bind_methods() { ADD_SIGNAL(MethodInfo("window_visibility_changed", PropertyInfo(Variant::BOOL, "visible"))); ADD_SIGNAL(MethodInfo("window_close_requested")); + ADD_SIGNAL(MethodInfo("window_before_closing")); + ADD_SIGNAL(MethodInfo("window_size_changed")); } void WindowWrapper::_notification(int p_what) { @@ -142,11 +154,9 @@ void WindowWrapper::_notification(int p_what) { } switch (p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { - if (get_window_enabled() && is_visible()) { - // Grab the focus when WindowWrapper.set_visible(true) is called - // and the window is showing. - window->grab_focus(); - } + // Grab the focus when WindowWrapper.set_visible(true) is called + // and the window is showing. + grab_window_focus(); } break; case NOTIFICATION_READY: { set_process_shortcut_input(true); @@ -314,6 +324,12 @@ void WindowWrapper::set_margins_enabled(bool p_enabled) { } } +void WindowWrapper::grab_window_focus() { + if (get_window_enabled() && is_visible()) { + window->grab_focus(); + } +} + WindowWrapper::WindowWrapper() { if (!EditorNode::get_singleton()->is_multi_window_enabled()) { return; @@ -326,6 +342,7 @@ WindowWrapper::WindowWrapper() { window->hide(); window->connect("close_requested", callable_mp(this, &WindowWrapper::set_window_enabled).bind(false)); + window->connect("size_changed", callable_mp(this, &WindowWrapper::_window_size_changed)); ShortcutBin *capturer = memnew(ShortcutBin); window->add_child(capturer); diff --git a/editor/window_wrapper.h b/editor/window_wrapper.h index 3597276de946..0a9f0f21d1b3 100644 --- a/editor/window_wrapper.h +++ b/editor/window_wrapper.h @@ -54,6 +54,7 @@ class WindowWrapper : public MarginContainer { void _set_window_enabled_with_rect(bool p_visible, const Rect2 p_rect); void _set_window_rect(const Rect2 p_rect); + void _window_size_changed(); protected: static void _bind_methods(); @@ -80,6 +81,7 @@ class WindowWrapper : public MarginContainer { void set_window_title(const String &p_title); void set_margins_enabled(bool p_enabled); + void grab_window_focus(); WindowWrapper(); }; diff --git a/main/main.cpp b/main/main.cpp index 0a905f16cac6..817bdf4db76a 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -226,6 +226,7 @@ static bool init_always_on_top = false; static bool init_use_custom_pos = false; static bool init_use_custom_screen = false; static Vector2 init_custom_pos; +static int64_t init_embed_parent_window_id = 0; // Debug @@ -617,6 +618,7 @@ void Main::print_help(const char *p_binary) { #ifndef _3D_DISABLED print_help_option("--xr-mode ", "Select XR (Extended Reality) mode [\"default\", \"off\", \"on\"].\n"); #endif + print_help_option("--wid ", "Request parented to window.\n"); print_help_title("Debug options"); print_help_option("-d, --debug", "Debug (local stdout debugger).\n"); @@ -1798,6 +1800,23 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph goto error; } #endif // TOOLS_ENABLED + } else if (arg == "--wid") { + if (N) { + init_embed_parent_window_id = N->get().to_int(); + if (init_embed_parent_window_id == 0) { + OS::get_singleton()->print(" argument for --wid must be different then 0.\n"); + goto error; + } + + OS::get_singleton()->_embedded_in_editor = true; + Engine::get_singleton()->set_embedded_in_editor(true); + + N = N->next(); + } else { + OS::get_singleton()->print("Missing argument for --wid .\n"); + goto error; + } + } else if (arg == "--" || arg == "++") { adding_user_args = true; } else { @@ -2960,9 +2979,16 @@ Error Main::setup2(bool p_show_boot_logo) { context = DisplayServer::CONTEXT_ENGINE; } + if (init_embed_parent_window_id) { + // Reset flags and other settings to be sure it's borderless and windowed. The position and size should have been initialized correctly + // from --position and --resolution parameters. + window_mode = DisplayServer::WINDOW_MODE_WINDOWED; + window_flags = DisplayServer::WINDOW_FLAG_BORDERLESS_BIT; + } + // rendering_driver now held in static global String in main and initialized in setup() Error err; - display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_vsync_mode, window_flags, window_position, window_size, init_screen, context, err); + display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_vsync_mode, window_flags, window_position, window_size, init_screen, context, init_embed_parent_window_id, err); if (err != OK || display_server == nullptr) { String last_name = DisplayServer::get_create_function_name(display_driver_idx); @@ -2976,7 +3002,7 @@ Error Main::setup2(bool p_show_boot_logo) { String name = DisplayServer::get_create_function_name(i); WARN_PRINT(vformat("Display driver %s failed, falling back to %s.", last_name, name)); - display_server = DisplayServer::create(i, rendering_driver, window_mode, window_vsync_mode, window_flags, window_position, window_size, init_screen, context, err); + display_server = DisplayServer::create(i, rendering_driver, window_mode, window_vsync_mode, window_flags, window_position, window_size, init_screen, context, init_embed_parent_window_id, err); if (err == OK && display_server != nullptr) { break; } @@ -3180,15 +3206,17 @@ Error Main::setup2(bool p_show_boot_logo) { MAIN_PRINT("Main: Setup Logo"); - if (init_windowed) { - //do none.. - } else if (init_maximized) { - DisplayServer::get_singleton()->window_set_mode(DisplayServer::WINDOW_MODE_MAXIMIZED); - } else if (init_fullscreen) { - DisplayServer::get_singleton()->window_set_mode(DisplayServer::WINDOW_MODE_FULLSCREEN); - } - if (init_always_on_top) { - DisplayServer::get_singleton()->window_set_flag(DisplayServer::WINDOW_FLAG_ALWAYS_ON_TOP, true); + if (!init_embed_parent_window_id) { + if (init_windowed) { + //do none.. + } else if (init_maximized) { + DisplayServer::get_singleton()->window_set_mode(DisplayServer::WINDOW_MODE_MAXIMIZED); + } else if (init_fullscreen) { + DisplayServer::get_singleton()->window_set_mode(DisplayServer::WINDOW_MODE_FULLSCREEN); + } + if (init_always_on_top) { + DisplayServer::get_singleton()->window_set_flag(DisplayServer::WINDOW_FLAG_ALWAYS_ON_TOP, true); + } } Color clear = GLOBAL_DEF_BASIC("rendering/environment/defaults/default_clear_color", Color(0.3, 0.3, 0.3)); diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 38f6931c8a86..140ce662624d 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -562,8 +562,8 @@ Vector DisplayServerAndroid::get_rendering_drivers_func() { return drivers; } -DisplayServer *DisplayServerAndroid::create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) { - DisplayServer *ds = memnew(DisplayServerAndroid(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, r_error)); +DisplayServer *DisplayServerAndroid::create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { + DisplayServer *ds = memnew(DisplayServerAndroid(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error)); if (r_error != OK) { if (p_rendering_driver == "vulkan") { OS::get_singleton()->alert( @@ -630,7 +630,7 @@ void DisplayServerAndroid::notify_surface_changed(int p_width, int p_height) { } } -DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) { +DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { rendering_driver = p_rendering_driver; keep_screen_on = GLOBAL_GET("display/window/energy_saving/keep_screen_on"); diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index 1744ad306907..c88153c07ca2 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -223,7 +223,7 @@ class DisplayServerAndroid : public DisplayServer { virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; - static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error); + static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); static Vector get_rendering_drivers_func(); static void register_android_driver(); @@ -240,7 +240,7 @@ class DisplayServerAndroid : public DisplayServer { virtual void set_native_icon(const String &p_filename) override; virtual void set_icon(const Ref &p_icon) override; - DisplayServerAndroid(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error); + DisplayServerAndroid(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); ~DisplayServerAndroid(); }; diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h index 7f199db99712..696f72b0f8d9 100644 --- a/platform/ios/display_server_ios.h +++ b/platform/ios/display_server_ios.h @@ -84,7 +84,7 @@ class DisplayServerIOS : public DisplayServer { void perform_event(const Ref &p_event); - DisplayServerIOS(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error); + DisplayServerIOS(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); ~DisplayServerIOS(); public: @@ -93,7 +93,7 @@ class DisplayServerIOS : public DisplayServer { static DisplayServerIOS *get_singleton(); static void register_ios_driver(); - static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error); + static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); static Vector get_rendering_drivers_func(); // MARK: - Events diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm index 5d9179bd9a79..4bb290b5d5bd 100644 --- a/platform/ios/display_server_ios.mm +++ b/platform/ios/display_server_ios.mm @@ -53,7 +53,7 @@ return (DisplayServerIOS *)DisplayServer::get_singleton(); } -DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) { +DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { KeyMappingIOS::initialize(); rendering_driver = p_rendering_driver; @@ -196,8 +196,8 @@ #endif } -DisplayServer *DisplayServerIOS::create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) { - return memnew(DisplayServerIOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, r_error)); +DisplayServer *DisplayServerIOS::create_func(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { + return memnew(DisplayServerIOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error)); } Vector DisplayServerIOS::get_rendering_drivers_func() { diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index fe359532bb03..c725e045603e 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -1299,8 +1299,8 @@ Vector DisplayServerWayland::get_rendering_drivers_func() { return drivers; } -DisplayServer *DisplayServerWayland::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Context p_context, Error &r_error) { - DisplayServer *ds = memnew(DisplayServerWayland(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, p_context, r_error)); +DisplayServer *DisplayServerWayland::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { + DisplayServer *ds = memnew(DisplayServerWayland(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, p_context, p_parent_window, r_error)); if (r_error != OK) { ERR_PRINT("Can't create the Wayland display server."); memdelete(ds); @@ -1310,7 +1310,7 @@ DisplayServer *DisplayServerWayland::create_func(const String &p_rendering_drive return ds; } -DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Context p_context, Error &r_error) { +DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Context p_context, int64_t p_parent_window, Error &r_error) { #ifdef GLES3_ENABLED #ifdef SOWRAP_ENABLED #ifdef DEBUG_ENABLED diff --git a/platform/linuxbsd/wayland/display_server_wayland.h b/platform/linuxbsd/wayland/display_server_wayland.h index e61153366421..bd5143f279e1 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.h +++ b/platform/linuxbsd/wayland/display_server_wayland.h @@ -290,12 +290,12 @@ class DisplayServerWayland : public DisplayServer { virtual bool is_window_transparency_available() const override; - static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Context p_context, Error &r_error); + static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); static Vector get_rendering_drivers_func(); static void register_wayland_driver(); - DisplayServerWayland(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Context p_context, Error &r_error); + DisplayServerWayland(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Context p_context, int64_t p_parent_window, Error &r_error); ~DisplayServerWayland(); }; diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index a9c94bd8238e..7040749fe720 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -140,6 +140,7 @@ bool DisplayServerX11::has_feature(Feature p_feature) const { #endif case FEATURE_CLIPBOARD_PRIMARY: case FEATURE_TEXT_TO_SPEECH: + case FEATURE_WINDOW_EMBEDDING: return true; case FEATURE_SCREEN_CAPTURE: return !xwayland; @@ -1455,6 +1456,36 @@ Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const { return rect; } +Rect2i DisplayServerX11::_screens_get_full_rect() const { + Rect2i full_rect; + + int count = get_screen_count(); + for (int i = 0; i < count; i++) { + if (i == 0) { + full_rect = _screen_get_rect(i); + continue; + } + + Rect2i screen_rect = _screen_get_rect(i); + if (full_rect.position.x > screen_rect.position.x) { + full_rect.size.x += full_rect.position.x - screen_rect.position.x; + full_rect.position.x = screen_rect.position.x; + } + if (full_rect.position.y > screen_rect.position.y) { + full_rect.size.y += full_rect.position.y - screen_rect.position.y; + full_rect.position.y = screen_rect.position.y; + } + if (full_rect.position.x + full_rect.size.x < screen_rect.position.x + screen_rect.size.x) { + full_rect.size.x = screen_rect.position.x + screen_rect.size.x - full_rect.position.x; + } + if (full_rect.position.y + full_rect.size.y < screen_rect.position.y + screen_rect.size.y) { + full_rect.size.y = screen_rect.position.y + screen_rect.size.y - full_rect.position.y; + } + } + + return full_rect; +} + int DisplayServerX11::screen_get_dpi(int p_screen) const { _THREAD_SAFE_METHOD_ @@ -1739,7 +1770,7 @@ Vector DisplayServerX11::get_window_list() const { DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, bool p_exclusive, WindowID p_transient_parent) { _THREAD_SAFE_METHOD_ - WindowID id = _create_window(p_mode, p_vsync_mode, p_flags, p_rect); + WindowID id = _create_window(p_mode, p_vsync_mode, p_flags, p_rect, 0); for (int i = 0; i < WINDOW_FLAG_MAX; i++) { if (p_flags & (1 << i)) { window_set_flag(WindowFlags(i), true, id); @@ -2068,6 +2099,8 @@ void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window return; } + ERR_FAIL_COND_MSG(wd.embed_parent, "Embedded window can't be moved to another screen."); + if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN || window_get_mode(p_window) == WINDOW_MODE_MAXIMIZED) { Point2i position = screen_get_position(p_screen); Size2i size = screen_get_size(p_screen); @@ -2225,6 +2258,8 @@ void DisplayServerX11::window_set_position(const Point2i &p_position, WindowID p ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; + ERR_FAIL_COND_MSG(wd.embed_parent, "Embedded window can't be moved."); + int x = 0; int y = 0; if (!window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) { @@ -2257,6 +2292,8 @@ void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_windo ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; + ERR_FAIL_COND_MSG(wd.embed_parent, "Embedded windows can't have a maximum size."); + if ((p_size != Size2i()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) { ERR_PRINT("Maximum window size can't be smaller than minimum window size!"); return; @@ -2282,6 +2319,8 @@ void DisplayServerX11::window_set_min_size(const Size2i p_size, WindowID p_windo ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; + ERR_FAIL_COND_MSG(wd.embed_parent, "Embedded windows can't have a minimum size."); + if ((p_size != Size2i()) && (wd.max_size != Size2i()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) { ERR_PRINT("Minimum window size can't be larger than maximum window size!"); return; @@ -2311,6 +2350,8 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) { WindowData &wd = windows[p_window]; + ERR_FAIL_COND_MSG(wd.embed_parent, "Embedded window can't be resized."); + if (wd.size.width == size.width && wd.size.height == size.height) { return; } @@ -2728,8 +2769,10 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { if (old_mode == p_mode) { return; // do nothing } - //remove all "extra" modes + ERR_FAIL_COND_MSG(p_mode != WINDOW_MODE_WINDOWED && wd.embed_parent, "Embedded window only supports Windowed mode."); + + // Remove all "extra" modes. switch (old_mode) { case WINDOW_MODE_WINDOWED: { //do nothing @@ -2829,8 +2872,9 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo switch (p_flag) { case WINDOW_FLAG_RESIZE_DISABLED: { - wd.resize_disabled = p_enabled; + ERR_FAIL_COND_MSG(p_enabled && wd.embed_parent, "Embedded window resize can't be disabled."); + wd.resize_disabled = p_enabled; _update_size_hints(p_window); XFlush(x11_display); @@ -2846,13 +2890,16 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo } // Preserve window size - window_set_size(window_get_size(p_window), p_window); + if (!wd.embed_parent) { + window_set_size(window_get_size(p_window), p_window); + } wd.borderless = p_enabled; _update_window_mouse_passthrough(p_window); } break; case WINDOW_FLAG_ALWAYS_ON_TOP: { ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID, "Can't make a window transient if the 'on top' flag is active."); + ERR_FAIL_COND_MSG(p_enabled && wd.embed_parent, "Embedded window can't become on top."); if (p_enabled && wd.fullscreen) { _set_wm_maximized(p_window, true); } @@ -2894,6 +2941,7 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup."); ERR_FAIL_COND_MSG((xwa.map_state == IsViewable) && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened."); + ERR_FAIL_COND_MSG(p_enabled && wd.embed_parent, "Embedded window can't be popup."); wd.is_popup = p_enabled; } break; default: { @@ -3348,6 +3396,7 @@ Key DisplayServerX11::keyboard_get_label_from_physical(Key p_keycode) const { } return (Key)(key | modifiers); } + DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, Window p_window, Atom p_property) { Atom actual_type = None; int actual_format = 0; @@ -5028,9 +5077,9 @@ void DisplayServerX11::process_events() { // Don't propagate the motion event unless we have focus // this is so that the relative motion doesn't get messed up // after we regain focus. - if (focused) { - Input::get_singleton()->parse_input_event(mm); - } else { + // Adjusted to parse the input event if the window is not focused allowing mouse hovering on the editor + // the embedding process has focus. + if (!focused) { // Propagate the event to the focused window, // because it's received only on the topmost window. // Note: This is needed for drag & drop to work between windows, @@ -5049,13 +5098,14 @@ void DisplayServerX11::process_events() { mm->set_position(pos_focused); mm->set_global_position(pos_focused); mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); - Input::get_singleton()->parse_input_event(mm); break; } } } + Input::get_singleton()->parse_input_event(mm); + } break; case KeyPress: case KeyRelease: { @@ -5425,6 +5475,268 @@ DisplayServer::VSyncMode DisplayServerX11::window_get_vsync_mode(WindowID p_wind return DisplayServer::VSYNC_ENABLED; } +pid_t get_window_pid(Display *p_display, Window p_window) { + Atom atom = XInternAtom(p_display, "_NET_WM_PID", False); + Atom actualType; + int actualFormat; + unsigned long nItems, bytesAfter; + unsigned char *prop = nullptr; + if (XGetWindowProperty(p_display, p_window, atom, 0, sizeof(pid_t), False, AnyPropertyType, + &actualType, &actualFormat, &nItems, &bytesAfter, &prop) == Success) { + if (nItems > 0) { + pid_t pid = *(pid_t *)prop; + XFree(prop); + return pid; + } + } + + return 0; // PID not found. +} + +Window find_window_from_process_id_internal(Display *p_display, pid_t p_process_id, Window p_window) { + Window dummy; + Window *children; + unsigned int num_children; + + if (!XQueryTree(p_display, p_window, &dummy, &dummy, &children, &num_children)) { + return 0; + } + + for (unsigned int i = 0; i < num_children; i++) { + pid_t pid = get_window_pid(p_display, children[i]); + if (pid == p_process_id) { + return children[i]; + } + } + + // Then check children of children. + for (unsigned int i = 0; i < num_children; i++) { + Window wnd = find_window_from_process_id_internal(p_display, p_process_id, children[i]); + if (wnd != 0) { + return wnd; + } + } + + if (children) { + XFree(children); + } + + return 0; +} + +Window find_window_from_process_id(Display *p_display, pid_t p_process_id) { + // Handle bad window errors silently because while looping + // windows can be destroyed, resulting in BadWindow errors. + int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler); + + const int screencount = XScreenCount(p_display); + Window process_window = 0; + + for (int screen_index = 0; screen_index < screencount; screen_index++) { + Window root = RootWindow(p_display, screen_index); + + Window wnd = find_window_from_process_id_internal(p_display, p_process_id, root); + + if (wnd != 0) { + process_window = wnd; + break; + } + } + + // Restore default error handler. + XSetErrorHandler(oldHandler); + + return process_window; +} + +Point2i DisplayServerX11::_get_window_position(Window p_window) const { + int x = 0, y = 0; + Window child; + XTranslateCoordinates(x11_display, p_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child); + return Point2i(x, y); +} + +Rect2i DisplayServerX11::_get_window_rect(Window p_window) const { + XWindowAttributes xwa; + XGetWindowAttributes(x11_display, p_window, &xwa); + return Rect2i(xwa.x, xwa.y, xwa.width, xwa.height); +} + +void DisplayServerX11::_set_window_taskbar_pager_enabled(Window p_window, bool p_enabled) { + Atom wmState = XInternAtom(x11_display, "_NET_WM_STATE", False); + Atom skipTaskbar = XInternAtom(x11_display, "_NET_WM_STATE_SKIP_TASKBAR", False); + Atom skipPager = XInternAtom(x11_display, "_NET_WM_STATE_SKIP_PAGER", False); + + XClientMessageEvent xev; + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.window = p_window; + xev.message_type = wmState; + xev.format = 32; + xev.data.l[0] = p_enabled ? _NET_WM_STATE_REMOVE : _NET_WM_STATE_ADD; // When enabled, we must remove the skip. + xev.data.l[1] = skipTaskbar; + xev.data.l[2] = skipPager; + xev.data.l[3] = 0; + xev.data.l[4] = 0; + + // Send the client message to the root window. + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev); +} + +Error DisplayServerX11::embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), FAILED); + + const WindowData &wd = windows[p_window]; + + DEBUG_LOG_X11("Starting embedding %ld to window %lu \n", p_pid, wd.x11_window); + + EmbeddedProcessData *ep = nullptr; + if (embedded_processes.has(p_pid)) { + ep = embedded_processes.get(p_pid); + } else { + // New process, trying to find the window. + Window process_window = find_window_from_process_id(x11_display, p_pid); + if (!process_window) { + return ERR_DOES_NOT_EXIST; + } + DEBUG_LOG_X11("Process %ld window found: %lu \n", p_pid, process_window); + ep = memnew(EmbeddedProcessData); + ep->process_window = process_window; + ep->visible = true; + XSetTransientForHint(x11_display, process_window, wd.x11_window); + _set_window_taskbar_pager_enabled(process_window, false); + embedded_processes.insert(p_pid, ep); + } + + // Handle bad window errors silently because just in case the embedded window was closed. + int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler); + + if (p_visible) { + // Resize and move the window to match the desired rectangle. + // X11 does not allow moving the window entirely outside the screen boundaries. + // To ensure the window remains visible, we will resize it to fit within both the screen and the specified rectangle. + Rect2i desired_rect = p_rect; + + // First resize the desired rect to fit inside all the screens without considering the + // working area. + Rect2i screens_full_rect = _screens_get_full_rect(); + Vector2i screens_full_end = screens_full_rect.get_end(); + if (desired_rect.position.x < screens_full_rect.position.x) { + desired_rect.size.x = MAX(desired_rect.size.x - (screens_full_rect.position.x - desired_rect.position.x), 0); + desired_rect.position.x = screens_full_rect.position.x; + } + if (desired_rect.position.x + desired_rect.size.x > screens_full_end.x) { + desired_rect.size.x = MAX(screens_full_end.x - desired_rect.position.x, 0); + } + if (desired_rect.position.y < screens_full_rect.position.y) { + desired_rect.size.y = MAX(desired_rect.size.y - (screens_full_rect.position.y - desired_rect.position.y), 0); + desired_rect.position.y = screens_full_rect.position.y; + } + if (desired_rect.position.y + desired_rect.size.y > screens_full_end.y) { + desired_rect.size.y = MAX(screens_full_end.y - desired_rect.position.y, 0); + } + + // Second, for each screen, check if the desired rectangle is within a portion of the screen + // that is outside the working area. Each screen can have a different working area + // depending on top, bottom, or side panels. + int desired_area = desired_rect.get_area(); + int count = get_screen_count(); + for (int i = 0; i < count; i++) { + Rect2i screen_rect = _screen_get_rect(i); + if (screen_rect.intersection(desired_rect).get_area() == 0) { + continue; + } + + // The desired rect is inside this screen. + Rect2i screen_usable_rect = screen_get_usable_rect(i); + int screen_usable_area = screen_usable_rect.intersection(desired_rect).get_area(); + if (screen_usable_area == desired_area) { + // The desired rect is fulling inside the usable rect of the screen. No need to resize. + continue; + } + + if (desired_rect.position.x >= screen_rect.position.x && desired_rect.position.x < screen_usable_rect.position.x) { + int offset = screen_usable_rect.position.x - desired_rect.position.x; + desired_rect.size.x = MAX(desired_rect.size.x - offset, 0); + desired_rect.position.x += offset; + } + if (desired_rect.position.y >= screen_rect.position.y && desired_rect.position.y < screen_usable_rect.position.y) { + int offset = screen_usable_rect.position.y - desired_rect.position.y; + desired_rect.size.y = MAX(desired_rect.size.y - offset, 0); + desired_rect.position.y += offset; + } + + Vector2i desired_end = desired_rect.get_end(); + Vector2i screen_end = screen_rect.get_end(); + Vector2i screen_usable_end = screen_usable_rect.get_end(); + if (desired_end.x > screen_usable_end.x && desired_end.x <= screen_end.x) { + desired_rect.size.x = MAX(desired_rect.size.x - (desired_end.x - screen_usable_end.x), 0); + } + if (desired_end.y > screen_usable_end.y && desired_end.y <= screen_end.y) { + desired_rect.size.y = MAX(desired_rect.size.y - (desired_end.y - screen_usable_end.y), 0); + } + } + + if (desired_rect.size.x < 100 || desired_rect.size.y < 100) { + p_visible = false; + } + + if (p_visible) { + Rect2i current_process_window_rect = _get_window_rect(ep->process_window); + if (current_process_window_rect != desired_rect) { + DEBUG_LOG_X11("Embedding XMoveResizeWindow process %ld, window %lu to %d, %d, %d, %d \n", p_pid, wd.x11_window, desired_rect.position.x, desired_rect.position.y, desired_rect.size.x, desired_rect.size.y); + XMoveResizeWindow(x11_display, ep->process_window, desired_rect.position.x, desired_rect.position.y, desired_rect.size.x, desired_rect.size.y); + } + } + } + + if (ep->visible != p_visible) { + if (p_visible) { + XMapWindow(x11_display, ep->process_window); + } else { + XUnmapWindow(x11_display, ep->process_window); + } + ep->visible = p_visible; + } + + if (p_grab_focus) { + XSetInputFocus(x11_display, ep->process_window, RevertToParent, CurrentTime); + } + + // Restore default error handler. + XSetErrorHandler(oldHandler); + return OK; +} + +Error DisplayServerX11::remove_embedded_process(OS::ProcessID p_pid) { + _THREAD_SAFE_METHOD_ + + if (!embedded_processes.has(p_pid)) { + return ERR_DOES_NOT_EXIST; + } + + EmbeddedProcessData *ep = embedded_processes.get(p_pid); + embedded_processes.erase(p_pid); + memdelete(ep); + + return OK; +} + +OS::ProcessID DisplayServerX11::get_focused_process_id() { + Window focused_window = 0; + int revert_to = 0; + + XGetInputFocus(x11_display, &focused_window, &revert_to); + + if (focused_window == None) { + return 0; + } + + return get_window_pid(x11_display, focused_window); +} + Vector DisplayServerX11::get_rendering_drivers_func() { Vector drivers; @@ -5439,12 +5751,12 @@ Vector DisplayServerX11::get_rendering_drivers_func() { return drivers; } -DisplayServer *DisplayServerX11::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) { - DisplayServer *ds = memnew(DisplayServerX11(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, r_error)); +DisplayServer *DisplayServerX11::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { + DisplayServer *ds = memnew(DisplayServerX11(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error)); return ds; } -DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) { +DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, Window p_parent_window) { //Create window XVisualInfo visualInfo; @@ -5543,16 +5855,19 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V } Rect2i win_rect = p_rect; - if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { - Rect2i screen_rect = Rect2i(screen_get_position(rq_screen), screen_get_size(rq_screen)); + if (!p_parent_window) { + // No parent. + if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { + Rect2i screen_rect = Rect2i(screen_get_position(rq_screen), screen_get_size(rq_screen)); - win_rect = screen_rect; - } else { - Rect2i srect = screen_get_usable_rect(rq_screen); - Point2i wpos = p_rect.position; - wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3); + win_rect = screen_rect; + } else { + Rect2i srect = screen_get_usable_rect(rq_screen); + Point2i wpos = p_rect.position; + wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3); - win_rect.position = wpos; + win_rect.position = wpos; + } } // Position and size hints are set from these values before they are updated to the actual @@ -5564,6 +5879,14 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo.screen), win_rect.position.x, win_rect.position.y, win_rect.size.width > 0 ? win_rect.size.width : 1, win_rect.size.height > 0 ? win_rect.size.height : 1, 0, visualInfo.depth, InputOutput, visualInfo.visual, valuemask, &windowAttributes); wd.parent = RootWindow(x11_display, visualInfo.screen); + + DEBUG_LOG_X11("CreateWindow window=%lu, parent: %lu \n", wd.x11_window, wd.parent); + + if (p_parent_window) { + wd.embed_parent = p_parent_window; + XSetTransientForHint(x11_display, wd.x11_window, p_parent_window); + } + XSetWindowAttributes window_attributes_ime = {}; window_attributes_ime.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask; @@ -5715,7 +6038,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V } } - if (wd.is_popup || wd.no_focus) { + if (wd.is_popup || wd.no_focus || wd.embed_parent) { // Set Utility type to disable fade animations. Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False); Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); @@ -5731,6 +6054,11 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V } } + if (p_parent_window) { + // Disable the window in the taskbar and alt-tab. + _set_window_taskbar_pager_enabled(wd.x11_window, false); + } + _update_size_hints(id); #if defined(RD_ENABLED) @@ -5852,7 +6180,7 @@ static ::XIMStyle _get_best_xim_style(const ::XIMStyle &p_style_a, const ::XIMSt return p_style_a; } -DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) { +DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { KeyMappingX11::initialize(); xwayland = OS::get_singleton()->get_environment("XDG_SESSION_TYPE").to_lower() == "wayland"; @@ -6309,7 +6637,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode window_position = scr_rect.position + (scr_rect.size - p_resolution) / 2; } - WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution)); + WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution), p_parent_window); if (main_window == INVALID_WINDOW_ID) { r_error = ERR_CANT_CREATE; return; diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index 0cbfbe51ef1f..5906d1eadd5d 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -208,6 +208,8 @@ class DisplayServerX11 : public DisplayServer { bool layered_window = false; bool mpass = false; + Window embed_parent = 0; + Rect2i parent_safe_rect; unsigned int focus_order = 0; @@ -234,7 +236,7 @@ class DisplayServerX11 : public DisplayServer { WindowID last_focused_window = INVALID_WINDOW_ID; WindowID window_id_counter = MAIN_WINDOW_ID; - WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect); + WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, Window p_parent_window); String internal_clipboard; String internal_clipboard_primary; @@ -375,6 +377,18 @@ class DisplayServerX11 : public DisplayServer { static Bool _predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg); static Bool _predicate_clipboard_save_targets(Display *display, XEvent *event, XPointer arg); + struct EmbeddedProcessData { + Window process_window = 0; + bool visible = true; + }; + HashMap embedded_processes; + + Point2i _get_window_position(Window p_window) const; + Rect2i _get_window_rect(Window p_window) const; + void _set_external_window_settings(Window p_window, Window p_parent_transient, WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect); + void _set_window_taskbar_pager_enabled(Window p_window, bool p_enabled); + Rect2i _screens_get_full_rect() const; + protected: void _window_changed(XEvent *event); @@ -510,6 +524,10 @@ class DisplayServerX11 : public DisplayServer { virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override; + virtual Error embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) override; + virtual Error remove_embedded_process(OS::ProcessID p_pid) override; + virtual OS::ProcessID get_focused_process_id() override; + virtual void cursor_set_shape(CursorShape p_shape) override; virtual CursorShape cursor_get_shape() const override; virtual void cursor_set_custom_image(const Ref &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) override; @@ -534,12 +552,12 @@ class DisplayServerX11 : public DisplayServer { virtual void set_native_icon(const String &p_filename) override; virtual void set_icon(const Ref &p_icon) override; - static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error); + static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); static Vector get_rendering_drivers_func(); static void register_x11_driver(); - DisplayServerX11(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error); + DisplayServerX11(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); ~DisplayServerX11(); }; diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 97af6d0a5a25..28641bd1317a 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -442,12 +442,12 @@ class DisplayServerMacOS : public DisplayServer { virtual bool is_window_transparency_available() const override; - static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error); + static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); static Vector get_rendering_drivers_func(); static void register_macos_driver(); - DisplayServerMacOS(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error); + DisplayServerMacOS(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); ~DisplayServerMacOS(); }; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index f1078d9868fd..3ab9b59a5d1e 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -3299,8 +3299,8 @@ return OS::get_singleton()->is_layered_allowed(); } -DisplayServer *DisplayServerMacOS::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) { - DisplayServer *ds = memnew(DisplayServerMacOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, r_error)); +DisplayServer *DisplayServerMacOS::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { + DisplayServer *ds = memnew(DisplayServerMacOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error)); if (r_error != OK) { if (p_rendering_driver == "vulkan") { String executable_command; @@ -3494,7 +3494,7 @@ return closed; } -DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) { +DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { KeyMappingMacOS::initialize(); Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp index b2db62ea2fc7..6671eb028ea1 100644 --- a/platform/web/display_server_web.cpp +++ b/platform/web/display_server_web.cpp @@ -1025,11 +1025,11 @@ void DisplayServerWeb::_dispatch_input_event(const Ref &p_event) { } } -DisplayServer *DisplayServerWeb::create_func(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Context p_context, Error &r_error) { - return memnew(DisplayServerWeb(p_rendering_driver, p_window_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, r_error)); +DisplayServer *DisplayServerWeb::create_func(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { + return memnew(DisplayServerWeb(p_rendering_driver, p_window_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error)); } -DisplayServerWeb::DisplayServerWeb(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Context p_context, Error &r_error) { +DisplayServerWeb::DisplayServerWeb(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { r_error = OK; // Always succeeds for now. tts = GLOBAL_GET("audio/general/text_to_speech"); diff --git a/platform/web/display_server_web.h b/platform/web/display_server_web.h index c28a6fd08291..de0eb93238cc 100644 --- a/platform/web/display_server_web.h +++ b/platform/web/display_server_web.h @@ -148,7 +148,7 @@ class DisplayServerWeb : public DisplayServer { void process_keys(); static Vector get_rendering_drivers_func(); - static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error); + static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); static void _dispatch_input_event(const Ref &p_event); @@ -280,7 +280,7 @@ class DisplayServerWeb : public DisplayServer { virtual void swap_buffers() override; static void register_web_driver(); - DisplayServerWeb(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Context p_context, Error &r_error); + DisplayServerWeb(const String &p_rendering_driver, WindowMode p_window_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); ~DisplayServerWeb(); }; diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 26dad095adee..faf05264f297 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -136,6 +136,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const { case FEATURE_TEXT_TO_SPEECH: case FEATURE_SCREEN_CAPTURE: case FEATURE_STATUS_INDICATOR: + case FEATURE_WINDOW_EMBEDDING: return true; default: return false; @@ -1483,7 +1484,7 @@ DisplayServer::WindowID DisplayServerWindows::get_window_at_screen_position(cons DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, bool p_exclusive, WindowID p_transient_parent) { _THREAD_SAFE_METHOD_ - WindowID window_id = _create_window(p_mode, p_vsync_mode, p_flags, p_rect, p_exclusive, p_transient_parent); + WindowID window_id = _create_window(p_mode, p_vsync_mode, p_flags, p_rect, p_exclusive, p_transient_parent, NULL); ERR_FAIL_COND_V_MSG(window_id == INVALID_WINDOW_ID, INVALID_WINDOW_ID, "Failed to create sub window."); WindowData &wd = windows[window_id]; @@ -1810,7 +1811,6 @@ void DisplayServerWindows::window_set_mouse_passthrough(const Vector &p void DisplayServerWindows::_update_window_mouse_passthrough(WindowID p_window) { ERR_FAIL_COND(!windows.has(p_window)); - if (windows[p_window].mpass || windows[p_window].mpath.size() == 0) { SetWindowRgn(windows[p_window].hWnd, nullptr, FALSE); } else { @@ -1851,6 +1851,7 @@ void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_wi return; } const WindowData &wd = windows[p_window]; + ERR_FAIL_COND_MSG(wd.parent_hwnd, "Embedded window can't be moved to another screen."); if (wd.fullscreen) { Point2 pos = screen_get_position(p_screen) + _get_screens_origin(); Size2 size = screen_get_size(p_screen); @@ -1931,6 +1932,8 @@ void DisplayServerWindows::window_set_position(const Point2i &p_position, Window ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; + ERR_FAIL_COND_MSG(wd.parent_hwnd, "Embedded window can't be moved."); + if (wd.fullscreen || wd.maximized) { return; } @@ -2015,6 +2018,8 @@ void DisplayServerWindows::window_set_max_size(const Size2i p_size, WindowID p_w ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; + ERR_FAIL_COND_MSG(wd.parent_hwnd, "Embedded windows can't have a maximum size."); + if ((p_size != Size2()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) { ERR_PRINT("Maximum window size can't be smaller than minimum window size!"); return; @@ -2036,6 +2041,8 @@ void DisplayServerWindows::window_set_min_size(const Size2i p_size, WindowID p_w ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; + ERR_FAIL_COND_MSG(wd.parent_hwnd, "Embedded windows can't have a minimum size."); + if ((p_size != Size2()) && (wd.max_size != Size2()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) { ERR_PRINT("Minimum window size can't be larger than maximum window size!"); return; @@ -2057,6 +2064,8 @@ void DisplayServerWindows::window_set_size(const Size2i p_size, WindowID p_windo ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; + ERR_FAIL_COND_MSG(wd.parent_hwnd, "Embedded window can't be resized."); + if (wd.fullscreen || wd.maximized) { return; } @@ -2108,7 +2117,7 @@ Size2i DisplayServerWindows::window_get_size_with_decorations(WindowID p_window) return Size2(); } -void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_initialized, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_minimized, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) { +void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_initialized, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_minimized, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, bool p_embed_child, DWORD &r_style, DWORD &r_style_ex) { // Windows docs for window styles: // https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles // https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles @@ -2116,13 +2125,19 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_initiali r_style = 0; r_style_ex = WS_EX_WINDOWEDGE; if (p_main_window) { - r_style_ex |= WS_EX_APPWINDOW; + // When embedded, we don't want the window to have WS_EX_APPWINDOW because it will + // show the embedded process in the taskbar and Alt-Tab. + if (!p_embed_child) { + r_style_ex |= WS_EX_APPWINDOW; + } if (p_initialized) { r_style |= WS_VISIBLE; } } - if (p_fullscreen || p_borderless) { + if (p_embed_child) { + r_style |= WS_POPUP; + } else if (p_fullscreen || p_borderless) { r_style |= WS_POPUP; // p_borderless was WS_EX_TOOLWINDOW in the past. if (p_minimized) { r_style |= WS_MINIMIZE; @@ -2157,7 +2172,7 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_initiali } } - if (p_no_activate_focus) { + if (p_no_activate_focus && !p_embed_child) { r_style_ex |= WS_EX_TOPMOST | WS_EX_NOACTIVATE; } @@ -2178,7 +2193,7 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain DWORD style = 0; DWORD style_ex = 0; - _get_window_style(p_window == MAIN_WINDOW_ID, wd.initialized, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.minimized, wd.maximized, wd.maximized_fs, wd.no_focus || wd.is_popup, style, style_ex); + _get_window_style(p_window == MAIN_WINDOW_ID, wd.initialized, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.minimized, wd.maximized, wd.maximized_fs, wd.no_focus || wd.is_popup, wd.parent_hwnd, style, style_ex); SetWindowLongPtr(wd.hWnd, GWL_STYLE, style); SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex); @@ -2192,6 +2207,7 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain if (p_repaint) { RECT rect; GetWindowRect(wd.hWnd, &rect); + MoveWindow(wd.hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } } @@ -2202,6 +2218,8 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; + ERR_FAIL_COND_MSG(p_mode != WINDOW_MODE_WINDOWED && wd.parent_hwnd, "Embedded window only supports Windowed mode."); + if (wd.fullscreen && p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { RECT rect; @@ -2325,6 +2343,7 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W WindowData &wd = windows[p_window]; switch (p_flag) { case WINDOW_FLAG_RESIZE_DISABLED: { + ERR_FAIL_COND_MSG(p_enabled && wd.parent_hwnd, "Embedded window resize can't be disabled."); wd.resizable = !p_enabled; _update_window_style(p_window); } break; @@ -2335,7 +2354,8 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W ShowWindow(wd.hWnd, (wd.no_focus || wd.is_popup) ? SW_SHOWNOACTIVATE : SW_SHOW); // Show the window. } break; case WINDOW_FLAG_ALWAYS_ON_TOP: { - ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID && p_enabled, "Transient windows can't become on top"); + ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID && p_enabled, "Transient windows can't become on top."); + ERR_FAIL_COND_MSG(p_enabled && wd.parent_hwnd, "Embedded window can't become on top."); wd.always_on_top = p_enabled; _update_window_style(p_window); } break; @@ -2383,6 +2403,7 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W case WINDOW_FLAG_POPUP: { ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup."); ERR_FAIL_COND_MSG(IsWindowVisible(wd.hWnd) && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened."); + ERR_FAIL_COND_MSG(p_enabled && wd.parent_hwnd, "Embedded window can't be popup."); wd.is_popup = p_enabled; } break; default: @@ -2731,6 +2752,183 @@ void DisplayServerWindows::enable_for_stealing_focus(OS::ProcessID pid) { AllowSetForegroundWindow(pid); } +struct WindowEnumData { + DWORD process_id; + HWND parent_hWnd; + HWND hWnd; +}; + +static BOOL CALLBACK _enum_proc_find_window_from_process_id_callback(HWND hWnd, LPARAM lParam) { + WindowEnumData &ed = *(WindowEnumData *)lParam; + DWORD process_id = 0x0; + + GetWindowThreadProcessId(hWnd, &process_id); + if (ed.process_id == process_id) { + if (GetParent(hWnd) != ed.parent_hWnd) { + const DWORD style = GetWindowLongPtr(hWnd, GWL_STYLE); + if ((style & WS_VISIBLE) != WS_VISIBLE) { + return TRUE; + } + } + + // Found it. + ed.hWnd = hWnd; + SetLastError(ERROR_SUCCESS); + return FALSE; + } + // Continue enumeration. + return TRUE; +} + +HWND DisplayServerWindows::_find_window_from_process_id(OS::ProcessID p_pid, HWND p_current_hwnd) { + DWORD pid = p_pid; + WindowEnumData ed = { pid, p_current_hwnd, NULL }; + + // First, check our own child, maybe it's already embedded. + if (!EnumChildWindows(p_current_hwnd, _enum_proc_find_window_from_process_id_callback, (LPARAM)&ed) && (GetLastError() == ERROR_SUCCESS)) { + if (ed.hWnd) { + return ed.hWnd; + } + } + + // Then check all the opened windows on the computer. + if (!EnumWindows(_enum_proc_find_window_from_process_id_callback, (LPARAM)&ed) && (GetLastError() == ERROR_SUCCESS)) { + return ed.hWnd; + } + + return NULL; +} + +Error DisplayServerWindows::embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), FAILED); + + const WindowData &wd = windows[p_window]; + + EmbeddedProcessData *ep = nullptr; + if (embedded_processes.has(p_pid)) { + ep = embedded_processes.get(p_pid); + } else { + // New process, trying to find the window. + HWND handle_to_embed = _find_window_from_process_id(p_pid, wd.hWnd); + if (!handle_to_embed) { + return ERR_DOES_NOT_EXIST; + } + + const DWORD style = GetWindowLongPtr(handle_to_embed, GWL_STYLE); + + ep = memnew(EmbeddedProcessData); + ep->window_handle = handle_to_embed; + ep->parent_window_handle = wd.hWnd; + ep->is_visible = (style & WS_VISIBLE) == WS_VISIBLE; + + embedded_processes.insert(p_pid, ep); + + HWND old_parent = GetParent(ep->window_handle); + if (old_parent != wd.hWnd) { + // It's important that the window does not have the WS_CHILD flag + // to prevent the current process from interfering with the embedded process. + // I observed lags and issues with mouse capture when WS_CHILD is set. + // Additionally, WS_POPUP must be set to ensure that the coordinates of the embedded + // window remain screen coordinates and not local coordinates of the parent window. + if ((style & WS_CHILD) == WS_CHILD || (style & WS_POPUP) != WS_POPUP) { + const DWORD new_style = (style & ~WS_CHILD) | WS_POPUP; + SetWindowLong(ep->window_handle, GWL_STYLE, new_style); + } + // Set the parent to current window. + SetParent(ep->window_handle, wd.hWnd); + } + } + + if (p_rect.size.x < 100 || p_rect.size.y < 100) { + p_visible = false; + } + + // In Godot, the window position is offset by the screen's origin coordinates. + // We need to adjust for this when a screen is positioned in the negative space + // (e.g., a screen to the left of the main screen). + const Rect2i adjusted_rect = Rect2i(p_rect.position + _get_screens_origin(), p_rect.size); + + SetWindowPos(ep->window_handle, HWND_BOTTOM, adjusted_rect.position.x, adjusted_rect.position.y, adjusted_rect.size.x, adjusted_rect.size.y, SWP_NOZORDER | SWP_NOACTIVATE); + + if (ep->is_visible != p_visible) { + if (p_visible) { + ShowWindow(ep->window_handle, SW_SHOWNA); + } else { + ShowWindow(ep->window_handle, SW_HIDE); + } + ep->is_visible = p_visible; + } + + if (p_grab_focus) { + SetFocus(ep->window_handle); + } + + return OK; +} + +Error DisplayServerWindows::remove_embedded_process(OS::ProcessID p_pid) { + _THREAD_SAFE_METHOD_ + + if (!embedded_processes.has(p_pid)) { + return ERR_DOES_NOT_EXIST; + } + + EmbeddedProcessData *ep = embedded_processes.get(p_pid); + + // This is a workaround to ensure the parent window correctly regains focus after the + // embedded window is closed. When the embedded window is closed while it has focus, + // the parent window (the editor) does not become active. It appears focused but is not truly activated. + // Opening a new window and closing it forces Windows to set the focus and activation correctly. + DWORD style = WS_POPUP | WS_VISIBLE; + DWORD style_ex = WS_EX_TOPMOST; + + WNDCLASSW wcTemp = {}; + wcTemp.lpfnWndProc = DefWindowProcW; + wcTemp.hInstance = GetModuleHandle(nullptr); + wcTemp.lpszClassName = L"Engine temp window"; + RegisterClassW(&wcTemp); + + HWND hWnd = CreateWindowExW( + style_ex, + L"Engine temp window", L"", + style, + 0, + 0, + 1, + 1, + ep->parent_window_handle, + nullptr, + GetModuleHandle(nullptr), + nullptr); + + SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE); + + DestroyWindow(hWnd); + UnregisterClassW(L"Engine temp window", GetModuleHandle(nullptr)); + + SetForegroundWindow(ep->parent_window_handle); + + embedded_processes.erase(p_pid); + memdelete(ep); + + return OK; +} + +OS::ProcessID DisplayServerWindows::get_focused_process_id() { + HWND hwnd = GetForegroundWindow(); + if (!hwnd) { + return 0; + } + + // Get the process ID of the window. + DWORD processID; + GetWindowThreadProcessId(hwnd, &processID); + + return processID; +} + static HRESULT CALLBACK win32_task_dialog_callback(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData) { if (msg == TDN_CREATED) { // To match the input text dialog. @@ -4086,6 +4284,12 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA if (windows[window_id].no_focus || windows[window_id].is_popup) { return MA_NOACTIVATE; // Do not activate, but process mouse messages. } + // When embedded, the window is a child of the parent and is not activated + // by default because it lacks native controls. + if (windows[window_id].parent_hwnd) { + SetFocus(windows[window_id].hWnd); + return MA_ACTIVATE; + } } break; case WM_ACTIVATEAPP: { bool new_app_focused = (bool)wParam; @@ -4102,8 +4306,15 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // Therefore, it's safer to defer the delivery of the event. // It's important to set an nIDEvent different from the SetTimer for move_timer_id because // if the same nIDEvent is passed, the timer is replaced and the same timer_id is returned. - windows[window_id].activate_timer_id = SetTimer(windows[window_id].hWnd, DisplayServerWindows::TIMER_ID_WINDOW_ACTIVATION, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); + // The problem with the timer is that the window cannot be resized or the buttons cannot be used correctly + // if the window is not activated first. This happens because the code in the activation process runs + // after the mouse click is handled. To address this, the timer is now used only when the window is created. windows[window_id].activate_state = GET_WM_ACTIVATE_STATE(wParam, lParam); + if (windows[window_id].first_activation_done) { + _process_activate_event(window_id); + } else { + windows[window_id].activate_timer_id = SetTimer(windows[window_id].hWnd, DisplayServerWindows::TIMER_ID_WINDOW_ACTIVATION, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); + } return 0; } break; case WM_GETMINMAXINFO: { @@ -5157,6 +5368,16 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA ClientToScreen(window.hWnd, (POINT *)&crect.right); ClipCursor(&crect); } + } else { + if (window.parent_hwnd) { + // WM_WINDOWPOSCHANGED is sent when the parent changes. + // If we are supposed to have a parent and now we don't, it's likely + // because the parent was closed. We will close our window as well. + // This prevents an embedded game from staying alive when the editor is closed or crashes. + if (!GetParent(window.hWnd)) { + SendMessage(window.hWnd, WM_CLOSE, 0, 0); + } + } } // Return here to prevent WM_MOVE and WM_SIZE from being sent @@ -5184,6 +5405,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA _process_activate_event(window_id); KillTimer(windows[window_id].hWnd, windows[window_id].activate_timer_id); windows[window_id].activate_timer_id = 0; + windows[window_id].first_activation_done = true; } } break; case WM_SYSKEYUP: @@ -5572,11 +5794,11 @@ void DisplayServerWindows::_update_tablet_ctx(const String &p_old_driver, const } } -DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, bool p_exclusive, WindowID p_transient_parent) { +DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, bool p_exclusive, WindowID p_transient_parent, HWND p_parent_hwnd) { DWORD dwExStyle; DWORD dwStyle; - _get_window_style(window_id_counter == MAIN_WINDOW_ID, false, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MINIMIZED, p_mode == WINDOW_MODE_MAXIMIZED, false, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), dwStyle, dwExStyle); + _get_window_style(window_id_counter == MAIN_WINDOW_ID, false, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MINIMIZED, p_mode == WINDOW_MODE_MAXIMIZED, false, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), p_parent_hwnd, dwStyle, dwExStyle); RECT WindowRect; @@ -5590,41 +5812,45 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, rq_screen = get_primary_screen(); // Requested window rect is outside any screen bounds. } - if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { - Rect2i screen_rect = Rect2i(screen_get_position(rq_screen), screen_get_size(rq_screen)); + if (!p_parent_hwnd) { + if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { + Rect2i screen_rect = Rect2i(screen_get_position(rq_screen), screen_get_size(rq_screen)); - WindowRect.left = screen_rect.position.x; - WindowRect.right = screen_rect.position.x + screen_rect.size.x; - WindowRect.top = screen_rect.position.y; - WindowRect.bottom = screen_rect.position.y + screen_rect.size.y; - } else { - Rect2i srect = screen_get_usable_rect(rq_screen); - Point2i wpos = p_rect.position; - if (srect != Rect2i()) { - wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3); - } + WindowRect.left = screen_rect.position.x; + WindowRect.right = screen_rect.position.x + screen_rect.size.x; + WindowRect.top = screen_rect.position.y; + WindowRect.bottom = screen_rect.position.y + screen_rect.size.y; + } else { + Rect2i srect = screen_get_usable_rect(rq_screen); + Point2i wpos = p_rect.position; + if (srect != Rect2i()) { + wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3); + } - WindowRect.left = wpos.x; - WindowRect.right = wpos.x + p_rect.size.x; - WindowRect.top = wpos.y; - WindowRect.bottom = wpos.y + p_rect.size.y; - } + WindowRect.left = wpos.x; + WindowRect.right = wpos.x + p_rect.size.x; + WindowRect.top = wpos.y; + WindowRect.bottom = wpos.y + p_rect.size.y; + } - Point2i offset = _get_screens_origin(); - WindowRect.left += offset.x; - WindowRect.right += offset.x; - WindowRect.top += offset.y; - WindowRect.bottom += offset.y; + Point2i offset = _get_screens_origin(); + WindowRect.left += offset.x; + WindowRect.right += offset.x; + WindowRect.top += offset.y; + WindowRect.bottom += offset.y; - if (p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { - AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); + if (p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { + AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); + } } WindowID id = window_id_counter; { WindowData *wd_transient_parent = nullptr; HWND owner_hwnd = nullptr; - if (p_transient_parent != INVALID_WINDOW_ID) { + if (p_parent_hwnd) { + owner_hwnd = p_parent_hwnd; + } else if (p_transient_parent != INVALID_WINDOW_ID) { if (!windows.has(p_transient_parent)) { ERR_PRINT("Condition \"!windows.has(p_transient_parent)\" is true."); p_transient_parent = INVALID_WINDOW_ID; @@ -5658,6 +5884,9 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, windows.erase(id); ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Failed to create Windows OS window."); } + + wd.parent_hwnd = p_parent_hwnd; + if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { wd.fullscreen = true; if (p_mode == WINDOW_MODE_FULLSCREEN) { @@ -6006,7 +6235,7 @@ void DisplayServerWindows::tablet_set_current_driver(const String &p_driver) { } } -DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) { +DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { KeyMappingWindows::initialize(); tested_drivers.clear(); @@ -6407,7 +6636,13 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win window_position = scr_rect.position + (scr_rect.size - p_resolution) / 2; } - WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution), false, INVALID_WINDOW_ID); + HWND parent_hwnd = NULL; + if (p_parent_window) { + // Parented window. + parent_hwnd = (HWND)p_parent_window; + } + + WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution), false, INVALID_WINDOW_ID, parent_hwnd); if (main_window == INVALID_WINDOW_ID) { r_error = ERR_UNAVAILABLE; ERR_FAIL_MSG("Failed to create main window."); @@ -6485,8 +6720,8 @@ Vector DisplayServerWindows::get_rendering_drivers_func() { return drivers; } -DisplayServer *DisplayServerWindows::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) { - DisplayServer *ds = memnew(DisplayServerWindows(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, r_error)); +DisplayServer *DisplayServerWindows::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { + DisplayServer *ds = memnew(DisplayServerWindows(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error)); if (r_error != OK) { if (tested_drivers == 0) { OS::get_singleton()->alert("Failed to register the window class.", "Unable to initialize DisplayServer"); diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 0462d3f8fa7a..a660b1066ac1 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -472,6 +472,7 @@ class DisplayServerWindows : public DisplayServer { bool resizable = true; bool window_focused = false; int activate_state = 0; + bool first_activation_done = false; bool was_maximized = false; bool always_on_top = false; bool no_focus = false; @@ -535,6 +536,8 @@ class DisplayServerWindows : public DisplayServer { Rect2i parent_safe_rect; bool initialized = false; + + HWND parent_hwnd = 0; }; JoypadWindows *joypad = nullptr; @@ -543,7 +546,7 @@ class DisplayServerWindows : public DisplayServer { uint64_t time_since_popup = 0; Ref icon; - WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, bool p_exclusive, WindowID p_transient_parent); + WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect, bool p_exclusive, WindowID p_transient_parent, HWND p_parent_hwnd); WindowID window_id_counter = MAIN_WINDOW_ID; RBMap windows; @@ -602,7 +605,7 @@ class DisplayServerWindows : public DisplayServer { HashMap pointer_last_pos; void _send_window_event(const WindowData &wd, WindowEvent p_event); - void _get_window_style(bool p_main_window, bool p_initialized, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_minimized, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex); + void _get_window_style(bool p_main_window, bool p_initialized, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_minimized, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, bool p_embed_child, DWORD &r_style, DWORD &r_style_ex); MouseMode mouse_mode; int restore_mouse_trails = 0; @@ -656,6 +659,15 @@ class DisplayServerWindows : public DisplayServer { String _get_keyboard_layout_display_name(const String &p_klid) const; String _get_klid(HKL p_hkl) const; + struct EmbeddedProcessData { + HWND window_handle = 0; + HWND parent_window_handle = 0; + bool is_visible = false; + }; + HashMap embedded_processes; + + HWND _find_window_from_process_id(OS::ProcessID p_pid, HWND p_current_hwnd); + public: LRESULT WndProcFileDialog(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); @@ -796,6 +808,9 @@ class DisplayServerWindows : public DisplayServer { virtual bool get_swap_cancel_ok() override; virtual void enable_for_stealing_focus(OS::ProcessID pid) override; + virtual Error embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) override; + virtual Error remove_embedded_process(OS::ProcessID p_pid) override; + virtual OS::ProcessID get_focused_process_id() override; virtual Error dialog_show(String p_title, String p_description, Vector p_buttons, const Callable &p_callback) override; virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override; @@ -835,11 +850,11 @@ class DisplayServerWindows : public DisplayServer { virtual bool is_window_transparency_available() const override; - static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error); + static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); static Vector get_rendering_drivers_func(); static void register_windows_driver(); - DisplayServerWindows(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error); + DisplayServerWindows(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); ~DisplayServerWindows(); }; diff --git a/scene/main/node.h b/scene/main/node.h index e2f3ce9b7801..f75a7a03333d 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -425,6 +425,7 @@ class Node : public Object { NOTIFICATION_WM_DPI_CHANGE = 1009, NOTIFICATION_VP_MOUSE_ENTER = 1010, NOTIFICATION_VP_MOUSE_EXIT = 1011, + NOTIFICATION_WM_POSITION_CHANGED = 1012, NOTIFICATION_OS_MEMORY_WARNING = MainLoop::NOTIFICATION_OS_MEMORY_WARNING, NOTIFICATION_TRANSLATION_CHANGED = MainLoop::NOTIFICATION_TRANSLATION_CHANGED, diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 05904fa8f999..8f37782e862b 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -703,7 +703,11 @@ void Window::_rect_changed_callback(const Rect2i &p_callback) { if (size == p_callback.size && position == p_callback.position) { return; } - position = p_callback.position; + + if (position != p_callback.position) { + position = p_callback.position; + _propagate_window_notification(this, NOTIFICATION_WM_POSITION_CHANGED); + } if (size != p_callback.size) { size = p_callback.size; @@ -1071,14 +1075,17 @@ void Window::_update_window_size() { embedder->_sub_window_update(this); } else if (window_id != DisplayServer::INVALID_WINDOW_ID) { - if (reset_min_first && wrap_controls) { - // Avoid an error if setting max_size to a value between min_size and the previous size_limit. - DisplayServer::get_singleton()->window_set_min_size(Size2i(), window_id); - } + // When main window embedded in the editor, we can't resize the main window. + if (window_id != DisplayServer::MAIN_WINDOW_ID || !Engine::get_singleton()->is_embedded_in_editor()) { + if (reset_min_first && wrap_controls) { + // Avoid an error if setting max_size to a value between min_size and the previous size_limit. + DisplayServer::get_singleton()->window_set_min_size(Size2i(), window_id); + } - DisplayServer::get_singleton()->window_set_max_size(max_size_used, window_id); - DisplayServer::get_singleton()->window_set_min_size(size_limit, window_id); - DisplayServer::get_singleton()->window_set_size(size, window_id); + DisplayServer::get_singleton()->window_set_max_size(max_size_used, window_id); + DisplayServer::get_singleton()->window_set_min_size(size_limit, window_id); + DisplayServer::get_singleton()->window_set_size(size, window_id); + } } //update the viewport diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 82ac62bc9fae..1567d6554b00 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -657,6 +657,21 @@ bool DisplayServer::get_swap_cancel_ok() { void DisplayServer::enable_for_stealing_focus(OS::ProcessID pid) { } +Error DisplayServer::embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) { + WARN_PRINT("Embedded process not supported by this display server."); + return ERR_UNAVAILABLE; +} + +Error DisplayServer::remove_embedded_process(OS::ProcessID p_pid) { + WARN_PRINT("Embedded process not supported by this display server."); + return ERR_UNAVAILABLE; +} + +OS::ProcessID DisplayServer::get_focused_process_id() { + WARN_PRINT("Embedded process not supported by this display server."); + return 0; +} + Error DisplayServer::dialog_show(String p_title, String p_description, Vector p_buttons, const Callable &p_callback) { WARN_PRINT("Native dialogs not supported by this display server."); return ERR_UNAVAILABLE; @@ -1057,6 +1072,7 @@ void DisplayServer::_bind_methods() { BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_INPUT); BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE); BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE_EXTRA); + BIND_ENUM_CONSTANT(FEATURE_WINDOW_EMBEDDING); BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE); BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN); @@ -1207,9 +1223,9 @@ Vector DisplayServer::get_create_function_rendering_drivers(int p_index) return server_create_functions[p_index].get_rendering_drivers_function(); } -DisplayServer *DisplayServer::create(int p_index, const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) { +DisplayServer *DisplayServer::create(int p_index, const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { ERR_FAIL_INDEX_V(p_index, server_create_count, nullptr); - return server_create_functions[p_index].create_function(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, r_error); + return server_create_functions[p_index].create_function(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, p_parent_window, r_error); } void DisplayServer::_input_set_mouse_mode(Input::MouseMode p_mode) { diff --git a/servers/display_server.h b/servers/display_server.h index 916c006f0113..ed8df1885632 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -92,7 +92,7 @@ class DisplayServer : public Object { CONTEXT_ENGINE, }; - typedef DisplayServer *(*CreateFunction)(const String &, WindowMode, VSyncMode, uint32_t, const Point2i *, const Size2i &, int p_screen, Context, Error &r_error); + typedef DisplayServer *(*CreateFunction)(const String &, WindowMode, VSyncMode, uint32_t, const Point2i *, const Size2i &, int p_screen, Context, int64_t p_parent_window, Error &r_error); typedef Vector (*GetRenderingDriversFunction)(); private: @@ -153,6 +153,7 @@ class DisplayServer : public Object { FEATURE_NATIVE_DIALOG_INPUT, FEATURE_NATIVE_DIALOG_FILE, FEATURE_NATIVE_DIALOG_FILE_EXTRA, + FEATURE_WINDOW_EMBEDDING, }; virtual bool has_feature(Feature p_feature) const = 0; @@ -543,6 +544,10 @@ class DisplayServer : public Object { virtual void enable_for_stealing_focus(OS::ProcessID pid); + virtual Error embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus); + virtual Error remove_embedded_process(OS::ProcessID p_pid); + virtual OS::ProcessID get_focused_process_id(); + virtual Error dialog_show(String p_title, String p_description, Vector p_buttons, const Callable &p_callback); virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback); @@ -600,7 +605,7 @@ class DisplayServer : public Object { static int get_create_function_count(); static const char *get_create_function_name(int p_index); static Vector get_create_function_rendering_drivers(int p_index); - static DisplayServer *create(int p_index, const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error); + static DisplayServer *create(int p_index, const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error); enum RenderingDeviceCreationStatus { UNKNOWN, diff --git a/servers/display_server_headless.h b/servers/display_server_headless.h index 5f53e762352b..12c174ae2b05 100644 --- a/servers/display_server_headless.h +++ b/servers/display_server_headless.h @@ -45,7 +45,7 @@ class DisplayServerHeadless : public DisplayServer { return drivers; } - static DisplayServer *create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) { + static DisplayServer *create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { r_error = OK; RasterizerDummy::make_current(); return memnew(DisplayServerHeadless()); diff --git a/tests/display_server_mock.h b/tests/display_server_mock.h index b44ff06b3546..d2bdb5183bee 100644 --- a/tests/display_server_mock.h +++ b/tests/display_server_mock.h @@ -55,7 +55,7 @@ class DisplayServerMock : public DisplayServerHeadless { return drivers; } - static DisplayServer *create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) { + static DisplayServer *create_func(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error) { r_error = OK; RasterizerDummy::make_current(); return memnew(DisplayServerMock()); diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 65d45ae92f9d..88df3d54bccd 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -285,7 +285,7 @@ struct GodotTestCaseListener : public doctest::IReporter { OS::get_singleton()->set_has_server_feature_callback(nullptr); for (int i = 0; i < DisplayServer::get_create_function_count(); i++) { if (String("mock") == DisplayServer::get_create_function_name(i)) { - DisplayServer::create(i, "", DisplayServer::WindowMode::WINDOW_MODE_MINIMIZED, DisplayServer::VSyncMode::VSYNC_ENABLED, 0, nullptr, Vector2i(0, 0), DisplayServer::SCREEN_PRIMARY, DisplayServer::CONTEXT_EDITOR, err); + DisplayServer::create(i, "", DisplayServer::WindowMode::WINDOW_MODE_MINIMIZED, DisplayServer::VSyncMode::VSYNC_ENABLED, 0, nullptr, Vector2i(0, 0), DisplayServer::SCREEN_PRIMARY, DisplayServer::CONTEXT_EDITOR, 0, err); break; } }