From f658161619fab52547c408b6d07ecd2ff4766f7e Mon Sep 17 00:00:00 2001 From: Stuart Carnie Date: Thu, 8 May 2025 18:51:28 +1000 Subject: [PATCH] macOS: Embedded window can be dismissed by clicking close - Installed a SIGINT handler to terminate the application gracefully. - Handle varying display scaling --- drivers/unix/os_unix.cpp | 9 ++- editor/plugins/game_view_plugin.cpp | 2 +- main/main.cpp | 7 +- platform/macos/display_server_embedded.h | 43 +++++------ platform/macos/display_server_embedded.mm | 71 +++++++++++++------ platform/macos/display_server_macos.mm | 5 +- .../macos/editor/embedded_process_macos.h | 13 ++-- .../macos/editor/embedded_process_macos.mm | 30 +++++--- platform/macos/embedded_debugger.h | 1 + platform/macos/embedded_debugger.mm | 14 ++++ platform/macos/godot_main_macos.mm | 4 +- platform/macos/os_macos.mm | 15 +++- 12 files changed, 144 insertions(+), 70 deletions(-) diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 3494c326946..33087768e07 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -131,6 +131,8 @@ static void _setup_clock() { } #endif +struct sigaction old_action; + static void handle_interrupt(int sig) { if (!EngineDebugger::is_active()) { return; @@ -138,6 +140,11 @@ static void handle_interrupt(int sig) { EngineDebugger::get_script_debugger()->set_depth(-1); EngineDebugger::get_script_debugger()->set_lines_left(1); + + // Ensure we call the old action if it was configured. + if (old_action.sa_handler && old_action.sa_handler != SIG_IGN && old_action.sa_handler != SIG_DFL) { + old_action.sa_handler(sig); + } } void OS_Unix::initialize_debugging() { @@ -145,7 +152,7 @@ void OS_Unix::initialize_debugging() { struct sigaction action; memset(&action, 0, sizeof(action)); action.sa_handler = handle_interrupt; - sigaction(SIGINT, &action, nullptr); + sigaction(SIGINT, &action, &old_action); } } diff --git a/editor/plugins/game_view_plugin.cpp b/editor/plugins/game_view_plugin.cpp index 5eb5ad768e0..d39139d21fb 100644 --- a/editor/plugins/game_view_plugin.cpp +++ b/editor/plugins/game_view_plugin.cpp @@ -864,7 +864,7 @@ void GameView::_update_arguments_for_instance(int p_idx, List &r_argumen N = r_arguments.insert_after(N, itos(DisplayServer::get_singleton()->window_get_native_handle(DisplayServer::WINDOW_HANDLE, get_window()->get_window_id()))); #if MACOS_ENABLED - r_arguments.push_back("--embedded"); + N = r_arguments.insert_after(N, "--embedded"); #endif // Be sure to have the correct window size in the embedded_process control. diff --git a/main/main.cpp b/main/main.cpp index 04d1503bcbe..512bcae4642 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1399,9 +1399,12 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph display_driver = NULL_DISPLAY_DRIVER; } else if (arg == "--embedded") { // Enable embedded mode. - +#ifdef MACOS_ENABLED display_driver = EMBEDDED_DISPLAY_DRIVER; - +#else + OS::get_singleton()->print("--embedded is only supported on macOS, aborting.\n"); + goto error; +#endif } else if (arg == "--log-file") { // write to log file if (N) { diff --git a/platform/macos/display_server_embedded.h b/platform/macos/display_server_embedded.h index 18a3638a136..e9de4ca0a54 100644 --- a/platform/macos/display_server_embedded.h +++ b/platform/macos/display_server_embedded.h @@ -33,33 +33,27 @@ #include "core/input/input.h" #include "servers/display_server.h" -#if defined(GLES3_ENABLED) -#include "embedded_gl_manager.h" -#include "platform_gl.h" -#endif // GLES3_ENABLED - -#if defined(RD_ENABLED) -#include "servers/rendering/rendering_device.h" - -#if defined(VULKAN_ENABLED) -#import "rendering_context_driver_vulkan_macos.h" -#endif // VULKAN_ENABLED -#if defined(METAL_ENABLED) -#import "drivers/metal/rendering_context_driver_metal.h" -#endif -#endif // RD_ENABLED - @class CAContext; +@class CALayer; +class GLManagerEmbedded; +class RenderingContextDriver; +class RenderingDevice; + +struct DisplayServerEmbeddedState { + /// Default to a scale of 2.0, which is the most common. + float screen_max_scale = 2.0f; + float screen_dpi = 96.0f; + + void serialize(PackedByteArray &r_data); + Error deserialize(const PackedByteArray &p_data); +}; class DisplayServerEmbedded : public DisplayServer { GDCLASS(DisplayServerEmbedded, DisplayServer) _THREAD_SAFE_CLASS_ - struct { - float screen_max_scale = 1.0f; - float screen_dpi = 96.0f; - } state; + DisplayServerEmbeddedState state; NativeMenu *native_menu = nullptr; @@ -70,13 +64,11 @@ class DisplayServerEmbedded : public DisplayServer { HashMap input_event_callbacks; HashMap input_text_callbacks; - float content_scale = 1.0f; - WindowID window_id_counter = MAIN_WINDOW_ID; - CAContext *ca_context = nil; + CAContext *ca_context = nullptr; // Either be a CAMetalLayer or a CALayer depending on the rendering driver. - CALayer *layer = nil; + CALayer *layer = nullptr; #ifdef GLES3_ENABLED GLManagerEmbedded *gl_manager = nullptr; #endif @@ -222,8 +214,7 @@ public: virtual CursorShape cursor_get_shape() const override; virtual void cursor_set_custom_image(const Ref &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override; - void update_state(const Dictionary &p_state); - void set_content_scale(float p_scale); + void set_state(const DisplayServerEmbeddedState &p_state); virtual void swap_buffers() override; DisplayServerEmbedded(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); diff --git a/platform/macos/display_server_embedded.mm b/platform/macos/display_server_embedded.mm index 7308b97b96e..c00c83fd140 100644 --- a/platform/macos/display_server_embedded.mm +++ b/platform/macos/display_server_embedded.mm @@ -30,19 +30,31 @@ #import "display_server_embedded.h" +#if defined(GLES3_ENABLED) +#import "embedded_gl_manager.h" +#import "platform_gl.h" + +#import "drivers/gles3/rasterizer_gles3.h" +#endif + +#if defined(RD_ENABLED) +#import "servers/rendering/renderer_rd/renderer_compositor_rd.h" +#import "servers/rendering/rendering_device.h" + +#if defined(VULKAN_ENABLED) +#import "rendering_context_driver_vulkan_macos.h" +#endif // VULKAN_ENABLED +#if defined(METAL_ENABLED) +#import "drivers/metal/rendering_context_driver_metal.h" +#endif +#endif // RD_ENABLED + #import "embedded_debugger.h" #import "macos_quartz_core_spi.h" #import "core/config/project_settings.h" #import "core/debugger/engine_debugger.h" - -#if defined(GLES3_ENABLED) -#include "drivers/gles3/rasterizer_gles3.h" -#endif - -#if defined(RD_ENABLED) -#import "servers/rendering/renderer_rd/renderer_compositor_rd.h" -#endif +#import "core/io/marshalls.h" DisplayServerEmbedded::DisplayServerEmbedded(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) { EmbeddedDebugger::initialize(this); @@ -176,15 +188,15 @@ DisplayServerEmbedded::DisplayServerEmbedded(const String &p_rendering_driver, W } #endif - constexpr CGFloat CONTENT_SCALE = 2.0; - layer.contentsScale = CONTENT_SCALE; + CGFloat scale = screen_get_max_scale(); + layer.contentsScale = scale; layer.magnificationFilter = kCAFilterNearest; layer.minificationFilter = kCAFilterNearest; - layer.opaque = NO; // Never opaque when embedded. + layer.opaque = YES; // Always opaque when embedded. layer.actions = @{ @"contents" : [NSNull null] }; // Disable implicit animations for contents. // AppKit frames, bounds and positions are always in points. CGRect bounds = CGRectMake(0, 0, p_resolution.width, p_resolution.height); - bounds = CGRectApplyAffineTransform(bounds, CGAffineTransformMakeScale(1.0 / CONTENT_SCALE, 1.0 / CONTENT_SCALE)); + bounds = CGRectApplyAffineTransform(bounds, CGAffineTransformInvert(CGAffineTransformMakeScale(scale, scale))); layer.bounds = bounds; CGSConnectionID connection_id = CGSMainConnectionID(); @@ -582,11 +594,11 @@ void DisplayServerEmbedded::window_set_size(const Size2i p_size, WindowID p_wind [CATransaction begin]; [CATransaction setDisableActions:YES]; - // TODO(sgc): Pass scale as argument from parent process. - constexpr CGFloat CONTENT_SCALE = 2.0; + CGFloat scale = screen_get_max_scale(); CGRect bounds = CGRectMake(0, 0, p_size.width, p_size.height); - bounds = CGRectApplyAffineTransform(bounds, CGAffineTransformMakeScale(1.0 / CONTENT_SCALE, 1.0 / CONTENT_SCALE)); + bounds = CGRectApplyAffineTransform(bounds, CGAffineTransformInvert(CGAffineTransformMakeScale(scale, scale))); layer.bounds = bounds; + layer.contentsScale = scale; #if defined(RD_ENABLED) if (rendering_context) { @@ -685,12 +697,8 @@ void DisplayServerEmbedded::window_set_ime_position(const Point2i &p_pos, Window ime_last_position = p_pos; } -void DisplayServerEmbedded::update_state(const Dictionary &p_state) { - state.screen_max_scale = p_state["screen_get_max_scale"]; -} - -void DisplayServerEmbedded::set_content_scale(float p_scale) { - content_scale = p_scale; +void DisplayServerEmbedded::set_state(const DisplayServerEmbeddedState &p_state) { + state = p_state; } void DisplayServerEmbedded::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { @@ -742,3 +750,24 @@ void DisplayServerEmbedded::swap_buffers() { } #endif } + +void DisplayServerEmbeddedState::serialize(PackedByteArray &r_data) { + r_data.resize(8); + + uint8_t *data = r_data.ptrw(); + data += encode_float(screen_max_scale, data); + data += encode_float(screen_dpi, data); + + // Assert we had enough space. + DEV_ASSERT(data - r_data.ptrw() >= r_data.size()); +} + +Error DisplayServerEmbeddedState::deserialize(const PackedByteArray &p_data) { + const uint8_t *data = p_data.ptr(); + + screen_max_scale = decode_float(data); + data += sizeof(float); + screen_dpi = decode_float(data); + + return OK; +} diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 4e99a7a9ec4..f6ddfd8f5cf 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -3297,6 +3297,7 @@ Error DisplayServerMacOS::embed_process_update(WindowID p_window, const Embedded [CATransaction setDisableActions:YES]; EmbeddedProcessData *ed = embedded_processes.getptr(p_pid); + CGFloat scale = screen_get_max_scale(); if (ed == nil) { ed = &embedded_processes.insert(p_pid, EmbeddedProcessData())->value; @@ -3305,7 +3306,7 @@ Error DisplayServerMacOS::embed_process_update(WindowID p_window, const Embedded CALayerHost *host = [CALayerHost new]; uint32_t p_context_id = p_process->get_context_id(); host.contextId = static_cast(p_context_id); - host.contentsScale = wd->window_object.backingScaleFactor; + host.contentsScale = scale; host.contentsGravity = kCAGravityCenter; ed->layer_host = host; [wd->window_view.layer addSublayer:host]; @@ -3313,7 +3314,7 @@ Error DisplayServerMacOS::embed_process_update(WindowID p_window, const Embedded Rect2i p_rect = p_process->get_screen_embedded_window_rect(); CGRect rect = CGRectMake(p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y); - rect = CGRectApplyAffineTransform(rect, CGAffineTransformMakeScale(0.5, 0.5)); + rect = CGRectApplyAffineTransform(rect, CGAffineTransformInvert(CGAffineTransformMakeScale(scale, scale))); CGFloat height = wd->window_view.frame.size.height; CGFloat x = rect.origin.x; diff --git a/platform/macos/editor/embedded_process_macos.h b/platform/macos/editor/embedded_process_macos.h index a81d5cec25e..0723c97510a 100644 --- a/platform/macos/editor/embedded_process_macos.h +++ b/platform/macos/editor/embedded_process_macos.h @@ -102,12 +102,12 @@ public: return embedding_state == EmbeddingState::COMPLETED; } - virtual bool is_process_focused() const override { return layer_host->has_focus(); } - virtual void embed_process(OS::ProcessID p_pid) override; - virtual int get_embedded_pid() const override { return current_process_id; } - virtual void reset() override; - virtual void request_close() override; - virtual void queue_update_embedded_process() override { update_embedded_process(); } + bool is_process_focused() const override { return layer_host->has_focus(); } + void embed_process(OS::ProcessID p_pid) override; + int get_embedded_pid() const override { return current_process_id; } + void reset() override; + void request_close() override; + void queue_update_embedded_process() override { update_embedded_process(); } Rect2i get_adjusted_embedded_window_rect(const Rect2i &p_rect) const override; @@ -117,4 +117,5 @@ public: _FORCE_INLINE_ DisplayServer::MouseMode get_mouse_mode() const { return mouse_mode; } EmbeddedProcessMacOS(); + ~EmbeddedProcessMacOS() override; }; diff --git a/platform/macos/editor/embedded_process_macos.mm b/platform/macos/editor/embedded_process_macos.mm index cd0b6e07105..b2c80fb25a2 100644 --- a/platform/macos/editor/embedded_process_macos.mm +++ b/platform/macos/editor/embedded_process_macos.mm @@ -30,6 +30,7 @@ #include "embedded_process_macos.h" +#include "platform/macos/display_server_embedded.h" #include "platform/macos/display_server_macos.h" #include "core/input/input_event_codec.h" @@ -122,7 +123,7 @@ void EmbeddedProcessMacOS::reset() { void EmbeddedProcessMacOS::request_close() { if (current_process_id != 0 && is_embedding_completed()) { - ds->request_close_embedded_process(current_process_id); + script_debugger->send_message("embed:win_event", { DisplayServer::WINDOW_EVENT_CLOSE_REQUEST }); } } @@ -133,19 +134,22 @@ void EmbeddedProcessMacOS::_try_embed_process() { Error err = ds->embed_process_update(window->get_window_id(), this); if (err == OK) { + // Replicate some of the DisplayServer state. + { + DisplayServerEmbeddedState state; + state.screen_max_scale = ds->screen_get_max_scale(); + state.screen_dpi = ds->screen_get_dpi(); + PackedByteArray data; + state.serialize(data); + script_debugger->send_message("embed:ds_state", { data }); + } + Rect2i rect = get_screen_embedded_window_rect(); script_debugger->send_message("embed:window_size", { rect.size }); embedding_state = EmbeddingState::COMPLETED; queue_redraw(); emit_signal(SNAME("embedding_completed")); - // Replicate some of the DisplayServer state. - { - Dictionary state; - state["screen_get_max_scale"] = ds->screen_get_max_scale(); - // script_debugger->send_message("embed:ds_state", { state }); - } - // Send initial joystick state. { Input *input = Input::get_singleton(); @@ -209,13 +213,21 @@ EmbeddedProcessMacOS::EmbeddedProcessMacOS() : ED_SHORTCUT("game_view/release_mouse", TTRC("Release Mouse"), KeyModifierMask::ALT | Key::ESCAPE); } +EmbeddedProcessMacOS::~EmbeddedProcessMacOS() { + if (current_process_id != 0) { + // Stop embedding the last process. + OS::get_singleton()->kill(current_process_id); + reset(); + } +} + void LayerHost::_notification(int p_what) { switch (p_what) { case NOTIFICATION_FOCUS_ENTER: { if (script_debugger) { script_debugger->send_message("embed:win_event", { DisplayServer::WINDOW_EVENT_MOUSE_ENTER }); } - // Temporarily release mouse capture, so we can interact with the editor. + // Restore mouse capture, if necessary. DisplayServer *ds = DisplayServer::get_singleton(); if (process->get_mouse_mode() != ds->mouse_get_mode()) { // Restore embedded process mouse mode. diff --git a/platform/macos/embedded_debugger.h b/platform/macos/embedded_debugger.h index 028c9542b9c..e62fdb48eaf 100644 --- a/platform/macos/embedded_debugger.h +++ b/platform/macos/embedded_debugger.h @@ -63,6 +63,7 @@ private: Error _msg_ime_update(const Array &p_args); Error _msg_joy_add(const Array &p_args); Error _msg_joy_del(const Array &p_args); + Error _msg_ds_state(const Array &p_args); public: static Error parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured); diff --git a/platform/macos/embedded_debugger.mm b/platform/macos/embedded_debugger.mm index 46b55c1b5c7..4a86118cdbe 100644 --- a/platform/macos/embedded_debugger.mm +++ b/platform/macos/embedded_debugger.mm @@ -76,21 +76,25 @@ void EmbeddedDebugger::_init_parse_message_handlers() { parse_message_handlers["ime_update"] = &EmbeddedDebugger::_msg_ime_update; parse_message_handlers["joy_add"] = &EmbeddedDebugger::_msg_joy_add; parse_message_handlers["joy_del"] = &EmbeddedDebugger::_msg_joy_del; + parse_message_handlers["ds_state"] = &EmbeddedDebugger::_msg_ds_state; } Error EmbeddedDebugger::_msg_window_size(const Array &p_args) { + ERR_FAIL_COND_V_MSG(p_args.size() != 1, ERR_INVALID_PARAMETER, "Invalid number of arguments for 'window_size' message."); Size2i size = p_args[0]; ds->window_set_size(size); return OK; } Error EmbeddedDebugger::_msg_mouse_set_mode(const Array &p_args) { + ERR_FAIL_COND_V_MSG(p_args.size() != 1, ERR_INVALID_PARAMETER, "Invalid number of arguments for 'mouse_set_mode' message."); DisplayServer::MouseMode mode = p_args[0]; ds->mouse_set_mode(mode); return OK; } Error EmbeddedDebugger::_msg_event(const Array &p_args) { + ERR_FAIL_COND_V_MSG(p_args.size() != 1, ERR_INVALID_PARAMETER, "Invalid number of arguments for 'event' message."); Input *input = Input::get_singleton(); if (!input) { // Ignore if we've received an event before the process has initialized. @@ -130,6 +134,7 @@ Error EmbeddedDebugger::_msg_event(const Array &p_args) { } Error EmbeddedDebugger::_msg_win_event(const Array &p_args) { + ERR_FAIL_COND_V_MSG(p_args.size() != 1, ERR_INVALID_PARAMETER, "Invalid number of arguments for 'win_event' message."); DisplayServer::WindowEvent win_event = p_args[0]; ds->send_window_event(win_event, DisplayServer::MAIN_WINDOW_ID); if (win_event == DisplayServer::WindowEvent::WINDOW_EVENT_MOUSE_EXIT) { @@ -161,6 +166,15 @@ Error EmbeddedDebugger::_msg_joy_del(const Array &p_args) { return OK; } +Error EmbeddedDebugger::_msg_ds_state(const Array &p_args) { + ERR_FAIL_COND_V_MSG(p_args.size() != 1, ERR_INVALID_PARAMETER, "Invalid number of arguments for 'ds_state' message."); + PackedByteArray data = p_args[0]; + DisplayServerEmbeddedState state; + state.deserialize(data); + ds->set_state(state); + return OK; +} + Error EmbeddedDebugger::parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured) { EmbeddedDebugger *self = static_cast(p_user); r_captured = true; diff --git a/platform/macos/godot_main_macos.mm b/platform/macos/godot_main_macos.mm index 9362179d36c..6d8a6fcecd7 100644 --- a/platform/macos/godot_main_macos.mm +++ b/platform/macos/godot_main_macos.mm @@ -116,7 +116,9 @@ int main(int argc, char **argv) { os->run(); + int exit_code = os->get_exit_code(); + memdelete(os); - return os->get_exit_code(); + return exit_code; } diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index 0f364f379a9..bc8dc88e992 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -923,6 +923,14 @@ void OS_MacOS_NSApp::run() { [NSApp run]; } +static bool sig_received = false; + +static void handle_interrupt(int sig) { + if (sig == SIGINT) { + sig_received = true; + } +} + void OS_MacOS_NSApp::start_main() { Error err; @autoreleasepool { @@ -954,7 +962,7 @@ void OS_MacOS_NSApp::start_main() { } joypad_apple->process_joypads(); - if (Main::iteration()) { + if (Main::iteration() || sig_received) { terminate(); } } @catch (NSException *exception) { @@ -1020,6 +1028,11 @@ OS_MacOS_NSApp::OS_MacOS_NSApp(const char *p_execpath, int p_argc, char **p_argv ERR_FAIL_NULL(delegate); [NSApp setDelegate:delegate]; [NSApp registerUserInterfaceItemSearchHandler:delegate]; + + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = handle_interrupt; + sigaction(SIGINT, &action, nullptr); } // MARK: - OS_MacOS_Embedded