You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-04 12:00:25 +00:00
[macOS] Fix clipboard and TTS not working in embedded game mode.
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
69
platform/macos/display_server_macos_base.h
Normal file
69
platform/macos/display_server_macos_base.h
Normal 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();
|
||||
};
|
||||
167
platform/macos/display_server_macos_base.mm
Normal file
167
platform/macos/display_server_macos_base.mm
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user