diff --git a/core/config/engine.cpp b/core/config/engine.cpp index d778f3a94185..b5bbc7e196ba 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -396,6 +396,14 @@ void Engine::set_suspend(bool p_enabled) { suspended = p_enabled; } +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 79a2ec7b29ac..f48be8527d61 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -84,6 +84,7 @@ class Engine { bool editor_hint = false; bool project_manager_hint = false; bool extension_reloading = false; + bool embedded = false; bool _print_header = true; @@ -194,6 +195,8 @@ class Engine { bool notify_frame_server_synced(); void set_suspend(bool p_enabled); + 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 891e3a28c9ed..49056fc1155a 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -1849,6 +1849,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(); } @@ -1926,6 +1934,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 ce0bde3c05b8..d7c247f97284 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -566,6 +566,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 79064a88ba6c..1ffba6239a6d 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -1883,6 +1883,12 @@ Display server supports spawning dialogs for selecting files or directories using the operating system's native look-and-feel. 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. @@ -2092,7 +2098,7 @@ All mouse events are passed to the underlying window of the same application. - + Max value of the [enum WindowFlags]. 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 ca155881c8ed..363b621a15f4 100644 --- a/doc/classes/Window.xml +++ b/doc/classes/Window.xml @@ -756,6 +756,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. @@ -842,7 +847,11 @@ All mouse events are passed to the underlying window of the same application. [b]Note:[/b] This flag has no effect in embedded windows. - + + 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_run.cpp b/editor/editor_run.cpp index d5135f419848..2f9cf1ff776a 100644 --- a/editor/editor_run.cpp +++ b/editor/editor_run.cpp @@ -34,6 +34,7 @@ #include "editor/debugger/editor_debugger_node.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/plugins/game_editor_plugin.h" #include "editor/run_instances_dialog.h" #include "main/main.h" #include "servers/display_server.h" @@ -175,12 +176,14 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie) { args.push_back(itos(pos.x) + "," + itos(pos.y)); } break; case 3: { // force maximized + Vector2 pos = screen_rect.position + screen_rect.size / 2; args.push_back("--position"); args.push_back(itos(pos.x) + "," + itos(pos.y)); args.push_back("--maximized"); } break; case 4: { // force fullscreen + Vector2 pos = screen_rect.position + screen_rect.size / 2; args.push_back("--position"); args.push_back(itos(pos.x) + "," + itos(pos.y)); @@ -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 (GameEditor::get_singleton()) { + GameEditor::get_singleton()->get_argument_list_for_instance(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..f688f3ff8523 100644 --- a/editor/editor_run.h +++ b/editor/editor_run.h @@ -32,6 +32,7 @@ #define EDITOR_RUN_H #include "core/os/os.h" +#include "servers/display_server.h" class EditorRun { public: @@ -58,6 +59,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 9050ee0cd4b9..829e4779cdd2 100644 --- a/editor/gui/editor_run_bar.cpp +++ b/editor/gui/editor_run_bar.cpp @@ -293,6 +293,34 @@ void EditorRunBar::play_custom_scene(const String &p_custom) { current_mode = RunMode::RUN_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) { @@ -345,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); } diff --git a/editor/gui/editor_run_bar.h b/editor/gui/editor_run_bar.h index 1cb999612a0c..fd73b2dbc2a7 100644 --- a/editor/gui/editor_run_bar.h +++ b/editor/gui/editor_run_bar.h @@ -96,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; @@ -105,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/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_editor_plugin.cpp b/editor/plugins/game_editor_plugin.cpp index 062d572714f4..508e88a75bc9 100644 --- a/editor/plugins/game_editor_plugin.cpp +++ b/editor/plugins/game_editor_plugin.cpp @@ -30,14 +30,15 @@ #include "game_editor_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/panel.h" -#include "scene/gui/separator.h" void GameEditorDebugger::_session_started(Ref p_session) { p_session->send_message("scene:runtime_node_select_setup", Array()); @@ -118,6 +119,12 @@ void GameEditorDebugger::_bind_methods() { /////// +GameEditor *GameEditor::singleton = nullptr; + +GameEditor *GameEditor::get_singleton() { + return singleton; +} + void GameEditor::_sessions_changed() { // The debugger session's `session_started/stopped` signal can be unreliable, so count it manually. active_sessions = 0; @@ -131,6 +138,41 @@ void GameEditor::_sessions_changed() { _update_debugger_buttons(); } +void GameEditor::_play_pressed() { + OS::ProcessID current_process_id = EditorRunBar::get_singleton()->get_current_process(); + if (current_process_id == 0) { + return; + } + + if (embedded_button->is_pressed()) { + _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 GameEditor::_stop_pressed() { + embedded_process->reset(); + _update_ui(); +} + +void GameEditor::_embedding_completed() { + _update_ui(); +} + +void GameEditor::_embedding_failed() { + state_label->set_text(TTR("Connection impossible to the game process.")); +} + +void GameEditor::_project_settings_changed() { + // Catch project settings changed to update window size/aspect ratio. + _update_embed_window_size(); +} + void GameEditor::_update_debugger_buttons() { bool empty = active_sessions == 0; suspend_button->set_disabled(empty); @@ -167,6 +209,55 @@ void GameEditor::_select_mode_pressed(int p_option) { debugger->set_select_mode(mode); } +void GameEditor::_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 GameEditor::_auto_focus_button_pressed() { + EditorSettings::get_singleton()->set_project_metadata("game_editor", "auto_focus", auto_focus_button->is_pressed()); +} + +void GameEditor::_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 GameEditor::_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 GameEditor::_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 GameEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: @@ -180,12 +271,89 @@ void GameEditor::_notification(int p_what) { select_mode_button[RuntimeNodeSelect::SELECT_MODE_SINGLE]->set_icon(get_editor_theme_icon(SNAME("ToolSelect"))); select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_icon(get_editor_theme_icon(SNAME("ListSelect"))); + embedded_button->set_icon(get_editor_theme_icon(SNAME("EmbeddedProcess"))); + auto_focus_button->set_icon(get_editor_theme_icon(SNAME("AutoFocus"))); + keep_aspect_button->set_icon(get_editor_theme_icon(SNAME("KeepAspect"))); + panel->set_theme_type_variation("GamePanel"); } 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, &GameEditor::_play_pressed)); + EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &GameEditor::_stop_pressed)); + + ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &GameEditor::_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; + } +} + +void GameEditor::get_argument_list_for_instance(int p_idx, List &r_list) { + if (p_idx != 0 || !embedded_button->is_pressed() || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) { + return; + } + + // Remove duplicates/unwanted parameters. + List::Element *position_item = r_list.find("--position"); + if (position_item) { + r_list.erase(position_item->next()); + r_list.erase(position_item); + } + List::Element *resolution_item = r_list.find("--resolution"); + if (resolution_item) { + r_list.erase(resolution_item->next()); + r_list.erase(resolution_item); } + List::Element *screen_item = r_list.find("--screen"); + if (screen_item) { + r_list.erase(screen_item->next()); + r_list.erase(screen_item); + } + r_list.erase("-f"); + r_list.erase("--fullscreen"); + r_list.erase("-m"); + r_list.erase("--maximized"); + r_list.erase("-t"); + r_list.erase("--always-on-top"); + r_list.erase("--hidden"); + + // Add editor window native id so the started game can directly set it's parent to it. + r_list.push_back("--wid"); + r_list.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()) { + r_list.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(); + r_list.push_back("--position"); + r_list.push_back(itos(rect.position.x) + "," + itos(rect.position.y)); + r_list.push_back("--resolution"); + r_list.push_back(itos(rect.size.x) + "x" + itos(rect.size.y)); } GameEditor::GameEditor(Ref p_debugger) { + singleton = this; debugger = p_debugger; // Add some margin to the sides for better aesthetics. @@ -252,14 +420,55 @@ GameEditor::GameEditor(Ref p_debugger) { select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->connect(SceneStringName(pressed), callable_mp(this, &GameEditor::_select_mode_pressed).bind(RuntimeNodeSelect::SELECT_MODE_LIST)); select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_tooltip_text(TTR("Show list of selectable nodes at position clicked.")); + 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("Activate the game embedding mode.")); + embedded_button->connect(SceneStringName(pressed), callable_mp(this, &GameEditor::_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, &GameEditor::_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, &GameEditor::_keep_aspect_button_pressed)); + panel = memnew(Panel); add_child(panel); 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, &GameEditor::_embedding_failed)); + embedded_process->connect(SNAME("embedding_completed"), callable_mp(this, &GameEditor::_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); + p_debugger->connect("session_started", callable_mp(this, &GameEditor::_sessions_changed)); p_debugger->connect("session_stopped", callable_mp(this, &GameEditor::_sessions_changed)); } +GameEditor::~GameEditor() { + singleton = nullptr; +} + /////// void GameEditorPlugin::make_visible(bool p_visible) { diff --git a/editor/plugins/game_editor_plugin.h b/editor/plugins/game_editor_plugin.h index 9f600902765e..27dfd788d85a 100644 --- a/editor/plugins/game_editor_plugin.h +++ b/editor/plugins/game_editor_plugin.h @@ -35,6 +35,9 @@ #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/separator.h" class GameEditorDebugger : public EditorDebuggerPlugin { GDCLASS(GameEditorDebugger, EditorDebuggerPlugin); @@ -66,6 +69,7 @@ class GameEditor : public VBoxContainer { GDCLASS(GameEditor, VBoxContainer); private: + static GameEditor *singleton; Ref debugger; int active_sessions = 0; @@ -75,8 +79,14 @@ class GameEditor : public VBoxContainer { 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; Panel *panel = nullptr; + EmbeddedProcess *embedded_process = nullptr; + Label *state_label = nullptr; void _sessions_changed(); @@ -86,12 +96,29 @@ class GameEditor : 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 _stop_pressed(); + void _embedding_completed(); + void _embedding_failed(); + void _project_settings_changed(); + + void _update_ui(); + void _update_embed_window_size(); protected: void _notification(int p_what); public: + static GameEditor *get_singleton(); + + void get_argument_list_for_instance(int p_idx, List &r_list); + GameEditor(Ref p_debugger); + ~GameEditor(); }; class GameEditorPlugin : public EditorPlugin { diff --git a/main/main.cpp b/main/main.cpp index 439cd385c028..4c2e790bfb62 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -224,6 +224,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 @@ -604,6 +606,8 @@ void Main::print_help(const char *p_binary) { print_help_option("--screen ", "Request window screen.\n"); print_help_option("--single-window", "Use a single window (no separate subwindows).\n"); print_help_option("--xr-mode ", "Select XR (Extended Reality) mode [\"default\", \"off\", \"on\"].\n"); + 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"); @@ -1760,6 +1764,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 { @@ -2910,9 +2931,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 | DisplayServer::WINDOW_FLAG_RESIZE_DISABLED_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); @@ -2926,7 +2959,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; } @@ -3123,15 +3156,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 c1053215c694..d6ba2086880c 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -514,8 +514,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( @@ -582,7 +582,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 90bda18cfa2c..32735bb57c2e 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -211,7 +211,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(); @@ -228,7 +228,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 bbb758074d9b..e50457c1fb9c 100644 --- a/platform/ios/display_server_ios.h +++ b/platform/ios/display_server_ios.h @@ -88,7 +88,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: @@ -97,7 +97,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 e51d43bd8999..9f1407f039a0 100644 --- a/platform/ios/display_server_ios.mm +++ b/platform/ios/display_server_ios.mm @@ -51,7 +51,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; @@ -189,8 +189,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 3fbbc263a01d..26a50eb38b78 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -1286,8 +1286,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); @@ -1297,7 +1297,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 7949f80f2486..3676b5fd6e26 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -139,6 +139,7 @@ bool DisplayServerX11::has_feature(Feature p_feature) const { #endif case FEATURE_CLIPBOARD_PRIMARY: case FEATURE_TEXT_TO_SPEECH: + case FEATURE_WINDOW_EMBEDDING: return true; case FEATURE_SCREEN_CAPTURE: return !xwayland; @@ -1738,7 +1739,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); @@ -3339,6 +3340,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; @@ -5416,6 +5418,182 @@ 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); + + Point2i window_position = _get_window_position(wd.x11_window); + Rect2i desired_rect = Rect2i(p_rect.position + window_position, p_rect.size); + + 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(p_window, false); + + embedded_processes.insert(p_pid, ep); + } + + // Resize and move window to match desired rect + 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; + } + + 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; @@ -5430,12 +5608,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; @@ -5534,16 +5712,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 @@ -5555,6 +5736,12 @@ 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) { + XSetTransientForHint(x11_display, wd.x11_window, p_parent_window); + } + XSetWindowAttributes window_attributes_ime = {}; window_attributes_ime.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask; @@ -5843,7 +6030,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"; @@ -6297,7 +6484,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..f2d8d085b67e 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -234,7 +234,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 +375,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); + void _set_embedded_mode(Window p_window, bool p_enabled); + protected: void _window_changed(XEvent *event); @@ -510,6 +522,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 +549,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 f6c1d11028db..dc7ae21b023b 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -3276,8 +3276,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; @@ -3471,7 +3471,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 4e55cc137a32..613434f6140b 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 ed9d5244a37a..24f99f134eed 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -122,6 +122,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; @@ -1469,7 +1471,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]; @@ -1533,6 +1535,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. @@ -1767,7 +1772,7 @@ 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)); - + return; if (windows[p_window].mpass || windows[p_window].mpath.size() == 0) { SetWindowRgn(windows[p_window].hWnd, nullptr, FALSE); } else { @@ -1808,6 +1813,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); @@ -2058,7 +2064,7 @@ Size2i DisplayServerWindows::window_get_size_with_decorations(WindowID p_window) return Size2(); } -void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, 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_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, 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 @@ -2066,11 +2072,19 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre r_style = 0; r_style_ex = WS_EX_WINDOWEDGE; if (p_main_window) { - r_style_ex |= WS_EX_APPWINDOW; - r_style |= WS_VISIBLE; + // 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_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_maximized) { r_style |= WS_MAXIMIZE; @@ -2097,11 +2111,11 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre } } - 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) { + if (!p_borderless && !p_no_activate_focus && !p_hidden) { r_style |= WS_VISIBLE; } @@ -2109,6 +2123,10 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre r_style_ex |= WS_EX_ACCEPTFILES; } +void DisplayServerWindows::_get_window_style_from_mode_and_flags(bool p_main_window, WindowMode p_mode, uint32_t p_flags, bool p_embbed_child, DWORD &r_style, DWORD &r_style_ex) { + _get_window_style(p_main_window, (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_MAXIMIZED, false, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), p_embbed_child, (p_flags & WINDOW_FLAG_HIDDEN_BIT), r_style, r_style_ex); +} + void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repaint) { _THREAD_SAFE_METHOD_ @@ -2118,7 +2136,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.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.maximized, wd.maximized_fs, wd.no_focus || wd.is_popup, style, style_ex); + _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, 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); @@ -2132,6 +2150,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); } } @@ -2142,6 +2161,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; @@ -2215,7 +2236,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. @@ -2272,10 +2292,13 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W 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; @@ -2317,8 +2340,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; } @@ -2665,6 +2693,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. @@ -4016,6 +4160,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; @@ -5084,6 +5234,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 @@ -5290,6 +5450,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); @@ -5525,11 +5688,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, (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_MAXIMIZED, false, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), dwStyle, dwExStyle); + _get_window_style_from_mode_and_flags(window_id_counter == MAIN_WINDOW_ID, p_mode, p_flags, p_parent_hwnd, dwStyle, dwExStyle); RECT WindowRect; @@ -5543,41 +5706,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; @@ -5611,6 +5778,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) { @@ -5627,6 +5797,8 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, wd_transient_parent->transient_children.insert(id); } + 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)); @@ -5953,7 +6125,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(); @@ -6350,7 +6522,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); ERR_FAIL_COND_MSG(main_window == INVALID_WINDOW_ID, "Failed to create main window."); joypad = new JoypadWindows(&windows[MAIN_WINDOW_ID].hWnd); @@ -6424,8 +6602,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 54e1c9681ddc..73b39992fe44 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -473,6 +473,7 @@ class DisplayServerWindows : public DisplayServer { bool exclusive = false; bool context_created = false; bool mpass = false; + bool hidden = false; // Used to transfer data between events using timer. WPARAM saved_wparam; @@ -524,6 +525,8 @@ class DisplayServerWindows : public DisplayServer { bool is_popup = false; Rect2i parent_safe_rect; + + HWND parent_hwnd = 0; }; JoypadWindows *joypad = nullptr; @@ -532,7 +535,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; @@ -591,7 +594,8 @@ 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_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, 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_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, 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); + void _get_window_style_from_mode_and_flags(bool p_main_window, WindowMode p_mode, uint32_t p_flags, bool p_embbed_child, DWORD &r_style, DWORD &r_style_ex); MouseMode mouse_mode; int restore_mouse_trails = 0; @@ -645,6 +649,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); @@ -785,6 +797,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; @@ -824,11 +838,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..6e7d6eb9b28b --- /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(_window_size.x, 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 cda657dfbbef..d31325e0e3d4 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 803ce89bc9e1..5aecfa49d97a 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; } } @@ -3036,6 +3043,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); @@ -3054,6 +3062,7 @@ void Window::_bind_methods() { BIND_ENUM_CONSTANT(FLAG_POPUP); BIND_ENUM_CONSTANT(FLAG_EXTEND_TO_TITLE); BIND_ENUM_CONSTANT(FLAG_MOUSE_PASSTHROUGH); + 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 47aaf7372870..e5d4dac1ea0b 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -62,6 +62,7 @@ class Window : public Viewport { FLAG_POPUP = DisplayServer::WINDOW_FLAG_POPUP, FLAG_EXTEND_TO_TITLE = DisplayServer::WINDOW_FLAG_EXTEND_TO_TITLE, FLAG_MOUSE_PASSTHROUGH = DisplayServer::WINDOW_FLAG_MOUSE_PASSTHROUGH, + 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 381a161ad57e..bc1ed617b7bf 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 86b4016da8bf..8811214dec47 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -653,6 +653,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; @@ -1050,6 +1060,8 @@ void DisplayServer::_bind_methods() { BIND_ENUM_CONSTANT(FEATURE_NATIVE_HELP); BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_INPUT); BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE); + BIND_ENUM_CONSTANT(FEATURE_WINDOW_EMBEDDING); + BIND_ENUM_CONSTANT(FEATURE_WINDOW_HIDDEN); BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE); BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN); @@ -1197,9 +1209,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 04f4b0c03d25..87476e2fb7a1 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -90,7 +90,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: @@ -150,6 +150,8 @@ class DisplayServer : public Object { FEATURE_NATIVE_HELP, FEATURE_NATIVE_DIALOG_INPUT, FEATURE_NATIVE_DIALOG_FILE, + FEATURE_WINDOW_EMBEDDING, + FEATURE_WINDOW_HIDDEN }; virtual bool has_feature(Feature p_feature) const = 0; @@ -381,6 +383,7 @@ class DisplayServer : public Object { WINDOW_FLAG_POPUP, WINDOW_FLAG_EXTEND_TO_TITLE, WINDOW_FLAG_MOUSE_PASSTHROUGH, + WINDOW_FLAG_HIDDEN, WINDOW_FLAG_MAX, }; @@ -394,6 +397,7 @@ class DisplayServer : public Object { WINDOW_FLAG_POPUP_BIT = (1 << WINDOW_FLAG_POPUP), WINDOW_FLAG_EXTEND_TO_TITLE_BIT = (1 << WINDOW_FLAG_EXTEND_TO_TITLE), WINDOW_FLAG_MOUSE_PASSTHROUGH_BIT = (1 << WINDOW_FLAG_MOUSE_PASSTHROUGH), + WINDOW_FLAG_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); @@ -422,6 +426,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; @@ -535,6 +540,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); @@ -592,7 +600,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); static bool can_create_rendering_device(); diff --git a/servers/display_server_headless.h b/servers/display_server_headless.h index a5277479caa1..c7efcb20c4ad 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 3c184ccc5d61..c1d9eaa4d5f4 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -275,7 +275,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; } }