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..3ce6937d11f2 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 game is running embedded in the editor, otherwise returns [code]false[/code]. This is useful to prevent attempting to update window mode or window flags that are not supported when running 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..5c0bc67bd104 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,13 @@ void EditorRun::stop() {
running_scene = "";
}
+OS::ProcessID EditorRun::get_current_process() const {
+ if (pids.is_empty()) {
+ return 0;
+ }
+ return pids.get(0);
+}
+
EditorRun::EditorRun() {
status = STATUS_STOP;
running_scene = "";
diff --git a/editor/editor_run.h b/editor/editor_run.h
index bd6770ae3dbb..0a5953eb7428 100644
--- a/editor/editor_run.h
+++ b/editor/editor_run.h
@@ -32,6 +32,9 @@
#define EDITOR_RUN_H
#include "core/os/os.h"
+#include "servers/display_server.h"
+
+typedef void (*EditorRunInstanceStarting)(int p_index, List &r_arguments);
class EditorRun {
public:
@@ -48,6 +51,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 +63,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..9fdbcab8aec2 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);
}
@@ -356,14 +360,39 @@ HBoxContainer *EditorRunBar::get_buttons_container() {
return main_hbox;
}
+void EditorRunBar::_instance_starting(int p_idx, List &r_arguments) {
+ singleton->_instance_starting_internal(p_idx, r_arguments);
+}
+
+void EditorRunBar::_instance_starting_internal(int p_index, List &r_arguments) {
+ Array arguments;
+
+ // We need to convert the an Array so it can be passed as parameter in a signal.
+ for (const String &arg : r_arguments) {
+ arguments.push_back(arg);
+ }
+
+ emit_signal(SNAME("instance_starting"), p_index, arguments);
+
+ // Copy back to a List.
+ r_arguments.clear();
+ for (const Variant &arg : arguments) {
+ r_arguments.push_back(arg);
+ }
+}
+
void EditorRunBar::_bind_methods() {
ADD_SIGNAL(MethodInfo("play_pressed"));
+ ADD_SIGNAL(MethodInfo("instance_starting", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::ARRAY, "arguments")));
ADD_SIGNAL(MethodInfo("stop_pressed"));
}
EditorRunBar::EditorRunBar() {
singleton = this;
+ // Callback from EditorRun to propagate the instance_starting signal.
+ editor_run.instance_starting_callback = _instance_starting;
+
main_panel = memnew(PanelContainer);
add_child(main_panel);
diff --git a/editor/gui/editor_run_bar.h b/editor/gui/editor_run_bar.h
index d8238aa2c541..511250386cdf 100644
--- a/editor/gui/editor_run_bar.h
+++ b/editor/gui/editor_run_bar.h
@@ -83,6 +83,9 @@ class EditorRunBar : public MarginContainer {
void _run_scene(const String &p_scene_path = "");
void _run_native(const Ref &p_preset);
+ static void _instance_starting(int p_idx, List &r_arguments);
+ void _instance_starting_internal(int p_index, List &r_arguments);
+
protected:
void _notification(int p_what);
static void _bind_methods();
@@ -102,6 +105,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..e775444c0da7
--- /dev/null
+++ b/editor/plugins/embedded_process.cpp
@@ -0,0 +1,351 @@
+/**************************************************************************/
+/* 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 = this->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_embedding_timeout(int p_timeout) {
+ _embedding_timeout = p_timeout;
+}
+
+int EmbeddedProcess::get_embedding_timeout() {
+ return _embedding_timeout;
+}
+
+void EmbeddedProcess::set_window_size(Size2i p_window_size) {
+ if (_window_size != p_window_size) {
+ _window_size = p_window_size;
+ _queue_update_embedded_process();
+ }
+}
+
+Size2i EmbeddedProcess::get_window_size() {
+ return _window_size;
+}
+
+void EmbeddedProcess::set_keep_aspect(bool p_keep_aspect) {
+ if (_keep_aspect != p_keep_aspect) {
+ _keep_aspect = p_keep_aspect;
+ _queue_update_embedded_process();
+ }
+}
+
+bool EmbeddedProcess::get_keep_aspect() {
+ return _keep_aspect;
+}
+
+Rect2i EmbeddedProcess::get_global_embedded_window_rect() {
+ Rect2i control_rect = this->get_global_rect();
+ control_rect = Rect2i(control_rect.position, Size2i(MAX(control_rect.size.x, 1), MAX(control_rect.size.y, 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(MAX(_window_size.x * ratio, 1), MAX(_window_size.y * ratio, 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);
+}
+
+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() {
+ ClassDB::bind_method(D_METHOD("embed_process", "process_id"), &EmbeddedProcess::embed_process);
+ ClassDB::bind_method(D_METHOD("reset"), &EmbeddedProcess::reset);
+ ClassDB::bind_method(D_METHOD("set_embedding_timeout", "timeout"), &EmbeddedProcess::set_embedding_timeout);
+ ClassDB::bind_method(D_METHOD("get_embedding_timeout"), &EmbeddedProcess::get_embedding_timeout);
+ ClassDB::bind_method(D_METHOD("is_embedding_completed"), &EmbeddedProcess::is_embedding_completed);
+ ClassDB::bind_method(D_METHOD("is_embedding_in_progress"), &EmbeddedProcess::is_embedding_in_progress);
+
+ ADD_SIGNAL(MethodInfo("embedding_completed"));
+ ADD_SIGNAL(MethodInfo("embedding_failed"));
+}
+
+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..4f6a99575ff7
--- /dev/null
+++ b/editor/plugins/embedded_process.h
@@ -0,0 +1,93 @@
+/**************************************************************************/
+/* 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;
+
+ 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();
+
+protected:
+ static void _bind_methods();
+ void _notification(int p_what);
+
+public:
+ void embed_process(OS::ProcessID p_pid);
+ void reset();
+
+ void set_embedding_timeout(int p_timeout);
+ int get_embedding_timeout();
+ void set_window_size(Size2i p_window_size);
+ Size2i get_window_size();
+ void set_keep_aspect(bool p_keep_aspect);
+ bool get_keep_aspect();
+ Rect2i get_global_embedded_window_rect();
+ 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..0febf21f67e4 100644
--- a/editor/plugins/game_view_plugin.cpp
+++ b/editor/plugins/game_view_plugin.cpp
@@ -30,15 +30,19 @@
#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/themes/editor_scale.h"
#include "scene/gui/button.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/panel.h"
-#include "scene/gui/separator.h"
void GameViewDebugger::_session_started(Ref p_session) {
Array setup_data;
@@ -184,6 +188,69 @@ void GameView::_sessions_changed() {
_update_debugger_buttons();
}
+void GameView::_instance_starting(int p_idx, Array p_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, p_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::_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 +296,63 @@ 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() {
+ 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("");
+ } 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."));
+ }
+}
+
+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 +411,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("instance_starting", callable_mp(this, &GameView::_instance_starting));
+ EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &GameView::_stop_pressed));
+
+ // 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 +487,89 @@ 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, Array p_arguments) {
+ if (p_idx != 0 || !embed_on_play || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) {
+ return;
+ }
+
+ // Remove duplicates/unwanted parameters.
+ int index = p_arguments.find("--position");
+ if (index >= 0) {
+ // Remove the --position and its value.
+ p_arguments.remove_at(index + 1);
+ p_arguments.remove_at(index);
+ }
+ index = p_arguments.find("--resolution");
+ if (index >= 0) {
+ // Remove the --resolution and its value.
+ p_arguments.remove_at(index + 1);
+ p_arguments.remove_at(index);
+ }
+ index = p_arguments.find("--screen");
+ if (index >= 0) {
+ // Remove the --screen and its value.
+ p_arguments.remove_at(index + 1);
+ p_arguments.remove_at(index);
+ }
+ p_arguments.erase("-f");
+ p_arguments.erase("--fullscreen");
+ p_arguments.erase("-m");
+ p_arguments.erase("--maximized");
+ p_arguments.erase("-t");
+ p_arguments.erase("--always-on-top");
+
+ // Add the editor window's native ID so the started game can directly set it as its parent.
+ p_arguments.push_back("--wid");
+ p_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();
+ p_arguments.push_back("--position");
+ p_arguments.push_back(itos(rect.position.x) + "," + itos(rect.position.y));
+ p_arguments.push_back("--resolution");
+ p_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) {
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 +677,79 @@ 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);
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(SNAME("embedding_failed"), callable_mp(this, &GameView::_embedding_failed));
+ embedded_process->connect(SNAME("embedding_completed"), callable_mp(this, &GameView::_embedding_completed));
+ 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 +764,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..9e6cf81f2160 100644
--- a/editor/plugins/game_view_plugin.h
+++ b/editor/plugins/game_view_plugin.h
@@ -34,8 +34,13 @@
#include "editor/debugger/editor_debugger_node.h"
#include "editor/plugins/editor_debugger_plugin.h"
#include "editor/plugins/editor_plugin.h"
+#include "editor/plugins/embedded_process.h"
+#include "editor/window_wrapper.h"
#include "scene/debugger/scene_debugger.h"
#include "scene/gui/box_container.h"
+#include "scene/gui/dialogs.h"
+#include "scene/gui/label.h"
+#include "scene/gui/separator.h"
class GameViewDebugger : public EditorDebuggerPlugin {
GDCLASS(GameViewDebugger, EditorDebuggerPlugin);
@@ -82,11 +87,22 @@ class GameView : public VBoxContainer {
CAMERA_RESET_3D,
CAMERA_MODE_INGAME,
CAMERA_MODE_EDITORS,
+ EMBED_RUN_GAME_EMBEDDED,
+ EMBED_MAKE_FLOATING_ON_PLAY
};
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 +115,15 @@ class GameView : public VBoxContainer {
Button *camera_override_button = nullptr;
MenuButton *camera_override_menu = nullptr;
+ VSeparator *embedding_separator;
+ Button *keep_aspect_button;
+ MenuButton *embed_options_menu = nullptr;
+
+ Button *make_floating = nullptr;
+
Panel *panel = nullptr;
+ EmbeddedProcess *embedded_process = nullptr;
+ Label *state_label = nullptr;
void _sessions_changed();
@@ -109,12 +133,29 @@ 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();
+ void _instance_starting(int p_idx, Array p_arguments);
+ void _stop_pressed();
+ void _embedding_completed();
+ void _embedding_failed();
+ 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, Array p_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 +163,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 +192,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..736ef0b9fe65 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("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..b5c2c88f6115 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 support 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..bd4800e7baa5 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_embbed_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_embbed_child) {
+ r_style_ex |= WS_EX_APPWINDOW;
+ }
if (p_initialized) {
r_style |= WS_VISIBLE;
}
}
- if (p_fullscreen || p_borderless) {
+ if (p_embbed_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_embbed_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 support 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,182 @@ 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 embed.
+ 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 wc = {};
+ wc.lpfnWndProc = DefWindowProcW;
+ wc.hInstance = GetModuleHandle(nullptr);
+ wc.lpszClassName = L"Engine temp window";
+ RegisterClassW(&wc);
+
+ 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);
+
+ 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 +4283,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 +4305,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);
- windows[window_id].activate_state = GET_WM_ACTIVATE_STATE(wParam, lParam);
+ // 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.
+ 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);
+ windows[window_id].activate_state = GET_WM_ACTIVATE_STATE(wParam, lParam);
+ }
return 0;
} break;
case WM_GETMINMAXINFO: {
@@ -5157,6 +5367,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 +5404,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 +5793,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 +5811,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 +5883,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 +6234,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 +6635,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 +6719,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..ff5ce641d518 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_embbed_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..f23780fa2c57 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;
@@ -399,7 +400,7 @@ class DisplayServer : public Object {
WINDOW_FLAG_POPUP_BIT = (1 << WINDOW_FLAG_POPUP),
WINDOW_FLAG_EXTEND_TO_TITLE_BIT = (1 << WINDOW_FLAG_EXTEND_TO_TITLE),
WINDOW_FLAG_MOUSE_PASSTHROUGH_BIT = (1 << WINDOW_FLAG_MOUSE_PASSTHROUGH),
- WINDOW_FLAG_SHARP_CORNERS_BIT = (1 << WINDOW_FLAG_SHARP_CORNERS),
+ WINDOW_FLAG_SHARP_CORNERS_BIT = (1 << WINDOW_FLAG_SHARP_CORNERS)
};
virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i(), bool p_exclusive = false, WindowID p_transient_parent = INVALID_WINDOW_ID);
@@ -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;
}
}