From 09727b29c31acf96d84b541bf164a46373e45f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pa=CC=84vels=20Nadtoc=CC=8Cajevs?= <7645683+bruvzg@users.noreply.github.com> Date: Thu, 5 Jun 2025 11:06:04 +0300 Subject: [PATCH] [macOS] Fix clipboard and TTS not working in embedded game mode. --- platform/macos/SCsub | 1 + platform/macos/display_server_embedded.h | 9 +- platform/macos/display_server_macos.h | 27 +--- platform/macos/display_server_macos.mm | 130 --------------- platform/macos/display_server_macos_base.h | 69 ++++++++ platform/macos/display_server_macos_base.mm | 167 ++++++++++++++++++++ 6 files changed, 243 insertions(+), 160 deletions(-) create mode 100644 platform/macos/display_server_macos_base.h create mode 100644 platform/macos/display_server_macos_base.mm diff --git a/platform/macos/SCsub b/platform/macos/SCsub index ef220efbde7..f1ee9c7d814 100644 --- a/platform/macos/SCsub +++ b/platform/macos/SCsub @@ -11,6 +11,7 @@ files = [ "godot_application_delegate.mm", "crash_handler_macos.mm", "macos_terminal_logger.mm", + "display_server_macos_base.mm", "display_server_embedded.mm", "display_server_macos.mm", "embedded_debugger.mm", diff --git a/platform/macos/display_server_embedded.h b/platform/macos/display_server_embedded.h index 598118abc18..52ee22cb59e 100644 --- a/platform/macos/display_server_embedded.h +++ b/platform/macos/display_server_embedded.h @@ -30,8 +30,7 @@ #pragma once -#include "core/input/input.h" -#include "servers/display_server.h" +#include "display_server_macos_base.h" @class CAContext; @class CALayer; @@ -54,10 +53,8 @@ struct DisplayServerEmbeddedState { } }; -class DisplayServerEmbedded : public DisplayServer { - GDCLASS(DisplayServerEmbedded, DisplayServer) - - _THREAD_SAFE_CLASS_ +class DisplayServerEmbedded : public DisplayServerMacOSBase { + GDSOFTCLASS(DisplayServerEmbedded, DisplayServerMacOSBase) DisplayServerEmbeddedState state; diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 33118963e69..3f4d34c2325 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -30,8 +30,7 @@ #pragma once -#include "core/input/input.h" -#include "servers/display_server.h" +#include "display_server_macos_base.h" #if defined(GLES3_ENABLED) #include "gl_manager_macos_angle.h" @@ -74,10 +73,8 @@ class EmbeddedProcessMacOS; -class DisplayServerMacOS : public DisplayServer { - GDSOFTCLASS(DisplayServerMacOS, DisplayServer); - - _THREAD_SAFE_CLASS_ +class DisplayServerMacOS : public DisplayServerMacOSBase { + GDSOFTCLASS(DisplayServerMacOS, DisplayServerMacOSBase); public: struct KeyEvent { @@ -175,7 +172,6 @@ private: Vector key_event_buffer; int key_event_pos = 0; - id tts = nullptr; id menu_delegate = nullptr; NativeMenuMacOS *native_menu = nullptr; @@ -253,8 +249,6 @@ private: Error _file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector &p_filters, const TypedArray &p_options, const Callable &p_callback, bool p_options_in_cb, WindowID p_window_id); - void initialize_tts() const; - struct EmbeddedProcessData { EmbeddedProcessMacOS *process; WindowData *wd = nullptr; @@ -315,15 +309,6 @@ public: Callable _help_get_search_callback() const; Callable _help_get_action_callback() const; - virtual bool tts_is_speaking() const override; - virtual bool tts_is_paused() const override; - virtual TypedArray tts_get_voices() const override; - - virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override; - virtual void tts_pause() override; - virtual void tts_resume() override; - virtual void tts_stop() override; - virtual bool is_dark_mode_supported() const override; virtual bool is_dark_mode() const override; virtual Color get_accent_color() const override; @@ -350,12 +335,6 @@ public: virtual Point2i mouse_get_position() const override; virtual BitField mouse_get_button_state() const override; - virtual void clipboard_set(const String &p_text) override; - virtual String clipboard_get() const override; - virtual Ref clipboard_get_image() const override; - virtual bool clipboard_has() const override; - virtual bool clipboard_has_image() const override; - virtual int get_screen_count() const override; virtual int get_primary_screen() const override; virtual int get_keyboard_focus_screen() const override; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 057d5c52464..cf526209697 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -43,7 +43,6 @@ #import "key_mapping_macos.h" #import "macos_quartz_core_spi.h" #import "os_macos.h" -#import "tts_macos.h" #include "core/config/project_settings.h" #include "core/io/marshalls.h" @@ -916,66 +915,6 @@ Callable DisplayServerMacOS::_help_get_action_callback() const { return help_action_callback; } -void DisplayServerMacOS::initialize_tts() const { - const_cast(this)->tts = [[TTS_MacOS alloc] init]; -} - -bool DisplayServerMacOS::tts_is_speaking() const { - if (unlikely(!tts)) { - initialize_tts(); - } - ERR_FAIL_NULL_V(tts, false); - return [tts isSpeaking]; -} - -bool DisplayServerMacOS::tts_is_paused() const { - if (unlikely(!tts)) { - initialize_tts(); - } - ERR_FAIL_NULL_V(tts, false); - return [tts isPaused]; -} - -TypedArray DisplayServerMacOS::tts_get_voices() const { - if (unlikely(!tts)) { - initialize_tts(); - } - ERR_FAIL_NULL_V(tts, TypedArray()); - return [tts getVoices]; -} - -void DisplayServerMacOS::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) { - if (unlikely(!tts)) { - initialize_tts(); - } - ERR_FAIL_NULL(tts); - [tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt]; -} - -void DisplayServerMacOS::tts_pause() { - if (unlikely(!tts)) { - initialize_tts(); - } - ERR_FAIL_NULL(tts); - [tts pauseSpeaking]; -} - -void DisplayServerMacOS::tts_resume() { - if (unlikely(!tts)) { - initialize_tts(); - } - ERR_FAIL_NULL(tts); - [tts resumeSpeaking]; -} - -void DisplayServerMacOS::tts_stop() { - if (unlikely(!tts)) { - initialize_tts(); - } - ERR_FAIL_NULL(tts); - [tts stopSpeaking]; -} - bool DisplayServerMacOS::is_dark_mode_supported() const { if (@available(macOS 10.14, *)) { return true; @@ -1642,69 +1581,6 @@ BitField DisplayServerMacOS::mouse_get_button_state() const { return last_button_state; } -void DisplayServerMacOS::clipboard_set(const String &p_text) { - _THREAD_SAFE_METHOD_ - - NSString *copiedString = [NSString stringWithUTF8String:p_text.utf8().get_data()]; - NSArray *copiedStringArray = [NSArray arrayWithObject:copiedString]; - - NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; - [pasteboard clearContents]; - [pasteboard writeObjects:copiedStringArray]; -} - -String DisplayServerMacOS::clipboard_get() const { - _THREAD_SAFE_METHOD_ - - NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; - NSArray *classArray = [NSArray arrayWithObject:[NSString class]]; - NSDictionary *options = [NSDictionary dictionary]; - - BOOL ok = [pasteboard canReadObjectForClasses:classArray options:options]; - - if (!ok) { - return ""; - } - - NSArray *objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options]; - NSString *string = [objectsToPaste objectAtIndex:0]; - - String ret; - ret.append_utf8([string UTF8String]); - return ret; -} - -Ref DisplayServerMacOS::clipboard_get_image() const { - Ref image; - NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; - NSString *result = [pasteboard availableTypeFromArray:[NSArray arrayWithObjects:NSPasteboardTypeTIFF, NSPasteboardTypePNG, nil]]; - if (!result) { - return image; - } - NSData *data = [pasteboard dataForType:result]; - if (!data) { - return image; - } - NSBitmapImageRep *bitmap = [NSBitmapImageRep imageRepWithData:data]; - NSData *pngData = [bitmap representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; - image.instantiate(); - PNGDriverCommon::png_to_image((const uint8_t *)pngData.bytes, pngData.length, false, image); - return image; -} - -bool DisplayServerMacOS::clipboard_has() const { - NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; - NSArray *classArray = [NSArray arrayWithObject:[NSString class]]; - NSDictionary *options = [NSDictionary dictionary]; - return [pasteboard canReadObjectForClasses:classArray options:options]; -} - -bool DisplayServerMacOS::clipboard_has_image() const { - NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; - NSString *result = [pasteboard availableTypeFromArray:[NSArray arrayWithObjects:NSPasteboardTypeTIFF, NSPasteboardTypePNG, nil]]; - return result; -} - int DisplayServerMacOS::get_screen_count() const { _THREAD_SAFE_METHOD_ @@ -4013,12 +3889,6 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM // Register to be notified on displays arrangement changes. CGDisplayRegisterReconfigurationCallback(_displays_arrangement_changed, nullptr); - // Init TTS - bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech"); - if (tts_enabled) { - initialize_tts(); - } - native_menu = memnew(NativeMenuMacOS); #ifdef ACCESSKIT_ENABLED diff --git a/platform/macos/display_server_macos_base.h b/platform/macos/display_server_macos_base.h new file mode 100644 index 00000000000..d66e341d620 --- /dev/null +++ b/platform/macos/display_server_macos_base.h @@ -0,0 +1,69 @@ +/**************************************************************************/ +/* display_server_macos_base.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. */ +/**************************************************************************/ + +#pragma once + +#include "core/input/input.h" +#include "servers/display_server.h" + +#define FontVariation __FontVariation + +#import + +#undef FontVariation + +class DisplayServerMacOSBase : public DisplayServer { + GDSOFTCLASS(DisplayServerMacOSBase, DisplayServer) + + id tts = nullptr; + +protected: + _THREAD_SAFE_CLASS_ + + void initialize_tts() const; + +public: + virtual void clipboard_set(const String &p_text) override; + virtual String clipboard_get() const override; + virtual Ref clipboard_get_image() const override; + virtual bool clipboard_has() const override; + virtual bool clipboard_has_image() const override; + + virtual bool tts_is_speaking() const override; + virtual bool tts_is_paused() const override; + virtual TypedArray tts_get_voices() const override; + + virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override; + virtual void tts_pause() override; + virtual void tts_resume() override; + virtual void tts_stop() override; + + DisplayServerMacOSBase(); +}; diff --git a/platform/macos/display_server_macos_base.mm b/platform/macos/display_server_macos_base.mm new file mode 100644 index 00000000000..fcf2fbd462f --- /dev/null +++ b/platform/macos/display_server_macos_base.mm @@ -0,0 +1,167 @@ +/**************************************************************************/ +/* display_server_macos_base.mm */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#import "display_server_macos_base.h" +#import "tts_macos.h" + +#include "core/config/project_settings.h" +#include "drivers/png/png_driver_common.h" + +void DisplayServerMacOSBase::clipboard_set(const String &p_text) { + _THREAD_SAFE_METHOD_ + + NSString *copiedString = [NSString stringWithUTF8String:p_text.utf8().get_data()]; + NSArray *copiedStringArray = [NSArray arrayWithObject:copiedString]; + + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard clearContents]; + [pasteboard writeObjects:copiedStringArray]; +} + +String DisplayServerMacOSBase::clipboard_get() const { + _THREAD_SAFE_METHOD_ + + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + NSArray *classArray = [NSArray arrayWithObject:[NSString class]]; + NSDictionary *options = [NSDictionary dictionary]; + + BOOL ok = [pasteboard canReadObjectForClasses:classArray options:options]; + + if (!ok) { + return ""; + } + + NSArray *objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options]; + NSString *string = [objectsToPaste objectAtIndex:0]; + + String ret; + ret.append_utf8([string UTF8String]); + return ret; +} + +Ref DisplayServerMacOSBase::clipboard_get_image() const { + Ref image; + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + NSString *result = [pasteboard availableTypeFromArray:[NSArray arrayWithObjects:NSPasteboardTypeTIFF, NSPasteboardTypePNG, nil]]; + if (!result) { + return image; + } + NSData *data = [pasteboard dataForType:result]; + if (!data) { + return image; + } + NSBitmapImageRep *bitmap = [NSBitmapImageRep imageRepWithData:data]; + NSData *pngData = [bitmap representationUsingType:NSBitmapImageFileTypePNG properties:@{}]; + image.instantiate(); + PNGDriverCommon::png_to_image((const uint8_t *)pngData.bytes, pngData.length, false, image); + return image; +} + +bool DisplayServerMacOSBase::clipboard_has() const { + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + NSArray *classArray = [NSArray arrayWithObject:[NSString class]]; + NSDictionary *options = [NSDictionary dictionary]; + return [pasteboard canReadObjectForClasses:classArray options:options]; +} + +bool DisplayServerMacOSBase::clipboard_has_image() const { + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + NSString *result = [pasteboard availableTypeFromArray:[NSArray arrayWithObjects:NSPasteboardTypeTIFF, NSPasteboardTypePNG, nil]]; + return result; +} + +void DisplayServerMacOSBase::initialize_tts() const { + const_cast(this)->tts = [[TTS_MacOS alloc] init]; +} + +bool DisplayServerMacOSBase::tts_is_speaking() const { + if (unlikely(!tts)) { + initialize_tts(); + } + ERR_FAIL_NULL_V(tts, false); + return [tts isSpeaking]; +} + +bool DisplayServerMacOSBase::tts_is_paused() const { + if (unlikely(!tts)) { + initialize_tts(); + } + ERR_FAIL_NULL_V(tts, false); + return [tts isPaused]; +} + +TypedArray DisplayServerMacOSBase::tts_get_voices() const { + if (unlikely(!tts)) { + initialize_tts(); + } + ERR_FAIL_NULL_V(tts, TypedArray()); + return [tts getVoices]; +} + +void DisplayServerMacOSBase::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) { + if (unlikely(!tts)) { + initialize_tts(); + } + ERR_FAIL_NULL(tts); + [tts speak:p_text voice:p_voice volume:p_volume pitch:p_pitch rate:p_rate utterance_id:p_utterance_id interrupt:p_interrupt]; +} + +void DisplayServerMacOSBase::tts_pause() { + if (unlikely(!tts)) { + initialize_tts(); + } + ERR_FAIL_NULL(tts); + [tts pauseSpeaking]; +} + +void DisplayServerMacOSBase::tts_resume() { + if (unlikely(!tts)) { + initialize_tts(); + } + ERR_FAIL_NULL(tts); + [tts resumeSpeaking]; +} + +void DisplayServerMacOSBase::tts_stop() { + if (unlikely(!tts)) { + initialize_tts(); + } + ERR_FAIL_NULL(tts); + [tts stopSpeaking]; +} + +DisplayServerMacOSBase::DisplayServerMacOSBase() { + // Init TTS + print_line("init tts"); + bool tts_enabled = GLOBAL_GET("audio/general/text_to_speech"); + if (tts_enabled) { + initialize_tts(); + } +}