1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-04 12:00:25 +00:00

Merge pull request #107135 from bruvzg/emb_clipboard

[macOS] Fix clipboard and TTS not working in embedded game mode.
This commit is contained in:
Rémi Verschelde
2025-06-10 12:30:16 +02:00
6 changed files with 243 additions and 160 deletions

View File

@@ -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",

View File

@@ -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;

View File

@@ -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<KeyEvent> 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<String> &p_filters, const TypedArray<Dictionary> &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<Dictionary> 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<MouseButtonMask> mouse_get_button_state() const override;
virtual void clipboard_set(const String &p_text) override;
virtual String clipboard_get() const override;
virtual Ref<Image> 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;

View File

@@ -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<DisplayServerMacOS *>(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<Dictionary> DisplayServerMacOS::tts_get_voices() const {
if (unlikely(!tts)) {
initialize_tts();
}
ERR_FAIL_NULL_V(tts, TypedArray<Dictionary>());
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<MouseButtonMask> 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<Image> DisplayServerMacOS::clipboard_get_image() const {
Ref<Image> 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

View File

@@ -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 <AppKit/AppKit.h>
#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<Image> 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<Dictionary> 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();
};

View File

@@ -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<Image> DisplayServerMacOSBase::clipboard_get_image() const {
Ref<Image> 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<DisplayServerMacOSBase *>(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<Dictionary> DisplayServerMacOSBase::tts_get_voices() const {
if (unlikely(!tts)) {
initialize_tts();
}
ERR_FAIL_NULL_V(tts, TypedArray<Dictionary>());
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();
}
}