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