From 4caef89f6bb41ed6a9ce91fc8e4a3296b1c3b888 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Wed, 20 May 2026 20:11:21 -0400 Subject: [PATCH] More work on sockets attempt to get socket command to standard daemon.sock to a running instance. Also, fixed a long standing misspelling in daemon, was deamon, so if your scripts that self-voice or whatever with fenrir no longer work, this is why, please update scripts to the new, correct, daemon.sock. --- AGENTS.md | 6 +- README.md | 77 +++--- docs/fenrir.1 | 29 ++- docs/fenrir.adoc | 31 +-- docs/user.md | 44 ++-- src/fenrirscreenreader/core/fenrirManager.py | 2 +- .../remoteDriver/unixDriver.py | 138 ++++++++++- tests/integration/test_remote_control.py | 2 +- tests/unit/test_unix_remote_driver.py | 223 ++++++++++++++++++ tools/startx.sh | 5 +- 10 files changed, 461 insertions(+), 96 deletions(-) create mode 100644 tests/unit/test_unix_remote_driver.py diff --git a/AGENTS.md b/AGENTS.md index abd3b578..61cdba5e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -88,12 +88,12 @@ Application-specific menus in `vmenu-profiles/KEY/{app}/`: ## Remote Control -Unix socket: `/tmp/fenrirscreenreader-deamon.sock` +Unix socket: `/tmp/fenrirscreenreader-daemon.sock` TCP: localhost:22447 ```bash -echo "command say Hello" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock -echo "ls" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command say Hello" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock +echo "ls" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock echo "setting set speech#rate=0.8" | nc localhost 22447 ``` diff --git a/README.md b/README.md index 27ae50ea..5f2f1479 100644 --- a/README.md +++ b/README.md @@ -281,7 +281,7 @@ enable_command_remote=True # allow command execution ### Remote Drivers 1. **unixDriver** (recommended): Uses Unix domain sockets - - Socket location: `/tmp/fenrirscreenreader-deamon.sock` for the standard control socket + - Socket location: `/tmp/fenrirscreenreader-daemon.sock` for the standard control socket - `fenrir -x` instances also create private sockets: `/tmp/fenrirscreenreader-.sock` - More secure, local-only access - Works with `socat` @@ -298,99 +298,102 @@ The `socat` command provides the easiest way to send commands to Fenrir: #### Instance Discovery ```bash # List registered Fenrir instances and their socket paths -echo "ls" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "ls" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock ``` In X terminal mode (`fenrir -x`), multiple Fenrir instances can run at the same time. Each instance has a private socket, and one instance may also own the standard control socket. Use `ls` or `command ls` on the standard socket to find -the private socket for a specific instance. Untargeted commands sent through a -shared or broadcast path are claimed by one instance so duplicate instances do -not all perform the same action. +the private socket for a specific instance. Commands sent to the standard socket +are handled by its owner when possible; otherwise they are forwarded to a +registered private socket, preferring the sender's Fenrir ancestor when one can +be found. Untargeted commands sent through a shared or broadcast path are +claimed by one instance so duplicate instances do not all perform the same +action. #### Basic Speech Control ```bash # Interrupt current speech -echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Speak custom text -echo "command say Hello, this is a test message" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command say Hello, this is a test message" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Temporarily disable speech (until next keystroke) -echo "command tempdisablespeech" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command tempdisablespeech" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock ``` #### Settings Control ```bash # Enable highlight tracking mode -echo "setting set focus#highlight=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set focus#highlight=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Change speech parameters -echo "setting set speech#rate=0.8" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock -echo "setting set speech#pitch=0.6" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock -echo "setting set speech#volume=0.9" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set speech#rate=0.8" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock +echo "setting set speech#pitch=0.6" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock +echo "setting set speech#volume=0.9" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Change punctuation level (none/some/most/all) -echo "setting set general#punctuation_level=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock -echo "setting set general#punctuation_level=none" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set general#punctuation_level=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock +echo "setting set general#punctuation_level=none" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Voice and TTS engine control -echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock -echo "setting set speech#module=espeak-ng" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock +echo "setting set speech#module=espeak-ng" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Disable sound temporarily -echo "setting set sound#enabled=False" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock -echo "setting set sound#volume=0.5" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set sound#enabled=False" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock +echo "setting set sound#volume=0.5" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Keyboard and input settings -echo "setting set keyboard#char_echo_mode=1" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock -echo "setting set keyboard#word_echo=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set keyboard#char_echo_mode=1" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock +echo "setting set keyboard#word_echo=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Screen control (ignore specific TTYs) -echo "setting set screen#ignore_screen=1,2,3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set screen#ignore_screen=1,2,3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Multiple settings at once -echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuation_level=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuation_level=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Reset all settings to defaults -echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Save current settings -echo "setting save" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock -echo "setting saveas /tmp/my-fenrir-settings.conf" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting save" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock +echo "setting saveas /tmp/my-fenrir-settings.conf" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock ``` #### Clipboard Operations ```bash # Place text into clipboard -echo "command clipboard This text will be copied to clipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command clipboard This text will be copied to clipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Export clipboard to file -echo "command exportclipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command exportclipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock ``` #### Window Management ```bash # Define a window area (x1 y1 x2 y2) -echo "command window 0 0 80 24" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command window 0 0 80 24" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Reset window to full screen -echo "command resetwindow" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command resetwindow" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock ``` #### VMenu Control ```bash # Set virtual menu context -echo "command vmenu nano/file" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command vmenu nano/file" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Reset virtual menu -echo "command resetvmenu" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command resetvmenu" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock ``` #### Application Control ```bash # Quit Fenrir -echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock ``` ### Using TCP Driver @@ -590,7 +593,7 @@ Fenrir provides intelligent progress bar detection and audio feedback for variou To enable progress monitoring: 1. Add a key binding in your keyboard layout file -2. Or use the remote control system: `echo "command progress_bar_monitor" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock` +2. Or use the remote control system: `echo "command progress_bar_monitor" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock` ### Progress Detection Patterns @@ -651,7 +654,7 @@ Building... ```bash # Enable progress monitoring -echo "command progress_bar_monitor" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command progress_bar_monitor" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Common scenarios where progress monitoring is useful: wget https://example.com/large-file.zip # Download progress @@ -675,7 +678,7 @@ Progress monitoring can be configured through settings: #!/bin/bash # notify_fenrir.sh - Send notifications to Fenrir -SOCKET="/tmp/fenrirscreenreader-deamon.sock" +SOCKET="/tmp/fenrirscreenreader-daemon.sock" fenrir_say() { echo "command say $1" | socat - UNIX-CLIENT:$SOCKET @@ -698,7 +701,7 @@ import os def send_fenrir_command(command): """Send command to Fenrir via Unix socket""" - socket_path = "/tmp/fenrirscreenreader-deamon.sock" + socket_path = "/tmp/fenrirscreenreader-daemon.sock" if os.path.exists(socket_path): sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: @@ -731,7 +734,7 @@ send_fenrir_command("setting set speech#rate=0.9") **Commands not working:** - Verify `enable_command_remote=True` in settings - Check Fenrir debug logs: `/var/log/fenrir.log` -- Test with simple command: `echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock` +- Test with simple command: `echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock` ## Command Line Options diff --git a/docs/fenrir.1 b/docs/fenrir.1 index b0d918ed..3e243b5b 100644 --- a/docs/fenrir.1 +++ b/docs/fenrir.1 @@ -321,58 +321,61 @@ enable_command_remote=True .B Instance Discovery: .EX # List registered Fenrir instances and their socket paths -echo "ls" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "ls" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock .EE In X terminal mode (fenrir -x), multiple Fenrir instances can run at the same time. Each instance has a private socket at /tmp/fenrirscreenreader-.sock, and one instance may also own the standard control socket. Use ls or "command ls" on the standard socket to -find the private socket for a specific instance. +find the private socket for a specific instance. Commands sent to the standard +socket are handled by its owner when possible; otherwise they are forwarded to a +registered private socket, preferring the sender's Fenrir ancestor when one can +be found. .TP .B Basic Speech Control: .EX # Interrupt current speech -echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Speak custom text -echo "command say Hello, this is a test" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command say Hello, this is a test" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Temporarily disable speech -echo "command tempdisablespeech" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command tempdisablespeech" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock .EE .TP .B Settings Control: .EX # Enable highlight tracking -echo "setting set focus#highlight=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set focus#highlight=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Change speech rate -echo "setting set speech#rate=0.8" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set speech#rate=0.8" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Change punctuation level (none/some/most/all) -echo "setting set general#punctuation_level=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set general#punctuation_level=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Voice and TTS control -echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Multiple settings at once -echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuation_level=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuation_level=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Reset all settings -echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock .EE .TP .B Clipboard Operations: .EX # Add text to clipboard -echo "command clipboard Text to copy" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command clipboard Text to copy" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Export clipboard to file -echo "command exportclipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command exportclipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock .EE .SS Command Reference diff --git a/docs/fenrir.adoc b/docs/fenrir.adoc index 30ccde5f..e212fa75 100644 --- a/docs/fenrir.adoc +++ b/docs/fenrir.adoc @@ -1278,65 +1278,68 @@ The `+socat+` command provides the easiest way to send commands to Fenrir: .... # List registered Fenrir instances and their socket paths -echo "ls" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "ls" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock .... In X terminal mode (`+fenrir -x+`), multiple Fenrir instances can run at the same time. Each instance has a private socket at `+/tmp/fenrirscreenreader-.sock+`, and one instance may also own the standard control socket. Use `+ls+` or `+command ls+` on the standard socket to -find the private socket for a specific instance. +find the private socket for a specific instance. Commands sent to the standard +socket are handled by its owner when possible; otherwise they are forwarded to a +registered private socket, preferring the sender's Fenrir ancestor when one can +be found. ===== Basic Speech Control .... # Interrupt current speech -echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Speak custom text -echo "command say Hello, this is a test message" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command say Hello, this is a test message" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Temporarily disable speech (until next keystroke) -echo "command tempdisablespeech" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command tempdisablespeech" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock .... ===== Settings Control .... # Enable highlight tracking mode -echo "setting set focus#highlight=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set focus#highlight=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Change speech rate -echo "setting set speech#rate=0.8" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set speech#rate=0.8" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Change punctuation level (none/some/most/all) -echo "setting set general#punctuation_level=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set general#punctuation_level=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Voice and TTS control -echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Multiple settings at once -echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuation_level=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuation_level=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Reset all settings to defaults -echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock .... ===== Clipboard Operations .... # Place text into clipboard -echo "command clipboard This text will be copied to clipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command clipboard This text will be copied to clipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Export clipboard to file -echo "command exportclipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command exportclipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock .... ===== Application Control .... # Quit Fenrir -echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock .... ==== Command Reference diff --git a/docs/user.md b/docs/user.md index 1e4f683b..52a8384c 100644 --- a/docs/user.md +++ b/docs/user.md @@ -143,68 +143,70 @@ enable_command_remote=True # allow command execution #### Instance Discovery ```bash # List registered Fenrir instances and their socket paths -echo "ls" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "ls" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock ``` In X terminal mode (`fenrir -x`), multiple Fenrir instances can run at the same time. Each instance has a private socket at `/tmp/fenrirscreenreader-.sock`, and one instance may also own the standard control socket. Use `ls` or `command ls` on the standard socket to find the private socket for a specific -instance. +instance. Commands sent to the standard socket are handled by its owner when +possible; otherwise they are forwarded to a registered private socket, +preferring the sender's Fenrir ancestor when one can be found. #### Speech Control ```bash # Interrupt current speech -echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Speak custom text -echo "command say Hello, this is a test" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command say Hello, this is a test" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Temporarily disable speech -echo "command tempdisablespeech" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command tempdisablespeech" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock ``` #### Settings Control ```bash # Enable highlight tracking -echo "setting set focus#highlight=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set focus#highlight=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Change speech parameters -echo "setting set speech#rate=0.8" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock -echo "setting set speech#pitch=0.6" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock -echo "setting set speech#volume=0.9" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set speech#rate=0.8" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock +echo "setting set speech#pitch=0.6" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock +echo "setting set speech#volume=0.9" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Change punctuation level (none/some/most/all) -echo "setting set general#punctuation_level=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set general#punctuation_level=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Voice and TTS control -echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock -echo "setting set speech#module=espeak-ng" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock +echo "setting set speech#module=espeak-ng" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Multiple settings at once -echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuation_level=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuation_level=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Reset all settings -echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Save settings -echo "setting save" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock -echo "setting saveas /tmp/my-settings.conf" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "setting save" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock +echo "setting saveas /tmp/my-settings.conf" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock ``` #### Clipboard Operations ```bash # Add text to clipboard -echo "command clipboard Text to copy" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command clipboard Text to copy" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Export clipboard to file -echo "command exportclipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command exportclipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock ``` #### Application Control ```bash # Quit Fenrir -echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock ``` ### Command Reference @@ -240,7 +242,7 @@ echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-dea #### Bash Helper Function ```bash fenrir_say() { - echo "command say $1" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock + echo "command say $1" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock } # Usage @@ -253,7 +255,7 @@ import socket import os def send_fenrir_command(command): - socket_path = "/tmp/fenrirscreenreader-deamon.sock" + socket_path = "/tmp/fenrirscreenreader-daemon.sock" if os.path.exists(socket_path): sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: diff --git a/src/fenrirscreenreader/core/fenrirManager.py b/src/fenrirscreenreader/core/fenrirManager.py index 3a59d8e1..ee272ef8 100644 --- a/src/fenrirscreenreader/core/fenrirManager.py +++ b/src/fenrirscreenreader/core/fenrirManager.py @@ -485,7 +485,7 @@ class FenrirManager: os.unlink(pid_socket_file) # Clean up main socket only if it is stale (not active) - main_socket_file = "/tmp/fenrirscreenreader-deamon.sock" + main_socket_file = "/tmp/fenrirscreenreader-daemon.sock" if os.path.exists(main_socket_file): try: test_sock = socket.socket( diff --git a/src/fenrirscreenreader/remoteDriver/unixDriver.py b/src/fenrirscreenreader/remoteDriver/unixDriver.py index e9e70826..e7411184 100644 --- a/src/fenrirscreenreader/remoteDriver/unixDriver.py +++ b/src/fenrirscreenreader/remoteDriver/unixDriver.py @@ -8,6 +8,7 @@ import os import os.path import select import socket +import struct import time from fenrirscreenreader.core import debug @@ -16,7 +17,7 @@ from fenrirscreenreader.core.eventData import FenrirEventType from fenrirscreenreader.core.remoteDriver import RemoteDriver as remoteDriver -MAIN_SOCKET_FILE = "/tmp/fenrirscreenreader-deamon.sock" +MAIN_SOCKET_FILE = "/tmp/fenrirscreenreader-daemon.sock" class driver(remoteDriver): @@ -132,6 +133,131 @@ class driver(remoteDriver): return False return False + def _socket_file_for_socket(self, fenrir_sock): + for bound_sock, socket_file in self.bound_sockets: + if bound_sock == fenrir_sock: + return socket_file + return "" + + def _get_peer_pid(self, client_sock): + so_peercred = getattr(socket, "SO_PEERCRED", 17) + try: + creds = client_sock.getsockopt( + socket.SOL_SOCKET, so_peercred, struct.calcsize("3i") + ) + pid, _uid, _gid = struct.unpack("3i", creds) + return pid + except OSError: + return 0 + + def _get_parent_pid(self, pid): + try: + with open(f"/proc/{pid}/stat", "r", encoding="utf-8") as proc_file: + stat_text = proc_file.read() + except OSError: + return 0 + + end_command = stat_text.rfind(")") + if end_command == -1: + return 0 + stat_fields = stat_text[end_command + 2 :].split() + if len(stat_fields) < 2: + return 0 + try: + return int(stat_fields[1]) + except ValueError: + return 0 + + def _find_ancestor_private_socket(self, pid): + seen_pids = set() + while pid > 1 and pid not in seen_pids: + seen_pids.add(pid) + socket_file = f"/tmp/fenrirscreenreader-{pid}.sock" + if self._is_registered_private_socket(socket_file): + return socket_file + pid = self._get_parent_pid(pid) + return "" + + def _is_registered_private_socket(self, socket_file): + for instance in remoteInstanceRegistry.list_instances(): + if socket_file in instance.get("socket_files", []): + return True + return False + + def _get_registered_private_sockets(self): + socket_files = [] + for instance in remoteInstanceRegistry.list_instances(): + for socket_file in instance.get("socket_files", []): + if socket_file == MAIN_SOCKET_FILE: + continue + if socket_file in socket_files: + continue + socket_files.append(socket_file) + return socket_files + + def _find_available_private_socket(self, preferred_socket=""): + socket_files = self._get_registered_private_sockets() + if preferred_socket and preferred_socket in socket_files: + socket_files.remove(preferred_socket) + socket_files.insert(0, preferred_socket) + + for socket_file in socket_files: + if self._is_own_socket_file(socket_file): + return socket_file + if self._is_socket_active(socket_file): + return socket_file + return "" + + def _is_own_socket_file(self, socket_file): + return any( + socket_file == bound_socket_file + for _bound_sock, bound_socket_file in self.bound_sockets + ) + + def _has_own_private_socket(self): + return any( + bound_socket_file != MAIN_SOCKET_FILE + for _bound_sock, bound_socket_file in self.bound_sockets + ) + + def _forward_remote_to_socket(self, data, socket_file): + forward_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + forward_sock.settimeout(0.2) + forward_sock.connect(socket_file) + forward_sock.sendall((data + "\n").encode("utf-8")) + return True + except OSError as e: + self.env["runtime"]["DebugManager"].write_debug_out( + "unixDriver watch_dog: Error forwarding remote data to " + + socket_file + + ": " + + str(e), + debug.DebugLevel.ERROR, + ) + return False + finally: + forward_sock.close() + + def _route_main_socket_command(self, data, client_sock, socket_file): + if socket_file != MAIN_SOCKET_FILE: + return False + if not self._has_own_private_socket(): + return False + + peer_pid = self._get_peer_pid(client_sock) + ancestor_socket = "" + if peer_pid > 1: + ancestor_socket = self._find_ancestor_private_socket(peer_pid) + target_socket = self._find_available_private_socket(ancestor_socket) + if not target_socket: + return False + + if self._is_own_socket_file(target_socket): + return False + + return self._forward_remote_to_socket(data, target_socket) + def _cleanup(self): for fenrir_sock in self.fenrirSocks: try: @@ -152,7 +278,7 @@ class driver(remoteDriver): self.bound_sockets = [] remoteInstanceRegistry.remove_instance() - def _handle_client(self, client_sock, event_queue): + def _handle_client(self, client_sock, event_queue, socket_file=""): try: rawdata = client_sock.recv(8129) except Exception as e: @@ -173,6 +299,9 @@ class driver(remoteDriver): client_sock.sendall((response["message"] + "\n").encode("utf-8")) return + if self._route_main_socket_command(data, client_sock, socket_file): + return + event_queue.put( { "Type": FenrirEventType.remote_incomming, @@ -188,7 +317,7 @@ class driver(remoteDriver): def watch_dog(self, active, event_queue): # echo "command say this is a test" | socat - - # UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock + # UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock for socket_file, optional in self._get_socket_candidates(): fenrir_sock = self._bind_socket(socket_file, optional) if fenrir_sock is None: @@ -215,10 +344,11 @@ class driver(remoteDriver): continue for fenrir_sock in r: client_sock, client_addr = fenrir_sock.accept() + socket_file = self._socket_file_for_socket(fenrir_sock) # Ensure client socket is always closed to prevent resource # leaks try: - self._handle_client(client_sock, event_queue) + self._handle_client(client_sock, event_queue, socket_file) finally: # Always close client socket, even if data processing fails try: diff --git a/tests/integration/test_remote_control.py b/tests/integration/test_remote_control.py index a66af6c5..9b834be0 100644 --- a/tests/integration/test_remote_control.py +++ b/tests/integration/test_remote_control.py @@ -232,7 +232,7 @@ class TestRemoteDataFormat: "x11_window_id": "0x123", "socket_files": [ "/tmp/fenrirscreenreader-123.sock", - "/tmp/fenrirscreenreader-deamon.sock", + "/tmp/fenrirscreenreader-daemon.sock", ], } ], diff --git a/tests/unit/test_unix_remote_driver.py b/tests/unit/test_unix_remote_driver.py new file mode 100644 index 00000000..ea95d816 --- /dev/null +++ b/tests/unit/test_unix_remote_driver.py @@ -0,0 +1,223 @@ +from unittest.mock import Mock, mock_open, patch + +from fenrirscreenreader.remoteDriver import unixDriver + + +class FakeClientSocket: + def __init__(self, data): + self.data = data + self.sent = b"" + + def recv(self, _size): + return self.data + + def sendall(self, data): + self.sent += data + + +def test_main_socket_routes_to_ancestor_private_socket(mock_environment): + driver = unixDriver.driver() + driver.env = mock_environment + driver.bound_sockets = [ + (Mock(), "/tmp/fenrirscreenreader-999.sock"), + (Mock(), unixDriver.MAIN_SOCKET_FILE), + ] + client_sock = FakeClientSocket(b"command say routed") + event_queue = Mock() + + with patch.object(driver, "_get_peer_pid", return_value=1234), patch.object( + driver, + "_find_ancestor_private_socket", + return_value="/tmp/fenrirscreenreader-222.sock", + ), patch.object( + driver, + "_find_available_private_socket", + return_value="/tmp/fenrirscreenreader-222.sock", + ), patch.object( + driver, "_forward_remote_to_socket", return_value=True + ) as forward: + driver._handle_client( + client_sock, event_queue, unixDriver.MAIN_SOCKET_FILE + ) + + forward.assert_called_once_with( + "command say routed", "/tmp/fenrirscreenreader-222.sock" + ) + event_queue.put.assert_not_called() + + +def test_main_socket_routes_to_first_available_private_socket( + mock_environment, +): + driver = unixDriver.driver() + driver.env = mock_environment + driver.bound_sockets = [ + (Mock(), "/tmp/fenrirscreenreader-999.sock"), + (Mock(), unixDriver.MAIN_SOCKET_FILE), + ] + client_sock = FakeClientSocket(b"command say fallback") + event_queue = Mock() + + with patch.object(driver, "_get_peer_pid", return_value=1234), patch.object( + driver, + "_find_ancestor_private_socket", + return_value="", + ), patch.object( + driver, + "_find_available_private_socket", + return_value="/tmp/fenrirscreenreader-333.sock", + ), patch.object( + driver, "_forward_remote_to_socket", return_value=True + ) as forward: + driver._handle_client( + client_sock, event_queue, unixDriver.MAIN_SOCKET_FILE + ) + + forward.assert_called_once_with( + "command say fallback", "/tmp/fenrirscreenreader-333.sock" + ) + event_queue.put.assert_not_called() + + +def test_main_socket_handles_command_locally_without_available_target( + mock_environment, +): + driver = unixDriver.driver() + driver.env = mock_environment + client_sock = FakeClientSocket(b"command say local") + event_queue = Mock() + + with patch.object(driver, "_get_peer_pid", return_value=1234), patch.object( + driver, + "_find_available_private_socket", + return_value="", + ): + driver._handle_client( + client_sock, event_queue, unixDriver.MAIN_SOCKET_FILE + ) + + event_queue.put.assert_called_once_with( + { + "Type": unixDriver.FenrirEventType.remote_incomming, + "data": "command say local", + } + ) + + +def test_vcsa_main_socket_owner_handles_command_locally(mock_environment): + driver = unixDriver.driver() + driver.env = mock_environment + driver.bound_sockets = [(Mock(), unixDriver.MAIN_SOCKET_FILE)] + client_sock = FakeClientSocket(b"command say root") + event_queue = Mock() + + with patch.object(driver, "_find_available_private_socket") as find_available: + driver._handle_client( + client_sock, event_queue, unixDriver.MAIN_SOCKET_FILE + ) + + find_available.assert_not_called() + event_queue.put.assert_called_once_with( + { + "Type": unixDriver.FenrirEventType.remote_incomming, + "data": "command say root", + } + ) + + +def test_private_socket_handles_command_locally(mock_environment): + driver = unixDriver.driver() + driver.env = mock_environment + client_sock = FakeClientSocket(b"command say private") + event_queue = Mock() + + driver._handle_client( + client_sock, event_queue, "/tmp/fenrirscreenreader-222.sock" + ) + + event_queue.put.assert_called_once_with( + { + "Type": unixDriver.FenrirEventType.remote_incomming, + "data": "command say private", + } + ) + + +def test_list_command_still_returns_response_from_main_socket(mock_environment): + driver = unixDriver.driver() + driver.env = mock_environment + mock_environment["runtime"]["RemoteManager"] = Mock() + client_sock = FakeClientSocket(b"command list") + event_queue = Mock() + mock_environment["runtime"][ + "RemoteManager" + ].handle_remote_incomming_with_response.return_value = { + "success": True, + "message": "pid=1", + } + + driver._handle_client(client_sock, event_queue, unixDriver.MAIN_SOCKET_FILE) + + assert client_sock.sent == b"pid=1\n" + event_queue.put.assert_not_called() + + +def test_get_parent_pid_parses_process_names_with_spaces(): + driver = unixDriver.driver() + + stat_file = mock_open( + read_data="1234 (name with spaces) S 567 1 1 0 -1 0\n" + ) + with patch("builtins.open", stat_file): + assert driver._get_parent_pid(1234) == 567 + + +def test_find_available_private_socket_prefers_ancestor_socket( + mock_environment, +): + driver = unixDriver.driver() + driver.env = mock_environment + + with patch( + "fenrirscreenreader.remoteDriver.unixDriver.remoteInstanceRegistry.list_instances", + return_value=[ + { + "pid": 111, + "socket_files": ["/tmp/fenrirscreenreader-111.sock"], + }, + { + "pid": 222, + "socket_files": ["/tmp/fenrirscreenreader-222.sock"], + }, + ], + ), patch.object(driver, "_is_socket_active", return_value=True): + assert ( + driver._find_available_private_socket( + "/tmp/fenrirscreenreader-222.sock" + ) + == "/tmp/fenrirscreenreader-222.sock" + ) + + +def test_find_available_private_socket_skips_main_socket( + mock_environment, +): + driver = unixDriver.driver() + driver.env = mock_environment + + with patch( + "fenrirscreenreader.remoteDriver.unixDriver.remoteInstanceRegistry.list_instances", + return_value=[ + { + "pid": 111, + "socket_files": [ + unixDriver.MAIN_SOCKET_FILE, + "/tmp/fenrirscreenreader-111.sock", + ], + } + ], + ), patch.object(driver, "_is_socket_active", return_value=True): + assert ( + driver._find_available_private_socket() + == "/tmp/fenrirscreenreader-111.sock" + ) diff --git a/tools/startx.sh b/tools/startx.sh index e92a9402..32271fac 100755 --- a/tools/startx.sh +++ b/tools/startx.sh @@ -1,8 +1,9 @@ #!/bin/bash +# shellcheck disable=SC2329 cleanup() { # Make sure Fenrir is restored on exit of this script - echo -n "setting set screen#suspendingScreen=" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock + echo -n "setting set screen#suspendingScreen=" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock } # Call the cleanup function on exit of this script @@ -20,7 +21,7 @@ if ! [[ "$term" =~ ^[1-9]+$ ]]; then fi # Suspend the current terminal for Fenrir -echo -n "setting set screen#suspendingScreen=$term" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock +echo -n "setting set screen#suspendingScreen=$term" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-daemon.sock # Start the x session command startx