Merged testing.
This commit is contained in:
@@ -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