From 5f6d5ead9258d4a61d215c4811660bb46347ead7 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 31 Aug 2024 12:41:09 +0300 Subject: [PATCH] Add incomplete SRAL_Register/UnregisterKeyboardHooks to controle interrupt and pause speech by Ctrl and Shift. --- CMakeLists.txt | 5 ++ Examples/C/SRALExample.c | 2 +- Include/SRAL.h | 11 +++- SRC/AVSpeech.h | 4 ++ SRC/Engine.h | 8 +++ SRC/Jaws.h | 5 +- SRC/NVDA.h | 4 ++ SRC/SAPI.cpp | 8 +++ SRC/SAPI.h | 3 + SRC/SRAL.cpp | 135 ++++++++++++++++++++++++++++++++++++++- SRC/SpeechDispatcher.cpp | 21 +++++- SRC/SpeechDispatcher.h | 9 ++- SRC/UIA.h | 3 + 13 files changed, 208 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cf61430..f0d933d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,6 +70,11 @@ elseif (APPLE) set(CMAKE_CXX_COMPILER clang++) target_link_libraries(${PROJECT_NAME} "-framework Foundation" "-framework AVFoundation") target_link_libraries(${PROJECT_NAME}_test "-framework Foundation" "-framework AVFoundation") +else() + find_package(X11 REQUIRED) + target_link_libraries(${PROJECT_NAME} ${X11_LIBRARIES}) + target_link_libraries(${PROJECT_NAME}_test ${X11_LIBRARIES}) + endif() diff --git a/Examples/C/SRALExample.c b/Examples/C/SRALExample.c index 92ce0e1..fe44bba 100644 --- a/Examples/C/SRALExample.c +++ b/Examples/C/SRALExample.c @@ -24,7 +24,7 @@ int main(void) { printf("Failed to initialize SRAL library.\n"); return 1; } - + SRAL_RegisterKeyboardHooks(); // Speak some text if (SRAL_GetEngineFeatures(0) & SUPPORTS_SPEECH) { printf("Enter the text you want to be spoken:\n"); diff --git a/Include/SRAL.h b/Include/SRAL.h index e1959f5..788c1c3 100644 --- a/Include/SRAL.h +++ b/Include/SRAL.h @@ -358,9 +358,18 @@ extern "C" { + SRAL_API bool SRAL_RegisterKeyboardHooks(void); + + + + + SRAL_API void SRAL_UnregisterKeyboardHooks(void); + + + + #ifdef __cplusplus }// extern "C" #endif - #endif // SRAL_H_ \ No newline at end of file diff --git a/SRC/AVSpeech.h b/SRC/AVSpeech.h index a310707..c52d6e2 100644 --- a/SRC/AVSpeech.h +++ b/SRC/AVSpeech.h @@ -30,6 +30,10 @@ class AVSpeech : public Engine { uint64_t GetVoiceCount()override; const char* GetVoiceName(uint64_t index)override; bool SetVoice(uint64_t index)override; + int GetKeyFlags()override { + return HANDLE_NONE; + } + private: AVSpeechSynthesizerWrapper* obj; }; \ No newline at end of file diff --git a/SRC/Engine.h b/SRC/Engine.h index 46751a1..2c8ac70 100644 --- a/SRC/Engine.h +++ b/SRC/Engine.h @@ -2,6 +2,12 @@ #define SCREENREADER_H_ #pragma once #include +enum KeyboardFlags { + HANDLE_NONE = 0, + HANDLE_INTERRUPT = 2, + HANDLE_PAUSE_RESUME = 4 +}; + class Engine { public: virtual bool Speak(const char* text, bool interrupt) = 0; @@ -22,5 +28,7 @@ class Engine { virtual uint64_t GetVoiceCount() = 0; virtual const char* GetVoiceName(uint64_t index) = 0; virtual bool SetVoice(uint64_t index) = 0; + virtual int GetKeyFlags() = 0; + bool paused; }; #endif \ No newline at end of file diff --git a/SRC/Jaws.h b/SRC/Jaws.h index bf25c11..4810c9c 100644 --- a/SRC/Jaws.h +++ b/SRC/Jaws.h @@ -1,6 +1,5 @@ #ifndef JAWS_H_ #define JAWS_H_ -#ifdef _WIN32 #pragma once #include "../Dep/fsapi.h" #include "../Include/SRAL.h" @@ -35,9 +34,11 @@ class Jaws : public Engine { bool SetVoice(uint64_t index)override { return false; } + int GetKeyFlags()override { + return HANDLE_NONE; + } private: IJawsApi* JawsAPI = nullptr; }; -#endif #endif \ No newline at end of file diff --git a/SRC/NVDA.h b/SRC/NVDA.h index 4ef0067..5311418 100644 --- a/SRC/NVDA.h +++ b/SRC/NVDA.h @@ -35,6 +35,10 @@ class NVDA : public Engine { return false; } + int GetKeyFlags()override { + return HANDLE_NONE; + } + private: HINSTANCE lib = nullptr; typedef error_status_t(__stdcall* NVDAController_speakText)(const wchar_t*); diff --git a/SRC/SAPI.cpp b/SRC/SAPI.cpp index 8d96d1b..7b472a1 100644 --- a/SRC/SAPI.cpp +++ b/SRC/SAPI.cpp @@ -140,6 +140,11 @@ bool SAPI::Speak(const char* text, bool interrupt) { PCMData dat = { 0, 0 }; dat.data = (unsigned char*)final; dat.size = bytes; + if (this->paused) { + this->paused = false; + if (!interrupt) + player->resume(); + } g_dataQueue.push_back(dat); return true; } @@ -148,12 +153,15 @@ bool SAPI::StopSpeech() { if (player == nullptr)return false; g_dataQueue.clear(); player->stop(); + this->paused = false; return true; } bool SAPI::PauseSpeech() { + paused = true; return SUCCEEDED(player->pause()); } bool SAPI::ResumeSpeech() { + paused = false; return SUCCEEDED(player->resume()); } void SAPI::SetVolume(uint64_t value) { diff --git a/SRC/SAPI.h b/SRC/SAPI.h index 0bb0bf5..5441b37 100644 --- a/SRC/SAPI.h +++ b/SRC/SAPI.h @@ -29,6 +29,9 @@ class SAPI : public Engine { uint64_t GetVoiceCount()override; const char* GetVoiceName(uint64_t index)override; bool SetVoice(uint64_t index)override; + int GetKeyFlags()override { + return HANDLE_INTERRUPT | HANDLE_PAUSE_RESUME; + } private: blastspeak* instance = nullptr; diff --git a/SRC/SRAL.cpp b/SRC/SRAL.cpp index 8f13a8e..881afec 100644 --- a/SRC/SRAL.cpp +++ b/SRC/SRAL.cpp @@ -13,6 +13,8 @@ #include "AVSpeech.h" #else #include "SpeechDispatcher.h" +#include +#include #endif #include #include @@ -53,7 +55,6 @@ struct QueuedOutput { int time; Engine* engine; }; - std::vector g_delayedOutputs; bool g_delayOperation = false; bool g_outputThreadRunning = false; @@ -85,6 +86,136 @@ static void output_thread() { g_outputThreadRunning = false; } + + +bool g_keyboardHookThread = false; +#if defined(_WIN32) +static HHOOK g_keyboardHook; +bool g_shiftPressed = false; +static LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) { + if (nCode >= 0) { + KBDLLHOOKSTRUCT* pKeyInfo = (KBDLLHOOKSTRUCT*)lParam; + for (uint64_t i = 0; i < g_engines.size(); ++i) { + if (g_engines[i] == nullptr || !g_engines[i]->GetActive()) continue; + + if (wParam == WM_KEYDOWN) { + if ((pKeyInfo->vkCode == VK_LCONTROL || pKeyInfo->vkCode == VK_RCONTROL) && g_engines[i]->GetKeyFlags() & HANDLE_INTERRUPT) { + g_engines[i]->StopSpeech(); + } + else if ((pKeyInfo->vkCode == VK_LSHIFT || pKeyInfo->vkCode == VK_RSHIFT) && g_engines[i]->GetKeyFlags() & HANDLE_PAUSE_RESUME && g_shiftPressed == false) { + if (g_engines[i]->paused) + g_engines[i]->ResumeSpeech(); + else + g_engines[i]->PauseSpeech(); + g_shiftPressed = true; + } + } + else if (wParam == WM_KEYUP) { + if (pKeyInfo->vkCode == VK_LSHIFT || pKeyInfo->vkCode == VK_RSHIFT) { + g_shiftPressed = false; + } + } + } + } + return CallNextHookEx(g_keyboardHook, nCode, wParam, lParam); +} +static void hook_thread() { + g_keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProc, GetModuleHandle(NULL), 0); + if (g_keyboardHook == nullptr)return; + MSG msg; + + while (g_keyboardHookThread) { + if (GetMessageW(&msg, nullptr, 0, 0)) { + DispatchMessageW(&msg); + TranslateMessage(&msg); + } + } + UnhookWindowsHookEx(g_keyboardHook); +} +extern "C" SRAL_API bool SRAL_RegisterKeyboardHooks(void) { + if (g_keyboardHookThread)return g_keyboardHookThread; + g_keyboardHookThread = true; + std::thread t(hook_thread); + t.detach(); + Timer timer; + while (timer.elapsed() < 3000) { + Sleep(5); + if (g_keyboardHook != nullptr) { + return true; + } + } + return false; // Timeout: Hook is not set +} + +extern "C" SRAL_API void SRAL_UnregisterKeyboardHooks(void) { + PostMessage(0, WM_KEYUP, 0, 0); + g_keyboardHookThread = false; +} +#elif defined(__APPLE__) +extern "C" SRAL_API bool SRAL_RegisterKeyboardHooks(void) { + return false; +} +extern "C" SRAL_API void SRAL_UnregisterKeyboardHooks(void) { + return; +} +#else +Display* g_display = nullptr; +static void hook_thread() { + + g_display = XOpenDisplay(nullptr); + + if (g_display == nullptr)return; + int num_screens = ScreenCount(g_display); + for (int screen = 0; screen < num_screens; ++screen) { + Window root = RootWindow(g_display, screen); + XSelectInput(g_display, root, KeyPressMask | KeyReleaseMask); + XGrabKeyboard(g_display, root, False, GrabModeAsync, GrabModeAsync, CurrentTime); + } + while (g_keyboardHookThread) { + XEvent event; + + XNextEvent(g_display, &event); + for (uint64_t i = false; i < g_engines.size(); i += true) { + if (g_engines[i] == nullptr || !g_engines[i]->GetActive()) continue; + + if (event.type == KeyPress) { + if ((event.xkey.keycode == XK_Control_L || event.xkey.keycode == XK_Control_R) && g_engines[i]->GetKeyFlags() & HANDLE_INTERRUPT) { + g_engines[i]->StopSpeech(); + } + else if ((event.xkey.keycode == XK_Shift_L || event.xkey.keycode == XK_Shift_R) && g_engines[i]->GetKeyFlags() & HANDLE_PAUSE_RESUME) { + if (g_engines[i]->paused) + g_engines[i]->ResumeSpeech(); + else + g_engines[i]->PauseSpeech(); + } + } + } + } + XCloseDisplay(g_display); +} +extern "C" SRAL_API bool SRAL_RegisterKeyboardHooks(void) { + if (g_keyboardHookThread)return g_keyboardHookThread; + g_keyboardHookThread = true; + std::thread t(hook_thread); + t.detach(); + Timer timer; + while (timer.elapsed() < 3000) { + usleep(5000); + if (g_display != nullptr) { + return true; + } + } + return false; // Timeout: Hook is not set +} + +extern "C" SRAL_API void SRAL_UnregisterKeyboardHooks(void) { + g_keyboardHookThread = false; +} + +#endif + + + extern "C" SRAL_API bool SRAL_Initialize(int engines_exclude) { if (g_initialized)return true; #if defined(_WIN32) @@ -170,7 +301,7 @@ static void speech_engine_update() { break; } } - } + } #ifdef _WIN32 } #endif diff --git a/SRC/SpeechDispatcher.cpp b/SRC/SpeechDispatcher.cpp index 6e34451..5e27546 100644 --- a/SRC/SpeechDispatcher.cpp +++ b/SRC/SpeechDispatcher.cpp @@ -8,6 +8,9 @@ #define spd_get_voice_rate (*spd_get_voice_rate) #define spd_set_volume (*spd_set_volume) #define spd_get_volume (*spd_get_volume) +#define spd_pause_all (*spd_pause_all) +#define spd_resume_all (*spd_resume_all) + #include "SpeechDispatcher.h" #undef spd_get_default_address #undef spd_open2 @@ -19,6 +22,8 @@ #undef spd_get_voice_rate #undef spd_set_volume #undef spd_get_volume +#undef spd_pause_all +#undef spd_resume_all #include // For dlopen, dlsym, dlclose @@ -38,7 +43,8 @@ bool SpeechDispatcher::Initialize() { *(void**)&spd_get_voice_rate = dlsym(Lib, "spd_get_voice_rate"); *(void**)&spd_set_volume = dlsym(Lib, "spd_set_volume"); *(void**)&spd_get_volume = dlsym(Lib, "spd_get_volume"); - + *(void**)&spd_pause_all = dlsym(Lib, "spd_pause_all"); + *(void**)&spd_resume_all = dlsym(Lib, "spd_resume_all"); const auto* address = spd_get_default_address(nullptr); if (address == nullptr) { return false; @@ -67,6 +73,7 @@ bool SpeechDispatcher::Speak(const char* text, bool interrupt) { spd_stop(Speech); spd_cancel(Speech); } + this->paused = false; return spd_say(Speech, interrupt ? SPD_IMPORTANT : SPD_TEXT, text); } bool SpeechDispatcher::StopSpeech() { @@ -75,6 +82,18 @@ bool SpeechDispatcher::StopSpeech() { spd_cancel(Speech); return true; } +bool SpeechDispatcher::PauseSpeech() { + if (!GetActive())return false; + this->paused = true; + return spd_pause_all(Speech) == 0; +} +bool SpeechDispatcher::ResumeSpeech() { + if (!GetActive())return false; + this->paused = false; + return spd_resume_all(Speech) == 0; +} + + void SpeechDispatcher::SetVolume(uint64_t value) { if (!GetActive())return; spd_set_volume(Speech, value); diff --git a/SRC/SpeechDispatcher.h b/SRC/SpeechDispatcher.h index ada74b5..6d3be69 100644 --- a/SRC/SpeechDispatcher.h +++ b/SRC/SpeechDispatcher.h @@ -10,8 +10,8 @@ class SpeechDispatcher : public Engine { bool Speak(const char* text, bool interrupt)override; bool Braille(const char* text)override { return false; } bool StopSpeech()override; - bool PauseSpeech()override { return false; } - bool ResumeSpeech()override { return false; } + bool PauseSpeech()override; + bool ResumeSpeech()override; int GetNumber()override { return ENGINE_SPEECH_DISPATCHER; @@ -20,7 +20,7 @@ class SpeechDispatcher : public Engine { bool Initialize()override; bool Uninitialize()override; int GetFeatures()override { - return SUPPORTS_SPEECH | SUPPORTS_SPEECH_RATE | SUPPORTS_SPEECH_VOLUME; + return SUPPORTS_SPEECH | SUPPORTS_SPEECH_RATE | SUPPORTS_SPEECH_VOLUME | SUPPORTS_PAUSE_SPEECH; } void SetVolume(uint64_t)override; uint64_t GetVolume()override; @@ -35,6 +35,9 @@ class SpeechDispatcher : public Engine { bool SetVoice(uint64_t index)override { return false; } + int GetKeyFlags()override { + return HANDLE_INTERRUPT | HANDLE_PAUSE_RESUME; + } private: SPDConnection* Speech = nullptr; diff --git a/SRC/UIA.h b/SRC/UIA.h index 2ba0bc6..6ff48ec 100644 --- a/SRC/UIA.h +++ b/SRC/UIA.h @@ -36,6 +36,9 @@ class UIA : public Engine { bool SetVoice(uint64_t index)override { return false; } + int GetKeyFlags()override { + return HANDLE_NONE; + } private: IUIAutomation* pAutomation = nullptr;