feat: server-side PPTX conversion + screenshot dashboard system
PPTX Conversion: - Remove LibreOffice from client dependencies (server uses Gotenberg) - Update docs to reflect clients only display pre-rendered PDFs - Clarify server-side conversion workflow in README and copilot-instructions Screenshot System: - Add background screenshot capture in display_manager.py - Implement Wayland/X11 session detection with tool fallback - Add client-side image processing (downscale + JPEG compression) - Create timestamped files + latest.jpg symlink for easy access - Implement file rotation (max 20 screenshots by default) - Enhance simclient.py to transmit via MQTT dashboard topic - Add structured JSON payload with screenshot, timestamp, system info - New env vars: SCREENSHOT_CAPTURE_INTERVAL, SCREENSHOT_MAX_WIDTH, SCREENSHOT_JPEG_QUALITY, SCREENSHOT_MAX_FILES, SCREENSHOT_ALWAYS Architecture: Two-process design with shared screenshots/ volume
This commit is contained in:
@@ -474,6 +474,22 @@ def get_latest_screenshot():
|
||||
screenshot_dir = os.path.join(os.path.dirname(__file__), "screenshots")
|
||||
if not os.path.exists(screenshot_dir):
|
||||
return None
|
||||
# Prefer 'latest.jpg' symlink/copy if present (written by display_manager)
|
||||
preferred_path = os.path.join(screenshot_dir, "latest.jpg")
|
||||
if os.path.exists(preferred_path):
|
||||
try:
|
||||
with open(preferred_path, "rb") as f:
|
||||
screenshot_data = base64.b64encode(f.read()).decode('utf-8')
|
||||
file_stats = os.stat(preferred_path)
|
||||
logging.debug(f"Using preferred latest.jpg for screenshot ({file_stats.st_size} bytes)")
|
||||
return {
|
||||
"filename": os.path.basename(preferred_path),
|
||||
"data": screenshot_data,
|
||||
"timestamp": datetime.fromtimestamp(file_stats.st_mtime).isoformat(),
|
||||
"size": file_stats.st_size
|
||||
}
|
||||
except Exception as e:
|
||||
logging.debug(f"Could not read latest.jpg, falling back to newest file: {e}")
|
||||
|
||||
# Find the most recent screenshot file
|
||||
screenshot_files = [f for f in os.listdir(screenshot_dir)
|
||||
@@ -495,12 +511,14 @@ def get_latest_screenshot():
|
||||
# Get file info
|
||||
file_stats = os.stat(screenshot_path)
|
||||
|
||||
return {
|
||||
info = {
|
||||
"filename": latest_file,
|
||||
"data": screenshot_data,
|
||||
"timestamp": datetime.fromtimestamp(file_stats.st_mtime).isoformat(),
|
||||
"size": file_stats.st_size
|
||||
}
|
||||
logging.debug(f"Selected latest screenshot: {latest_file} ({file_stats.st_size} bytes)")
|
||||
return info
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error reading screenshot: {e}")
|
||||
@@ -526,12 +544,17 @@ def send_screenshot_heartbeat(client, client_id):
|
||||
|
||||
# Send to dashboard monitoring topic
|
||||
dashboard_topic = f"infoscreen/{client_id}/dashboard"
|
||||
client.publish(dashboard_topic, json.dumps(heartbeat_data))
|
||||
|
||||
if screenshot_info:
|
||||
logging.info(f"Screenshot heartbeat sent: {screenshot_info['filename']} ({screenshot_info['size']} bytes)")
|
||||
payload = json.dumps(heartbeat_data)
|
||||
res = client.publish(dashboard_topic, payload, qos=0)
|
||||
if res.rc == mqtt.MQTT_ERR_SUCCESS:
|
||||
if screenshot_info:
|
||||
logging.info(f"Dashboard heartbeat sent with screenshot: {screenshot_info['filename']} ({screenshot_info['size']} bytes)")
|
||||
else:
|
||||
logging.info("Dashboard heartbeat sent (no screenshot available)")
|
||||
elif res.rc == mqtt.MQTT_ERR_NO_CONN:
|
||||
logging.warning("Dashboard heartbeat publish returned NO_CONN; will retry on next interval")
|
||||
else:
|
||||
logging.debug("Heartbeat sent without screenshot")
|
||||
logging.warning(f"Dashboard heartbeat publish failed with code: {res.rc}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error sending screenshot heartbeat: {e}")
|
||||
|
||||
Reference in New Issue
Block a user