Compare commits

..

2 Commits

Author SHA1 Message Date
a4cc026e38 changes 2025-08-06 02:27:50 +03:00
65843c255a feat: Full-screen scaling system with three display modes
- Implemented comprehensive image/video scaling system
- Added fit, fill, and stretch scaling modes
- Keyboard shortcuts 1,2,3 for real-time mode switching
- Enhanced PIL/Pillow integration for image processing
- OpenCV video playback with full-screen scaling
- Settings integration for scaling preferences
- Fixed touch feedback for control buttons
- Thread-safe video frame processing
- Perfect aspect ratio calculations for any resolution
2025-08-06 02:26:12 +03:00
14 changed files with 2429 additions and 195 deletions

View File

@@ -5,7 +5,7 @@
set -e # Exit on any error set -e # Exit on any error
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
echo "=== Signage Player Offline Installation ===" echo "=== Signage Player Offline Installation ==="
echo "Project root: $PROJECT_ROOT" echo "Project root: $PROJECT_ROOT"
@@ -45,10 +45,13 @@ fi
echo "Step 2: Creating virtual environment..." echo "Step 2: Creating virtual environment..."
cd "$PROJECT_ROOT" cd "$PROJECT_ROOT"
if [ ! -d "venv" ]; then if [ ! -d "venv" ]; then
python3 -m venv venv python3 -m venv --system-site-packages venv
echo "Virtual environment created." echo "Virtual environment created with system site packages access."
else else
echo "Virtual environment already exists." echo "Removing existing virtual environment and creating new one with system site packages..."
rm -rf venv
python3 -m venv --system-site-packages venv
echo "Virtual environment recreated with system site packages access."
fi fi
# Activate virtual environment # Activate virtual environment
@@ -57,35 +60,38 @@ source venv/bin/activate
# Install Python packages from offline wheels # Install Python packages from offline wheels
echo "Step 4: Installing Python packages from offline wheels..." echo "Step 4: Installing Python packages from offline wheels..."
if [ -d "src/offline_packages" ]; then OFFLINE_PACKAGES_DIR="$PROJECT_ROOT/src/offline_packages"
if [ -d "$OFFLINE_PACKAGES_DIR" ]; then
pip install --upgrade pip pip install --upgrade pip
pip install --no-index --find-links "src/offline_packages" \ pip install --no-index --find-links "$OFFLINE_PACKAGES_DIR" \
requests pillow pygame certifi charset_normalizer idna urllib3 requests pillow pygame certifi charset_normalizer idna urllib3
echo "Python packages installed successfully." echo "Python packages installed successfully."
else else
echo "ERROR: Offline packages directory not found!" echo "ERROR: Offline packages directory not found at: $OFFLINE_PACKAGES_DIR"
exit 1 exit 1
fi fi
# Copy shared modules to tkinter app # Copy shared modules to tkinter app
echo "Step 5: Setting up shared modules..." echo "Step 5: Setting up shared modules..."
if [ -d "src/shared_modules" ]; then SHARED_MODULES_DIR="$PROJECT_ROOT/src/shared_modules"
cp src/shared_modules/*.py tkinter_app/src/ 2>/dev/null || echo "Shared modules already in place." TKINTER_APP_DIR="$PROJECT_ROOT/tkinter_app/src"
if [ -d "$SHARED_MODULES_DIR" ]; then
cp "$SHARED_MODULES_DIR"/*.py "$TKINTER_APP_DIR"/ 2>/dev/null || echo "Shared modules already in place."
echo "Shared modules configured." echo "Shared modules configured."
else else
echo "Warning: Shared modules directory not found." echo "Warning: Shared modules directory not found at: $SHARED_MODULES_DIR"
fi fi
# Create necessary directories # Create necessary directories
echo "Step 6: Creating application directories..." echo "Step 6: Creating application directories..."
mkdir -p tkinter_app/resources/static/resurse mkdir -p "$PROJECT_ROOT/tkinter_app/resources/static/resurse"
mkdir -p tkinter_app/src/static/resurse mkdir -p "$PROJECT_ROOT/tkinter_app/src/static/resurse"
# Set permissions # Set permissions
echo "Step 7: Setting permissions..." echo "Step 7: Setting permissions..."
chmod +x run_tkinter_debug.sh chmod +x "$PROJECT_ROOT/run_tkinter_debug.sh" 2>/dev/null || true
chmod +x install_tkinter.sh chmod +x "$PROJECT_ROOT/install_tkinter.sh" 2>/dev/null || true
chmod +x src/scripts/*.sh 2>/dev/null || true chmod +x "$PROJECT_ROOT/src/scripts"/*.sh 2>/dev/null || true
echo "" echo ""
echo "=== Installation Complete! ===" echo "=== Installation Complete! ==="

143
test_centering.py Normal file
View File

@@ -0,0 +1,143 @@
#!/usr/bin/env python3
"""
Test script to verify window centering functionality
"""
import tkinter as tk
import sys
import os
sys.path.append('tkinter_app/src')
from tkinter_simple_player import SettingsWindow, SimpleMediaPlayerApp
def test_settings_centering():
"""Test settings window centering"""
root = tk.Tk()
root.withdraw() # Hide main window
# Create a mock app object
class MockApp:
def __init__(self):
self.playlist = []
self.current_index = 0
def play_current_media(self):
print('play_current_media called')
app = MockApp()
# Test settings window centering
try:
print("Testing settings window centering...")
settings = SettingsWindow(root, app)
# Get screen dimensions
screen_width = settings.window.winfo_screenwidth()
screen_height = settings.window.winfo_screenheight()
# Get window position
settings.window.update_idletasks()
window_x = settings.window.winfo_x()
window_y = settings.window.winfo_y()
window_width = 900
window_height = 700
# Calculate expected center position
expected_x = (screen_width - window_width) // 2
expected_y = (screen_height - window_height) // 2
print(f"Screen size: {screen_width}x{screen_height}")
print(f"Window position: {window_x}, {window_y}")
print(f"Expected center: {expected_x}, {expected_y}")
print(f"Window size: {window_width}x{window_height}")
# Check if window is roughly centered (allow some margin for window decorations)
margin = 50
is_centered_x = abs(window_x - expected_x) <= margin
is_centered_y = abs(window_y - expected_y) <= margin
if is_centered_x and is_centered_y:
print("✅ Settings window is properly centered!")
else:
print("❌ Settings window centering needs adjustment")
# Keep window open for 3 seconds to visually verify
root.after(3000, root.quit)
root.mainloop()
except Exception as e:
print(f"❌ Error testing settings window: {e}")
def test_exit_dialog_centering():
"""Test exit dialog centering"""
print("\nTesting exit dialog centering...")
# Create a simple test for the centering function
root = tk.Tk()
root.withdraw()
# Create a test dialog
dialog = tk.Toplevel(root)
dialog.title("Test Exit Dialog")
dialog.geometry("400x200")
dialog.configure(bg='#2d2d2d')
dialog.resizable(False, False)
# Test the centering logic
dialog.update_idletasks()
screen_width = dialog.winfo_screenwidth()
screen_height = dialog.winfo_screenheight()
dialog_width = 400
dialog_height = 200
# Calculate center position
center_x = int((screen_width - dialog_width) / 2)
center_y = int((screen_height - dialog_height) / 2)
# Ensure the dialog doesn't go off-screen
center_x = max(0, min(center_x, screen_width - dialog_width))
center_y = max(0, min(center_y, screen_height - dialog_height))
dialog.geometry(f"{dialog_width}x{dialog_height}+{center_x}+{center_y}")
dialog.lift()
# Add test content
tk.Label(dialog, text="🎬 Test Exit Dialog",
font=('Arial', 16, 'bold'),
fg='white', bg='#2d2d2d').pack(pady=20)
tk.Label(dialog, text="This dialog should be centered on screen",
font=('Arial', 12),
fg='white', bg='#2d2d2d').pack(pady=10)
# Get actual position
dialog.update_idletasks()
actual_x = dialog.winfo_x()
actual_y = dialog.winfo_y()
print(f"Screen size: {screen_width}x{screen_height}")
print(f"Dialog position: {actual_x}, {actual_y}")
print(f"Expected center: {center_x}, {center_y}")
# Check centering
margin = 50
is_centered_x = abs(actual_x - center_x) <= margin
is_centered_y = abs(actual_y - center_y) <= margin
if is_centered_x and is_centered_y:
print("✅ Exit dialog is properly centered!")
else:
print("❌ Exit dialog centering needs adjustment")
# Close dialog after 3 seconds
root.after(3000, root.quit)
root.mainloop()
if __name__ == "__main__":
print("🧪 Testing Window Centering Functionality")
print("=" * 50)
test_settings_centering()
test_exit_dialog_centering()
print("\n✅ Centering tests completed!")
print("\nThe windows should appear centered on your screen regardless of resolution.")
print("This works for any screen size: 1024x768, 1920x1080, 4K, etc.")

54
test_image_display.py Normal file
View File

@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""
Test script to verify image display functionality
"""
import tkinter as tk
from PIL import Image, ImageTk
import os
def test_image_display():
# Create a simple tkinter window
root = tk.Tk()
root.title("Image Display Test")
root.geometry("800x600")
root.configure(bg='black')
# Create image label
image_label = tk.Label(root, bg='black')
image_label.pack(fill=tk.BOTH, expand=True)
# Test image path
test_image = "/home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg"
try:
if os.path.exists(test_image):
print(f"Loading image: {test_image}")
# Load and display image
img = Image.open(test_image)
img.thumbnail((800, 600), Image.LANCZOS)
photo = ImageTk.PhotoImage(img)
image_label.config(image=photo)
image_label.image = photo # Keep reference
print(f"Image loaded successfully: {img.size}")
# Close after 3 seconds
root.after(3000, root.quit)
else:
print(f"Image file not found: {test_image}")
image_label.config(text="Image file not found", fg='white')
root.after(2000, root.quit)
except Exception as e:
print(f"Error loading image: {e}")
image_label.config(text=f"Error: {e}", fg='red')
root.after(2000, root.quit)
root.mainloop()
print("Image display test completed")
if __name__ == "__main__":
test_image_display()

38
test_imports.py Normal file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env python3
import sys
print("Testing imports...")
try:
import tkinter as tk
print("✓ tkinter imported successfully")
except Exception as e:
print(f"✗ tkinter import failed: {e}")
try:
from PIL import Image, ImageTk
print("✓ PIL and ImageTk imported successfully")
except Exception as e:
print(f"✗ PIL import failed: {e}")
try:
from virtual_keyboard import VirtualKeyboard
print("✓ Virtual keyboard imported successfully")
except Exception as e:
print(f"✗ Virtual keyboard import failed: {e}")
try:
from python_functions import load_local_playlist
print("✓ Python functions imported successfully")
# Test loading playlist
playlist_data = load_local_playlist()
playlist = playlist_data.get('playlist', [])
print(f"✓ Local playlist loaded: {len(playlist)} items")
for i, item in enumerate(playlist):
print(f" {i+1}. {item.get('file_name', 'Unknown')}")
except Exception as e:
print(f"✗ Python functions import/execution failed: {e}")
print("Import test completed")

97
test_scaling.py Normal file
View File

@@ -0,0 +1,97 @@
#!/usr/bin/env python3
"""
Test script for full-screen image scaling functionality
"""
import tkinter as tk
from PIL import Image, ImageTk
import os
def test_scaling_modes():
"""Test different scaling modes for images"""
def scale_image_to_screen(img, screen_width, screen_height, mode='fit'):
"""Test scaling function"""
img_width, img_height = img.size
if mode == 'stretch':
return img.resize((screen_width, screen_height), Image.LANCZOS), (0, 0)
elif mode == 'fill':
screen_ratio = screen_width / screen_height
img_ratio = img_width / img_height
if img_ratio > screen_ratio:
new_height = screen_height
new_width = int(screen_height * img_ratio)
x_offset = (screen_width - new_width) // 2
y_offset = 0
else:
new_width = screen_width
new_height = int(screen_width / img_ratio)
x_offset = 0
y_offset = (screen_height - new_height) // 2
img_resized = img.resize((new_width, new_height), Image.LANCZOS)
final_img = Image.new('RGB', (screen_width, screen_height), 'black')
if new_width > screen_width:
crop_x = (new_width - screen_width) // 2
img_resized = img_resized.crop((crop_x, 0, crop_x + screen_width, new_height))
x_offset = 0
if new_height > screen_height:
crop_y = (new_height - screen_height) // 2
img_resized = img_resized.crop((0, crop_y, new_width, crop_y + screen_height))
y_offset = 0
final_img.paste(img_resized, (x_offset, y_offset))
return final_img, (x_offset, y_offset)
else: # fit mode
screen_ratio = screen_width / screen_height
img_ratio = img_width / img_height
if img_ratio > screen_ratio:
new_width = screen_width
new_height = int(screen_width / img_ratio)
else:
new_height = screen_height
new_width = int(screen_height * img_ratio)
img_resized = img.resize((new_width, new_height), Image.LANCZOS)
final_img = Image.new('RGB', (screen_width, screen_height), 'black')
x_offset = (screen_width - new_width) // 2
y_offset = (screen_height - new_height) // 2
final_img.paste(img_resized, (x_offset, y_offset))
return final_img, (x_offset, y_offset)
# Test image path
test_image = "/home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg"
if not os.path.exists(test_image):
print(f"Test image not found: {test_image}")
return
try:
# Load test image
img = Image.open(test_image)
original_size = img.size
screen_width, screen_height = 800, 600
print(f"Testing scaling modes for image: {original_size}")
print(f"Target screen size: {screen_width}x{screen_height}")
# Test each scaling mode
modes = ['fit', 'fill', 'stretch']
for mode in modes:
final_img, offset = scale_image_to_screen(img, screen_width, screen_height, mode)
print(f"{mode.upper()} mode: Final size: {final_img.size}, Offset: {offset}")
print("✅ All scaling modes tested successfully!")
except Exception as e:
print(f"❌ Error testing scaling: {e}")
if __name__ == "__main__":
test_scaling_modes()

230
test_touch.py Normal file
View File

@@ -0,0 +1,230 @@
#!/usr/bin/env python3
"""
Touch Display Test - Test the touch-optimized interface with virtual keyboard
"""
import tkinter as tk
import sys
import os
sys.path.append('tkinter_app/src')
def test_touch_interface():
"""Test the touch-optimized settings interface"""
try:
from tkinter_simple_player import SettingsWindow
# Create main window
root = tk.Tk()
root.title("🎮 Touch Display Test")
root.geometry("1024x768")
root.configure(bg='#2c3e50')
# Create welcome screen
welcome_frame = tk.Frame(root, bg='#2c3e50', padx=40, pady=40)
welcome_frame.pack(fill=tk.BOTH, expand=True)
# Title
title_label = tk.Label(welcome_frame,
text="🎬 Touch Display Digital Signage",
font=('Segoe UI', 24, 'bold'),
fg='white', bg='#2c3e50')
title_label.pack(pady=30)
# Description
desc_text = (
"Touch-Optimized Features:\n\n"
"📱 Virtual On-Screen Keyboard\n"
"🎯 Larger Touch-Friendly Buttons\n"
"⌨️ Auto-Show Keyboard on Input Focus\n"
"👆 Enhanced Touch Feedback\n"
"🎨 Dark Theme Optimized for Displays\n\n"
"Click the button below to test the settings interface:"
)
desc_label = tk.Label(welcome_frame, text=desc_text,
font=('Segoe UI', 14),
fg='#ecf0f1', bg='#2c3e50',
justify=tk.CENTER)
desc_label.pack(pady=20)
# Create mock app for testing
class MockApp:
def __init__(self):
self.playlist = []
self.current_index = 0
def play_current_media(self):
print("Mock: play_current_media called")
mock_app = MockApp()
# Test button to open touch-optimized settings
def open_touch_settings():
try:
settings = SettingsWindow(root, mock_app)
print("✅ Touch-optimized settings window opened successfully!")
except Exception as e:
print(f"❌ Error opening settings: {e}")
import traceback
traceback.print_exc()
# Large touch-friendly button
settings_btn = tk.Button(welcome_frame,
text="🔧 Open Touch Settings",
command=open_touch_settings,
bg='#3498db', fg='white',
font=('Segoe UI', 16, 'bold'),
relief=tk.FLAT, padx=40, pady=20,
cursor='hand2')
settings_btn.pack(pady=30)
# Instructions
instructions = (
"Touch Instructions:\n"
"• Tap input fields to show virtual keyboard\n"
"• Use large buttons for easy touch interaction\n"
"• Virtual keyboard stays on top for easy access\n"
"• Click outside input fields to hide keyboard"
)
instr_label = tk.Label(welcome_frame, text=instructions,
font=('Segoe UI', 11),
fg='#bdc3c7', bg='#2c3e50',
justify=tk.LEFT)
instr_label.pack(pady=20)
# Exit button
exit_btn = tk.Button(welcome_frame,
text="❌ Exit Test",
command=root.quit,
bg='#e74c3c', fg='white',
font=('Segoe UI', 12, 'bold'),
relief=tk.FLAT, padx=30, pady=15,
cursor='hand2')
exit_btn.pack(pady=20)
# Add touch feedback to buttons
def add_touch_feedback(button):
def on_press(e):
button.configure(relief=tk.SUNKEN)
def on_release(e):
button.configure(relief=tk.FLAT)
def on_enter(e):
button.configure(relief=tk.RAISED)
def on_leave(e):
button.configure(relief=tk.FLAT)
button.bind("<Button-1>", on_press)
button.bind("<ButtonRelease-1>", on_release)
button.bind("<Enter>", on_enter)
button.bind("<Leave>", on_leave)
add_touch_feedback(settings_btn)
add_touch_feedback(exit_btn)
print("🎮 Touch Display Test Started")
print("=" * 50)
print("Features being tested:")
print("- Virtual keyboard integration")
print("- Touch-optimized input fields")
print("- Large, finger-friendly buttons")
print("- Enhanced visual feedback")
print("- Dark theme for displays")
print("\nClick 'Open Touch Settings' to test the interface!")
root.mainloop()
except Exception as e:
print(f"❌ Error in touch interface test: {e}")
import traceback
traceback.print_exc()
def test_virtual_keyboard_standalone():
"""Test just the virtual keyboard component"""
try:
from virtual_keyboard import VirtualKeyboard, TouchOptimizedEntry, TouchOptimizedButton
root = tk.Tk()
root.title("🎹 Virtual Keyboard Test")
root.geometry("800x500")
root.configure(bg='#2f3136')
# Create virtual keyboard
vk = VirtualKeyboard(root, dark_theme=True)
# Test interface
test_frame = tk.Frame(root, bg='#2f3136', padx=30, pady=30)
test_frame.pack(fill=tk.BOTH, expand=True)
tk.Label(test_frame, text="🎹 Virtual Keyboard Test",
font=('Segoe UI', 20, 'bold'),
bg='#2f3136', fg='white').pack(pady=20)
tk.Label(test_frame, text="Click on the input fields below to test the virtual keyboard:",
font=('Segoe UI', 12),
bg='#2f3136', fg='#b9bbbe').pack(pady=10)
# Test input fields
tk.Label(test_frame, text="Server IP:", bg='#2f3136', fg='white',
font=('Segoe UI', 11, 'bold')).pack(anchor=tk.W, pady=(20, 5))
entry1 = TouchOptimizedEntry(test_frame, vk, width=40, bg='#36393f',
fg='white', insertbackground='white')
entry1.pack(pady=5, fill=tk.X)
tk.Label(test_frame, text="Device Name:", bg='#2f3136', fg='white',
font=('Segoe UI', 11, 'bold')).pack(anchor=tk.W, pady=(15, 5))
entry2 = TouchOptimizedEntry(test_frame, vk, width=40, bg='#36393f',
fg='white', insertbackground='white')
entry2.pack(pady=5, fill=tk.X)
tk.Label(test_frame, text="Password:", bg='#2f3136', fg='white',
font=('Segoe UI', 11, 'bold')).pack(anchor=tk.W, pady=(15, 5))
entry3 = TouchOptimizedEntry(test_frame, vk, width=40, bg='#36393f',
fg='white', insertbackground='white', show='*')
entry3.pack(pady=5, fill=tk.X)
# Control buttons
btn_frame = tk.Frame(test_frame, bg='#2f3136')
btn_frame.pack(pady=30)
TouchOptimizedButton(btn_frame, text="🎹 Show Keyboard",
command=lambda: vk.show_keyboard(entry1),
bg='#7289da', fg='white').pack(side=tk.LEFT, padx=10)
TouchOptimizedButton(btn_frame, text="❌ Hide Keyboard",
command=vk.hide_keyboard,
bg='#ed4245', fg='white').pack(side=tk.LEFT, padx=10)
TouchOptimizedButton(btn_frame, text="🔄 Clear All",
command=lambda: [e.delete(0, tk.END) for e in [entry1, entry2, entry3]],
bg='#faa61a', fg='white').pack(side=tk.LEFT, padx=10)
print("🎹 Virtual Keyboard Test Started")
print("- Click input fields to auto-show keyboard")
print("- Type using virtual or physical keyboard")
print("- Test touch-friendly interface")
root.mainloop()
except Exception as e:
print(f"❌ Error in virtual keyboard test: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Touch Display Tests")
parser.add_argument("--keyboard-only", action="store_true",
help="Test only the virtual keyboard component")
args = parser.parse_args()
print("🎮 Touch Display Digital Signage Tests")
print("=" * 50)
if args.keyboard_only:
test_virtual_keyboard_standalone()
else:
test_touch_interface()
print("\n✅ Touch display tests completed!")

View File

@@ -1,9 +1,9 @@
{ {
"screen_orientation": "Landscape", "screen_orientation": "Landscape",
"screen_name": "tv-holba1", "screen_name": "tv-terasa",
"quickconnect_key": "8887779", "quickconnect_key": "8887779",
"server_ip": "192.168.1.245", "server_ip": "digi-signage.moto-adv.com",
"port": "5000", "port": "8880",
"screen_w": "1920", "screen_w": "1920",
"screen_h": "1080", "screen_h": "1080",
"playlist_version": 5 "playlist_version": 5

View File

@@ -859,3 +859,328 @@
[INFO] [SignageApp] python_functions: Starting load_config function. [INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully. [INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Application exit requested [INFO] [SignageApp] Application exit requested
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Configuration loaded: server=192.168.1.245, host=tv-holba1, quick=8887779, port=5000
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Starting load_local_playlist function.
[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5}
[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully.
[INFO] [SignageApp] Found fallback playlist with 3 items
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Initializing with settings: server=192.168.1.245, host=tv-holba1, port=5000
[INFO] [SignageApp] Attempting to connect to server...
[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.245:5000/api/playlists with params: {'hostname': 'tv-holba1', 'quickconnect_code': '8887779'}
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Starting load_local_playlist function.
[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5}
[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully.
[INFO] [SignageApp] Found fallback playlist with 3 items
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Initializing with settings: server=digi-server.moto-adv.com, host=tv-terasa, port=8880
[INFO] [SignageApp] Attempting to connect to server...
[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'}
[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xd59e9190>: Failed to establish a new connection: [Errno -2] Name or service not known'))
[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist
[INFO] [SignageApp] Loaded fallback playlist with 3 items
[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
2025-08-05 19:12:35 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] Starting Simple Tkinter Media Player
[INFO] [SignageApp] Loading configuration in settings window
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Config loaded: {'screen_orientation': 'Landscape', 'screen_name': 'tv-terasa', 'quickconnect_key': '8887779', 'server_ip': 'digi-server.moto-adv.com', 'port': '8880', 'screen_w': '1920', 'screen_h': '1080', 'playlist_version': 5}
[INFO] [SignageApp] Configuration values loaded successfully in settings
[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg
2025-08-05 19:12:55 - STARTED: wp2782770-1846651530.jpg
[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4
2025-08-05 19:13:10 - STARTED: SampleVideo_1280x720_1mb.mp4
[ERROR] [SignageApp] Error playing video /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4: name 'ImageTk' is not defined
[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'}
[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xd59fe590>: Failed to establish a new connection: [Errno -2] Name or service not known'))
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
2025-08-05 19:13:16 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'}
[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xd59e94d0>: Failed to establish a new connection: [Errno -2] Name or service not known'))
[INFO] [SignageApp] No playlist updates available
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Application exit requested
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880
[INFO] [SignageApp] Loading configuration in settings window
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Config loaded: {'screen_orientation': 'Landscape', 'screen_name': 'tv-terasa', 'quickconnect_key': '8887779', 'server_ip': 'digi-server.moto-adv.com', 'port': '8880', 'screen_w': '1920', 'screen_h': '1080', 'playlist_version': 5}
[INFO] [SignageApp] Configuration values loaded successfully in settings
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Starting load_local_playlist function.
[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5}
[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully.
[INFO] [SignageApp] Found fallback playlist with 3 items
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Initializing with settings: server=digi-server.moto-adv.com, host=tv-terasa, port=8880
[INFO] [SignageApp] Attempting to connect to server...
[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'}
[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xd5cf6670>: Failed to establish a new connection: [Errno -2] Name or service not known'))
[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist
[INFO] [SignageApp] Loaded fallback playlist with 3 items
[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
2025-08-06 01:11:39 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] Starting Simple Tkinter Media Player
[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg
2025-08-06 01:11:59 - STARTED: wp2782770-1846651530.jpg
[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4
2025-08-06 01:12:14 - STARTED: SampleVideo_1280x720_1mb.mp4
[ERROR] [SignageApp] Error playing video /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4: name 'ImageTk' is not defined
[INFO] [SignageApp] Loading configuration in settings window
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Config loaded: {'screen_orientation': 'Landscape', 'screen_name': 'tv-terasa', 'quickconnect_key': '8887779', 'server_ip': 'digi-server.moto-adv.com', 'port': '8880', 'screen_w': '1920', 'screen_h': '1080', 'playlist_version': 5}
[INFO] [SignageApp] Configuration values loaded successfully in settings
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
2025-08-06 01:12:20 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'}
[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xd5d07670>: Failed to establish a new connection: [Errno -2] Name or service not known'))
[INFO] [SignageApp] No playlist updates available
[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg
2025-08-06 01:12:40 - STARTED: wp2782770-1846651530.jpg
[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4
2025-08-06 01:12:55 - STARTED: SampleVideo_1280x720_1mb.mp4
[ERROR] [SignageApp] Error playing video /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4: name 'ImageTk' is not defined
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
2025-08-06 01:13:00 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'}
[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xd5d00570>: Failed to establish a new connection: [Errno -2] Name or service not known'))
[INFO] [SignageApp] No playlist updates available
[INFO] [SignageApp] Application exit requested
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880
[INFO] [SignageApp] Loading configuration in enhanced settings window
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Config loaded: {'screen_orientation': 'Landscape', 'screen_name': 'tv-terasa', 'quickconnect_key': '8887779', 'server_ip': 'digi-server.moto-adv.com', 'port': '8880', 'screen_w': '1920', 'screen_h': '1080', 'playlist_version': 5}
[INFO] [SignageApp] Configuration values loaded successfully in enhanced settings
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Starting load_local_playlist function.
[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5}
[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully.
[INFO] [SignageApp] Found fallback playlist with 3 items
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Initializing with settings: server=digi-server.moto-adv.com, host=tv-terasa, port=8880
[INFO] [SignageApp] Attempting to connect to server...
[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'}
[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xd5c15e30>: Failed to establish a new connection: [Errno -2] Name or service not known'))
[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist
[INFO] [SignageApp] Loaded fallback playlist with 3 items
[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
2025-08-06 01:31:08 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] Starting Simple Tkinter Media Player
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg
2025-08-06 01:31:28 - STARTED: wp2782770-1846651530.jpg
[INFO] [SignageApp] Application exit requested
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880
[INFO] [SignageApp] Loading configuration in enhanced settings window
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Config loaded: {'screen_orientation': 'Landscape', 'screen_name': 'tv-terasa', 'quickconnect_key': '8887779', 'server_ip': 'digi-server.moto-adv.com', 'port': '8880', 'screen_w': '1920', 'screen_h': '1080', 'playlist_version': 5}
[INFO] [SignageApp] Configuration values loaded successfully in enhanced settings
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Starting load_local_playlist function.
[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5}
[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully.
[INFO] [SignageApp] Found fallback playlist with 3 items
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Initializing with settings: server=digi-server.moto-adv.com, host=tv-terasa, port=8880
[INFO] [SignageApp] Attempting to connect to server...
[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'}
[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xd5b844f0>: Failed to establish a new connection: [Errno -2] Name or service not known'))
[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist
[INFO] [SignageApp] Loaded fallback playlist with 3 items
[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
2025-08-06 01:51:49 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] Starting Simple Tkinter Media Player
[INFO] [SignageApp] Loading configuration in enhanced settings window
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Config loaded: {'screen_orientation': 'Landscape', 'screen_name': 'tv-terasa', 'quickconnect_key': '8887779', 'server_ip': 'digi-server.moto-adv.com', 'port': '8880', 'screen_w': '1920', 'screen_h': '1080', 'playlist_version': 5}
[INFO] [SignageApp] Configuration values loaded successfully in enhanced settings
[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg
2025-08-06 01:52:09 - STARTED: wp2782770-1846651530.jpg
[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4
2025-08-06 01:52:24 - STARTED: SampleVideo_1280x720_1mb.mp4
[ERROR] [SignageApp] Error playing video /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4: name 'ImageTk' is not defined
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
2025-08-06 01:52:30 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'}
[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xd5a552b0>: Failed to establish a new connection: [Errno -2] Name or service not known'))
[INFO] [SignageApp] No playlist updates available
[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg
2025-08-06 01:52:50 - STARTED: wp2782770-1846651530.jpg
[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4
2025-08-06 01:53:05 - STARTED: SampleVideo_1280x720_1mb.mp4
[ERROR] [SignageApp] Error playing video /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4: name 'ImageTk' is not defined
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
2025-08-06 01:53:05 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'}
[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xd5a29970>: Failed to establish a new connection: [Errno -2] Name or service not known'))
[INFO] [SignageApp] No playlist updates available
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Application exit requested
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880
[INFO] [SignageApp] python_functions: Starting load_local_playlist function.
[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5}
[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully.
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-server.moto-adv.com, host=tv-terasa, quick=8887779, port=8880
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Starting load_local_playlist function.
[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5}
[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully.
[INFO] [SignageApp] Found fallback playlist with 3 items
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Initializing with settings: server=digi-server.moto-adv.com, host=tv-terasa, port=8880
[INFO] [SignageApp] Attempting to connect to server...
[INFO] [SignageApp] Fetching playlist from URL: http://digi-server.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'}
[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='digi-server.moto-adv.com', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0xd586dfb0>: Failed to establish a new connection: [Errno -2] Name or service not known'))
[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist
[INFO] [SignageApp] Loaded fallback playlist with 3 items
[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
2025-08-06 02:02:39 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] Successfully displayed image: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg ((1600, 1000))
[INFO] [SignageApp] Starting Simple Tkinter Media Player
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Application exit requested
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-signage.moto-adv.com, host=tv-terasa, quick=8887779, port=8880
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-signage.moto-adv.com, host=tv-terasa, quick=8887779, port=8880
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Starting load_local_playlist function.
[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5}
[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully.
[INFO] [SignageApp] Found fallback playlist with 3 items
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Initializing with settings: server=digi-signage.moto-adv.com, host=tv-terasa, port=8880
[INFO] [SignageApp] Attempting to connect to server...
[INFO] [SignageApp] Fetching playlist from URL: http://digi-signage.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'}
[ERROR] [SignageApp] Failed to fetch playlist. Status Code: 522
[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist
[INFO] [SignageApp] Loading configuration in enhanced settings window
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Config loaded: {'screen_orientation': 'Landscape', 'screen_name': 'tv-terasa', 'quickconnect_key': '8887779', 'server_ip': 'digi-signage.moto-adv.com', 'port': '8880', 'screen_w': '1920', 'screen_h': '1080', 'playlist_version': 5}
[INFO] [SignageApp] Configuration values loaded successfully in enhanced settings
[INFO] [SignageApp] Loaded fallback playlist with 3 items
[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
2025-08-06 02:14:39 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] Successfully displayed image: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg (Original: (1600, 1000), Screen: 3840x2160, Mode: fit, Offset: (192, 0))
[INFO] [SignageApp] Starting Simple Tkinter Media Player
[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg
2025-08-06 02:15:03 - STARTED: wp2782770-1846651530.jpg
[INFO] [SignageApp] Successfully displayed image: wp2782770-1846651530.jpg (Original: (3840, 2400), Screen: 3840x2160, Mode: fit, Offset: (192, 0))
[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4
2025-08-06 02:15:23 - STARTED: SampleVideo_1280x720_1mb.mp4
[INFO] [SignageApp] Video dimensions: 1280x720, Screen dimensions: 3840x2160
[INFO] [SignageApp] Media paused
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
2025-08-06 02:17:55 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] Fetching playlist from URL: http://digi-signage.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'}
[INFO] [SignageApp] Successfully displayed image: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg (Original: (1600, 1000), Screen: 3840x2160, Mode: fit, Offset: (192, 0))
[INFO] [SignageApp] Application exit requested
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Configuration loaded: server=digi-signage.moto-adv.com, host=tv-terasa, quick=8887779, port=8880
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] python_functions: Starting load_local_playlist function.
[INFO] [SignageApp] python_functions: Local playlist loaded: {'playlist': [{'file_name': '1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'url': 'static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg', 'duration': 20}, {'file_name': 'wp2782770-1846651530.jpg', 'url': 'static/resurse/wp2782770-1846651530.jpg', 'duration': 15}, {'file_name': 'SampleVideo_1280x720_1mb.mp4', 'url': 'static/resurse/SampleVideo_1280x720_1mb.mp4', 'duration': 5}], 'version': 5}
[INFO] [SignageApp] python_functions: Finished load_local_playlist function successfully.
[INFO] [SignageApp] Found fallback playlist with 3 items
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Initializing with settings: server=digi-signage.moto-adv.com, host=tv-terasa, port=8880
[INFO] [SignageApp] Attempting to connect to server...
[INFO] [SignageApp] Fetching playlist from URL: http://digi-signage.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'}
[ERROR] [SignageApp] Failed to fetch playlist. Status Code: 522
[WARNING] [SignageApp] Server returned empty playlist, falling back to local playlist
[INFO] [SignageApp] Loaded fallback playlist with 3 items
[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
2025-08-06 02:20:20 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] Successfully displayed image: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg (Original: (1600, 1000), Screen: 1920x1080, Mode: fit, Offset: (96, 0))
[INFO] [SignageApp] Starting Simple Tkinter Media Player
[INFO] [SignageApp] Playing media: wp2782770-1846651530.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/wp2782770-1846651530.jpg
2025-08-06 02:20:40 - STARTED: wp2782770-1846651530.jpg
[INFO] [SignageApp] Successfully displayed image: wp2782770-1846651530.jpg (Original: (3840, 2400), Screen: 1920x1080, Mode: fit, Offset: (96, 0))
[INFO] [SignageApp] Playing media: SampleVideo_1280x720_1mb.mp4 from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/SampleVideo_1280x720_1mb.mp4
2025-08-06 02:20:57 - STARTED: SampleVideo_1280x720_1mb.mp4
[INFO] [SignageApp] Video dimensions: 1280x720, Screen dimensions: 1920x1080
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] Playing media: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg from /home/pi/Desktop/signage-player/tkinter_app/src/static/resurse/1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
2025-08-06 02:21:44 - STARTED: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Fetching playlist from URL: http://digi-signage.moto-adv.com:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'}
[INFO] [SignageApp] Successfully displayed image: 1307306470-nature_wallpaper_hd_hd_nature_3-3828209637.jpg (Original: (1600, 1000), Screen: 1920x1080, Mode: fit, Offset: (96, 0))
[INFO] [SignageApp] python_functions: Starting load_config function.
[INFO] [SignageApp] python_functions: Configuration file loaded successfully.
[INFO] [SignageApp] Application exit requested

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,360 @@
#!/usr/bin/env python3
"""
Virtual Keyboard Component for Touch Displays
Provides an on-screen keyboard for touch-friendly input
"""
import tkinter as tk
from tkinter import ttk
class VirtualKeyboard:
def __init__(self, parent, target_entry=None, dark_theme=True):
self.parent = parent
self.target_entry = target_entry
self.dark_theme = dark_theme
self.keyboard_window = None
self.caps_lock = False
self.shift_pressed = False
# Define color schemes
if dark_theme:
self.colors = {
'bg_primary': '#1e2124',
'bg_secondary': '#2f3136',
'bg_tertiary': '#36393f',
'accent': '#7289da',
'accent_hover': '#677bc4',
'text_primary': '#ffffff',
'text_secondary': '#b9bbbe',
'key_normal': '#4f545c',
'key_hover': '#5865f2',
'key_special': '#ed4245',
'key_function': '#57f287'
}
else:
self.colors = {
'bg_primary': '#ffffff',
'bg_secondary': '#f8f9fa',
'bg_tertiary': '#e9ecef',
'accent': '#0d6efd',
'accent_hover': '#0b5ed7',
'text_primary': '#000000',
'text_secondary': '#6c757d',
'key_normal': '#dee2e6',
'key_hover': '#0d6efd',
'key_special': '#dc3545',
'key_function': '#198754'
}
def show_keyboard(self, entry_widget=None):
"""Show the virtual keyboard"""
if entry_widget:
self.target_entry = entry_widget
if self.keyboard_window and self.keyboard_window.winfo_exists():
self.keyboard_window.lift()
return
self.create_keyboard()
def hide_keyboard(self):
"""Hide the virtual keyboard"""
if self.keyboard_window and self.keyboard_window.winfo_exists():
self.keyboard_window.destroy()
self.keyboard_window = None
def create_keyboard(self):
"""Create the virtual keyboard window"""
self.keyboard_window = tk.Toplevel(self.parent)
self.keyboard_window.title("Virtual Keyboard")
self.keyboard_window.configure(bg=self.colors['bg_primary'])
self.keyboard_window.resizable(False, False)
# Make keyboard stay on top
self.keyboard_window.attributes('-topmost', True)
# Position keyboard at bottom of screen
self.position_keyboard()
# Create keyboard layout
self.create_keyboard_layout()
# Bind events
self.keyboard_window.protocol("WM_DELETE_WINDOW", self.hide_keyboard)
def position_keyboard(self):
"""Position keyboard at bottom center of screen"""
self.keyboard_window.update_idletasks()
# Get screen dimensions
screen_width = self.keyboard_window.winfo_screenwidth()
screen_height = self.keyboard_window.winfo_screenheight()
# Keyboard dimensions
kb_width = 800
kb_height = 300
# Position at bottom center
x = (screen_width - kb_width) // 2
y = screen_height - kb_height - 50 # 50px from bottom
self.keyboard_window.geometry(f"{kb_width}x{kb_height}+{x}+{y}")
def create_keyboard_layout(self):
"""Create the keyboard layout"""
main_frame = tk.Frame(self.keyboard_window, bg=self.colors['bg_primary'], padx=10, pady=10)
main_frame.pack(fill=tk.BOTH, expand=True)
# Title bar
title_frame = tk.Frame(main_frame, bg=self.colors['bg_secondary'], height=40)
title_frame.pack(fill=tk.X, pady=(0, 10))
title_frame.pack_propagate(False)
title_label = tk.Label(title_frame, text="⌨️ Virtual Keyboard",
font=('Segoe UI', 12, 'bold'),
bg=self.colors['bg_secondary'], fg=self.colors['text_primary'])
title_label.pack(side=tk.LEFT, padx=10, pady=10)
# Close button
close_btn = tk.Button(title_frame, text="", command=self.hide_keyboard,
bg=self.colors['key_special'], fg=self.colors['text_primary'],
font=('Segoe UI', 12, 'bold'), relief=tk.FLAT, width=3)
close_btn.pack(side=tk.RIGHT, padx=10, pady=5)
# Keyboard rows
self.create_keyboard_rows(main_frame)
def create_keyboard_rows(self, parent):
"""Create keyboard rows"""
# Define keyboard layout
rows = [
['`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 'Backspace'],
['Tab', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\'],
['Caps', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'", 'Enter'],
['Shift', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 'Shift'],
['Ctrl', 'Alt', 'Space', 'Alt', 'Ctrl']
]
# Special keys with different sizes
special_keys = {
'Backspace': 2,
'Tab': 1.5,
'Enter': 2,
'Caps': 1.8,
'Shift': 2.3,
'Ctrl': 1.2,
'Alt': 1.2,
'Space': 6
}
for row_index, row in enumerate(rows):
row_frame = tk.Frame(parent, bg=self.colors['bg_primary'])
row_frame.pack(fill=tk.X, pady=2)
for key in row:
width = special_keys.get(key, 1)
self.create_key_button(row_frame, key, width)
def create_key_button(self, parent, key, width=1):
"""Create a keyboard key button"""
# Determine key type and color
if key in ['Backspace', 'Tab', 'Enter', 'Caps', 'Shift', 'Ctrl', 'Alt']:
bg_color = self.colors['key_function']
elif key == 'Space':
bg_color = self.colors['key_normal']
else:
bg_color = self.colors['key_normal']
# Calculate button width
base_width = 4
button_width = int(base_width * width)
# Display text for special keys
display_text = {
'Backspace': '',
'Tab': '',
'Enter': '',
'Caps': '',
'Shift': '',
'Ctrl': 'Ctrl',
'Alt': 'Alt',
'Space': '___'
}.get(key, key.upper() if self.caps_lock or self.shift_pressed else key)
button = tk.Button(parent, text=display_text,
command=lambda k=key: self.key_pressed(k),
bg=bg_color, fg=self.colors['text_primary'],
font=('Segoe UI', 10, 'bold'),
relief=tk.FLAT, bd=1,
width=button_width, height=2)
# Add hover effects
def on_enter(e, btn=button):
btn.configure(bg=self.colors['key_hover'])
def on_leave(e, btn=button):
btn.configure(bg=bg_color)
button.bind("<Enter>", on_enter)
button.bind("<Leave>", on_leave)
button.pack(side=tk.LEFT, padx=1, pady=1)
def key_pressed(self, key):
"""Handle key press"""
if not self.target_entry:
return
if key == 'Backspace':
current_pos = self.target_entry.index(tk.INSERT)
if current_pos > 0:
self.target_entry.delete(current_pos - 1)
elif key == 'Tab':
self.target_entry.insert(tk.INSERT, '\t')
elif key == 'Enter':
# Try to trigger any bound return event
self.target_entry.event_generate('<Return>')
elif key == 'Caps':
self.caps_lock = not self.caps_lock
self.update_key_display()
elif key == 'Shift':
self.shift_pressed = not self.shift_pressed
self.update_key_display()
elif key == 'Space':
self.target_entry.insert(tk.INSERT, ' ')
elif key in ['Ctrl', 'Alt']:
# These could be used for key combinations in the future
pass
else:
# Regular character
char = key.upper() if self.caps_lock or self.shift_pressed else key
# Handle shifted characters
if self.shift_pressed and not self.caps_lock:
shift_map = {
'1': '!', '2': '@', '3': '#', '4': '$', '5': '%',
'6': '^', '7': '&', '8': '*', '9': '(', '0': ')',
'-': '_', '=': '+', '[': '{', ']': '}', '\\': '|',
';': ':', "'": '"', ',': '<', '.': '>', '/': '?',
'`': '~'
}
char = shift_map.get(key, char)
self.target_entry.insert(tk.INSERT, char)
# Reset shift after character input
if self.shift_pressed:
self.shift_pressed = False
self.update_key_display()
def update_key_display(self):
"""Update key display based on caps lock and shift state"""
# This would update the display of keys, but for simplicity
# we'll just recreate the keyboard when needed
pass
class TouchOptimizedEntry(tk.Entry):
"""Entry widget optimized for touch displays with virtual keyboard"""
def __init__(self, parent, virtual_keyboard=None, **kwargs):
# Make entry larger for touch
kwargs.setdefault('font', ('Segoe UI', 12))
kwargs.setdefault('relief', tk.FLAT)
kwargs.setdefault('bd', 8)
super().__init__(parent, **kwargs)
self.virtual_keyboard = virtual_keyboard
# Bind focus events to show/hide keyboard
self.bind('<FocusIn>', self.on_focus_in)
self.bind('<Button-1>', self.on_click)
def on_focus_in(self, event):
"""Show virtual keyboard when entry gets focus"""
if self.virtual_keyboard:
self.virtual_keyboard.show_keyboard(self)
def on_click(self, event):
"""Show virtual keyboard when entry is clicked"""
if self.virtual_keyboard:
self.virtual_keyboard.show_keyboard(self)
class TouchOptimizedButton(tk.Button):
"""Button widget optimized for touch displays"""
def __init__(self, parent, **kwargs):
# Make buttons larger for touch
kwargs.setdefault('font', ('Segoe UI', 11, 'bold'))
kwargs.setdefault('relief', tk.FLAT)
kwargs.setdefault('padx', 20)
kwargs.setdefault('pady', 12)
kwargs.setdefault('cursor', 'hand2')
super().__init__(parent, **kwargs)
# Add touch feedback
self.bind('<Button-1>', self.on_touch_down)
self.bind('<ButtonRelease-1>', self.on_touch_up)
def on_touch_down(self, event):
"""Visual feedback when button is touched"""
self.configure(relief=tk.SUNKEN)
def on_touch_up(self, event):
"""Reset visual feedback when touch is released"""
self.configure(relief=tk.FLAT)
# Test the virtual keyboard
if __name__ == "__main__":
def test_virtual_keyboard():
root = tk.Tk()
root.title("Virtual Keyboard Test")
root.geometry("600x400")
root.configure(bg='#2f3136')
# Create virtual keyboard instance
vk = VirtualKeyboard(root, dark_theme=True)
# Test frame
test_frame = tk.Frame(root, bg='#2f3136', padx=20, pady=20)
test_frame.pack(fill=tk.BOTH, expand=True)
# Title
tk.Label(test_frame, text="🎮 Touch Display Test",
font=('Segoe UI', 16, 'bold'),
bg='#2f3136', fg='white').pack(pady=20)
# Test entries
tk.Label(test_frame, text="Click entries to show virtual keyboard:",
bg='#2f3136', fg='white', font=('Segoe UI', 12)).pack(pady=10)
entry1 = TouchOptimizedEntry(test_frame, vk, width=30, bg='#36393f',
fg='white', insertbackground='white')
entry1.pack(pady=10)
entry2 = TouchOptimizedEntry(test_frame, vk, width=30, bg='#36393f',
fg='white', insertbackground='white')
entry2.pack(pady=10)
# Test buttons
TouchOptimizedButton(test_frame, text="Show Keyboard",
command=lambda: vk.show_keyboard(entry1),
bg='#7289da', fg='white').pack(pady=10)
TouchOptimizedButton(test_frame, text="Hide Keyboard",
command=vk.hide_keyboard,
bg='#ed4245', fg='white').pack(pady=5)
root.mainloop()
test_virtual_keyboard()