Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/run/embedded_process.cpp
9903 views
1
/**************************************************************************/
2
/* embedded_process.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "embedded_process.h"
32
33
#include "core/config/project_settings.h"
34
#include "editor/editor_string_names.h"
35
#include "scene/main/window.h"
36
#include "scene/resources/style_box_flat.h"
37
#include "scene/theme/theme_db.h"
38
39
void EmbeddedProcessBase::_notification(int p_what) {
40
switch (p_what) {
41
case NOTIFICATION_READY: {
42
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &EmbeddedProcessBase::_project_settings_changed));
43
} break;
44
case NOTIFICATION_ENTER_TREE: {
45
window = get_window();
46
transp_enabled = GLOBAL_GET("display/window/per_pixel_transparency/allowed");
47
clear_color = GLOBAL_GET("rendering/environment/defaults/default_clear_color");
48
} break;
49
case NOTIFICATION_DRAW: {
50
_draw();
51
} break;
52
case NOTIFICATION_THEME_CHANGED: {
53
checkerboard = get_editor_theme_icon(SNAME("Checkerboard"));
54
focus_style_box = get_theme_stylebox(SNAME("FocusViewport"), EditorStringName(EditorStyles));
55
Ref<StyleBoxFlat> focus_style_box_flat = focus_style_box;
56
if (focus_style_box_flat.is_valid()) {
57
margin_top_left = Point2i(focus_style_box_flat->get_border_width(SIDE_LEFT), focus_style_box_flat->get_border_width(SIDE_TOP));
58
margin_bottom_right = Point2i(focus_style_box_flat->get_border_width(SIDE_RIGHT), focus_style_box_flat->get_border_width(SIDE_BOTTOM));
59
} else if (focus_style_box.is_valid()) {
60
margin_top_left = Point2i(focus_style_box->get_margin(SIDE_LEFT), focus_style_box->get_margin(SIDE_TOP));
61
margin_bottom_right = Point2i(focus_style_box->get_margin(SIDE_RIGHT), focus_style_box->get_margin(SIDE_BOTTOM));
62
} else {
63
margin_top_left = Point2i();
64
margin_bottom_right = Point2i();
65
}
66
} break;
67
}
68
}
69
70
void EmbeddedProcessBase::_project_settings_changed() {
71
transp_enabled = GLOBAL_GET("display/window/per_pixel_transparency/allowed");
72
clear_color = GLOBAL_GET("rendering/environment/defaults/default_clear_color");
73
queue_redraw();
74
}
75
76
void EmbeddedProcessBase::_bind_methods() {
77
ADD_SIGNAL(MethodInfo("embedding_completed"));
78
ADD_SIGNAL(MethodInfo("embedding_failed"));
79
ADD_SIGNAL(MethodInfo("embedded_process_updated"));
80
ADD_SIGNAL(MethodInfo("embedded_process_focused"));
81
}
82
83
void EmbeddedProcessBase::_draw() {
84
if (is_embedding_completed()) {
85
Rect2 r = get_adjusted_embedded_window_rect(get_rect());
86
#ifndef MACOS_ENABLED
87
r.position -= get_window()->get_position();
88
#endif
89
if (transp_enabled) {
90
draw_texture_rect(checkerboard, r, true);
91
} else {
92
draw_rect(r, clear_color, true);
93
}
94
}
95
if (is_process_focused() && focus_style_box.is_valid()) {
96
Size2 size = get_size();
97
Rect2 r = Rect2(Point2(), size);
98
focus_style_box->draw(get_canvas_item(), r);
99
}
100
}
101
102
void EmbeddedProcessBase::set_window_size(const Size2i &p_window_size) {
103
if (window_size != p_window_size) {
104
window_size = p_window_size;
105
queue_update_embedded_process();
106
queue_redraw();
107
}
108
}
109
110
void EmbeddedProcessBase::set_keep_aspect(bool p_keep_aspect) {
111
if (keep_aspect != p_keep_aspect) {
112
keep_aspect = p_keep_aspect;
113
queue_update_embedded_process();
114
queue_redraw();
115
}
116
}
117
118
Rect2i EmbeddedProcessBase::get_screen_embedded_window_rect() const {
119
return get_adjusted_embedded_window_rect(get_global_rect());
120
}
121
122
int EmbeddedProcessBase::get_margin_size(Side p_side) const {
123
ERR_FAIL_INDEX_V((int)p_side, 4, 0);
124
125
switch (p_side) {
126
case SIDE_LEFT:
127
return margin_top_left.x;
128
case SIDE_RIGHT:
129
return margin_bottom_right.x;
130
case SIDE_TOP:
131
return margin_top_left.y;
132
case SIDE_BOTTOM:
133
return margin_bottom_right.y;
134
}
135
136
return 0;
137
}
138
139
Size2 EmbeddedProcessBase::get_margins_size() const {
140
return margin_top_left + margin_bottom_right;
141
}
142
143
EmbeddedProcessBase::EmbeddedProcessBase() {
144
set_focus_mode(FOCUS_ALL);
145
}
146
147
EmbeddedProcessBase::~EmbeddedProcessBase() {
148
}
149
150
Rect2i EmbeddedProcess::get_adjusted_embedded_window_rect(const Rect2i &p_rect) const {
151
Rect2i control_rect = Rect2i(p_rect.position + margin_top_left, (p_rect.size - get_margins_size()).maxi(1));
152
if (window) {
153
control_rect.position += window->get_position();
154
}
155
if (window_size != Size2i()) {
156
Rect2i desired_rect;
157
if (!keep_aspect && control_rect.size.x >= window_size.x && control_rect.size.y >= window_size.y) {
158
// Fixed at the desired size.
159
desired_rect.size = window_size;
160
} else {
161
float ratio = MIN((float)control_rect.size.x / window_size.x, (float)control_rect.size.y / window_size.y);
162
desired_rect.size = Size2i(window_size.x * ratio, window_size.y * ratio).maxi(1);
163
}
164
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));
165
return desired_rect;
166
} else {
167
// Stretch, use all the control area.
168
return control_rect;
169
}
170
}
171
172
bool EmbeddedProcess::is_embedding_in_progress() const {
173
return !timer_embedding->is_stopped();
174
}
175
176
bool EmbeddedProcess::is_embedding_completed() const {
177
return embedding_completed;
178
}
179
180
bool EmbeddedProcess::is_process_focused() const {
181
return focused_process_id == current_process_id && has_focus();
182
}
183
184
int EmbeddedProcess::get_embedded_pid() const {
185
return current_process_id;
186
}
187
188
void EmbeddedProcess::embed_process(OS::ProcessID p_pid) {
189
if (!window) {
190
return;
191
}
192
193
ERR_FAIL_COND_MSG(!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING), "Embedded process not supported by this display server.");
194
195
if (current_process_id != 0) {
196
// Stop embedding the last process.
197
OS::get_singleton()->kill(current_process_id);
198
}
199
200
reset();
201
202
current_process_id = p_pid;
203
start_embedding_time = OS::get_singleton()->get_ticks_msec();
204
embedding_grab_focus = has_focus();
205
timer_update_embedded_process->start();
206
set_process(true);
207
set_notify_transform(true);
208
209
// Attempt to embed the process, but if it has just started and the window is not ready yet,
210
// we will retry in this case.
211
_try_embed_process();
212
}
213
214
void EmbeddedProcess::reset() {
215
if (current_process_id != 0 && embedding_completed) {
216
DisplayServer::get_singleton()->remove_embedded_process(current_process_id);
217
}
218
current_process_id = 0;
219
embedding_completed = false;
220
start_embedding_time = 0;
221
embedding_grab_focus = false;
222
timer_embedding->stop();
223
timer_update_embedded_process->stop();
224
set_process(false);
225
set_notify_transform(false);
226
queue_redraw();
227
}
228
229
void EmbeddedProcess::request_close() {
230
if (current_process_id != 0 && embedding_completed) {
231
DisplayServer::get_singleton()->request_close_embedded_process(current_process_id);
232
}
233
}
234
235
void EmbeddedProcess::_try_embed_process() {
236
bool is_visible = is_visible_in_tree();
237
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);
238
if (err == OK) {
239
embedding_completed = true;
240
queue_redraw();
241
emit_signal(SNAME("embedding_completed"));
242
} else if (err == ERR_DOES_NOT_EXIST) {
243
if (OS::get_singleton()->get_ticks_msec() - start_embedding_time >= (uint64_t)embedding_timeout) {
244
// Embedding process timed out.
245
reset();
246
emit_signal(SNAME("embedding_failed"));
247
} else {
248
// Tries another shot.
249
timer_embedding->start();
250
}
251
} else {
252
// Another unknown error.
253
reset();
254
emit_signal(SNAME("embedding_failed"));
255
}
256
}
257
258
bool EmbeddedProcess::_is_embedded_process_updatable() {
259
return window && current_process_id != 0 && embedding_completed;
260
}
261
262
void EmbeddedProcess::queue_update_embedded_process() {
263
updated_embedded_process_queued = true;
264
}
265
266
void EmbeddedProcess::_timer_update_embedded_process_timeout() {
267
_check_focused_process_id();
268
_check_mouse_over();
269
270
if (!updated_embedded_process_queued) {
271
// We need to detect when the control globally changes location or size on the screen.
272
// NOTIFICATION_RESIZED and NOTIFICATION_WM_POSITION_CHANGED are not enough to detect
273
// resized parent to siblings controls that can affect global position.
274
Rect2i new_global_rect = get_global_rect();
275
if (last_global_rect != new_global_rect) {
276
last_global_rect = new_global_rect;
277
queue_update_embedded_process();
278
}
279
}
280
}
281
282
void EmbeddedProcess::_update_embedded_process() {
283
if (!_is_embedded_process_updatable()) {
284
return;
285
}
286
287
bool must_grab_focus = false;
288
bool focus = has_focus();
289
if (last_updated_embedded_process_focused != focus) {
290
if (focus) {
291
must_grab_focus = true;
292
}
293
last_updated_embedded_process_focused = focus;
294
}
295
296
DisplayServer::get_singleton()->embed_process(window->get_window_id(), current_process_id, get_screen_embedded_window_rect(), is_visible_in_tree(), must_grab_focus);
297
emit_signal(SNAME("embedded_process_updated"));
298
}
299
300
void EmbeddedProcess::_timer_embedding_timeout() {
301
_try_embed_process();
302
}
303
304
void EmbeddedProcess::_notification(int p_what) {
305
switch (p_what) {
306
case NOTIFICATION_PROCESS: {
307
if (updated_embedded_process_queued) {
308
updated_embedded_process_queued = false;
309
_update_embedded_process();
310
}
311
} break;
312
case NOTIFICATION_RESIZED:
313
case NOTIFICATION_VISIBILITY_CHANGED:
314
case NOTIFICATION_WM_POSITION_CHANGED: {
315
queue_update_embedded_process();
316
} break;
317
case NOTIFICATION_APPLICATION_FOCUS_IN: {
318
application_has_focus = true;
319
last_application_focus_time = OS::get_singleton()->get_ticks_msec();
320
} break;
321
case NOTIFICATION_APPLICATION_FOCUS_OUT: {
322
application_has_focus = false;
323
} break;
324
case NOTIFICATION_FOCUS_ENTER: {
325
queue_update_embedded_process();
326
} break;
327
}
328
}
329
330
void EmbeddedProcess::_check_mouse_over() {
331
// This method checks if the mouse is over the embedded process while the current application is focused.
332
// The goal is to give focus to the embedded process as soon as the mouse hovers over it,
333
// allowing the user to interact with it immediately without needing to click first.
334
if (!embedding_completed || !application_has_focus || !window || has_focus() || !is_visible_in_tree() || !window->has_focus() || Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) || Input::get_singleton()->is_mouse_button_pressed(MouseButton::RIGHT)) {
335
return;
336
}
337
338
// Before checking whether the mouse is truly inside the embedded process, ensure
339
// the editor has enough time to re-render. When a breakpoint is hit in the script editor,
340
// `_check_mouse_over` may be triggered before the editor hides the game workspace.
341
// This prevents the embedded process from regaining focus immediately after the editor has taken it.
342
if (OS::get_singleton()->get_ticks_msec() - last_application_focus_time < 500) {
343
return;
344
}
345
346
// Input::is_mouse_button_pressed is not sufficient to detect the mouse button state
347
// while the floating game window is being resized.
348
BitField<MouseButtonMask> mouse_button_mask = DisplayServer::get_singleton()->mouse_get_button_state();
349
if (!mouse_button_mask.is_empty()) {
350
return;
351
}
352
353
// Not stealing focus from a textfield.
354
if (get_viewport()->gui_get_focus_owner() && get_viewport()->gui_get_focus_owner()->is_text_field()) {
355
return;
356
}
357
358
Vector2 mouse_position = DisplayServer::get_singleton()->mouse_get_position();
359
Rect2i window_rect = get_screen_embedded_window_rect();
360
if (!window_rect.has_point(mouse_position)) {
361
return;
362
}
363
364
// Don't grab the focus if mouse over another window.
365
DisplayServer::WindowID window_id_over = DisplayServer::get_singleton()->get_window_at_screen_position(mouse_position);
366
if (window_id_over > 0 && window_id_over != window->get_window_id()) {
367
return;
368
}
369
370
// Check if there's an exclusive popup, an open menu, or a tooltip.
371
// We don't want to grab focus to prevent the game window from coming to the front of the modal window
372
// or the open menu from closing when the mouse cursor moves outside the menu and over the embedded game.
373
Vector<DisplayServer::WindowID> wl = DisplayServer::get_singleton()->get_window_list();
374
for (const DisplayServer::WindowID &window_id : wl) {
375
Window *w = Window::get_from_id(window_id);
376
if (w && (w->is_exclusive() || w->get_flag(Window::FLAG_POPUP))) {
377
return;
378
}
379
}
380
381
// Force "regrabbing" the game window focus.
382
last_updated_embedded_process_focused = false;
383
384
grab_focus();
385
queue_redraw();
386
}
387
388
void EmbeddedProcess::_check_focused_process_id() {
389
OS::ProcessID process_id = DisplayServer::get_singleton()->get_focused_process_id();
390
if (process_id != focused_process_id) {
391
focused_process_id = process_id;
392
if (focused_process_id == current_process_id) {
393
// The embedded process got the focus.
394
395
// Refocus the current model when focusing the embedded process.
396
Window *modal_window = _get_current_modal_window();
397
if (!modal_window) {
398
emit_signal(SNAME("embedded_process_focused"));
399
if (has_focus()) {
400
// Redraw to updated the focus style.
401
queue_redraw();
402
} else {
403
grab_focus();
404
}
405
}
406
} else if (has_focus()) {
407
release_focus();
408
}
409
}
410
411
// Ensure that the opened modal dialog is refocused when the focused process is the embedded process.
412
if (!application_has_focus && focused_process_id == current_process_id) {
413
Window *modal_window = _get_current_modal_window();
414
if (modal_window) {
415
if (modal_window->get_mode() == Window::MODE_MINIMIZED) {
416
modal_window->set_mode(Window::MODE_WINDOWED);
417
}
418
callable_mp(modal_window, &Window::grab_focus).call_deferred();
419
}
420
}
421
}
422
423
Window *EmbeddedProcess::_get_current_modal_window() {
424
Vector<DisplayServer::WindowID> wl = DisplayServer::get_singleton()->get_window_list();
425
for (const DisplayServer::WindowID &window_id : wl) {
426
Window *w = Window::get_from_id(window_id);
427
if (!w) {
428
continue;
429
}
430
431
if (w->is_exclusive()) {
432
return w;
433
}
434
}
435
return nullptr;
436
}
437
438
EmbeddedProcess::EmbeddedProcess() :
439
EmbeddedProcessBase() {
440
timer_embedding = memnew(Timer);
441
timer_embedding->set_wait_time(0.1);
442
timer_embedding->set_one_shot(true);
443
add_child(timer_embedding);
444
timer_embedding->connect("timeout", callable_mp(this, &EmbeddedProcess::_timer_embedding_timeout));
445
446
timer_update_embedded_process = memnew(Timer);
447
timer_update_embedded_process->set_wait_time(0.1);
448
add_child(timer_update_embedded_process);
449
timer_update_embedded_process->connect("timeout", callable_mp(this, &EmbeddedProcess::_timer_update_embedded_process_timeout));
450
}
451
452
EmbeddedProcess::~EmbeddedProcess() {
453
if (current_process_id != 0) {
454
// Stop embedding the last process.
455
OS::get_singleton()->kill(current_process_id);
456
reset();
457
}
458
}
459
460