Embedding game process in editor

This commit is contained in:
Hilderin 2024-09-25 16:57:23 -04:00
parent fafc07335b
commit 9d2a4c03be
44 changed files with 1712 additions and 134 deletions

View file

@ -415,6 +415,14 @@ void Engine::set_freeze_time_scale(bool p_frozen) {
freeze_time_scale = p_frozen; freeze_time_scale = p_frozen;
} }
void Engine::set_embedded_in_editor(bool p_enabled) {
embedded_in_editor = p_enabled;
}
bool Engine::is_embedded_in_editor() const {
return embedded_in_editor;
}
Engine::Engine() { Engine::Engine() {
singleton = this; singleton = this;
} }

View file

@ -87,6 +87,7 @@ private:
bool editor_hint = false; bool editor_hint = false;
bool project_manager_hint = false; bool project_manager_hint = false;
bool extension_reloading = false; bool extension_reloading = false;
bool embedded_in_editor = false;
bool _print_header = true; bool _print_header = true;
@ -201,6 +202,8 @@ public:
bool notify_frame_server_synced(); bool notify_frame_server_synced();
void set_freeze_time_scale(bool p_frozen); void set_freeze_time_scale(bool p_frozen);
void set_embedded_in_editor(bool p_enabled);
bool is_embedded_in_editor() const;
Engine(); Engine();
virtual ~Engine(); virtual ~Engine();

View file

@ -1908,6 +1908,10 @@ bool Engine::is_editor_hint() const {
return ::Engine::get_singleton()->is_editor_hint(); return ::Engine::get_singleton()->is_editor_hint();
} }
bool Engine::is_embedded_in_editor() const {
return ::Engine::get_singleton()->is_embedded_in_editor();
}
String Engine::get_write_movie_path() const { String Engine::get_write_movie_path() const {
return ::Engine::get_singleton()->get_write_movie_path(); return ::Engine::get_singleton()->get_write_movie_path();
} }
@ -1985,6 +1989,7 @@ void Engine::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_script_language", "index"), &Engine::get_script_language); 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_editor_hint"), &Engine::is_editor_hint);
ClassDB::bind_method(D_METHOD("is_embedded_in_editor"), &Engine::is_embedded_in_editor);
ClassDB::bind_method(D_METHOD("get_write_movie_path"), &Engine::get_write_movie_path); ClassDB::bind_method(D_METHOD("get_write_movie_path"), &Engine::get_write_movie_path);

View file

@ -586,6 +586,8 @@ public:
void set_editor_hint(bool p_enabled); void set_editor_hint(bool p_enabled);
bool is_editor_hint() const; bool is_editor_hint() const;
bool is_embedded_in_editor() const;
// `set_write_movie_path()` is not exposed to the scripting API as changing it at run-time has no effect. // `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; String get_write_movie_path() const;

View file

