Added screen reader support for linux

master
Mohammad Shoaib 2022-01-30 22:25:02 +05:30
parent fe011b239e
commit 4b7d011a7b
8 changed files with 202 additions and 71 deletions

View File

@ -34,7 +34,8 @@ namespace stardew_access.Game
{ {
if (!manuallyTriggered && prevTile != gt) if (!manuallyTriggered && prevTile != gt)
{ {
MainClass.screenReader.prevTextTile = " "; if(MainClass.screenReader!=null)
MainClass.screenReader.PrevTextTile = " ";
} }
bool isColliding = isCollidingAtTile(x, y); bool isColliding = isCollidingAtTile(x, y);

View File

@ -168,6 +168,11 @@ namespace stardew_access
original: AccessTools.Method(typeof(Game1), nameof(Game1.playSound)), original: AccessTools.Method(typeof(Game1), nameof(Game1.playSound)),
prefix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.PlaySoundPatch)) prefix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.PlaySoundPatch))
); );
harmony.Patch(
original: AccessTools.Method(typeof(InstanceGame), nameof(InstanceGame.Exit)),
prefix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.ExitEventPatch))
);
} }
} }
} }

View File

@ -6,20 +6,10 @@ using HarmonyLib;
using stardew_access.Patches; using stardew_access.Patches;
using AutoHotkey.Interop; using AutoHotkey.Interop;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using stardew_access.ScreenReader;
namespace stardew_access namespace stardew_access
{ {
public struct GoString
{
public string msg;
public long len;
public GoString(string msg, long len)
{
this.msg = msg;
this.len = len;
}
}
public class MainClass : Mod public class MainClass : Mod
{ {
private Harmony? harmony; private Harmony? harmony;
@ -33,7 +23,7 @@ namespace stardew_access
AutoHotkeyEngine ahk; AutoHotkeyEngine ahk;
public static string hudMessageQueryKey = ""; public static string hudMessageQueryKey = "";
public static Radar radarFeature; public static Radar radarFeature;
public static ScreenReader screenReader; public static ScreenReaderInterface? screenReader;
private static IModHelper _modHelper; private static IModHelper _modHelper;
public static IModHelper ModHelper public static IModHelper ModHelper
@ -41,17 +31,6 @@ namespace stardew_access
get{return _modHelper;} get{return _modHelper;}
} }
[DllImport("libspeechdwrapper.so")]
private static extern void Initialize();
[DllImport("libspeechdwrapper.so")]
private static extern void Speak(GoString text, bool interrupt);
[DllImport("libspeechdwrapper.so")]
private static extern void Close();
/********* /*********
** Public methods ** Public methods
*********/ *********/
@ -70,8 +49,7 @@ namespace stardew_access
if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
InitializeAutoHotKey(); InitializeAutoHotKey();
screenReader = new ScreenReader(); screenReader = new ScreenReaderController().Initialize();
screenReader.InitializeScreenReader();
CustomSoundEffects.Initialize(); CustomSoundEffects.Initialize();
@ -84,24 +62,26 @@ namespace stardew_access
#endregion #endregion
Initialize();
string text = "Testing";
Speak(new GoString(text,text.Length), false);
Close();
helper.Events.Input.ButtonPressed += this.OnButtonPressed; helper.Events.Input.ButtonPressed += this.OnButtonPressed;
helper.Events.GameLoop.UpdateTicked += this.onUpdateTicked; helper.Events.GameLoop.UpdateTicked += this.onUpdateTicked;
AppDomain.CurrentDomain.DomainUnload += OnExit;
AppDomain.CurrentDomain.ProcessExit += OnExit;
}
public void OnExit (object? sender, EventArgs? e)
{
// Don't if this ever gets called or not but, just in case if it does.
if(screenReader!=null)
screenReader.CloseScreenReader();
} }
/// <summary>Returns the Screen Reader class for other mods to use.</summary> /// <summary>Returns the Screen Reader class for other mods to use.</summary>
public override object GetApi() public override object GetApi()
{ {
return new ScreenReader(); return new ScreenReaderController().Initialize();
} }
private void onUpdateTicked(object sender, UpdateTickedEventArgs e) private void onUpdateTicked(object? sender, UpdateTickedEventArgs? e)
{ {
if (!Context.IsPlayerFree) if (!Context.IsPlayerFree)
return; return;
@ -129,7 +109,7 @@ namespace stardew_access
} }
} }
private void OnButtonPressed(object sender, ButtonPressedEventArgs e) private void OnButtonPressed(object? sender, ButtonPressedEventArgs? e)
{ {
if (!Context.IsPlayerFree) if (!Context.IsPlayerFree)
return; return;

View File

@ -338,6 +338,11 @@ namespace stardew_access.Patches
} }
} }
internal static void ExitEventPatch()
{
if(MainClass.screenReader!=null)
MainClass.screenReader.CloseScreenReader();
}
internal static void resetGlobalVars() internal static void resetGlobalVars()
{ {
currentLetterText = " "; currentLetterText = " ";

View File

@ -0,0 +1,24 @@
using System.Runtime.InteropServices;
namespace stardew_access.ScreenReader
{
public class ScreenReaderController{
public ScreenReaderInterface? Initialize(){
ScreenReaderInterface? ScreenReader = null;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)){
ScreenReaderWindows screenReaderWindows = new ScreenReaderWindows();
screenReaderWindows.InitializeScreenReader();
ScreenReader = screenReaderWindows;
} else if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)){
ScreenReaderLinux screenReaderLinux = new ScreenReaderLinux();
screenReaderLinux.InitializeScreenReader();
ScreenReader = screenReaderLinux;
}
return ScreenReader;
}
}
}

