diff --git a/extras/controllerClient/readme.md b/extras/controllerClient/readme.md index 240457eaf91..a4b6c5784e9 100644 --- a/extras/controllerClient/readme.md +++ b/extras/controllerClient/readme.md @@ -8,12 +8,18 @@ The client API is implemented as a dll (dynamic link library). The functions in ## Compatibility notice +Version 3.0 of the controller client was introduced in NVDA 2026.3 and provides the following new function: + +* `nvdaController_isSpeaking` + +On older versions, this returns `RPC_S_UNKNOWN_IF` (`1717`). + Version 2.0 of the controller client was introduced in NVDA 2024.1, offering the following additional functions compared to version 1.0: -* nvdaController_getProcessId -* nvdaController_speakSsml +* `nvdaController_getProcessId` +* `nvdaController_speakSsml` -These functions are supported in NVDA 2024.1 and newer. On older versions, they return error code 1717 (RPC_S_UNKNOWN_IF). +These functions are supported in NVDA 2024.1 and newer. On older versions, they return error code `1717` (`RPC_S_UNKNOWN_IF`). ## Security practices diff --git a/nvdaHelper/client/client.cpp b/nvdaHelper/client/client.cpp index 5913f0c4aab..8a71d2b23ac 100644 --- a/nvdaHelper/client/client.cpp +++ b/nvdaHelper/client/client.cpp @@ -46,9 +46,17 @@ BOOL WINAPI DllMain(HINSTANCE hModule,DWORD reason,LPVOID lpReserved) { if (RPC_S_OK != status) { return FALSE; } + status = RpcBindingFromStringBinding(rpcWstr, &nvdaController3BindingHandle); + if (RPC_S_OK != status) { + return FALSE; + } } else if(reason==DLL_PROCESS_DETACH) { - RpcBindingFree(&nvdaController2BindingHandle); - RpcBindingFree(&nvdaControllerBindingHandle); + if (nvdaController3BindingHandle) + RpcBindingFree(&nvdaController3BindingHandle); + if (nvdaController2BindingHandle) + RpcBindingFree(&nvdaController2BindingHandle); + if (nvdaControllerBindingHandle) + RpcBindingFree(&nvdaControllerBindingHandle); } return TRUE; } diff --git a/nvdaHelper/client/nvdaControllerClient.def b/nvdaHelper/client/nvdaControllerClient.def index 8c53a07512a..c9fed355de0 100644 --- a/nvdaHelper/client/nvdaControllerClient.def +++ b/nvdaHelper/client/nvdaControllerClient.def @@ -1,7 +1,7 @@ ;;; ;This file is a part of the NVDA project. ;URL: http://www.nvda-project.org/ -;Copyright 2006-2023 NV Access Limited, Leonard de Ruijter. +;Copyright 2006-2026 NV Access Limited, Leonard de Ruijter, Ethin Probst. ;This program is free software: you can redistribute it and/or modify ;it under the terms of the GNU Lesser General Public License version 2.1, as published by ;the Free Software Foundation. @@ -20,3 +20,4 @@ EXPORTS nvdaController_getProcessId nvdaController_speakSsml nvdaController_setOnSsmlMarkReachedCallback + nvdaController_isSpeaking diff --git a/nvdaHelper/interfaces/nvdaController/nvdaController.acf b/nvdaHelper/interfaces/nvdaController/nvdaController.acf index a227ee287d2..99f6ab7b13e 100644 --- a/nvdaHelper/interfaces/nvdaController/nvdaController.acf +++ b/nvdaHelper/interfaces/nvdaController/nvdaController.acf @@ -1,7 +1,7 @@ /* This file is a part of the NVDA project. URL: http://www.nvda-project.org/ -Copyright 2006-2023 NV Access Limited, Leonard de Ruijter. +Copyright 2006-2026 NV Access Limited, Leonard de Ruijter, Ethin Probst. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 2.1, as published by the Free Software Foundation. @@ -30,3 +30,10 @@ interface NvdaController2 { [fault_status,comm_status] speakSsml(); [fault_status,comm_status] onSsmlMarkReached(); } + +[ + implicit_handle(handle_t nvdaController3BindingHandle) +] +interface NvdaController3 { + [fault_status,comm_status] isSpeaking(); +} diff --git a/nvdaHelper/interfaces/nvdaController/nvdaController.idl b/nvdaHelper/interfaces/nvdaController/nvdaController.idl index 82b65638365..462396950b5 100644 --- a/nvdaHelper/interfaces/nvdaController/nvdaController.idl +++ b/nvdaHelper/interfaces/nvdaController/nvdaController.idl @@ -1,7 +1,7 @@ /* This file is a part of the NVDA project. URL: http://www.nvda-project.org/ -Copyright 2006-2023 NV Access Limited, Leonard de Ruijter. +Copyright 2006-2026 NV Access Limited, Leonard de Ruijter, Ethin Probst. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 2.1, as published by the Free Software Foundation. @@ -146,3 +146,15 @@ interface NvdaController2 { */ [callback] error_status_t __stdcall onSsmlMarkReached([in, string] const wchar_t* mark); }; + +[ + uuid(019e4216-a9a1-7540-a217-e1a2d2793025), + version(1.0), +] +interface NvdaController3 { +/** + * Retrieves whether NVDA is speaking or not + * @param speaking Out parameter that receives the speaking status. + */ + error_status_t __stdcall isSpeaking([out] boolean* speaking); +}; diff --git a/nvdaHelper/local/nvdaController.cpp b/nvdaHelper/local/nvdaController.cpp index e1017f7fca1..acfb9324ab2 100644 --- a/nvdaHelper/local/nvdaController.cpp +++ b/nvdaHelper/local/nvdaController.cpp @@ -1,7 +1,7 @@ /* This file is a part of the NVDA project. URL: http://www.nvda-project.org/ -Copyright 2006-2023 NV Access Limited, Leonard de Ruijter. +Copyright 2006-2026 NV Access Limited, Leonard de Ruijter, Ethin Probst. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 2.0, as published by the Free Software Foundation. @@ -62,3 +62,11 @@ error_status_t __stdcall nvdaController_getProcessId(unsigned long* pid) { error_status_t __stdcall nvdaController_testIfRunning() { return ERROR_SUCCESS; } + +error_status_t(__stdcall *_nvdaController_isSpeaking)(boolean*) = nullptr; + +error_status_t __stdcall nvdaController_isSpeaking(boolean* isSpeaking) { + if (isSpeaking == nullptr) return ERROR_INVALID_PARAMETER; + if (_nvdaController_isSpeaking == nullptr) return ERROR_CALL_NOT_IMPLEMENTED; + return _nvdaController_isSpeaking(isSpeaking); +} diff --git a/nvdaHelper/local/nvdaHelperLocal.def b/nvdaHelper/local/nvdaHelperLocal.def index 48121920414..671f42638e7 100644 --- a/nvdaHelper/local/nvdaHelperLocal.def +++ b/nvdaHelper/local/nvdaHelperLocal.def @@ -52,6 +52,7 @@ EXPORTS _nvdaController_cancelSpeech DATA _nvdaController_speakText DATA _nvdaController_speakSsml DATA + _nvdaController_isSpeaking DATA nvdaController_onSsmlMarkReached _nvdaControllerInternal_vbufChangeNotify DATA displayModel_getWindowTextInRect diff --git a/nvdaHelper/local/rpcSrv.cpp b/nvdaHelper/local/rpcSrv.cpp index 0a5418670cc..3b8a2f59887 100644 --- a/nvdaHelper/local/rpcSrv.cpp +++ b/nvdaHelper/local/rpcSrv.cpp @@ -1,7 +1,7 @@ /* This file is a part of the NVDA project. URL: http://www.nvda-project.org/ -Copyright 2006-2010 NVDA contributers. +Copyright 2006-2026 NVDA contributors. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 2.0, as published by the Free Software Foundation. @@ -29,6 +29,7 @@ typedef RPC_STATUS(RPC_ENTRY *RpcServerRegisterIf3_functype)(RPC_IF_HANDLE,UUID RPC_IF_HANDLE availableInterfaces[]={ nvdaController_NvdaController_v1_0_s_ifspec, nvdaController_NvdaController2_v1_0_s_ifspec, + nvdaController_NvdaController3_v1_0_s_ifspec, nvdaControllerInternal_NvdaControllerInternal_v1_0_s_ifspec }; diff --git a/source/NVDAHelper/__init__.py b/source/NVDAHelper/__init__.py index e15d0ee52b1..fb0ea6c54db 100755 --- a/source/NVDAHelper/__init__.py +++ b/source/NVDAHelper/__init__.py @@ -30,6 +30,7 @@ create_unicode_buffer, windll, wstring_at, + _Pointer, ) from winBindings import user32 @@ -196,6 +197,16 @@ def nvdaController_brailleMessage(text: str) -> SystemErrorCodes: return SystemErrorCodes.SUCCESS +@WINFUNCTYPE(c_long, POINTER(c_bool)) +def nvdaController_isSpeaking(pSpeaking: _Pointer[c_bool]) -> int: + if not pSpeaking: + return SystemErrorCodes.INVALID_PARAMETER.value + import speech + + pSpeaking[0] = speech.isSpeaking() + return SystemErrorCodes.SUCCESS.value + + def _lookupKeyboardLayoutNameWithHexString(layoutString): buf = create_unicode_buffer(1024) bufSize = c_ulong(2048) @@ -787,6 +798,7 @@ def initialize() -> None: ("nvdaController_speakSsml", nvdaController_speakSsml), ("nvdaController_cancelSpeech", nvdaController_cancelSpeech), ("nvdaController_brailleMessage", nvdaController_brailleMessage), + ("nvdaController_isSpeaking", nvdaController_isSpeaking), ("nvdaControllerInternal_requestRegistration", nvdaControllerInternal_requestRegistration), ("nvdaControllerInternal_reportLiveRegion", nvdaControllerInternal_reportLiveRegion), ("nvdaControllerInternal_inputLangChangeNotify", nvdaControllerInternal_inputLangChangeNotify), diff --git a/source/speech/__init__.py b/source/speech/__init__.py index 478091f7f84..b2f0d343523 100644 --- a/source/speech/__init__.py +++ b/source/speech/__init__.py @@ -38,6 +38,7 @@ IDT_MAX_SPACES, getIndentToneDuration, isBlank, + isSpeaking, LANGS_WITH_CONJUNCT_CHARS, pauseSpeech, processText, @@ -118,6 +119,7 @@ "IDT_MAX_SPACES", "getIndentToneDuration", "isBlank", + "isSpeaking", "LANGS_WITH_CONJUNCT_CHARS", "pauseSpeech", "processText", diff --git a/source/speech/speech.py b/source/speech/speech.py index 3b3135af71b..880c24ed6cf 100644 --- a/source/speech/speech.py +++ b/source/speech/speech.py @@ -3179,3 +3179,16 @@ def clearTypedWordBuffer() -> None: complete the word (such as a focus change or choosing to move the caret). """ _curWordChars.clear() + + +def isSpeaking() -> bool: + """Whether NVDA is currently producing speech audio. + True if the synth driver has reported it is mid-utterance + and speech is neither paused nor disabled. + """ + state = getState() + if state.speechMode in (SpeechMode.off, SpeechMode.beeps): + return False + if state.isPaused: + return False + return _manager._synthStillSpeaking()