diff --git a/src/vocalinux/text_injection/text_injector.py b/src/vocalinux/text_injection/text_injector.py index 1c876bb..c5cbb41 100644 --- a/src/vocalinux/text_injection/text_injector.py +++ b/src/vocalinux/text_injection/text_injector.py @@ -197,19 +197,19 @@ class TextInjector: # Get information about the current window/application self._log_current_window_info() - # Escape special characters for shell - escaped_text = self._escape_text(text) - logger.debug(f"Escaped text: '{escaped_text}'") + # Note: No shell escaping needed - subprocess is called with list arguments, + # which passes text directly without shell interpretation + logger.debug(f"Text to inject: '{text}'") try: if ( self.environment == DesktopEnvironment.X11 or self.environment == DesktopEnvironment.WAYLAND_XDOTOOL ): - self._inject_with_xdotool(escaped_text) + self._inject_with_xdotool(text) else: try: - self._inject_with_wayland_tool(escaped_text) + self._inject_with_wayland_tool(text) except subprocess.CalledProcessError as e: stderr_msg = e.stderr.strip() if e.stderr else "No stderr output" logger.warning( @@ -220,7 +220,7 @@ class TextInjector: ).lower() + " " + stderr_msg.lower() and shutil.which("xdotool"): logger.info("Automatically switching to XWayland fallback permanently") self.environment = DesktopEnvironment.WAYLAND_XDOTOOL - self._inject_with_xdotool(escaped_text) + self._inject_with_xdotool(text) else: raise logger.info("Text injection completed successfully") @@ -235,37 +235,12 @@ class TextInjector: logger.warning("Could not import audio feedback module") return False - def _escape_text(self, text: str) -> str: - """ - Escape special characters in the text for shell commands. - - Args: - text: The original text - - Returns: - The escaped text - """ - # Replace common special characters - replacements = { - "'": r"\'", - '"': r"\"", - "`": r"\`", - "\\": r"\\", - "$": r"\$", - } - - result = text - for char, replacement in replacements.items(): - result = result.replace(char, replacement) - - return result - def _inject_with_xdotool(self, text: str): """ Inject text using xdotool for X11 environments. Args: - text: The text to inject (already escaped) + text: The text to inject """ # Create environment with explicit X11 settings for Wayland compatibility env = os.environ.copy() @@ -384,7 +359,7 @@ class TextInjector: Inject text using a Wayland-compatible tool (wtype or ydotool). Args: - text: The text to inject (already escaped) + text: The text to inject Raises: subprocess.CalledProcessError: If the tool fails, with stderr captured diff --git a/tests/test_text_injector.py b/tests/test_text_injector.py index 08dfd74..d7162e9 100644 --- a/tests/test_text_injector.py +++ b/tests/test_text_injector.py @@ -229,21 +229,33 @@ class TestTextInjector(unittest.TestCase): # Inject text injector.inject_text(special_text) - # Verify xdotool was called with proper escaping - # Find calls that contain xdotool and check they contain escaped text + # Verify xdotool was called with the original text (no escaping needed) + # Find calls that contain xdotool and check they contain the original text + found_unescaped = False found_escaped = False for args, _ in calls: if len(args) > 0 and isinstance(args[0], list): cmd = args[0] if "xdotool" in cmd and "type" in cmd: - # Join the command to check for escaped characters + # Join the command to check for characters cmd_str = " ".join(cmd) - # Look for escaped quotes and dollar signs - if "'" in cmd_str or '\\"' in cmd_str or "\\$" in cmd_str: + # Check for unescaped special characters + if "'" in cmd_str: + found_unescaped = True + if "\\'" in cmd_str or '\\"' in cmd_str or "\\$" in cmd_str: + # Found escaped characters - this is bad! found_escaped = True break - self.assertTrue(found_escaped, "Special characters were not properly escaped") + # Should find unescaped characters and NOT find escaped ones + self.assertTrue( + found_unescaped, + "Text was not passed correctly to xdotool", + ) + self.assertFalse( + found_escaped, + "Text should not be shell-escaped when passed to xdotool", + ) def test_empty_text_injection(self): """Test injecting empty text (should do nothing).""" @@ -291,26 +303,6 @@ class TestTextInjector(unittest.TestCase): # Check that error sound was triggered audio_feedback.play_error_sound.assert_called_once() - def test_escape_text(self): - """Test the _escape_text method.""" - injector = TextInjector() - - # Test various special characters - result = injector._escape_text("'single'") - self.assertIn("\\'", result) - - result = injector._escape_text('"double"') - self.assertIn('\\"', result) - - result = injector._escape_text("$var") - self.assertIn("\\$", result) - - result = injector._escape_text("`backtick`") - self.assertIn("\\`", result) - - result = injector._escape_text("back\\slash") - self.assertIn("\\\\", result) - def test_detect_environment_unknown(self): """Test environment detection when no indicators are present.""" with patch.dict("os.environ", {}, clear=True):