diff --git a/90-start-services b/90-start-services new file mode 100644 index 0000000..75870bb --- /dev/null +++ b/90-start-services @@ -0,0 +1,74 @@ +#!/usr/bin/with-contenv bash +# Start notification bridge and window title sync services +# This runs during container initialization + +echo "[init] Starting background services for notifications and window title sync" + +# Create log directory +mkdir -p /config/logs +chown abc:abc /config/logs + +# Wait for X server in background, then start services +( + # Log current environment + echo "[init] $(date) - Starting background services" >> /config/logs/startup.log + echo "[init] Running as user: $(whoami)" >> /config/logs/startup.log + echo "[init] Python version: $(python3 --version 2>&1)" >> /config/logs/startup.log + + # Wait for X server to be ready + echo "[init] Waiting for X server..." >> /config/logs/startup.log + timeout=60 + while [ $timeout -gt 0 ]; do + if DISPLAY=:0 xdpyinfo >/dev/null 2>&1; then + echo "[init] X server is ready!" >> /config/logs/startup.log + break + fi + sleep 1 + ((timeout--)) + done + + if [ $timeout -eq 0 ]; then + echo "[init] ERROR: X server failed to start within 60 seconds" >> /config/logs/startup.log + exit 1 + fi + + # Give X server and desktop environment time to fully initialize + echo "[init] Waiting 10 seconds for desktop environment to stabilize..." >> /config/logs/startup.log + sleep 10 + + # Start notification bridge with proper permissions + echo "[init] Starting notification bridge..." >> /config/logs/startup.log + s6-setuidgid abc env DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-unix:path=/run/user/911/bus}" \ + /usr/local/bin/notification-bridge >> /config/logs/notification-bridge.log 2>&1 & + NOTIF_PID=$! + echo "[init] Notification bridge started with PID: $NOTIF_PID" >> /config/logs/startup.log + + # Verify it's running + sleep 1 + if kill -0 $NOTIF_PID 2>/dev/null; then + echo "[init] Notification bridge is running" >> /config/logs/startup.log + else + echo "[init] WARNING: Notification bridge may have failed to start" >> /config/logs/startup.log + fi + + # Start window title sync with proper permissions + echo "[init] Starting window title sync..." >> /config/logs/startup.log + s6-setuidgid abc env DISPLAY=:0 /usr/local/bin/window-title-sync >> /config/logs/window-title-sync.log 2>&1 & + TITLE_PID=$! + echo "[init] Window title sync started with PID: $TITLE_PID" >> /config/logs/startup.log + + # Verify it's running + sleep 1 + if kill -0 $TITLE_PID 2>/dev/null; then + echo "[init] Window title sync is running" >> /config/logs/startup.log + else + echo "[init] WARNING: Window title sync may have failed to start" >> /config/logs/startup.log + fi + + echo "[init] All background services started successfully at $(date)" >> /config/logs/startup.log + + # Keep log files readable + chown abc:abc /config/logs/*.log 2>/dev/null +) & + +echo "[init] Background services initialization launched" diff --git a/Dockerfile b/Dockerfile index fbc14e8..0a3fe53 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 test-features.sh /usr/local/bin/test-features +COPY 90-start-services /etc/cont-init.d/90-start-services 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/test-features \ + && chmod +x /etc/cont-init.d/90-start-services # Configure window manager to hide title bars and maximize windows RUN mkdir -p /etc/xdg/openbox 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()