Compare commits
	
		
			9 Commits
		
	
	
		
			98b9c56af7
			...
			e255651c28
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e255651c28 | ||
|  | b3d73102fc | ||
|  | 78c1cbbb6b | ||
|  | 8c26d93001 | ||
|  | 0a2c8472c0 | ||
|  | 69eade3327 | ||
|  | 73f67c2a04 | ||
|  | 9ef9d762f4 | ||
|  | 84293db6dc | 
| @@ -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]: | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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"(\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) | ||||
|         # 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 | ||||
|         ) | ||||
| @@ -183,7 +213,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 +278,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 = { | ||||
| @@ -274,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 <duration> tri <frequency> gain | ||||
|         # -8 | ||||
|   | ||||
| @@ -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 | ||||
|         ) | ||||
| @@ -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 | ||||
|         ) | ||||
| @@ -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 | ||||
|         ) | ||||
| @@ -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 | ||||
|         ) | ||||
| @@ -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 | ||||
|         ) | ||||
| @@ -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 | ||||
|         ) | ||||
| @@ -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 | ||||
|         ) | ||||
| @@ -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 | ||||
|         ) | ||||
| @@ -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 | ||||
|         ) | ||||
| @@ -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 | ||||
|         ) | ||||
| @@ -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 | ||||
|         ) | ||||
|  | ||||
| @@ -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 | ||||
|         ) | ||||
| @@ -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 | ||||
|         ) | ||||
| @@ -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 | ||||
|         ) | ||||
| @@ -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 | ||||
|         ) | ||||
| @@ -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 | ||||
|         ) | ||||
| @@ -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 | ||||
|         ) | ||||
| @@ -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 | ||||
|         ) | ||||
|  | ||||
| @@ -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'(?<!\w)\.(\w+)', r'dot \1', text) | ||||
|          | ||||
|         # Replace dots that appear between word characters with spoken form | ||||
|         # Use a loop to handle multiple consecutive dots like www.example.com or a.b.c.d | ||||
|         while re.search(r'\b\w+\.\w+\b', text): | ||||
|             text = re.sub(r'\b(\w+)\.(\w+)\b', r'\1 dot \2', text) | ||||
|          | ||||
|         return text | ||||
|  | ||||
|     def speak_text( | ||||
|         self, | ||||
|         text, | ||||
| @@ -208,6 +229,7 @@ class OutputManager: | ||||
|             clean_text = self.env["runtime"]["TextManager"].replace_head_lines( | ||||
|                 clean_text | ||||
|             ) | ||||
|             clean_text = self.process_mid_word_punctuation(clean_text) | ||||
|             clean_text = self.env["runtime"][ | ||||
|                 "PunctuationManager" | ||||
|             ].proceed_punctuation(clean_text, ignore_punctuation) | ||||
|   | ||||
| @@ -4,6 +4,5 @@ | ||||
| # Fenrir TTY screen reader | ||||
| # By Chrys, Storm Dragon, and contributors. | ||||
|  | ||||
| version = "2025.08.04" | ||||
| version = "2025.08.22" | ||||
| codeName = "master" | ||||
| code_name = "master" | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user