View File

@ -0,0 +1,50 @@
namespace stardew_access.ScreenReader
{
public interface ScreenReaderInterface{
public string PrevTextTile{
get;
set;
}
/// <summary>Initializes the screen reader.</summary>
public void InitializeScreenReader();
// <summary>Closes the screen reader, this is important, call this function when closing the game.</summary>
public void CloseScreenReader();
/// <summary>Speaks the text via the loaded screen reader (if any).</summary>
/// <param name="text">The text to be narrated.</param>
/// <param name="interrupt">Whether to skip the currently speaking text or not.</param>
public void Say(string text, bool interrupt);
/// <summary>Speaks the text via the loaded screen reader (if any).
/// <br/>Skips the text narration if the previously narrated text was the same as the one provided.</summary>
/// <param name="text">The text to be narrated.</param>
/// <param name="interrupt">Whether to skip the currently speaking text or not.</param>
public void SayWithChecker(string text, bool interrupt);
/// <summary>Speaks the text via the loaded screen reader (if any).
/// <br/>Skips the text narration if the previously narrated text was the same as the one provided.
/// <br/><br/>Use this when narrating hovered component in menus to avoid interference.</summary>
/// <param name="text">The text to be narrated.</param>
/// <param name="interrupt">Whether to skip the currently speaking text or not.</param>
public void SayWithMenuChecker(string text, bool interrupt);
/// <summary>Speaks the text via the loaded screen reader (if any).
/// <br/>Skips the text narration if the previously narrated text was the same as the one provided.
/// <br/><br/>Use this when narrating chat messages to avoid interference.</summary>
/// <param name="text">The text to be narrated.</param>
/// <param name="interrupt">Whether to skip the currently speaking text or not.</param>
public void SayWithChatChecker(string text, bool interrupt);
/// <summary>Speaks the text via the loaded screen reader (if any).
/// <br/>Skips the text narration if the previously narrated text was the same as the one provided.
/// <br/><br/>Use this when narrating texts based on tile position to avoid interference.</summary>
/// <param name="text">The text to be narrated.</param>
/// <param name="x">The X location of tile.</param>
/// <param name="y">The Y location of tile.</param>
/// <param name="interrupt">Whether to skip the currently speaking text or not.</param>
public void SayWithTileQuery(string text, int x, int y, bool interrupt);
}
}

View File

@ -0,0 +1,88 @@
using System.Runtime.InteropServices;
namespace stardew_access.ScreenReader
{
public struct GoString
{
public string msg;
public long len;
public GoString(string msg, long len)
{
this.msg = msg;
this.len = len;
}
}
public class ScreenReaderLinux : ScreenReaderInterface
{
[DllImport("libspeechdwrapper.so")]
private static extern void Initialize();
[DllImport("libspeechdwrapper.so")]
private static extern void Speak(GoString text, bool interrupt);
[DllImport("libspeechdwrapper.so")]
private static extern void Close();
public string prevText = "", prevTextTile = " ", prevChatText = "", prevMenuText = "";
public string PrevTextTile{
get{ return prevTextTile; }
set{ prevTextTile=value; }
}
public void InitializeScreenReader()
{
Initialize();
}
public void CloseScreenReader(){
Close();
}
public void Say(string text, bool interrupt)
{
GoString str = new GoString(text, text.Length);
Speak(str, interrupt);
}
public void SayWithChecker(string text, bool interrupt)
{
if (prevText != text)
{
prevText = text;
Say(text, interrupt);
}
}
public void SayWithMenuChecker(string text, bool interrupt)
{
if (prevMenuText != text)
{
prevMenuText = text;
Say(text, interrupt);
}
}
public void SayWithChatChecker(string text, bool interrupt)
{
if (prevChatText != text)
{
prevChatText = text;
Say(text, interrupt);
}
}
public void SayWithTileQuery(string text, int x, int y, bool interrupt)
{
string query = $"{text} x:{x} y:{y}";
if (prevTextTile != query)
{
prevTextTile = query;
Say(text, interrupt);
}
}
}
}

