- Removed native overlay

parent d697940f
...@@ -33,10 +33,11 @@ Successfully replaced the PyQt5 video player implementation with a comprehensive ...@@ -33,10 +33,11 @@ Successfully replaced the PyQt5 video player implementation with a comprehensive
- **Responsive Design**: Automatic scaling for different resolutions - **Responsive Design**: Automatic scaling for different resolutions
- **GSAP-ready Structure**: Animation framework integration support - **GSAP-ready Structure**: Animation framework integration support
### 3. Legacy Compatibility (mbetterclient/qt_player/overlay_engine.py) ### 3. Legacy Removal (overlay_engine.py)
**UPDATED** PyQt5 → PyQt6 imports and constants for compatibility: **REMOVED** the legacy native overlay engine entirely:
- Updated Qt enums to PyQt6 format (Qt.AlignLeft → Qt.AlignmentFlag.AlignLeft) - The native Qt overlay implementation was replaced by the superior QWebEngineView system
- Maintained backward compatibility for any remaining legacy code - All overlay functionality now uses HTML/CSS/JavaScript templates exclusively
- Removed OverlayEngine and OverlayRenderer classes as they are no longer needed
### 4. Message Bus Integration ### 4. Message Bus Integration
**ENHANCED** message handling for complete thread communication: **ENHANCED** message handling for complete thread communication:
...@@ -135,7 +136,10 @@ message_bus.publish(overlay_message) ...@@ -135,7 +136,10 @@ message_bus.publish(overlay_message)
### Modified Files: ### Modified Files:
- `mbetterclient/qt_player/player.py` - **COMPLETELY REPLACED** with PyQt6 implementation - `mbetterclient/qt_player/player.py` - **COMPLETELY REPLACED** with PyQt6 implementation
- `mbetterclient/qt_player/overlay_engine.py` - Updated PyQt5 → PyQt6 imports - `mbetterclient/qt_player/__init__.py` - Removed legacy overlay engine imports
### Removed Files:
- `mbetterclient/qt_player/overlay_engine.py` - Legacy native overlay implementation removed
### Unchanged Files: ### Unchanged Files:
- `mbetterclient/core/application.py` - Works seamlessly with new implementation - `mbetterclient/core/application.py` - Works seamlessly with new implementation
......
...@@ -173,11 +173,6 @@ Examples: ...@@ -173,11 +173,6 @@ Examples:
help='Disable web dashboard (PyQt interface only)' help='Disable web dashboard (PyQt interface only)'
) )
parser.add_argument(
'--native-overlay',
action='store_true',
help='Use native Qt overlay instead of QWebEngineView (prevents freezing on some systems)'
)
# Screen cast options # Screen cast options
parser.add_argument( parser.add_argument(
...@@ -275,7 +270,6 @@ def main(): ...@@ -275,7 +270,6 @@ def main():
settings.debug_overlay = args.debug_overlay settings.debug_overlay = args.debug_overlay
settings.enable_qt = not args.no_qt settings.enable_qt = not args.no_qt
settings.enable_web = not args.no_web settings.enable_web = not args.no_web
settings.qt.use_native_overlay = args.native_overlay
# Timer settings # Timer settings
if args.start_timer is not None: if args.start_timer is not None:
......
...@@ -922,27 +922,27 @@ class APIClient(ThreadedComponent): ...@@ -922,27 +922,27 @@ class APIClient(ThreadedComponent):
self.message_bus.publish(ready_message) self.message_bus.publish(ready_message)
# Main execution loop # Main execution loop
while self.running: while self.running and not self.shutdown_event.is_set():
try: try:
# Update heartbeat at the start of each loop # Update heartbeat at the start of each loop
self.heartbeat() self.heartbeat()
# Process messages # Process messages
message = self.message_bus.get_message(self.name, timeout=1.0) message = self.message_bus.get_message(self.name, timeout=1.0)
if message: if message:
self._process_message(message) self._process_message(message)
# Update heartbeat before potentially long operations # Update heartbeat before potentially long operations
self.heartbeat() self.heartbeat()
# Execute scheduled API requests # Execute scheduled API requests
self._execute_scheduled_requests() self._execute_scheduled_requests()
# Update heartbeat after operations # Update heartbeat after operations
self.heartbeat() self.heartbeat()
time.sleep(1.0) time.sleep(1.0)
except Exception as e: except Exception as e:
logger.error(f"APIClient run loop error: {e}") logger.error(f"APIClient run loop error: {e}")
# Update heartbeat even in error cases # Update heartbeat even in error cases
......
...@@ -228,7 +228,6 @@ class QtConfig: ...@@ -228,7 +228,6 @@ class QtConfig:
overlay_enabled: bool = True overlay_enabled: bool = True
default_template: str = "news_template" default_template: str = "news_template"
overlay_opacity: float = 0.9 overlay_opacity: float = 0.9
use_native_overlay: bool = False # Use native Qt widgets instead of QWebEngineView
# Performance settings # Performance settings
hardware_acceleration: bool = True hardware_acceleration: bool = True
......
...@@ -133,8 +133,6 @@ class MbetterClientApplication: ...@@ -133,8 +133,6 @@ class MbetterClientApplication:
stored_settings.enable_web = self.settings.enable_web stored_settings.enable_web = self.settings.enable_web
stored_settings.enable_screen_cast = self.settings.enable_screen_cast # Preserve screen cast setting stored_settings.enable_screen_cast = self.settings.enable_screen_cast # Preserve screen cast setting
# Preserve command line Qt overlay setting
stored_settings.qt.use_native_overlay = self.settings.qt.use_native_overlay
# Preserve command line debug settings # Preserve command line debug settings
stored_settings.debug_overlay = self.settings.debug_overlay stored_settings.debug_overlay = self.settings.debug_overlay
......
...@@ -63,7 +63,7 @@ class MatchTimerComponent(ThreadedComponent): ...@@ -63,7 +63,7 @@ class MatchTimerComponent(ThreadedComponent):
"""Main timer loop""" """Main timer loop"""
logger.info("MatchTimer component started") logger.info("MatchTimer component started")
while self.running: while self.running and not self.shutdown_event.is_set():
try: try:
# Process any pending messages first # Process any pending messages first
message = self.message_bus.get_message(self.name, timeout=0.1) message = self.message_bus.get_message(self.name, timeout=0.1)
......
...@@ -183,21 +183,21 @@ class ScreenCastComponent(ThreadedComponent): ...@@ -183,21 +183,21 @@ class ScreenCastComponent(ThreadedComponent):
self._connect_chromecast() self._connect_chromecast()
# Main loop - monitor and restart capture if needed # Main loop - monitor and restart capture if needed
while self.running: while self.running and not self.shutdown_event.is_set():
try: try:
# Process messages # Process messages
message = self.message_bus.get_message(self.name, timeout=1.0) message = self.message_bus.get_message(self.name, timeout=1.0)
if message: if message:
self._process_message(message) self._process_message(message)
# Check capture health # Check capture health
self._check_capture_health() self._check_capture_health()
# Update heartbeat # Update heartbeat
self.heartbeat() self.heartbeat()
time.sleep(1.0) time.sleep(1.0)
except Exception as e: except Exception as e:
logger.error(f"ScreenCast run loop error: {e}") logger.error(f"ScreenCast run loop error: {e}")
time.sleep(1.0) time.sleep(1.0)
......
...@@ -233,10 +233,11 @@ class ThreadManager: ...@@ -233,10 +233,11 @@ class ThreadManager:
def stop_all(self, timeout: float = 10.0) -> bool: def stop_all(self, timeout: float = 10.0) -> bool:
"""Stop all components""" """Stop all components"""
logger.info("Stopping all components...") logger.info("Stopping all components...")
success = True success = True
stop_timeout = timeout / max(len(self.components), 1) # Distribute timeout # Use at least 8 seconds per component, but distribute total timeout
stop_timeout = max(timeout / max(len(self.components), 1), 8.0)
with self._lock: with self._lock:
for name, component in self.components.items(): for name, component in self.components.items():
try: try:
...@@ -248,12 +249,12 @@ class ThreadManager: ...@@ -248,12 +249,12 @@ class ThreadManager:
except Exception as e: except Exception as e:
logger.error(f"Exception stopping component {name}: {e}") logger.error(f"Exception stopping component {name}: {e}")
success = False success = False
if success: if success:
logger.info("All components stopped successfully") logger.info("All components stopped successfully")
else: else:
logger.warning("Some components failed to stop cleanly") logger.warning("Some components failed to stop cleanly")
return success return success
def restart_component(self, name: str, timeout: float = 5.0) -> bool: def restart_component(self, name: str, timeout: float = 5.0) -> bool:
......
...@@ -3,13 +3,10 @@ PyQt video player with overlay templates for MbetterClient ...@@ -3,13 +3,10 @@ PyQt video player with overlay templates for MbetterClient
""" """
from .player import QtVideoPlayer from .player import QtVideoPlayer
from .overlay_engine import OverlayEngine, OverlayRenderer
from .templates import TemplateManager, NewsTemplate from .templates import TemplateManager, NewsTemplate
__all__ = [ __all__ = [
'QtVideoPlayer', 'QtVideoPlayer',
'OverlayEngine',
'OverlayRenderer',
'TemplateManager', 'TemplateManager',
'NewsTemplate' 'NewsTemplate'
] ]
\ No newline at end of file
"""
Overlay engine for rendering dynamic overlays on video content
NOTE: This is legacy code - the new PyQt6 implementation uses QWebEngineView for overlays
"""
import time
import logging
from typing import Dict, Any, Optional, List, Tuple
from pathlib import Path
from PyQt6.QtCore import Qt, QRect, QPoint, QTimer, QPropertyAnimation, QEasingCurve
from PyQt6.QtGui import (
QPainter, QPen, QBrush, QColor, QFont, QPixmap, QFontMetrics,
QLinearGradient, QRadialGradient, QPolygon
)
from PyQt6.QtWidgets import QGraphicsEffect
logger = logging.getLogger(__name__)
class OverlayElement:
"""Base class for overlay elements"""
def __init__(self, element_id: str, element_type: str, config: Dict[str, Any]):
self.id = element_id
self.type = element_type
self.config = config
self.visible = config.get('visible', True)
self.opacity = config.get('opacity', 1.0)
self.x = config.get('x', 0)
self.y = config.get('y', 0)
self.width = config.get('width', 100)
self.height = config.get('height', 50)
self.z_index = config.get('z_index', 0)
# Animation properties
self.animation_config = config.get('animation', {})
self.animation_start_time = None
def get_rect(self) -> QRect:
"""Get element rectangle"""
return QRect(int(self.x), int(self.y), int(self.width), int(self.height))
def update_animation(self, elapsed_time: float):
"""Update animation state"""
if not self.animation_config:
return
animation_type = self.animation_config.get('type')
if animation_type == 'scroll':
self._update_scroll_animation(elapsed_time)
elif animation_type == 'fade':
self._update_fade_animation(elapsed_time)
elif animation_type == 'bounce':
self._update_bounce_animation(elapsed_time)
def _update_scroll_animation(self, elapsed_time: float):
"""Update scrolling text animation"""
speed = self.animation_config.get('speed', 100) # pixels per second
direction = self.animation_config.get('direction', 'left')
if direction == 'left':
self.x -= speed * elapsed_time / 1000.0
elif direction == 'right':
self.x += speed * elapsed_time / 1000.0
elif direction == 'up':
self.y -= speed * elapsed_time / 1000.0
elif direction == 'down':
self.y += speed * elapsed_time / 1000.0
def _update_fade_animation(self, elapsed_time: float):
"""Update fade animation"""
duration = self.animation_config.get('duration', 2000) # ms
fade_type = self.animation_config.get('fade_type', 'in')
if self.animation_start_time is None:
self.animation_start_time = elapsed_time
progress = (elapsed_time - self.animation_start_time) / duration
progress = max(0.0, min(1.0, progress))
if fade_type == 'in':
self.opacity = progress
elif fade_type == 'out':
self.opacity = 1.0 - progress
elif fade_type == 'in_out':
if progress <= 0.5:
self.opacity = progress * 2
else:
self.opacity = (1.0 - progress) * 2
def _update_bounce_animation(self, elapsed_time: float):
"""Update bounce animation"""
import math
frequency = self.animation_config.get('frequency', 1.0) # Hz
amplitude = self.animation_config.get('amplitude', 10) # pixels
offset = amplitude * math.sin(2 * math.pi * frequency * elapsed_time / 1000.0)
self.y = self.config.get('y', 0) + offset
def render(self, painter: QPainter, canvas_rect: QRect):
"""Render element (to be implemented by subclasses)"""
pass
class RectangleElement(OverlayElement):
"""Rectangle overlay element"""
def __init__(self, element_id: str, config: Dict[str, Any]):
super().__init__(element_id, 'rectangle', config)
self.color = QColor(config.get('color', '#000000'))
self.border_color = QColor(config.get('border_color', '#ffffff'))
self.border_width = config.get('border_width', 0)
self.corner_radius = config.get('corner_radius', 0)
def render(self, painter: QPainter, canvas_rect: QRect):
"""Render rectangle"""
if not self.visible or self.opacity <= 0:
return
# Set opacity
painter.setOpacity(self.opacity)
rect = self.get_rect()
# Fill
if self.color.alpha() > 0:
brush = QBrush(self.color)
painter.setBrush(brush)
else:
painter.setBrush(Qt.NoBrush)
# Border
if self.border_width > 0:
pen = QPen(self.border_color, self.border_width)
painter.setPen(pen)
else:
painter.setPen(Qt.NoPen)
# Draw
if self.corner_radius > 0:
painter.drawRoundedRect(rect, self.corner_radius, self.corner_radius)
else:
painter.drawRect(rect)
class TextElement(OverlayElement):
"""Text overlay element"""
def __init__(self, element_id: str, config: Dict[str, Any]):
super().__init__(element_id, 'text', config)
self.text = config.get('text', '')
self.font_family = config.get('font_family', 'Arial')
self.font_size = config.get('font_size', 16)
self.font_weight = config.get('font_weight', 'normal')
self.color = QColor(config.get('color', '#ffffff'))
self.background_color = QColor(config.get('background_color', 'transparent'))
self.alignment = config.get('alignment', 'left')
self.word_wrap = config.get('word_wrap', False)
# Scrolling text properties
self.scroll_position = 0
self.text_width = 0
def set_text(self, text: str):
"""Update text content"""
self.text = text
self.scroll_position = 0 # Reset scroll position
def render(self, painter: QPainter, canvas_rect: QRect):
"""Render text"""
if not self.visible or self.opacity <= 0 or not self.text:
return
# Set opacity
painter.setOpacity(self.opacity)
# Setup font
font = QFont(self.font_family, self.font_size)
if self.font_weight == 'bold':
font.setBold(True)
painter.setFont(font)
# Get text metrics
metrics = QFontMetrics(font)
self.text_width = metrics.boundingRect(self.text).width()
rect = self.get_rect()
# Background
if self.background_color.alpha() > 0:
painter.fillRect(rect, self.background_color)
# Text color
painter.setPen(self.color)
# Handle scrolling animation
if self.animation_config.get('type') == 'scroll':
self._render_scrolling_text(painter, rect, metrics)
else:
self._render_static_text(painter, rect)
def _render_scrolling_text(self, painter: QPainter, rect: QRect, metrics: QFontMetrics):
"""Render scrolling text"""
if self.text_width <= rect.width():
# Text fits, no scrolling needed
self._render_static_text(painter, rect)
return
# Calculate scroll position
direction = self.animation_config.get('direction', 'left')
if direction == 'left':
# Reset position when text fully scrolled out
if self.x < -self.text_width:
self.x = rect.right()
# Draw text at current position
text_rect = QRect(int(self.x), rect.y(), self.text_width, rect.height())
else:
text_rect = rect
# Draw text
alignment = Qt.AlignVCenter
if self.alignment == 'center':
alignment |= Qt.AlignHCenter
elif self.alignment == 'right':
alignment |= Qt.AlignRight
else:
alignment |= Qt.AlignLeft
painter.drawText(text_rect, alignment, self.text)
def _render_static_text(self, painter: QPainter, rect: QRect):
"""Render static text"""
# Text alignment
alignment = Qt.AlignVCenter
if self.alignment == 'center':
alignment |= Qt.AlignHCenter
elif self.alignment == 'right':
alignment |= Qt.AlignRight
else:
alignment |= Qt.AlignLeft
if self.word_wrap:
alignment |= Qt.TextWordWrap
painter.drawText(rect, alignment, self.text)
class ImageElement(OverlayElement):
"""Image overlay element"""
def __init__(self, element_id: str, config: Dict[str, Any]):
super().__init__(element_id, 'image', config)
self.source = config.get('source', '')
self.fit = config.get('fit', 'contain') # contain, cover, fill, scale-down
self.pixmap: Optional[QPixmap] = None
self._load_image()
def _load_image(self):
"""Load image from source"""
try:
if self.source:
# Handle both absolute and relative paths
if Path(self.source).is_absolute():
image_path = Path(self.source)
else:
# Relative to project root
project_root = Path(__file__).parent.parent.parent
image_path = project_root / self.source
if image_path.exists():
self.pixmap = QPixmap(str(image_path))
logger.debug(f"Loaded image: {image_path}")
else:
logger.warning(f"Image not found: {image_path}")
except Exception as e:
logger.error(f"Failed to load image {self.source}: {e}")
def set_source(self, source: str):
"""Update image source"""
self.source = source
self._load_image()
def render(self, painter: QPainter, canvas_rect: QRect):
"""Render image"""
if not self.visible or self.opacity <= 0 or not self.pixmap:
return
# Set opacity
painter.setOpacity(self.opacity)
rect = self.get_rect()
# Scale pixmap based on fit mode
scaled_pixmap = self._scale_pixmap(self.pixmap, rect)
# Calculate position for centering if needed
if self.fit in ['contain', 'scale-down']:
x_offset = (rect.width() - scaled_pixmap.width()) // 2
y_offset = (rect.height() - scaled_pixmap.height()) // 2
draw_pos = QPoint(rect.x() + x_offset, rect.y() + y_offset)
else:
draw_pos = rect.topLeft()
painter.drawPixmap(draw_pos, scaled_pixmap)
def _scale_pixmap(self, pixmap: QPixmap, target_rect: QRect) -> QPixmap:
"""Scale pixmap according to fit mode"""
if self.fit == 'fill':
return pixmap.scaled(target_rect.size(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
elif self.fit == 'cover':
return pixmap.scaled(target_rect.size(), Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)
elif self.fit == 'contain':
return pixmap.scaled(target_rect.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
elif self.fit == 'scale-down':
if pixmap.width() <= target_rect.width() and pixmap.height() <= target_rect.height():
return pixmap # No scaling needed
else:
return pixmap.scaled(target_rect.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
else:
return pixmap
class OverlayRenderer:
"""Manages rendering of overlay elements"""
def __init__(self):
self.elements: Dict[str, OverlayElement] = {}
self.start_time = time.time() * 1000 # milliseconds
self.last_update_time = self.start_time
def add_element(self, element: OverlayElement):
"""Add overlay element"""
self.elements[element.id] = element
def remove_element(self, element_id: str):
"""Remove overlay element"""
if element_id in self.elements:
del self.elements[element_id]
def get_element(self, element_id: str) -> Optional[OverlayElement]:
"""Get overlay element by ID"""
return self.elements.get(element_id)
def clear_elements(self):
"""Clear all elements"""
self.elements.clear()
def update_element_data(self, element_id: str, data: Dict[str, Any]):
"""Update element data"""
element = self.get_element(element_id)
if element:
if element.type == 'text' and 'text' in data:
element.set_text(data['text'])
elif element.type == 'image' and 'source' in data:
element.set_source(data['source'])
# Update other properties
for key, value in data.items():
if hasattr(element, key):
setattr(element, key, value)
def render(self, painter: QPainter, canvas_rect: QRect):
"""Render all overlay elements"""
current_time = time.time() * 1000
elapsed_time = current_time - self.last_update_time
self.last_update_time = current_time
# Sort elements by z-index
sorted_elements = sorted(self.elements.values(), key=lambda e: e.z_index)
# Render elements
for element in sorted_elements:
try:
# Update animations
element.update_animation(current_time - self.start_time)
# Render element
element.render(painter, canvas_rect)
except Exception as e:
logger.error(f"Failed to render element {element.id}: {e}")
class OverlayEngine:
"""Main overlay engine"""
def __init__(self):
self.renderer = OverlayRenderer()
self.template_config: Optional[Dict[str, Any]] = None
self.overlay_data: Dict[str, Any] = {}
self.playback_position = 0
self.playback_duration = 0
def load_template(self, template_config: Dict[str, Any], overlay_data: Dict[str, Any] = None):
"""Load overlay template"""
try:
self.template_config = template_config
self.overlay_data = overlay_data or {}
# Clear existing elements
self.renderer.clear_elements()
# Create elements from template
elements_config = template_config.get('elements', [])
for element_config in elements_config:
element = self._create_element(element_config)
if element:
self.renderer.add_element(element)
# Apply overlay data
self._apply_overlay_data()
logger.info(f"Loaded template with {len(elements_config)} elements")
except Exception as e:
logger.error(f"Failed to load template: {e}")
def _create_element(self, config: Dict[str, Any]) -> Optional[OverlayElement]:
"""Create overlay element from configuration"""
element_type = config.get('type')
element_id = config.get('id')
if not element_type or not element_id:
logger.warning("Element missing type or id")
return None
try:
if element_type == 'rectangle':
return RectangleElement(element_id, config)
elif element_type == 'text':
return TextElement(element_id, config)
elif element_type == 'image':
return ImageElement(element_id, config)
else:
logger.warning(f"Unknown element type: {element_type}")
return None
except Exception as e:
logger.error(f"Failed to create element {element_id}: {e}")
return None
def update_overlay_data(self, overlay_data: Dict[str, Any]):
"""Update overlay data"""
self.overlay_data.update(overlay_data)
self._apply_overlay_data()
def _apply_overlay_data(self):
"""Apply overlay data to elements"""
try:
for element_id, data in self.overlay_data.items():
self.renderer.update_element_data(element_id, data)
except Exception as e:
logger.error(f"Failed to apply overlay data: {e}")
def update_playback_position(self, position: int, duration: int):
"""Update video playback position"""
self.playback_position = position
self.playback_duration = duration
def render(self, painter: QPainter, canvas_rect: QRect):
"""Render overlay"""
try:
self.renderer.render(painter, canvas_rect)
except Exception as e:
logger.error(f"Overlay render failed: {e}")
def get_element_by_id(self, element_id: str) -> Optional[OverlayElement]:
"""Get element by ID"""
return self.renderer.get_element(element_id)
def set_element_visibility(self, element_id: str, visible: bool):
"""Set element visibility"""
element = self.get_element_by_id(element_id)
if element:
element.visible = visible
def set_element_text(self, element_id: str, text: str):
"""Set text content for text element"""
element = self.get_element_by_id(element_id)
if element and element.type == 'text':
element.set_text(text)
def set_element_image(self, element_id: str, image_source: str):
"""Set image source for image element"""
element = self.get_element_by_id(element_id)
if element and element.type == 'image':
element.set_source(image_source)
\ No newline at end of file
...@@ -647,16 +647,44 @@ class OverlayWebView(QWebEngineView): ...@@ -647,16 +647,44 @@ class OverlayWebView(QWebEngineView):
self.setup_web_view() self.setup_web_view()
self._setup_custom_scheme() self._setup_custom_scheme()
logger.info(f"OverlayWebView initialized - builtin: {self.builtin_templates_dir}, uploaded: {self.uploaded_templates_dir}") logger.info(f"OverlayWebView initialized - builtin: {self.builtin_templates_dir}, uploaded: {self.uploaded_templates_dir}")
def setup_web_view(self): def setup_web_view(self):
"""Setup web view with proper transparency for overlay""" """Setup web view with proper transparency for overlay"""
logger.info("OverlayWebView.setup_web_view() - Starting setup") logger.info("OverlayWebView.setup_web_view() - Starting setup")
# Enhanced GPU detection and logging # Enhanced GPU detection and logging for VirtualBox compatibility
import os import os
is_mesa = os.environ.get('LIBGL_ALWAYS_SOFTWARE') == '1' or \ is_mesa = os.environ.get('LIBGL_ALWAYS_SOFTWARE') == '1' or \
os.environ.get('MESA_GL_VERSION_OVERRIDE') is not None os.environ.get('MESA_GL_VERSION_OVERRIDE') is not None
# Check if running in VirtualBox
is_virtualbox = False
try:
with open('/sys/devices/virtual/dmi/id/sys_vendor', 'r') as f:
vendor = f.read().strip().lower()
if 'virtualbox' in vendor or 'innotek' in vendor:
is_virtualbox = True
logger.warning("VIRTUALBOX ENVIRONMENT DETECTED - Overlay compatibility issues likely")
except:
# Check for VirtualBox guest additions
try:
import subprocess
result = subprocess.run(['lsmod'], capture_output=True, text=True, timeout=2)
if 'vboxvideo' in result.stdout or 'vboxguest' in result.stdout:
is_virtualbox = True
logger.warning("VIRTUALBOX GUEST MODULES DETECTED - Overlay compatibility issues likely")
except:
pass
if is_virtualbox:
logger.warning("=== VIRTUALBOX OVERLAY DIAGNOSTICS ===")
logger.warning("VirtualBox detected - common overlay issues:")
logger.warning("1. GPU acceleration disabled by default")
logger.warning("2. Limited OpenGL support")
logger.warning("3. Qt WebEngine may fallback to software rendering")
logger.warning("4. Transparency effects may not work")
logger.warning("5. WebChannel communication may be unreliable")
# Log GPU-related environment variables # Log GPU-related environment variables
gpu_env_vars = [ gpu_env_vars = [
'LIBGL_ALWAYS_SOFTWARE', 'LIBGL_ALWAYS_SOFTWARE',
...@@ -666,7 +694,9 @@ class OverlayWebView(QWebEngineView): ...@@ -666,7 +694,9 @@ class OverlayWebView(QWebEngineView):
'DISPLAY', 'DISPLAY',
'XDG_SESSION_TYPE', 'XDG_SESSION_TYPE',
'QTWEBENGINE_DISABLE_SANDBOX', 'QTWEBENGINE_DISABLE_SANDBOX',
'QTWEBENGINE_REMOTE_DEBUGGING' 'QTWEBENGINE_REMOTE_DEBUGGING',
'QTWEBENGINE_DISABLE_GPU',
'QT_OPENGL'
] ]
logger.info("=== GPU Environment Check ===") logger.info("=== GPU Environment Check ===")
...@@ -684,7 +714,7 @@ class OverlayWebView(QWebEngineView): ...@@ -684,7 +714,7 @@ class OverlayWebView(QWebEngineView):
gpu_info = result.stdout.strip().split(',') gpu_info = result.stdout.strip().split(',')
logger.info(f"NVIDIA GPU detected: {gpu_info[0]}, Memory: {gpu_info[1]}/{gpu_info[2]}MB, Driver: {gpu_info[3]}") logger.info(f"NVIDIA GPU detected: {gpu_info[0]}, Memory: {gpu_info[1]}/{gpu_info[2]}MB, Driver: {gpu_info[3]}")
else: else:
logger.warning("nvidia-smi not available or failed") logger.warning("nvidia-smi not available or failed - likely VirtualBox or no NVIDIA GPU")
# Check GPU processes specifically # Check GPU processes specifically
proc_result = subprocess.run(['nvidia-smi', 'pmon'], capture_output=True, text=True, timeout=5) proc_result = subprocess.run(['nvidia-smi', 'pmon'], capture_output=True, text=True, timeout=5)
...@@ -696,12 +726,12 @@ class OverlayWebView(QWebEngineView): ...@@ -696,12 +726,12 @@ class OverlayWebView(QWebEngineView):
for proc in gpu_processes[:3]: # Log first 3 processes for proc in gpu_processes[:3]: # Log first 3 processes
logger.info(f"GPU Process: {proc}") logger.info(f"GPU Process: {proc}")
else: else:
logger.warning("NO GPU PROCESSES FOUND - This indicates GPU is not being utilized") logger.warning("NO GPU PROCESSES FOUND - This indicates GPU is not being utilized (common in VirtualBox)")
else: else:
logger.warning("Could not check GPU processes") logger.warning("Could not check GPU processes - likely VirtualBox environment")
except Exception as e: except Exception as e:
logger.warning(f"Could not check NVIDIA GPU: {e}") logger.warning(f"Could not check NVIDIA GPU: {e} - likely VirtualBox or no GPU")
# Check OpenGL # Check OpenGL
try: try:
...@@ -710,9 +740,9 @@ class OverlayWebView(QWebEngineView): ...@@ -710,9 +740,9 @@ class OverlayWebView(QWebEngineView):
if result.returncode == 0: if result.returncode == 0:
logger.info(f"OpenGL renderer: {result.stdout.strip()}") logger.info(f"OpenGL renderer: {result.stdout.strip()}")
else: else:
logger.warning("Could not get OpenGL renderer info") logger.warning("Could not get OpenGL renderer info - VirtualBox may not support OpenGL properly")
except Exception as e: except Exception as e:
logger.warning(f"Could not check OpenGL: {e}") logger.warning(f"Could not check OpenGL: {e} - VirtualBox OpenGL issues likely")
# Check Vulkan (Qt WebEngine can use Vulkan on some systems) # Check Vulkan (Qt WebEngine can use Vulkan on some systems)
try: try:
...@@ -720,13 +750,22 @@ class OverlayWebView(QWebEngineView): ...@@ -720,13 +750,22 @@ class OverlayWebView(QWebEngineView):
if result.returncode == 0: if result.returncode == 0:
logger.info("Vulkan is available") logger.info("Vulkan is available")
else: else:
logger.info("Vulkan not available or not configured") logger.info("Vulkan not available or not configured - VirtualBox typically lacks Vulkan support")
except Exception as e: except Exception as e:
logger.debug(f"Vulkan check failed: {e}") logger.debug(f"Vulkan check failed: {e}")
logger.info(f"Mesa software rendering detection: {is_mesa}") logger.info(f"Mesa software rendering detection: {is_mesa}")
if is_mesa: if is_mesa:
logger.warning("MESA SOFTWARE RENDERING DETECTED - GPU acceleration may be disabled") logger.warning("MESA SOFTWARE RENDERING DETECTED - GPU acceleration may be disabled (VirtualBox default)")
elif is_virtualbox:
logger.warning("VirtualBox detected but Mesa not forced - Qt WebEngine may still fallback to software rendering")
# Check Qt WebEngine version and capabilities
try:
from PyQt6.QtWebEngineCore import QWebEngineProfile
logger.info("Qt WebEngine is available")
except ImportError as e:
logger.error(f"Qt WebEngine not available: {e} - overlays will not work")
# Set transparent background on the web page # Set transparent background on the web page
page = self.page() page = self.page()
...@@ -764,14 +803,20 @@ class OverlayWebView(QWebEngineView): ...@@ -764,14 +803,20 @@ class OverlayWebView(QWebEngineView):
logger.info(f"OverlayWebView setup completed - Mesa: {is_mesa}, transparency configured") logger.info(f"OverlayWebView setup completed - Mesa: {is_mesa}, transparency configured")
# Setup WebChannel # Setup WebChannel
logger.info("Setting up WebChannel for overlay communication")
self.web_channel = QWebChannel() self.web_channel = QWebChannel()
# Get message bus from parent window # Get message bus from parent window
message_bus = None message_bus = None
if hasattr(self.parent(), '_message_bus'): if hasattr(self.parent(), '_message_bus'):
message_bus = self.parent()._message_bus message_bus = self.parent()._message_bus
logger.info("Message bus found for WebChannel")
else:
logger.warning("No message bus available for WebChannel - overlay communication may be limited")
self.overlay_channel = OverlayWebChannel(db_manager=self.db_manager, message_bus=message_bus) self.overlay_channel = OverlayWebChannel(db_manager=self.db_manager, message_bus=message_bus)
self.web_channel.registerObject("overlay", self.overlay_channel) self.web_channel.registerObject("overlay", self.overlay_channel)
page.setWebChannel(self.web_channel) page.setWebChannel(self.web_channel)
logger.info("WebChannel setup completed - overlay object registered as 'overlay'")
# Monitor GPU processes after WebEngine initialization # Monitor GPU processes after WebEngine initialization
self._monitor_gpu_processes() self._monitor_gpu_processes()
...@@ -800,6 +845,8 @@ class OverlayWebView(QWebEngineView): ...@@ -800,6 +845,8 @@ class OverlayWebView(QWebEngineView):
# Check Qt WebEngine GPU acceleration status # Check Qt WebEngine GPU acceleration status
QTimer.singleShot(2000, self._check_qt_webengine_gpu_status) QTimer.singleShot(2000, self._check_qt_webengine_gpu_status)
logger.info("OverlayWebView setup completed successfully")
def _enable_debug_console(self, ok=None): def _enable_debug_console(self, ok=None):
"""Enable debug console and ensure JavaScript overrides are active""" """Enable debug console and ensure JavaScript overrides are active"""
try: try:
...@@ -937,6 +984,20 @@ class OverlayWebView(QWebEngineView): ...@@ -937,6 +984,20 @@ class OverlayWebView(QWebEngineView):
"""Load a specific template file, prioritizing uploaded templates""" """Load a specific template file, prioritizing uploaded templates"""
try: try:
logger.info(f"=== LOADING OVERLAY TEMPLATE: {template_name} ===") logger.info(f"=== LOADING OVERLAY TEMPLATE: {template_name} ===")
# Check if running in VirtualBox for additional diagnostics
is_virtualbox = False
try:
with open('/sys/devices/virtual/dmi/id/sys_vendor', 'r') as f:
vendor = f.read().strip().lower()
if 'virtualbox' in vendor or 'innotek' in vendor:
is_virtualbox = True
except:
pass
if is_virtualbox:
logger.warning("VIRTUALBOX: Loading overlay template - potential compatibility issues")
if self.debug_overlay: if self.debug_overlay:
logger.debug(f"GREEN SCREEN DEBUG: Starting template load - {template_name}") logger.debug(f"GREEN SCREEN DEBUG: Starting template load - {template_name}")
logger.debug(f"GREEN SCREEN DEBUG: Current page URL before load: {self.url().toString()}") logger.debug(f"GREEN SCREEN DEBUG: Current page URL before load: {self.url().toString()}")
...@@ -944,6 +1005,7 @@ class OverlayWebView(QWebEngineView): ...@@ -944,6 +1005,7 @@ class OverlayWebView(QWebEngineView):
# CRITICAL FIX: Store visibility state before template load # CRITICAL FIX: Store visibility state before template load
was_visible = self.isVisible() was_visible = self.isVisible()
logger.info(f"Overlay visibility before template load: {was_visible}")
# If no template name provided, use default # If no template name provided, use default
if not template_name: if not template_name:
...@@ -983,6 +1045,16 @@ class OverlayWebView(QWebEngineView): ...@@ -983,6 +1045,16 @@ class OverlayWebView(QWebEngineView):
if template_path and template_path.exists(): if template_path and template_path.exists():
logger.info(f"TEMPLATE FOUND: {template_path} (source: {template_source})") logger.info(f"TEMPLATE FOUND: {template_path} (source: {template_source})")
# Check template file permissions and size
try:
stat_info = template_path.stat()
logger.info(f"Template file size: {stat_info.st_size} bytes")
logger.info(f"Template file permissions: {oct(stat_info.st_mode)}")
logger.info(f"Template file readable: {template_path.stat().st_mode & 0o400 != 0}")
except Exception as stat_error:
logger.warning(f"Could not check template file stats: {stat_error}")
if self.debug_overlay: if self.debug_overlay:
logger.debug(f"GREEN SCREEN DEBUG: About to load template file: {template_path}") logger.debug(f"GREEN SCREEN DEBUG: About to load template file: {template_path}")
logger.debug(f"GREEN SCREEN DEBUG: Template source: {template_source}") logger.debug(f"GREEN SCREEN DEBUG: Template source: {template_source}")
...@@ -1011,12 +1083,28 @@ class OverlayWebView(QWebEngineView): ...@@ -1011,12 +1083,28 @@ class OverlayWebView(QWebEngineView):
self._load_fallback_overlay() self._load_fallback_overlay()
return return
# Check WebEngine page state before loading
page = self.page()
if page:
logger.info("WebEngine page state before template load:")
logger.info(f" - Page URL: {page.url().toString()}")
logger.info(f" - Page title: {page.title()}")
logger.info(f" - Page background color: {page.backgroundColor()}")
logger.info(f" - WebChannel available: {page.webChannel() is not None}")
else:
logger.warning("No WebEngine page available before template load")
logger.info(f"LOADING TEMPLATE INTO WEBENGINE: {template_path}") logger.info(f"LOADING TEMPLATE INTO WEBENGINE: {template_path}")
load_start_time = time.time()
self.load(QUrl.fromLocalFile(str(template_path))) self.load(QUrl.fromLocalFile(str(template_path)))
load_end_time = time.time()
logger.info(f"Template load initiated in {load_end_time - load_start_time:.3f} seconds")
self.current_template = template_name self.current_template = template_name
# CRITICAL FIX: Force visibility recovery after template load # CRITICAL FIX: Force visibility recovery after template load
if was_visible and not self.isVisible(): if was_visible and not self.isVisible():
logger.warning("Overlay lost visibility after template load, recovering")
if self.debug_overlay: if self.debug_overlay:
logger.debug(f"GREEN SCREEN FIX: Recovering overlay visibility after template load") logger.debug(f"GREEN SCREEN FIX: Recovering overlay visibility after template load")
self.show() self.show()
...@@ -1046,6 +1134,10 @@ class OverlayWebView(QWebEngineView): ...@@ -1046,6 +1134,10 @@ class OverlayWebView(QWebEngineView):
if self.debug_overlay: if self.debug_overlay:
logger.debug(f"GREEN SCREEN DEBUG: Template load initiated - {template_path}") logger.debug(f"GREEN SCREEN DEBUG: Template load initiated - {template_path}")
logger.info(f"=== TEMPLATE LOAD COMPLETED: {template_path} (source: {template_source}) ===") logger.info(f"=== TEMPLATE LOAD COMPLETED: {template_path} (source: {template_source}) ===")
# Schedule a check for template load success
QTimer.singleShot(1000, lambda: self._check_template_load_success(template_name))
else: else:
logger.error(f"No template found: {template_name}") logger.error(f"No template found: {template_name}")
# Load fallback minimal overlay # Load fallback minimal overlay
...@@ -1364,10 +1456,86 @@ class OverlayWebView(QWebEngineView): ...@@ -1364,10 +1456,86 @@ class OverlayWebView(QWebEngineView):
except Exception as e: except Exception as e:
logger.error(f"Failed to start GPU process monitoring: {e}") logger.error(f"Failed to start GPU process monitoring: {e}")
def _check_template_load_success(self, expected_template: str):
"""Check if template loaded successfully"""
try:
logger.info(f"=== TEMPLATE LOAD SUCCESS CHECK: {expected_template} ===")
page = self.page()
if page:
current_url = page.url().toString()
logger.info(f"Current page URL: {current_url}")
logger.info(f"Expected template: {expected_template}")
# Check if URL contains the expected template
if expected_template in current_url or expected_template.replace('.html', '') in current_url:
logger.info("✓ Template URL matches expected template")
else:
logger.warning(f"✗ Template URL mismatch - expected {expected_template}, got {current_url}")
# Check page title
title = page.title()
logger.info(f"Page title: '{title}'")
# Run JavaScript to check page content
page.runJavaScript("""
console.log('TEMPLATE CHECK: Page content validation');
console.log('Document readyState:', document.readyState);
console.log('Body exists:', !!document.body);
if (document.body) {
console.log('Body children count:', document.body.children.length);
console.log('Body innerHTML length:', document.body.innerHTML.length);
console.log('Body background:', window.getComputedStyle(document.body).background);
console.log('Body display:', window.getComputedStyle(document.body).display);
console.log('Body visibility:', window.getComputedStyle(document.body).visibility);
// Check for critical overlay elements
const criticalElements = ['titleMain', 'titleSubtitle', 'tickerText'];
criticalElements.forEach(id => {
const el = document.getElementById(id);
console.log(`Element ${id}: ${el ? 'EXISTS' : 'MISSING'}`);
if (el) {
console.log(` - display: ${window.getComputedStyle(el).display}`);
console.log(` - visibility: ${window.getComputedStyle(el).visibility}`);
console.log(` - textContent: '${el.textContent}'`);
}
});
}
console.log('TEMPLATE CHECK: Validation complete');
""")
# Check WebChannel status
web_channel = page.webChannel()
logger.info(f"WebChannel available: {web_channel is not None}")
if web_channel:
logger.info("WebChannel is properly configured")
else:
logger.warning("WebChannel not available - overlay communication will fail")
else:
logger.error("No WebEngine page available for template check")
logger.info(f"=== END TEMPLATE LOAD SUCCESS CHECK ===")
except Exception as e:
logger.error(f"Failed to check template load success: {e}")
def _check_overlay_visibility(self): def _check_overlay_visibility(self):
"""Check overlay window visibility and positioning""" """Check overlay window visibility and positioning"""
try: try:
logger.info("=== OVERLAY VISIBILITY CHECK ===") logger.info("=== OVERLAY VISIBILITY CHECK ===")
# Check if running in VirtualBox
is_virtualbox = False
try:
with open('/sys/devices/virtual/dmi/id/sys_vendor', 'r') as f:
vendor = f.read().strip().lower()
if 'virtualbox' in vendor or 'innotek' in vendor:
is_virtualbox = True
logger.warning("VIRTUALBOX: Running overlay visibility check")
except:
pass
logger.info(f"Overlay window exists: {self.parent() is not None}") logger.info(f"Overlay window exists: {self.parent() is not None}")
if self.parent(): if self.parent():
parent = self.parent() parent = self.parent()
...@@ -1380,6 +1548,15 @@ class OverlayWebView(QWebEngineView): ...@@ -1380,6 +1548,15 @@ class OverlayWebView(QWebEngineView):
logger.info(f"Overlay WebView size: {self.size()}") logger.info(f"Overlay WebView size: {self.size()}")
logger.info(f"Overlay WebView pos: {self.pos()}") logger.info(f"Overlay WebView pos: {self.pos()}")
# Check if overlay is properly positioned over parent
if self.parent():
parent_geo = self.parent().geometry()
overlay_geo = self.geometry()
if parent_geo == overlay_geo:
logger.info("✓ Overlay properly positioned over parent window")
else:
logger.warning(f"✗ Overlay positioning mismatch - parent: {parent_geo}, overlay: {overlay_geo}")
# Check WebEngine page status # Check WebEngine page status
page = self.page() page = self.page()
if page: if page:
...@@ -1389,11 +1566,30 @@ class OverlayWebView(QWebEngineView): ...@@ -1389,11 +1566,30 @@ class OverlayWebView(QWebEngineView):
# Check if page has content # Check if page has content
page.runJavaScript(""" page.runJavaScript("""
console.log('OVERLAY VISIBILITY: Checking page content'); console.log('OVERLAY VISIBILITY: Checking page content');
console.log('Document readyState:', document.readyState);
console.log('Body exists:', !!document.body); console.log('Body exists:', !!document.body);
if (document.body) { if (document.body) {
console.log('Body innerHTML length:', document.body.innerHTML.length); console.log('Body innerHTML length:', document.body.innerHTML.length);
console.log('Body background:', window.getComputedStyle(document.body).background); console.log('Body background:', window.getComputedStyle(document.body).background);
console.log('Body opacity:', window.getComputedStyle(document.body).opacity); console.log('Body opacity:', window.getComputedStyle(document.body).opacity);
console.log('Body display:', window.getComputedStyle(document.body).display);
console.log('Body visibility:', window.getComputedStyle(document.body).visibility);
// Check for VirtualBox-specific issues
console.log('Window devicePixelRatio:', window.devicePixelRatio);
console.log('Canvas support:', !!document.createElement('canvas').getContext);
console.log('WebGL support check:');
try {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
console.log(' WebGL available:', !!gl);
if (gl) {
console.log(' WebGL renderer:', gl.getParameter(gl.RENDERER));
console.log(' WebGL vendor:', gl.getParameter(gl.VENDOR));
}
} catch (e) {
console.log(' WebGL check failed:', e.message);
}
} }
console.log('OVERLAY VISIBILITY: Page check complete'); console.log('OVERLAY VISIBILITY: Page check complete');
""") """)
...@@ -1411,10 +1607,35 @@ class OverlayWebView(QWebEngineView): ...@@ -1411,10 +1607,35 @@ class OverlayWebView(QWebEngineView):
logger.info(f"GPU Process: {proc}") logger.info(f"GPU Process: {proc}")
else: else:
logger.warning("NO Qt WebEngine GPU processes found - overlay may not be using GPU acceleration") logger.warning("NO Qt WebEngine GPU processes found - overlay may not be using GPU acceleration")
if is_virtualbox:
logger.warning("VIRTUALBOX: This is expected - VirtualBox typically disables GPU acceleration")
else: else:
logger.warning("Could not check GPU processes for Qt WebEngine") logger.warning("Could not check GPU processes for Qt WebEngine")
if is_virtualbox:
logger.warning("VIRTUALBOX: GPU process check failed - common in VirtualBox environment")
except Exception as e: except Exception as e:
logger.debug(f"GPU process check failed: {e}") logger.debug(f"GPU process check failed: {e}")
if is_virtualbox:
logger.warning("VIRTUALBOX: GPU monitoring not available - this is normal")
# Check for VirtualBox-specific display issues
if is_virtualbox:
logger.info("VIRTUALBOX: Checking for display compatibility issues")
try:
import os
display = os.environ.get('DISPLAY', 'not set')
logger.info(f"VIRTUALBOX: DISPLAY environment: {display}")
# Check if X11 forwarding is working
if display.startswith(':'):
logger.info("VIRTUALBOX: Local X11 display detected")
elif display.startswith('localhost:') or display.startswith('127.0.0.1:'):
logger.warning("VIRTUALBOX: X11 forwarding detected - may cause overlay issues")
else:
logger.warning("VIRTUALBOX: Unusual DISPLAY setting - may indicate display issues")
except Exception as e:
logger.debug(f"VIRTUALBOX: Display check failed: {e}")
logger.info("=== END OVERLAY VISIBILITY CHECK ===") logger.info("=== END OVERLAY VISIBILITY CHECK ===")
...@@ -1520,205 +1741,15 @@ class OverlayWebView(QWebEngineView): ...@@ -1520,205 +1741,15 @@ class OverlayWebView(QWebEngineView):
class NativeOverlayWidget(QWidget):
"""Native Qt overlay widget - no WebEngine to prevent freezing"""
def __init__(self, parent=None):
super().__init__(parent)
# Initialize overlay_data BEFORE setup_ui() to prevent attribute errors
self.overlay_data = {
'title': 'MbetterClient PyQt6 Player',
'subtitle': 'Ready for Content',
'ticker': 'Welcome to MbetterClient • Professional Video Overlay System • Real-time Updates',
'currentTime': '00:00:00'
}
# Add overlay_channel as None for compatibility with WebEngine interface
self.overlay_channel = None
self.setup_ui()
logger.info("NativeOverlayWidget initialized")
def setup_ui(self):
"""Setup native Qt overlay widgets with forced visibility"""
logger.debug("NativeOverlayWidget.setup_ui() - Starting")
# FORCE widget to be completely visible with bright background
self.setStyleSheet("""
NativeOverlayWidget {
background-color: rgba(255, 0, 255, 200);
border: 5px solid cyan;
}
""")
# Ensure widget is visible
self.setVisible(True)
self.setAutoFillBackground(True)
self.raise_() # Bring to front
# Main title label - ENHANCED VISIBILITY
self.title_label = QLabel()
self.title_label.setStyleSheet("""
QLabel {
color: white;
font-size: 32px;
font-weight: bold;
background: rgba(0,0,0,0.8);
padding: 15px;
border-radius: 8px;
border: 2px solid rgba(255,255,255,0.3);
text-shadow: 2px 2px 4px rgba(0,0,0,1.0);
}
""")
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.title_label.setWordWrap(True)
# Subtitle label - ENHANCED VISIBILITY
self.subtitle_label = QLabel()
self.subtitle_label.setStyleSheet("""
QLabel {
color: #ffffff;
font-size: 18px;
background: rgba(0,0,0,0.7);
padding: 10px;
border-radius: 5px;
text-shadow: 1px 1px 2px rgba(0,0,0,1.0);
}
""")
self.subtitle_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
# Time display - ENHANCED VISIBILITY
self.time_label = QLabel()
self.time_label.setStyleSheet("""
QLabel {
color: yellow;
font-size: 16px;
font-weight: bold;
background: rgba(0,0,0,0.8);
padding: 8px;
border-radius: 5px;
border: 1px solid yellow;
}
""")
# Ticker label - ENHANCED VISIBILITY
self.ticker_label = QLabel()
self.ticker_label.setStyleSheet("""
QLabel {
color: white;
font-size: 14px;
font-weight: bold;
background: rgba(220, 53, 69, 0.95);
padding: 12px;
border-radius: 5px;
border: 2px solid rgba(255,255,255,0.3);
}
""")
self.ticker_label.setWordWrap(True)
# Layout
layout = QVBoxLayout(self)
layout.setContentsMargins(20, 20, 20, 20)
# Top section
layout.addStretch(1)
layout.addWidget(self.title_label)
layout.addWidget(self.subtitle_label)
layout.addStretch(2)
# Bottom section
layout.addWidget(self.ticker_label)
layout.addSpacing(10)
# Position time label in top-right corner
self.time_label.setParent(self)
# Update display with initial data
self.update_display()
logger.debug("NativeOverlayWidget UI setup completed")
def resizeEvent(self, event):
"""Handle resize events"""
super().resizeEvent(event)
# Position time label in top-right corner
if hasattr(self, 'time_label') and self.time_label:
self.time_label.adjustSize()
self.time_label.move(self.width() - self.time_label.width() - 20, 20)
def update_overlay_data(self, data: Dict[str, Any]):
"""Update overlay display with new data"""
try:
updated = False
if 'title' in data:
self.overlay_data['title'] = data['title']
updated = True
if 'subtitle' in data:
self.overlay_data['subtitle'] = data['subtitle']
updated = True
if 'ticker' in data:
self.overlay_data['ticker'] = data['ticker']
updated = True
if 'currentTime' in data:
self.overlay_data['currentTime'] = data['currentTime']
updated = True
if updated:
self.update_display()
logger.debug(f"Native overlay updated: {data}")
except Exception as e:
logger.error(f"Failed to update native overlay: {e}")
def update_display(self):
"""Update all display elements"""
try:
self.title_label.setText(self.overlay_data.get('title', ''))
self.subtitle_label.setText(self.overlay_data.get('subtitle', ''))
self.ticker_label.setText(self.overlay_data.get('ticker', ''))
self.time_label.setText(self.overlay_data.get('currentTime', ''))
# Adjust time label position
self.time_label.adjustSize()
if self.width() > 0:
self.time_label.move(self.width() - self.time_label.width() - 20, 20)
except Exception as e:
logger.error(f"Failed to update native overlay display: {e}")
def update_position(self, position: float, duration: float):
"""Update playback position (compatible with WebEngine interface)"""
try:
current_time = self.format_time(position)
total_time = self.format_time(duration)
self.update_overlay_data({'currentTime': f"{current_time} / {total_time}"})
except Exception as e:
logger.error(f"Failed to update position in native overlay: {e}")
def update_video_info(self, info: Dict[str, Any]):
"""Update video information (compatible with WebEngine interface)"""
# Native overlay doesn't show detailed video stats by default
# This is for compatibility with the WebEngine interface
pass
def format_time(self, seconds: float) -> str:
"""Format time in seconds to MM:SS"""
mins = int(seconds // 60)
secs = int(seconds % 60)
return f"{mins:02d}:{secs:02d}"
class VideoWidget(QWidget): class VideoWidget(QWidget):
"""Composite video widget with selectable overlay type""" """Video widget for Qt multimedia player"""
def __init__(self, parent=None, use_native_overlay=False): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.use_native_overlay = use_native_overlay
self.setup_ui() self.setup_ui()
logger.info(f"VideoWidget initialized (native_overlay={use_native_overlay})") logger.info("VideoWidget initialized")
def setup_ui(self): def setup_ui(self):
"""Setup video player - ONLY video widget, overlay handled by PlayerWindow""" """Setup video player - ONLY video widget, overlay handled by PlayerWindow"""
...@@ -1989,11 +2020,10 @@ class PlayerWindow(QMainWindow): ...@@ -1989,11 +2020,10 @@ class PlayerWindow(QMainWindow):
layout.setSpacing(0) layout.setSpacing(0)
# SIMPLE VIDEO WIDGET ONLY - overlay as separate top-level window # SIMPLE VIDEO WIDGET ONLY - overlay as separate top-level window
overlay_type = "Native Qt Widgets" if self.settings.use_native_overlay else "QWebEngineView" logger.info("PlayerWindow: Using QWebEngineView overlay")
logger.info(f"PlayerWindow: Overlay configuration - use_native_overlay={self.settings.use_native_overlay}, using {overlay_type}")
# Create simple video widget without any overlay (VideoWidget no longer handles overlays) # Create simple video widget without any overlay (VideoWidget no longer handles overlays)
self.video_widget = VideoWidget(parent=central_widget, use_native_overlay=False) self.video_widget = VideoWidget(parent=central_widget)
layout.addWidget(self.video_widget, 1) layout.addWidget(self.video_widget, 1)
# THREADING FIXED: Re-enable overlay system with proper Qt main thread architecture # THREADING FIXED: Re-enable overlay system with proper Qt main thread architecture
...@@ -2023,18 +2053,13 @@ class PlayerWindow(QMainWindow): ...@@ -2023,18 +2053,13 @@ class PlayerWindow(QMainWindow):
self.overlay_window.setStyleSheet("background: transparent;") self.overlay_window.setStyleSheet("background: transparent;")
logger.info("Hardware overlay window configured with native transparency (WA_TranslucentBackground=True)") logger.info("Hardware overlay window configured with native transparency (WA_TranslucentBackground=True)")
# Create overlay based on configuration - matching test_video_debug.py behavior # Create overlay using QWebEngineView
if self.settings.use_native_overlay: debug_overlay = getattr(self, 'debug_overlay', False)
self.window_overlay = NativeOverlayWidget(self.overlay_window) web_server_url = self._get_web_server_base_url()
logger.debug("PlayerWindow: Created NativeOverlayWidget overlay as separate window") # Get database manager from message bus
else: db_manager = self._get_database_manager()
# Pass debug_overlay setting and web server URL to OverlayWebView self.window_overlay = OverlayWebView(self.overlay_window, debug_overlay=debug_overlay, web_server_url=web_server_url, db_manager=db_manager)
debug_overlay = getattr(self, 'debug_overlay', False) logger.debug("PlayerWindow: Created QWebEngineView overlay as separate window")
web_server_url = self._get_web_server_base_url()
# Get database manager from message bus
db_manager = self._get_database_manager()
self.window_overlay = OverlayWebView(self.overlay_window, debug_overlay=debug_overlay, web_server_url=web_server_url, db_manager=db_manager)
logger.debug("PlayerWindow: Created QWebEngineView overlay as separate window")
# Layout for overlay window # Layout for overlay window
overlay_layout = QVBoxLayout(self.overlay_window) overlay_layout = QVBoxLayout(self.overlay_window)
...@@ -2862,7 +2887,7 @@ class PlayerWindow(QMainWindow): ...@@ -2862,7 +2887,7 @@ class PlayerWindow(QMainWindow):
def _is_native_overlay(self, overlay_view): def _is_native_overlay(self, overlay_view):
"""Check if this is a native overlay""" """Check if this is a native overlay"""
return isinstance(overlay_view, NativeOverlayWidget) return False # Native overlay removed
def _clean_overlay_data(self, data: Dict[str, Any]) -> Dict[str, Any]: def _clean_overlay_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""Clean overlay data by removing null/undefined values""" """Clean overlay data by removing null/undefined values"""
...@@ -3682,8 +3707,7 @@ class QtVideoPlayer(QObject): ...@@ -3682,8 +3707,7 @@ class QtVideoPlayer(QObject):
return return
# Log current overlay configuration # Log current overlay configuration
overlay_type = "Native Qt Widgets" if self.settings.use_native_overlay else "QWebEngineView" logger.info("Qt Player overlay configuration: QWebEngineView")
logger.info(f"Qt Player overlay configuration: {overlay_type}")
# PERFECT: Now running directly on Qt main thread - no threading issues! # PERFECT: Now running directly on Qt main thread - no threading issues!
logger.info(f"Handler thread: {threading.current_thread().name}") logger.info(f"Handler thread: {threading.current_thread().name}")
......
...@@ -169,18 +169,18 @@ class TemplateWatcher(ThreadedComponent): ...@@ -169,18 +169,18 @@ class TemplateWatcher(ThreadedComponent):
self._scan_existing_templates() self._scan_existing_templates()
# Message processing loop # Message processing loop
while self.running: while self.running and not self.shutdown_event.is_set():
try: try:
# Process messages # Process messages
message = self.message_bus.get_message(self.name, timeout=1.0) message = self.message_bus.get_message(self.name, timeout=1.0)
if message: if message:
self._process_message(message) self._process_message(message)
# Update heartbeat # Update heartbeat
self.heartbeat() self.heartbeat()
time.sleep(0.1) time.sleep(0.1)
except Exception as e: except Exception as e:
logger.error(f"TemplateWatcher run loop error: {e}") logger.error(f"TemplateWatcher run loop error: {e}")
time.sleep(1.0) time.sleep(1.0)
...@@ -199,14 +199,23 @@ class TemplateWatcher(ThreadedComponent): ...@@ -199,14 +199,23 @@ class TemplateWatcher(ThreadedComponent):
"""Shutdown template watcher""" """Shutdown template watcher"""
try: try:
logger.info("Shutting down TemplateWatcher...") logger.info("Shutting down TemplateWatcher...")
if self.observer: if self.observer:
# Unschedule all handlers
for handler in self.event_handlers:
try:
self.observer.unschedule(handler)
except Exception:
pass
self.event_handlers.clear()
self.observer.stop() self.observer.stop()
self.observer.join() try:
self.observer.join(timeout=2.0)
except Exception:
pass
self.observer = None self.observer = None
self.event_handler = None
except Exception as e: except Exception as e:
logger.error(f"TemplateWatcher shutdown error: {e}") logger.error(f"TemplateWatcher shutdown error: {e}")
......
...@@ -432,25 +432,25 @@ class WebDashboard(ThreadedComponent): ...@@ -432,25 +432,25 @@ class WebDashboard(ThreadedComponent):
server_thread.start() server_thread.start()
# Message processing loop # Message processing loop
while self.running: while self.running and not self.shutdown_event.is_set():
try: try:
# Process messages # Process messages
message = self.message_bus.get_message(self.name, timeout=1.0) message = self.message_bus.get_message(self.name, timeout=1.0)
if message: if message:
self._process_message(message) self._process_message(message)
# Update heartbeat # Update heartbeat
self.heartbeat() self.heartbeat()
time.sleep(0.1) time.sleep(0.1)
except Exception as e: except Exception as e:
logger.error(f"WebDashboard run loop error: {e}") logger.error(f"WebDashboard run loop error: {e}")
time.sleep(1.0) time.sleep(1.0)
# Wait for server thread # Wait for server thread to finish (with timeout since it's daemon)
if server_thread.is_alive(): if server_thread and server_thread.is_alive():
server_thread.join(timeout=5.0) server_thread.join(timeout=2.0)
except Exception as e: except Exception as e:
logger.error(f"WebDashboard run failed: {e}") logger.error(f"WebDashboard run failed: {e}")
...@@ -527,7 +527,8 @@ class WebDashboard(ThreadedComponent): ...@@ -527,7 +527,8 @@ class WebDashboard(ThreadedComponent):
if self.server: if self.server:
logger.info("Shutting down HTTP server...") logger.info("Shutting down HTTP server...")
self.server.shutdown() self.server.shutdown()
logger.info("HTTP server shutdown initiated") self.server.server_close()
logger.info("HTTP server shutdown completed")
# Note: SocketIO connections will be closed when the server shuts down # Note: SocketIO connections will be closed when the server shuts down
# No explicit SocketIO shutdown needed as it's handled by the WSGI server # No explicit SocketIO shutdown needed as it's handled by the WSGI server
......
...@@ -3,20 +3,106 @@ class OverlayManager { ...@@ -3,20 +3,106 @@ class OverlayManager {
this.overlayData = {}; this.overlayData = {};
this.pendingUpdates = []; this.pendingUpdates = [];
this.webChannelReady = false; this.webChannelReady = false;
// Detect VirtualBox environment
this.isVirtualBox = this.detectVirtualBox();
if (this.isVirtualBox) {
console.warn('VIRTUALBOX DETECTED: Overlay may have compatibility issues');
console.warn('Common VirtualBox overlay problems:');
console.warn('1. GPU acceleration disabled');
console.warn('2. WebGL not available');
console.warn('3. Qt WebEngine software rendering fallback');
console.warn('4. Transparency effects may fail');
console.warn('5. WebChannel communication may be unreliable');
}
this.initWebChannel(); this.initWebChannel();
this.initCanvas(); this.initCanvas();
} }
detectVirtualBox() {
// Check for VirtualBox-specific indicators
try {
// Check user agent for VirtualBox
if (navigator.userAgent && navigator.userAgent.includes('VirtualBox')) {
return true;
}
// Check for VirtualBox-specific plugins or extensions
if (navigator.plugins) {
for (let i = 0; i < navigator.plugins.length; i++) {
if (navigator.plugins[i].name && navigator.plugins[i].name.includes('VirtualBox')) {
return true;
}
}
}
// Check for VirtualBox guest additions in renderer info
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (gl) {
const renderer = gl.getParameter(gl.RENDERER);
if (renderer && (renderer.includes('VirtualBox') || renderer.includes('VBox'))) {
return true;
}
}
// Check for Mesa software rendering (common in VirtualBox)
if (gl) {
const vendor = gl.getParameter(gl.VENDOR);
const renderer = gl.getParameter(gl.RENDERER);
if ((vendor && vendor.includes('Mesa')) ||
(renderer && (renderer.includes('Software') || renderer.includes('Mesa')))) {
console.warn('MESA SOFTWARE RENDERING DETECTED - likely VirtualBox environment');
return true;
}
}
return false;
} catch (e) {
console.debug('VirtualBox detection failed:', e);
return false;
}
}
initWebChannel() { initWebChannel() {
try { try {
console.log('OVERLAY: Starting WebChannel initialization');
console.log('OVERLAY: VirtualBox detected:', this.isVirtualBox);
if (this.isVirtualBox) {
console.warn('VIRTUALBOX: WebChannel initialization - potential communication issues');
}
// Log browser/environment information
console.log('OVERLAY: Browser environment check:');
console.log(' - User agent:', navigator.userAgent);
console.log(' - Platform:', navigator.platform);
console.log(' - Cookie enabled:', navigator.cookieEnabled);
console.log(' - OnLine:', navigator.onLine);
console.log(' - Qt object available:', typeof Qt !== 'undefined');
console.log(' - qt object available:', typeof qt !== 'undefined');
console.log(' - QWebChannel available:', typeof QWebChannel !== 'undefined');
// Wait for DOM to be fully loaded // Wait for DOM to be fully loaded
this.waitForFullInitialization(() => { this.waitForFullInitialization(() => {
console.log('OVERLAY: DOM fully loaded, checking Qt WebChannel transport');
// Check if Qt WebChannel transport is available (set by QWebEnginePage.setWebChannel) // Check if Qt WebChannel transport is available (set by QWebEnginePage.setWebChannel)
if (typeof qt !== 'undefined' && qt.webChannelTransport) { if (typeof qt !== 'undefined' && qt.webChannelTransport) {
console.log('OVERLAY: Qt WebChannel transport available, initializing channel');
// Use the transport provided by Qt WebEngine // Use the transport provided by Qt WebEngine
new QWebChannel(qt.webChannelTransport, (channel) => { new QWebChannel(qt.webChannelTransport, (channel) => {
console.log('OVERLAY: QWebChannel created successfully');
// Connect to overlay object // Connect to overlay object
window.overlay = channel.objects.overlay; window.overlay = channel.objects.overlay;
console.log('OVERLAY: Connected to overlay object:', !!window.overlay);
if (this.isVirtualBox) {
console.warn('VIRTUALBOX: WebChannel connected - testing communication reliability');
}
// Flush any buffered console messages // Flush any buffered console messages
if (window.flushConsoleBuffer) { if (window.flushConsoleBuffer) {
...@@ -25,94 +111,128 @@ class OverlayManager { ...@@ -25,94 +111,128 @@ class OverlayManager {
// Connect signals if overlay object exists // Connect signals if overlay object exists
if (window.overlay) { if (window.overlay) {
console.log('OVERLAY: Setting up signal connections');
// Connect positionChanged signal // Connect positionChanged signal
if (window.overlay.positionChanged) { if (window.overlay.positionChanged) {
window.overlay.positionChanged.connect((position, duration) => { window.overlay.positionChanged.connect((position, duration) => {
console.log('OVERLAY: positionChanged signal received:', position, duration);
if (position !== null && duration !== null) { if (position !== null && duration !== null) {
this.updateProgress(position, duration); this.updateProgress(position, duration);
} else { } else {
console.warn('positionChanged signal received null/undefined parameters, skipping'); console.warn('positionChanged signal received null/undefined parameters, skipping');
} }
}); });
console.log('OVERLAY: positionChanged signal connected');
} else {
console.warn('OVERLAY: positionChanged signal not available');
} }
// Connect videoInfoChanged signal // Connect videoInfoChanged signal
if (window.overlay.videoInfoChanged) { if (window.overlay.videoInfoChanged) {
window.overlay.videoInfoChanged.connect((info) => { window.overlay.videoInfoChanged.connect((info) => {
console.log('OVERLAY: videoInfoChanged signal received:', info);
if (info && typeof info === 'object') { if (info && typeof info === 'object') {
this.updateVideoInfo(info); this.updateVideoInfo(info);
} else { } else {
console.warn('videoInfoChanged signal received null/undefined parameter, skipping'); console.warn('videoInfoChanged signal received null/undefined parameter, skipping');
} }
}); });
console.log('OVERLAY: videoInfoChanged signal connected');
} else {
console.warn('OVERLAY: videoInfoChanged signal not available');
} }
// Connect dataUpdated signal for templates that need it (like results.html) // Connect dataUpdated signal for templates that need it (like results.html)
if (window.overlay.dataUpdated) { if (window.overlay.dataUpdated) {
window.overlay.dataUpdated.connect((data) => { window.overlay.dataUpdated.connect((data) => {
console.log('OVERLAY: dataUpdated signal received:', data);
if (data !== null && data !== undefined) { if (data !== null && data !== undefined) {
// Call a global callback if it exists (for results.html and other templates) // Call a global callback if it exists (for results.html and other templates)
if (window.onDataUpdated) { if (window.onDataUpdated) {
window.onDataUpdated(data); window.onDataUpdated(data);
} }
console.log('dataUpdated signal received:', data); console.log('dataUpdated signal processed');
} else { } else {
console.warn('dataUpdated signal received null/undefined data'); console.warn('dataUpdated signal received null/undefined data');
} }
}); });
console.log('OVERLAY: dataUpdated signal connected');
} else {
console.warn('OVERLAY: dataUpdated signal not available');
} }
// Test WebChannel communication
this.testWebChannelCommunication();
// Mark WebChannel as ready // Mark WebChannel as ready
this.webChannelReady = true; this.webChannelReady = true;
console.log('OVERLAY: WebChannel fully initialized and ready');
// Process pending updates after full initialization // Process pending updates after full initialization
setTimeout(() => this.processPendingUpdates(), 100); setTimeout(() => this.processPendingUpdates(), 100);
console.log('WebChannel connected and ready'); console.log('WebChannel connected and ready');
} else { } else {
console.warn('Overlay object not found in WebChannel'); console.error('OVERLAY: Overlay object not found in WebChannel');
if (this.isVirtualBox) {
console.error('VIRTUALBOX: WebChannel object missing - communication setup failed');
}
} }
}); });
} else { } else {
console.warn('Qt WebChannel transport not available, falling back to QtWebChannel constructor'); console.warn('OVERLAY: Qt WebChannel transport not available, falling back to QtWebChannel constructor');
if (this.isVirtualBox) {
console.warn('VIRTUALBOX: Primary WebChannel transport failed, trying fallback');
}
// Fallback: try the old method // Fallback: try the old method
if (typeof Qt === 'object' && typeof QtWebChannel === 'function') { if (typeof Qt === 'object' && typeof QtWebChannel === 'function') {
console.log('OVERLAY: Using QtWebChannel fallback method');
const channel = new QtWebChannel(); const channel = new QtWebChannel();
channel.connectTo('overlay', (overlay) => { channel.connectTo('overlay', (overlay) => {
console.log('OVERLAY: Fallback WebChannel connected');
window.overlay = overlay; window.overlay = overlay;
// Connect positionChanged signal // Connect signals (same as above)
overlay.positionChanged.connect((position, duration) => { if (overlay.positionChanged) {
if (position !== null && duration !== null) { overlay.positionChanged.connect((position, duration) => {
this.updateProgress(position, duration); console.log('OVERLAY: [FALLBACK] positionChanged signal received:', position, duration);
} else { if (position !== null && duration !== null) {
console.warn('positionChanged signal received null/undefined parameters, skipping'); this.updateProgress(position, duration);
} } else {
}); console.warn('positionChanged signal received null/undefined parameters, skipping');
}
});
}
// Connect videoInfoChanged signal if (overlay.videoInfoChanged) {
overlay.videoInfoChanged.connect((info) => { overlay.videoInfoChanged.connect((info) => {
if (info && typeof info === 'object') { console.log('OVERLAY: [FALLBACK] videoInfoChanged signal received:', info);
this.updateVideoInfo(info); if (info && typeof info === 'object') {
} else { this.updateVideoInfo(info);
console.warn('videoInfoChanged signal received null/undefined parameter, skipping'); } else {
} console.warn('videoInfoChanged signal received null/undefined parameter, skipping');
}); }
});
}
// Connect dataUpdated signal for templates that need it (like results.html)
if (overlay.dataUpdated) { if (overlay.dataUpdated) {
overlay.dataUpdated.connect((data) => { overlay.dataUpdated.connect((data) => {
console.log('OVERLAY: [FALLBACK] dataUpdated signal received:', data);
if (data !== null && data !== undefined) { if (data !== null && data !== undefined) {
// Call a global callback if it exists (for results.html and other templates)
if (window.onDataUpdated) { if (window.onDataUpdated) {
window.onDataUpdated(data); window.onDataUpdated(data);
} }
console.log('dataUpdated signal received:', data); console.log('dataUpdated signal processed');
} else { } else {
console.warn('dataUpdated signal received null/undefined data'); console.warn('dataUpdated signal received null/undefined data');
} }
}); });
} }
// Test WebChannel communication
this.testWebChannelCommunication();
// Mark WebChannel as ready // Mark WebChannel as ready
this.webChannelReady = true; this.webChannelReady = true;
...@@ -121,19 +241,54 @@ class OverlayManager { ...@@ -121,19 +241,54 @@ class OverlayManager {
console.log('WebChannel connected via fallback method'); console.log('WebChannel connected via fallback method');
}); });
} else { } else {
console.warn('QtWebChannel not available either'); console.error('OVERLAY: QtWebChannel not available either');
if (this.isVirtualBox) {
console.error('VIRTUALBOX: Both WebChannel methods failed - overlays will not work');
}
// Retry with exponential backoff // Retry with exponential backoff
setTimeout(() => this.initWebChannel(), 1000); setTimeout(() => this.initWebChannel(), 1000);
} }
} }
}); });
} catch (error) { } catch (error) {
console.error('WebChannel initialization error:', error); console.error('OVERLAY: WebChannel initialization error:', error);
if (this.isVirtualBox) {
console.error('VIRTUALBOX: WebChannel initialization failed - overlays disabled');
}
// Retry with exponential backoff // Retry with exponential backoff
setTimeout(() => this.initWebChannel(), 1000); setTimeout(() => this.initWebChannel(), 1000);
} }
} }
testWebChannelCommunication() {
try {
console.log('OVERLAY: Testing WebChannel communication');
if (window.overlay && window.overlay.log) {
// Test basic communication
window.overlay.log('OVERLAY: WebChannel communication test - basic log message');
// Test data retrieval if available
if (window.overlay.getCurrentData) {
window.overlay.getCurrentData().then(result => {
console.log('OVERLAY: WebChannel data retrieval test successful:', result);
}).catch(error => {
console.error('OVERLAY: WebChannel data retrieval test failed:', error);
if (this.isVirtualBox) {
console.error('VIRTUALBOX: WebChannel data retrieval failed - communication unreliable');
}
});
}
console.log('OVERLAY: WebChannel communication test completed');
} else {
console.error('OVERLAY: Cannot test WebChannel - overlay object or log method missing');
}
} catch (error) {
console.error('OVERLAY: WebChannel communication test failed:', error);
}
}
waitForFullInitialization(callback) { waitForFullInitialization(callback) {
const checkReady = () => { const checkReady = () => {
if (document.readyState === 'complete' && this.validateCriticalElements()) { if (document.readyState === 'complete' && this.validateCriticalElements()) {
...@@ -442,15 +597,117 @@ class OverlayManager { ...@@ -442,15 +597,117 @@ class OverlayManager {
} }
initCanvas() { initCanvas() {
console.log('OVERLAY: Initializing canvas for diagnostics');
this.canvas = document.getElementById('canvasOverlay'); this.canvas = document.getElementById('canvasOverlay');
this.ctx = this.canvas ? this.canvas.getContext('2d') : null; this.ctx = this.canvas ? this.canvas.getContext('2d') : null;
this.animationFrame = null; this.animationFrame = null;
console.log('OVERLAY: Canvas element found:', !!this.canvas);
console.log('OVERLAY: Canvas 2D context available:', !!this.ctx);
if (this.isVirtualBox) {
console.warn('VIRTUALBOX: Canvas initialization - checking WebGL support');
this.checkWebGLSupport();
}
// Initialize canvas dimensions // Initialize canvas dimensions
this.resizeCanvas(); this.resizeCanvas();
// Setup resize listener // Setup resize listener
window.addEventListener('resize', () => this.resizeCanvas()); window.addEventListener('resize', () => this.resizeCanvas());
// Run rendering diagnostics
this.runRenderingDiagnostics();
}
checkWebGLSupport() {
try {
console.log('VIRTUALBOX: Checking WebGL support for overlay rendering');
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
console.log('VIRTUALBOX: WebGL context available:', !!gl);
if (gl) {
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
if (debugInfo) {
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
console.log('VIRTUALBOX: WebGL renderer:', renderer);
console.log('VIRTUALBOX: WebGL vendor:', vendor);
// Check for software rendering indicators
if (renderer && (renderer.includes('Software') || renderer.includes('Mesa') || renderer.includes('LLVM'))) {
console.warn('VIRTUALBOX: SOFTWARE WEBGL RENDERING DETECTED - overlays may not display properly');
console.warn('VIRTUALBOX: This is the likely cause of invisible overlays');
}
} else {
console.log('VIRTUALBOX: WebGL debug info not available');
}
// Test basic WebGL functionality
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
console.log('VIRTUALBOX: WebGL shader creation works:', !!(vertexShader && fragmentShader));
} else {
console.error('VIRTUALBOX: WebGL not available - overlays will fail to render');
console.error('VIRTUALBOX: This is the primary cause of overlay visibility issues');
}
} catch (error) {
console.error('VIRTUALBOX: WebGL check failed:', error);
}
}
runRenderingDiagnostics() {
try {
console.log('OVERLAY: Running rendering diagnostics');
// Check CSS rendering capabilities
const testElement = document.createElement('div');
testElement.style.position = 'absolute';
testElement.style.left = '-9999px';
testElement.style.background = 'rgba(255, 0, 0, 0.5)';
testElement.style.width = '100px';
testElement.style.height = '100px';
document.body.appendChild(testElement);
// Test CSS transparency
const computedStyle = window.getComputedStyle(testElement);
console.log('OVERLAY: CSS rgba() support:', computedStyle.background.includes('rgba'));
// Test CSS transforms
testElement.style.transform = 'translate3d(0, 0, 0)';
const hasTransform = computedStyle.transform && computedStyle.transform !== 'none';
console.log('OVERLAY: CSS 3D transforms support:', hasTransform);
if (this.isVirtualBox && !hasTransform) {
console.warn('VIRTUALBOX: CSS 3D transforms not supported - overlay positioning may fail');
}
// Clean up
document.body.removeChild(testElement);
// Check for compositor issues
console.log('OVERLAY: Window device pixel ratio:', window.devicePixelRatio);
console.log('OVERLAY: Screen color depth:', screen.colorDepth);
console.log('OVERLAY: Screen pixel depth:', screen.pixelDepth);
if (this.isVirtualBox) {
if (window.devicePixelRatio !== 1) {
console.warn('VIRTUALBOX: Non-standard device pixel ratio detected - may cause rendering issues');
}
}
console.log('OVERLAY: Rendering diagnostics completed');
} catch (error) {
console.error('OVERLAY: Rendering diagnostics failed:', error);
}
} }
resizeCanvas() { resizeCanvas() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment