mirror of
https://github.com/libretro/RetroArch
synced 2025-01-29 18:32:44 +00:00
Added in accessibility.
This commit is contained in:
parent
203a80aeaa
commit
1e6e7a698f
@ -55,6 +55,7 @@ static enum action_iterate_type action_iterate_type(const char *label)
|
||||
return ITERATE_TYPE_DEFAULT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* menu_iterate:
|
||||
* @input : input sample for this frame
|
||||
@ -68,6 +69,8 @@ static enum action_iterate_type action_iterate_type(const char *label)
|
||||
**/
|
||||
int generic_menu_iterate(void *data, void *userdata, enum menu_action action)
|
||||
{
|
||||
static enum action_iterate_type last_iterate_type = ITERATE_TYPE_DEFAULT;
|
||||
|
||||
enum action_iterate_type iterate_type;
|
||||
unsigned file_type = 0;
|
||||
int ret = 0;
|
||||
@ -91,12 +94,17 @@ int generic_menu_iterate(void *data, void *userdata, enum menu_action action)
|
||||
{
|
||||
BIT64_SET(menu->state, MENU_STATE_RENDER_FRAMEBUFFER);
|
||||
}
|
||||
|
||||
switch (iterate_type)
|
||||
{
|
||||
case ITERATE_TYPE_HELP:
|
||||
ret = menu_dialog_iterate(
|
||||
menu->menu_state_msg, sizeof(menu->menu_state_msg), label);
|
||||
|
||||
if (iterate_type != last_iterate_type && is_accessibility_enabled())
|
||||
{
|
||||
accessibility_speak(menu->menu_state_msg);
|
||||
}
|
||||
|
||||
BIT64_SET(menu->state, MENU_STATE_RENDER_MESSAGEBOX);
|
||||
BIT64_SET(menu->state, MENU_STATE_POST_ITERATE);
|
||||
if (ret == 1 || action == MENU_ACTION_OK)
|
||||
@ -138,8 +146,24 @@ int generic_menu_iterate(void *data, void *userdata, enum menu_action action)
|
||||
: NULL;
|
||||
|
||||
if (cbs && cbs->enum_idx != MSG_UNKNOWN)
|
||||
{
|
||||
ret = menu_hash_get_help_enum(cbs->enum_idx,
|
||||
menu->menu_state_msg, sizeof(menu->menu_state_msg));
|
||||
if (iterate_type != last_iterate_type && is_accessibility_enabled())
|
||||
{
|
||||
if (strcmp(menu->menu_state_msg, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_INFORMATION_AVAILABLE))==0)
|
||||
{
|
||||
char current_sublabel[255];
|
||||
get_current_menu_sublabel(current_sublabel);
|
||||
if (strcmp(current_sublabel, "")==0)
|
||||
accessibility_speak(menu->menu_state_msg);
|
||||
else
|
||||
accessibility_speak(current_sublabel);
|
||||
}
|
||||
else
|
||||
accessibility_speak(menu->menu_state_msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned type = 0;
|
||||
@ -254,6 +278,12 @@ int generic_menu_iterate(void *data, void *userdata, enum menu_action action)
|
||||
break;
|
||||
}
|
||||
|
||||
if ((last_iterate_type == ITERATE_TYPE_HELP || last_iterate_type == ITERATE_TYPE_INFO) && last_iterate_type != iterate_type && is_accessibility_enabled())
|
||||
{
|
||||
accessibility_speak("Closed dialog.");
|
||||
}
|
||||
|
||||
last_iterate_type = iterate_type;
|
||||
BIT64_SET(menu->state, MENU_STATE_BLIT);
|
||||
|
||||
if (BIT64_GET(menu->state, MENU_STATE_POP_STACK))
|
||||
@ -383,5 +413,59 @@ int generic_menu_entry_action(
|
||||
}
|
||||
}
|
||||
|
||||
if (action != 0 && is_accessibility_enabled() && !is_input_keyboard_display_on())
|
||||
{
|
||||
char current_label[255];
|
||||
char current_value[255];
|
||||
char title_name[255];
|
||||
char speak_string[512];
|
||||
|
||||
strcpy(title_name, "");
|
||||
strcpy(current_label, "");
|
||||
get_current_menu_value(current_value);
|
||||
switch (action)
|
||||
{
|
||||
case MENU_ACTION_INFO:
|
||||
break;
|
||||
case MENU_ACTION_OK:
|
||||
case MENU_ACTION_LEFT:
|
||||
case MENU_ACTION_RIGHT:
|
||||
case MENU_ACTION_CANCEL:
|
||||
menu_entries_get_title(title_name, 255);
|
||||
case MENU_ACTION_UP:
|
||||
case MENU_ACTION_DOWN:
|
||||
case MENU_ACTION_SCROLL_UP:
|
||||
case MENU_ACTION_SCROLL_DOWN:
|
||||
get_current_menu_label(current_label);
|
||||
break;
|
||||
case MENU_ACTION_START:
|
||||
case MENU_ACTION_SELECT:
|
||||
case MENU_ACTION_SEARCH:
|
||||
get_current_menu_label(current_label);
|
||||
case MENU_ACTION_SCAN:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
strcpy(speak_string, "");
|
||||
if (strcmp(title_name, "") != 0)
|
||||
{
|
||||
strcpy(speak_string, title_name);
|
||||
strcat(speak_string, " ");
|
||||
}
|
||||
strcat(speak_string, current_label);
|
||||
if (strcmp(current_value, "...")!=0)
|
||||
{
|
||||
strcat(speak_string, " ");
|
||||
strcat(speak_string, current_value);
|
||||
}
|
||||
|
||||
if (strcmp(speak_string, "") != 0)
|
||||
{
|
||||
accessibility_speak(speak_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
@ -4114,7 +4114,7 @@ void menu_subsystem_populate(const struct retro_subsystem_info* subsystem, menu_
|
||||
RARCH_WARN("Menu subsystem entry: Description label truncated.\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
strlcpy(s, tmp, sizeof(s));
|
||||
}
|
||||
}
|
||||
@ -4128,3 +4128,43 @@ void menu_subsystem_populate(const struct retro_subsystem_info* subsystem, menu_
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void get_current_menu_value(char* retstr)
|
||||
{
|
||||
const char* entry_label;
|
||||
menu_entry_t entry;
|
||||
|
||||
menu_driver_selection_ptr = menu_navigation_get_selection();
|
||||
menu_entry_init(&entry);
|
||||
menu_entry_get(&entry, 0, menu_navigation_get_selection(), NULL, true);
|
||||
menu_entry_get_value(&entry, &entry_label);
|
||||
|
||||
strcpy(retstr, entry_label);
|
||||
}
|
||||
|
||||
void get_current_menu_label(char* retstr)
|
||||
{
|
||||
const char* entry_label;
|
||||
menu_entry_t entry;
|
||||
|
||||
menu_driver_selection_ptr = menu_navigation_get_selection();
|
||||
menu_entry_init(&entry);
|
||||
menu_entry_get(&entry, 0, menu_navigation_get_selection(), NULL, true);
|
||||
menu_entry_get_rich_label(&entry, &entry_label);
|
||||
|
||||
strcpy(retstr, entry_label);
|
||||
}
|
||||
|
||||
void get_current_menu_sublabel(char* retstr)
|
||||
{
|
||||
const char* entry_sublabel;
|
||||
menu_entry_t entry;
|
||||
|
||||
menu_driver_selection_ptr = menu_navigation_get_selection();
|
||||
menu_entry_init(&entry);
|
||||
menu_entry_get(&entry, 0, menu_navigation_get_selection(), NULL, true);
|
||||
|
||||
menu_entry_get_sublabel(&entry, &entry_sublabel);
|
||||
strcpy(retstr, entry_sublabel);
|
||||
}
|
||||
|
@ -281,6 +281,10 @@ int menu_entry_action(
|
||||
|
||||
void menu_entry_init(menu_entry_t *entry);
|
||||
|
||||
void get_current_menu_value(char* retstr);
|
||||
void get_current_menu_label(char* retstr);
|
||||
void get_current_menu_sublabel(char* retstr);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
|
@ -398,7 +398,6 @@ static int setting_generic_action_ok_linefeed(rarch_setting_t *setting, bool wra
|
||||
|
||||
if (!menu_input_dialog_start(&line))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -329,6 +329,10 @@ void menu_widgets_msg_queue_push(
|
||||
{
|
||||
menu_widget_msg_t* msg_widget = NULL;
|
||||
|
||||
if (is_accessibility_enabled())
|
||||
{
|
||||
accessibility_speak_priority((char*)msg, 0);
|
||||
}
|
||||
if (fifo_write_avail(msg_queue) > 0)
|
||||
{
|
||||
/* Get current msg if it exists */
|
||||
@ -2489,7 +2493,7 @@ void menu_widgets_set_libretro_message(const char *msg, unsigned duration)
|
||||
menu_timer_ctx_entry_t timer;
|
||||
|
||||
strlcpy(libretro_message, msg, LIBRETRO_MESSAGE_SIZE);
|
||||
|
||||
|
||||
libretro_message_alpha = DEFAULT_BACKDROP;
|
||||
|
||||
/* Kill and restart the timer / animation */
|
||||
|
71
msg_hash.c
71
msg_hash.c
@ -102,6 +102,77 @@ int menu_hash_get_help_enum(enum msg_hash_enums msg, char *s, size_t len)
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *get_user_language_iso639_1(bool limit)
|
||||
{
|
||||
char *voice;
|
||||
voice = "en";
|
||||
switch (uint_user_language)
|
||||
{
|
||||
case RETRO_LANGUAGE_FRENCH:
|
||||
voice = "fr";
|
||||
break;
|
||||
case RETRO_LANGUAGE_GERMAN:
|
||||
voice = "de";
|
||||
break;
|
||||
case RETRO_LANGUAGE_SPANISH:
|
||||
voice = "es";
|
||||
break;
|
||||
case RETRO_LANGUAGE_ITALIAN:
|
||||
voice = "it";
|
||||
break;
|
||||
case RETRO_LANGUAGE_PORTUGUESE_BRAZIL:
|
||||
if (limit)
|
||||
voice = "pt";
|
||||
else
|
||||
voice = "pt_br";
|
||||
break;
|
||||
case RETRO_LANGUAGE_PORTUGUESE_PORTUGAL:
|
||||
if (limit)
|
||||
voice = "pt";
|
||||
else
|
||||
voice = "pt_pt";
|
||||
break;
|
||||
case RETRO_LANGUAGE_DUTCH:
|
||||
voice = "nl";
|
||||
break;
|
||||
case RETRO_LANGUAGE_ESPERANTO:
|
||||
voice = "eo";
|
||||
break;
|
||||
case RETRO_LANGUAGE_POLISH:
|
||||
voice = "pl";
|
||||
break;
|
||||
case RETRO_LANGUAGE_JAPANESE:
|
||||
voice = "ja";
|
||||
break;
|
||||
case RETRO_LANGUAGE_KOREAN:
|
||||
voice = "ko";
|
||||
break;
|
||||
case RETRO_LANGUAGE_VIETNAMESE:
|
||||
voice = "vi";
|
||||
break;
|
||||
case RETRO_LANGUAGE_CHINESE_SIMPLIFIED:
|
||||
voice = "zh";
|
||||
break;
|
||||
case RETRO_LANGUAGE_CHINESE_TRADITIONAL:
|
||||
voice = "zh";
|
||||
break;
|
||||
case RETRO_LANGUAGE_ARABIC:
|
||||
voice = "ar";
|
||||
break;
|
||||
case RETRO_LANGUAGE_GREEK:
|
||||
voice = "el";
|
||||
break;
|
||||
case RETRO_LANGUAGE_TURKISH:
|
||||
voice = "tr";
|
||||
break;
|
||||
case RETRO_LANGUAGE_RUSSIAN:
|
||||
voice = "ru";
|
||||
break;
|
||||
|
||||
}
|
||||
return voice;
|
||||
}
|
||||
|
||||
const char *msg_hash_to_str(enum msg_hash_enums msg)
|
||||
{
|
||||
const char *ret = NULL;
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <boolean.h>
|
||||
#include <retro_common_api.h>
|
||||
|
||||
#include "input/input_defines.h"
|
||||
@ -2987,6 +2987,8 @@ void msg_hash_set_uint(enum msg_hash_action type, unsigned val);
|
||||
|
||||
uint32_t msg_hash_calculate(const char *s);
|
||||
|
||||
const char *get_user_language_iso639_1(bool limit);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
|
610
retroarch.c
610
retroarch.c
@ -37,6 +37,7 @@
|
||||
|
||||
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
||||
#include <objbase.h>
|
||||
#include <process.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
@ -808,7 +809,8 @@ enum
|
||||
RA_OPT_MAX_FRAMES,
|
||||
RA_OPT_MAX_FRAMES_SCREENSHOT,
|
||||
RA_OPT_MAX_FRAMES_SCREENSHOT_PATH,
|
||||
RA_OPT_SET_SHADER
|
||||
RA_OPT_SET_SHADER,
|
||||
RA_OPT_ACCESSIBILITY,
|
||||
};
|
||||
|
||||
enum runloop_state
|
||||
@ -2309,6 +2311,10 @@ static bool menu_driver_is_binding = false;
|
||||
* it will be closed; if the menu was not running, it will be opened */
|
||||
static bool menu_driver_toggled = false;
|
||||
|
||||
/* Is text-to-speech accessibility turned on? */
|
||||
static bool accessibility_enabled = false;
|
||||
|
||||
|
||||
#ifdef HAVE_LIBNX
|
||||
#define LIBNX_SWKBD_LIMIT 500 /* enforced by HOS */
|
||||
extern u32 __nx_applet_type;
|
||||
@ -2405,6 +2411,11 @@ bool menu_input_dialog_start_search(void)
|
||||
sizeof(menu_input_dialog_keyboard_label));
|
||||
|
||||
input_keyboard_ctl(RARCH_INPUT_KEYBOARD_CTL_LINE_FREE, NULL);
|
||||
|
||||
if (is_accessibility_enabled())
|
||||
{
|
||||
accessibility_speak((char*) msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SEARCH));
|
||||
}
|
||||
|
||||
menu_input_dialog_keyboard_buffer =
|
||||
input_keyboard_start_line(menu, menu_input_search_cb);
|
||||
@ -2434,6 +2445,8 @@ bool menu_input_dialog_start(menu_input_ctx_line_t *line)
|
||||
|
||||
input_keyboard_ctl(RARCH_INPUT_KEYBOARD_CTL_LINE_FREE, NULL);
|
||||
|
||||
accessibility_speak("Keyboard input:");
|
||||
|
||||
menu_input_dialog_keyboard_buffer =
|
||||
input_keyboard_start_line(menu, line->cb);
|
||||
|
||||
@ -2475,7 +2488,6 @@ bool menu_input_dialog_get_display_kb(void)
|
||||
/* In case a previous "Enter" press closed the keyboard */
|
||||
if (!menu_input_dialog_keyboard_display)
|
||||
break;
|
||||
|
||||
if (buf[i] == '\n' || buf[i] == '\0')
|
||||
input_keyboard_event(true, '\n', '\n', 0, RETRO_DEVICE_KEYBOARD);
|
||||
else
|
||||
@ -4071,6 +4083,7 @@ static void handle_translation_cb(
|
||||
int start = -1;
|
||||
char* found_string = NULL;
|
||||
char* error_string = NULL;
|
||||
char* text_string = NULL;
|
||||
int curr_state = 0;
|
||||
|
||||
RARCH_LOG("RESULT FROM AI SERVICE...\n");
|
||||
@ -4098,7 +4111,6 @@ static void handle_translation_cb(
|
||||
{
|
||||
found_string = (char*)malloc(i-start+1);
|
||||
strlcpy(found_string, body_copy+start+1, i-start);
|
||||
|
||||
if (curr_state == 1)/*image*/
|
||||
{
|
||||
raw_image_file_data = (char*)unbase64(found_string,
|
||||
@ -4115,6 +4127,12 @@ static void handle_translation_cb(
|
||||
}
|
||||
#endif
|
||||
else if (curr_state == 3)
|
||||
{
|
||||
text_string = (char*)malloc(i-start+1);
|
||||
strlcpy(text_string, body_copy+start+1, i-start);
|
||||
curr_state = 0;
|
||||
}
|
||||
else if (curr_state == 4)
|
||||
{
|
||||
error_string = (char*)malloc(i-start+1);
|
||||
strlcpy(error_string, body_copy+start+1, i-start);
|
||||
@ -4130,11 +4148,16 @@ static void handle_translation_cb(
|
||||
curr_state = 2;
|
||||
free(found_string);
|
||||
}
|
||||
else if (string_is_equal(found_string, "error"))
|
||||
else if (string_is_equal(found_string, "text"))
|
||||
{
|
||||
curr_state = 3;
|
||||
free(found_string);
|
||||
}
|
||||
else if (string_is_equal(found_string, "error"))
|
||||
{
|
||||
curr_state = 4;
|
||||
free(found_string);
|
||||
}
|
||||
else
|
||||
{
|
||||
curr_state = 0;
|
||||
@ -4149,6 +4172,7 @@ static void handle_translation_cb(
|
||||
if (string_is_equal(error_string, "No text found."))
|
||||
{
|
||||
RARCH_LOG("No text found...\n");
|
||||
strcpy(text_string, error_string);
|
||||
#ifdef HAVE_MENU_WIDGETS
|
||||
if (menu_widgets_paused)
|
||||
{
|
||||
@ -4159,7 +4183,7 @@ static void handle_translation_cb(
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!raw_image_file_data && !raw_sound_data)
|
||||
if (!raw_image_file_data && !raw_sound_data && !text_string)
|
||||
{
|
||||
error = "Invalid JSON body.";
|
||||
goto finish;
|
||||
@ -4372,7 +4396,7 @@ static void handle_translation_cb(
|
||||
return;
|
||||
|
||||
params.volume = 1.0f;
|
||||
params.slot_selection_type = AUDIO_MIXER_SLOT_SELECTION_AUTOMATIC; /* user->slot_selection_type; */
|
||||
params.slot_selection_type = AUDIO_MIXER_SLOT_SELECTION_MANUAL; /* user->slot_selection_type; */
|
||||
params.slot_selection_idx = 10;
|
||||
params.stream_type = AUDIO_STREAM_TYPE_SYSTEM; /* user->stream_type; */
|
||||
params.type = AUDIO_MIXER_TYPE_WAV;
|
||||
@ -4392,6 +4416,11 @@ static void handle_translation_cb(
|
||||
}
|
||||
#endif
|
||||
|
||||
if (text_string && is_accessibility_enabled())
|
||||
{
|
||||
accessibility_speak(text_string);
|
||||
}
|
||||
|
||||
finish:
|
||||
if (error)
|
||||
RARCH_ERR("%s: %s\n", msg_hash_to_str(MSG_DOWNLOAD_FAILED), error);
|
||||
@ -4417,10 +4446,28 @@ finish:
|
||||
free(scaler);
|
||||
if (error_string)
|
||||
free(error_string);
|
||||
if (text_string)
|
||||
free(text_string);
|
||||
if (raw_output_data)
|
||||
free(raw_output_data);
|
||||
}
|
||||
|
||||
bool is_ai_service_speech_running()
|
||||
{
|
||||
enum audio_mixer_state res = audio_driver_mixer_get_stream_state(10);
|
||||
RARCH_LOG("TTT %d\n", res);
|
||||
if (res == AUDIO_STREAM_STATE_NONE || res == AUDIO_STREAM_STATE_STOPPED)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ai_service_speech_stop()
|
||||
{
|
||||
audio_driver_mixer_stop_stream(10);
|
||||
audio_driver_mixer_remove_stream(10);
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char *ai_service_get_str(enum translation_lang id)
|
||||
{
|
||||
switch (id)
|
||||
@ -4880,6 +4927,8 @@ static bool run_translation_service(void)
|
||||
else if (settings->uints.ai_service_mode == 1)
|
||||
mode_chr = "sound,wav";
|
||||
else if (settings->uints.ai_service_mode == 2)
|
||||
mode_chr = "text";
|
||||
else if (settings->uints.ai_service_mode == 3)
|
||||
{
|
||||
if (use_overlay)
|
||||
mode_chr = "image,png,png-a,sound,wav";
|
||||
@ -4887,6 +4936,7 @@ static bool run_translation_service(void)
|
||||
mode_chr = "image,png,sound,wav";
|
||||
}
|
||||
|
||||
|
||||
snprintf(temp_string,
|
||||
sizeof(temp_string),
|
||||
"%coutput=%s", separator, mode_chr);
|
||||
@ -6266,6 +6316,8 @@ bool command_event(enum event_command cmd, void *data)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (is_accessibility_enabled())
|
||||
accessibility_speak((char*) msg_hash_to_str(MSG_UNPAUSED));
|
||||
command_event(CMD_EVENT_UNPAUSE, NULL);
|
||||
}
|
||||
}
|
||||
@ -6952,6 +7004,14 @@ TODO: Add a setting for these tweaks */
|
||||
case CMD_EVENT_PAUSE_TOGGLE:
|
||||
boolean = runloop_paused;
|
||||
boolean = !boolean;
|
||||
if (is_accessibility_enabled())
|
||||
{
|
||||
if (boolean)
|
||||
accessibility_speak((char*) msg_hash_to_str(MSG_PAUSED));
|
||||
else
|
||||
accessibility_speak((char*) msg_hash_to_str(MSG_UNPAUSED));
|
||||
}
|
||||
|
||||
runloop_paused = boolean;
|
||||
retroarch_pause_checks();
|
||||
break;
|
||||
@ -7404,11 +7464,28 @@ TODO: Add a setting for these tweaks */
|
||||
break;
|
||||
|
||||
case CMD_EVENT_AI_SERVICE_CALL:
|
||||
{
|
||||
#ifdef HAVE_TRANSLATE
|
||||
settings_t *settings = configuration_settings;
|
||||
if (settings->uints.ai_service_mode == 1 && is_ai_service_speech_running())
|
||||
{
|
||||
ai_service_speech_stop();
|
||||
if (is_accessibility_enabled())
|
||||
accessibility_speak("stopped.");
|
||||
}
|
||||
else if (is_accessibility_enabled() && settings->uints.ai_service_mode == 2 &&
|
||||
is_narrator_running() == true)
|
||||
{
|
||||
accessibility_speak("stopped.");
|
||||
}
|
||||
else
|
||||
{
|
||||
RARCH_LOG("AI Service Called...\n");
|
||||
run_translation_service();
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case CMD_EVENT_NONE:
|
||||
return false;
|
||||
}
|
||||
@ -16365,6 +16442,90 @@ void input_keyboard_event(bool down, unsigned code,
|
||||
uint32_t character, uint16_t mod, unsigned device)
|
||||
{
|
||||
static bool deferred_wait_keys;
|
||||
if (menu_input_dialog_keyboard_display && down && is_accessibility_enabled())
|
||||
{
|
||||
if (code != 303 && code != 0)
|
||||
{
|
||||
char* say_char = malloc(sizeof(char));
|
||||
if (say_char != NULL)
|
||||
{
|
||||
char c = (char) character;
|
||||
*say_char = c;
|
||||
|
||||
if (character == 127)
|
||||
accessibility_speak("backspace");
|
||||
else if (c == '`')
|
||||
accessibility_speak("left quote");
|
||||
else if (c == '`')
|
||||
accessibility_speak("tilde");
|
||||
else if (c == '!')
|
||||
accessibility_speak("exclamation point");
|
||||
else if (c == '@')
|
||||
accessibility_speak("at sign");
|
||||
else if (c == '#')
|
||||
accessibility_speak("hash sign");
|
||||
else if (c == '$')
|
||||
accessibility_speak("dollar sign");
|
||||
else if (c == '%')
|
||||
accessibility_speak("percent sign");
|
||||
else if (c == '^')
|
||||
accessibility_speak("carrot");
|
||||
else if (c == '&')
|
||||
accessibility_speak("ampersand");
|
||||
else if (c == '*')
|
||||
accessibility_speak("asterisk");
|
||||
else if (c == '(')
|
||||
accessibility_speak("left bracket");
|
||||
else if (c == ')')
|
||||
accessibility_speak("right bracket");
|
||||
else if (c == '-')
|
||||
accessibility_speak("minus");
|
||||
else if (c == '_')
|
||||
accessibility_speak("underscore");
|
||||
else if (c == '=')
|
||||
accessibility_speak("equals");
|
||||
else if (c == '+')
|
||||
accessibility_speak("plus");
|
||||
else if (c == '[')
|
||||
accessibility_speak("left square bracket");
|
||||
else if (c == '{')
|
||||
accessibility_speak("left curl bracket");
|
||||
else if (c == ']')
|
||||
accessibility_speak("right square bracket");
|
||||
else if (c == '}')
|
||||
accessibility_speak("right curl bracket");
|
||||
else if (c == '\\')
|
||||
accessibility_speak("back slash");
|
||||
else if (c == '|')
|
||||
accessibility_speak("pipe");
|
||||
else if (c == ';')
|
||||
accessibility_speak("semicolon");
|
||||
else if (c == ':')
|
||||
accessibility_speak("colon");
|
||||
else if (c == '\'')
|
||||
accessibility_speak("single quote");
|
||||
else if (c == '\"')
|
||||
accessibility_speak("double quote");
|
||||
else if (c == ',')
|
||||
accessibility_speak("comma");
|
||||
else if (c == '<')
|
||||
accessibility_speak("left angle bracket");
|
||||
else if (c == '.')
|
||||
accessibility_speak("period");
|
||||
else if (c == '>')
|
||||
accessibility_speak("right angle bracket");
|
||||
else if (c == '/')
|
||||
accessibility_speak("front slash");
|
||||
else if (c == '?')
|
||||
accessibility_speak("question mark");
|
||||
else if (c == ' ')
|
||||
accessibility_speak("space");
|
||||
else if (character != 0)
|
||||
accessibility_speak(say_char);
|
||||
free(say_char);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deferred_wait_keys)
|
||||
{
|
||||
@ -23850,7 +24011,7 @@ static void retroarch_print_help(const char *arg0)
|
||||
printf("Usage: %s [OPTIONS]... [FILE]\n", arg0);
|
||||
|
||||
{
|
||||
char buf[2048];
|
||||
char buf[2148];
|
||||
buf[0] = '\0';
|
||||
|
||||
strlcpy(buf, " -h, --help Show this help message.\n", sizeof(buf));
|
||||
@ -23927,7 +24088,7 @@ static void retroarch_print_help(const char *arg0)
|
||||
"the device (1 to %d).\n", MAX_USERS);
|
||||
|
||||
{
|
||||
char buf[2048];
|
||||
char buf[2148];
|
||||
buf[0] = '\0';
|
||||
strlcpy(buf, " Format is PORT:ID, where ID is a number "
|
||||
"corresponding to the particular device.\n", sizeof(buf));
|
||||
@ -23983,6 +24144,8 @@ static void retroarch_print_help(const char *arg0)
|
||||
" Path to save the screenshot to at the end of max-frames.\n", sizeof(buf));
|
||||
puts(buf);
|
||||
}
|
||||
printf(" --accessibility\n"
|
||||
" Enables accessibilty for blind users using text-to-speech.\n");
|
||||
}
|
||||
|
||||
#define FFMPEG_RECORD_ARG "r:"
|
||||
@ -24064,6 +24227,7 @@ static void retroarch_parse_input_and_config(int argc, char *argv[])
|
||||
{ "eof-exit", 0, NULL, RA_OPT_EOF_EXIT },
|
||||
{ "version", 0, NULL, RA_OPT_VERSION },
|
||||
{ "log-file", 1, NULL, RA_OPT_LOG_FILE },
|
||||
{ "accessibility", 0, NULL, RA_OPT_ACCESSIBILITY},
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
@ -24578,7 +24742,9 @@ static void retroarch_parse_input_and_config(int argc, char *argv[])
|
||||
case '?':
|
||||
retroarch_print_help(argv[0]);
|
||||
retroarch_fail(1, "retroarch_parse_input()");
|
||||
|
||||
case RA_OPT_ACCESSIBILITY:
|
||||
accessibility_enabled = true;
|
||||
break;
|
||||
default:
|
||||
RARCH_ERR("%s\n", msg_hash_to_str(MSG_ERROR_PARSING_ARGUMENTS));
|
||||
retroarch_fail(1, "retroarch_parse_input()");
|
||||
@ -24777,6 +24943,9 @@ bool retroarch_main_init(int argc, char *argv[])
|
||||
|
||||
retroarch_parse_input_and_config(argc, argv);
|
||||
|
||||
if (is_accessibility_enabled())
|
||||
accessibility_startup_message();
|
||||
|
||||
if (verbosity_is_enabled())
|
||||
{
|
||||
{
|
||||
@ -26083,7 +26252,8 @@ void runloop_msg_queue_push(const char *msg,
|
||||
enum message_queue_category category)
|
||||
{
|
||||
runloop_msg_queue_lock();
|
||||
|
||||
if (is_accessibility_enabled())
|
||||
accessibility_speak_priority((char*) msg, 0);
|
||||
#if defined(HAVE_MENU) && defined(HAVE_MENU_WIDGETS)
|
||||
if (menu_widgets_inited)
|
||||
{
|
||||
@ -26608,7 +26778,6 @@ static enum runloop_state runloop_check_state(void)
|
||||
|
||||
bits_clear_bits(trigger_input.data, old_input.data,
|
||||
ARRAY_SIZE(trigger_input.data));
|
||||
|
||||
action = (enum menu_action)menu_event(¤t_bits, &trigger_input, display_kb);
|
||||
focused = pause_nonactive ? is_focused : true;
|
||||
focused = focused && !main_ui_companion_is_on_foreground;
|
||||
@ -28778,3 +28947,422 @@ unsigned int retroarch_get_rotation(void)
|
||||
settings_t *settings = configuration_settings;
|
||||
return settings->uints.video_rotation + runloop_system.rotation;
|
||||
}
|
||||
|
||||
|
||||
/* Accessibility */
|
||||
int speak_pid = 0;
|
||||
|
||||
bool is_accessibility_enabled()
|
||||
{
|
||||
return accessibility_enabled;
|
||||
}
|
||||
|
||||
bool is_input_keyboard_display_on()
|
||||
{
|
||||
return menu_input_dialog_keyboard_display;
|
||||
}
|
||||
|
||||
bool accessibility_speak(char* speak_text)
|
||||
{
|
||||
return accessibility_speak_priority(speak_text, 10);
|
||||
}
|
||||
|
||||
bool accessibility_speak_priority(char* speak_text, int priority)
|
||||
{
|
||||
RARCH_LOG("Spoke: %s\n", speak_text);
|
||||
const char* voice = NULL;
|
||||
if (accessibility_enabled)
|
||||
{
|
||||
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
||||
voice = get_user_language_iso639_1(true);
|
||||
return accessibility_speak_windows(speak_text, voice, priority);
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
voice = get_user_language_iso639_1(false);
|
||||
return accessibility_speak_macos(speak_text, voice, priority);
|
||||
#elif defined(__linux__) || defined(__unix__)
|
||||
voice = get_user_language_iso639_1(true);
|
||||
return accessibility_speak_linux(speak_text, voice, priority);
|
||||
#endif
|
||||
|
||||
if (1==0)
|
||||
{
|
||||
return accessibility_speak_ai_service(speak_text, voice, priority);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool is_narrator_running()
|
||||
{
|
||||
if (accessibility_enabled)
|
||||
{
|
||||
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
||||
return is_narrator_running_windows();
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
return is_narrator_running_macos();
|
||||
#elif defined(__linux__) || defined(__unix__)
|
||||
return is_narrator_running_linux();
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
||||
PROCESS_INFORMATION pi;
|
||||
bool pi_set = false;
|
||||
|
||||
bool terminate_win32_process(PROCESS_INFORMATION pi)
|
||||
{
|
||||
TerminateProcess(pi.hProcess,0);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CreateWin32Process(char* cmd)
|
||||
{
|
||||
STARTUPINFO si;
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
|
||||
if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW,
|
||||
NULL, NULL, &si, &pi))
|
||||
{
|
||||
pi_set = false;
|
||||
return false;
|
||||
}
|
||||
pi_set = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
char* accessibility_win_language_code(const char* language)
|
||||
{
|
||||
if (strcmp(language,"en") == 0)
|
||||
return "Microsoft David Desktop";
|
||||
else if (strcmp(language,"it") == 0)
|
||||
return "Microsoft Cosimo Desktop";
|
||||
else if (strcmp(language,"sv") == 0)
|
||||
return "Microsoft Bengt Desktop";
|
||||
else if (strcmp(language,"fr") == 0)
|
||||
return "Microsoft Paul Desktop";
|
||||
else if (strcmp(language,"de") == 0)
|
||||
return "Microsoft Stefan Desktop";
|
||||
else if (strcmp(language,"he") == 0)
|
||||
return "Microsoft Hemant Desktop";
|
||||
else if (strcmp(language,"id") == 0)
|
||||
return "Microsoft Asaf Desktop";
|
||||
else if (strcmp(language,"es") == 0)
|
||||
return "Microsoft Pablo Desktop";
|
||||
else if (strcmp(language,"nl") == 0)
|
||||
return "Microsoft Frank Desktop";
|
||||
else if (strcmp(language,"ro") == 0)
|
||||
return "Microsoft Andrei Desktop";
|
||||
else if (strcmp(language,"pt_pt") == 0)
|
||||
return "Microsoft Helia Desktop";
|
||||
else if (strcmp(language,"pt_bt") == 0 || strcmp(language,"pt") == 0)
|
||||
return "Microsoft Daniel Desktop";
|
||||
else if (strcmp(language,"th") == 0)
|
||||
return "Microsoft Pattara Desktop";
|
||||
else if (strcmp(language,"ja") == 0)
|
||||
return "Microsoft Ichiro Desktop";
|
||||
else if (strcmp(language,"sk") == 0)
|
||||
return "Microsoft Filip Desktop";
|
||||
else if (strcmp(language,"hi") == 0)
|
||||
return "Microsoft Hemant Desktop";
|
||||
else if (strcmp(language,"ar") == 0)
|
||||
return "Microsoft Naayf Desktop";
|
||||
else if (strcmp(language,"hu") == 0)
|
||||
return "Microsoft Szabolcs Desktop";
|
||||
else if (strcmp(language,"zh_tw") == 0 || strcmp(language,"zh")==0)
|
||||
return "Microsoft Zhiwei Desktop";
|
||||
else if (strcmp(language,"el") == 0)
|
||||
return "Microsoft Stefanos Desktop";
|
||||
else if (strcmp(language,"ru") == 0)
|
||||
return "Microsoft Pavel Desktop";
|
||||
else if (strcmp(language,"nb") == 0)
|
||||
return "Microsoft Jon Desktop";
|
||||
else if (strcmp(language,"da") == 0)
|
||||
return "Microsoft Helle Desktop";
|
||||
else if (strcmp(language,"fi") == 0)
|
||||
return "Microsoft Heidi Desktop";
|
||||
else if (strcmp(language,"zh_hk") == 0)
|
||||
return "Microsoft Danny Desktop";
|
||||
else if (strcmp(language,"zh_cn") == 0)
|
||||
return "Microsoft Kangkang Desktop";
|
||||
else if (strcmp(language,"tr") == 0)
|
||||
return "Microsoft Tolga Desktop";
|
||||
else if (strcmp(language,"ko") == 0)
|
||||
return "Microsoft Heami Desktop";
|
||||
else if (strcmp(language,"pl") == 0)
|
||||
return "Microsoft Adam Desktop";
|
||||
else if (strcmp(language,"cs") == 0)
|
||||
return "Microsoft Jakub Desktop";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
bool is_narrator_running_windows()
|
||||
{
|
||||
DWORD status = 0;
|
||||
bool res;
|
||||
if (pi_set == false)
|
||||
return false;
|
||||
res = GetExitCodeProcess(&pi, &status);
|
||||
if (res == true && status == STILL_ACTIVE)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool accessibility_speak_windows(char* speak_text, const char* voice, int priority)
|
||||
{
|
||||
char cmd[1200];
|
||||
char* language = accessibility_win_language_code(voice);
|
||||
bool res;
|
||||
if (priority < 10)
|
||||
{
|
||||
if (is_narrator_running_windows())
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strlen(language) > 0)
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"powershell.exe -NoProfile -WindowStyle Hidden -Command \"Add-Type -AssemblyName System.Speech; $synth = New-Object System.Speech.Synthesis.SpeechSynthesizer; $synth.SelectVoice(\\\"%s\\\"); $synth.Speak(\\\"%s\\\");\"", language, speak_text);
|
||||
else
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"powershell.exe -NoProfile -WindowStyle Hidden -Command \"Add-Type -AssemblyName System.Speech; $synth = New-Object System.Speech.Synthesis.SpeechSynthesizer; $synth.Speak(\\\"%s\\\");\"", speak_text);
|
||||
if (pi_set)
|
||||
{
|
||||
terminate_win32_process(pi);
|
||||
}
|
||||
res = CreateWin32Process(cmd);
|
||||
if (!res)
|
||||
{
|
||||
RARCH_LOG("Create subprocess failed. Error: %d\n", GetLastError());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
char* accessibility_mac_language_code(const char* language)
|
||||
{
|
||||
if (strcmp(language,"en") == 0)
|
||||
return "Alex";
|
||||
else if (strcmp(language,"it") == 0)
|
||||
return "Alice";
|
||||
else if (strcmp(language,"sv") == 0)
|
||||
return "Alva";
|
||||
else if (strcmp(language,"fr") == 0)
|
||||
return "Amelie";
|
||||
else if (strcmp(language,"de") == 0)
|
||||
return "Anna";
|
||||
else if (strcmp(language,"he") == 0)
|
||||
return "Carmit";
|
||||
else if (strcmp(language,"id") == 0)
|
||||
return "Damayanti";
|
||||
else if (strcmp(language,"es") == 0)
|
||||
return "Diego";
|
||||
else if (strcmp(language,"nl") == 0)
|
||||
return "Ellen";
|
||||
else if (strcmp(language,"ro") == 0)
|
||||
return "Ioana";
|
||||
else if (strcmp(language,"pt_pt") == 0)
|
||||
return "Joana";
|
||||
else if (strcmp(language,"pt_bt") == 0 || strcmp(language,"pt") == 0)
|
||||
return "Luciana";
|
||||
else if (strcmp(language,"th") == 0)
|
||||
return "Kanya";
|
||||
else if (strcmp(language,"ja") == 0)
|
||||
return "Kyoko";
|
||||
else if (strcmp(language,"sk") == 0)
|
||||
return "Laura";
|
||||
else if (strcmp(language,"hi") == 0)
|
||||
return "Lekha";
|
||||
else if (strcmp(language,"ar") == 0)
|
||||
return "Maged";
|
||||
else if (strcmp(language,"hu") == 0)
|
||||
return "Mariska";
|
||||
else if (strcmp(language,"zh_tw") == 0 || strcmp(language,"zh")==0)
|
||||
return "Mei-Jia";
|
||||
else if (strcmp(language,"el") == 0)
|
||||
return "Melina";
|
||||
else if (strcmp(language,"ru") == 0)
|
||||
return "Milena";
|
||||
else if (strcmp(language,"nb") == 0)
|
||||
return "Nora";
|
||||
else if (strcmp(language,"da") == 0)
|
||||
return "Sara";
|
||||
else if (strcmp(language,"fi") == 0)
|
||||
return "Satu";
|
||||
else if (strcmp(language,"zh_hk") == 0)
|
||||
return "Sin-ji";
|
||||
else if (strcmp(language,"zh_cn") == 0)
|
||||
return "Ting-Ting";
|
||||
else if (strcmp(language,"tr") == 0)
|
||||
return "Yelda";
|
||||
else if (strcmp(language,"ko") == 0)
|
||||
return "Yuna";
|
||||
else if (strcmp(language,"pl") == 0)
|
||||
return "Zosia";
|
||||
else if (strcmp(language,"cs") == 0)
|
||||
return "Zuzana";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
bool is_narrator_running_macos()
|
||||
{
|
||||
if (kill(speak_pid, 0) == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool accessibility_speak_macos(char* speak_text, const char* voice, int priority)
|
||||
{
|
||||
int pid;
|
||||
char* language_speaker = accessibility_mac_language_code(voice);
|
||||
|
||||
if (priority < 10 && speak_pid > 0)
|
||||
{
|
||||
/* check if old pid is running */
|
||||
if (is_narrator_running_macos())
|
||||
return true;
|
||||
}
|
||||
|
||||
if (speak_pid > 0)
|
||||
{
|
||||
/* Kill the running espeak */
|
||||
kill(speak_pid, SIGTERM);
|
||||
speak_pid = 0;
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0)
|
||||
{
|
||||
/* error */
|
||||
RARCH_LOG("ERROR: could not fork for say command.\n");
|
||||
}
|
||||
else if (pid > 0)
|
||||
{
|
||||
/* parent process */
|
||||
speak_pid = pid;
|
||||
|
||||
/* Tell the system that we'll ignore the exit status of the child
|
||||
* process. This prevents zombie processes. */
|
||||
signal(SIGCHLD,SIG_IGN);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* child process: replace process with the espeak command */
|
||||
if (strlen(language_speaker)> 0)
|
||||
execvp("say", (char* []) {"say", "-v", language_speaker,
|
||||
speak_text, NULL});
|
||||
else
|
||||
execvp("say", (char* []) {"say", speak_text, NULL});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(__linux__) || defined(__unix__)
|
||||
bool is_narrator_running_linux()
|
||||
{
|
||||
if (kill(speak_pid, 0) == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool accessibility_speak_linux(char* speak_text, const char* language, int priority)
|
||||
{
|
||||
int pid;
|
||||
char* voice_out = malloc(3+strlen(language));
|
||||
strcpy(voice_out, "-v");
|
||||
strcat(voice_out, language);
|
||||
if (priority < 10 && speak_pid > 0)
|
||||
{
|
||||
/* check if old pid is running */
|
||||
if (is_narrator_running_linux())
|
||||
return true;
|
||||
}
|
||||
|
||||
if (speak_pid > 0)
|
||||
{
|
||||
/* Kill the running espeak */
|
||||
kill(speak_pid, SIGTERM);
|
||||
speak_pid = 0;
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0)
|
||||
{
|
||||
/* error */
|
||||
RARCH_LOG("ERROR: could not fork for espeak.\n");
|
||||
}
|
||||
else if (pid > 0)
|
||||
{
|
||||
/* parent process */
|
||||
speak_pid = pid;
|
||||
|
||||
/* Tell the system that we'll ignore the exit status of the child
|
||||
* process. This prevents zombie processes. */
|
||||
signal(SIGCHLD,SIG_IGN);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* child process: replace process with the espeak command */
|
||||
execvp("espeak", (char* []) {"espeak", voice_out, speak_text, NULL});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool accessibility_speak_ai_service(char* speak_text, const char* language, int priority)
|
||||
{
|
||||
/* Call the ai service listed to do espeak for us. */
|
||||
/* NOTE: This call works, but the audio mixer will not
|
||||
* play sound files while the core is paused, so it's
|
||||
* not practical at the moment. */
|
||||
|
||||
char new_ai_service_url[PATH_MAX_LENGTH];
|
||||
char temp_string[PATH_MAX_LENGTH];
|
||||
char json_buffer[2048];
|
||||
char separator = '?';
|
||||
settings_t *settings = configuration_settings;
|
||||
|
||||
strlcpy(new_ai_service_url, settings->arrays.ai_service_url,
|
||||
sizeof(new_ai_service_url));
|
||||
|
||||
if (strrchr(new_ai_service_url, '?') != NULL)
|
||||
separator = '&';
|
||||
snprintf(temp_string, sizeof(temp_string),
|
||||
"%csource_lang=%s&target_lang=%s&output=espeak",
|
||||
separator, language, language);
|
||||
strlcat(new_ai_service_url, temp_string, sizeof(new_ai_service_url));
|
||||
|
||||
strlcpy(temp_string, speak_text, sizeof(temp_string));
|
||||
for (int i=0;i<strlen(temp_string);i++)
|
||||
{
|
||||
if (temp_string[i]=='\"')
|
||||
temp_string[i] = ' ';
|
||||
}
|
||||
snprintf(json_buffer, sizeof(json_buffer),
|
||||
"{\"text\": \"%s\"}", speak_text);
|
||||
RARCH_LOG("SENDING accessibilty request... %s\n", new_ai_service_url);
|
||||
task_push_http_post_transfer(new_ai_service_url,
|
||||
json_buffer, true, NULL, handle_translation_cb, NULL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool accessibility_startup_message()
|
||||
{
|
||||
/* Note: for the ai service tts call, this is called too early... */
|
||||
accessibility_speak("RetroArch accessibility on.");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
21
retroarch.h
21
retroarch.h
@ -2022,4 +2022,25 @@ unsigned int retroarch_get_rotation(void);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
bool is_input_keyboard_display_on();
|
||||
bool is_accessibility_enabled();
|
||||
bool accessibility_speak(char* speak_text);
|
||||
bool accessibility_speak_priority(char* speak_text, int priority);
|
||||
bool accessibility_startup_message();
|
||||
|
||||
bool is_narrator_running();
|
||||
|
||||
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
||||
bool is_narrator_running_windows();
|
||||
bool accessibility_speak_windows(char* speak_text, const char* voice, int priority);
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
bool is_narrator_running_macos();
|
||||
char* accessibility_mac_language_code(const char* language);
|
||||
bool accessibility_speak_macos(char* speak_text, const char* voice, int priority);
|
||||
#elif defined(__linux__) || defined(__unix__)
|
||||
bool is_narrator_running_linux();
|
||||
bool accessibility_speak_linux(char* speak_text, const char* voice, int priority);
|
||||
#endif
|
||||
bool accessibility_speak_ai_service(char* speak_text, const char* voice, int priority);
|
||||
|
||||
#endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user