Establishes the mod skeleton and gets speech working end to end. Game-file footprint: one line in main.lua. - accessibility/init.lua: sole entry point. Patch registry, hooks states.switch (announces screen changes) and states.update (per-frame speech flush, avoiding an edit to love.update). Startup self-test prints what resolved -- the canary for when a future game version moves something we hook. - accessibility/speech.lua: TTS wrapper. pcall-guarded so a missing DLL degrades to silence instead of crashing. Queues utterances within a frame and joins them, since one game turn emits several console.print calls and speaking each with interrupt would leave only the last audible. - accessibility/stateNames.lua: spoken names for the 28 states. - accessibility/vendor/love2talk: vendored TTS library, see VENDORED.md. Loads Tolk.dll by absolute path via love.filesystem.getSource() and adds that directory via SetDllDirectory, so the DLLs live in the project instead of needing an elevated copy into Program Files. Verified with lovec against NVDA: TTS available, speech channel yes, 'Main menu' spoken on startup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
107 lines
2.7 KiB
Lua
107 lines
2.7 KiB
Lua
local ffi = require "ffi"
|
|
local encoding = require "accessibility.vendor.love2talk.encoding"
|
|
ffi.cdef[[
|
|
void Tolk_Load();
|
|
void Tolk_Output(const char *s, bool interrupt);
|
|
void Tolk_Silence();
|
|
void Tolk_Speak(const char *s, bool interrupt);
|
|
void Tolk_Braille(const char *s, bool interrupt);
|
|
void Tolk_TrySAPI(bool try);
|
|
void Tolk_PreferSAPI(bool prefer);
|
|
const wchar_t * Tolk_DetectScreenReader();
|
|
bool Tolk_HasSpeech();
|
|
bool Tolk_HasBraille();
|
|
bool Tolk_IsSpeaking();]]
|
|
|
|
-- LOCAL MODIFICATION: load Tolk from the mod's own lib/ directory.
|
|
--
|
|
-- ffi.load() goes through the Windows loader, which does not know about LOVE's
|
|
-- virtual filesystem. Upstream relies on Tolk.dll sitting next to love.exe, which
|
|
-- needs admin rights to arrange. Instead we resolve the real OS path via
|
|
-- love.filesystem.getSource() and load by absolute path.
|
|
--
|
|
-- Tolk.dll in turn loads the screen-reader clients (nvdaControllerClient64.dll,
|
|
-- SAAPI64.dll) itself, and those go through the normal search order — so we must
|
|
-- also add our directory to the DLL search path via SetDllDirectory, or Tolk would
|
|
-- load but find no screen reader and silently fall back to SAPI.
|
|
local function loadTolk()
|
|
local ok, source = pcall(function()
|
|
return love.filesystem.getSource()
|
|
end)
|
|
|
|
if ok and source then
|
|
local libDir = source .. "\\accessibility\\lib"
|
|
|
|
pcall(function()
|
|
ffi.cdef [[ int SetDllDirectoryA(const char *lpPathName); ]]
|
|
ffi.C.SetDllDirectoryA(libDir)
|
|
end)
|
|
|
|
local loaded, handle = pcall(ffi.load, libDir .. "\\Tolk.dll")
|
|
if loaded then
|
|
return handle
|
|
end
|
|
end
|
|
|
|
-- Fall back to the normal search order: next to love.exe, or on PATH.
|
|
return ffi.load("Tolk")
|
|
end
|
|
|
|
local tolk = loadTolk()
|
|
tolk.Tolk_Load()
|
|
local function output(s, interrupt)
|
|
interrupt=interrupt or false
|
|
|
|
tolk.Tolk_Output(encoding.to_utf16(s), interrupt)
|
|
end
|
|
|
|
local function speak(s, interrupt)
|
|
interrupt=interrupt or false
|
|
|
|
tolk.Tolk_Speak(encoding.to_utf16(s), interrupt)
|
|
end
|
|
|
|
local function braille(s)
|
|
tolk.Tolk_Braille(encoding.to_utf16(s))
|
|
end
|
|
|
|
local function silence()
|
|
tolk.Tolk_Silence()
|
|
end
|
|
|
|
local function trySAPI(try)
|
|
tolk.Tolk_TrySAPI(try)
|
|
end
|
|
|
|
local function preferSAPI(prefer)
|
|
tolk.Tolk_PreferSAPI(prefer)
|
|
end
|
|
local function isSpeaking()
|
|
return tolk.Tolk_IsSpeaking()
|
|
end
|
|
|
|
local function detectScreenReader()
|
|
--todo, need to convert the returned value to something Lua likes
|
|
end
|
|
|
|
local function hasSpeech()
|
|
return tolk.Tolk_HasSpeech()
|
|
end
|
|
|
|
local function hasBraille()
|
|
return tolk.Tolk_HasBraille()
|
|
end
|
|
|
|
|
|
return {
|
|
output=output,
|
|
speak=speak,
|
|
braille=braille,
|
|
silence=silence,
|
|
isSpeaking=isSpeaking,
|
|
trySAPI=trySAPI,
|
|
preferSAPI=preferSAPI,
|
|
detectScreenReader=detectScreenReader,
|
|
hasSpeech=hasSpeech,
|
|
hasBraille=hasBraille}
|