Another attempt at fixing external numpad detection. I *think* some of them send numlock state with every event. If that's the case, this should fix it.
This commit is contained in:
		| @@ -24,11 +24,22 @@ class command: | |||||||
|     def run(self): |     def run(self): | ||||||
|         if self.env["input"]["oldNumLock"] == self.env["input"]["newNumLock"]: |         if self.env["input"]["oldNumLock"] == self.env["input"]["newNumLock"]: | ||||||
|             return |             return | ||||||
|          |  | ||||||
|         # Only announce numlock changes if an actual numlock key was pressed |         # Only announce numlock changes if an actual numlock key was pressed | ||||||
|         # This prevents spurious announcements from external numpad automatic state changes |         # AND the LED state actually changed (some numpads send spurious NUMLOCK events) | ||||||
|         current_input = self.env["input"]["currInput"] |         current_input = self.env["input"]["currInput"] | ||||||
|         if current_input and "KEY_NUMLOCK" in current_input: |  | ||||||
|  |         # Check if this is a genuine numlock key press by verifying: | ||||||
|  |         # 1. KEY_NUMLOCK is in the current input sequence | ||||||
|  |         # 2. The LED state has actually changed | ||||||
|  |         # 3. This isn't just a side effect from a KP_ key (which some buggy numpads do) | ||||||
|  |         is_genuine_numlock = ( | ||||||
|  |             current_input and | ||||||
|  |             "KEY_NUMLOCK" in current_input and | ||||||
|  |             not any(key.startswith("KEY_KP") for key in current_input if isinstance(key, str)) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         if is_genuine_numlock: | ||||||
|             if self.env["input"]["newNumLock"]: |             if self.env["input"]["newNumLock"]: | ||||||
|                 self.env["runtime"]["OutputManager"].present_text( |                 self.env["runtime"]["OutputManager"].present_text( | ||||||
|                     _("Numlock on"), interrupt=True |                     _("Numlock on"), interrupt=True | ||||||
|   | |||||||
| @@ -159,8 +159,7 @@ class command(config_command): | |||||||
|  |  | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             self.present_text( |             self.present_text( | ||||||
|                 f"Failed to create default configuration: { |                 f"Failed to create default configuration: {str(e)}", | ||||||
|                     str(e)}", |  | ||||||
|                 interrupt=False, |                 interrupt=False, | ||||||
|                 flush=False, |                 flush=False, | ||||||
|             ) |             ) | ||||||
|   | |||||||
| @@ -45,8 +45,7 @@ class command: | |||||||
|                     sound_driver.initialize(self.env) |                     sound_driver.initialize(self.env) | ||||||
|                 except Exception as e: |                 except Exception as e: | ||||||
|                     print( |                     print( | ||||||
|                         f"revert_to_saved sound_driver: Error reinitializing sound driver: { |                         f"revert_to_saved sound_driver: Error reinitializing sound driver: {str(e)}" | ||||||
|                             str(e)}" |  | ||||||
|                     ) |                     ) | ||||||
|  |  | ||||||
|             self.env["runtime"]["OutputManager"].present_text( |             self.env["runtime"]["OutputManager"].present_text( | ||||||
|   | |||||||
| @@ -189,9 +189,7 @@ class DynamicApplyVoiceCommand: | |||||||
|  |  | ||||||
|                     # Debug: verify what was actually set |                     # Debug: verify what was actually set | ||||||
|                     self.env["runtime"]["OutputManager"].present_text( |                     self.env["runtime"]["OutputManager"].present_text( | ||||||
|                         f"Speech driver now has module: { |                         f"Speech driver now has module: {SpeechDriver.module}, voice: {SpeechDriver.voice}", | ||||||
|                             SpeechDriver.module}, voice: { |  | ||||||
|                             SpeechDriver.voice}", |  | ||||||
|                         interrupt=True, |                         interrupt=True, | ||||||
|                     ) |                     ) | ||||||
|  |  | ||||||
| @@ -233,8 +231,7 @@ class DynamicApplyVoiceCommand: | |||||||
|                         ) |                         ) | ||||||
|  |  | ||||||
|                 self.env["runtime"]["OutputManager"].present_text( |                 self.env["runtime"]["OutputManager"].present_text( | ||||||
|                     f"Failed to apply voice, reverted: { |                     f"Failed to apply voice, reverted: {str(e)}", | ||||||
|                         str(e)}", |  | ||||||
|                     interrupt=False, |                     interrupt=False, | ||||||
|                     flush=False, |                     flush=False, | ||||||
|                 ) |                 ) | ||||||
|   | |||||||
| @@ -4,5 +4,5 @@ | |||||||
| # Fenrir TTY screen reader | # Fenrir TTY screen reader | ||||||
| # By Chrys, Storm Dragon, and contributors. | # By Chrys, Storm Dragon, and contributors. | ||||||
|  |  | ||||||
| version = "2025.09.12" | version = "2025.09.13" | ||||||
| code_name = "testing" | code_name = "testing" | ||||||
|   | |||||||
| @@ -43,11 +43,18 @@ if [ -n "$STAGED_PYTHON_FILES" ]; then | |||||||
|      |      | ||||||
|     for file in $STAGED_PYTHON_FILES; do |     for file in $STAGED_PYTHON_FILES; do | ||||||
|         if [ -f "$file" ]; then |         if [ -f "$file" ]; then | ||||||
|             # Check for unterminated strings (the main issue from the email) |             # Check for broken f-strings (multiline issues that cause syntax errors) | ||||||
|             if grep -n 'f".*{$' "$file" >/dev/null 2>&1; then |             # Pattern 1: f-string with opening brace at end of line (likely broken across lines) | ||||||
|                 echo -e "${RED}✗ $file: Potential unterminated f-string${NC}" |             if grep -n 'f"[^"]*{[^}]*$' "$file" >/dev/null 2>&1; then | ||||||
|  |                 echo -e "${RED}✗ $file: Potential broken multiline f-string${NC}" | ||||||
|  |                 grep -n 'f"[^"]*{[^}]*$' "$file" | head -3 | ||||||
|                 ISSUES_FOUND=1 |                 ISSUES_FOUND=1 | ||||||
|             fi |             fi | ||||||
|  |  | ||||||
|  |             # Pattern 2: Lines that end with just an opening brace (common in broken f-strings) | ||||||
|  |             if grep -n '^\s*[^#]*{$' "$file" >/dev/null 2>&1; then | ||||||
|  |                 echo -e "${YELLOW}⚠ $file: Lines ending with lone opening brace (check f-strings)${NC}" | ||||||
|  |             fi | ||||||
|              |              | ||||||
|             # Check for missing imports that are commonly used |             # Check for missing imports that are commonly used | ||||||
|             if grep -q 'debug\.DebugLevel\.' "$file" && ! grep -q 'from.*debug' "$file" && ! grep -q 'import.*debug' "$file"; then |             if grep -q 'debug\.DebugLevel\.' "$file" && ! grep -q 'from.*debug' "$file" && ! grep -q 'import.*debug' "$file"; then | ||||||
| @@ -110,7 +117,7 @@ if [ -n "$STAGED_PYTHON_FILES" ]; then | |||||||
|     for file in $STAGED_PYTHON_FILES; do |     for file in $STAGED_PYTHON_FILES; do | ||||||
|         if [ -f "$file" ]; then |         if [ -f "$file" ]; then | ||||||
|             # Check for potential passwords, keys, tokens |             # Check for potential passwords, keys, tokens | ||||||
|             if grep -i -E '(password|passwd|pwd|key|token|secret|api_key).*=.*["\'][^"\']{8,}["\']' "$file" >/dev/null 2>&1; then |             if grep -i -E '(password|passwd|pwd|key|token|secret|api_key).*=.*["'"'"'][^"'"'"']{8,}["'"'"']' "$file" >/dev/null 2>&1; then | ||||||
|                 echo -e "${RED}✗ $file: Potential hardcoded secret detected${NC}" |                 echo -e "${RED}✗ $file: Potential hardcoded secret detected${NC}" | ||||||
|                 SECRETS_FOUND=1 |                 SECRETS_FOUND=1 | ||||||
|             fi |             fi | ||||||
| @@ -126,7 +133,7 @@ else | |||||||
| fi | fi | ||||||
|  |  | ||||||
| # Summary | # Summary | ||||||
| echo -e "\n${'='*50}" | echo -e "\n==================================================" | ||||||
| if [ $VALIDATION_FAILED -eq 0 ]; then | if [ $VALIDATION_FAILED -eq 0 ]; then | ||||||
|     echo -e "${GREEN}✓ All pre-commit validations passed${NC}" |     echo -e "${GREEN}✓ All pre-commit validations passed${NC}" | ||||||
|     echo -e "${GREEN}Commit allowed to proceed${NC}" |     echo -e "${GREEN}Commit allowed to proceed${NC}" | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ Usage: | |||||||
|  |  | ||||||
| import ast | import ast | ||||||
| import os | import os | ||||||
|  | import re | ||||||
| import sys | import sys | ||||||
| import argparse | import argparse | ||||||
| import tempfile | import tempfile | ||||||
| @@ -24,13 +25,34 @@ class SyntaxValidator: | |||||||
|         self.errors = [] |         self.errors = [] | ||||||
|         self.warnings = [] |         self.warnings = [] | ||||||
|         self.fixed = [] |         self.fixed = [] | ||||||
|          |  | ||||||
|  |     def check_fstring_issues(self, content): | ||||||
|  |         """Check for common f-string formatting issues that cause syntax errors.""" | ||||||
|  |         issues = [] | ||||||
|  |         lines = content.split('\n') | ||||||
|  |  | ||||||
|  |         for i, line in enumerate(lines, 1): | ||||||
|  |             # Check for f-strings that appear to be broken across lines | ||||||
|  |             # Pattern: f"some text { | ||||||
|  |             if re.search(r'f"[^"]*{[^}]*$', line.strip()): | ||||||
|  |                 issues.append(f"Line {i}: Potential broken multiline f-string") | ||||||
|  |  | ||||||
|  |         return issues | ||||||
|  |  | ||||||
|     def validate_file(self, filepath): |     def validate_file(self, filepath): | ||||||
|         """Validate syntax of a single Python file.""" |         """Validate syntax of a single Python file.""" | ||||||
|         try: |         try: | ||||||
|             with open(filepath, 'r', encoding='utf-8') as f: |             with open(filepath, 'r', encoding='utf-8') as f: | ||||||
|                 content = f.read() |                 content = f.read() | ||||||
|              |  | ||||||
|  |             # Check for specific f-string issues before AST parsing | ||||||
|  |             fstring_issues = self.check_fstring_issues(content) | ||||||
|  |             if fstring_issues: | ||||||
|  |                 # Create a synthetic syntax error for f-string issues | ||||||
|  |                 error_msg = f"F-string issues: {'; '.join(fstring_issues)}" | ||||||
|  |                 self.errors.append((filepath, SyntaxError(error_msg), content)) | ||||||
|  |                 return False, content | ||||||
|  |  | ||||||
|             # Parse with AST (catches syntax errors) |             # Parse with AST (catches syntax errors) | ||||||
|             ast.parse(content, filename=str(filepath)) |             ast.parse(content, filename=str(filepath)) | ||||||
|             return True, content |             return True, content | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user