@ -409,6 +409,8 @@ bool OS::has_feature(const String &p_feature) {
return _in_editor; return _in_editor;
} else if (p_feature == "editor_runtime") { } else if (p_feature == "editor_runtime") {
return !_in_editor; return !_in_editor;
} else if (p_feature == "embedded_in_editor") {
return _embedded_in_editor;
} }
#else #else
if (p_feature == "template") { if (p_feature == "template") {

View file

@ -63,6 +63,7 @@ class OS {
bool _stderr_enabled = true; bool _stderr_enabled = true;
bool _writing_movie = false; bool _writing_movie = false;
bool _in_editor = false; bool _in_editor = false;
bool _embedded_in_editor = false;
CompositeLogger *_logger = nullptr; CompositeLogger *_logger = nullptr;

View file

@ -1925,6 +1925,9 @@
<constant name="FEATURE_SCREEN_EXCLUDE_FROM_CAPTURE" value="28" enum="Feature"> <constant name="FEATURE_SCREEN_EXCLUDE_FROM_CAPTURE" value="28" enum="Feature">
Display server supports [constant WINDOW_FLAG_EXCLUDE_FROM_CAPTURE] window flag. Display server supports [constant WINDOW_FLAG_EXCLUDE_FROM_CAPTURE] window flag.
</constant> </constant>
<constant name="FEATURE_WINDOW_EMBEDDING" value="29" enum="Feature">
Display server supports embedding a window from another process. [b]Windows, Linux (X11)[/b]
</constant>
<constant name="MOUSE_MODE_VISIBLE" value="0" enum="MouseMode"> <constant name="MOUSE_MODE_VISIBLE" value="0" enum="MouseMode">
Makes the mouse cursor visible if it is hidden. Makes the mouse cursor visible if it is hidden.
</constant> </constant>

View file

@ -240,6 +240,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. [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.
</description> </description>
</method> </method>
<method name="is_embedded_in_editor" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the engine is running embedded in the editor. This is useful to prevent attempting to update window mode or window flags that are not supported when running the project embedded in the editor.
</description>
</method>
<method name="is_in_physics_frame" qualifiers="const"> <method name="is_in_physics_frame" qualifiers="const">
<return type="bool" /> <return type="bool" />
<description> <description>

View file

@ -755,7 +755,9 @@ void EditorNode::_notification(int p_what) {
} }
// Set a low FPS cap to decrease CPU/GPU usage while the editor is unfocused. // Set a low FPS cap to decrease CPU/GPU usage while the editor is unfocused.
OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(int(EDITOR_GET("interface/editor/unfocused_low_processor_mode_sleep_usec"))); if (unfocused_low_processor_usage_mode_enabled) {
OS::get_singleton()->set_low_processor_usage_mode_sleep_usec(int(EDITOR_GET("interface/editor/unfocused_low_processor_mode_sleep_usec")));
}
} break; } break;
case NOTIFICATION_WM_ABOUT: { case NOTIFICATION_WM_ABOUT: {
@ -6733,6 +6735,10 @@ int EditorNode::execute_and_show_output(const String &p_title, const String &p_p
return eta.exitcode; return eta.exitcode;
} }
void EditorNode::set_unfocused_low_processor_usage_mode_enabled(bool p_enabled) {
unfocused_low_processor_usage_mode_enabled = p_enabled;
}
EditorNode::EditorNode() { EditorNode::EditorNode() {
DEV_ASSERT(!singleton); DEV_ASSERT(!singleton);
singleton = this; singleton = this;

View file

@ -476,6 +476,8 @@ private:
bool was_window_windowed_last = false; bool was_window_windowed_last = false;
bool unfocused_low_processor_usage_mode_enabled = true;
static EditorBuildCallback build_callbacks[MAX_BUILD_CALLBACKS]; static EditorBuildCallback build_callbacks[MAX_BUILD_CALLBACKS];
static EditorPluginInitializeCallback plugin_init_callbacks[MAX_INIT_CALLBACKS]; static EditorPluginInitializeCallback plugin_init_callbacks[MAX_INIT_CALLBACKS];
static int build_callback_count; static int build_callback_count;
@ -791,6 +793,8 @@ public:
HashMap<StringName, Variant> get_modified_properties_for_node(Node *p_node, bool p_node_references_only); HashMap<StringName, Variant> get_modified_properties_for_node(Node *p_node, bool p_node_references_only);
HashMap<StringName, Variant> get_modified_properties_reference_to_nodes(Node *p_node, List<Node *> &p_nodes_referenced_by); HashMap<StringName, Variant> get_modified_properties_reference_to_nodes(Node *p_node, List<Node *> &p_nodes_referenced_by);
void set_unfocused_low_processor_usage_mode_enabled(bool p_enabled);
struct AdditiveNodeEntry { struct AdditiveNodeEntry {
Node *node = nullptr; Node *node = nullptr;
NodePath parent; NodePath parent;

View file

@ -229,6 +229,9 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie) {
List<String> instance_args(args); List<String> instance_args(args);
RunInstancesDialog::get_singleton()->get_argument_list_for_instance(i, instance_args); RunInstancesDialog::get_singleton()->get_argument_list_for_instance(i, instance_args);
RunInstancesDialog::get_singleton()->apply_custom_features(i); RunInstancesDialog::get_singleton()->apply_custom_features(i);
if (instance_starting_callback) {
instance_starting_callback(i, instance_args);
}
if (OS::get_singleton()->is_stdout_verbose()) { if (OS::get_singleton()->is_stdout_verbose()) {
print_line(vformat("Running: %s", exec)); print_line(vformat("Running: %s", exec));
@ -281,6 +284,10 @@ void EditorRun::stop() {
running_scene = ""; running_scene = "";
} }
OS::ProcessID EditorRun::get_current_process() const {
return pids.front()->get();
}
EditorRun::EditorRun() { EditorRun::EditorRun() {
status = STATUS_STOP; status = STATUS_STOP;
running_scene = ""; running_scene = "";

View file

@ -33,6 +33,8 @@
#include "core/os/os.h" #include "core/os/os.h"
typedef void (*EditorRunInstanceStarting)(int p_index, List<String> &r_arguments);
class EditorRun { class EditorRun {
public: public:
enum Status { enum Status {
@ -48,6 +50,8 @@ private:
String running_scene; String running_scene;
public: public:
inline static EditorRunInstanceStarting instance_starting_callback = nullptr;
Status get_status() const; Status get_status() const;
String get_running_scene() const; String get_running_scene() const;
@ -58,6 +62,7 @@ public:
void stop_child_process(OS::ProcessID p_pid); void stop_child_process(OS::ProcessID p_pid);
bool has_child_process(OS::ProcessID p_pid) const; bool has_child_process(OS::ProcessID p_pid) const;
int get_child_process_count() const { return pids.size(); } int get_child_process_count() const { return pids.size(); }
OS::ProcessID get_current_process() const;
EditorRun(); EditorRun();
}; };

View file

@ -361,6 +361,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) { void EditorRunBar::set_movie_maker_enabled(bool p_enabled) {
write_movie_button->set_pressed(p_enabled); write_movie_button->set_pressed(p_enabled);
} }

View file

@ -107,6 +107,7 @@ public:
OS::ProcessID has_child_process(OS::ProcessID p_pid) const; OS::ProcessID has_child_process(OS::ProcessID p_pid) const;
void stop_child_process(OS::ProcessID p_pid); void stop_child_process(OS::ProcessID p_pid);
OS::ProcessID get_current_process() const;
void set_movie_maker_enabled(bool p_enabled); void set_movie_maker_enabled(bool p_enabled);
bool is_movie_maker_enabled() const; bool is_movie_maker_enabled() const;

View file

@ -30,11 +30,13 @@
#include "editor_scene_tabs.h" #include "editor_scene_tabs.h"
#include "editor/editor_main_screen.h"
#include "editor/editor_node.h" #include "editor/editor_node.h"
#include "editor/editor_resource_preview.h" #include "editor/editor_resource_preview.h"
#include "editor/editor_settings.h" #include "editor/editor_settings.h"
#include "editor/editor_string_names.h" #include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h" #include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_run_bar.h"
#include "editor/inspector_dock.h" #include "editor/inspector_dock.h"
#include "editor/themes/editor_scale.h" #include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h" #include "scene/gui/box_container.h"
@ -90,6 +92,14 @@ void EditorSceneTabs::_scene_tab_hovered(int p_tab) {
if (!bool(EDITOR_GET("interface/scene_tabs/show_thumbnail_on_hover"))) { if (!bool(EDITOR_GET("interface/scene_tabs/show_thumbnail_on_hover"))) {
return; return;
} }
// Currently the tab previews are displayed under the running game process when embed.
// Right now, the easiest technique to fix that is to prevent displaying the tab preview
// when the user is in the Game View.
if (EditorNode::get_singleton()->get_editor_main_screen()->get_selected_index() == EditorMainScreen::EDITOR_GAME && EditorRunBar::get_singleton()->is_playing()) {
return;
}
int current_tab = scene_tabs->get_current_tab(); int current_tab = scene_tabs->get_current_tab();
if (p_tab == current_tab || p_tab < 0) { if (p_tab == current_tab || p_tab < 0) {

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" stroke="#e0e0e0" stroke-linejoin="round" stroke-miterlimit="0"><path d="M2.1058 5.8844v-2.7749h2.9127M10.875 3.1593h2.7749v2.9127" transform="translate(.122 -.675)"/><path d="M13.757 11.466v2.7749h-2.9127M4.9883 14.191h-2.7749v-2.9127" transform="translate(.015 -.675)"/></g></svg>

After

Width:  |  Height:  |  Size: 359 B

View file

@ -0,0 +1,332 @@
/**************************************************************************/
/* embedded_process.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "embedded_process.h"
#include "editor/editor_string_names.h"
#include "scene/main/window.h"
#include "scene/resources/style_box_flat.h"
#include "scene/theme/theme_db.h"
void EmbeddedProcess::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
window = get_window();
} break;
case NOTIFICATION_PROCESS: {
_check_focused_process_id();
_check_mouse_over();
// We need to detect when the control globally changes location or size on the screen.
// NOTIFICATION_RESIZED and NOTIFICATION_WM_POSITION_CHANGED are not enough to detect
// resized parent to siblings controls that can affect global position.
Rect2i new_global_rect = get_global_rect();
if (last_global_rect != new_global_rect) {
last_global_rect = new_global_rect;
_queue_update_embedded_process();
}
} break;
case NOTIFICATION_DRAW: {
_draw();
} break;
case NOTIFICATION_RESIZED:
case NOTIFICATION_VISIBILITY_CHANGED:
case NOTIFICATION_WM_POSITION_CHANGED: {
_queue_update_embedded_process();
} break;
case NOTIFICATION_THEME_CHANGED: {
focus_style_box = get_theme_stylebox(SNAME("FocusViewport"), EditorStringName(EditorStyles));
Ref<StyleBoxFlat> focus_style_box_flat = focus_style_box;
if (focus_style_box_flat.is_valid()) {
margin_top_left = Point2i(focus_style_box_flat->get_corner_radius(CORNER_TOP_LEFT), focus_style_box_flat->get_corner_radius(CORNER_TOP_LEFT));
margin_bottom_right = Point2i(focus_style_box_flat->get_corner_radius(CORNER_BOTTOM_RIGHT), focus_style_box_flat->get_corner_radius(CORNER_BOTTOM_RIGHT));
} else if (focus_style_box.is_valid()) {
margin_top_left = Point2i(focus_style_box->get_margin(SIDE_LEFT), focus_style_box->get_margin(SIDE_TOP));
margin_bottom_right = Point2i(focus_style_box->get_margin(SIDE_RIGHT), focus_style_box->get_margin(SIDE_BOTTOM));
} else {
margin_top_left = Point2i();
margin_bottom_right = Point2i();
}
} break;
case NOTIFICATION_FOCUS_ENTER: {
_queue_update_embedded_process();
} break;
case NOTIFICATION_APPLICATION_FOCUS_IN: {
application_has_focus = true;
if (embedded_process_was_focused) {
embedded_process_was_focused = false;
// Refocus the embedded process if it was focused when the application lost focus,
// but do not refocus if the embedded process is currently focused (indicating it just lost focus)
// or if the current window is a different popup or secondary window.
if (embedding_completed && current_process_id != focused_process_id && window && window->has_focus()) {
grab_focus();
_queue_update_embedded_process();
}
}
} break;
case NOTIFICATION_APPLICATION_FOCUS_OUT: {
application_has_focus = false;
embedded_process_was_focused = embedding_completed && current_process_id == focused_process_id;
} break;
}
}
void EmbeddedProcess::set_window_size(const Size2i p_window_size) {
if (window_size != p_window_size) {
window_size = p_window_size;
_queue_update_embedded_process();
}
}
void EmbeddedProcess::set_keep_aspect(bool p_keep_aspect) {
if (keep_aspect != p_keep_aspect) {
keep_aspect = p_keep_aspect;
_queue_update_embedded_process();
}
}
Rect2i EmbeddedProcess::_get_global_embedded_window_rect() {
Rect2i control_rect = get_global_rect();
control_rect = Rect2i(control_rect.position, control_rect.size.maxi(1));
if (keep_aspect) {
Rect2i desired_rect = control_rect;
float ratio = MIN((float)control_rect.size.x / window_size.x, (float)control_rect.size.y / window_size.y);
desired_rect.size = Size2i(window_size.x * ratio, window_size.y * ratio).maxi(1);
desired_rect.position = Size2i(control_rect.position.x + ((control_rect.size.x - desired_rect.size.x) / 2), control_rect.position.y + ((control_rect.size.y - desired_rect.size.y) / 2));
return desired_rect;
} else {
return control_rect;
}
}
Rect2i EmbeddedProcess::get_screen_embedded_window_rect() {
Rect2i rect = _get_global_embedded_window_rect();
if (window) {
rect.position += window->get_position();
}
// Removing margins to make space for the focus border style.
return Rect2i(rect.position.x + margin_top_left.x, rect.position.y + margin_top_left.y, MAX(rect.size.x - (margin_top_left.x + margin_bottom_right.x), 1), MAX(rect.size.y - (margin_top_left.y + margin_bottom_right.y), 1));
}
bool EmbeddedProcess::is_embedding_in_progress() {
return !timer_embedding->is_stopped();
}
bool EmbeddedProcess::is_embedding_completed() {
return embedding_completed;
}
void EmbeddedProcess::embed_process(OS::ProcessID p_pid) {
if (!window) {
return;
}
ERR_FAIL_COND_MSG(!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING), "Embedded process not supported by this display server.");
if (current_process_id != 0) {
// Stop embedding the last process.
OS::get_singleton()->kill(current_process_id);
}
reset();
current_process_id = p_pid;
start_embedding_time = OS::get_singleton()->get_ticks_msec();
embedding_grab_focus = has_focus();
set_process(true);
set_notify_transform(true);
// Attempt to embed the process, but if it has just started and the window is not ready yet,
// we will retry in this case.
_try_embed_process();
}
void EmbeddedProcess::reset() {
if (current_process_id != 0 && embedding_completed) {
DisplayServer::get_singleton()->remove_embedded_process(current_process_id);
}
current_process_id = 0;
embedding_completed = false;
start_embedding_time = 0;
embedding_grab_focus = false;
timer_embedding->stop();
set_process(false);
set_notify_transform(false);
queue_redraw();
}
void EmbeddedProcess::_try_embed_process() {
bool is_visible = is_visible_in_tree();
Error err = DisplayServer::get_singleton()->embed_process(window->get_window_id(), current_process_id, get_screen_embedded_window_rect(), is_visible, is_visible && application_has_focus && embedding_grab_focus);
if (err == OK) {
embedding_completed = true;
queue_redraw();
emit_signal(SNAME("embedding_completed"));
} else if (err == ERR_DOES_NOT_EXIST) {
if (OS::get_singleton()->get_ticks_msec() - start_embedding_time >= (uint64_t)embedding_timeout) {
// Embedding process timed out.
reset();
emit_signal(SNAME("embedding_failed"));
} else {
// Tries another shot.
timer_embedding->start();
}
} else {
// Another unknown error.
reset();
emit_signal(SNAME("embedding_failed"));
}
}
bool EmbeddedProcess::_is_embedded_process_updatable() {
return window && current_process_id != 0 && embedding_completed;
}
void EmbeddedProcess::_queue_update_embedded_process() {
if (updated_embedded_process_queued || !_is_embedded_process_updatable()) {
return;
}
updated_embedded_process_queued = true;
callable_mp(this, &EmbeddedProcess::_update_embedded_process).call_deferred();
}
void EmbeddedProcess::_update_embedded_process() {
updated_embedded_process_queued = false;
if (!_is_embedded_process_updatable()) {
return;
}
bool must_grab_focus = false;
bool focus = has_focus();
if (last_updated_embedded_process_focused != focus) {
if (focus) {
must_grab_focus = true;
}
last_updated_embedded_process_focused = focus;
}
DisplayServer::get_singleton()->embed_process(window->get_window_id(), current_process_id, get_screen_embedded_window_rect(), is_visible_in_tree(), must_grab_focus);
emit_signal(SNAME("embedded_process_updated"));
}
void EmbeddedProcess::_timer_embedding_timeout() {
_try_embed_process();
}
void EmbeddedProcess::_draw() {
if (focused_process_id == current_process_id && has_focus() && focus_style_box.is_valid()) {
Size2 size = get_size();
Rect2 r = Rect2(Point2(), size);
focus_style_box->draw(get_canvas_item(), r);
}
}
void EmbeddedProcess::_check_mouse_over() {
// This method checks if the mouse is over the embedded process while the current application is focused.
// The goal is to give focus to the embedded process as soon as the mouse hovers over it,
// allowing the user to interact with it immediately without needing to click first.
if (!is_visible_in_tree() || !embedding_completed || !application_has_focus || !window || !window->has_focus() || Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) || Input::get_singleton()->is_mouse_button_pressed(MouseButton::RIGHT)) {
return;
}
bool focused = has_focus();
// Not stealing focus from a textfield.
if (!focused && get_viewport()->gui_get_focus_owner() && get_viewport()->gui_get_focus_owner()->is_text_field()) {
return;
}
Vector2 mouse_position = DisplayServer::get_singleton()->mouse_get_position();
Rect2i window_rect = get_screen_embedded_window_rect();
if (!window_rect.has_point(mouse_position)) {
return;
}
// Don't grab the focus if mouse over another window.
DisplayServer::WindowID window_id_over = DisplayServer::get_singleton()->get_window_at_screen_position(mouse_position);
if (window_id_over > 0 && window_id_over != window->get_window_id()) {
return;
}
// When we already have the focus and the user moves the mouse over the embedded process,
// we just need to refocus the process.
if (focused) {
_queue_update_embedded_process();
} else {
grab_focus();
queue_redraw();
}
}
void EmbeddedProcess::_check_focused_process_id() {
OS::ProcessID process_id = DisplayServer::get_singleton()->get_focused_process_id();
if (process_id != focused_process_id) {
focused_process_id = process_id;
if (focused_process_id == current_process_id) {
// The embedded process got the focus.
if (has_focus()) {
// Redraw to updated the focus style.
queue_redraw();
} else {
grab_focus();
}
} else if (has_focus()) {
release_focus();
}
}
}
void EmbeddedProcess::_bind_methods() {
ADD_SIGNAL(MethodInfo("embedding_completed"));
ADD_SIGNAL(MethodInfo("embedding_failed"));
ADD_SIGNAL(MethodInfo("embedded_process_updated"));
}
EmbeddedProcess::EmbeddedProcess() {
timer_embedding = memnew(Timer);
timer_embedding->set_wait_time(0.1);
timer_embedding->set_one_shot(true);
add_child(timer_embedding);
timer_embedding->connect("timeout", callable_mp(this, &EmbeddedProcess::_timer_embedding_timeout));
set_focus_mode(FOCUS_ALL);
}
EmbeddedProcess::~EmbeddedProcess() {
if (current_process_id != 0) {
// Stop embedding the last process.
OS::get_singleton()->kill(current_process_id);
reset();
}
}

View file

@ -0,0 +1,90 @@
/**************************************************************************/
/* embedded_process.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef EMBEDDED_PROCESS_H
#define EMBEDDED_PROCESS_H
#include "scene/gui/control.h"
class EmbeddedProcess : public Control {
GDCLASS(EmbeddedProcess, Control);
bool application_has_focus = true;
bool embedded_process_was_focused = false;
OS::ProcessID focused_process_id = 0;
OS::ProcessID current_process_id = 0;
bool embedding_grab_focus = false;
bool embedding_completed = false;
uint64_t start_embedding_time = 0;
bool updated_embedded_process_queued = false;
bool last_updated_embedded_process_focused = false;
Window *window = nullptr;
Timer *timer_embedding = nullptr;
const int embedding_timeout = 45000;
bool keep_aspect = false;
Size2i window_size;
Ref<StyleBox> focus_style_box;
Point2i margin_top_left;
Point2i margin_bottom_right;
Rect2i last_global_rect;
void _try_embed_process();
void _queue_update_embedded_process();
void _update_embedded_process();
void _timer_embedding_timeout();
void _draw();
void _check_mouse_over();
void _check_focused_process_id();
bool _is_embedded_process_updatable();
Rect2i _get_global_embedded_window_rect();
protected:
static void _bind_methods();
void _notification(int p_what);
public:
void embed_process(OS::ProcessID p_pid);
void reset();
void set_window_size(const Size2i p_window_size);
void set_keep_aspect(bool p_keep_aspect);
Rect2i get_screen_embedded_window_rect();
bool is_embedding_in_progress();
bool is_embedding_completed();
EmbeddedProcess();
~EmbeddedProcess();
};
#endif // EMBEDDED_PROCESS_H

View file

@ -30,12 +30,20 @@
#include "game_view_plugin.h" #include "game_view_plugin.h"
#include "core/config/project_settings.h"
#include "core/debugger/debugger_marshalls.h" #include "core/debugger/debugger_marshalls.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_command_palette.h"
#include "editor/editor_interface.h"
#include "editor/editor_main_screen.h" #include "editor/editor_main_screen.h"
#include "editor/editor_node.h" #include "editor/editor_node.h"
#include "editor/editor_settings.h" #include "editor/editor_settings.h"
#include "editor/gui/editor_run_bar.h"
#include "editor/plugins/embedded_process.h"
#include "editor/themes/editor_scale.h" #include "editor/themes/editor_scale.h"
#include "editor/window_wrapper.h"
#include "scene/gui/button.h" #include "scene/gui/button.h"
#include "scene/gui/label.h"
#include "scene/gui/menu_button.h" #include "scene/gui/menu_button.h"
#include "scene/gui/panel.h" #include "scene/gui/panel.h"
#include "scene/gui/separator.h" #include "scene/gui/separator.h"
@ -184,6 +192,79 @@ void GameView::_sessions_changed() {
_update_debugger_buttons(); _update_debugger_buttons();
} }
void GameView::_instance_starting_static(int p_idx, List<String> &r_arguments) {
ERR_FAIL_NULL(singleton);
singleton->_instance_starting(p_idx, r_arguments);
}
void GameView::_instance_starting(int p_idx, List<String> &r_arguments) {
if (p_idx == 0 && embed_on_play && make_floating_on_play && !window_wrapper->get_window_enabled() && EditorNode::get_singleton()->is_multi_window_enabled()) {
window_wrapper->restore_window_from_saved_position(floating_window_rect, floating_window_screen, floating_window_screen_rect);
}
_update_arguments_for_instance(p_idx, r_arguments);
}
void GameView::_play_pressed() {
OS::ProcessID current_process_id = EditorRunBar::get_singleton()->get_current_process();
if (current_process_id == 0) {
return;
}
if (!window_wrapper->get_window_enabled()) {
screen_index_before_start = EditorNode::get_singleton()->get_editor_main_screen()->get_selected_index();
}
if (embed_on_play) {
// It's important to disable the low power mode when unfocused because otherwise
// the button in the editor are not responsive and if the user moves the mouse quickly,
// the mouse clicks are not registered.
EditorNode::get_singleton()->set_unfocused_low_processor_usage_mode_enabled(false);
_update_embed_window_size();
if (!window_wrapper->get_window_enabled()) {
EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_GAME);
embedded_process->grab_focus();
}
embedded_process->embed_process(current_process_id);
_update_ui();
}
}
void GameView::_stop_pressed() {
EditorNode::get_singleton()->set_unfocused_low_processor_usage_mode_enabled(true);
embedded_process->reset();
_update_ui();
if (window_wrapper->get_window_enabled()) {
window_wrapper->set_window_enabled(false);
}
if (screen_index_before_start >= 0 && EditorNode::get_singleton()->get_editor_main_screen()->get_selected_index() == EditorMainScreen::EDITOR_GAME) {
// We go back to the screen where the user was before starting the game.
EditorNode::get_singleton()->get_editor_main_screen()->select(screen_index_before_start);
}
screen_index_before_start = -1;
}
void GameView::_embedding_completed() {
_update_ui();
}
void GameView::_embedding_failed() {
state_label->set_text(TTR("Connection impossible to the game process."));
}
void GameView::_embedded_process_updated() {
const Rect2i game_rect = embedded_process->get_screen_embedded_window_rect();
game_size_label->set_text(vformat("%dx%d", game_rect.size.x, game_rect.size.y));
}
void GameView::_project_settings_changed() {
// Update the window size and aspect ratio.
_update_embed_window_size();
}
void GameView::_update_debugger_buttons() { void GameView::_update_debugger_buttons() {
bool empty = active_sessions == 0; bool empty = active_sessions == 0;
@ -229,6 +310,67 @@ void GameView::_select_mode_pressed(int p_option) {
debugger->set_select_mode(mode); debugger->set_select_mode(mode);
} }
void GameView::_embed_options_menu_menu_id_pressed(int p_id) {
switch (p_id) {
case EMBED_RUN_GAME_EMBEDDED: {
embed_on_play = !embed_on_play;
EditorSettings::get_singleton()->set_project_metadata("game_view", "embed_on_play", embed_on_play);
} break;
case EMBED_MAKE_FLOATING_ON_PLAY: {
make_floating_on_play = !make_floating_on_play;
EditorSettings::get_singleton()->set_project_metadata("game_view", "make_floating_on_play", make_floating_on_play);
} break;
}
_update_embed_menu_options();
}
void GameView::_keep_aspect_button_pressed() {
embedded_process->set_keep_aspect(keep_aspect_button->is_pressed());
}
void GameView::_update_ui() {
bool show_game_size = false;
if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) {
state_label->set_text(TTR("Game embedding not available on your OS."));
} else if (embedded_process->is_embedding_completed()) {
state_label->set_text("");
show_game_size = true;
} else if (embedded_process->is_embedding_in_progress()) {
state_label->set_text(TTR("Game starting..."));
} else if (EditorRunBar::get_singleton()->is_playing()) {
state_label->set_text(TTR("Game running not embedded."));
} else if (embed_on_play) {
state_label->set_text(TTR("Press play to start the game."));
} else {
state_label->set_text(TTR("Embedding is disabled."));
}
game_size_label->set_visible(show_game_size);
}
void GameView::_update_embed_menu_options() {
PopupMenu *menu = embed_options_menu->get_popup();
menu->set_item_checked(menu->get_item_index(EMBED_RUN_GAME_EMBEDDED), embed_on_play);
menu->set_item_checked(menu->get_item_index(EMBED_MAKE_FLOATING_ON_PLAY), make_floating_on_play);
// When embed is Off or in single window mode, Make floating is not available.
menu->set_item_disabled(menu->get_item_index(EMBED_MAKE_FLOATING_ON_PLAY), !embed_on_play || !EditorNode::get_singleton()->is_multi_window_enabled());
}
void GameView::_update_embed_window_size() {
Size2 window_size;
window_size.x = GLOBAL_GET("display/window/size/viewport_width");
window_size.y = GLOBAL_GET("display/window/size/viewport_height");
Size2 desired_size;
desired_size.x = GLOBAL_GET("display/window/size/window_width_override");
desired_size.y = GLOBAL_GET("display/window/size/window_height_override");
if (desired_size.x > 0 && desired_size.y > 0) {
window_size = desired_size;
}
embedded_process->set_window_size(window_size);
}
void GameView::_hide_selection_toggled(bool p_pressed) { void GameView::_hide_selection_toggled(bool p_pressed) {
hide_selection->set_button_icon(get_editor_theme_icon(p_pressed ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible"))); hide_selection->set_button_icon(get_editor_theme_icon(p_pressed ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible")));
@ -287,10 +429,44 @@ void GameView::_notification(int p_what) {
select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_button_icon(get_editor_theme_icon(SNAME("ListSelect"))); select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_button_icon(get_editor_theme_icon(SNAME("ListSelect")));
hide_selection->set_button_icon(get_editor_theme_icon(hide_selection->is_pressed() ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible"))); hide_selection->set_button_icon(get_editor_theme_icon(hide_selection->is_pressed() ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible")));
keep_aspect_button->set_button_icon(get_editor_theme_icon(SNAME("KeepAspect")));
embed_options_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
camera_override_button->set_button_icon(get_editor_theme_icon(SNAME("Camera"))); camera_override_button->set_button_icon(get_editor_theme_icon(SNAME("Camera")));
camera_override_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl"))); camera_override_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
} break; } break;
case NOTIFICATION_READY: {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) {
// Embedding available.
embed_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "embed_on_play", true);
make_floating_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "make_floating_on_play", true);
keep_aspect_button->set_pressed(EditorSettings::get_singleton()->get_project_metadata("game_view", "keep_aspect", true));
_update_embed_menu_options();
EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &GameView::_play_pressed));
EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &GameView::_stop_pressed));
EditorRun::instance_starting_callback = _instance_starting_static;
// Listen for project settings changes to update the window size and aspect ratio.
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &GameView::_project_settings_changed));
embedded_process->set_keep_aspect(keep_aspect_button->is_pressed());
} else {
// Embedding not available.
embedding_separator->hide();
embed_options_menu->hide();
keep_aspect_button->hide();
keep_aspect_button->hide();
}
_update_ui();
} break;
case NOTIFICATION_WM_POSITION_CHANGED: {
if (window_wrapper->get_window_enabled()) {
_update_floating_window_settings();
}
} break;
} }
} }
@ -329,8 +505,85 @@ Dictionary GameView::get_state() const {
return d; return d;
} }
GameView::GameView(Ref<GameViewDebugger> p_debugger) { void GameView::set_window_layout(Ref<ConfigFile> p_layout) {
floating_window_rect = p_layout->get_value("GameView", "floating_window_rect", Rect2i());
floating_window_screen = p_layout->get_value("GameView", "floating_window_screen", -1);
floating_window_screen_rect = p_layout->get_value("GameView", "floating_window_screen_rect", Rect2i());
}
void GameView::get_window_layout(Ref<ConfigFile> p_layout) {
if (window_wrapper->get_window_enabled()) {
_update_floating_window_settings();
}
p_layout->set_value("GameView", "floating_window_rect", floating_window_rect);
p_layout->set_value("GameView", "floating_window_screen", floating_window_screen);
p_layout->set_value("GameView", "floating_window_screen_rect", floating_window_screen_rect);
}
void GameView::_update_floating_window_settings() {
if (window_wrapper->get_window_enabled()) {
floating_window_rect = window_wrapper->get_window_rect();
floating_window_screen = window_wrapper->get_window_screen();
floating_window_screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(floating_window_screen);
}
}
void GameView::_update_arguments_for_instance(int p_idx, List<String> &r_arguments) {
if (p_idx != 0 || !embed_on_play || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) {
return;
}
// Remove duplicates/unwanted parameters.
List<String>::Element *E = r_arguments.front();
while (E) {
List<String>::Element *N = E->next();
//For these parameters, we need to also renove the value.
if (E->get() == "--position" || E->get() == "--resolution" || E->get() == "--screen") {
r_arguments.erase(E);
if (N) {
List<String>::Element *V = N->next();
r_arguments.erase(N);
N = V;
}
} else if (E->get() == "-f" || E->get() == "--fullscreen" || E->get() == "-m" || E->get() == "--maximized" || E->get() == "-t" || E->get() == "-always-on-top") {
r_arguments.erase(E);
}
E = N;
}
// Add the editor window's native ID so the started game can directly set it as its parent.
r_arguments.push_back("--wid");
r_arguments.push_back(itos(DisplayServer::get_singleton()->window_get_native_handle(DisplayServer::WINDOW_HANDLE, get_window()->get_window_id())));
// Be sure to have the correct window size in the embedded_process control.
_update_embed_window_size();
Rect2i rect = embedded_process->get_screen_embedded_window_rect();
r_arguments.push_back("--position");
r_arguments.push_back(itos(rect.position.x) + "," + itos(rect.position.y));
r_arguments.push_back("--resolution");
r_arguments.push_back(itos(rect.size.x) + "x" + itos(rect.size.y));
}
void GameView::_window_before_closing() {
// Before the parent window closed, we close the embedded game. That prevents
// the embedded game to be seen without a parent window for a fraction of second.
if (EditorRunBar::get_singleton()->is_playing() && (embedded_process->is_embedding_completed() || embedded_process->is_embedding_in_progress())) {
embedded_process->reset();
// Call deferred to prevent the _stop_pressed callback to be executed before the wrapper window
// actually closes.
callable_mp(EditorRunBar::get_singleton(), &EditorRunBar::stop_playing).call_deferred();
}
}
GameView::GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper) {
singleton = this;
debugger = p_debugger; debugger = p_debugger;
window_wrapper = p_wrapper;
// Add some margin to the sides for better aesthetics. // Add some margin to the sides for better aesthetics.
// This prevents the first button's hover/pressed effect from "touching" the panel's border, // This prevents the first button's hover/pressed effect from "touching" the panel's border,
@ -438,21 +691,86 @@ GameView::GameView(Ref<GameViewDebugger> p_debugger) {
menu->set_item_checked(menu->get_item_index(CAMERA_MODE_INGAME), true); menu->set_item_checked(menu->get_item_index(CAMERA_MODE_INGAME), true);
menu->add_radio_check_item(TTR("Manipulate From Editors"), CAMERA_MODE_EDITORS); menu->add_radio_check_item(TTR("Manipulate From Editors"), CAMERA_MODE_EDITORS);
_update_debugger_buttons(); embedding_separator = memnew(VSeparator);
main_menu_hbox->add_child(embedding_separator);
keep_aspect_button = memnew(Button);
main_menu_hbox->add_child(keep_aspect_button);
keep_aspect_button->set_toggle_mode(true);
keep_aspect_button->set_theme_type_variation("FlatButton");
keep_aspect_button->set_tooltip_text(TTR("Keep the aspect ratio of the embedded game."));
keep_aspect_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_keep_aspect_button_pressed));
embed_options_menu = memnew(MenuButton);
main_menu_hbox->add_child(embed_options_menu);
embed_options_menu->set_flat(false);
embed_options_menu->set_theme_type_variation("FlatMenuButton");
embed_options_menu->set_h_size_flags(SIZE_SHRINK_END);
embed_options_menu->set_tooltip_text(TTR("Embedding Options"));
menu = embed_options_menu->get_popup();
menu->connect(SceneStringName(id_pressed), callable_mp(this, &GameView::_embed_options_menu_menu_id_pressed));
menu->add_check_item(TTR("Embed Game on Next Play"), EMBED_RUN_GAME_EMBEDDED);
menu->add_check_item(TTR("Make Game Workspace Floating on Next Play"), EMBED_MAKE_FLOATING_ON_PLAY);
main_menu_hbox->add_spacer();
game_size_label = memnew(Label());
main_menu_hbox->add_child(game_size_label);
game_size_label->hide();
panel = memnew(Panel); panel = memnew(Panel);
add_child(panel); add_child(panel);
panel->set_theme_type_variation("GamePanel"); panel->set_theme_type_variation("GamePanel");
panel->set_v_size_flags(SIZE_EXPAND_FILL); panel->set_v_size_flags(SIZE_EXPAND_FILL);
embedded_process = memnew(EmbeddedProcess);
panel->add_child(embedded_process);
embedded_process->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
embedded_process->connect("embedding_failed", callable_mp(this, &GameView::_embedding_failed));
embedded_process->connect("embedding_completed", callable_mp(this, &GameView::_embedding_completed));
embedded_process->connect("embedded_process_updated", callable_mp(this, &GameView::_embedded_process_updated));
embedded_process->set_custom_minimum_size(Size2i(100, 100));
state_label = memnew(Label());
panel->add_child(state_label);
state_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
state_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
state_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD);
state_label->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
_update_debugger_buttons();
p_debugger->connect("session_started", callable_mp(this, &GameView::_sessions_changed)); p_debugger->connect("session_started", callable_mp(this, &GameView::_sessions_changed));
p_debugger->connect("session_stopped", callable_mp(this, &GameView::_sessions_changed)); p_debugger->connect("session_stopped", callable_mp(this, &GameView::_sessions_changed));
p_wrapper->connect("window_before_closing", callable_mp(this, &GameView::_window_before_closing));
p_wrapper->connect("window_size_changed", callable_mp(this, &GameView::_update_floating_window_settings));
} }
/////// ///////
void GameViewPlugin::make_visible(bool p_visible) { void GameViewPlugin::make_visible(bool p_visible) {
game_view->set_visible(p_visible); if (p_visible) {
window_wrapper->show();
} else {
window_wrapper->hide();
}
}
void GameViewPlugin::selected_notify() {
if (window_wrapper->get_window_enabled()) {
window_wrapper->grab_window_focus();
_focus_another_editor();
}
}
void GameViewPlugin::set_window_layout(Ref<ConfigFile> p_layout) {
game_view->set_window_layout(p_layout);
}
void GameViewPlugin::get_window_layout(Ref<ConfigFile> p_layout) {
game_view->get_window_layout(p_layout);
} }
void GameViewPlugin::set_state(const Dictionary &p_state) { void GameViewPlugin::set_state(const Dictionary &p_state) {
@ -467,20 +785,48 @@ void GameViewPlugin::_notification(int p_what) {
switch (p_what) { switch (p_what) {
case NOTIFICATION_ENTER_TREE: { case NOTIFICATION_ENTER_TREE: {
add_debugger_plugin(debugger); add_debugger_plugin(debugger);
connect("main_screen_changed", callable_mp(this, &GameViewPlugin::_save_last_editor));
} break; } break;
case NOTIFICATION_EXIT_TREE: { case NOTIFICATION_EXIT_TREE: {
remove_debugger_plugin(debugger); remove_debugger_plugin(debugger);
disconnect("main_screen_changed", callable_mp(this, &GameViewPlugin::_save_last_editor));
} break; } break;
} }
} }
void GameViewPlugin::_window_visibility_changed(bool p_visible) {
_focus_another_editor();
}
void GameViewPlugin::_save_last_editor(const String &p_editor) {
if (p_editor != get_name()) {
last_editor = p_editor;
}
}
void GameViewPlugin::_focus_another_editor() {
if (window_wrapper->get_window_enabled()) {
ERR_FAIL_COND(last_editor.is_empty());
EditorInterface::get_singleton()->set_main_screen_editor(last_editor);
}
}
GameViewPlugin::GameViewPlugin() { GameViewPlugin::GameViewPlugin() {
window_wrapper = memnew(WindowWrapper);
window_wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), TTR("Game Workspace")));
window_wrapper->set_margins_enabled(true);
debugger.instantiate(); debugger.instantiate();
game_view = memnew(GameView(debugger)); game_view = memnew(GameView(debugger, window_wrapper));
game_view->set_v_size_flags(Control::SIZE_EXPAND_FILL); game_view->set_v_size_flags(Control::SIZE_EXPAND_FILL);
EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(game_view);
game_view->hide(); window_wrapper->set_wrapped_control(game_view, nullptr);
EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(window_wrapper);
window_wrapper->set_v_size_flags(Control::SIZE_EXPAND_FILL);
window_wrapper->hide();
window_wrapper->connect("window_visibility_changed", callable_mp(this, &GameViewPlugin::_window_visibility_changed));
} }
GameViewPlugin::~GameViewPlugin() { GameViewPlugin::~GameViewPlugin() {

View file

@ -37,6 +37,10 @@
#include "scene/debugger/scene_debugger.h" #include "scene/debugger/scene_debugger.h"
#include "scene/gui/box_container.h" #include "scene/gui/box_container.h"
class EmbeddedProcess;
class VSeparator;
class WindowWrapper;
class GameViewDebugger : public EditorDebuggerPlugin { class GameViewDebugger : public EditorDebuggerPlugin {
GDCLASS(GameViewDebugger, EditorDebuggerPlugin); GDCLASS(GameViewDebugger, EditorDebuggerPlugin);
@ -82,11 +86,24 @@ class GameView : public VBoxContainer {
CAMERA_RESET_3D, CAMERA_RESET_3D,
CAMERA_MODE_INGAME, CAMERA_MODE_INGAME,
CAMERA_MODE_EDITORS, CAMERA_MODE_EDITORS,
EMBED_RUN_GAME_EMBEDDED,
EMBED_MAKE_FLOATING_ON_PLAY,
}; };
inline static GameView *singleton = nullptr;
Ref<GameViewDebugger> debugger; Ref<GameViewDebugger> debugger;
WindowWrapper *window_wrapper = nullptr;
int active_sessions = 0; int active_sessions = 0;
int screen_index_before_start = -1;
bool embed_on_play = true;
bool make_floating_on_play = true;
Rect2i floating_window_rect;
int floating_window_screen = -1;
Rect2i floating_window_screen_rect;
Button *suspend_button = nullptr; Button *suspend_button = nullptr;
Button *next_frame_button = nullptr; Button *next_frame_button = nullptr;
@ -99,7 +116,14 @@ class GameView : public VBoxContainer {
Button *camera_override_button = nullptr; Button *camera_override_button = nullptr;
MenuButton *camera_override_menu = nullptr; MenuButton *camera_override_menu = nullptr;
VSeparator *embedding_separator = nullptr;
Button *keep_aspect_button = nullptr;
MenuButton *embed_options_menu = nullptr;
Label *game_size_label = nullptr;
Panel *panel = nullptr; Panel *panel = nullptr;
EmbeddedProcess *embedded_process = nullptr;
Label *state_label = nullptr;
void _sessions_changed(); void _sessions_changed();
@ -109,12 +133,31 @@ class GameView : public VBoxContainer {
void _node_type_pressed(int p_option); void _node_type_pressed(int p_option);
void _select_mode_pressed(int p_option); void _select_mode_pressed(int p_option);
void _embed_options_menu_menu_id_pressed(int p_id);
void _keep_aspect_button_pressed();
void _play_pressed();
static void _instance_starting_static(int p_idx, List<String> &r_arguments);
void _instance_starting(int p_idx, List<String> &r_arguments);
void _stop_pressed();
void _embedding_completed();
void _embedding_failed();
void _embedded_process_updated();
void _project_settings_changed();
void _update_ui();
void _update_embed_menu_options();
void _update_embed_window_size();
void _update_arguments_for_instance(int p_idx, List<String> &r_arguments);
void _hide_selection_toggled(bool p_pressed); void _hide_selection_toggled(bool p_pressed);
void _camera_override_button_toggled(bool p_pressed); void _camera_override_button_toggled(bool p_pressed);
void _camera_override_menu_id_pressed(int p_id); void _camera_override_menu_id_pressed(int p_id);
void _window_before_closing();
void _update_floating_window_settings();
protected: protected:
void _notification(int p_what); void _notification(int p_what);
@ -122,16 +165,26 @@ public:
void set_state(const Dictionary &p_state); void set_state(const Dictionary &p_state);
Dictionary get_state() const; Dictionary get_state() const;
GameView(Ref<GameViewDebugger> p_debugger); void set_window_layout(Ref<ConfigFile> p_layout);
void get_window_layout(Ref<ConfigFile> p_layout);
GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper);
}; };
class GameViewPlugin : public EditorPlugin { class GameViewPlugin : public EditorPlugin {
GDCLASS(GameViewPlugin, EditorPlugin); GDCLASS(GameViewPlugin, EditorPlugin);
GameView *game_view = nullptr; GameView *game_view = nullptr;
WindowWrapper *window_wrapper = nullptr;
Ref<GameViewDebugger> debugger; Ref<GameViewDebugger> debugger;
String last_editor;
void _window_visibility_changed(bool p_visible);
void _save_last_editor(const String &p_editor);
void _focus_another_editor();
protected: protected:
void _notification(int p_what); void _notification(int p_what);
@ -141,6 +194,10 @@ public:
virtual void edit(Object *p_object) override {} virtual void edit(Object *p_object) override {}
virtual bool handles(Object *p_object) const override { return false; } virtual bool handles(Object *p_object) const override { return false; }
virtual void make_visible(bool p_visible) override; virtual void make_visible(bool p_visible) override;
virtual void selected_notify() override;
virtual void set_window_layout(Ref<ConfigFile> p_layout) override;
virtual void get_window_layout(Ref<ConfigFile> p_layout) override;
virtual void set_state(const Dictionary &p_state) override; virtual void set_state(const Dictionary &p_state) override;
virtual Dictionary get_state() const override; virtual Dictionary get_state() const override;

View file

@ -101,6 +101,12 @@ void WindowWrapper::_set_window_enabled_with_rect(bool p_visible, const Rect2 p_
Node *parent = _get_wrapped_control_parent(); Node *parent = _get_wrapped_control_parent();
// In the GameView plugin, we need to the the signal before the window is actually closed
// to prevent the embedded game to be seen the parent window for a fraction of a second.
if (!p_visible) {
emit_signal("window_before_closing");
}
if (wrapped_control->get_parent() != parent) { if (wrapped_control->get_parent() != parent) {
// Move the control to the window. // Move the control to the window.
wrapped_control->reparent(parent, false); wrapped_control->reparent(parent, false);
@ -131,9 +137,15 @@ void WindowWrapper::_set_window_rect(const Rect2 p_rect) {
} }
} }
void WindowWrapper::_window_size_changed() {
emit_signal(SNAME("window_size_changed"));
}
void WindowWrapper::_bind_methods() { void WindowWrapper::_bind_methods() {
ADD_SIGNAL(MethodInfo("window_visibility_changed", PropertyInfo(Variant::BOOL, "visible"))); ADD_SIGNAL(MethodInfo("window_visibility_changed", PropertyInfo(Variant::BOOL, "visible")));
ADD_SIGNAL(MethodInfo("window_close_requested")); ADD_SIGNAL(MethodInfo("window_close_requested"));
ADD_SIGNAL(MethodInfo("window_before_closing"));
ADD_SIGNAL(MethodInfo("window_size_changed"));
} }
void WindowWrapper::_notification(int p_what) { void WindowWrapper::_notification(int p_what) {
@ -142,11 +154,9 @@ void WindowWrapper::_notification(int p_what) {
} }
switch (p_what) { switch (p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: { case NOTIFICATION_VISIBILITY_CHANGED: {
if (get_window_enabled() && is_visible()) { // Grab the focus when WindowWrapper.set_visible(true) is called
// Grab the focus when WindowWrapper.set_visible(true) is called // and the window is showing.
// and the window is showing. grab_window_focus();
window->grab_focus();
}
} break; } break;
case NOTIFICATION_READY: { case NOTIFICATION_READY: {
set_process_shortcut_input(true); set_process_shortcut_input(true);
@ -314,6 +324,12 @@ void WindowWrapper::set_margins_enabled(bool p_enabled) {
} }
} }
void WindowWrapper::grab_window_focus() {
if (get_window_enabled() && is_visible()) {
window->grab_focus();
}
}
WindowWrapper::WindowWrapper() { WindowWrapper::WindowWrapper() {
if (!EditorNode::get_singleton()->is_multi_window_enabled()) { if (!EditorNode::get_singleton()->is_multi_window_enabled()) {
return; return;
@ -326,6 +342,7 @@ WindowWrapper::WindowWrapper() {
window->hide(); window->hide();
window->connect("close_requested", callable_mp(this, &WindowWrapper::set_window_enabled).bind(false)); window->connect("close_requested", callable_mp(this, &WindowWrapper::set_window_enabled).bind(false));
window->connect("size_changed", callable_mp(this, &WindowWrapper::_window_size_changed));
ShortcutBin *capturer = memnew(ShortcutBin); ShortcutBin *capturer = memnew(ShortcutBin);
window->add_child(capturer); window->add_child(capturer);

View file

@ -54,6 +54,7 @@ class WindowWrapper : public MarginContainer {
void _set_window_enabled_with_rect(bool p_visible, const Rect2 p_rect); void _set_window_enabled_with_rect(bool p_visible, const Rect2 p_rect);
void _set_window_rect(const Rect2 p_rect); void _set_window_rect(const Rect2 p_rect);
void _window_size_changed();
protected: protected:
static void _bind_methods(); static void _bind_methods();
@ -80,6 +81,7 @@ public:
void set_window_title(const String &p_title); void set_window_title(const String &p_title);
void set_margins_enabled(bool p_enabled); void set_margins_enabled(bool p_enabled);
void grab_window_focus();
WindowWrapper(); WindowWrapper();
}; };

View file

@ -226,6 +226,7 @@ static bool init_always_on_top = false;
static bool init_use_custom_pos = false; static bool init_use_custom_pos = false;
static bool init_use_custom_screen = false; static bool init_use_custom_screen = false;
static Vector2 init_custom_pos; static Vector2 init_custom_pos;
static int64_t init_embed_parent_window_id = 0;
// Debug // Debug
@ -621,6 +622,7 @@ void Main::print_help(const char *p_binary) {
#ifndef _3D_DISABLED #ifndef _3D_DISABLED
print_help_option("--xr-mode <mode>", "Select XR (Extended Reality) mode [\"default\", \"off\", \"on\"].\n"); print_help_option("--xr-mode <mode>", "Select XR (Extended Reality) mode [\"default\", \"off\", \"on\"].\n");
#endif #endif
print_help_option("--wid <window_id>", "Request parented to window.\n");
print_help_title("Debug options"); print_help_title("Debug options");
print_help_option("-d, --debug", "Debug (local stdout debugger).\n"); print_help_option("-d, --debug", "Debug (local stdout debugger).\n");
@ -1811,6 +1813,23 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
goto error; goto error;
} }
#endif // TOOLS_ENABLED #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("<window_id> argument for --wid <window_id> must be different then 0.\n");
goto error;
}
OS::get_singleton()->_embedded_in_editor = true;
Engine::get_singleton()->set_embedded_in_editor(true);
N = N->next();
} else {
OS::get_singleton()->print("Missing <window_id> argument for --wid <window_id>.\n");
goto error;
}
} else if (arg == "--" || arg == "++") { } else if (arg == "--" || arg == "++") {
adding_user_args = true; adding_user_args = true;
} else { } else {
@ -2974,9 +2993,16 @@ Error Main::setup2(bool p_show_boot_logo) {
context = DisplayServer::CONTEXT_ENGINE; context = DisplayServer::CONTEXT_ENGINE;
} }
if (init_embed_parent_window_id) {
// Reset flags and other settings to be sure it's borderless and windowed. The position and size should have been initialized correctly
// from --position and --resolution parameters.
window_mode = DisplayServer::WINDOW_MODE_WINDOWED;
window_flags = DisplayServer::WINDOW_FLAG_BORDERLESS_BIT;
}
// rendering_driver now held in static global String in main and initialized in setup() // rendering_driver now held in static global String in main and initialized in setup()
Error err; 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) { if (err != OK || display_server == nullptr) {
String last_name = DisplayServer::get_create_function_name(display_driver_idx); String last_name = DisplayServer::get_create_function_name(display_driver_idx);
@ -2990,7 +3016,7 @@ Error Main::setup2(bool p_show_boot_logo) {
String name = DisplayServer::get_create_function_name(i); String name = DisplayServer::get_create_function_name(i);
WARN_PRINT(vformat("Display driver %s failed, falling back to %s.", last_name, name)); 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) { if (err == OK && display_server != nullptr) {
break; break;
} }
@ -3193,15 +3219,17 @@ Error Main::setup2(bool p_show_boot_logo) {
MAIN_PRINT("Main: Setup Logo"); MAIN_PRINT("Main: Setup Logo");
if (init_windowed) { if (!init_embed_parent_window_id) {
//do none.. if (init_windowed) {
} else if (init_maximized) { //do none..
DisplayServer::get_singleton()->window_set_mode(DisplayServer::WINDOW_MODE_MAXIMIZED); } else if (init_maximized) {
} else if (init_fullscreen) { DisplayServer::get_singleton()->window_set_mode(DisplayServer::WINDOW_MODE_MAXIMIZED);
DisplayServer::get_singleton()->window_set_mode(DisplayServer::WINDOW_MODE_FULLSCREEN); } 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_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)); Color clear = GLOBAL_DEF_BASIC("rendering/environment/defaults/default_clear_color", Color(0.3, 0.3, 0.3));

View file

@ -568,8 +568,8 @@ Vector<String> DisplayServerAndroid::get_rendering_drivers_func() {
return drivers; 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 *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, 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 (r_error != OK) {
if (p_rendering_driver == "vulkan") { if (p_rendering_driver == "vulkan") {
OS::get_singleton()->alert( OS::get_singleton()->alert(
@ -636,7 +636,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; rendering_driver = p_rendering_driver;
keep_screen_on = GLOBAL_GET("display/window/energy_saving/keep_screen_on"); keep_screen_on = GLOBAL_GET("display/window/energy_saving/keep_screen_on");

View file

@ -224,7 +224,7 @@ public:
virtual void mouse_set_mode(MouseMode p_mode) override; virtual void mouse_set_mode(MouseMode p_mode) override;
virtual MouseMode mouse_get_mode() const 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<String> get_rendering_drivers_func(); static Vector<String> get_rendering_drivers_func();
static void register_android_driver(); static void register_android_driver();
@ -241,7 +241,7 @@ public:
virtual void set_native_icon(const String &p_filename) override; virtual void set_native_icon(const String &p_filename) override;
virtual void set_icon(const Ref<Image> &p_icon) override; virtual void set_icon(const Ref<Image> &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(); ~DisplayServerAndroid();
}; };

View file

@ -84,7 +84,7 @@ class DisplayServerIOS : public DisplayServer {
void perform_event(const Ref<InputEvent> &p_event); void perform_event(const Ref<InputEvent> &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(); ~DisplayServerIOS();
public: public:
@ -93,7 +93,7 @@ public:
static DisplayServerIOS *get_singleton(); static DisplayServerIOS *get_singleton();
static void register_ios_driver(); 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<String> get_rendering_drivers_func(); static Vector<String> get_rendering_drivers_func();
// MARK: - Events // MARK: - Events

View file

@ -53,7 +53,7 @@ DisplayServerIOS *DisplayServerIOS::get_singleton() {
return (DisplayServerIOS *)DisplayServer::get_singleton(); 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(); KeyMappingIOS::initialize();
rendering_driver = p_rendering_driver; rendering_driver = p_rendering_driver;
@ -196,8 +196,8 @@ DisplayServerIOS::~DisplayServerIOS() {
#endif #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) { 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, 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<String> DisplayServerIOS::get_rendering_drivers_func() { Vector<String> DisplayServerIOS::get_rendering_drivers_func() {

View file

@ -1309,8 +1309,8 @@ Vector<String> DisplayServerWayland::get_rendering_drivers_func() {
return drivers; 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 *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, 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) { if (r_error != OK) {
ERR_PRINT("Can't create the Wayland display server."); ERR_PRINT("Can't create the Wayland display server.");
memdelete(ds); memdelete(ds);
@ -1320,7 +1320,7 @@ DisplayServer *DisplayServerWayland::create_func(const String &p_rendering_drive
return ds; 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 GLES3_ENABLED
#ifdef SOWRAP_ENABLED #ifdef SOWRAP_ENABLED
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED

View file

@ -294,12 +294,12 @@ public:
virtual bool is_window_transparency_available() const override; 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<String> get_rendering_drivers_func(); static Vector<String> get_rendering_drivers_func();
static void register_wayland_driver(); 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(); ~DisplayServerWayland();
}; };

View file

@ -142,6 +142,7 @@ bool DisplayServerX11::has_feature(Feature p_feature) const {
#endif #endif
case FEATURE_CLIPBOARD_PRIMARY: case FEATURE_CLIPBOARD_PRIMARY:
case FEATURE_TEXT_TO_SPEECH: case FEATURE_TEXT_TO_SPEECH:
case FEATURE_WINDOW_EMBEDDING:
return true; return true;
case FEATURE_SCREEN_CAPTURE: case FEATURE_SCREEN_CAPTURE:
return !xwayland; return !xwayland;
@ -1461,6 +1462,36 @@ Rect2i DisplayServerX11::screen_get_usable_rect(int p_screen) const {
return rect; return rect;
} }
Rect2i DisplayServerX11::_screens_get_full_rect() const {
Rect2i full_rect;
int count = get_screen_count();
for (int i = 0; i < count; i++) {
if (i == 0) {
full_rect = _screen_get_rect(i);
continue;
}
Rect2i screen_rect = _screen_get_rect(i);
if (full_rect.position.x > screen_rect.position.x) {
full_rect.size.x += full_rect.position.x - screen_rect.position.x;
full_rect.position.x = screen_rect.position.x;
}
if (full_rect.position.y > screen_rect.position.y) {
full_rect.size.y += full_rect.position.y - screen_rect.position.y;
full_rect.position.y = screen_rect.position.y;
}
if (full_rect.position.x + full_rect.size.x < screen_rect.position.x + screen_rect.size.x) {
full_rect.size.x = screen_rect.position.x + screen_rect.size.x - full_rect.position.x;
}
if (full_rect.position.y + full_rect.size.y < screen_rect.position.y + screen_rect.size.y) {
full_rect.size.y = screen_rect.position.y + screen_rect.size.y - full_rect.position.y;
}
}
return full_rect;
}
int DisplayServerX11::screen_get_dpi(int p_screen) const { int DisplayServerX11::screen_get_dpi(int p_screen) const {
_THREAD_SAFE_METHOD_ _THREAD_SAFE_METHOD_
@ -1745,7 +1776,7 @@ Vector<DisplayServer::WindowID> 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) { 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_ _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++) { for (int i = 0; i < WINDOW_FLAG_MAX; i++) {
if (p_flags & (1 << i)) { if (p_flags & (1 << i)) {
window_set_flag(WindowFlags(i), true, id); window_set_flag(WindowFlags(i), true, id);
@ -2074,6 +2105,8 @@ void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window
return; return;
} }
ERR_FAIL_COND_MSG(wd.embed_parent, "Embedded window can't be moved to another screen.");
if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN || window_get_mode(p_window) == WINDOW_MODE_MAXIMIZED) { if (window_get_mode(p_window) == WINDOW_MODE_FULLSCREEN || window_get_mode(p_window) == WINDOW_MODE_MAXIMIZED) {
Point2i position = screen_get_position(p_screen); Point2i position = screen_get_position(p_screen);
Size2i size = screen_get_size(p_screen); Size2i size = screen_get_size(p_screen);
@ -2231,6 +2264,8 @@ void DisplayServerX11::window_set_position(const Point2i &p_position, WindowID p
ERR_FAIL_COND(!windows.has(p_window)); ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window]; WindowData &wd = windows[p_window];
ERR_FAIL_COND_MSG(wd.embed_parent, "Embedded window can't be moved.");
int x = 0; int x = 0;
int y = 0; int y = 0;
if (!window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) { if (!window_get_flag(WINDOW_FLAG_BORDERLESS, p_window)) {
@ -2263,6 +2298,8 @@ void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_windo
ERR_FAIL_COND(!windows.has(p_window)); ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window]; WindowData &wd = windows[p_window];
ERR_FAIL_COND_MSG(wd.embed_parent, "Embedded windows can't have a maximum size.");
if ((p_size != Size2i()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) { if ((p_size != Size2i()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) {
ERR_PRINT("Maximum window size can't be smaller than minimum window size!"); ERR_PRINT("Maximum window size can't be smaller than minimum window size!");
return; return;
@ -2288,6 +2325,8 @@ void DisplayServerX11::window_set_min_size(const Size2i p_size, WindowID p_windo
ERR_FAIL_COND(!windows.has(p_window)); ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window]; WindowData &wd = windows[p_window];
ERR_FAIL_COND_MSG(wd.embed_parent, "Embedded windows can't have a minimum size.");
if ((p_size != Size2i()) && (wd.max_size != Size2i()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) { if ((p_size != Size2i()) && (wd.max_size != Size2i()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) {
ERR_PRINT("Minimum window size can't be larger than maximum window size!"); ERR_PRINT("Minimum window size can't be larger than maximum window size!");
return; return;
@ -2317,6 +2356,8 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) {
WindowData &wd = windows[p_window]; WindowData &wd = windows[p_window];
ERR_FAIL_COND_MSG(wd.embed_parent, "Embedded window can't be resized.");
if (wd.size.width == size.width && wd.size.height == size.height) { if (wd.size.width == size.width && wd.size.height == size.height) {
return; return;
} }
@ -2734,8 +2775,10 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) {
if (old_mode == p_mode) { if (old_mode == p_mode) {
return; // do nothing return; // do nothing
} }
//remove all "extra" modes
ERR_FAIL_COND_MSG(p_mode != WINDOW_MODE_WINDOWED && wd.embed_parent, "Embedded window only supports Windowed mode.");
// Remove all "extra" modes.
switch (old_mode) { switch (old_mode) {
case WINDOW_MODE_WINDOWED: { case WINDOW_MODE_WINDOWED: {
//do nothing //do nothing
@ -2835,8 +2878,9 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
switch (p_flag) { switch (p_flag) {
case WINDOW_FLAG_RESIZE_DISABLED: { case WINDOW_FLAG_RESIZE_DISABLED: {
wd.resize_disabled = p_enabled; ERR_FAIL_COND_MSG(p_enabled && wd.embed_parent, "Embedded window resize can't be disabled.");
wd.resize_disabled = p_enabled;
_update_size_hints(p_window); _update_size_hints(p_window);
XFlush(x11_display); XFlush(x11_display);
@ -2852,13 +2896,16 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
} }
// Preserve window size // Preserve window size
window_set_size(window_get_size(p_window), p_window); if (!wd.embed_parent) {
window_set_size(window_get_size(p_window), p_window);
}
wd.borderless = p_enabled; wd.borderless = p_enabled;
_update_window_mouse_passthrough(p_window); _update_window_mouse_passthrough(p_window);
} break; } break;
case WINDOW_FLAG_ALWAYS_ON_TOP: { case WINDOW_FLAG_ALWAYS_ON_TOP: {
ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID, "Can't make a window transient if the 'on top' flag is active."); ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID, "Can't make a window transient if the 'on top' flag is active.");
ERR_FAIL_COND_MSG(p_enabled && wd.embed_parent, "Embedded window can't become on top.");
if (p_enabled && wd.fullscreen) { if (p_enabled && wd.fullscreen) {
_set_wm_maximized(p_window, true); _set_wm_maximized(p_window, true);
} }
@ -2900,6 +2947,7 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup."); ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup.");
ERR_FAIL_COND_MSG((xwa.map_state == IsViewable) && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened."); ERR_FAIL_COND_MSG((xwa.map_state == IsViewable) && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened.");
ERR_FAIL_COND_MSG(p_enabled && wd.embed_parent, "Embedded window can't be popup.");
wd.is_popup = p_enabled; wd.is_popup = p_enabled;
} break; } break;
default: { default: {
@ -3354,6 +3402,7 @@ Key DisplayServerX11::keyboard_get_label_from_physical(Key p_keycode) const {
} }
return (Key)(key | modifiers); return (Key)(key | modifiers);
} }
DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, Window p_window, Atom p_property) { DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, Window p_window, Atom p_property) {
Atom actual_type = None; Atom actual_type = None;
int actual_format = 0; int actual_format = 0;
@ -4003,7 +4052,9 @@ void DisplayServerX11::_window_changed(XEvent *event) {
unsigned int nchildren; unsigned int nchildren;
if (XQueryTree(x11_display, wd.x11_window, &root, &parent, &children, &nchildren) && wd.parent != parent) { if (XQueryTree(x11_display, wd.x11_window, &root, &parent, &children, &nchildren) && wd.parent != parent) {
wd.parent = parent; wd.parent = parent;
window_set_position(wd.position, window_id); if (!wd.embed_parent) {
window_set_position(wd.position, window_id);
}
} }
XFree(children); XFree(children);
@ -5027,9 +5078,9 @@ void DisplayServerX11::process_events() {
// Don't propagate the motion event unless we have focus // Don't propagate the motion event unless we have focus
// this is so that the relative motion doesn't get messed up // this is so that the relative motion doesn't get messed up
// after we regain focus. // after we regain focus.
if (focused) { // Adjusted to parse the input event if the window is not focused allowing mouse hovering on the editor
Input::get_singleton()->parse_input_event(mm); // the embedding process has focus.
} else { if (!focused) {
// Propagate the event to the focused window, // Propagate the event to the focused window,
// because it's received only on the topmost window. // because it's received only on the topmost window.
// Note: This is needed for drag & drop to work between windows, // Note: This is needed for drag & drop to work between windows,
@ -5048,13 +5099,14 @@ void DisplayServerX11::process_events() {
mm->set_position(pos_focused); mm->set_position(pos_focused);
mm->set_global_position(pos_focused); mm->set_global_position(pos_focused);
mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
Input::get_singleton()->parse_input_event(mm);
break; break;
} }
} }
} }
Input::get_singleton()->parse_input_event(mm);
} break; } break;
case KeyPress: case KeyPress:
case KeyRelease: { case KeyRelease: {
@ -5458,6 +5510,277 @@ void DisplayServerX11::window_start_drag(WindowID p_window) {
XSync(x11_display, 0); XSync(x11_display, 0);
} }
pid_t get_window_pid(Display *p_display, Window p_window) {
Atom atom = XInternAtom(p_display, "_NET_WM_PID", False);
Atom actualType;
int actualFormat;
unsigned long nItems, bytesAfter;
unsigned char *prop = nullptr;
if (XGetWindowProperty(p_display, p_window, atom, 0, sizeof(pid_t), False, AnyPropertyType,
&actualType, &actualFormat, &nItems, &bytesAfter, &prop) == Success) {
if (nItems > 0) {
pid_t pid = *(pid_t *)prop;
XFree(prop);
return pid;
}
}
return 0; // PID not found.
}
Window find_window_from_process_id_internal(Display *p_display, pid_t p_process_id, Window p_window) {
Window dummy;
Window *children;
unsigned int num_children;
if (!XQueryTree(p_display, p_window, &dummy, &dummy, &children, &num_children)) {
return 0;
}
for (unsigned int i = 0; i < num_children; i++) {
pid_t pid = get_window_pid(p_display, children[i]);
if (pid == p_process_id) {
return children[i];
}
}
// Then check children of children.
for (unsigned int i = 0; i < num_children; i++) {
Window wnd = find_window_from_process_id_internal(p_display, p_process_id, children[i]);
if (wnd != 0) {
return wnd;
}
}
if (children) {
XFree(children);
}
return 0;
}
Window find_window_from_process_id(Display *p_display, pid_t p_process_id) {
// Handle bad window errors silently because while looping
// windows can be destroyed, resulting in BadWindow errors.
int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler);
const int screencount = XScreenCount(p_display);
Window process_window = 0;
for (int screen_index = 0; screen_index < screencount; screen_index++) {
Window root = RootWindow(p_display, screen_index);
Window wnd = find_window_from_process_id_internal(p_display, p_process_id, root);
if (wnd != 0) {
process_window = wnd;
break;
}
}
// Restore default error handler.
XSetErrorHandler(oldHandler);
return process_window;
}
Point2i DisplayServerX11::_get_window_position(Window p_window) const {
int x = 0, y = 0;
Window child;
XTranslateCoordinates(x11_display, p_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);
return Point2i(x, y);
}
Rect2i DisplayServerX11::_get_window_rect(Window p_window) const {
XWindowAttributes xwa;
XGetWindowAttributes(x11_display, p_window, &xwa);
return Rect2i(xwa.x, xwa.y, xwa.width, xwa.height);
}
void DisplayServerX11::_set_window_taskbar_pager_enabled(Window p_window, bool p_enabled) {
Atom wmState = XInternAtom(x11_display, "_NET_WM_STATE", False);
Atom skipTaskbar = XInternAtom(x11_display, "_NET_WM_STATE_SKIP_TASKBAR", False);
Atom skipPager = XInternAtom(x11_display, "_NET_WM_STATE_SKIP_PAGER", False);
XClientMessageEvent xev;
memset(&xev, 0, sizeof(xev));
xev.type = ClientMessage;
xev.window = p_window;
xev.message_type = wmState;
xev.format = 32;
xev.data.l[0] = p_enabled ? _NET_WM_STATE_REMOVE : _NET_WM_STATE_ADD; // When enabled, we must remove the skip.
xev.data.l[1] = skipTaskbar;
xev.data.l[2] = skipPager;
xev.data.l[3] = 0;
xev.data.l[4] = 0;
// Send the client message to the root window.
XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent *)&xev);
}
Error DisplayServerX11::embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(!windows.has(p_window), FAILED);
const WindowData &wd = windows[p_window];
DEBUG_LOG_X11("Starting embedding %ld to window %lu \n", p_pid, wd.x11_window);
EmbeddedProcessData *ep = nullptr;
if (embedded_processes.has(p_pid)) {
ep = embedded_processes.get(p_pid);
} else {
// New process, trying to find the window.
Window process_window = find_window_from_process_id(x11_display, p_pid);
if (!process_window) {
return ERR_DOES_NOT_EXIST;
}
DEBUG_LOG_X11("Process %ld window found: %lu \n", p_pid, process_window);
ep = memnew(EmbeddedProcessData);
ep->process_window = process_window;
ep->visible = true;
XSetTransientForHint(x11_display, process_window, wd.x11_window);
_set_window_taskbar_pager_enabled(process_window, false);
embedded_processes.insert(p_pid, ep);
}
// Handle bad window errors silently because just in case the embedded window was closed.
int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&bad_window_error_handler);
if (p_visible) {
// Resize and move the window to match the desired rectangle.
// X11 does not allow moving the window entirely outside the screen boundaries.
// To ensure the window remains visible, we will resize it to fit within both the screen and the specified rectangle.
Rect2i desired_rect = p_rect;
// First resize the desired rect to fit inside all the screens without considering the
// working area.
Rect2i screens_full_rect = _screens_get_full_rect();
Vector2i screens_full_end = screens_full_rect.get_end();
if (desired_rect.position.x < screens_full_rect.position.x) {
desired_rect.size.x = MAX(desired_rect.size.x - (screens_full_rect.position.x - desired_rect.position.x), 0);
desired_rect.position.x = screens_full_rect.position.x;
}
if (desired_rect.position.x + desired_rect.size.x > screens_full_end.x) {
desired_rect.size.x = MAX(screens_full_end.x - desired_rect.position.x, 0);
}
if (desired_rect.position.y < screens_full_rect.position.y) {
desired_rect.size.y = MAX(desired_rect.size.y - (screens_full_rect.position.y - desired_rect.position.y), 0);
desired_rect.position.y = screens_full_rect.position.y;
}
if (desired_rect.position.y + desired_rect.size.y > screens_full_end.y) {
desired_rect.size.y = MAX(screens_full_end.y - desired_rect.position.y, 0);
}
// Second, for each screen, check if the desired rectangle is within a portion of the screen
// that is outside the working area. Each screen can have a different working area
// depending on top, bottom, or side panels.
int desired_area = desired_rect.get_area();
int count = get_screen_count();
for (int i = 0; i < count; i++) {
Rect2i screen_rect = _screen_get_rect(i);
if (screen_rect.intersection(desired_rect).get_area() == 0) {
continue;
}
// The desired rect is inside this screen.
Rect2i screen_usable_rect = screen_get_usable_rect(i);
int screen_usable_area = screen_usable_rect.intersection(desired_rect).get_area();
if (screen_usable_area == desired_area) {
// The desired rect is fulling inside the usable rect of the screen. No need to resize.
continue;
}
if (desired_rect.position.x >= screen_rect.position.x && desired_rect.position.x < screen_usable_rect.position.x) {
int offset = screen_usable_rect.position.x - desired_rect.position.x;
desired_rect.size.x = MAX(desired_rect.size.x - offset, 0);
desired_rect.position.x += offset;
}
if (desired_rect.position.y >= screen_rect.position.y && desired_rect.position.y < screen_usable_rect.position.y) {
int offset = screen_usable_rect.position.y - desired_rect.position.y;
desired_rect.size.y = MAX(desired_rect.size.y - offset, 0);
desired_rect.position.y += offset;
}
Vector2i desired_end = desired_rect.get_end();
Vector2i screen_end = screen_rect.get_end();
Vector2i screen_usable_end = screen_usable_rect.get_end();
if (desired_end.x > screen_usable_end.x && desired_end.x <= screen_end.x) {
desired_rect.size.x = MAX(desired_rect.size.x - (desired_end.x - screen_usable_end.x), 0);
}
if (desired_end.y > screen_usable_end.y && desired_end.y <= screen_end.y) {
desired_rect.size.y = MAX(desired_rect.size.y - (desired_end.y - screen_usable_end.y), 0);
}
}
if (desired_rect.size.x < 100 || desired_rect.size.y < 100) {
p_visible = false;
}
if (p_visible) {
Rect2i current_process_window_rect = _get_window_rect(ep->process_window);
if (current_process_window_rect != desired_rect) {
DEBUG_LOG_X11("Embedding XMoveResizeWindow process %ld, window %lu to %d, %d, %d, %d \n", p_pid, wd.x11_window, desired_rect.position.x, desired_rect.position.y, desired_rect.size.x, desired_rect.size.y);
XMoveResizeWindow(x11_display, ep->process_window, desired_rect.position.x, desired_rect.position.y, desired_rect.size.x, desired_rect.size.y);
}
}
}
if (ep->visible != p_visible) {
if (p_visible) {
XMapWindow(x11_display, ep->process_window);
} else {
XUnmapWindow(x11_display, ep->process_window);
}
ep->visible = p_visible;
}
if (p_grab_focus && p_visible) {
Window focused_window = 0;
int revert_to = 0;
XGetInputFocus(x11_display, &focused_window, &revert_to);
if (focused_window != ep->process_window) {
// Be sure that the window is visible to prevent BadMatch error when calling XSetInputFocus on a not viewable window.
XWindowAttributes attr;
if (XGetWindowAttributes(x11_display, ep->process_window, &attr) && attr.map_state == IsViewable) {
XSetInputFocus(x11_display, ep->process_window, RevertToParent, CurrentTime);
}
}
}
// Restore default error handler.
XSetErrorHandler(oldHandler);
return OK;
}
Error DisplayServerX11::remove_embedded_process(OS::ProcessID p_pid) {
_THREAD_SAFE_METHOD_
if (!embedded_processes.has(p_pid)) {
return ERR_DOES_NOT_EXIST;
}
EmbeddedProcessData *ep = embedded_processes.get(p_pid);
embedded_processes.erase(p_pid);
memdelete(ep);
return OK;
}
OS::ProcessID DisplayServerX11::get_focused_process_id() {
Window focused_window = 0;
int revert_to = 0;
XGetInputFocus(x11_display, &focused_window, &revert_to);
if (focused_window == None) {
return 0;
}
return get_window_pid(x11_display, focused_window);
}
Vector<String> DisplayServerX11::get_rendering_drivers_func() { Vector<String> DisplayServerX11::get_rendering_drivers_func() {
Vector<String> drivers; Vector<String> drivers;
@ -5472,12 +5795,12 @@ Vector<String> DisplayServerX11::get_rendering_drivers_func() {
return drivers; 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 *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, 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; 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 //Create window
XVisualInfo visualInfo; XVisualInfo visualInfo;
@ -5576,16 +5899,19 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
} }
Rect2i win_rect = p_rect; Rect2i win_rect = p_rect;
if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { if (!p_parent_window) {
Rect2i screen_rect = Rect2i(screen_get_position(rq_screen), screen_get_size(rq_screen)); // 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; win_rect = screen_rect;
} else { } else {
Rect2i srect = screen_get_usable_rect(rq_screen); Rect2i srect = screen_get_usable_rect(rq_screen);
Point2i wpos = p_rect.position; Point2i wpos = p_rect.position;
wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3); 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 // Position and size hints are set from these values before they are updated to the actual
@ -5597,6 +5923,14 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo.screen), win_rect.position.x, win_rect.position.y, win_rect.size.width > 0 ? win_rect.size.width : 1, win_rect.size.height > 0 ? win_rect.size.height : 1, 0, visualInfo.depth, InputOutput, visualInfo.visual, valuemask, &windowAttributes); wd.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); wd.parent = RootWindow(x11_display, visualInfo.screen);
DEBUG_LOG_X11("CreateWindow window=%lu, parent: %lu \n", wd.x11_window, wd.parent);
if (p_parent_window) {
wd.embed_parent = p_parent_window;
XSetTransientForHint(x11_display, wd.x11_window, p_parent_window);
}
XSetWindowAttributes window_attributes_ime = {}; XSetWindowAttributes window_attributes_ime = {};
window_attributes_ime.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask; window_attributes_ime.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask;
@ -5748,7 +6082,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
} }
} }
if (wd.is_popup || wd.no_focus) { if (wd.is_popup || wd.no_focus || wd.embed_parent) {
// Set Utility type to disable fade animations. // Set Utility type to disable fade animations.
Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False); Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False);
Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False);
@ -5764,6 +6098,11 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
} }
} }
if (p_parent_window) {
// Disable the window in the taskbar and alt-tab.
_set_window_taskbar_pager_enabled(wd.x11_window, false);
}
_update_size_hints(id); _update_size_hints(id);
#if defined(RD_ENABLED) #if defined(RD_ENABLED)
@ -5885,7 +6224,7 @@ static ::XIMStyle _get_best_xim_style(const ::XIMStyle &p_style_a, const ::XIMSt
return p_style_a; 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(); KeyMappingX11::initialize();
xwayland = OS::get_singleton()->get_environment("XDG_SESSION_TYPE").to_lower() == "wayland"; xwayland = OS::get_singleton()->get_environment("XDG_SESSION_TYPE").to_lower() == "wayland";
@ -6342,7 +6681,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
window_position = scr_rect.position + (scr_rect.size - p_resolution) / 2; 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) { if (main_window == INVALID_WINDOW_ID) {
r_error = ERR_CANT_CREATE; r_error = ERR_CANT_CREATE;
return; return;

View file

@ -208,6 +208,8 @@ class DisplayServerX11 : public DisplayServer {
bool layered_window = false; bool layered_window = false;
bool mpass = false; bool mpass = false;
Window embed_parent = 0;
Rect2i parent_safe_rect; Rect2i parent_safe_rect;
unsigned int focus_order = 0; unsigned int focus_order = 0;
@ -234,7 +236,7 @@ class DisplayServerX11 : public DisplayServer {
WindowID last_focused_window = INVALID_WINDOW_ID; WindowID last_focused_window = INVALID_WINDOW_ID;
WindowID window_id_counter = MAIN_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;
String internal_clipboard_primary; String internal_clipboard_primary;
@ -375,6 +377,18 @@ class DisplayServerX11 : public DisplayServer {
static Bool _predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg); static Bool _predicate_clipboard_incr(Display *display, XEvent *event, XPointer arg);
static Bool _predicate_clipboard_save_targets(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<OS::ProcessID, EmbeddedProcessData *> embedded_processes;
Point2i _get_window_position(Window p_window) const;
Rect2i _get_window_rect(Window p_window) const;
void _set_external_window_settings(Window p_window, Window p_parent_transient, WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect);
void _set_window_taskbar_pager_enabled(Window p_window, bool p_enabled);
Rect2i _screens_get_full_rect() const;
protected: protected:
void _window_changed(XEvent *event); void _window_changed(XEvent *event);
@ -514,6 +528,10 @@ public:
virtual void window_start_drag(WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_start_drag(WindowID p_window = MAIN_WINDOW_ID) override;
virtual Error embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) override;
virtual Error remove_embedded_process(OS::ProcessID p_pid) override;
virtual OS::ProcessID get_focused_process_id() override;
virtual void cursor_set_shape(CursorShape p_shape) override; virtual void cursor_set_shape(CursorShape p_shape) override;
virtual CursorShape cursor_get_shape() const override; virtual CursorShape cursor_get_shape() const override;
virtual void cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) override; virtual void cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) override;
@ -538,12 +556,12 @@ public:
virtual void set_native_icon(const String &p_filename) override; virtual void set_native_icon(const String &p_filename) override;
virtual void set_icon(const Ref<Image> &p_icon) override; virtual void set_icon(const Ref<Image> &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<String> get_rendering_drivers_func(); static Vector<String> get_rendering_drivers_func();
static void register_x11_driver(); 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(); ~DisplayServerX11();
}; };

View file

@ -450,12 +450,12 @@ public:
virtual bool is_window_transparency_available() const override; 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<String> get_rendering_drivers_func(); static Vector<String> get_rendering_drivers_func();
static void register_macos_driver(); 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(); ~DisplayServerMacOS();
}; };

View file

@ -3399,8 +3399,8 @@ bool DisplayServerMacOS::is_window_transparency_available() const {
return OS::get_singleton()->is_layered_allowed(); 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 *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, 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 (r_error != OK) {
if (p_rendering_driver == "vulkan") { if (p_rendering_driver == "vulkan") {
String executable_command; String executable_command;
@ -3594,7 +3594,7 @@ bool DisplayServerMacOS::mouse_process_popups(bool p_close) {
return closed; 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(); KeyMappingMacOS::initialize();
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);

View file

@ -1025,11 +1025,11 @@ void DisplayServerWeb::_dispatch_input_event(const Ref<InputEvent> &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) { 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, 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. r_error = OK; // Always succeeds for now.
tts = GLOBAL_GET("audio/general/text_to_speech"); tts = GLOBAL_GET("audio/general/text_to_speech");

View file

@ -148,7 +148,7 @@ private:
void process_keys(); void process_keys();
static Vector<String> get_rendering_drivers_func(); static Vector<String> 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<InputEvent> &p_event); static void _dispatch_input_event(const Ref<InputEvent> &p_event);
@ -280,7 +280,7 @@ public:
virtual void swap_buffers() override; virtual void swap_buffers() override;
static void register_web_driver(); 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(); ~DisplayServerWeb();
}; };

View file

@ -136,6 +136,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const {
case FEATURE_TEXT_TO_SPEECH: case FEATURE_TEXT_TO_SPEECH:
case FEATURE_SCREEN_CAPTURE: case FEATURE_SCREEN_CAPTURE:
case FEATURE_STATUS_INDICATOR: case FEATURE_STATUS_INDICATOR:
case FEATURE_WINDOW_EMBEDDING:
return true; return true;
default: default:
return false; return false;
@ -1539,7 +1540,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) { 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_ _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."); ERR_FAIL_COND_V_MSG(window_id == INVALID_WINDOW_ID, INVALID_WINDOW_ID, "Failed to create sub window.");
WindowData &wd = windows[window_id]; WindowData &wd = windows[window_id];
@ -1889,7 +1890,6 @@ void DisplayServerWindows::window_set_mouse_passthrough(const Vector<Vector2> &p
void DisplayServerWindows::_update_window_mouse_passthrough(WindowID p_window) { void DisplayServerWindows::_update_window_mouse_passthrough(WindowID p_window) {
ERR_FAIL_COND(!windows.has(p_window)); ERR_FAIL_COND(!windows.has(p_window));
if (windows[p_window].mpass || windows[p_window].mpath.size() == 0) { if (windows[p_window].mpass || windows[p_window].mpath.size() == 0) {
SetWindowRgn(windows[p_window].hWnd, nullptr, FALSE); SetWindowRgn(windows[p_window].hWnd, nullptr, FALSE);
} else { } else {
@ -1930,6 +1930,7 @@ void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_wi
return; return;
} }
const WindowData &wd = windows[p_window]; 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) { if (wd.fullscreen) {
Point2 pos = screen_get_position(p_screen) + _get_screens_origin(); Point2 pos = screen_get_position(p_screen) + _get_screens_origin();
Size2 size = screen_get_size(p_screen); Size2 size = screen_get_size(p_screen);
@ -2010,6 +2011,8 @@ void DisplayServerWindows::window_set_position(const Point2i &p_position, Window
ERR_FAIL_COND(!windows.has(p_window)); ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window]; WindowData &wd = windows[p_window];
ERR_FAIL_COND_MSG(wd.parent_hwnd, "Embedded window can't be moved.");
if (wd.fullscreen || wd.maximized) { if (wd.fullscreen || wd.maximized) {
return; return;
} }
@ -2094,6 +2097,8 @@ void DisplayServerWindows::window_set_max_size(const Size2i p_size, WindowID p_w
ERR_FAIL_COND(!windows.has(p_window)); ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window]; WindowData &wd = windows[p_window];
ERR_FAIL_COND_MSG(wd.parent_hwnd, "Embedded windows can't have a maximum size.");
if ((p_size != Size2()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) { if ((p_size != Size2()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) {
ERR_PRINT("Maximum window size can't be smaller than minimum window size!"); ERR_PRINT("Maximum window size can't be smaller than minimum window size!");
return; return;
@ -2115,6 +2120,8 @@ void DisplayServerWindows::window_set_min_size(const Size2i p_size, WindowID p_w
ERR_FAIL_COND(!windows.has(p_window)); ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window]; WindowData &wd = windows[p_window];
ERR_FAIL_COND_MSG(wd.parent_hwnd, "Embedded windows can't have a minimum size.");
if ((p_size != Size2()) && (wd.max_size != Size2()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) { if ((p_size != Size2()) && (wd.max_size != Size2()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) {
ERR_PRINT("Minimum window size can't be larger than maximum window size!"); ERR_PRINT("Minimum window size can't be larger than maximum window size!");
return; return;
@ -2136,6 +2143,8 @@ void DisplayServerWindows::window_set_size(const Size2i p_size, WindowID p_windo
ERR_FAIL_COND(!windows.has(p_window)); ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window]; WindowData &wd = windows[p_window];
ERR_FAIL_COND_MSG(wd.parent_hwnd, "Embedded window can't be resized.");
if (wd.fullscreen || wd.maximized) { if (wd.fullscreen || wd.maximized) {
return; return;
} }
@ -2187,7 +2196,7 @@ Size2i DisplayServerWindows::window_get_size_with_decorations(WindowID p_window)
return Size2(); return Size2();
} }
void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_initialized, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_minimized, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) { void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_initialized, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_minimized, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, bool p_embed_child, DWORD &r_style, DWORD &r_style_ex) {
// Windows docs for window styles: // 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/window-styles
// https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles // https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
@ -2195,13 +2204,19 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_initiali
r_style = 0; r_style = 0;
r_style_ex = WS_EX_WINDOWEDGE; r_style_ex = WS_EX_WINDOWEDGE;
if (p_main_window) { if (p_main_window) {
r_style_ex |= WS_EX_APPWINDOW; // When embedded, we don't want the window to have WS_EX_APPWINDOW because it will
// show the embedded process in the taskbar and Alt-Tab.
if (!p_embed_child) {
r_style_ex |= WS_EX_APPWINDOW;
}
if (p_initialized) { if (p_initialized) {
r_style |= WS_VISIBLE; r_style |= WS_VISIBLE;
} }
} }
if (p_fullscreen || p_borderless) { if (p_embed_child) {
r_style |= WS_POPUP;
} else if (p_fullscreen || p_borderless) {
r_style |= WS_POPUP; // p_borderless was WS_EX_TOOLWINDOW in the past. r_style |= WS_POPUP; // p_borderless was WS_EX_TOOLWINDOW in the past.
if (p_minimized) { if (p_minimized) {
r_style |= WS_MINIMIZE; r_style |= WS_MINIMIZE;
@ -2236,7 +2251,7 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_initiali
} }
} }
if (p_no_activate_focus) { if (p_no_activate_focus && !p_embed_child) {
r_style_ex |= WS_EX_TOPMOST | WS_EX_NOACTIVATE; r_style_ex |= WS_EX_TOPMOST | WS_EX_NOACTIVATE;
} }
@ -2261,7 +2276,7 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain
DWORD style = 0; DWORD style = 0;
DWORD style_ex = 0; DWORD style_ex = 0;
_get_window_style(p_window == MAIN_WINDOW_ID, wd.initialized, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.minimized, wd.maximized, wd.maximized_fs, wd.no_focus || wd.is_popup, style, style_ex); _get_window_style(p_window == MAIN_WINDOW_ID, wd.initialized, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.minimized, wd.maximized, wd.maximized_fs, wd.no_focus || wd.is_popup, wd.parent_hwnd, style, style_ex);
SetWindowLongPtr(wd.hWnd, GWL_STYLE, style); SetWindowLongPtr(wd.hWnd, GWL_STYLE, style);
SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex); SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex);
@ -2275,6 +2290,7 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain
if (p_repaint) { if (p_repaint) {
RECT rect; RECT rect;
GetWindowRect(wd.hWnd, &rect); GetWindowRect(wd.hWnd, &rect);
MoveWindow(wd.hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); MoveWindow(wd.hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE);
} }
} }
@ -2285,6 +2301,8 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window)
ERR_FAIL_COND(!windows.has(p_window)); ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window]; WindowData &wd = windows[p_window];
ERR_FAIL_COND_MSG(p_mode != WINDOW_MODE_WINDOWED && wd.parent_hwnd, "Embedded window only supports Windowed mode.");
bool was_fullscreen = wd.fullscreen; bool was_fullscreen = wd.fullscreen;
wd.was_fullscreen_pre_min = false; wd.was_fullscreen_pre_min = false;
@ -2418,6 +2436,7 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W
WindowData &wd = windows[p_window]; WindowData &wd = windows[p_window];
switch (p_flag) { switch (p_flag) {
case WINDOW_FLAG_RESIZE_DISABLED: { case WINDOW_FLAG_RESIZE_DISABLED: {
ERR_FAIL_COND_MSG(p_enabled && wd.parent_hwnd, "Embedded window resize can't be disabled.");
wd.resizable = !p_enabled; wd.resizable = !p_enabled;
_update_window_style(p_window); _update_window_style(p_window);
} break; } break;
@ -2428,7 +2447,8 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W
ShowWindow(wd.hWnd, (wd.no_focus || wd.is_popup) ? SW_SHOWNOACTIVATE : SW_SHOW); // Show the window. ShowWindow(wd.hWnd, (wd.no_focus || wd.is_popup) ? SW_SHOWNOACTIVATE : SW_SHOW); // Show the window.
} break; } break;
case WINDOW_FLAG_ALWAYS_ON_TOP: { 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; wd.always_on_top = p_enabled;
_update_window_style(p_window); _update_window_style(p_window);
} break; } break;
@ -2488,6 +2508,7 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W
case WINDOW_FLAG_POPUP: { case WINDOW_FLAG_POPUP: {
ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be 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(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; wd.is_popup = p_enabled;
} break; } break;
default: default:
@ -2842,6 +2863,183 @@ void DisplayServerWindows::enable_for_stealing_focus(OS::ProcessID pid) {
AllowSetForegroundWindow(pid); AllowSetForegroundWindow(pid);
} }
struct WindowEnumData {
DWORD process_id;
HWND parent_hWnd;
HWND hWnd;
};
static BOOL CALLBACK _enum_proc_find_window_from_process_id_callback(HWND hWnd, LPARAM lParam) {
WindowEnumData &ed = *(WindowEnumData *)lParam;
DWORD process_id = 0x0;
GetWindowThreadProcessId(hWnd, &process_id);
if (ed.process_id == process_id) {
if (GetParent(hWnd) != ed.parent_hWnd) {
const DWORD style = GetWindowLongPtr(hWnd, GWL_STYLE);
if ((style & WS_VISIBLE) != WS_VISIBLE) {
return TRUE;
}
}
// Found it.
ed.hWnd = hWnd;
SetLastError(ERROR_SUCCESS);
return FALSE;
}
// Continue enumeration.
return TRUE;
}
HWND DisplayServerWindows::_find_window_from_process_id(OS::ProcessID p_pid, HWND p_current_hwnd) {
DWORD pid = p_pid;
WindowEnumData ed = { pid, p_current_hwnd, NULL };
// First, check our own child, maybe it's already embedded.
if (!EnumChildWindows(p_current_hwnd, _enum_proc_find_window_from_process_id_callback, (LPARAM)&ed) && (GetLastError() == ERROR_SUCCESS)) {
if (ed.hWnd) {
return ed.hWnd;
}
}
// Then check all the opened windows on the computer.
if (!EnumWindows(_enum_proc_find_window_from_process_id_callback, (LPARAM)&ed) && (GetLastError() == ERROR_SUCCESS)) {
return ed.hWnd;
}
return NULL;
}
Error DisplayServerWindows::embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V(!windows.has(p_window), FAILED);
const WindowData &wd = windows[p_window];
EmbeddedProcessData *ep = nullptr;
if (embedded_processes.has(p_pid)) {
ep = embedded_processes.get(p_pid);
} else {
// New process, trying to find the window.
HWND handle_to_embed = _find_window_from_process_id(p_pid, wd.hWnd);
if (!handle_to_embed) {
return ERR_DOES_NOT_EXIST;
}
const DWORD style = GetWindowLongPtr(handle_to_embed, GWL_STYLE);
ep = memnew(EmbeddedProcessData);
ep->window_handle = handle_to_embed;
ep->parent_window_handle = wd.hWnd;
ep->is_visible = (style & WS_VISIBLE) == WS_VISIBLE;
embedded_processes.insert(p_pid, ep);
HWND old_parent = GetParent(ep->window_handle);
if (old_parent != wd.hWnd) {
// It's important that the window does not have the WS_CHILD flag
// to prevent the current process from interfering with the embedded process.
// I observed lags and issues with mouse capture when WS_CHILD is set.
// Additionally, WS_POPUP must be set to ensure that the coordinates of the embedded
// window remain screen coordinates and not local coordinates of the parent window.
if ((style & WS_CHILD) == WS_CHILD || (style & WS_POPUP) != WS_POPUP) {
const DWORD new_style = (style & ~WS_CHILD) | WS_POPUP;
SetWindowLong(ep->window_handle, GWL_STYLE, new_style);
}
// Set the parent to current window.
SetParent(ep->window_handle, wd.hWnd);
}
}
if (p_rect.size.x < 100 || p_rect.size.y < 100) {
p_visible = false;
}
// In Godot, the window position is offset by the screen's origin coordinates.
// We need to adjust for this when a screen is positioned in the negative space
// (e.g., a screen to the left of the main screen).
const Rect2i adjusted_rect = Rect2i(p_rect.position + _get_screens_origin(), p_rect.size);
SetWindowPos(ep->window_handle, HWND_BOTTOM, adjusted_rect.position.x, adjusted_rect.position.y, adjusted_rect.size.x, adjusted_rect.size.y, SWP_NOZORDER | SWP_NOACTIVATE);
if (ep->is_visible != p_visible) {
if (p_visible) {
ShowWindow(ep->window_handle, SW_SHOWNA);
} else {
ShowWindow(ep->window_handle, SW_HIDE);
}
ep->is_visible = p_visible;
}
if (p_grab_focus) {
SetFocus(ep->window_handle);
}
return OK;
}
Error DisplayServerWindows::remove_embedded_process(OS::ProcessID p_pid) {
_THREAD_SAFE_METHOD_
if (!embedded_processes.has(p_pid)) {
return ERR_DOES_NOT_EXIST;
}
EmbeddedProcessData *ep = embedded_processes.get(p_pid);
// This is a workaround to ensure the parent window correctly regains focus after the
// embedded window is closed. When the embedded window is closed while it has focus,
// the parent window (the editor) does not become active. It appears focused but is not truly activated.
// Opening a new window and closing it forces Windows to set the focus and activation correctly.
DWORD style = WS_POPUP | WS_VISIBLE;
DWORD style_ex = WS_EX_TOPMOST;
WNDCLASSW wcTemp = {};
wcTemp.lpfnWndProc = DefWindowProcW;
wcTemp.hInstance = GetModuleHandle(nullptr);
wcTemp.lpszClassName = L"Engine temp window";
RegisterClassW(&wcTemp);
HWND hWnd = CreateWindowExW(
style_ex,
L"Engine temp window", L"",
style,
0,
0,
1,
1,
ep->parent_window_handle,
nullptr,
GetModuleHandle(nullptr),
nullptr);
SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE);
DestroyWindow(hWnd);
UnregisterClassW(L"Engine temp window", GetModuleHandle(nullptr));
SetForegroundWindow(ep->parent_window_handle);
embedded_processes.erase(p_pid);
memdelete(ep);
return OK;
}
OS::ProcessID DisplayServerWindows::get_focused_process_id() {
HWND hwnd = GetForegroundWindow();
if (!hwnd) {
return 0;
}
// Get the process ID of the window.
DWORD processID;
GetWindowThreadProcessId(hwnd, &processID);
return processID;
}
static HRESULT CALLBACK win32_task_dialog_callback(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData) { static HRESULT CALLBACK win32_task_dialog_callback(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData) {
if (msg == TDN_CREATED) { if (msg == TDN_CREATED) {
// To match the input text dialog. // To match the input text dialog.
@ -4216,6 +4414,12 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
if (windows[window_id].no_focus || windows[window_id].is_popup) { if (windows[window_id].no_focus || windows[window_id].is_popup) {
return MA_NOACTIVATE; // Do not activate, but process mouse messages. return MA_NOACTIVATE; // Do not activate, but process mouse messages.
} }
// When embedded, the window is a child of the parent and is not activated
// by default because it lacks native controls.
if (windows[window_id].parent_hwnd) {
SetFocus(windows[window_id].hWnd);
return MA_ACTIVATE;
}
} break; } break;
case WM_ACTIVATEAPP: { case WM_ACTIVATEAPP: {
bool new_app_focused = (bool)wParam; bool new_app_focused = (bool)wParam;
@ -4232,8 +4436,15 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
// Therefore, it's safer to defer the delivery of the event. // Therefore, it's safer to defer the delivery of the event.
// It's important to set an nIDEvent different from the SetTimer for move_timer_id because // It's important to set an nIDEvent different from the SetTimer for move_timer_id because
// if the same nIDEvent is passed, the timer is replaced and the same timer_id is returned. // if the same nIDEvent is passed, the timer is replaced and the same timer_id is returned.
windows[window_id].activate_timer_id = SetTimer(windows[window_id].hWnd, DisplayServerWindows::TIMER_ID_WINDOW_ACTIVATION, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); // The problem with the timer is that the window cannot be resized or the buttons cannot be used correctly
// if the window is not activated first. This happens because the code in the activation process runs
// after the mouse click is handled. To address this, the timer is now used only when the window is created.
windows[window_id].activate_state = GET_WM_ACTIVATE_STATE(wParam, lParam); windows[window_id].activate_state = GET_WM_ACTIVATE_STATE(wParam, lParam);
if (windows[window_id].first_activation_done) {
_process_activate_event(window_id);
} else {
windows[window_id].activate_timer_id = SetTimer(windows[window_id].hWnd, DisplayServerWindows::TIMER_ID_WINDOW_ACTIVATION, USER_TIMER_MINIMUM, (TIMERPROC) nullptr);
}
return 0; return 0;
} break; } break;
case WM_GETMINMAXINFO: { case WM_GETMINMAXINFO: {
@ -5303,6 +5514,16 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
MoveWindow(window.hWnd, pos.x, pos.y, size.width, size.height, TRUE); MoveWindow(window.hWnd, pos.x, pos.y, size.width, size.height, TRUE);
} }
} 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 // Return here to prevent WM_MOVE and WM_SIZE from being sent
@ -5330,6 +5551,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
_process_activate_event(window_id); _process_activate_event(window_id);
KillTimer(windows[window_id].hWnd, windows[window_id].activate_timer_id); KillTimer(windows[window_id].hWnd, windows[window_id].activate_timer_id);
windows[window_id].activate_timer_id = 0; windows[window_id].activate_timer_id = 0;
windows[window_id].first_activation_done = true;
} }
} break; } break;
case WM_SYSKEYUP: case WM_SYSKEYUP:
@ -5718,11 +5940,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 dwExStyle;
DWORD dwStyle; DWORD dwStyle;
_get_window_style(window_id_counter == MAIN_WINDOW_ID, false, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MINIMIZED, p_mode == WINDOW_MODE_MAXIMIZED, false, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), dwStyle, dwExStyle); _get_window_style(window_id_counter == MAIN_WINDOW_ID, false, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MINIMIZED, p_mode == WINDOW_MODE_MAXIMIZED, false, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), p_parent_hwnd, dwStyle, dwExStyle);
RECT WindowRect; RECT WindowRect;
@ -5736,41 +5958,46 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
rq_screen = get_primary_screen(); // Requested window rect is outside any screen bounds. 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) { Point2i offset = _get_screens_origin();
Rect2i screen_rect = Rect2i(screen_get_position(rq_screen), screen_get_size(rq_screen));
WindowRect.left = screen_rect.position.x; if (!p_parent_hwnd) {
WindowRect.right = screen_rect.position.x + screen_rect.size.x; if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
WindowRect.top = screen_rect.position.y; Rect2i screen_rect = Rect2i(screen_get_position(rq_screen), screen_get_size(rq_screen));
WindowRect.bottom = screen_rect.position.y + screen_rect.size.y;
} else { WindowRect.left = screen_rect.position.x;
Rect2i srect = screen_get_usable_rect(rq_screen); WindowRect.right = screen_rect.position.x + screen_rect.size.x;
Point2i wpos = p_rect.position; WindowRect.top = screen_rect.position.y;
if (srect != Rect2i()) { WindowRect.bottom = screen_rect.position.y + screen_rect.size.y;
wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3); } 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.left += offset.x;
WindowRect.right = wpos.x + p_rect.size.x; WindowRect.right += offset.x;
WindowRect.top = wpos.y; WindowRect.top += offset.y;
WindowRect.bottom = wpos.y + p_rect.size.y; WindowRect.bottom += offset.y;
}
Point2i offset = _get_screens_origin(); if (p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
WindowRect.left += offset.x; AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);
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);
} }
WindowID id = window_id_counter; WindowID id = window_id_counter;
{ {
WindowData *wd_transient_parent = nullptr; WindowData *wd_transient_parent = nullptr;
HWND owner_hwnd = 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)) { if (!windows.has(p_transient_parent)) {
ERR_PRINT("Condition \"!windows.has(p_transient_parent)\" is true."); ERR_PRINT("Condition \"!windows.has(p_transient_parent)\" is true.");
p_transient_parent = INVALID_WINDOW_ID; p_transient_parent = INVALID_WINDOW_ID;
@ -5804,6 +6031,9 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
windows.erase(id); windows.erase(id);
ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Failed to create Windows OS window."); 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) { if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
wd.fullscreen = true; wd.fullscreen = true;
if (p_mode == WINDOW_MODE_FULLSCREEN) { if (p_mode == WINDOW_MODE_FULLSCREEN) {
@ -6164,7 +6394,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(); KeyMappingWindows::initialize();
tested_drivers.clear(); tested_drivers.clear();
@ -6564,7 +6794,13 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
window_position = scr_rect.position + (scr_rect.size - p_resolution) / 2; window_position = scr_rect.position + (scr_rect.size - p_resolution) / 2;
} }
WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution), false, INVALID_WINDOW_ID); HWND parent_hwnd = NULL;
if (p_parent_window) {
// Parented window.
parent_hwnd = (HWND)p_parent_window;
}
WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution), false, INVALID_WINDOW_ID, parent_hwnd);
if (main_window == INVALID_WINDOW_ID) { if (main_window == INVALID_WINDOW_ID) {
r_error = ERR_UNAVAILABLE; r_error = ERR_UNAVAILABLE;
ERR_FAIL_MSG("Failed to create main window."); ERR_FAIL_MSG("Failed to create main window.");
@ -6642,8 +6878,8 @@ Vector<String> DisplayServerWindows::get_rendering_drivers_func() {
return drivers; 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 *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, 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 (r_error != OK) {
if (tested_drivers == 0) { if (tested_drivers == 0) {
OS::get_singleton()->alert("Failed to register the window class.", "Unable to initialize DisplayServer"); OS::get_singleton()->alert("Failed to register the window class.", "Unable to initialize DisplayServer");

View file

@ -483,6 +483,8 @@ class DisplayServerWindows : public DisplayServer {
int activate_state = 0; int activate_state = 0;
bool was_maximized_pre_fs = false; bool was_maximized_pre_fs = false;
bool was_fullscreen_pre_min = false; bool was_fullscreen_pre_min = false;
bool first_activation_done = false;
bool was_maximized = false;
bool always_on_top = false; bool always_on_top = false;
bool no_focus = false; bool no_focus = false;
bool exclusive = false; bool exclusive = false;
@ -546,6 +548,8 @@ class DisplayServerWindows : public DisplayServer {
Rect2i parent_safe_rect; Rect2i parent_safe_rect;
bool initialized = false; bool initialized = false;
HWND parent_hwnd = 0;
}; };
JoypadWindows *joypad = nullptr; JoypadWindows *joypad = nullptr;
@ -554,7 +558,7 @@ class DisplayServerWindows : public DisplayServer {
uint64_t time_since_popup = 0; uint64_t time_since_popup = 0;
Ref<Image> icon; Ref<Image> 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; WindowID window_id_counter = MAIN_WINDOW_ID;
RBMap<WindowID, WindowData> windows; RBMap<WindowID, WindowData> windows;
@ -613,7 +617,7 @@ class DisplayServerWindows : public DisplayServer {
HashMap<int64_t, Vector2> pointer_last_pos; HashMap<int64_t, Vector2> pointer_last_pos;
void _send_window_event(const WindowData &wd, WindowEvent p_event); void _send_window_event(const WindowData &wd, WindowEvent p_event);
void _get_window_style(bool p_main_window, bool p_initialized, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_minimized, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex); void _get_window_style(bool p_main_window, bool p_initialized, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_minimized, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, bool p_embed_child, DWORD &r_style, DWORD &r_style_ex);
MouseMode mouse_mode; MouseMode mouse_mode;
int restore_mouse_trails = 0; int restore_mouse_trails = 0;
@ -669,6 +673,15 @@ class DisplayServerWindows : public DisplayServer {
String _get_keyboard_layout_display_name(const String &p_klid) const; String _get_keyboard_layout_display_name(const String &p_klid) const;
String _get_klid(HKL p_hkl) const; String _get_klid(HKL p_hkl) const;
struct EmbeddedProcessData {
HWND window_handle = 0;
HWND parent_window_handle = 0;
bool is_visible = false;
};
HashMap<OS::ProcessID, EmbeddedProcessData *> embedded_processes;
HWND _find_window_from_process_id(OS::ProcessID p_pid, HWND p_current_hwnd);
public: public:
LRESULT WndProcFileDialog(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT WndProcFileDialog(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
@ -814,6 +827,9 @@ public:
virtual bool get_swap_cancel_ok() override; virtual bool get_swap_cancel_ok() override;
virtual void enable_for_stealing_focus(OS::ProcessID pid) override; virtual void enable_for_stealing_focus(OS::ProcessID pid) override;
virtual Error embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) override;
virtual Error remove_embedded_process(OS::ProcessID p_pid) override;
virtual OS::ProcessID get_focused_process_id() override;
virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) override; virtual Error dialog_show(String p_title, String p_description, Vector<String> 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; virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override;
@ -853,11 +869,11 @@ public:
virtual bool is_window_transparency_available() const override; 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<String> get_rendering_drivers_func(); static Vector<String> get_rendering_drivers_func();
static void register_windows_driver(); 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(); ~DisplayServerWindows();
}; };

View file

@ -432,6 +432,7 @@ public:
NOTIFICATION_WM_DPI_CHANGE = 1009, NOTIFICATION_WM_DPI_CHANGE = 1009,
NOTIFICATION_VP_MOUSE_ENTER = 1010, NOTIFICATION_VP_MOUSE_ENTER = 1010,
NOTIFICATION_VP_MOUSE_EXIT = 1011, NOTIFICATION_VP_MOUSE_EXIT = 1011,
NOTIFICATION_WM_POSITION_CHANGED = 1012,
NOTIFICATION_OS_MEMORY_WARNING = MainLoop::NOTIFICATION_OS_MEMORY_WARNING, NOTIFICATION_OS_MEMORY_WARNING = MainLoop::NOTIFICATION_OS_MEMORY_WARNING,
NOTIFICATION_TRANSLATION_CHANGED = MainLoop::NOTIFICATION_TRANSLATION_CHANGED, NOTIFICATION_TRANSLATION_CHANGED = MainLoop::NOTIFICATION_TRANSLATION_CHANGED,

View file

@ -728,7 +728,11 @@ void Window::_rect_changed_callback(const Rect2i &p_callback) {
if (size == p_callback.size && position == p_callback.position) { if (size == p_callback.size && position == p_callback.position) {
return; return;
} }
position = p_callback.position;
if (position != p_callback.position) {
position = p_callback.position;
_propagate_window_notification(this, NOTIFICATION_WM_POSITION_CHANGED);
}
if (size != p_callback.size) { if (size != p_callback.size) {
size = p_callback.size; size = p_callback.size;
@ -1096,14 +1100,17 @@ void Window::_update_window_size() {
embedder->_sub_window_update(this); embedder->_sub_window_update(this);
} else if (window_id != DisplayServer::INVALID_WINDOW_ID) { } else if (window_id != DisplayServer::INVALID_WINDOW_ID) {
if (reset_min_first && wrap_controls) { // When main window embedded in the editor, we can't resize the main window.
// Avoid an error if setting max_size to a value between min_size and the previous size_limit. if (window_id != DisplayServer::MAIN_WINDOW_ID || !Engine::get_singleton()->is_embedded_in_editor()) {
DisplayServer::get_singleton()->window_set_min_size(Size2i(), window_id); if (reset_min_first && wrap_controls) {
} // Avoid an error if setting max_size to a value between min_size and the previous size_limit.
DisplayServer::get_singleton()->window_set_min_size(Size2i(), window_id);
}
DisplayServer::get_singleton()->window_set_max_size(max_size_used, window_id); DisplayServer::get_singleton()->window_set_max_size(max_size_used, window_id);
DisplayServer::get_singleton()->window_set_min_size(size_limit, window_id); DisplayServer::get_singleton()->window_set_min_size(size_limit, window_id);
DisplayServer::get_singleton()->window_set_size(size, window_id); DisplayServer::get_singleton()->window_set_size(size, window_id);
}
} }
//update the viewport //update the viewport

View file

@ -657,6 +657,21 @@ bool DisplayServer::get_swap_cancel_ok() {
void DisplayServer::enable_for_stealing_focus(OS::ProcessID pid) { void DisplayServer::enable_for_stealing_focus(OS::ProcessID pid) {
} }
Error DisplayServer::embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) {
WARN_PRINT("Embedded process not supported by this display server.");
return ERR_UNAVAILABLE;
}
Error DisplayServer::remove_embedded_process(OS::ProcessID p_pid) {
WARN_PRINT("Embedded process not supported by this display server.");
return ERR_UNAVAILABLE;
}
OS::ProcessID DisplayServer::get_focused_process_id() {
WARN_PRINT("Embedded process not supported by this display server.");
return 0;
}
Error DisplayServer::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) { Error DisplayServer::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) {
WARN_PRINT("Native dialogs not supported by this display server."); WARN_PRINT("Native dialogs not supported by this display server.");
return ERR_UNAVAILABLE; return ERR_UNAVAILABLE;
@ -1067,6 +1082,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE_EXTRA); BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE_EXTRA);
BIND_ENUM_CONSTANT(FEATURE_WINDOW_DRAG); BIND_ENUM_CONSTANT(FEATURE_WINDOW_DRAG);
BIND_ENUM_CONSTANT(FEATURE_SCREEN_EXCLUDE_FROM_CAPTURE); BIND_ENUM_CONSTANT(FEATURE_SCREEN_EXCLUDE_FROM_CAPTURE);
BIND_ENUM_CONSTANT(FEATURE_WINDOW_EMBEDDING);
BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE); BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE);
BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN); BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN);
@ -1218,9 +1234,9 @@ Vector<String> DisplayServer::get_create_function_rendering_drivers(int p_index)
return server_create_functions[p_index].get_rendering_drivers_function(); 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); 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) { void DisplayServer::_input_set_mouse_mode(Input::MouseMode p_mode) {

View file

@ -92,7 +92,7 @@ public:
CONTEXT_ENGINE, 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<String> (*GetRenderingDriversFunction)(); typedef Vector<String> (*GetRenderingDriversFunction)();
private: private:
@ -155,6 +155,7 @@ public:
FEATURE_NATIVE_DIALOG_FILE_EXTRA, FEATURE_NATIVE_DIALOG_FILE_EXTRA,
FEATURE_WINDOW_DRAG, FEATURE_WINDOW_DRAG,
FEATURE_SCREEN_EXCLUDE_FROM_CAPTURE, FEATURE_SCREEN_EXCLUDE_FROM_CAPTURE,
FEATURE_WINDOW_EMBEDDING,
}; };
virtual bool has_feature(Feature p_feature) const = 0; virtual bool has_feature(Feature p_feature) const = 0;
@ -550,6 +551,10 @@ public:
virtual void enable_for_stealing_focus(OS::ProcessID pid); virtual void enable_for_stealing_focus(OS::ProcessID pid);
virtual Error embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus);
virtual Error remove_embedded_process(OS::ProcessID p_pid);
virtual OS::ProcessID get_focused_process_id();
virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback); virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback);
virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback); virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback);
@ -609,7 +614,7 @@ public:
static int get_create_function_count(); static int get_create_function_count();
static const char *get_create_function_name(int p_index); static const char *get_create_function_name(int p_index);
static Vector<String> get_create_function_rendering_drivers(int p_index); static Vector<String> get_create_function_rendering_drivers(int p_index);
static DisplayServer *create(int p_index, const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error); static DisplayServer *create(int p_index, const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error);
enum RenderingDeviceCreationStatus { enum RenderingDeviceCreationStatus {
UNKNOWN, UNKNOWN,

View file

@ -45,7 +45,7 @@ private:
return drivers; 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; r_error = OK;
RasterizerDummy::make_current(); RasterizerDummy::make_current();
return memnew(DisplayServerHeadless()); return memnew(DisplayServerHeadless());

View file

@ -55,7 +55,7 @@ private:
return drivers; 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; r_error = OK;
RasterizerDummy::make_current(); RasterizerDummy::make_current();
return memnew(DisplayServerMock()); return memnew(DisplayServerMock());

View file

@ -291,7 +291,7 @@ struct GodotTestCaseListener : public doctest::IReporter {
OS::get_singleton()->set_has_server_feature_callback(nullptr); OS::get_singleton()->set_has_server_feature_callback(nullptr);
for (int i = 0; i < DisplayServer::get_create_function_count(); i++) { for (int i = 0; i < DisplayServer::get_create_function_count(); i++) {
if (String("mock") == DisplayServer::get_create_function_name(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; break;
} }
} }