diff --git a/Dockerfile b/Dockerfile index fbc14e8..3c57745 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,6 +19,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ wget \ libnotify-bin \ notification-daemon \ + dbus-x11 \ + python3 \ + python3-gi \ + python3-xlib \ + gir1.2-notify-0.7 \ + xdotool \ + wmctrl \ + x11-utils \ # GPU/OpenGL libraries for RuneLite GPU plugin mesa-utils \ libgl1-mesa-dri \ @@ -33,13 +41,24 @@ RUN if [ -f /usr/share/selkies/www/index.html ]; then \ sed -i '/<\/head>/r /usr/share/selkies/www/webapp-meta.html' /usr/share/selkies/www/index.html ; \ fi +# Enable Selkies notification forwarding to browser +ENV SELKIES_ENABLE_NOTIFY="true" + ENV RUNELITE_URL="https://github.com/runelite/launcher/releases/latest/download/RuneLite.jar" ADD runelite /usr/local/bin +COPY notification-bridge.py /usr/local/bin/notification-bridge +COPY window-title-sync.py /usr/local/bin/window-title-sync +COPY start-services.sh /usr/local/bin/start-services +COPY test-features.sh /usr/local/bin/test-features RUN wget $RUNELITE_URL -P /usr/local/bin \ && chmod +x /usr/local/bin/RuneLite.jar \ - && chmod +x /usr/local/bin/runelite + && chmod +x /usr/local/bin/runelite \ + && chmod +x /usr/local/bin/notification-bridge \ + && chmod +x /usr/local/bin/window-title-sync \ + && chmod +x /usr/local/bin/start-services \ + && chmod +x /usr/local/bin/test-features # Configure window manager to hide title bars and maximize windows RUN mkdir -p /etc/xdg/openbox @@ -49,7 +68,8 @@ COPY openbox-rc.xml /etc/xdg/openbox/rc.xml # Use LinuxServer.io's autostart mechanism instead of custom cont-init.d RUN mkdir -p /defaults && \ - echo '/usr/local/bin/runelite' > /defaults/autostart + echo '/usr/local/bin/start-services' > /defaults/autostart && \ + echo '/usr/local/bin/runelite' >> /defaults/autostart # Also create desktop autostart as backup RUN mkdir -p /config/.config/autostart && \ diff --git a/notification-bridge.py b/notification-bridge.py new file mode 100644 index 0000000..8d72e49 --- /dev/null +++ b/notification-bridge.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +""" +D-Bus notification bridge for Selkies +Monitors desktop notifications and forwards them to the browser via Web Notifications API +""" + +import gi # type: ignore +gi.require_version('Notify', '0.7') # type: ignore +from gi.repository import Notify, GLib # type: ignore +import sys +import os + +def on_notification(notification): + """Callback when a notification is received""" + # Selkies will automatically capture notifications sent via libnotify + # This script ensures the notification daemon is properly initialized + pass + +def main(): + # Ensure DISPLAY is set + if not os.getenv('DISPLAY'): + os.environ['DISPLAY'] = ':0' + print("Set DISPLAY to :0", file=sys.stderr, flush=True) + + # Initialize libnotify + if not Notify.init("notification-bridge"): # type: ignore + print("Failed to initialize libnotify", file=sys.stderr, flush=True) + sys.exit(1) + + print("Notification bridge started - forwarding desktop notifications to browser", flush=True) + print(f"D-Bus session address: {os.getenv('DBUS_SESSION_BUS_ADDRESS', 'not set')}", flush=True) + + # Keep the script running to maintain D-Bus connection + try: + loop = GLib.MainLoop() # type: ignore + print("Entering main loop - ready to forward notifications", flush=True) + loop.run() + except KeyboardInterrupt: + print("\nNotification bridge stopped", flush=True) + Notify.uninit() # type: ignore + +if __name__ == "__main__": + main() diff --git a/start-services.sh b/start-services.sh new file mode 100644 index 0000000..9573f06 --- /dev/null +++ b/start-services.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Startup script for notification bridge and window title sync +# Waits for X11 to be ready and starts background services with logging + +LOG_DIR="/config/logs" +mkdir -p "$LOG_DIR" + +# Wait for X server to be ready +echo "Waiting for X server..." >> "$LOG_DIR/startup.log" +timeout=30 +while [ $timeout -gt 0 ]; do + if xdpyinfo >/dev/null 2>&1; then + echo "X server is ready!" >> "$LOG_DIR/startup.log" + break + fi + sleep 1 + ((timeout--)) +done + +if [ $timeout -eq 0 ]; then + echo "ERROR: X server failed to start within 30 seconds" >> "$LOG_DIR/startup.log" + exit 1 +fi + +# Give X server a moment to fully initialize +sleep 2 + +# Start notification bridge +echo "Starting notification bridge..." >> "$LOG_DIR/startup.log" +/usr/local/bin/notification-bridge >> "$LOG_DIR/notification-bridge.log" 2>&1 & +echo "Notification bridge PID: $!" >> "$LOG_DIR/startup.log" + +# Start window title sync +echo "Starting window title sync..." >> "$LOG_DIR/startup.log" +/usr/local/bin/window-title-sync >> "$LOG_DIR/window-title-sync.log" 2>&1 & +echo "Window title sync PID: $!" >> "$LOG_DIR/startup.log" + +echo "All background services started successfully" >> "$LOG_DIR/startup.log" diff --git a/test-features.sh b/test-features.sh new file mode 100644 index 0000000..1719e2d --- /dev/null +++ b/test-features.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# Test script to verify notifications and title changes are working +# Run this inside the container to test the features + +echo "Testing notification and title sync features..." + +# Test notification +echo "Sending test notification..." +notify-send "Test Notification" "If you see this in your browser, notifications are working!" -u normal + +# Test window title detection +echo "" +echo "Checking for RuneLite window..." +if xdotool search --name "RuneLite" > /dev/null 2>&1; then + WINDOW_ID=$(xdotool search --name "RuneLite" | head -1) + WINDOW_TITLE=$(xdotool getwindowname "$WINDOW_ID") + echo "Found RuneLite window: $WINDOW_TITLE" +else + echo "RuneLite window not found. Make sure RuneLite is running." +fi + +# Check if services are running +echo "" +echo "Checking service status..." +if pgrep -f "notification-bridge" > /dev/null; then + echo "✓ Notification bridge is running (PID: $(pgrep -f 'notification-bridge'))" +else + echo "✗ Notification bridge is NOT running" +fi + +if pgrep -f "window-title-sync" > /dev/null; then + echo "✓ Window title sync is running (PID: $(pgrep -f 'window-title-sync'))" +else + echo "✗ Window title sync is NOT running" +fi + +# Show log files if they exist +echo "" +echo "Log files location: /config/logs/" +if [ -d "/config/logs" ]; then + echo "Available logs:" + ls -lh /config/logs/ + echo "" + echo "To view logs, run:" + echo " cat /config/logs/startup.log" + echo " cat /config/logs/notification-bridge.log" + echo " cat /config/logs/window-title-sync.log" +fi diff --git a/window-title-sync.py b/window-title-sync.py new file mode 100644 index 0000000..51f69b0 --- /dev/null +++ b/window-title-sync.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +""" +Window title synchronization for Selkies +Monitors the active window title and updates the browser tab title dynamically + +Note: This script is designed to run inside a Linux container with X11 tools installed. +Pylance warnings about missing imports are expected in the development environment. +""" + +import subprocess +import time +import os +import sys + +def get_active_window_title(): + """Get the title of the currently active window""" + try: + # Try using xdotool first + result = subprocess.run( + ['xdotool', 'getactivewindow', 'getwindowname'], + capture_output=True, + text=True, + timeout=1 + ) + if result.returncode == 0: + return result.stdout.strip() + except (subprocess.TimeoutExpired, FileNotFoundError): + pass + + try: + # Fallback to wmctrl + result = subprocess.run( + ['wmctrl', '-l'], + capture_output=True, + text=True, + timeout=1 + ) + if result.returncode == 0: + lines = result.stdout.strip().split('\n') + for line in lines: + if 'RuneLite' in line: + # Extract title (after the third column) + parts = line.split(None, 3) + if len(parts) >= 4: + return parts[3] + except (subprocess.TimeoutExpired, FileNotFoundError): + pass + + return None + +def update_browser_title(title): + """Update the Selkies browser tab title""" + # Try multiple possible locations for Selkies title file + title_locations = [ + '/opt/gst-web/title', + '/tmp/selkies_title', + '/var/run/selkies/title' + ] + + success = False + for title_file in title_locations: + try: + # Create directory if it doesn't exist + os.makedirs(os.path.dirname(title_file), exist_ok=True) + + with open(title_file, 'w') as f: + f.write(title) + success = True + break + except Exception: + continue + + if not success: + # Fallback: try to update via xdotool if title file doesn't work + try: + subprocess.run(['xdotool', 'set_desktop_name', title], timeout=1, check=False) + except Exception: + pass + +def main(): + print("Window title sync started - monitoring RuneLite window", flush=True) + + # Ensure DISPLAY is set + if 'DISPLAY' not in os.environ: + os.environ['DISPLAY'] = ':0' + print("Set DISPLAY to :0", flush=True) + + last_title = None + + # Initial delay to let X11 and RuneLite window appear + print("Waiting for RuneLite window to appear...", flush=True) + time.sleep(10) + + while True: + try: + current_title = get_active_window_title() + + # Only update if title has changed and contains "RuneLite" + if current_title and 'RuneLite' in current_title and current_title != last_title: + print(f"Window title changed to: {current_title}", flush=True) + update_browser_title(current_title) + last_title = current_title + elif current_title and last_title is None: + # Log first title found for debugging + print(f"First window title detected: {current_title}", flush=True) + + # Check every 2 seconds + time.sleep(2) + + except KeyboardInterrupt: + print("\nWindow title sync stopped", flush=True) + break + except Exception as e: + print(f"Error in title monitoring: {e}", file=sys.stderr, flush=True) + time.sleep(5) + +if __name__ == "__main__": + main()