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; } }