View File

@ -1,19 +1,19 @@
using AccessibleOutput; using AccessibleOutput;
using StardewModdingAPI;
using System.Runtime.InteropServices;
namespace stardew_access namespace stardew_access.ScreenReader
{ {
public class ScreenReader public class ScreenReaderWindows : ScreenReaderInterface
{ {
public IAccessibleOutput? screenReader = null; public IAccessibleOutput? screenReader = null;
public string prevText = "", prevTextTile = " ", prevChatText = "", prevMenuText = ""; public string prevText = "", prevTextTile = " ", prevChatText = "", prevMenuText = "";
/// <summary>Initializes the screen reader.</summary> public string PrevTextTile{
get{ return prevTextTile; }
set{ prevTextTile=value; }
}
public void InitializeScreenReader() public void InitializeScreenReader()
{ {
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return;
NvdaOutput? nvdaOutput = null; NvdaOutput? nvdaOutput = null;
JawsOutput? jawsOutput = null; JawsOutput? jawsOutput = null;
@ -46,13 +46,12 @@ namespace stardew_access
screenReader = jawsOutput; screenReader = jawsOutput;
else if (sapiOutput != null && sapiOutput.IsAvailable()) else if (sapiOutput != null && sapiOutput.IsAvailable())
screenReader = sapiOutput; screenReader = sapiOutput;
else
MainClass.monitor.Log($"Unable to load any screen reader!", LogLevel.Error);
} }
/// <summary>Speaks the text via the loaded screen reader (if any).</summary> public void CloseScreenReader(){
/// <param name="text">The text to be narrated.</param>
/// <param name="interrupt">Whether to skip the currently speaking text or not.</param> }
public void Say(string text, bool interrupt) public void Say(string text, bool interrupt)
{ {
if (screenReader == null) if (screenReader == null)
@ -61,10 +60,6 @@ namespace stardew_access
screenReader.Speak(text, interrupt); screenReader.Speak(text, interrupt);
} }
/// <summary>Speaks the text via the loaded screen reader (if any).
/// <br/>Skips the text narration if the previously narrated text was the same as the one provided.</summary>
/// <param name="text">The text to be narrated.</param>
/// <param name="interrupt">Whether to skip the currently speaking text or not.</param>
public void SayWithChecker(string text, bool interrupt) public void SayWithChecker(string text, bool interrupt)
{ {
if (screenReader == null) if (screenReader == null)
@ -77,11 +72,6 @@ namespace stardew_access
} }
} }
/// <summary>Speaks the text via the loaded screen reader (if any).
/// <br/>Skips the text narration if the previously narrated text was the same as the one provided.
/// <br/><br/>Use this when narrating hovered component in menus to avoid interference.</summary>
/// <param name="text">The text to be narrated.</param>
/// <param name="interrupt">Whether to skip the currently speaking text or not.</param>
public void SayWithMenuChecker(string text, bool interrupt) public void SayWithMenuChecker(string text, bool interrupt)
{ {
if (screenReader == null) if (screenReader == null)
@ -94,11 +84,6 @@ namespace stardew_access
} }
} }
/// <summary>Speaks the text via the loaded screen reader (if any).
/// <br/>Skips the text narration if the previously narrated text was the same as the one provided.
/// <br/><br/>Use this when narrating chat messages to avoid interference.</summary>
/// <param name="text">The text to be narrated.</param>
/// <param name="interrupt">Whether to skip the currently speaking text or not.</param>
public void SayWithChatChecker(string text, bool interrupt) public void SayWithChatChecker(string text, bool interrupt)
{ {
if (screenReader == null) if (screenReader == null)
@ -111,13 +96,6 @@ namespace stardew_access
} }
} }
/// <summary>Speaks the text via the loaded screen reader (if any).
/// <br/>Skips the text narration if the previously narrated text was the same as the one provided.
/// <br/><br/>Use this when narrating texts based on tile position to avoid interference.</summary>
/// <param name="text">The text to be narrated.</param>
/// <param name="x">The X location of tile.</param>
/// <param name="y">The Y location of tile.</param>
/// <param name="interrupt">Whether to skip the currently speaking text or not.</param>
public void SayWithTileQuery(string text, int x, int y, bool interrupt) public void SayWithTileQuery(string text, int x, int y, bool interrupt)
{ {
if (screenReader == null) if (screenReader == null)