From 6bd1fafbc4721eb9895f6ce4f019ba7449ba0c95 Mon Sep 17 00:00:00 2001 From: Hilderin <81109165+Hilderin@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:57:23 -0400 Subject: [PATCH] Embedding game process in editor --- core/config/engine.cpp | 8 + core/config/engine.h | 3 + core/core_bind.cpp | 9 + core/core_bind.h | 3 + doc/classes/DisplayServer.xml | 9 +- doc/classes/Engine.xml | 6 + doc/classes/Window.xml | 11 +- editor/editor_node.cpp | 8 +- editor/editor_node.h | 4 + editor/editor_run.cpp | 13 + editor/editor_run.h | 6 + editor/gui/editor_run_bar.cpp | 58 +++ editor/gui/editor_run_bar.h | 5 + editor/gui/editor_scene_tabs.cpp | 10 + editor/icons/AutoFocus.svg | 1 + editor/icons/EmbeddedProcess.svg | 1 + editor/icons/KeepAspect.svg | 1 + editor/plugins/game_view_plugin.cpp | 237 +++++++++++- editor/plugins/game_view_plugin.h | 29 ++ main/main.cpp | 57 ++- platform/android/display_server_android.cpp | 6 +- platform/android/display_server_android.h | 4 +- platform/ios/display_server_ios.h | 4 +- platform/ios/display_server_ios.mm | 6 +- .../wayland/display_server_wayland.cpp | 6 +- .../linuxbsd/wayland/display_server_wayland.h | 4 +- platform/linuxbsd/x11/display_server_x11.cpp | 355 +++++++++++++++++- platform/linuxbsd/x11/display_server_x11.h | 24 +- platform/macos/display_server_macos.h | 4 +- platform/macos/display_server_macos.mm | 6 +- platform/web/display_server_web.cpp | 6 +- platform/web/display_server_web.h | 4 +- platform/windows/display_server_windows.cpp | 258 ++++++++++--- platform/windows/display_server_windows.h | 21 +- scene/gui/embedded_process.cpp | 202 ++++++++++ scene/gui/embedded_process.h | 78 ++++ scene/main/node.h | 1 + scene/main/window.cpp | 13 +- scene/main/window.h | 1 + scene/scene_string_names.h | 1 + servers/display_server.cpp | 16 +- servers/display_server.h | 12 +- servers/display_server_headless.h | 2 +- tests/display_server_mock.h | 2 +- tests/test_main.cpp | 2 +- 45 files changed, 1399 insertions(+), 118 deletions(-) create mode 100644 editor/icons/AutoFocus.svg create mode 100644 editor/icons/EmbeddedProcess.svg create mode 100644 editor/icons/KeepAspect.svg create mode 100644 scene/gui/embedded_process.cpp create mode 100644 scene/gui/embedded_process.h diff --git a/core/config/engine.cpp b/core/config/engine.cpp index aac048e93f7e..062dae973cab 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(bool p_enabled) { + embedded = p_enabled; +} + +bool Engine::is_embedded() const { + return embedded; +} + Engine::Engine() { singleton = this; } diff --git a/core/config/engine.h b/core/config/engine.h index b38412308ae7..fa939251082e 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 = 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(bool p_enabled); + bool is_embedded() const; Engine(); virtual ~Engine(); diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 6ef466bee0df..5c02b80fdca1 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -1864,6 +1864,14 @@ bool Engine::is_editor_hint() const { return ::Engine::get_singleton()->is_editor_hint(); } +void Engine::set_embedded(bool p_enabled) { + ::Engine::get_singleton()->set_embedded(p_enabled); +} + +bool Engine::is_embedded() const { + return ::Engine::get_singleton()->is_embedded(); +} + String Engine::get_write_movie_path() const { return ::Engine::get_singleton()->get_write_movie_path(); } @@ -1941,6 +1949,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"), &Engine::is_embedded); 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 430ecdc90630..838fb38c3567 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -567,6 +567,9 @@ class Engine : public Object { void set_editor_hint(bool p_enabled); bool is_editor_hint() const; + void set_embedded(bool p_enabled); + bool is_embedded() 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/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index dafa86d42e01..93882878cb9f 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -1895,6 +1895,12 @@ 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] + + + Display server supports hiding a window without destroying it. [b]Windows, Linux (X11)[/b] + Makes the mouse cursor visible if it is hidden. @@ -2110,8 +2116,7 @@ Window style is overridden, forcing sharp corners. [b]Note:[/b] This flag is implemented only on Windows (11). - - Max value of the [enum WindowFlags]. + Sent when the mouse pointer enters the window. diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml index bba515705384..9d014c18ab6d 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/doc/classes/Window.xml b/doc/classes/Window.xml index 02110f01628b..e83a44e00ff7 100644 --- a/doc/classes/Window.xml +++ b/doc/classes/Window.xml @@ -761,6 +761,11 @@ Emitted when the mouse cursor leaves the [Window]'s visible area, that is not occluded behind other [Control]s or windows, provided its [member Viewport.gui_disable_input] is [code]false[/code] and regardless if it's currently focused or not. + + + Emitted when the [Window] moved. + + Emitted when the [constant NOTIFICATION_THEME_CHANGED] notification is sent. @@ -852,7 +857,11 @@ [b]Note:[/b] This flag has no effect in embedded windows. [b]Note:[/b] This flag is implemented only on Windows (11). - + + Thie window is hidden but not destroyed. + [b]Note:[/b] This flag is implemented only on Windows and Linux (X11). + + Max value of the [enum Flags]. diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 36b43b7e9b71..094254baac23 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: { @@ -6697,6 +6699,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 49c1699c2828..a160f74c6216 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; @@ -787,6 +789,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 d5135f419848..0a53fffb417a 100644 --- a/editor/editor_run.cpp +++ b/editor/editor_run.cpp @@ -34,10 +34,13 @@ #include "editor/debugger/editor_debugger_node.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/plugins/game_view_plugin.h" #include "editor/run_instances_dialog.h" #include "main/main.h" #include "servers/display_server.h" +EditorRunInstanceStarting EditorRun::instance_starting_callback = nullptr; + EditorRun::Status EditorRun::get_status() const { return status; } @@ -234,6 +237,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)); @@ -286,6 +292,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..66ab4652bd32 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: + static EditorRunInstanceStarting instance_starting_callback; + 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..2f3f8d9f2201 100644 --- a/editor/gui/editor_run_bar.cpp +++ b/editor/gui/editor_run_bar.cpp @@ -293,6 +293,35 @@ void EditorRunBar::play_custom_scene(const String &p_custom) { _run_scene(p_custom); } +void EditorRunBar::restart() { + if (!is_playing()) { + return; + } + + RunMode last_current_mode = current_mode; + String last_run_custom_filename = run_custom_filename; + String last_run_current_filename = run_current_filename; + + stop_playing(); + + switch (last_current_mode) { + case RunMode::RUN_MAIN: { + _run_scene(); + } break; + case RunMode::RUN_CUSTOM: { + play_custom_scene(last_run_custom_filename); + } break; + case RunMode::RUN_CURRENT: { + _run_scene(last_run_current_filename); + } break; + case RunMode::STOPPED: { + // Nothing to do. + } break; + } + + current_mode = last_current_mode; +} + void EditorRunBar::stop_playing() { if (editor_run.get_status() == EditorRun::STATUS_STOP) { return; @@ -344,6 +373,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 +389,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, "stack_dump"))); 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..c41da131f61b 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(); @@ -93,6 +96,7 @@ class EditorRunBar : public MarginContainer { void play_main_scene(bool p_from_native = false); void play_current_scene(bool p_reload = false); void play_custom_scene(const String &p_custom); + void restart(); void stop_playing(); bool is_playing() const; @@ -102,6 +106,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/AutoFocus.svg b/editor/icons/AutoFocus.svg new file mode 100644 index 000000000000..e7673dc88fb3 --- /dev/null +++ b/editor/icons/AutoFocus.svg @@ -0,0 +1 @@ + diff --git a/editor/icons/EmbeddedProcess.svg b/editor/icons/EmbeddedProcess.svg new file mode 100644 index 000000000000..5c8a1b3d4742 --- /dev/null +++ b/editor/icons/EmbeddedProcess.svg @@ -0,0 +1 @@ + diff --git a/editor/icons/KeepAspect.svg b/editor/icons/KeepAspect.svg new file mode 100644 index 000000000000..884eefc576fa --- /dev/null +++ b/editor/icons/KeepAspect.svg @@ -0,0 +1 @@ + diff --git a/editor/plugins/game_view_plugin.cpp b/editor/plugins/game_view_plugin.cpp index f45af72e909e..b389498887cf 100644 --- a/editor/plugins/game_view_plugin.cpp +++ b/editor/plugins/game_view_plugin.cpp @@ -30,14 +30,16 @@ #include "game_view_plugin.h" +#include "core/config/project_settings.h" +#include "editor/debugger/editor_debugger_node.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) { p_session->send_message("scene:runtime_node_select_setup", Array()); @@ -162,6 +164,12 @@ void GameViewDebugger::_bind_methods() { /////// +GameView *GameView::singleton = nullptr; + +GameView *GameView::get_singleton() { + return singleton; +} + void GameView::_sessions_changed() { // The debugger session's `session_started/stopped` signal can be unreliable, so count it manually. active_sessions = 0; @@ -175,6 +183,59 @@ void GameView::_sessions_changed() { _update_debugger_buttons(); } +void GameView::_instance_starting(int p_idx, Array p_arguments) { + _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; + } + + screen_index_before_start = EditorNode::get_singleton()->get_editor_main_screen()->get_selected_index(); + + if (embedded_button->is_pressed()) { + // 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(); + embedded_process->embed_process(current_process_id); + _update_ui(); + + if (auto_focus_button->is_pressed()) { + EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_GAME); + } + } +} + +void GameView::_stop_pressed() { + EditorNode::get_singleton()->set_unfocused_low_processor_usage_mode_enabled(true); + embedded_process->reset(); + _update_ui(); + + 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() { + // Catch project settings changed to update window size/aspect ratio. + _update_embed_window_size(); +} + void GameView::_update_debugger_buttons() { bool empty = active_sessions == 0; @@ -220,6 +281,55 @@ void GameView::_select_mode_pressed(int p_option) { debugger->set_select_mode(mode); } +void GameView::_embedded_button_pressed() { + EditorSettings::get_singleton()->set_project_metadata("game_editor", "embedded", embedded_button->is_pressed()); + + if (EditorRunBar::get_singleton()->is_playing()) { + EditorRunBar::get_singleton()->restart(); + } + + _update_ui(); +} + +void GameView::_auto_focus_button_pressed() { + EditorSettings::get_singleton()->set_project_metadata("game_editor", "auto_focus", auto_focus_button->is_pressed()); +} + +void GameView::_keep_aspect_button_pressed() { + EditorSettings::get_singleton()->set_project_metadata("game_editor", "keep_aspect", keep_aspect_button->is_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 (embedded_button->is_pressed()) { + state_label->set_text(TTR("Press play to start the game.")); + } else { + state_label->set_text(TTR("Embedding is disabled.")); + } +} + +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"))); @@ -277,11 +387,42 @@ void GameView::_notification(int p_what) { select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_button_icon(get_editor_theme_icon(SNAME("ToolSelect"))); 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"))); + embedded_button->set_button_icon(get_editor_theme_icon(SNAME("EmbeddedProcess"))); + auto_focus_button->set_button_icon(get_editor_theme_icon(SNAME("AutoFocus"))); + keep_aspect_button->set_button_icon(get_editor_theme_icon(SNAME("KeepAspect"))); + hide_selection->set_button_icon(get_editor_theme_icon(hide_selection->is_pressed() ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible"))); 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. + embedded_button->set_pressed(EditorSettings::get_singleton()->get_project_metadata("game_editor", "embedded", true)); + auto_focus_button->set_pressed(EditorSettings::get_singleton()->get_project_metadata("game_editor", "auto_focus", true)); + keep_aspect_button->set_pressed(EditorSettings::get_singleton()->get_project_metadata("game_editor", "keep_aspect", true)); + + 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)); + + 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(); + embedded_button->hide(); + auto_focus_button->hide(); + keep_aspect_button->hide(); + keep_aspect_button->hide(); + } + + _update_ui(); + } break; } } @@ -320,7 +461,58 @@ Dictionary GameView::get_state() const { return d; } +void GameView::_update_arguments_for_instance(int p_idx, Array p_arguments) { + if (p_idx != 0 || !embedded_button->is_pressed() || !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"); + p_arguments.erase("--hidden"); + + // Add editor window native id so the started game can directly set it's parent to it. + 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()))); + + if (!embedded_process->is_visible_in_tree() && !auto_focus_button->is_pressed()) { + p_arguments.push_back("--hidden"); + } + + // 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)); +} + GameView::GameView(Ref p_debugger) { + singleton = this; debugger = p_debugger; // Add some margin to the sides for better aesthetics. @@ -429,17 +621,58 @@ 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); + + embedded_button = memnew(Button); + main_menu_hbox->add_child(embedded_button); + embedded_button->set_toggle_mode(true); + embedded_button->set_theme_type_variation("FlatButton"); + embedded_button->set_tooltip_text(TTR("Embed the game process in the editor.")); + embedded_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_embedded_button_pressed)); + + auto_focus_button = memnew(Button); + main_menu_hbox->add_child(auto_focus_button); + auto_focus_button->set_toggle_mode(true); + auto_focus_button->set_theme_type_variation("FlatButton"); + auto_focus_button->set_tooltip_text(TTR("Focus the game editor on project run.")); + auto_focus_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_auto_focus_button_pressed)); + + 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 aspect ratio of the embedded game.")); + keep_aspect_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_keep_aspect_button_pressed)); 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)); + + 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)); } +GameView::~GameView() { + singleton = nullptr; +} + /////// void GameViewPlugin::make_visible(bool p_visible) { diff --git a/editor/plugins/game_view_plugin.h b/editor/plugins/game_view_plugin.h index f8701c3e76fd..174e981c87a2 100644 --- a/editor/plugins/game_view_plugin.h +++ b/editor/plugins/game_view_plugin.h @@ -36,6 +36,10 @@ #include "editor/plugins/editor_plugin.h" #include "scene/debugger/scene_debugger.h" #include "scene/gui/box_container.h" +#include "scene/gui/embedded_process.h" +#include "scene/gui/label.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/separator.h" class GameViewDebugger : public EditorDebuggerPlugin { GDCLASS(GameViewDebugger, EditorDebuggerPlugin); @@ -84,15 +88,21 @@ class GameView : public VBoxContainer { CAMERA_MODE_EDITORS, }; + static GameView *singleton; Ref debugger; int active_sessions = 0; + int screen_index_before_start = -1; Button *suspend_button = nullptr; Button *next_frame_button = nullptr; Button *node_type_button[RuntimeNodeSelect::NODE_TYPE_MAX]; Button *select_mode_button[RuntimeNodeSelect::SELECT_MODE_MAX]; + VSeparator *embedding_separator; + Button *embedded_button; + Button *auto_focus_button; + Button *keep_aspect_button; Button *hide_selection = nullptr; @@ -100,6 +110,8 @@ class GameView : public VBoxContainer { MenuButton *camera_override_menu = nullptr; Panel *panel = nullptr; + EmbeddedProcess *embedded_process = nullptr; + Label *state_label = nullptr; void _sessions_changed(); @@ -109,6 +121,20 @@ class GameView : public VBoxContainer { void _node_type_pressed(int p_option); void _select_mode_pressed(int p_option); + void _embedded_button_pressed(); + void _auto_focus_button_pressed(); + 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_window_size(); + void _update_arguments_for_instance(int p_idx, Array p_arguments); void _hide_selection_toggled(bool p_pressed); @@ -122,7 +148,10 @@ class GameView : public VBoxContainer { void set_state(const Dictionary &p_state); Dictionary get_state() const; + static GameView *get_singleton(); + GameView(Ref p_debugger); + ~GameView(); }; class GameViewPlugin : public EditorPlugin { diff --git a/main/main.cpp b/main/main.cpp index e8086db9d3c7..59671581d723 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -226,6 +226,8 @@ 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; +static bool init_hidden = false; // Debug @@ -617,6 +619,8 @@ 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_option("--hidden", "Request hidden window.\n"); print_help_title("Debug options"); print_help_option("-d, --debug", "Debug (local stdout debugger).\n"); @@ -1798,6 +1802,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; + } + + N = N->next(); + } else { + OS::get_singleton()->print("Missing argument for --wid .\n"); + goto error; + } + + } else if (arg == "--hidden") { + init_hidden = true; + } else if (arg == "--" || arg == "++") { adding_user_args = true; } else { @@ -2958,9 +2979,21 @@ 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 initalized correctly + // from --position and --resolution parameters. + window_mode = DisplayServer::WINDOW_MODE_WINDOWED; + window_flags = DisplayServer::WINDOW_FLAG_BORDERLESS_BIT; + + Engine::get_singleton()->set_embedded(true); + } + if (init_hidden) { + window_flags |= DisplayServer::WINDOW_FLAG_HIDDEN_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); @@ -2974,7 +3007,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; } @@ -3171,15 +3204,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..fb0a18468294 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -140,6 +140,8 @@ bool DisplayServerX11::has_feature(Feature p_feature) const { #endif case FEATURE_CLIPBOARD_PRIMARY: case FEATURE_TEXT_TO_SPEECH: + case FEATURE_WINDOW_EMBEDDING: + case FEATURE_WINDOW_HIDDEN: return true; case FEATURE_SCREEN_CAPTURE: return !xwayland; @@ -1455,6 +1457,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 +1771,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); @@ -1764,6 +1796,10 @@ void DisplayServerX11::show_window(WindowID p_id) { const WindowData &wd = windows[p_id]; popup_open(p_id); + if (wd.hidden) { + return; + } + DEBUG_LOG_X11("show_window: %lu (%u) \n", wd.x11_window, p_id); XMapWindow(x11_display, wd.x11_window); @@ -2068,6 +2104,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); @@ -2728,8 +2766,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 @@ -2831,6 +2871,8 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo 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."); + _update_size_hints(p_window); XFlush(x11_display); @@ -2853,6 +2895,7 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo } 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 +2937,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 +3392,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; @@ -4602,7 +4647,7 @@ void DisplayServerX11::process_events() { break; } - const WindowData &wd = windows[window_id]; + WindowData &wd = windows[window_id]; XWindowAttributes xwa; XSync(x11_display, False); @@ -4615,10 +4660,21 @@ void DisplayServerX11::process_events() { _set_input_focus(wd.x11_window, RevertToPointerRoot); } + wd.hidden = false; + // Have we failed to set fullscreen while the window was unmapped? _validate_mode_on_map(window_id); } break; + case UnmapNotify: { + DEBUG_LOG_X11("[%u] UnmapNotify window=%lu (%u) \n", frame, event.xmap.window, window_id); + if (ime_window_event) { + break; + } + WindowData &wd = windows[window_id]; + wd.hidden = true; + } break; + case Expose: { DEBUG_LOG_X11("[%u] Expose window=%lu (%u), count='%u' \n", frame, event.xexpose.window, window_id, event.xexpose.count); if (ime_window_event) { @@ -5425,6 +5481,251 @@ 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) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), FAILED); + + const WindowData &wd = windows[p_window]; + + DEBUG_LOG_X11("Starting embeding %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; + } + + // 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; +} + Vector DisplayServerX11::get_rendering_drivers_func() { Vector drivers; @@ -5439,12 +5740,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; @@ -5526,6 +5827,10 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V wd.is_popup = true; } + if (p_flags & WINDOW_FLAG_HIDDEN_BIT) { + wd.hidden = true; + } + // Setup for menu subwindows: // - override_redirect forces the WM not to interfere with the window, to avoid delays due to // handling decorations and placement. @@ -5543,16 +5848,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 +5872,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 +6031,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 +6047,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 +6173,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 +6630,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..4fdb6260b394 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -207,6 +207,9 @@ class DisplayServerX11 : public DisplayServer { bool is_popup = false; bool layered_window = false; bool mpass = false; + bool hidden = false; + + Window embed_parent = 0; Rect2i parent_safe_rect; @@ -234,7 +237,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 +378,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 +525,9 @@ 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) override; + virtual Error remove_embedded_process(OS::ProcessID p_pid) 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 352b3fe523de..eec771e2a0c6 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); @@ -278,7 +278,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 a6eab1bd2963..5565398f4945 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -136,6 +136,8 @@ 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: + case FEATURE_WINDOW_HIDDEN: return true; default: return false; @@ -1483,7 +1485,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]; @@ -1551,6 +1553,9 @@ void DisplayServerWindows::show_window(WindowID p_id) { _update_window_style(p_id); } + if (wd.hidden) { + return; + } if (wd.maximized) { ShowWindow(wd.hWnd, SW_SHOWMAXIMIZED); SetForegroundWindow(wd.hWnd); // Slightly higher priority. @@ -1810,7 +1815,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 +1855,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); @@ -2108,7 +2113,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, bool p_hidden, 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 +2121,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; - if (p_initialized) { + // 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 && !p_hidden) { 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,11 +2168,11 @@ 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; } - if (!p_borderless && !p_no_activate_focus && p_initialized) { + if (!p_borderless && !p_no_activate_focus && p_initialized && !p_hidden) { r_style |= WS_VISIBLE; } @@ -2178,7 +2189,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, wd.hidden, style, style_ex); SetWindowLongPtr(wd.hWnd, GWL_STYLE, style); SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex); @@ -2192,6 +2203,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 +2214,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; @@ -2275,7 +2289,6 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) wd.minimized = false; _update_window_style(p_window, false); - MoveWindow(wd.hWnd, pos.x, pos.y, size.width, size.height, TRUE); // If the user has mouse trails enabled in windows, then sometimes the cursor disappears in fullscreen mode. @@ -2326,16 +2339,20 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W switch (p_flag) { case WINDOW_FLAG_RESIZE_DISABLED: { wd.resizable = !p_enabled; + ERR_FAIL_COND_MSG(p_enabled && wd.parent_hwnd, "Embedded window resize can't be disabled."); _update_window_style(p_window); } break; case WINDOW_FLAG_BORDERLESS: { wd.borderless = p_enabled; _update_window_style(p_window); _update_window_mouse_passthrough(p_window); - ShowWindow(wd.hWnd, (wd.no_focus || wd.is_popup) ? SW_SHOWNOACTIVATE : SW_SHOW); // Show the window. + if (!wd.hidden) { + 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,8 +2400,13 @@ 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; + case WINDOW_FLAG_HIDDEN: { + wd.hidden = p_enabled; + _update_window_style(p_window); + } break; default: break; } @@ -2731,6 +2753,122 @@ 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 embbed. + 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) { + _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->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); + } + } + + SetWindowPos(ep->window_handle, HWND_BOTTOM, p_rect.position.x, p_rect.position.y, p_rect.size.x, p_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; + } + + 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); + embedded_processes.erase(p_pid); + memdelete(ep); + + return OK; +} + 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 +4224,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 are is not activated + // by default because there's no native controls in it. + if (windows[window_id].parent_hwnd) { + SetFocus(windows[window_id].hWnd); + return MA_ACTIVATE; + } } break; case WM_ACTIVATEAPP: { bool new_app_focused = (bool)wParam; @@ -5154,6 +5298,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 @@ -5334,6 +5488,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } } break; + case WM_SHOWWINDOW: { + windows[window_id].hidden = !wParam; + } break; default: { if (user_proc) { return CallWindowProcW(user_proc, hWnd, uMsg, wParam, lParam); @@ -5569,11 +5726,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, (p_flags & WINDOW_FLAG_HIDDEN_BIT), dwStyle, dwExStyle); RECT WindowRect; @@ -5587,41 +5744,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; @@ -5655,6 +5816,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) { @@ -5677,6 +5841,8 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, ::DwmSetWindowAttribute(wd.hWnd, DWMWA_WINDOW_CORNER_PREFERENCE, &value, sizeof(value)); } + wd.hidden = (p_flags & WINDOW_FLAG_HIDDEN_BIT); + if (is_dark_mode_supported() && dark_title_available) { BOOL value = is_dark_mode(); ::DwmSetWindowAttribute(wd.hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); @@ -6003,7 +6169,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(); @@ -6404,7 +6570,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."); @@ -6482,8 +6654,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..6fcd6dea86dd 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -479,6 +479,7 @@ class DisplayServerWindows : public DisplayServer { bool context_created = false; bool mpass = false; bool sharp_corners = false; + bool hidden = false; // Used to transfer data between events using timer. WPARAM saved_wparam; @@ -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, bool p_hidden, DWORD &r_style, DWORD &r_style_ex); MouseMode mouse_mode; int restore_mouse_trails = 0; @@ -656,6 +659,14 @@ 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; + 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 +807,8 @@ 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) override; + virtual Error remove_embedded_process(OS::ProcessID p_pid) 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 +848,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/gui/embedded_process.cpp b/scene/gui/embedded_process.cpp new file mode 100644 index 000000000000..8c28103d0769 --- /dev/null +++ b/scene/gui/embedded_process.cpp @@ -0,0 +1,202 @@ +/**************************************************************************/ +/* 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 "scene/main/window.h" +#include "scene/theme/theme_db.h" + +void EmbeddedProcess::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_READY: { + _window = get_window(); + } break; + case NOTIFICATION_RESIZED: + case NOTIFICATION_VISIBILITY_CHANGED: + case NOTIFICATION_WM_POSITION_CHANGED: { + _update_embedded_process(); + } 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) { + _window_size = p_window_size; + _update_embedded_process(); +} + +Size2i EmbeddedProcess::get_window_size() { + return _window_size; +} + +void EmbeddedProcess::set_keep_aspect(bool p_keep_aspect) { + _keep_aspect = p_keep_aspect; + _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(); + if (control_rect.size == Size2i()) { + // The control is probably not visible. We will spawn the window anyway + // at its "normal" size. It will not be visible regardless + // because embed_process should be called with p_visible set to false. + control_rect = Rect2i(0, 0, _window_size.x, _window_size.y); + } + if (_keep_aspect) { + Rect2i desired_rect = control_rect; + float ratio = MIN((float)control_rect.size.x / _window_size.x, (float)control_rect.size.y / _window_size.y); + desired_rect.size = Size2i(_window_size.x * ratio, _window_size.y * ratio); + 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() { + if (_keep_aspect) { + Rect2i rect = get_global_embedded_window_rect(); + rect.position = get_screen_position() + (rect.position - get_global_position()); + return rect; + } else { + return this->get_screen_rect(); + } +} + +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(); + + // Try to embed, but the process may be just started and the window is not yet ready + // 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; + timer_embedding->stop(); +} + +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()); + if (err == OK) { + _embedding_completed = true; + 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 failed. + reset(); + emit_signal(SNAME("embedding_failed")); + } else { + // Tries another shot. + timer_embedding->start(); + } + } else { + // Another error. + reset(); + emit_signal(SNAME("embedding_failed")); + } +} + +void EmbeddedProcess::_update_embedded_process() { + if (!_window || _current_process_id == 0 || !_embedding_completed) { + return; + } + + DisplayServer::get_singleton()->embed_process(_window->get_window_id(), _current_process_id, get_screen_embedded_window_rect(), is_visible_in_tree()); +} + +void EmbeddedProcess::_timer_embedding_timeout() { + _try_embed_process(); +} + +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)); +} + +EmbeddedProcess::~EmbeddedProcess() { + if (_current_process_id != 0) { + // Stop embedding the last process. + OS::get_singleton()->kill(_current_process_id); + reset(); + } +} diff --git a/scene/gui/embedded_process.h b/scene/gui/embedded_process.h new file mode 100644 index 000000000000..c47f2cfd2c1f --- /dev/null +++ b/scene/gui/embedded_process.h @@ -0,0 +1,78 @@ +/**************************************************************************/ +/* 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); + + OS::ProcessID _current_process_id = 0; + bool _embedding_completed = false; + uint64_t _start_embedding_time = 0; + + Window *_window = nullptr; + Timer *timer_embedding = nullptr; + + int _embedding_timeout = 45000; + + bool _keep_aspect = false; + Size2i _window_size; + + void _try_embed_process(); + void _update_embedded_process(); + void _timer_embedding_timeout(); + +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/scene/main/node.h b/scene/main/node.h index e2f3ce9b7801..b2311101c828 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -423,6 +423,7 @@ class Node : public Object { NOTIFICATION_WM_GO_BACK_REQUEST = 1007, NOTIFICATION_WM_SIZE_CHANGED = 1008, NOTIFICATION_WM_DPI_CHANGE = 1009, + NOTIFICATION_WM_POSITION_CHANGED = 1012, NOTIFICATION_VP_MOUSE_ENTER = 1010, NOTIFICATION_VP_MOUSE_EXIT = 1011, diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 045c3ae02d8b..b0f26d23b4e7 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -700,7 +700,12 @@ 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); + emit_signal(SceneStringName(position_changed)); + } if (size != p_callback.size) { size = p_callback.size; @@ -766,7 +771,6 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) { focused = true; _propagate_window_notification(this, NOTIFICATION_WM_WINDOW_FOCUS_IN); emit_signal(SceneStringName(focus_entered)); - } break; case DisplayServer::WINDOW_EVENT_FOCUS_OUT: { focused = false; @@ -792,6 +796,9 @@ void Window::_event_callback(DisplayServer::WindowEvent p_event) { case DisplayServer::WINDOW_EVENT_TITLEBAR_CHANGE: { emit_signal(SNAME("titlebar_changed")); } break; + case DisplayServer::WINDOW_EVENT_POSITION_CHANGED: { + emit_signal(SNAME("position_changed")); + } break; } } @@ -3044,6 +3051,7 @@ void Window::_bind_methods() { ADD_SIGNAL(MethodInfo("theme_changed")); ADD_SIGNAL(MethodInfo("dpi_changed")); ADD_SIGNAL(MethodInfo("titlebar_changed")); + ADD_SIGNAL(MethodInfo("position_changed")); BIND_CONSTANT(NOTIFICATION_VISIBILITY_CHANGED); BIND_CONSTANT(NOTIFICATION_THEME_CHANGED); @@ -3063,6 +3071,7 @@ void Window::_bind_methods() { BIND_ENUM_CONSTANT(FLAG_EXTEND_TO_TITLE); BIND_ENUM_CONSTANT(FLAG_MOUSE_PASSTHROUGH); BIND_ENUM_CONSTANT(FLAG_SHARP_CORNERS); + BIND_ENUM_CONSTANT(FLAG_HIDDEN); BIND_ENUM_CONSTANT(FLAG_MAX); BIND_ENUM_CONSTANT(CONTENT_SCALE_MODE_DISABLED); diff --git a/scene/main/window.h b/scene/main/window.h index 0994fc601278..c26abbd1c666 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -63,6 +63,7 @@ class Window : public Viewport { FLAG_EXTEND_TO_TITLE = DisplayServer::WINDOW_FLAG_EXTEND_TO_TITLE, FLAG_MOUSE_PASSTHROUGH = DisplayServer::WINDOW_FLAG_MOUSE_PASSTHROUGH, FLAG_SHARP_CORNERS = DisplayServer::WINDOW_FLAG_SHARP_CORNERS, + FLAG_HIDDEN = DisplayServer::WINDOW_FLAG_HIDDEN, FLAG_MAX = DisplayServer::WINDOW_FLAG_MAX, }; diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index 0a2ebeda7af7..c6bd7a378d45 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -80,6 +80,7 @@ class SceneStringNames { StringName mouse_shape_exited; StringName focus_entered; StringName focus_exited; + StringName position_changed; StringName pre_sort_children; StringName sort_children; diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 82ac62bc9fae..935b1d20b298 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -657,6 +657,16 @@ 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) { + 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; +} + 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 +1067,8 @@ 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(FEATURE_WINDOW_HIDDEN); BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE); BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN); @@ -1207,9 +1219,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..9d38c2879cc8 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,8 @@ class DisplayServer : public Object { FEATURE_NATIVE_DIALOG_INPUT, FEATURE_NATIVE_DIALOG_FILE, FEATURE_NATIVE_DIALOG_FILE_EXTRA, + FEATURE_WINDOW_EMBEDDING, + FEATURE_WINDOW_HIDDEN }; virtual bool has_feature(Feature p_feature) const = 0; @@ -386,6 +388,7 @@ class DisplayServer : public Object { WINDOW_FLAG_EXTEND_TO_TITLE, WINDOW_FLAG_MOUSE_PASSTHROUGH, WINDOW_FLAG_SHARP_CORNERS, + WINDOW_FLAG_HIDDEN, WINDOW_FLAG_MAX, }; @@ -400,6 +403,7 @@ class DisplayServer : public Object { 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_HIDDEN_BIT = (1 << WINDOW_FLAG_HIDDEN) }; 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); @@ -428,6 +432,7 @@ class DisplayServer : public Object { WINDOW_EVENT_GO_BACK_REQUEST, WINDOW_EVENT_DPI_CHANGE, WINDOW_EVENT_TITLEBAR_CHANGE, + WINDOW_EVENT_POSITION_CHANGED, }; virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) = 0; virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) = 0; @@ -543,6 +548,9 @@ 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); + virtual Error remove_embedded_process(OS::ProcessID p_pid); + 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 +608,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 979aee800134..20bdd7319e44 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -284,7 +284,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; } }