From d80352f4c807344d5a1e0601ad3a012fe59bcbf8 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Wed, 13 Aug 2025 14:34:49 -0400 Subject: [PATCH] Add local and remote ip address options to Accessories menu. Fixed a bug that allowed wine32 games to show up on the Raspberry Pi. --- home/stormux/Documents/change_log.md | 7 + usr/local/bin/game_launcher.py | 9 +- usr/local/bin/ip_info.py | 185 +++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 2 deletions(-) create mode 100755 usr/local/bin/ip_info.py diff --git a/home/stormux/Documents/change_log.md b/home/stormux/Documents/change_log.md index 5c1a81a..86704e0 100644 --- a/home/stormux/Documents/change_log.md +++ b/home/stormux/Documents/change_log.md @@ -5,6 +5,10 @@ Dates are given for the image. All items listed are available for the listed ima ## September 1, 2025 +- Wine32 games only appear in x86_64 +- Added local and remote ip address information to accessories menu +- Added Kitchensinc Games and Swamp to x86_64 image +- Set up wine32 on x86_64 image - Fixed a bug with ocr, should work on both images now - Hopefully fix a bug where setting default voice would sometimes not save across reboots. - Add pitch and volume parameters to the rate script @@ -14,6 +18,9 @@ Dates are given for the image. All items listed are available for the listed ima - Fix bug with resizing disk in x86_64 - Installed missing packages for text games. Remembered the games, forgot the dependencies +During this release cycle my development box broke in a very unusual way. The computer itself seems to work fine, but it will no longer boot from usb. I, thinking the image was broken, started over. +I am sure you know or have some idea of how much work was involved in that. Fortunately, I now have a working temporary development machine, so development can continue. + ## August 1, 2025 diff --git a/usr/local/bin/game_launcher.py b/usr/local/bin/game_launcher.py index 4e7077e..1ce8ac0 100755 --- a/usr/local/bin/game_launcher.py +++ b/usr/local/bin/game_launcher.py @@ -916,8 +916,11 @@ if __name__ == "__main__": menu.add_item("Arcade", "Villains From Beyond", "GAME='Villains From Beyond' startx") menu.add_item("Arcade", "Wicked Quest", "GAME='Wicked Quest' startx") menu.add_item("Arcade", "Zombowl", "GAME='Zombowl' startx") - menu.add_item("Arcade", "Kitchen's Sink", "GAME=\"Kitchen's Sink\" startx") - menu.add_item("Arcade", "Swamp", "GAME='Swamp' startx") + + # Add Kitchen's Sink and Swamp only on x86_64 + if platform.machine() == "x86_64": + menu.add_item("Arcade", "Kitchen's Sink", "GAME=\"Kitchen's Sink\" startx") + menu.add_item("Arcade", "Swamp", "GAME='Swamp' startx") # Add board and card games section menu.add_section("Board and Card Games") @@ -1009,6 +1012,8 @@ if __name__ == "__main__": # Add accessories section menu.add_section("Accessories") menu.add_item("Accessories", "Music Player", "/usr/local/bin/music_player.py") + menu.add_item("Accessories", "Local IP Address", "/usr/local/bin/ip_info.py local") + menu.add_item("Accessories", "Remote IP Address", "/usr/local/bin/ip_info.py remote") menu.add_item("Accessories", "Web Browser", "GAME=Brave startx") # Add system section diff --git a/usr/local/bin/ip_info.py b/usr/local/bin/ip_info.py new file mode 100755 index 0000000..35df3ab --- /dev/null +++ b/usr/local/bin/ip_info.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# IP Address Information Tool for Stormux +# Provides local and remote IP addresses with speech-friendly formatting + +import sys +import subprocess +import re +import socket +import urllib.request +import urllib.error +import speechd + +def init_speech(): + """Initialize speech client""" + try: + client = speechd.SSIPClient('ip_info') + client.set_priority(speechd.Priority.IMPORTANT) + client.set_punctuation(speechd.PunctuationMode.SOME) + return client + except Exception as e: + print(f"Could not initialize speech: {e}") + return None + +def speak(client, text): + """Speak text using speech client""" + if client: + try: + client.speak(text) + except Exception: + print(text) + else: + print(text) + +def format_ip_for_speech(ip_address): + """Format IP address for clear speech synthesis""" + if not ip_address: + return "No IP address found" + + # Split IP into parts and replace dots with "dot" + parts = ip_address.split('.') + if len(parts) != 4: + return ip_address # Return as-is if not a standard IPv4 + + # Join with " dot " and add spaces between digits for clarity + formatted_parts = [] + for part in parts: + # Add spaces between digits for better speech clarity + spaced_digits = ' '.join(part) + formatted_parts.append(spaced_digits) + + return ' dot '.join(formatted_parts) + +def get_local_ip(): + """Get the local IP address using multiple methods""" + + # Method 1: Try to connect to a remote host to determine local IP + try: + # Create a socket and connect to a remote address + # This doesn't actually send data, just determines routing + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.connect(("8.8.8.8", 80)) + local_ip = sock.getsockname()[0] + sock.close() + + # Validate it's not a loopback address + if not local_ip.startswith('127.'): + return local_ip + except Exception: + pass + + # Method 2: Parse ip route command output + try: + result = subprocess.run(['ip', 'route', 'get', '8.8.8.8'], + capture_output=True, text=True, check=True) + + # Look for "src" in the output + match = re.search(r'src\s+(\d+\.\d+\.\d+\.\d+)', result.stdout) + if match: + return match.group(1) + except Exception: + pass + + # Method 3: Parse ip addr show output for common network interfaces + try: + result = subprocess.run(['ip', 'addr', 'show'], + capture_output=True, text=True, check=True) + + # Look for inet addresses that are not loopback + # Common patterns: 192.168.x.x, 10.x.x.x, 172.16-31.x.x + pattern = r'inet\s+(\d+\.\d+\.\d+\.\d+)/\d+' + matches = re.findall(pattern, result.stdout) + + for ip in matches: + # Skip loopback + if ip.startswith('127.'): + continue + # Prefer common private network ranges + if (ip.startswith('192.168.') or + ip.startswith('10.') or + re.match(r'^172\.(1[6-9]|2[0-9]|3[01])\.', ip)): + return ip + + # If no private IPs found, return the first non-loopback + for ip in matches: + if not ip.startswith('127.'): + return ip + + except Exception: + pass + + # Method 4: Fallback using hostname + try: + hostname = socket.gethostname() + return socket.gethostbyname(hostname) + except Exception: + pass + + return None + +def get_remote_ip(): + """Get the remote/public IP address""" + services = [ + 'https://icanhazip.com', + 'https://ipecho.net/plain', + 'https://ifconfig.me/ip', + 'https://api.ipify.org' + ] + + for service in services: + try: + with urllib.request.urlopen(service, timeout=10) as response: + ip = response.read().decode('utf-8').strip() + # Validate it looks like an IP address + if re.match(r'^\d+\.\d+\.\d+\.\d+$', ip): + return ip + except Exception: + continue + + return None + +def main(): + if len(sys.argv) != 2 or sys.argv[1] not in ['local', 'remote']: + print("Usage: ip_info.py [local|remote]") + sys.exit(1) + + mode = sys.argv[1] + speech_client = init_speech() + + if mode == 'local': + print("Getting local IP address...") + ip = get_local_ip() + if ip: + formatted_ip = format_ip_for_speech(ip) + message = f"Local IP address: {formatted_ip}" + speak(speech_client, message) + print(f"Local IP: {ip}") + else: + message = "Could not determine local IP address" + speak(speech_client, message) + print(message) + + elif mode == 'remote': + print("Getting remote IP address...") + ip = get_remote_ip() + if ip: + formatted_ip = format_ip_for_speech(ip) + message = f"Remote IP address: {formatted_ip}" + speak(speech_client, message) + print(f"Remote IP: {ip}") + else: + message = "Could not determine remote IP address. Check internet connection." + speak(speech_client, message) + print(message) + + # Clean up speech client + if speech_client: + try: + speech_client.close() + except: + pass + +if __name__ == "__main__": + main() \ No newline at end of file