diff --git a/Build/Makefile b/Build/Makefile index 9a49d8d6da..28cbacda76 100644 --- a/Build/Makefile +++ b/Build/Makefile @@ -69,7 +69,7 @@ ubuntu: -DTOOLCHAIN_OPENSSL=OFF" generic ubuntu22: - $(MAKE) UBUNTUDIR=$@ ubuntu + $(MAKE) UBUNTUDIR=$@ CMAKE_EXTRA+="-DBUILD_TEAMTALK_CLIENT_PRISM=OFF" ubuntu ubuntu24: $(MAKE) UBUNTUDIR=$@ ubuntu @@ -198,8 +198,10 @@ depend-ubuntu24: g++ \ junit4 \ libasound2-dev \ + libglib2.0-dev \ libpcap-dev \ libpulse-dev \ + libspeechd-dev \ libssl-dev \ libtool \ libxss-dev \ @@ -243,8 +245,10 @@ depend-raspios12: git \ junit4 \ libasound2-dev \ + libglib2.0-dev \ libpcap-dev \ libpulse-dev \ + libspeechd-dev \ libssl-dev \ libtool \ libxss-dev \ diff --git a/ChangeLog.txt b/ChangeLog.txt index 516fadac77..ef5b6589c4 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -4,7 +4,10 @@ Version 5.22.1, unreleased Default Qt Client -- +- Replaced Tolk with Prism for screen reader support +- Screen reader backend can now be selected in Preferences (e.g. NVDA, JAWS, SAPI, UIA, Orca) +- Added screen reader support on Linux via Speech Dispatcher and Orca +- First launch now asks all users whether to enable accessibility settings Android Client - iOS Client diff --git a/Client/CMakeLists.txt b/Client/CMakeLists.txt index 329b1daa09..5e410050d6 100644 --- a/Client/CMakeLists.txt +++ b/Client/CMakeLists.txt @@ -1,11 +1,11 @@ project (TeamTalkClients) -if (MSVC) +option (BUILD_TEAMTALK_CLIENT_PRISM "Build TeamTalk clients with Prism screen reader support" ON) +if (BUILD_TEAMTALK_CLIENT_PRISM) + add_subdirectory (Prism) +endif() - option (BUILD_TEAMTALK_CLIENT_TOLK "Build TeamTalk clients with Tolk" ON) - if (BUILD_TEAMTALK_CLIENT_TOLK) - add_subdirectory (Tolk) - endif() +if (MSVC) add_subdirectory (TeamTalkClassic) if (CMAKE_CSharp_COMPILER) diff --git a/Client/Prism/0001-Add-PRISM_USE_STATIC_CRT-option-to-select-MT-d-runti.patch b/Client/Prism/0001-Add-PRISM_USE_STATIC_CRT-option-to-select-MT-d-runti.patch new file mode 100644 index 0000000000..8e2799f076 --- /dev/null +++ b/Client/Prism/0001-Add-PRISM_USE_STATIC_CRT-option-to-select-MT-d-runti.patch @@ -0,0 +1,45 @@ +From 22cf931fa04810b941badefbc68ca3f46e47b7dd Mon Sep 17 00:00:00 2001 +From: Sihu Hwang <129564966+hwangsihu@users.noreply.github.com> +Date: Fri, 24 Apr 2026 20:19:54 +0900 +Subject: [PATCH] Add PRISM_USE_STATIC_CRT option to select /MT(d) runtime + +--- + CMakeLists.txt | 15 +++++---------- + 1 file changed, 5 insertions(+), 10 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 02e4e48..7ebf131 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -39,6 +39,7 @@ if(PRISM_ENABLE_VCPKG_SPECIFIC_OPTIONS) + OFF) + endif() + option(PRISM_ENABLE_GDEXTENSION "Enable building of the Godot GDExtension" ON) ++option(PRISM_USE_STATIC_CRT "Use /MT(d) static CRT on MSVC" OFF) + option(PRISM_ENABLE_LEGACY_BACKENDS + "Enable building of legacy backends which are dead or rarely used" OFF) + if(PRISM_ENABLE_LEGACY_BACKENDS) +@@ -63,16 +64,10 @@ if(WIN32) + set(CMAKE_MSVC_RUNTIME_LIBRARY + "MultiThreadedDLL" + CACHE STRING "Match godot-cpp non-debug CRT") +- else() +- if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") +- set(CMAKE_MSVC_RUNTIME_LIBRARY +- "MultiThreaded" +- CACHE STRING "Set MSVC CRT library type") +- else() +- set(CMAKE_MSVC_RUNTIME_LIBRARY +- "MultiThreadedDebug" +- CACHE STRING "Set MSVC CRT library type") +- endif() ++ elseif(PRISM_USE_STATIC_CRT) ++ set(CMAKE_MSVC_RUNTIME_LIBRARY ++ "MultiThreaded$<$:Debug>" ++ CACHE STRING "Use static CRT") + endif() + endif() + function(add_import_library target def_file dll_name) +-- +2.54.0.windows.1 + diff --git a/Client/Prism/CMakeLists.txt b/Client/Prism/CMakeLists.txt new file mode 100644 index 0000000000..f4fe33e617 --- /dev/null +++ b/Client/Prism/CMakeLists.txt @@ -0,0 +1,211 @@ +project(Prism) + +include(ExternalProject) + +################################################## +# Prism +################################################## + +if (MSVC) + + set (PRISM_INSTALL_DIR_MD ${CMAKE_CURRENT_BINARY_DIR}/prism-md) + set (PRISM_INSTALL_DIR_MT ${CMAKE_CURRENT_BINARY_DIR}/prism-mt) + + set (PRISM_STATIC_LIB_MD ${PRISM_INSTALL_DIR_MD}/lib/prism.lib) + set (PRISM_STATIC_LIB_MT ${PRISM_INSTALL_DIR_MT}/lib/prism.lib) + + ExternalProject_Add(prism-md-src + GIT_REPOSITORY https://github.com/ethindp/prism.git + GIT_TAG v0.13.0 + PATCH_COMMAND git apply ${CMAKE_CURRENT_LIST_DIR}/0001-Add-PRISM_USE_STATIC_CRT-option-to-select-MT-d-runti.patch + UPDATE_COMMAND "" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= + -DBUILD_SHARED_LIBS=OFF + -DPRISM_ENABLE_GDEXTENSION=OFF + -DCMAKE_DEBUG_POSTFIX=d + BUILD_COMMAND ${CMAKE_COMMAND} --build . --config Debug + COMMAND ${CMAKE_COMMAND} --build . --config Release + INSTALL_DIR ${PRISM_INSTALL_DIR_MD} + INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config Debug + COMMAND ${CMAKE_COMMAND} --build . --target install --config Release + COMMAND ${CMAKE_COMMAND} -E copy_if_different /ZDSR.lib /BoyCtrl.lib /PCTalker.lib /PrismOrcaBridge.lib /PrismSpeechDispatcherBridge.lib /lib/ + BUILD_BYPRODUCTS ${PRISM_INSTALL_DIR_MD}/lib/prismd.lib + ${PRISM_STATIC_LIB_MD} + ) + + ExternalProject_Add(prism-mt-src + GIT_REPOSITORY https://github.com/ethindp/prism.git + GIT_TAG v0.13.0 + PATCH_COMMAND git apply ${CMAKE_CURRENT_LIST_DIR}/0001-Add-PRISM_USE_STATIC_CRT-option-to-select-MT-d-runti.patch + UPDATE_COMMAND "" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= + -DBUILD_SHARED_LIBS=OFF + -DPRISM_ENABLE_GDEXTENSION=OFF + -DPRISM_USE_STATIC_CRT=ON + -DCMAKE_DEBUG_POSTFIX=d + BUILD_COMMAND ${CMAKE_COMMAND} --build . --config Debug + COMMAND ${CMAKE_COMMAND} --build . --config Release + INSTALL_DIR ${PRISM_INSTALL_DIR_MT} + INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config Debug + COMMAND ${CMAKE_COMMAND} --build . --target install --config Release + COMMAND ${CMAKE_COMMAND} -E copy_if_different /ZDSR.lib /BoyCtrl.lib /PCTalker.lib /PrismOrcaBridge.lib /PrismSpeechDispatcherBridge.lib /lib/ + BUILD_BYPRODUCTS ${PRISM_INSTALL_DIR_MT}/lib/prismd.lib + ${PRISM_STATIC_LIB_MT} + ) + + file(MAKE_DIRECTORY ${PRISM_INSTALL_DIR_MD}/include) + + add_library(Prism STATIC IMPORTED GLOBAL) + add_dependencies(Prism prism-md-src) + target_include_directories(Prism INTERFACE ${PRISM_INSTALL_DIR_MD}/include) + target_compile_definitions(Prism INTERFACE PRISM_STATIC) + set_target_properties(Prism PROPERTIES + IMPORTED_LOCATION_DEBUG ${PRISM_INSTALL_DIR_MD}/lib/prismd.lib + IMPORTED_LOCATION ${PRISM_STATIC_LIB_MD}) + + foreach (implib ZDSR BoyCtrl PCTalker PrismOrcaBridge PrismSpeechDispatcherBridge) + add_library(Prism_${implib} SHARED IMPORTED GLOBAL) + add_dependencies(Prism_${implib} prism-md-src) + set_target_properties(Prism_${implib} PROPERTIES + IMPORTED_IMPLIB ${PRISM_INSTALL_DIR_MD}/lib/${implib}.lib) + endforeach() + + target_link_libraries(Prism INTERFACE + rpcrt4 delayimp onecore uiautomationcore ole32 oleaut32 + Prism_ZDSR Prism_BoyCtrl Prism_PCTalker Prism_PrismOrcaBridge Prism_PrismSpeechDispatcherBridge) + target_link_options(Prism INTERFACE + "/WHOLEARCHIVE:$" + /delayload:ZDSRAPI_x64.dll + /delayload:BoyCtrl-x64.dll /delayload:PCTKUSR.dll + /delayload:prism_orca_bridge.dll /delayload:prism_speech_dispatcher_bridge.dll) + + file(MAKE_DIRECTORY ${PRISM_INSTALL_DIR_MT}/include) + + add_library(PrismMT STATIC IMPORTED GLOBAL) + add_dependencies(PrismMT prism-mt-src) + target_include_directories(PrismMT INTERFACE ${PRISM_INSTALL_DIR_MT}/include) + target_compile_definitions(PrismMT INTERFACE PRISM_STATIC) + set_target_properties(PrismMT PROPERTIES + IMPORTED_LOCATION_DEBUG ${PRISM_INSTALL_DIR_MT}/lib/prismd.lib + IMPORTED_LOCATION ${PRISM_STATIC_LIB_MT}) + + foreach (implib ZDSR BoyCtrl PCTalker PrismOrcaBridge PrismSpeechDispatcherBridge) + add_library(PrismMT_${implib} SHARED IMPORTED GLOBAL) + add_dependencies(PrismMT_${implib} prism-mt-src) + set_target_properties(PrismMT_${implib} PROPERTIES + IMPORTED_IMPLIB ${PRISM_INSTALL_DIR_MT}/lib/${implib}.lib) + endforeach() + + target_link_libraries(PrismMT INTERFACE + rpcrt4 delayimp onecore uiautomationcore ole32 oleaut32 + PrismMT_ZDSR PrismMT_BoyCtrl PrismMT_PCTalker PrismMT_PrismOrcaBridge PrismMT_PrismSpeechDispatcherBridge) + target_link_options(PrismMT INTERFACE + "/WHOLEARCHIVE:$" + /delayload:ZDSRAPI_x64.dll + /delayload:BoyCtrl-x64.dll /delayload:PCTKUSR.dll + /delayload:prism_orca_bridge.dll /delayload:prism_speech_dispatcher_bridge.dll) + +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + + set (PRISM_INSTALL_DIR_ARM64 ${CMAKE_CURRENT_BINARY_DIR}/prism-arm64) + set (PRISM_INSTALL_DIR_X86_64 ${CMAKE_CURRENT_BINARY_DIR}/prism-x86_64) + set (PRISM_INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/prism) + set (PRISM_STATIC_LIB ${PRISM_INSTALL_DIR}/lib/libprism.a) + + set (PRISM_COMMON_CMAKE_ARGS_MAC + -DBUILD_SHARED_LIBS=OFF + -DPRISM_ENABLE_GDEXTENSION=OFF + -DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}) + + ExternalProject_Add(prism-arm64-src + GIT_REPOSITORY https://github.com/ethindp/prism.git + GIT_TAG v0.13.0 + UPDATE_COMMAND "" + CMAKE_GENERATOR Xcode + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= + -DCMAKE_OSX_ARCHITECTURES=arm64 + ${PRISM_COMMON_CMAKE_ARGS_MAC} + BUILD_COMMAND ${CMAKE_COMMAND} --build . --config Release + INSTALL_DIR ${PRISM_INSTALL_DIR_ARM64} + INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config Release + BUILD_BYPRODUCTS ${PRISM_INSTALL_DIR_ARM64}/lib/libprism.a + ) + + ExternalProject_Add(prism-x86_64-src + GIT_REPOSITORY https://github.com/ethindp/prism.git + GIT_TAG v0.13.0 + UPDATE_COMMAND "" + CMAKE_GENERATOR Xcode + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= + -DCMAKE_OSX_ARCHITECTURES=x86_64 + ${PRISM_COMMON_CMAKE_ARGS_MAC} + BUILD_COMMAND ${CMAKE_COMMAND} --build . --config Release + INSTALL_DIR ${PRISM_INSTALL_DIR_X86_64} + INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config Release + BUILD_BYPRODUCTS ${PRISM_INSTALL_DIR_X86_64}/lib/libprism.a + ) + + file(MAKE_DIRECTORY ${PRISM_INSTALL_DIR}/lib) + file(MAKE_DIRECTORY ${PRISM_INSTALL_DIR}/include) + add_custom_command (OUTPUT ${PRISM_STATIC_LIB} + COMMAND lipo -create ${PRISM_INSTALL_DIR_ARM64}/lib/libprism.a ${PRISM_INSTALL_DIR_X86_64}/lib/libprism.a -output ${PRISM_STATIC_LIB} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${PRISM_INSTALL_DIR_ARM64}/include ${PRISM_INSTALL_DIR}/include + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating universal Prism binary" + DEPENDS prism-arm64-src prism-x86_64-src) + + add_custom_target (prism-src DEPENDS ${PRISM_STATIC_LIB}) + + add_library(Prism STATIC IMPORTED GLOBAL) + add_dependencies(Prism prism-src) + target_include_directories(Prism INTERFACE ${PRISM_INSTALL_DIR}/include) + target_compile_definitions(Prism INTERFACE PRISM_STATIC) + set_target_properties(Prism PROPERTIES IMPORTED_LOCATION ${PRISM_STATIC_LIB}) + + find_library(AVFOUNDATION_FRAMEWORK AVFoundation) + find_library(FOUNDATION_FRAMEWORK Foundation) + target_link_libraries(Prism INTERFACE ${AVFOUNDATION_FRAMEWORK} ${FOUNDATION_FRAMEWORK}) + target_link_options(Prism INTERFACE "LINKER:-force_load,$") + +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + + set (PRISM_INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/prism) + set (PRISM_STATIC_LIB ${PRISM_INSTALL_DIR}/lib/libprism.a) + + ExternalProject_Add(prism-src + GIT_REPOSITORY https://github.com/ethindp/prism.git + GIT_TAG v0.13.0 + UPDATE_COMMAND "" + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= + -DBUILD_SHARED_LIBS=OFF + -DPRISM_ENABLE_GDEXTENSION=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DCMAKE_BUILD_TYPE=release + INSTALL_DIR ${PRISM_INSTALL_DIR} + BUILD_BYPRODUCTS ${PRISM_STATIC_LIB} + ) + + file(MAKE_DIRECTORY ${PRISM_INSTALL_DIR}/include) + + add_library(Prism STATIC IMPORTED GLOBAL) + add_dependencies(Prism prism-src) + target_include_directories(Prism INTERFACE ${PRISM_INSTALL_DIR}/include) + target_compile_definitions(Prism INTERFACE PRISM_STATIC) + set_target_properties(Prism PROPERTIES IMPORTED_LOCATION ${PRISM_STATIC_LIB}) + + find_package(PkgConfig) + if (PkgConfig_FOUND) + pkg_check_modules(SPEECHD IMPORTED_TARGET speech-dispatcher) + pkg_check_modules(GLIB IMPORTED_TARGET glib-2.0) + pkg_check_modules(GIO IMPORTED_TARGET gio-2.0) + if (SPEECHD_FOUND) + target_link_libraries(Prism INTERFACE PkgConfig::SPEECHD) + endif() + if (GLIB_FOUND AND GIO_FOUND) + target_link_libraries(Prism INTERFACE PkgConfig::GLIB PkgConfig::GIO) + endif() + endif() + target_link_options(Prism INTERFACE + "LINKER:--whole-archive" "$" "LINKER:--no-whole-archive") + +endif() diff --git a/Client/TeamTalkClassic/CMakeLists.txt b/Client/TeamTalkClassic/CMakeLists.txt index 40589da8e7..02a84ad505 100644 --- a/Client/TeamTalkClassic/CMakeLists.txt +++ b/Client/TeamTalkClassic/CMakeLists.txt @@ -84,25 +84,16 @@ if (MSVC) if (BUILD_TEAMTALK_CLIENT_TEAMTALKCLASSIC) set_property(TARGET TeamTalk5Classic PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../../Library/TeamTalk_DLL") - if (BUILD_TEAMTALK_CLIENT_TOLK) - target_compile_options (TeamTalk5Classic PUBLIC -DENABLE_TOLK ${TTCLASSIC_COMPILE_FLAGS}) - install (FILES ${TOLK_DLL_FILES} DESTINATION Client/TeamTalkClassic) - if (BUILD_TEAMTALK_LIBRARY_DLL) - target_include_directories (TeamTalk5Classic PUBLIC ./) - target_link_libraries (TeamTalk5Classic TeamTalk5DLL Tolk tinyxml2-classic) - else() - target_include_directories (TeamTalk5Classic PUBLIC ./ ../../Library/TeamTalk_DLL) - target_link_libraries (TeamTalk5Classic TeamTalk5 Tolk tinyxml2-classic) - endif() + target_compile_options (TeamTalk5Classic PUBLIC ${TTCLASSIC_COMPILE_FLAGS}) + if (BUILD_TEAMTALK_CLIENT_PRISM) + target_compile_definitions (TeamTalk5Classic PUBLIC ENABLE_PRISM) + endif() + if (BUILD_TEAMTALK_LIBRARY_DLL) + target_include_directories (TeamTalk5Classic PUBLIC ./) + target_link_libraries (TeamTalk5Classic TeamTalk5DLL tinyxml2-classic $<$:PrismMT>) else() - target_compile_options (TeamTalk5Classic PUBLIC ${TTCLASSIC_COMPILE_FLAGS}) - if (BUILD_TEAMTALK_LIBRARY_DLL) - target_include_directories (TeamTalk5Classic PUBLIC ./) - target_link_libraries (TeamTalk5Classic TeamTalk5DLL tinyxml2-classic) - else() - target_include_directories (TeamTalk5Classic PUBLIC ./ ../../Library/TeamTalk_DLL) - target_link_libraries (TeamTalk5Classic TeamTalk5 tinyxml2-classic) - endif() + target_include_directories (TeamTalk5Classic PUBLIC ./ ../../Library/TeamTalk_DLL) + target_link_libraries (TeamTalk5Classic TeamTalk5 tinyxml2-classic $<$:PrismMT>) endif() set_output_dir(TeamTalk5Classic "${CMAKE_CURRENT_SOURCE_DIR}/") install (TARGETS TeamTalk5Classic DESTINATION Client/TeamTalkClassic) diff --git a/Client/TeamTalkClassic/Helper.cpp b/Client/TeamTalkClassic/Helper.cpp index 46a12ac317..407ac51fb1 100644 --- a/Client/TeamTalkClassic/Helper.cpp +++ b/Client/TeamTalkClassic/Helper.cpp @@ -644,8 +644,12 @@ extern BOOL g_bSpeech; void AddTextToSpeechMessage(const CString& szMsg) { -#if defined(ENABLE_TOLK) - if(g_bSpeech) - Tolk_Output(szMsg); +#if defined(ENABLE_PRISM) + extern PrismBackend* g_prismBackend; + if (g_bSpeech && g_prismBackend) + { + CStringA utf8(szMsg); + prism_backend_output(g_prismBackend, utf8, true); + } #endif } diff --git a/Client/TeamTalkClassic/Helper.h b/Client/TeamTalkClassic/Helper.h index 78f38bcdbd..ac3ba5b500 100644 --- a/Client/TeamTalkClassic/Helper.h +++ b/Client/TeamTalkClassic/Helper.h @@ -26,8 +26,8 @@ #include "settings/ClientXML.h" -#if defined(ENABLE_TOLK) -#include +#if defined(ENABLE_PRISM) +#include #endif typedef struct diff --git a/Client/TeamTalkClassic/TeamTalkDlg.cpp b/Client/TeamTalkClassic/TeamTalkDlg.cpp index df636138cd..ddeede212a 100644 --- a/Client/TeamTalkClassic/TeamTalkDlg.cpp +++ b/Client/TeamTalkClassic/TeamTalkDlg.cpp @@ -160,18 +160,35 @@ void CTeamTalkDlg::EnableVoiceActivation(BOOL bEnable, PlaySoundEvent(bEnable? on : off); } +#if defined(ENABLE_PRISM) +PrismContext* g_prismContext = nullptr; +PrismBackend* g_prismBackend = nullptr; +#endif + void CTeamTalkDlg::EnableSpeech(BOOL bEnable) { -#if defined(ENABLE_TOLK) - if(g_bSpeech) +#if defined(ENABLE_PRISM) + if (g_prismBackend) { - Tolk_Unload(); + prism_backend_free(g_prismBackend); + g_prismBackend = nullptr; + } + if (g_prismContext) + { + prism_shutdown(g_prismContext); + g_prismContext = nullptr; } - if(bEnable) + if (bEnable) { - Tolk_Load(); - Tolk_TrySAPI(true); + PrismConfig cfg = prism_config_init(); + g_prismContext = prism_init(&cfg); + if (g_prismContext) + { + g_prismBackend = prism_registry_create_best(g_prismContext); + if (g_prismBackend) + prism_backend_initialize(g_prismBackend); + } } #endif g_bSpeech = bEnable; @@ -2970,9 +2987,16 @@ void CTeamTalkDlg::Exit() //Close TeamTalk DLLs TT_CloseTeamTalk(ttInst); -#if defined(ENABLE_TOLK) - if (Tolk_IsLoaded()) { - Tolk_Unload(); +#if defined(ENABLE_PRISM) + if (g_prismBackend) + { + prism_backend_free(g_prismBackend); + g_prismBackend = nullptr; + } + if (g_prismContext) + { + prism_shutdown(g_prismContext); + g_prismContext = nullptr; } #endif m_xmlSettings.SaveFile(); diff --git a/Client/TeamTalkClassic/TeamTalkDlg.h b/Client/TeamTalkClassic/TeamTalkDlg.h index 9be158e4c9..baae186aab 100644 --- a/Client/TeamTalkClassic/TeamTalkDlg.h +++ b/Client/TeamTalkClassic/TeamTalkDlg.h @@ -38,8 +38,8 @@ #include "HttpRequest.h" #include "PlaySoundThread.h" -#if defined(ENABLE_TOLK) -#include +#if defined(ENABLE_PRISM) +#include #endif #include diff --git a/Client/TeamTalkClassic/gui/SoundEventsPage.cpp b/Client/TeamTalkClassic/gui/SoundEventsPage.cpp index 309b50ef59..77519c793e 100644 --- a/Client/TeamTalkClassic/gui/SoundEventsPage.cpp +++ b/Client/TeamTalkClassic/gui/SoundEventsPage.cpp @@ -25,10 +25,6 @@ #include "Resource.h" #include "SoundEventsPage.h" -#if defined(ENABLE_TOLK) -#include -#endif - #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE diff --git a/Client/Tolk/0001-Add-CMake-script-for-building-tolk.patch b/Client/Tolk/0001-Add-CMake-script-for-building-tolk.patch deleted file mode 100644 index a653850bbc..0000000000 --- a/Client/Tolk/0001-Add-CMake-script-for-building-tolk.patch +++ /dev/null @@ -1,75 +0,0 @@ -From cd93b63c164a0f0db10e173d19aa71c87c26a450 Mon Sep 17 00:00:00 2001 -From: hwangsihu <129564966+hwangsihu@users.noreply.github.com> -Date: Tue, 21 Oct 2025 15:06:59 +0900 -Subject: [PATCH] Add CMake script for building tolk - ---- - CMakeLists.txt | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ - 1 file changed, 56 insertions(+) - create mode 100644 CMakeLists.txt - -diff --git a/CMakeLists.txt b/CMakeLists.txt -new file mode 100644 -index 0000000..8b7984a ---- /dev/null -+++ b/CMakeLists.txt -@@ -0,0 +1,56 @@ -+cmake_minimum_required(VERSION 3.10) -+project(Tolk) -+ -+option (TOLK_MULTITHREADED "Build Multi-Threaded (/MT) instead of Multi-Threaded DLL (/MD)" OFF) -+ -+if (TOLK_MULTITHREADED) -+ foreach (flag_var -+ CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE -+ CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO -+ CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE -+ CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) -+ if (${flag_var} MATCHES "/MD") -+ STRING(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") -+ endif() -+ endforeach(flag_var) -+endif() -+ -+set (TOLK_COMPILE_FLAGS -D_EXPORTING -DUNICODE -D_UNICODE) -+set (TOLK_LINK_FLAGS User32 Ole32 OleAut32) -+ -+# Find Java JDK so we can enable JNI support -+option (TOLK_WITH_JAVAJNI "Build Tolk with Java support" ON) -+if (TOLK_WITH_JAVAJNI) -+ find_package(JNI) -+ -+ if (JNI_FOUND) -+ include_directories (${JNI_INCLUDE_DIRS}) -+ list (APPEND TOLK_COMPILE_FLAGS -D_WITH_JNI) -+ list (APPEND TOLK_LINK_FLAGS ${JNI_LIBRARIES}) -+ else() -+ message("Cannot find Java JDK. Specify JAVA_HOME environment variable to help CMake find Java JDK") -+ endif() -+endif() -+ -+add_library(Tolk SHARED src/Tolk.cpp src/Tolk.h src/TolkVersion.h src/TolkJNI.cpp -+ src/ScreenReaderDriver.h src/Tolk.rc -+ src/ScreenReaderDriverJAWS.cpp src/ScreenReaderDriverJAWS.h -+ src/ScreenReaderDriverNVDA.cpp src/ScreenReaderDriverNVDA.h -+ src/ScreenReaderDriverSA.cpp src/ScreenReaderDriverSA.h -+ src/ScreenReaderDriverSNova.cpp src/ScreenReaderDriverSNova.h -+ src/ScreenReaderDriverWE.cpp src/ScreenReaderDriverWE.h -+ src/ScreenReaderDriverZT.cpp src/ScreenReaderDriverZT.h -+ src/ScreenReaderDriverSAPI.cpp src/ScreenReaderDriverSAPI.h -+ src/fsapi.c src/fsapi.h src/wineyes.c src/wineyes.h src/zt.c src/zt.h) -+ -+target_include_directories (Tolk INTERFACE src) -+target_link_libraries (Tolk PRIVATE ${TOLK_LINK_FLAGS}) -+target_compile_options (Tolk PRIVATE ${TOLK_COMPILE_FLAGS}) -+ -+install (TARGETS Tolk DESTINATION lib) -+install (FILES src/Tolk.h DESTINATION include) -+if (${CMAKE_GENERATOR_PLATFORM} MATCHES "x64") -+ install (FILES libs/x64/nvdaControllerClient64.dll libs/x64/SAAPI64.dll DESTINATION lib) -+else() -+ install (FILES libs/x86/dolapi32.dll libs/x86/nvdaControllerClient32.dll libs/x86/SAAPI32.dll DESTINATION lib) -+endif() --- -2.51.0.windows.2 - diff --git a/Client/Tolk/CMakeLists.txt b/Client/Tolk/CMakeLists.txt deleted file mode 100644 index 6edaea48e0..0000000000 --- a/Client/Tolk/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -project(Tolk) - -include(ExternalProject) - -################################################## -# TOLK -################################################## - -ExternalProject_Add(tolk-src - GIT_REPOSITORY https://github.com/dkager/tolk.git - GIT_TAG e5149f0 - UPDATE_COMMAND "" - PATCH_COMMAND git clean -fdx - COMMAND git apply ${CMAKE_CURRENT_LIST_DIR}/0001-Add-CMake-script-for-building-tolk.patch - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= -DTOLK_WITH_JAVAJNI=OFF -DTOLK_MULTITHREADED=ON - BUILD_COMMAND ${CMAKE_COMMAND} --build . --config Release - INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/Tolk - INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config Release - BUILD_BYPRODUCTS /lib/Tolk.lib - ) -ExternalProject_Get_Property(tolk-src INSTALL_DIR) -file(MAKE_DIRECTORY ${INSTALL_DIR}/include) - -add_library(Tolk STATIC IMPORTED GLOBAL) -add_dependencies(Tolk tolk-src) -target_include_directories (Tolk INTERFACE ${INSTALL_DIR}/include) -set_target_properties(Tolk PROPERTIES - IMPORTED_LOCATION_DEBUG ${INSTALL_DIR}/lib/Tolk.lib - IMPORTED_LOCATION ${INSTALL_DIR}/lib/Tolk.lib) - -if (${CMAKE_SIZEOF_VOID_P} EQUAL 8) - set (TOLK_DLL_FILES ${INSTALL_DIR}/lib/nvdaControllerClient64.dll ${INSTALL_DIR}/lib/SAAPI64.dll ${INSTALL_DIR}/lib/Tolk.dll PARENT_SCOPE) -else() - set (TOLK_DLL_FILES ${INSTALL_DIR}/lib/dolapi32.dll ${INSTALL_DIR}/lib/nvdaControllerClient32.dll ${INSTALL_DIR}/lib/SAAPI32.dll ${INSTALL_DIR}/lib/Tolk.dll PARENT_SCOPE) -endif() diff --git a/Client/qtTeamTalk/CMakeLists.txt b/Client/qtTeamTalk/CMakeLists.txt index c40ae07560..2154a24276 100644 --- a/Client/qtTeamTalk/CMakeLists.txt +++ b/Client/qtTeamTalk/CMakeLists.txt @@ -91,7 +91,7 @@ if (Qt5_FOUND OR Qt6_FOUND) customvideofmtdlg.h license.h bearwarelogindlg.h audiopreprocessordlg.h ttseventsmodel.h statusbardlg.h statusbareventsmodel.h mycombobox.h - utilsound.h utilvideo.h utiltts.h utilui.h utilhotkey.h + utilsound.h utilvideo.h utiltts.h prismworker.h utilui.h utilhotkey.h serverlogeventsmodel.h textmessagecontainer.h useraccountsmodel.h encryptionsetupdlg.h utiltt.h utilxml.h utilos.h serverdlg.h moveusersdlg.h useraccountdlg.h soundeventsmodel.h @@ -113,7 +113,7 @@ if (Qt5_FOUND OR Qt6_FOUND) generatettfiledlg.cpp customvideofmtdlg.cpp bearwarelogindlg.cpp audiopreprocessordlg.cpp ttseventsmodel.cpp statusbardlg.cpp statusbareventsmodel.cpp mycombobox.cpp - utilsound.cpp utilvideo.cpp utiltts.cpp utilui.cpp utilhotkey.cpp + utilsound.cpp utilvideo.cpp utiltts.cpp prismworker.cpp utilui.cpp utilhotkey.cpp serverlogeventsmodel.cpp textmessagecontainer.cpp useraccountsmodel.cpp encryptionsetupdlg.cpp utiltt.cpp utilxml.cpp utilos.cpp serverdlg.cpp moveusersdlg.cpp useraccountdlg.cpp soundeventsmodel.cpp @@ -166,9 +166,8 @@ if (Qt5_FOUND OR Qt6_FOUND) option (BUILD_TEAMTALK_CLIENT_QTTEAMTALK "Build Qt TeamTalk client example" ON) - if (BUILD_TEAMTALK_CLIENT_TOLK) - list (APPEND TEAMTALK_LINK_FLAGS Tolk) - install (FILES ${TOLK_DLL_FILES} DESTINATION Client/qtTeamTalk) + if (BUILD_TEAMTALK_CLIENT_PRISM) + list (APPEND TEAMTALK_LINK_FLAGS Prism) endif() if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") @@ -260,12 +259,14 @@ if (Qt5_FOUND OR Qt6_FOUND) endif() endif() - if (BUILD_TEAMTALK_CLIENT_TOLK) - target_compile_options(${TEAMTALK_TARGET} PUBLIC -DUNICODE -D_UNICODE -DENABLE_TOLK -D_CRT_SECURE_NO_WARNINGS) - elseif (MSVC) + if (MSVC) target_compile_options(${TEAMTALK_TARGET} PUBLIC -DUNICODE -D_UNICODE -D_CRT_SECURE_NO_WARNINGS) endif() + if (BUILD_TEAMTALK_CLIENT_PRISM) + target_compile_definitions(${TEAMTALK_TARGET} PUBLIC ENABLE_PRISM) + endif() + # Build translations file (GLOB TS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/languages/*.ts) set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/languages) diff --git a/Client/qtTeamTalk/mainwindow.cpp b/Client/qtTeamTalk/mainwindow.cpp index 5f4f17f1ee..ef90085324 100644 --- a/Client/qtTeamTalk/mainwindow.cpp +++ b/Client/qtTeamTalk/mainwindow.cpp @@ -44,6 +44,7 @@ #include "utilos.h" #include "utilvideo.h" #include "utiltts.h" +#include "prismworker.h" #include "utilxml.h" #include "utilmedia.h" #include "moveusersdlg.h" @@ -71,6 +72,7 @@ #include #include #include +#include #if defined(QT_TEXTTOSPEECH_LIB) #include @@ -100,6 +102,10 @@ QTextToSpeech* ttSpeech = nullptr; #if QT_VERSION >= QT_VERSION_CHECK(6,8,0) QObject* announcerObject = nullptr; #endif +#if defined(ENABLE_PRISM) +PrismWorker* prismWorker = nullptr; +QThread* prismThread = nullptr; +#endif //strip ampersand from menutext #define MENUTEXT(text) text.replace("&", "") @@ -868,37 +874,31 @@ void MainWindow::loadSettings() void MainWindow::initialScreenReaderSetup() { -#if defined(ENABLE_TOLK) || defined(Q_OS_LINUX) if (ttSettings->value(SETTINGS_GENERAL_FIRSTSTART, SETTINGS_GENERAL_FIRSTSTART_DEFAULT).toBool()) { - bool SRActive = isScreenReaderActive(); - if (SRActive) - { - QMessageBox answer; - answer.setText(tr("%1 has detected usage of a screenreader on your computer. Do you wish to enable accessibility options offered by %1 with recommended settings?").arg(APPNAME_SHORT)); - QAbstractButton* YesButton = answer.addButton(tr("&Yes"), QMessageBox::YesRole); - QAbstractButton* NoButton = answer.addButton(tr("&No"), QMessageBox::NoRole); - Q_UNUSED(NoButton); - answer.setIcon(QMessageBox::Question); - answer.setWindowTitle(APPNAME_SHORT); - answer.exec(); + QMessageBox answer; + answer.setText(tr("Would you like to enable accessibility options with recommended settings for screen reader usage?")); + QAbstractButton* YesButton = answer.addButton(tr("&Yes"), QMessageBox::YesRole); + QAbstractButton* NoButton = answer.addButton(tr("&No"), QMessageBox::NoRole); + Q_UNUSED(NoButton); + answer.setIcon(QMessageBox::Question); + answer.setWindowTitle(APPNAME_SHORT); + answer.exec(); - if (answer.clickedButton() == YesButton) - { -#if defined(ENABLE_TOLK) - ttSettings->setValue(SETTINGS_TTS_ENGINE, TTSENGINE_TOLK); + if (answer.clickedButton() == YesButton) + { +#if defined(ENABLE_PRISM) + ttSettings->setValue(SETTINGS_TTS_ENGINE, TTSENGINE_PRISM); #elif defined(Q_OS_LINUX) - if (QFile::exists(NOTIFY_PATH)) - ttSettings->setValue(SETTINGS_TTS_TOAST, true); - else - ttSettings->setValue(SETTINGS_TTS_ENGINE, TTSENGINE_QT); + if (QFile::exists(NOTIFY_PATH)) + ttSettings->setValue(SETTINGS_TTS_TOAST, true); + else + ttSettings->setValue(SETTINGS_TTS_ENGINE, TTSENGINE_QT); #endif - ttSettings->setValue(SETTINGS_DISPLAY_VU_METER_UPDATES, false); - ttSettings->setValue(SETTINGS_DISPLAY_CHAT_HISTORY_LISTVIEW, true); - } + ttSettings->setValue(SETTINGS_DISPLAY_VU_METER_UPDATES, false); + ttSettings->setValue(SETTINGS_DISPLAY_CHAT_HISTORY_LISTVIEW, true); } } -#endif } bool MainWindow::parseArgs(const QStringList& args) @@ -4543,9 +4543,16 @@ bool MainWindow::slotClientExit(bool /*checked =false */) } if (ok) { -#if defined(ENABLE_TOLK) - if(Tolk_IsLoaded()) - Tolk_Unload(); +#if defined(ENABLE_PRISM) + if (prismWorker) + QMetaObject::invokeMethod(prismWorker, "shutdown", Qt::BlockingQueuedConnection); + if (prismThread) + { + prismThread->quit(); + prismThread->wait(); + prismWorker = nullptr; + prismThread = nullptr; + } #endif if(TT_GetFlags(ttInst) & CLIENT_CONNECTED) disconnectFromServer(); @@ -7960,14 +7967,19 @@ void MainWindow::startTTS() break; #endif -#if defined(ENABLE_TOLK) - case TTSENGINE_TOLK : +#if defined(ENABLE_PRISM) + case TTSENGINE_PRISM : { - if (!Tolk_IsLoaded()) + if (!prismThread) { - Tolk_Load(); - Tolk_TrySAPI(true); + prismThread = new QThread(); + prismWorker = new PrismWorker(); + prismWorker->moveToThread(prismThread); + connect(prismThread, &QThread::finished, prismWorker, &QObject::deleteLater); + prismThread->start(); } + quint64 backendId = ttSettings->value(SETTINGS_TTS_PRISM_BACKEND, SETTINGS_TTS_PRISM_BACKEND_DEFAULT).toULongLong(); + QMetaObject::invokeMethod(prismWorker, "initialize", Qt::QueuedConnection, Q_ARG(quint64, backendId)); } break; #endif diff --git a/Client/qtTeamTalk/preferences.ui b/Client/qtTeamTalk/preferences.ui index 1e1ca70597..9afd4515b9 100644 --- a/Client/qtTeamTalk/preferences.ui +++ b/Client/qtTeamTalk/preferences.ui @@ -1912,20 +1912,6 @@ - - - Use SAPI instead of current screenreader - - - - - - - Switch to SAPI if current screenreader is not available - - - - Interrupt current screenreader speech on new event diff --git a/Client/qtTeamTalk/preferencesdlg.cpp b/Client/qtTeamTalk/preferencesdlg.cpp index 8b0599f66a..53486425c5 100644 --- a/Client/qtTeamTalk/preferencesdlg.cpp +++ b/Client/qtTeamTalk/preferencesdlg.cpp @@ -31,6 +31,9 @@ #include "utiltts.h" #include "utilui.h" #include "settings.h" +#if defined(ENABLE_PRISM) +#include +#endif #include "soundeventsmodel.h" #include "shortcutsmodel.h" @@ -616,11 +619,10 @@ void PreferencesDlg::initTTSEventsTab() #if defined(QT_TEXTTOSPEECH_LIB) ui.ttsengineComboBox->addItem(tr("Default"), TTSENGINE_QT); #endif -#if defined(Q_OS_WIN) - ui.ttsengineComboBox->addItem(tr("Tolk"), TTSENGINE_TOLK); -#elif defined(Q_OS_LINUX) - -#elif defined(Q_OS_MAC) +#if defined(ENABLE_PRISM) + ui.ttsengineComboBox->addItem(tr("Prism"), TTSENGINE_PRISM); +#endif +#if defined(Q_OS_MAC) ui.ttsengineComboBox->addItem(tr("VoiceOver (via Apple Script)"), TTSENGINE_APPLESCRIPT); #endif #if QT_VERSION >= QT_VERSION_CHECK(6,8,0) @@ -1055,14 +1057,19 @@ void PreferencesDlg::slotSaveChanges() ttSettings->setValue(SETTINGS_TTS_VOICE, getCurrentItemData(ui.ttsVoiceComboBox, "")); ttSettings->setValueOrClear(SETTINGS_TTS_RATE, ui.ttsVoiceRateSpinBox->value(), SETTINGS_TTS_RATE_DEFAULT); ttSettings->setValueOrClear(SETTINGS_TTS_VOLUME, ui.ttsVoiceVolumeSpinBox->value(), SETTINGS_TTS_VOLUME_DEFAULT); -#if defined(Q_OS_WIN) - ttSettings->setValueOrClear(SETTINGS_TTS_SAPI, ui.ttsForceSapiChkBox->isChecked(), SETTINGS_TTS_SAPI_DEFAULT); - ttSettings->setValueOrClear(SETTINGS_TTS_TRY_SAPI, ui.ttsTrySapiChkBox->isChecked(), SETTINGS_TTS_TRY_SAPI_DEFAULT); +#if defined(ENABLE_PRISM) + if (getCurrentItemData(ui.ttsengineComboBox).toUInt() == TTSENGINE_PRISM) + { + ttSettings->setValueOrClear(SETTINGS_TTS_PRISM_BACKEND, getCurrentItemData(ui.ttsVoiceComboBox, quint64(0)).toULongLong(), quint64(SETTINGS_TTS_PRISM_BACKEND_DEFAULT)); + ttSettings->setValueOrClear(SETTINGS_TTS_OUTPUT_MODE, getCurrentItemData(ui.ttsOutputModeComboBox, ""), SETTINGS_TTS_OUTPUT_MODE_DEFAULT); + ttSettings->setValueOrClear(SETTINGS_TTS_INTERRUPT, ui.ttsAssertiveChkBox->isChecked(), SETTINGS_TTS_INTERRUPT_DEFAULT); + } +#endif #if QT_VERSION >= QT_VERSION_CHECK(6,8,0) - ttSettings->setValueOrClear(SETTINGS_TTS_ASSERTIVE, ui.ttsAssertiveChkBox->isChecked(), SETTINGS_TTS_ASSERTIVE_DEFAULT); + if (getCurrentItemData(ui.ttsengineComboBox).toUInt() == TTSENGINE_QTANNOUNCEMENT) + ttSettings->setValueOrClear(SETTINGS_TTS_ASSERTIVE, ui.ttsAssertiveChkBox->isChecked(), SETTINGS_TTS_ASSERTIVE_DEFAULT); #endif - ttSettings->setValueOrClear(SETTINGS_TTS_OUTPUT_MODE, getCurrentItemData(ui.ttsOutputModeComboBox, ""), SETTINGS_TTS_OUTPUT_MODE_DEFAULT); -#elif defined(Q_OS_DARWIN) +#if defined(Q_OS_DARWIN) ttSettings->setValueOrClear(SETTINGS_TTS_SPEAKLISTS, ui.ttsSpeakListsChkBox->isChecked(), SETTINGS_TTS_SPEAKLISTS_DEFAULT); #endif ttSettings->setValue(SETTINGS_DISPLAY_TTSHEADER, ui.ttsTableView->horizontalHeader()->saveState()); @@ -1427,8 +1434,6 @@ void PreferencesDlg::slotUpdateTTSTab() ui.label_ttsnotifTimestamp->hide(); ui.ttsNotifTimestampSpinBox->hide(); - ui.ttsForceSapiChkBox->hide(); - ui.ttsTrySapiChkBox->hide(); ui.ttsSpeakListsChkBox->hide(); ui.ttsAssertiveChkBox->hide(); ui.label_ttsoutputmode->hide(); @@ -1475,37 +1480,51 @@ void PreferencesDlg::slotUpdateTTSTab() #endif /* QT_TEXTTOSPEECH_LIB */ } break; - case TTSENGINE_TOLK : -#if defined(ENABLE_TOLK) + case TTSENGINE_PRISM : +#if defined(ENABLE_PRISM) { + ui.label_ttsvoice->setText(tr("Backend")); + ui.label_ttsvoice->show(); + ui.ttsVoiceComboBox->show(); ui.label_ttsoutputmode->show(); ui.ttsOutputModeComboBox->show(); - ui.ttsForceSapiChkBox->show(); - ui.ttsTrySapiChkBox->show(); - - bool tolkLoaded = Tolk_IsLoaded(); - if (!tolkLoaded) - Tolk_Load(); - QString currentSR = QString("%1").arg(Tolk_DetectScreenReader()); - bool hasBraille = Tolk_HasBraille(); - bool hasSpeech = Tolk_HasSpeech(); - if (!tolkLoaded) - Tolk_Unload(); - if(currentSR.size()) + + ui.ttsVoiceComboBox->clear(); + ui.ttsVoiceComboBox->addItem(tr("Auto"), quint64(PRISM_BACKEND_INVALID)); { - ui.ttsForceSapiChkBox->setText(tr("Use SAPI instead of %1 screenreader").arg(currentSR)); - ui.ttsTrySapiChkBox->setText(tr("Switch to SAPI if %1 screenreader is not available").arg(currentSR)); + PrismConfig cfg = prism_config_init(); + PrismContext* ctx = prism_init(&cfg); + if (ctx) + { + size_t count = prism_registry_count(ctx); + for (size_t i = 0; i < count; i++) + { + PrismBackendId id = prism_registry_id_at(ctx, i); + const char* name = prism_registry_name(ctx, id); + if (name) + ui.ttsVoiceComboBox->addItem(QString::fromUtf8(name), quint64(id)); + } + prism_shutdown(ctx); + } + } + quint64 savedBackend = ttSettings->value(SETTINGS_TTS_PRISM_BACKEND, SETTINGS_TTS_PRISM_BACKEND_DEFAULT).toULongLong(); + for (int i = 0; i < ui.ttsVoiceComboBox->count(); i++) + { + if (ui.ttsVoiceComboBox->itemData(i).toULongLong() == savedBackend) + { + ui.ttsVoiceComboBox->setCurrentIndex(i); + break; + } } - ui.ttsForceSapiChkBox->setChecked(ttSettings->value(SETTINGS_TTS_SAPI, SETTINGS_TTS_SAPI_DEFAULT).toBool()); - ui.ttsTrySapiChkBox->setChecked(ttSettings->value(SETTINGS_TTS_TRY_SAPI, SETTINGS_TTS_TRY_SAPI_DEFAULT).toBool()); + ui.ttsOutputModeComboBox->clear(); - if (hasSpeech == true && hasBraille == true) - ui.ttsOutputModeComboBox->addItem(tr("Speech and Braille"), TTS_OUTPUTMODE_SPEECHBRAILLE); - if (hasBraille == true) - ui.ttsOutputModeComboBox->addItem(tr("Braille only"), TTS_OUTPUTMODE_BRAILLE); - if (hasSpeech == true) - ui.ttsOutputModeComboBox->addItem(tr("Speech only"), TTS_OUTPUTMODE_SPEECH); + ui.ttsOutputModeComboBox->addItem(tr("Speech and Braille"), TTS_OUTPUTMODE_SPEECHBRAILLE); + ui.ttsOutputModeComboBox->addItem(tr("Speech only"), TTS_OUTPUTMODE_SPEECH); + ui.ttsOutputModeComboBox->addItem(tr("Braille only"), TTS_OUTPUTMODE_BRAILLE); setCurrentItemData(ui.ttsOutputModeComboBox, ttSettings->value(SETTINGS_TTS_OUTPUT_MODE, SETTINGS_TTS_OUTPUT_MODE_DEFAULT).toInt()); + + ui.ttsAssertiveChkBox->show(); + ui.ttsAssertiveChkBox->setChecked(ttSettings->value(SETTINGS_TTS_INTERRUPT, SETTINGS_TTS_INTERRUPT_DEFAULT).toBool()); } #endif break; diff --git a/Client/qtTeamTalk/prismworker.cpp b/Client/qtTeamTalk/prismworker.cpp new file mode 100644 index 0000000000..f1eeccf7ab --- /dev/null +++ b/Client/qtTeamTalk/prismworker.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023, Bjørn D. Rasmussen, BearWare.dk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "prismworker.h" + +#if defined(ENABLE_PRISM) + +PrismWorker::PrismWorker(QObject* parent) + : QObject(parent) +{ +} + +PrismWorker::~PrismWorker() +{ + shutdown(); +} + +void PrismWorker::initialize(quint64 backendId) +{ + if (m_backend) + { + prism_backend_free(m_backend); + m_backend = nullptr; + } + if (m_context) + { + prism_shutdown(m_context); + m_context = nullptr; + } + + PrismConfig cfg = prism_config_init(); + m_context = prism_init(&cfg); + if (!m_context) + return; + + PrismBackendId id = static_cast(backendId); + if (id != PRISM_BACKEND_INVALID) + m_backend = prism_registry_create(m_context, id); + else + m_backend = prism_registry_create_best(m_context); + + if (m_backend) + prism_backend_initialize(m_backend); +} + +void PrismWorker::speak(const QString& text, bool interrupt) +{ + if (m_backend) + prism_backend_speak(m_backend, text.toUtf8().constData(), interrupt); +} + +void PrismWorker::braille(const QString& text) +{ + if (m_backend) + prism_backend_braille(m_backend, text.toUtf8().constData()); +} + +void PrismWorker::output(const QString& text, bool interrupt) +{ + if (m_backend) + prism_backend_output(m_backend, text.toUtf8().constData(), interrupt); +} + +void PrismWorker::shutdown() +{ + if (m_backend) + { + prism_backend_free(m_backend); + m_backend = nullptr; + } + if (m_context) + { + prism_shutdown(m_context); + m_context = nullptr; + } +} + +#endif // ENABLE_PRISM diff --git a/Client/qtTeamTalk/prismworker.h b/Client/qtTeamTalk/prismworker.h new file mode 100644 index 0000000000..2ad274dcae --- /dev/null +++ b/Client/qtTeamTalk/prismworker.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023, Bjørn D. Rasmussen, BearWare.dk + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PRISMWORKER_H +#define PRISMWORKER_H + +#if defined(ENABLE_PRISM) + +#include +#include +#include + +// Runs all blocking Prism calls (initialize, speak, braille, output) in a +// dedicated thread so they cannot freeze the main/UI thread. +class PrismWorker : public QObject +{ + Q_OBJECT + +public: + explicit PrismWorker(QObject* parent = nullptr); + ~PrismWorker(); + +public slots: + void initialize(quint64 backendId); + void speak(const QString& text, bool interrupt); + void braille(const QString& text); + void output(const QString& text, bool interrupt); + void shutdown(); + +private: + PrismContext* m_context = nullptr; + PrismBackend* m_backend = nullptr; +}; + +#endif // ENABLE_PRISM +#endif // PRISMWORKER_H diff --git a/Client/qtTeamTalk/settings.h b/Client/qtTeamTalk/settings.h index f94687a1b7..70da7bf1da 100644 --- a/Client/qtTeamTalk/settings.h +++ b/Client/qtTeamTalk/settings.h @@ -413,14 +413,13 @@ #define SETTINGS_TTS_RATE_DEFAULT 0.0 #define SETTINGS_TTS_VOLUME "texttospeech/tts-volume" #define SETTINGS_TTS_VOLUME_DEFAULT 0.5 -#if defined(Q_OS_WIN) -#define SETTINGS_TTS_SAPI "texttospeech/force-sapi" -#define SETTINGS_TTS_SAPI_DEFAULT false -#define SETTINGS_TTS_TRY_SAPI "texttospeech/try-sapi" -#define SETTINGS_TTS_TRY_SAPI_DEFAULT true -#define SETTINGS_TTS_OUTPUT_MODE "texttospeech/output-mode" -#define SETTINGS_TTS_OUTPUT_MODE_DEFAULT TTS_OUTPUTMODE_SPEECHBRAILLE -#elif defined(Q_OS_DARWIN) +#define SETTINGS_TTS_PRISM_BACKEND "texttospeech/prism-backend" +#define SETTINGS_TTS_PRISM_BACKEND_DEFAULT 0 +#define SETTINGS_TTS_OUTPUT_MODE "texttospeech/output-mode" +#define SETTINGS_TTS_OUTPUT_MODE_DEFAULT TTS_OUTPUTMODE_SPEECHBRAILLE +#define SETTINGS_TTS_INTERRUPT "texttospeech/interrupt" +#define SETTINGS_TTS_INTERRUPT_DEFAULT false +#if defined(Q_OS_DARWIN) #define SETTINGS_TTS_SPEAKLISTS "texttospeech/speak-lists" #define SETTINGS_TTS_SPEAKLISTS_DEFAULT isScreenReaderActive() #endif diff --git a/Client/qtTeamTalk/utiltts.cpp b/Client/qtTeamTalk/utiltts.cpp index 769b96fa5a..9dc05d0324 100644 --- a/Client/qtTeamTalk/utiltts.cpp +++ b/Client/qtTeamTalk/utiltts.cpp @@ -16,6 +16,7 @@ */ #include "utiltts.h" +#include "prismworker.h" #include "settings.h" #include "common.h" #include "appinfo.h" @@ -41,6 +42,9 @@ extern QTextToSpeech* ttSpeech; #if QT_VERSION >= QT_VERSION_CHECK(6,8,0) extern QObject* announcerObject; #endif +#if defined(ENABLE_PRISM) +extern PrismWorker* prismWorker; +#endif extern NonDefaultSettings* ttSettings; @@ -97,22 +101,26 @@ void addTextToSpeechMessage(const QString& msg) ttSpeech->say(msg); #endif break; - case TTSENGINE_TOLK: -#if defined(ENABLE_TOLK) - Tolk_PreferSAPI(ttSettings->value(SETTINGS_TTS_SAPI, SETTINGS_TTS_SAPI_DEFAULT).toBool()); - Tolk_TrySAPI(ttSettings->value(SETTINGS_TTS_TRY_SAPI, SETTINGS_TTS_TRY_SAPI_DEFAULT).toBool()); - switch (ttSettings->value(SETTINGS_TTS_OUTPUT_MODE, SETTINGS_TTS_OUTPUT_MODE_DEFAULT).toInt()) + case TTSENGINE_PRISM: +#if defined(ENABLE_PRISM) + { + if (prismWorker) { + bool interrupt = ttSettings->value(SETTINGS_TTS_INTERRUPT, SETTINGS_TTS_INTERRUPT_DEFAULT).toBool(); + switch (ttSettings->value(SETTINGS_TTS_OUTPUT_MODE, SETTINGS_TTS_OUTPUT_MODE_DEFAULT).toInt()) + { case TTS_OUTPUTMODE_BRAILLE: - Tolk_Braille(_W(msg)); + QMetaObject::invokeMethod(prismWorker, "braille", Qt::QueuedConnection, Q_ARG(QString, msg)); break; case TTS_OUTPUTMODE_SPEECH: - Tolk_Speak(_W(msg)); + QMetaObject::invokeMethod(prismWorker, "speak", Qt::QueuedConnection, Q_ARG(QString, msg), Q_ARG(bool, interrupt)); break; case TTS_OUTPUTMODE_SPEECHBRAILLE: - Tolk_Output(_W(msg)); + QMetaObject::invokeMethod(prismWorker, "output", Qt::QueuedConnection, Q_ARG(QString, msg), Q_ARG(bool, interrupt)); break; + } } + } #endif break; case TTSENGINE_QTANNOUNCEMENT: @@ -157,13 +165,28 @@ void addTextToSpeechMessage(TextToSpeechEvent event, const QString& msg) bool isScreenReaderActive() { bool SRActive = false; -#if defined(ENABLE_TOLK) - bool tolkLoaded = Tolk_IsLoaded(); - if (!tolkLoaded) - Tolk_Load(); - SRActive = Tolk_DetectScreenReader() != nullptr; - if (!tolkLoaded) - Tolk_Unload(); +#if defined(ENABLE_PRISM) + PrismConfig cfg = prism_config_init(); + PrismContext* ctx = prism_init(&cfg); + if (ctx) + { + size_t count = prism_registry_count(ctx); + for (size_t i = 0; i < count; i++) + { + PrismBackendId id = prism_registry_id_at(ctx, i); + PrismBackend* backend = prism_registry_get(ctx, id); + if (backend) + { + uint64_t features = prism_backend_get_features(backend); + if (features & PRISM_BACKEND_IS_SUPPORTED_AT_RUNTIME) + { + SRActive = true; + break; + } + } + } + prism_shutdown(ctx); + } #elif defined(Q_OS_LINUX) QDBusInterface interface("org.a11y.Bus", "/org/a11y/bus", "org.a11y.Status", QDBusConnection::sessionBus()); if (interface.isValid()) diff --git a/Client/qtTeamTalk/utiltts.h b/Client/qtTeamTalk/utiltts.h index 9e77fd512e..7c58b42c08 100644 --- a/Client/qtTeamTalk/utiltts.h +++ b/Client/qtTeamTalk/utiltts.h @@ -22,10 +22,6 @@ #include #include -#if defined(ENABLE_TOLK) -#include -#endif - enum TextToSpeechEvent : qulonglong { TTS_NONE = 0x0, @@ -95,7 +91,7 @@ enum TextToSpeechEngine { TTSENGINE_NONE = 0, TTSENGINE_QT = 1, - TTSENGINE_TOLK = 2, + TTSENGINE_PRISM = 2, TTSENGINE_NOTIFY_OBSOLETE = 3, TTSENGINE_QTANNOUNCEMENT = 4, TTSENGINE_APPLESCRIPT = 5, diff --git a/Setup/Installer/Windows/TeamTalk5.iss b/Setup/Installer/Windows/TeamTalk5.iss index 05e3406d66..fa8c62e2c4 100644 --- a/Setup/Installer/Windows/TeamTalk5.iss +++ b/Setup/Installer/Windows/TeamTalk5.iss @@ -97,9 +97,6 @@ Source: "{#InstallDir}\Client\qtTeamTalk\windeployqt\*"; Excludes: "vc_redist.x6 Source: "{#InstallDir}\Client\qtTeamTalk\windeployqt\vc_redist.x64.exe"; DestDir: {tmp}; Components: client; Flags: deleteafterinstall; Check: Is64BitInstallMode; Source: "{#InstallDir}\Client\qtTeamTalk\TeamTalk5.exe"; DestDir: "{app}"; Components: client; Flags: ignoreversion; Check: Is64BitInstallMode; Source: "{#InstallDir}\Library\TeamTalk_DLL\TeamTalk5.dll"; DestDir: "{app}"; Components: client; Flags: ignoreversion; Check: Is64BitInstallMode; -Source: "{#InstallDir}\Client\qtTeamTalk\nvdaControllerClient64.dll"; DestDir: "{app}"; Components: client; Flags: ignoreversion; Check: Is64BitInstallMode; -Source: "{#InstallDir}\Client\qtTeamTalk\SAAPI64.dll"; DestDir: "{app}"; Components: client; Flags: ignoreversion; Check: Is64BitInstallMode; -Source: "{#InstallDir}\Client\qtTeamTalk\Tolk.dll"; DestDir: "{app}"; Components: client; Flags: ignoreversion; Check: Is64BitInstallMode; Source: "{#InstallDir}\Server\tt5svc.exe"; DestDir: "{app}"; Components: server; Flags: ignoreversion; Check: Is64BitInstallMode; Source: "{#InstallDir}\Server\tt5srv.exe"; DestDir: "{app}"; Components: server; Flags: ignoreversion; Check: Is64BitInstallMode; @@ -161,6 +158,10 @@ Root: HKCR; Subkey: ".tt"; ValueType: string; ValueData: "TeamTalk"; Flags: unin Root: HKCR; Subkey: "TeamTalk\DefaultIcon"; ValueType: none; ValueName: "InstallPath"; ValueData: "{app}" [InstallDelete] +; Delete Tolk DLLs from previous installations +Type: files; Name: "{app}\Tolk.dll" +Type: files; Name: "{app}\nvdaControllerClient64.dll" +Type: files; Name: "{app}\SAAPI64.dll" ; Delete pre version 5.17 language files Type: files; Name: "{app}\Languages\Bulgarian.qm" Type: files; Name: "{app}\Languages\Chinese_Simplified.qm" diff --git a/Setup/Portable/Makefile b/Setup/Portable/Makefile index c7637a7e1c..2bff189120 100644 --- a/Setup/Portable/Makefile +++ b/Setup/Portable/Makefile @@ -25,10 +25,6 @@ win32: rm -rf $(OUTDIR) mkdir -p $(OUTDIR) make OUTDIR=$(OUTDIR) license winserver winclient - cp $(INSTALLDIR)/Client/qtTeamTalk/dolapi32.dll $(OUTDIR)/Client/ - cp $(INSTALLDIR)/Client/qtTeamTalk/nvdaControllerClient32.dll $(OUTDIR)/Client/ - cp $(INSTALLDIR)/Client/qtTeamTalk/SAAPI32.dll $(OUTDIR)/Client/ - cp $(INSTALLDIR)/Client/qtTeamTalk/Tolk.dll $(OUTDIR)/Client/ make TT5DIST=$(TT5DIST) arczip win64: @@ -37,9 +33,6 @@ win64: rm -rf $(OUTDIR) mkdir -p $(OUTDIR) make OUTDIR=$(OUTDIR) license winserver winclient - cp $(INSTALLDIR)/Client/qtTeamTalk/nvdaControllerClient64.dll $(OUTDIR)/Client/ - cp $(INSTALLDIR)/Client/qtTeamTalk/SAAPI64.dll $(OUTDIR)/Client/ - cp $(INSTALLDIR)/Client/qtTeamTalk/Tolk.dll $(OUTDIR)/Client/ make TT5DIST=$(TT5DIST) arczip win32pro: