From 84293db6dc1c19ab1fde3bf9c91f3bf5e384a7bd Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Fri, 25 Jul 2025 14:17:17 -0400 Subject: [PATCH 1/8] Another shot at fixing multiple numpads. --- .../commands/onKeyInput/80500-numlock.py | 21 ++++++++++++------- src/fenrirscreenreader/fenrirVersion.py | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/fenrirscreenreader/commands/onKeyInput/80500-numlock.py b/src/fenrirscreenreader/commands/onKeyInput/80500-numlock.py index 429ffe26..4eb2ec0f 100644 --- a/src/fenrirscreenreader/commands/onKeyInput/80500-numlock.py +++ b/src/fenrirscreenreader/commands/onKeyInput/80500-numlock.py @@ -24,14 +24,19 @@ class command: def run(self): if self.env["input"]["oldNumLock"] == self.env["input"]["newNumLock"]: return - if self.env["input"]["newNumLock"]: - self.env["runtime"]["OutputManager"].present_text( - _("Numlock on"), interrupt=True - ) - else: - self.env["runtime"]["OutputManager"].present_text( - _("Numlock off"), interrupt=True - ) + + # Only announce numlock changes if an actual numlock key was pressed + # This prevents spurious announcements from external numpad automatic state changes + current_input = self.env["input"]["currInput"] + if current_input and "KEY_NUMLOCK" in current_input: + if self.env["input"]["newNumLock"]: + self.env["runtime"]["OutputManager"].present_text( + _("Numlock on"), interrupt=True + ) + else: + self.env["runtime"]["OutputManager"].present_text( + _("Numlock off"), interrupt=True + ) def set_callback(self, callback): pass diff --git a/src/fenrirscreenreader/fenrirVersion.py b/src/fenrirscreenreader/fenrirVersion.py index 7da2f324..05bc9d62 100644 --- a/src/fenrirscreenreader/fenrirVersion.py +++ b/src/fenrirscreenreader/fenrirVersion.py @@ -4,6 +4,6 @@ # Fenrir TTY screen reader # By Chrys, Storm Dragon, and contributors. -version = "2025.07.24" +version = "2025.07.25" codeName = "testing" code_name = "testing" From 9ef9d762f49df632db58c5097fbba712fcdc222d Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Thu, 31 Jul 2025 03:16:02 -0400 Subject: [PATCH 2/8] A few emoji updates. --- .../KEY/emoji/holidays/alien_monster.py | 22 +++++++++++++++++ .../KEY/emoji/holidays/cauldron.py | 4 ++-- .../KEY/emoji/holidays/coffin.py | 22 +++++++++++++++++ .../KEY/emoji/holidays/mummy.py | 22 +++++++++++++++++ .../vmenu-profiles/KEY/emoji/holidays/web.py | 22 +++++++++++++++++ .../KEY/emoji/holidays/zombie.py | 22 +++++++++++++++++ .../vmenu-profiles/KEY/emoji/people/dizzy.py | 22 +++++++++++++++++ .../KEY/emoji/people/exploding_head.py | 22 +++++++++++++++++ .../KEY/emoji/people/face_with_symbols.py | 22 +++++++++++++++++ .../vmenu-profiles/KEY/emoji/people/imp.py | 22 +++++++++++++++++ .../KEY/emoji/people/mindblown.py | 23 ++++++++++++++++++ .../KEY/emoji/people/nauseated.py | 22 +++++++++++++++++ .../KEY/emoji/people/screaming.py | 22 +++++++++++++++++ .../KEY/emoji/symbols/chains.py | 22 +++++++++++++++++ .../KEY/emoji/symbols/crossbones.py | 22 +++++++++++++++++ .../KEY/emoji/symbols/dagger.py | 22 +++++++++++++++++ .../KEY/emoji/symbols/high_voltage.py | 22 +++++++++++++++++ .../vmenu-profiles/KEY/emoji/symbols/sword.py | 24 +++++++++++++++++++ src/fenrirscreenreader/fenrirVersion.py | 2 +- 19 files changed, 380 insertions(+), 3 deletions(-) create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/alien_monster.py create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/coffin.py create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/mummy.py create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/web.py create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/zombie.py create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/dizzy.py create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/exploding_head.py create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/face_with_symbols.py create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/imp.py create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/mindblown.py create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/nauseated.py create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/screaming.py create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/chains.py create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/crossbones.py create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/dagger.py create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/high_voltage.py create mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/sword.py diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/alien_monster.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/alien_monster.py new file mode 100644 index 00000000..5e749c99 --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/alien_monster.py @@ -0,0 +1,22 @@ +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "πŸ‘Ύ" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "Alien monster emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added alien monster to clipboard", + interrupt=False, flush=False + ) \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/cauldron.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/cauldron.py index 22426f64..61d67110 100644 --- a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/cauldron.py +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/cauldron.py @@ -10,13 +10,13 @@ class command(): pass def getDescription(self): - return "Magic cauldron emoji" + return "Mage emoji" def run(self): self.env["runtime"]["MemoryManager"].add_value_to_first_index( "clipboardHistory", self.emoji ) self.env["runtime"]["OutputManager"].present_text( - "Added magic cauldron to clipboard", + "Added mage to clipboard", interrupt=False, flush=False ) \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/coffin.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/coffin.py new file mode 100644 index 00000000..0011a3c2 --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/coffin.py @@ -0,0 +1,22 @@ +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "⚰️" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "Coffin emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added coffin to clipboard", + interrupt=False, flush=False + ) \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/mummy.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/mummy.py new file mode 100644 index 00000000..057503eb --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/mummy.py @@ -0,0 +1,22 @@ +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "🧟" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "Mummy emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added mummy to clipboard", + interrupt=False, flush=False + ) \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/web.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/web.py new file mode 100644 index 00000000..7c42a148 --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/web.py @@ -0,0 +1,22 @@ +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "πŸ•ΈοΈ" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "Spider web emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added spider web to clipboard", + interrupt=False, flush=False + ) \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/zombie.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/zombie.py new file mode 100644 index 00000000..dfdf7612 --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/holidays/zombie.py @@ -0,0 +1,22 @@ +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "🧟" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "Zombie emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added zombie to clipboard", + interrupt=False, flush=False + ) \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/dizzy.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/dizzy.py new file mode 100644 index 00000000..9c992f68 --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/dizzy.py @@ -0,0 +1,22 @@ +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "😡" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "Dizzy face emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added dizzy face to clipboard", + interrupt=False, flush=False + ) \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/exploding_head.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/exploding_head.py new file mode 100644 index 00000000..a8773423 --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/exploding_head.py @@ -0,0 +1,22 @@ +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "🀯" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "Exploding head emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added exploding head to clipboard", + interrupt=False, flush=False + ) \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/face_with_symbols.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/face_with_symbols.py new file mode 100644 index 00000000..934e9e15 --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/face_with_symbols.py @@ -0,0 +1,22 @@ +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "🀬" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "Face with symbols over mouth emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added face with symbols to clipboard", + interrupt=False, flush=False + ) \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/imp.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/imp.py new file mode 100644 index 00000000..671557e2 --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/imp.py @@ -0,0 +1,22 @@ +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "πŸ‘Ώ" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "Angry face with horns emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added angry face with horns to clipboard", + interrupt=False, flush=False + ) \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/mindblown.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/mindblown.py new file mode 100644 index 00000000..76b3908b --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/mindblown.py @@ -0,0 +1,23 @@ +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "🀯" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "Mind blown emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added mind blown to clipboard", + interrupt=False, flush=False + ) + diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/nauseated.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/nauseated.py new file mode 100644 index 00000000..2b8a0e64 --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/nauseated.py @@ -0,0 +1,22 @@ +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "🀒" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "Nauseated face emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added nauseated face to clipboard", + interrupt=False, flush=False + ) \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/screaming.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/screaming.py new file mode 100644 index 00000000..ccdbb9c6 --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/people/screaming.py @@ -0,0 +1,22 @@ +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "😱" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "Screaming face emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added screaming face to clipboard", + interrupt=False, flush=False + ) \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/chains.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/chains.py new file mode 100644 index 00000000..a441303e --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/chains.py @@ -0,0 +1,22 @@ +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "⛓️" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "Chains emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added chains to clipboard", + interrupt=False, flush=False + ) \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/crossbones.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/crossbones.py new file mode 100644 index 00000000..60123ff7 --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/crossbones.py @@ -0,0 +1,22 @@ +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "🦴" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "Bone emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added bone to clipboard", + interrupt=False, flush=False + ) \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/dagger.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/dagger.py new file mode 100644 index 00000000..4a641ad3 --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/dagger.py @@ -0,0 +1,22 @@ +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "πŸ—‘οΈ" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "Dagger emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added dagger to clipboard", + interrupt=False, flush=False + ) \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/high_voltage.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/high_voltage.py new file mode 100644 index 00000000..aba0e3ee --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/high_voltage.py @@ -0,0 +1,22 @@ +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "⚑" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "High voltage lightning bolt emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added high voltage lightning bolt to clipboard", + interrupt=False, flush=False + ) \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/sword.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/sword.py new file mode 100644 index 00000000..f0aefe4b --- /dev/null +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/emoji/symbols/sword.py @@ -0,0 +1,24 @@ + +class command(): + def initialize(self, environment): + self.env = environment + self.emoji = "βš”οΈ" + + def shutdown(self): + pass + + def setCallback(self, callback): + pass + + def getDescription(self): + return "Sword emoji" + + def run(self): + self.env["runtime"]["MemoryManager"].add_value_to_first_index( + "clipboardHistory", self.emoji + ) + self.env["runtime"]["OutputManager"].present_text( + "Added sword to clipboard", + interrupt=False, flush=False + ) + diff --git a/src/fenrirscreenreader/fenrirVersion.py b/src/fenrirscreenreader/fenrirVersion.py index 05bc9d62..b46f49b2 100644 --- a/src/fenrirscreenreader/fenrirVersion.py +++ b/src/fenrirscreenreader/fenrirVersion.py @@ -4,6 +4,6 @@ # Fenrir TTY screen reader # By Chrys, Storm Dragon, and contributors. -version = "2025.07.25" +version = "2025.07.31" codeName = "testing" code_name = "testing" From 73f67c2a048f4e2c658ef90f7920bb2160a5df73 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Mon, 4 Aug 2025 12:38:14 -0400 Subject: [PATCH 3/8] Multiple fixes to indentation beep code, including volume for lower ranged beeps using the gstreamer driver. --- ...-present_line_if_cursor_change_vertical.py | 7 ++- .../68000-auto_identation_horizontal.py | 63 ++++++++++--------- src/fenrirscreenreader/fenrirVersion.py | 2 +- .../soundDriver/gstreamerDriver.py | 17 ++++- 4 files changed, 55 insertions(+), 34 deletions(-) diff --git a/src/fenrirscreenreader/commands/onCursorChange/65000-present_line_if_cursor_change_vertical.py b/src/fenrirscreenreader/commands/onCursorChange/65000-present_line_if_cursor_change_vertical.py index 28f9d7ab..6d8577d7 100644 --- a/src/fenrirscreenreader/commands/onCursorChange/65000-present_line_if_cursor_change_vertical.py +++ b/src/fenrirscreenreader/commands/onCursorChange/65000-present_line_if_cursor_change_vertical.py @@ -60,9 +60,10 @@ class command: if self.env["runtime"]["SettingsManager"].get_setting_as_int( "general", "autoPresentIndentMode" ) in [0, 1]: - self.env["runtime"]["OutputManager"].play_frequence( - curr_ident * 50, 0.1, interrupt=do_interrupt - ) + if self.lastIdent != curr_ident: + self.env["runtime"]["OutputManager"].play_frequence( + curr_ident * 50, 0.1, interrupt=do_interrupt + ) if self.env["runtime"]["SettingsManager"].get_setting_as_int( "general", "autoPresentIndentMode" ) in [0, 2]: diff --git a/src/fenrirscreenreader/commands/onCursorChange/68000-auto_identation_horizontal.py b/src/fenrirscreenreader/commands/onCursorChange/68000-auto_identation_horizontal.py index 1392562c..947cc0b5 100644 --- a/src/fenrirscreenreader/commands/onCursorChange/68000-auto_identation_horizontal.py +++ b/src/fenrirscreenreader/commands/onCursorChange/68000-auto_identation_horizontal.py @@ -31,10 +31,9 @@ class command: self.lastIdent = 0 return - # is a vertical change? - if not self.env["runtime"][ - "CursorManager" - ].is_cursor_horizontal_move(): + # Skip if no cursor movement at all + if (not self.env["runtime"]["CursorManager"].is_cursor_horizontal_move() and + not self.env["runtime"]["CursorManager"].is_cursor_vertical_move()): return x, y, curr_line = line_utils.get_current_line( self.env["screen"]["new_cursor"]["x"], @@ -43,27 +42,34 @@ class command: ) curr_ident = self.env["screen"]["new_cursor"]["x"] - if not curr_line.isspace(): - # ident - lastIdent, lastY, last_line = line_utils.get_current_line( - self.env["screen"]["new_cursor"]["x"], - self.env["screen"]["new_cursor"]["y"], - self.env["screen"]["old_content_text"], - ) - if curr_line.strip() != last_line.strip(): - return - if len(curr_line.lstrip()) == len(last_line.lstrip()): - return + if curr_line.isspace(): + # Don't beep for lines with only spaces - no meaningful indentation + return + + # Lines with actual content - calculate proper indentation + lastIdent, lastY, last_line = line_utils.get_current_line( + self.env["screen"]["new_cursor"]["x"], + self.env["screen"]["new_cursor"]["y"], + self.env["screen"]["old_content_text"], + ) + if curr_line.strip() != last_line.strip(): + return + if len(curr_line.lstrip()) == len(last_line.lstrip()): + return - curr_ident = len(curr_line) - len(curr_line.lstrip()) + curr_ident = len(curr_line) - len(curr_line.lstrip()) - if self.lastIdent == -1: - self.lastIdent = curr_ident - if curr_ident <= 0: - return + if curr_ident <= 0: + return + + # Initialize lastIdent if needed + if self.lastIdent == -1: + self.lastIdent = curr_ident + + # Only beep/announce if indentation level has changed if self.env["runtime"]["SettingsManager"].get_setting_as_bool( "general", "autoPresentIndent" - ): + ) and self.lastIdent != curr_ident: if self.env["runtime"]["SettingsManager"].get_setting_as_int( "general", "autoPresentIndentMode" ) in [0, 1]: @@ -71,14 +77,15 @@ class command: curr_ident * 50, 0.1, interrupt=False ) if self.env["runtime"]["SettingsManager"].get_setting_as_int( - "general", "autoPresentIndentMode" + "general", "autePresentIndentMode" ) in [0, 2]: - if self.lastIdent != curr_ident: - self.env["runtime"]["OutputManager"].present_text( - _("indented ") + str(curr_ident) + " ", - interrupt=False, - flush=False, - ) + self.env["runtime"]["OutputManager"].present_text( + _("indented ") + str(curr_ident) + " ", + interrupt=False, + flush=False, + ) + + # Always update lastIdent for next comparison self.lastIdent = curr_ident def set_callback(self, callback): diff --git a/src/fenrirscreenreader/fenrirVersion.py b/src/fenrirscreenreader/fenrirVersion.py index b46f49b2..bd44604a 100644 --- a/src/fenrirscreenreader/fenrirVersion.py +++ b/src/fenrirscreenreader/fenrirVersion.py @@ -4,6 +4,6 @@ # Fenrir TTY screen reader # By Chrys, Storm Dragon, and contributors. -version = "2025.07.31" +version = "2025.08.04" codeName = "testing" code_name = "testing" diff --git a/src/fenrirscreenreader/soundDriver/gstreamerDriver.py b/src/fenrirscreenreader/soundDriver/gstreamerDriver.py index 8889cd12..27e3064c 100644 --- a/src/fenrirscreenreader/soundDriver/gstreamerDriver.py +++ b/src/fenrirscreenreader/soundDriver/gstreamerDriver.py @@ -52,10 +52,13 @@ class driver(sound_driver): bus.connect("message", self._on_pipeline_message) self._source = Gst.ElementFactory.make("audiotestsrc", "src") + self._volume = Gst.ElementFactory.make("volume", "volume") self._sink = Gst.ElementFactory.make("autoaudiosink", "output") self._pipeline.add(self._source) + self._pipeline.add(self._volume) self._pipeline.add(self._sink) - self._source.link(self._sink) + self._source.link(self._volume) + self._volume.link(self._sink) self.mainloop = GLib.MainLoop() self.thread = threading.Thread(target=self.mainloop.run) self.thread.start() @@ -117,8 +120,18 @@ class driver(sound_driver): return if interrupt: self.cancel() + # Always reset pipeline to prevent volume accumulation + self._pipeline.set_state(Gst.State.NULL) duration = duration * 1000 - self._source.set_property("volume", self.volume * adjust_volume) + # Use dedicated volume element for better control + # GStreamer volume property behaves very differently than sox for low frequencies + if adjust_volume > 0.8: # This indicates low frequency indentation beeps + # Extremely aggressive boost - GStreamer really struggles with low frequencies + effective_volume = self.volume * adjust_volume * 50.0 # Ridiculous multiplier to match sox + else: + effective_volume = self.volume * adjust_volume * 3.0 + self._volume.set_property("volume", effective_volume) + self._source.set_property("volume", 1.0) # Set source to full, control via volume element self._source.set_property("freq", frequence) self._pipeline.set_state(Gst.State.PLAYING) GLib.timeout_add(duration, self._on_timeout, self._pipeline) From 69eade33272309d270ac43df8abd16999f5f1867 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Mon, 4 Aug 2025 14:24:41 -0400 Subject: [PATCH 4/8] Updated documentation for stand-alone pipewire or pulse configuration scripts. --- README.md | 55 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 48a2cd79..5a6eda25 100644 --- a/README.md +++ b/README.md @@ -640,30 +640,57 @@ send_fenrir_command("setting set speech#rate=0.9") - Check Fenrir debug logs: `/var/log/fenrir.log` - Test with simple command: `echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock` -## Configure pulseaudio +## Audio Configuration -Pulseaudio by default only plays sound for the user its currently running for. As fenrir is running as root, your local user does not hear the sound and speech produced by fenrir. -for this fenrir provides a script to configure pulseaudio to stream the sound played as root to your local user. This is not a issue of fenrir but this is how pulseaudio works. +Both PulseAudio and PipeWire require special configuration to allow console applications running as root (like Fenrir) to route audio to your regular user session. This is normal audio system behavior, not a Fenrir issue. -just run the configuration script twice (once as user, once as root): +### Quick Setup - Direct Script Download - /usr/share/fenrirscreenreader/tools/configure_pulse.sh - sudo /usr/share/fenrirscreenreader/tools/configure_pulse.sh +For non-Fenrir users or quick setup, download and run these scripts directly: -The script is also located in the tools directory in git +#### PulseAudio Configuration +```bash +# Download the script +wget https://raw.githubusercontent.com/storm-dragon/fenrir/master/tools/configure_pulse.sh +chmod +x configure_pulse.sh +# Run twice: once as user, once as root +./configure_pulse.sh +sudo ./configure_pulse.sh +``` -## Configure pipewire +#### PipeWire Configuration +```bash +# Download the script +wget https://raw.githubusercontent.com/storm-dragon/fenrir/master/tools/configure_pipewire.sh +chmod +x configure_pipewire.sh -Pipewire by default only plays sound for the user its currently running for. As fenrir is running as root, your local user does not hear the sound and speech produced by fenrir. -for this fenrir provides a script to configure pipewire to stream the sound played as root to your local user. This is not a issue of fenrir but this is how pipewire works. +# Run twice: once as user, once as root +./configure_pipewire.sh +sudo ./configure_pipewire.sh +``` -just run the configuration script twice (once as user, once as root): +**Direct links:** +- [configure_pulse.sh](https://raw.githubusercontent.com/storm-dragon/fenrir/master/tools/configure_pulse.sh) +- [configure_pipewire.sh](https://raw.githubusercontent.com/storm-dragon/fenrir/master/tools/configure_pipewire.sh) - /usr/share/fenrirscreenreader/tools/configure_pipewire.sh - sudo /usr/share/fenrirscreenreader/tools/configure_pipewire.sh +### Using Installed Scripts -The script is also located in the tools directory in git +If you have Fenrir installed, the scripts are available at: + +**PulseAudio:** +```bash +/usr/share/fenrirscreenreader/tools/configure_pulse.sh +sudo /usr/share/fenrirscreenreader/tools/configure_pulse.sh +``` + +**PipeWire:** +```bash +/usr/share/fenrirscreenreader/tools/configure_pipewire.sh +sudo /usr/share/fenrirscreenreader/tools/configure_pipewire.sh +``` + +**Note:** These scripts work for any console application that needs root audio access, not just Fenrir. ## Command Line Options From 0a2c8472c0c5476ab2cb7b26d5f6cf6affd4329b Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Mon, 4 Aug 2025 14:36:06 -0400 Subject: [PATCH 5/8] Fixed errors in README. Moved the audio configuration script stuff nearer the top. --- README.md | 104 +++++++++++++++++++++++++++--------------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 5a6eda25..bd55dd58 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,58 @@ By default Fenrir uses: - **Input driver**: evdevDriver (Linux) or ptyDriver (other platforms) - **Screen driver**: vcsaDriver (Linux TTY) or ptyDriver (terminal emulation) +## Audio Configuration + +Both PulseAudio and PipeWire require special configuration to allow console applications running as root (like Fenrir) to route audio to your regular user session. This is normal audio system behavior, not a Fenrir issue. + +### Quick Setup - Direct Script Download + +For non-Fenrir users or quick setup, download and run these scripts directly: + +#### PulseAudio Configuration +```bash +# Download the script +wget https://git.stormux.org/storm/fenrir/raw/branch/master/tools/configure_pulse.sh +chmod +x configure_pulse.sh + +# Run twice: once as user, once as root +./configure_pulse.sh +sudo ./configure_pulse.sh +``` + +#### PipeWire Configuration +```bash +# Download the script +wget https://git.stormux.org/storm/fenrir/raw/branch/master/tools/configure_pipewire.sh +chmod +x configure_pipewire.sh + +# Run twice: once as user, once as root +./configure_pipewire.sh +sudo ./configure_pipewire.sh +``` + +**Direct links:** +- [configure_pulse.sh](https://git.stormux.org/storm/fenrir/raw/branch/master/tools/configure_pulse.sh) +- [configure_pipewire.sh](https://git.stormux.org/storm/fenrir/raw/branch/master/tools/configure_pipewire.sh) + +### Using Installed Scripts + +If you have Fenrir installed, the scripts are available at: + +**PulseAudio:** +```bash +/usr/share/fenrirscreenreader/tools/configure_pulse.sh +sudo /usr/share/fenrirscreenreader/tools/configure_pulse.sh +``` + +**PipeWire:** +```bash +/usr/share/fenrirscreenreader/tools/configure_pipewire.sh +sudo /usr/share/fenrirscreenreader/tools/configure_pipewire.sh +``` + +**Note:** These scripts work for any console application that needs root audio access, not just Fenrir. + ## Getting Started ### Basic Usage @@ -640,58 +692,6 @@ send_fenrir_command("setting set speech#rate=0.9") - Check Fenrir debug logs: `/var/log/fenrir.log` - Test with simple command: `echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock` -## Audio Configuration - -Both PulseAudio and PipeWire require special configuration to allow console applications running as root (like Fenrir) to route audio to your regular user session. This is normal audio system behavior, not a Fenrir issue. - -### Quick Setup - Direct Script Download - -For non-Fenrir users or quick setup, download and run these scripts directly: - -#### PulseAudio Configuration -```bash -# Download the script -wget https://raw.githubusercontent.com/storm-dragon/fenrir/master/tools/configure_pulse.sh -chmod +x configure_pulse.sh - -# Run twice: once as user, once as root -./configure_pulse.sh -sudo ./configure_pulse.sh -``` - -#### PipeWire Configuration -```bash -# Download the script -wget https://raw.githubusercontent.com/storm-dragon/fenrir/master/tools/configure_pipewire.sh -chmod +x configure_pipewire.sh - -# Run twice: once as user, once as root -./configure_pipewire.sh -sudo ./configure_pipewire.sh -``` - -**Direct links:** -- [configure_pulse.sh](https://raw.githubusercontent.com/storm-dragon/fenrir/master/tools/configure_pulse.sh) -- [configure_pipewire.sh](https://raw.githubusercontent.com/storm-dragon/fenrir/master/tools/configure_pipewire.sh) - -### Using Installed Scripts - -If you have Fenrir installed, the scripts are available at: - -**PulseAudio:** -```bash -/usr/share/fenrirscreenreader/tools/configure_pulse.sh -sudo /usr/share/fenrirscreenreader/tools/configure_pulse.sh -``` - -**PipeWire:** -```bash -/usr/share/fenrirscreenreader/tools/configure_pipewire.sh -sudo /usr/share/fenrirscreenreader/tools/configure_pipewire.sh -``` - -**Note:** These scripts work for any console application that needs root audio access, not just Fenrir. - ## Command Line Options Fenrir supports several command-line options for different use cases: From 8c26d93001ca63f439d3b315a69a1482d48d185a Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Wed, 20 Aug 2025 14:33:22 -0400 Subject: [PATCH 6/8] Progress bar monitoring updates. Hopefully fixed some false positives, and updated claude-code progress monitoring. --- .../onScreenUpdate/65000-progress_detector.py | 19 +++++++++++++++---- src/fenrirscreenreader/fenrirVersion.py | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/fenrirscreenreader/commands/onScreenUpdate/65000-progress_detector.py b/src/fenrirscreenreader/commands/onScreenUpdate/65000-progress_detector.py index da24037a..75dcb8e3 100644 --- a/src/fenrirscreenreader/commands/onScreenUpdate/65000-progress_detector.py +++ b/src/fenrirscreenreader/commands/onScreenUpdate/65000-progress_detector.py @@ -146,8 +146,8 @@ class command: # Pattern 1b: Time/token activity (not percentage-based, so use single # beep) - time_match = re.search(r"(\d+)s\s", text) - token_match = re.search(r"(\d+)\s+tokens", text) + time_match = re.search(r"(?:(?:remaining|elapsed|left|ETA|eta)[:;\s]*(\d+)s|(\d+)s\s+(?:remaining|elapsed|left))", text, re.IGNORECASE) + token_match = re.search(r"(?:processing|generating|used|consumed)\s+(\d+)\s+tokens", text, re.IGNORECASE) # Pattern 1c: dd command output (bytes copied with transfer rate) dd_match = re.search(r"\d+\s+bytes.*copied.*\d+\s+s.*[kMGT]?B/s", text) # Pattern 1d: Curl-style transfer data (bytes, speed indicators) @@ -183,7 +183,10 @@ class command: if fraction_match: current = int(fraction_match.group(1)) total = int(fraction_match.group(2)) - if total > 0: + # Filter out dates, page numbers, and other non-progress fractions + if (total > 0 and total <= 1000 and current <= total and + not re.search(r"\b(?:page|chapter|section|line|row|column|year|month|day)\b", text, re.IGNORECASE) and + not re.search(r"\d{1,2}/\d{1,2}/\d{2,4}", text)): # Date pattern percentage = (current / total) * 100 if ( percentage @@ -245,7 +248,15 @@ class command: self.env["commandBuffer"]["lastProgressTime"] = current_time return - # Pattern 6: Moon phase progress indicators + # Pattern 6: Claude Code progress indicators + claude_progress_match = re.search(r'^[·✢✒*]\s+\w+[…\.]*\s*\(esc to interrupt\)\s*$', text) + if claude_progress_match: + if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0: + self.play_activity_beep() + self.env["commandBuffer"]["lastProgressTime"] = current_time + return + + # Pattern 7: Moon phase progress indicators moon_match = re.search(r'[πŸŒ‘πŸŒ’πŸŒ“πŸŒ”πŸŒ•πŸŒ–πŸŒ—πŸŒ˜]', text) if moon_match: moon_phases = { diff --git a/src/fenrirscreenreader/fenrirVersion.py b/src/fenrirscreenreader/fenrirVersion.py index bd44604a..a35af8df 100644 --- a/src/fenrirscreenreader/fenrirVersion.py +++ b/src/fenrirscreenreader/fenrirVersion.py @@ -4,6 +4,6 @@ # Fenrir TTY screen reader # By Chrys, Storm Dragon, and contributors. -version = "2025.08.04" +version = "2025.08.20" codeName = "testing" code_name = "testing" From 78c1cbbb6ba8d5e797b19d6b02370a5ee4e4758c Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Thu, 21 Aug 2025 19:59:40 -0400 Subject: [PATCH 7/8] Improved progress bar detection for curl. Experimental flood protection for updates. --- .../onScreenUpdate/65000-progress_detector.py | 46 ++++++++++++++++++- src/fenrirscreenreader/fenrirVersion.py | 2 +- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/fenrirscreenreader/commands/onScreenUpdate/65000-progress_detector.py b/src/fenrirscreenreader/commands/onScreenUpdate/65000-progress_detector.py index 75dcb8e3..ada54b5b 100644 --- a/src/fenrirscreenreader/commands/onScreenUpdate/65000-progress_detector.py +++ b/src/fenrirscreenreader/commands/onScreenUpdate/65000-progress_detector.py @@ -144,13 +144,43 @@ class command: ] = current_time return + # Pattern 1a2: Curl classic progress format (percentage without % symbol) + # Extract percentage from curl's classic format + curl_classic_match = re.search( + r"^\s*(\d+)\s+\d+[kMGT]?\s+(\d+)\s+\d+[kMGT]?\s+\d+\s+\d+\s+\d+[kMGT]?\s+\d+\s+\d+:\d+:\d+\s+\d+:\d+:\d+\s+\d+:\d+:\d+\s+\d+[kMGT]?\s*$", text + ) + if curl_classic_match: + # Use the first percentage (total progress) + percentage = float(curl_classic_match.group(1)) + if 0 <= percentage <= 100: + self.env["runtime"]["DebugManager"].write_debug_out( + "found curl classic percentage: " + str(percentage), + debug.DebugLevel.INFO, + ) + if ( + percentage + != self.env["commandBuffer"]["lastProgressValue"] + ): + self.env["runtime"]["DebugManager"].write_debug_out( + "Playing tone for curl: " + str(percentage), + debug.DebugLevel.INFO, + ) + self.play_progress_tone(percentage) + self.env["commandBuffer"][ + "lastProgressValue" + ] = percentage + self.env["commandBuffer"][ + "lastProgressTime" + ] = current_time + return + # Pattern 1b: Time/token activity (not percentage-based, so use single # beep) time_match = re.search(r"(?:(?:remaining|elapsed|left|ETA|eta)[:;\s]*(\d+)s|(\d+)s\s+(?:remaining|elapsed|left))", text, re.IGNORECASE) token_match = re.search(r"(?:processing|generating|used|consumed)\s+(\d+)\s+tokens", text, re.IGNORECASE) # Pattern 1c: dd command output (bytes copied with transfer rate) dd_match = re.search(r"\d+\s+bytes.*copied.*\d+\s+s.*[kMGT]?B/s", text) - # Pattern 1d: Curl-style transfer data (bytes, speed indicators) + # Pattern 1d: Curl-style transfer data (bytes, speed indicators - legacy) curl_match = re.search( r"(\d+\s+\d+\s+\d+\s+\d+.*?(?:k|M|G)?.*?--:--:--|Speed)", text ) @@ -285,9 +315,21 @@ class command: self.play_quiet_tone(800, 0.08) def play_quiet_tone(self, frequency, duration): - """Play a quiet tone using Sox directly""" + """Play a quiet tone using Sox directly with flood protection""" import shlex import subprocess + import time + + # Flood protection: prevent beeps closer than 0.1 seconds apart + current_time = time.time() + if not hasattr(self, '_last_beep_time'): + self._last_beep_time = 0 + + if current_time - self._last_beep_time < 0.1: + # Skip this beep to prevent audio crackling on low-resource systems + return + + self._last_beep_time = current_time # Build the Sox command: play -qn synth tri gain # -8 diff --git a/src/fenrirscreenreader/fenrirVersion.py b/src/fenrirscreenreader/fenrirVersion.py index a35af8df..f7054342 100644 --- a/src/fenrirscreenreader/fenrirVersion.py +++ b/src/fenrirscreenreader/fenrirVersion.py @@ -4,6 +4,6 @@ # Fenrir TTY screen reader # By Chrys, Storm Dragon, and contributors. -version = "2025.08.20" +version = "2025.08.21" codeName = "testing" code_name = "testing" From b3d73102fc41ed85ae77bfcd9d852f17707ced82 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Thu, 21 Aug 2025 23:29:45 -0400 Subject: [PATCH 8/8] Fenrir will now read dots in the middle of or at the beginning of words regardless of punctuation settings. --- src/fenrirscreenreader/core/outputManager.py | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/fenrirscreenreader/core/outputManager.py b/src/fenrirscreenreader/core/outputManager.py index 44555e62..68e5690a 100644 --- a/src/fenrirscreenreader/core/outputManager.py +++ b/src/fenrirscreenreader/core/outputManager.py @@ -77,6 +77,27 @@ class OutputManager: def get_last_echo(self): return self.last_echo + def process_mid_word_punctuation(self, text): + """ + Process punctuation that appears mid-word to ensure proper pronunciation. + Specifically handles dots between word characters (e.g., "settings.conf" -> "settings dot conf") + and dots at word beginnings (e.g., ".local" -> "dot local") + while preserving sentence-ending periods and other punctuation behavior. + """ + if not text: + return text + + # Handle dots at the beginning of words (like .local, .bashrc, .config) + # Look for non-word character (or start of string), dot, then word characters + text = re.sub(r'(?