1054 lines
36 KiB
Python
1054 lines
36 KiB
Python
# sdl2.py - SDL2 constants, structures, bindings and cross-platform loader
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import shutil
|
|
import subprocess
|
|
import ssl
|
|
import hashlib
|
|
import urllib.request
|
|
from ctypes import *
|
|
|
|
|
|
# ============================================================
|
|
# SDL2 Library Loader
|
|
# ============================================================
|
|
|
|
_sdl_lib = None
|
|
_mix_lib = None
|
|
|
|
|
|
def _load_from_directory(directory):
|
|
"""Load SDL2 libraries from specified directory"""
|
|
global _sdl_lib, _mix_lib
|
|
platform = sys.platform
|
|
|
|
if platform == "win32":
|
|
sdl2_path = os.path.join(directory, "SDL2.dll")
|
|
sdl2_mixer_path = os.path.join(directory, "SDL2_mixer.dll")
|
|
if os.path.exists(sdl2_path) and os.path.exists(sdl2_mixer_path):
|
|
if hasattr(os, 'add_dll_directory'):
|
|
os.add_dll_directory(directory)
|
|
os.environ['PATH'] = directory + os.pathsep + os.environ.get('PATH', '')
|
|
_sdl_lib = CDLL(sdl2_path)
|
|
_mix_lib = CDLL(sdl2_mixer_path)
|
|
return True
|
|
return False
|
|
|
|
elif platform == "darwin":
|
|
sdl2_path = os.path.join(directory, "SDL2.framework", "SDL2")
|
|
sdl2_mixer_path = os.path.join(directory, "SDL2_mixer.framework", "SDL2_mixer")
|
|
if os.path.exists(sdl2_path) and os.path.exists(sdl2_mixer_path):
|
|
framework_dir = os.path.dirname(os.path.dirname(sdl2_path))
|
|
if 'DYLD_FRAMEWORK_PATH' not in os.environ:
|
|
os.environ['DYLD_FRAMEWORK_PATH'] = framework_dir
|
|
else:
|
|
os.environ['DYLD_FRAMEWORK_PATH'] = framework_dir + ':' + os.environ['DYLD_FRAMEWORK_PATH']
|
|
_sdl_lib = CDLL(sdl2_path)
|
|
_mix_lib = CDLL(sdl2_mixer_path)
|
|
return True
|
|
return False
|
|
|
|
elif platform.startswith("linux"):
|
|
names_to_try = [
|
|
("libSDL2.so", "libSDL2_mixer.so"),
|
|
("SDL2.so", "SDL2_mixer.so"),
|
|
]
|
|
for sdl_name, mixer_name in names_to_try:
|
|
sdl_path = os.path.join(directory, sdl_name)
|
|
mixer_path = os.path.join(directory, mixer_name)
|
|
if os.path.exists(sdl_path) and os.path.exists(mixer_path):
|
|
if 'LD_LIBRARY_PATH' not in os.environ:
|
|
os.environ['LD_LIBRARY_PATH'] = directory
|
|
else:
|
|
os.environ['LD_LIBRARY_PATH'] = directory + ':' + os.environ['LD_LIBRARY_PATH']
|
|
_sdl_lib = CDLL(sdl_path)
|
|
_mix_lib = CDLL(mixer_path)
|
|
return True
|
|
return False
|
|
|
|
return False
|
|
|
|
|
|
def _load_from_system():
|
|
"""Try loading from system paths"""
|
|
global _sdl_lib, _mix_lib
|
|
platform = sys.platform
|
|
|
|
try:
|
|
import ctypes.util
|
|
sdl_path = ctypes.util.find_library("SDL2")
|
|
mixer_path = ctypes.util.find_library("SDL2_mixer")
|
|
if sdl_path and mixer_path:
|
|
_sdl_lib = CDLL(sdl_path)
|
|
_mix_lib = CDLL(mixer_path)
|
|
return True
|
|
except:
|
|
pass
|
|
return False
|
|
|
|
|
|
def _load_user_config():
|
|
"""Load user-saved SDL2 paths from config file"""
|
|
try:
|
|
config_file = os.path.expanduser('~/.config/ap_ds/sdl_paths.conf')
|
|
if os.path.exists(config_file):
|
|
with open(config_file, 'r') as f:
|
|
sdl_path = None
|
|
mixer_path = None
|
|
for line in f:
|
|
if line.startswith('SDL2_PATH='):
|
|
sdl_path = line.strip().split('=', 1)[1]
|
|
elif line.startswith('SDL2_MIXER_PATH='):
|
|
mixer_path = line.strip().split('=', 1)[1]
|
|
if sdl_path and mixer_path and os.path.exists(sdl_path) and os.path.exists(mixer_path):
|
|
global _sdl_lib, _mix_lib
|
|
_sdl_lib = CDLL(sdl_path)
|
|
_mix_lib = CDLL(mixer_path)
|
|
return True
|
|
except:
|
|
pass
|
|
return False
|
|
|
|
|
|
def _run_sudo_command(cmd, packages):
|
|
"""Run a sudo command with interactive password input.
|
|
|
|
First tries without password (if already root or passwordless sudo).
|
|
If that fails, prompts for the sudo password interactively.
|
|
|
|
Args:
|
|
cmd: Base command list (e.g. ['apt-get', 'install', '-y'])
|
|
packages: Package names to install
|
|
|
|
Returns:
|
|
bool: True if command succeeded
|
|
"""
|
|
import subprocess
|
|
import getpass
|
|
|
|
full_cmd = cmd + packages
|
|
|
|
# Try without password first (if already root / passwordless sudo)
|
|
try:
|
|
result = subprocess.run(
|
|
['sudo', '-n'] + full_cmd,
|
|
capture_output=True, text=True, timeout=120
|
|
)
|
|
if result.returncode == 0:
|
|
return True
|
|
except Exception:
|
|
pass
|
|
|
|
# Prompt for sudo password interactively
|
|
print("🔑 sudo password required for package installation")
|
|
try:
|
|
password = getpass.getpass("Enter sudo password: ")
|
|
except (EOFError, KeyboardInterrupt):
|
|
print("\n❌ Password input cancelled")
|
|
return False
|
|
|
|
# Use sudo -S to read password from stdin
|
|
try:
|
|
result = subprocess.run(
|
|
['sudo', '-S'] + full_cmd,
|
|
input=password + '\n',
|
|
capture_output=True, text=True, timeout=180
|
|
)
|
|
if result.returncode == 0:
|
|
print("✅ Packages installed successfully")
|
|
return True
|
|
else:
|
|
print(f"❌ Installation failed: {result.stderr.strip()}")
|
|
return False
|
|
except Exception as e:
|
|
print(f"❌ Installation error: {e}")
|
|
return False
|
|
|
|
|
|
def _linux_auto_install():
|
|
"""Try automatic package manager installation on Linux"""
|
|
try:
|
|
import subprocess
|
|
import shutil
|
|
|
|
if shutil.which('apt-get'):
|
|
print("📦 Detected apt-based system (Ubuntu/Debian)")
|
|
if _run_sudo_command(['apt-get', 'install', '-y'],
|
|
['libsdl2-dev', 'libsdl2-mixer-dev']):
|
|
if _load_from_system():
|
|
print("✅ SDL2 libraries installed and loaded")
|
|
return True
|
|
|
|
elif shutil.which('dnf'):
|
|
print("📦 Detected dnf-based system (Fedora)")
|
|
if _run_sudo_command(['dnf', 'install', '-y'],
|
|
['SDL2-devel', 'SDL2_mixer-devel']):
|
|
if _load_from_system():
|
|
print("✅ SDL2 libraries installed and loaded")
|
|
return True
|
|
|
|
elif shutil.which('pacman'):
|
|
print("📦 Detected pacman-based system (Arch)")
|
|
if _run_sudo_command(['pacman', '-S', '--noconfirm'],
|
|
['sdl2', 'sdl2_mixer']):
|
|
if _load_from_system():
|
|
print("✅ SDL2 libraries installed and loaded")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"⚠️ Automatic installation failed: {e}")
|
|
return False
|
|
|
|
def _linux_interactive_setup():
|
|
"""Linux interactive setup for SDL2"""
|
|
global _sdl_lib, _mix_lib
|
|
print("\n" + "="*70)
|
|
print("Linux SDL2 Library Loading")
|
|
print("="*70)
|
|
print("Options:")
|
|
print("1. Use system-installed libraries (re-check)")
|
|
print("2. Specify path to your compiled .so files")
|
|
print("3. Show installation instructions")
|
|
print("="*70)
|
|
|
|
while True:
|
|
choice = input("\nChoose option (1/2/3): ").strip()
|
|
|
|
if choice == "1":
|
|
if _load_from_system():
|
|
print("✅ SDL2 libraries loaded from system")
|
|
return True
|
|
print("❌ System libraries not found")
|
|
continue
|
|
|
|
elif choice == "2":
|
|
sdl_path = input("Enter full path to libSDL2.so: ").strip()
|
|
mixer_path = input("Enter full path to libSDL2_mixer.so: ").strip()
|
|
if os.path.exists(sdl_path) and os.path.exists(mixer_path):
|
|
try:
|
|
_sdl_lib = CDLL(sdl_path)
|
|
_mix_lib = CDLL(mixer_path)
|
|
# Save for future
|
|
try:
|
|
config_dir = os.path.expanduser('~/.config/ap_ds')
|
|
os.makedirs(config_dir, exist_ok=True)
|
|
with open(os.path.join(config_dir, 'sdl_paths.conf'), 'w') as f:
|
|
f.write(f"SDL2_PATH={sdl_path}\n")
|
|
f.write(f"SDL2_MIXER_PATH={mixer_path}\n")
|
|
except:
|
|
pass
|
|
print("✅ Libraries loaded from user-specified paths")
|
|
return True
|
|
except Exception as e:
|
|
print(f"❌ Failed to load: {e}")
|
|
else:
|
|
print("❌ One or both files not found")
|
|
continue
|
|
|
|
elif choice == "3":
|
|
print("\n📚 Installation instructions:")
|
|
print("="*50)
|
|
print("For Ubuntu/Debian:")
|
|
print(" sudo apt-get install libsdl2-dev libsdl2-mixer-dev")
|
|
print("\nFor Fedora:")
|
|
print(" sudo dnf install SDL2-devel SDL2_mixer-devel")
|
|
print("\nFor Arch:")
|
|
print(" sudo pacman -S sdl2 sdl2_mixer")
|
|
print("\nManual compilation:")
|
|
print("1. Download SDL2 from: https://www.libsdl.org/download-2.0.php")
|
|
print("2. Download SDL2_mixer from: https://www.libsdl.org/projects/SDL_mixer/")
|
|
print("3. Compile: ./configure && make && sudo make install")
|
|
print("="*50)
|
|
continue
|
|
|
|
else:
|
|
print("❌ Invalid choice. Please enter 1, 2, or 3.")
|
|
|
|
|
|
def _check_sdl_libraries_exist(directory):
|
|
"""Check if SDL2 libraries exist in directory"""
|
|
platform = sys.platform
|
|
|
|
if platform == "win32":
|
|
return os.path.exists(os.path.join(directory, "SDL2.dll")) and \
|
|
os.path.exists(os.path.join(directory, "SDL2_mixer.dll"))
|
|
elif platform == "darwin":
|
|
return os.path.exists(os.path.join(directory, "SDL2.framework")) and \
|
|
os.path.exists(os.path.join(directory, "SDL2_mixer.framework"))
|
|
elif platform.startswith("linux"):
|
|
return os.path.exists(os.path.join(directory, "libSDL2.so")) and \
|
|
os.path.exists(os.path.join(directory, "libSDL2_mixer.so"))
|
|
return False
|
|
|
|
|
|
def _check_sdl2_loaded():
|
|
"""Check if SDL2 is already loaded"""
|
|
return _sdl_lib is not None and _mix_lib is not None
|
|
|
|
|
|
def download_sdl_libraries():
|
|
"""Download SDL2 libraries to package directory based on platform with file hash verification"""
|
|
FILE_HASHES = {
|
|
"SDL2.dll": "520d0459b91efa32fbccf9027a9ca1fc5aae657e679ce8e90f179f9cf5afd279",
|
|
"SDL2_mixer.dll": "2a0fc5e9f72c2eaec3240cb82b7594a58ccda609485981f256b94d0a4dd8d6f8",
|
|
"SDL2.dmg": "2bf2cb8f6b44d584b14e8d4ca7437080d1d968fe3962303be27217b336b82249",
|
|
"SDL2_mixer.dmg": "d74052391ee4d91836bf1072a060f1d821710f3498a54996c66b9a17c79a72d1",
|
|
}
|
|
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
print(f"Package directory: {current_dir}")
|
|
platform = sys.platform
|
|
|
|
def download_file(url, expected_hash=None):
|
|
temp_file = tempfile.NamedTemporaryFile(delete=False)
|
|
temp_file.close()
|
|
|
|
try:
|
|
print(f" Attempting download with SSL verification...")
|
|
try:
|
|
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
|
|
with urllib.request.urlopen(req, timeout=30) as response:
|
|
content = response.read()
|
|
print(f" ✅ Download successful with SSL verification")
|
|
except (urllib.error.URLError, ssl.SSLError, Exception) as e:
|
|
print(f" SSL verification failed: {e}")
|
|
print(f" Attempting download without SSL verification...")
|
|
context = ssl.create_default_context()
|
|
context.check_hostname = False
|
|
context.verify_mode = ssl.CERT_NONE
|
|
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
|
|
with urllib.request.urlopen(req, timeout=30, context=context) as response:
|
|
content = response.read()
|
|
print(f" ✅ Download successful without SSL verification")
|
|
|
|
with open(temp_file.name, 'wb') as f:
|
|
f.write(content)
|
|
print(f" Downloaded {len(content)} bytes")
|
|
|
|
if expected_hash and expected_hash != "expected_sha256_hash_of_xxx":
|
|
file_hash = hashlib.sha256(content).hexdigest()
|
|
print(f" File SHA256: {file_hash}")
|
|
if file_hash.lower() != expected_hash.lower():
|
|
raise Exception(f"Hash verification failed! Expected: {expected_hash}, Got: {file_hash}")
|
|
print(f" ✅ Hash verification passed")
|
|
else:
|
|
print(f" ⚠️ No hash verification (hash not configured)")
|
|
|
|
return content, temp_file.name
|
|
|
|
except Exception as e:
|
|
print(f" ❌ Download failed: {str(e)}")
|
|
try:
|
|
os.unlink(temp_file.name)
|
|
except:
|
|
pass
|
|
raise
|
|
|
|
def verify_file_hash(file_path, expected_hash):
|
|
if not expected_hash or expected_hash == "expected_sha256_hash_of_xxx":
|
|
print(f" ⚠️ No hash configured for {os.path.basename(file_path)}, skipping verification")
|
|
return True
|
|
|
|
try:
|
|
with open(file_path, 'rb') as f:
|
|
content = f.read()
|
|
file_hash = hashlib.sha256(content).hexdigest()
|
|
print(f" Existing file SHA256: {file_hash}")
|
|
if file_hash.lower() == expected_hash.lower():
|
|
print(f" ✅ Hash verification passed")
|
|
return True
|
|
else:
|
|
print(f" ❌ Hash verification failed! Expected: {expected_hash}, Got: {file_hash}")
|
|
return False
|
|
except Exception as e:
|
|
print(f" ❌ Error verifying hash: {e}")
|
|
return False
|
|
|
|
# Windows
|
|
if platform == "win32":
|
|
files = [
|
|
{"url": "https://dvsyun.top/ap_ds/download/SDL2", "filename": "SDL2.dll", "expected_hash": FILE_HASHES.get("SDL2.dll")},
|
|
{"url": "https://dvsyun.top/ap_ds/download/SDL2_M", "filename": "SDL2_mixer.dll", "expected_hash": FILE_HASHES.get("SDL2_mixer.dll")}
|
|
]
|
|
for file_info in files:
|
|
file_path = os.path.join(current_dir, file_info["filename"])
|
|
if os.path.exists(file_path):
|
|
print(f"\n📁 {file_info['filename']} already exists, verifying hash...")
|
|
if verify_file_hash(file_path, file_info["expected_hash"]):
|
|
print(f"✅ {file_info['filename']} is valid, skipping download")
|
|
continue
|
|
else:
|
|
print(f"⚠️ {file_info['filename']} hash mismatch, re-downloading...")
|
|
try:
|
|
os.remove(file_path)
|
|
except:
|
|
pass
|
|
print(f"\n📥 Downloading {file_info['filename']}...")
|
|
try:
|
|
content, temp_path = download_file(file_info["url"], file_info["expected_hash"])
|
|
shutil.move(temp_path, file_path)
|
|
print(f"✅ Successfully downloaded {file_info['filename']}")
|
|
except Exception as e:
|
|
print(f"❌ Failed to download {file_info['filename']}: {str(e)}")
|
|
continue
|
|
|
|
# macOS
|
|
elif platform == "darwin":
|
|
frameworks = [
|
|
{"name": "SDL2", "url": "https://dvsyun.top/ap_ds/download/SDL2/MAC", "dmg_filename": "SDL2.dmg", "framework_name": "SDL2.framework", "expected_hash": FILE_HASHES.get("SDL2.dmg")},
|
|
{"name": "SDL2_mixer", "url": "https://dvsyun.top/ap_ds/download/SDL2_M/MAC", "dmg_filename": "SDL2_mixer.dmg", "framework_name": "SDL2_mixer.framework", "expected_hash": FILE_HASHES.get("SDL2_mixer.dmg")}
|
|
]
|
|
for framework_info in frameworks:
|
|
framework_path = os.path.join(current_dir, framework_info["framework_name"])
|
|
if os.path.exists(framework_path):
|
|
print(f"\n📁 {framework_info['framework_name']} already exists, skipping download")
|
|
continue
|
|
print(f"\n📥 Downloading {framework_info['dmg_filename']}...")
|
|
temp_dmg = None
|
|
try:
|
|
content, temp_dmg = download_file(framework_info["url"], framework_info["expected_hash"])
|
|
print(f"✅ Downloaded {framework_info['dmg_filename']}")
|
|
mount_point = tempfile.mkdtemp(prefix=f"{framework_info['name']}_mount_")
|
|
try:
|
|
cmd = ["hdiutil", "attach", temp_dmg, "-mountpoint", mount_point, "-nobrowse", "-quiet"]
|
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
if result.returncode != 0:
|
|
print(f"❌ Failed to mount {framework_info['dmg_filename']}: {result.stderr}")
|
|
continue
|
|
framework_src = None
|
|
for root, dirs, files in os.walk(mount_point):
|
|
if framework_info["framework_name"] in dirs:
|
|
framework_src = os.path.join(root, framework_info["framework_name"])
|
|
break
|
|
if not framework_src:
|
|
possible_paths = [
|
|
os.path.join(mount_point, framework_info["framework_name"]),
|
|
os.path.join(mount_point, framework_info["name"], framework_info["framework_name"]),
|
|
os.path.join(mount_point, "Frameworks", framework_info["framework_name"]),
|
|
]
|
|
for path in possible_paths:
|
|
if os.path.exists(path):
|
|
framework_src = path
|
|
break
|
|
if not framework_src:
|
|
print(f"❌ Could not find {framework_info['framework_name']} in dmg")
|
|
subprocess.run(["hdiutil", "detach", mount_point, "-quiet"])
|
|
continue
|
|
shutil.copytree(framework_src, framework_path)
|
|
print(f"✅ Extracted {framework_info['framework_name']} to package directory")
|
|
subprocess.run(["hdiutil", "detach", mount_point, "-quiet"])
|
|
except Exception as e:
|
|
print(f"❌ Extraction failed: {e}")
|
|
try:
|
|
subprocess.run(["hdiutil", "detach", mount_point, "-force", "-quiet"])
|
|
except:
|
|
pass
|
|
continue
|
|
finally:
|
|
if temp_dmg and os.path.exists(temp_dmg):
|
|
os.unlink(temp_dmg)
|
|
if os.path.exists(mount_point):
|
|
try:
|
|
os.rmdir(mount_point)
|
|
except:
|
|
pass
|
|
except Exception as e:
|
|
print(f"❌ Download failed: {str(e)}")
|
|
if temp_dmg and os.path.exists(temp_dmg):
|
|
try:
|
|
os.unlink(temp_dmg)
|
|
except:
|
|
pass
|
|
continue
|
|
|
|
# Linux
|
|
elif platform.startswith("linux"):
|
|
print("\n" + "="*70)
|
|
print("⚠️ Linux Support Notice")
|
|
print("="*70)
|
|
print("For Linux systems, SDL2 libraries are NOT provided via automatic download.")
|
|
print("Reason: There are too many Linux distributions and library dependencies.")
|
|
print("")
|
|
print("To use ap_ds on Linux:")
|
|
print("1. Install SDL2 and SDL2_mixer using your package manager:")
|
|
print(" - Ubuntu/Debian: sudo apt-get install libsdl2-dev libsdl2-mixer-dev")
|
|
print(" - Fedora: sudo dnf install SDL2-devel SDL2_mixer-devel")
|
|
print(" - Arch: sudo pacman -S sdl2 sdl2_mixer")
|
|
print("2. Or compile from source:")
|
|
print(" - Download from: https://www.libsdl.org/")
|
|
print(" - Build instructions: https://wiki.libsdl.org/Installation")
|
|
print("")
|
|
print("After installation, run ap_ds again.")
|
|
print("="*70 + "\n")
|
|
|
|
response = input("Do you have pre-compiled .so files? (y/n): ").strip().lower()
|
|
if response == 'y':
|
|
sdl2_path = input("Enter full path to libSDL2.so: ").strip()
|
|
sdl2_mixer_path = input("Enter full path to libSDL2_mixer.so: ").strip()
|
|
if os.path.exists(sdl2_path) and os.path.exists(sdl2_mixer_path):
|
|
shutil.copy2(sdl2_path, os.path.join(current_dir, "libSDL2.so"))
|
|
shutil.copy2(sdl2_mixer_path, os.path.join(current_dir, "libSDL2_mixer.so"))
|
|
print("✅ Libraries copied to package directory")
|
|
else:
|
|
print("❌ One or both library files not found")
|
|
else:
|
|
print("Please install SDL2 libraries and try again.")
|
|
return
|
|
|
|
print(f"\n📂 Files in package directory: {os.listdir(current_dir)}")
|
|
|
|
|
|
def import_sdl2():
|
|
"""Main function: Import SDL2 libraries with cross-platform support"""
|
|
global _sdl_lib, _mix_lib
|
|
|
|
# Already loaded?
|
|
if _check_sdl2_loaded():
|
|
return _sdl_lib, _mix_lib
|
|
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
platform = sys.platform
|
|
|
|
# Windows
|
|
if platform == "win32":
|
|
# Try current directory
|
|
if _load_from_directory(current_dir):
|
|
print("✅ SDL2 loaded from package directory")
|
|
return _sdl_lib, _mix_lib
|
|
# Try system path
|
|
try:
|
|
_sdl_lib = CDLL("SDL2.dll")
|
|
_mix_lib = CDLL("SDL2_mixer.dll")
|
|
print("✅ SDL2 loaded from system path")
|
|
return _sdl_lib, _mix_lib
|
|
except:
|
|
pass
|
|
# Download
|
|
print("SDL2 libraries not found, downloading...")
|
|
download_sdl_libraries()
|
|
if _load_from_directory(current_dir):
|
|
print("✅ SDL2 loaded after download")
|
|
return _sdl_lib, _mix_lib
|
|
raise ImportError("Failed to load SDL2 libraries after download")
|
|
|
|
# macOS
|
|
elif platform == "darwin":
|
|
# Try current directory
|
|
if _load_from_directory(current_dir):
|
|
print("✅ SDL2 loaded from package directory")
|
|
return _sdl_lib, _mix_lib
|
|
# Try system framework paths
|
|
if _load_from_system():
|
|
print("✅ SDL2 loaded from system")
|
|
return _sdl_lib, _mix_lib
|
|
# Download
|
|
print("SDL2 frameworks not found, downloading...")
|
|
download_sdl_libraries()
|
|
if _load_from_directory(current_dir):
|
|
print("✅ SDL2 loaded after download")
|
|
return _sdl_lib, _mix_lib
|
|
raise ImportError("Failed to load SDL2 frameworks after download")
|
|
|
|
# Linux
|
|
elif platform.startswith("linux"):
|
|
# Layer 1: Load from current directory
|
|
if _load_from_directory(current_dir):
|
|
print("✅ SDL2 loaded from package directory")
|
|
return _sdl_lib, _mix_lib
|
|
|
|
# Layer 2: User config
|
|
if _load_user_config():
|
|
print("✅ SDL2 loaded from user config")
|
|
return _sdl_lib, _mix_lib
|
|
|
|
# Layer 3: System libraries
|
|
if _load_from_system():
|
|
print("✅ SDL2 loaded from system")
|
|
return _sdl_lib, _mix_lib
|
|
|
|
# Layer 4: Auto install
|
|
if _linux_auto_install():
|
|
return _sdl_lib, _mix_lib
|
|
|
|
# Layer 5: Interactive setup
|
|
if _linux_interactive_setup():
|
|
return _sdl_lib, _mix_lib
|
|
|
|
raise ImportError("Failed to load SDL2 libraries on Linux")
|
|
|
|
else:
|
|
raise ImportError(f"Unsupported platform: {platform}")
|
|
|
|
|
|
# ============================================================
|
|
# SDL2 Constants
|
|
# ============================================================
|
|
|
|
SDL_bool = c_int
|
|
SDL_TRUE = 1
|
|
SDL_FALSE = 0
|
|
|
|
SDL_INIT_TIMER = 0x00000001
|
|
SDL_INIT_AUDIO = 0x00000010
|
|
SDL_INIT_VIDEO = 0x00000020
|
|
SDL_INIT_JOYSTICK = 0x00000200
|
|
SDL_INIT_HAPTIC = 0x00001000
|
|
SDL_INIT_GAMECONTROLLER = 0x00002000
|
|
SDL_INIT_EVENTS = 0x00004000
|
|
SDL_INIT_EVERYTHING = (SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO |
|
|
SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC |
|
|
SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS)
|
|
|
|
AUDIO_U8 = 0x0008
|
|
AUDIO_S8 = 0x8008
|
|
AUDIO_U16LSB = 0x0010
|
|
AUDIO_S16LSB = 0x8010
|
|
AUDIO_U16MSB = 0x1010
|
|
AUDIO_S16MSB = 0x9010
|
|
AUDIO_U16 = AUDIO_U16LSB
|
|
AUDIO_S16 = AUDIO_S16LSB
|
|
AUDIO_S32LSB = 0x8020
|
|
AUDIO_S32MSB = 0x9020
|
|
AUDIO_S32 = AUDIO_S32LSB
|
|
AUDIO_F32LSB = 0x8120
|
|
AUDIO_F32MSB = 0x9120
|
|
AUDIO_F32 = AUDIO_F32LSB
|
|
|
|
if sys.byteorder == 'little':
|
|
AUDIO_U16SYS = AUDIO_U16LSB
|
|
AUDIO_S16SYS = AUDIO_S16LSB
|
|
AUDIO_S32SYS = AUDIO_S32LSB
|
|
AUDIO_F32SYS = AUDIO_F32LSB
|
|
else:
|
|
AUDIO_U16SYS = AUDIO_U16MSB
|
|
AUDIO_S16SYS = AUDIO_S16MSB
|
|
AUDIO_S32SYS = AUDIO_S32MSB
|
|
AUDIO_F32SYS = AUDIO_F32MSB
|
|
|
|
MIX_DEFAULT_FORMAT = AUDIO_S16SYS
|
|
|
|
MIX_INIT_FLAC = 0x00000001
|
|
MIX_INIT_MOD = 0x00000002
|
|
MIX_INIT_MP3 = 0x00000008
|
|
MIX_INIT_OGG = 0x00000010
|
|
MIX_INIT_MID = 0x00000020
|
|
MIX_INIT_OPUS = 0x00000040
|
|
|
|
MIX_CHANNEL_POST = -2
|
|
MIX_DEFAULT_CHANNELS = 2
|
|
|
|
MUS_NONE = 0
|
|
MUS_CMD = 1
|
|
MUS_WAV = 2
|
|
MUS_MOD = 3
|
|
MUS_MID = 4
|
|
MUS_OGG = 5
|
|
MUS_MP3 = 6
|
|
MUS_FLAC = 7
|
|
MUS_OPUS = 8
|
|
|
|
|
|
# ============================================================
|
|
# SDL2 Structures
|
|
# ============================================================
|
|
|
|
class SDL_AudioSpec(Structure):
|
|
_fields_ = [
|
|
("freq", c_int),
|
|
("format", c_uint16),
|
|
("channels", c_uint8),
|
|
("silence", c_uint8),
|
|
("samples", c_uint16),
|
|
("padding", c_uint16),
|
|
("size", c_uint32),
|
|
("callback", c_void_p),
|
|
("userdata", c_void_p)
|
|
]
|
|
|
|
|
|
class Mix_Chunk(Structure):
|
|
_fields_ = [
|
|
("allocated", c_int),
|
|
("abuf", POINTER(c_uint8)),
|
|
("alen", c_uint32),
|
|
("volume", c_uint8)
|
|
]
|
|
|
|
|
|
# ============================================================
|
|
# SDL2 Function Bindings (set up AFTER loading)
|
|
# ============================================================
|
|
|
|
# These are set up after _sdl_lib and _mix_lib are loaded
|
|
# We define them as functions that will use the global _sdl_lib and _mix_lib
|
|
|
|
|
|
def SDL_Init(flags):
|
|
return _sdl_lib.SDL_Init(flags)
|
|
|
|
|
|
def SDL_InitSubSystem(flags):
|
|
return _sdl_lib.SDL_InitSubSystem(flags)
|
|
|
|
|
|
def SDL_Quit():
|
|
_sdl_lib.SDL_Quit()
|
|
|
|
|
|
def SDL_QuitSubSystem(flags):
|
|
_sdl_lib.SDL_QuitSubSystem(flags)
|
|
|
|
|
|
def SDL_WasInit(flags):
|
|
return _sdl_lib.SDL_WasInit(flags)
|
|
|
|
|
|
def SDL_GetError():
|
|
return _sdl_lib.SDL_GetError()
|
|
|
|
|
|
def SDL_RWFromFile(file, mode):
|
|
if isinstance(file, str):
|
|
file = file.encode('utf-8')
|
|
if isinstance(mode, str):
|
|
mode = mode.encode('utf-8')
|
|
return _sdl_lib.SDL_RWFromFile(file, mode)
|
|
|
|
|
|
def SDL_Delay(ms):
|
|
_sdl_lib.SDL_Delay(ms)
|
|
|
|
|
|
def Mix_OpenAudio(frequency, format, channels, chunksize):
|
|
return _mix_lib.Mix_OpenAudio(frequency, format, channels, chunksize)
|
|
|
|
|
|
def Mix_CloseAudio():
|
|
_mix_lib.Mix_CloseAudio()
|
|
|
|
|
|
def Mix_QuerySpec(frequency, format, channels):
|
|
return _mix_lib.Mix_QuerySpec(byref(frequency), byref(format), byref(channels))
|
|
|
|
|
|
def Mix_LoadWAV(file):
|
|
if isinstance(file, str):
|
|
file = file.encode('utf-8')
|
|
return _mix_lib.Mix_LoadWAV_RW(SDL_RWFromFile(file, b"rb"), 1)
|
|
|
|
|
|
def Mix_LoadMUS(file):
|
|
if isinstance(file, str):
|
|
file = file.encode('utf-8')
|
|
return _mix_lib.Mix_LoadMUS_RW(SDL_RWFromFile(file, b"rb"), 1)
|
|
|
|
|
|
def Mix_FreeChunk(chunk):
|
|
_mix_lib.Mix_FreeChunk(chunk)
|
|
|
|
|
|
def Mix_FreeMusic(music):
|
|
_mix_lib.Mix_FreeMusic(music)
|
|
|
|
|
|
def Mix_PlayChannel(channel, chunk, loops):
|
|
return _mix_lib.Mix_PlayChannel(channel, chunk, loops)
|
|
|
|
|
|
def Mix_PlayMusic(music, loops):
|
|
return _mix_lib.Mix_PlayMusic(music, loops)
|
|
|
|
|
|
def Mix_Pause(channel):
|
|
_mix_lib.Mix_Pause(channel)
|
|
|
|
|
|
def Mix_PauseMusic():
|
|
_mix_lib.Mix_PauseMusic()
|
|
|
|
|
|
def Mix_Resume(channel):
|
|
_mix_lib.Mix_Resume(channel)
|
|
|
|
|
|
def Mix_ResumeMusic():
|
|
_mix_lib.Mix_ResumeMusic()
|
|
|
|
|
|
def Mix_HaltChannel(channel):
|
|
return _mix_lib.Mix_HaltChannel(channel)
|
|
|
|
|
|
def Mix_HaltMusic():
|
|
return _mix_lib.Mix_HaltMusic()
|
|
|
|
|
|
def Mix_SetMusicPosition(position):
|
|
return _mix_lib.Mix_SetMusicPosition(position)
|
|
|
|
|
|
def Mix_MusicDuration(music):
|
|
return _mix_lib.Mix_MusicDuration(music)
|
|
|
|
|
|
def Mix_Volume(channel, volume):
|
|
return _mix_lib.Mix_Volume(channel, volume)
|
|
|
|
|
|
def Mix_VolumeMusic(volume):
|
|
return _mix_lib.Mix_VolumeMusic(volume)
|
|
|
|
|
|
def Mix_AllocateChannels(numchans):
|
|
return _mix_lib.Mix_AllocateChannels(numchans)
|
|
|
|
|
|
def Mix_GetMusicType(music):
|
|
return _mix_lib.Mix_GetMusicType(music)
|
|
|
|
|
|
def Mix_FadingMusic():
|
|
return _mix_lib.Mix_FadingMusic()
|
|
|
|
|
|
def Mix_FadeInMusic(music, loops, ms):
|
|
return _mix_lib.Mix_FadeInMusic(music, loops, ms)
|
|
|
|
|
|
def Mix_FadeOutMusic(ms):
|
|
return _mix_lib.Mix_FadeOutMusic(ms)
|
|
|
|
|
|
def Mix_FadeInChannel(channel, chunk, loops, ms):
|
|
return _mix_lib.Mix_FadeInChannel(channel, chunk, loops, ms)
|
|
|
|
|
|
def Mix_FadeOutChannel(channel, ms):
|
|
return _mix_lib.Mix_FadeOutChannel(channel, ms)
|
|
|
|
|
|
def Mix_Playing(channel):
|
|
return _mix_lib.Mix_Playing(channel)
|
|
|
|
|
|
def Mix_PlayingMusic():
|
|
return _mix_lib.Mix_PlayingMusic()
|
|
|
|
|
|
def Mix_Paused(channel):
|
|
return _mix_lib.Mix_Paused(channel)
|
|
|
|
|
|
def Mix_PausedMusic():
|
|
return _mix_lib.Mix_PausedMusic()
|
|
|
|
|
|
def Mix_SetPanning(channel, left, right):
|
|
return _mix_lib.Mix_SetPanning(channel, left, right)
|
|
|
|
|
|
def Mix_SetDistance(channel, distance):
|
|
return _mix_lib.Mix_SetDistance(channel, distance)
|
|
|
|
|
|
def Mix_SetPosition(channel, angle, distance):
|
|
return _mix_lib.Mix_SetPosition(channel, angle, distance)
|
|
|
|
|
|
def Mix_SetReverseStereo(channel, flip):
|
|
return _mix_lib.Mix_SetReverseStereo(channel, flip)
|
|
|
|
|
|
def Mix_FadeInMusicPos(music, loops, ms, position):
|
|
return _mix_lib.Mix_FadeInMusicPos(music, loops, ms, position)
|
|
|
|
|
|
# ============================================================
|
|
# Function Prototypes (set after libraries are loaded)
|
|
# ============================================================
|
|
|
|
def _setup_prototypes():
|
|
"""Set up function prototypes for SDL2 and SDL2_mixer"""
|
|
# SDL function prototypes
|
|
_sdl_lib.SDL_Init.argtypes = [c_uint32]
|
|
_sdl_lib.SDL_Init.restype = c_int
|
|
|
|
_sdl_lib.SDL_InitSubSystem.argtypes = [c_uint32]
|
|
_sdl_lib.SDL_InitSubSystem.restype = c_int
|
|
|
|
_sdl_lib.SDL_Quit.argtypes = []
|
|
_sdl_lib.SDL_Quit.restype = None
|
|
|
|
_sdl_lib.SDL_QuitSubSystem.argtypes = [c_uint32]
|
|
_sdl_lib.SDL_QuitSubSystem.restype = None
|
|
|
|
_sdl_lib.SDL_WasInit.argtypes = [c_uint32]
|
|
_sdl_lib.SDL_WasInit.restype = c_uint32
|
|
|
|
_sdl_lib.SDL_GetError.argtypes = []
|
|
_sdl_lib.SDL_GetError.restype = c_char_p
|
|
|
|
_sdl_lib.SDL_RWFromFile.argtypes = [c_char_p, c_char_p]
|
|
_sdl_lib.SDL_RWFromFile.restype = c_void_p
|
|
|
|
_sdl_lib.SDL_Delay.argtypes = [c_uint32]
|
|
_sdl_lib.SDL_Delay.restype = None
|
|
|
|
# SDL_mixer function prototypes
|
|
_mix_lib.Mix_OpenAudio.argtypes = [c_int, c_uint16, c_int, c_int]
|
|
_mix_lib.Mix_OpenAudio.restype = c_int
|
|
|
|
_mix_lib.Mix_CloseAudio.argtypes = []
|
|
_mix_lib.Mix_CloseAudio.restype = None
|
|
|
|
if hasattr(_mix_lib, 'Mix_QuerySpec'):
|
|
_mix_lib.Mix_QuerySpec.argtypes = [POINTER(c_int), POINTER(c_uint16), POINTER(c_int)]
|
|
_mix_lib.Mix_QuerySpec.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_LoadWAV_RW'):
|
|
_mix_lib.Mix_LoadWAV_RW.argtypes = [c_void_p, c_int]
|
|
_mix_lib.Mix_LoadWAV_RW.restype = POINTER(Mix_Chunk)
|
|
|
|
if hasattr(_mix_lib, 'Mix_LoadMUS_RW'):
|
|
_mix_lib.Mix_LoadMUS_RW.argtypes = [c_void_p, c_int]
|
|
_mix_lib.Mix_LoadMUS_RW.restype = c_void_p
|
|
|
|
if hasattr(_mix_lib, 'Mix_FreeChunk'):
|
|
_mix_lib.Mix_FreeChunk.argtypes = [POINTER(Mix_Chunk)]
|
|
_mix_lib.Mix_FreeChunk.restype = None
|
|
|
|
if hasattr(_mix_lib, 'Mix_FreeMusic'):
|
|
_mix_lib.Mix_FreeMusic.argtypes = [c_void_p]
|
|
_mix_lib.Mix_FreeMusic.restype = None
|
|
|
|
if hasattr(_mix_lib, 'Mix_FadeInMusicPos'):
|
|
_mix_lib.Mix_FadeInMusicPos.argtypes = [c_void_p, c_int, c_int, c_double]
|
|
_mix_lib.Mix_FadeInMusicPos.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_PlayChannel'):
|
|
_mix_lib.Mix_PlayChannel.argtypes = [c_int, POINTER(Mix_Chunk), c_int]
|
|
_mix_lib.Mix_PlayChannel.restype = c_int
|
|
elif hasattr(_mix_lib, 'Mix_PlayChannelTimed'):
|
|
_mix_lib.Mix_PlayChannelTimed.argtypes = [c_int, POINTER(Mix_Chunk), c_int, c_int]
|
|
_mix_lib.Mix_PlayChannelTimed.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_PlayMusic'):
|
|
_mix_lib.Mix_PlayMusic.argtypes = [c_void_p, c_int]
|
|
_mix_lib.Mix_PlayMusic.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_Pause'):
|
|
_mix_lib.Mix_Pause.argtypes = [c_int]
|
|
_mix_lib.Mix_Pause.restype = None
|
|
|
|
if hasattr(_mix_lib, 'Mix_PauseMusic'):
|
|
_mix_lib.Mix_PauseMusic.argtypes = []
|
|
_mix_lib.Mix_PauseMusic.restype = None
|
|
|
|
if hasattr(_mix_lib, 'Mix_Resume'):
|
|
_mix_lib.Mix_Resume.argtypes = [c_int]
|
|
_mix_lib.Mix_Resume.restype = None
|
|
|
|
if hasattr(_mix_lib, 'Mix_ResumeMusic'):
|
|
_mix_lib.Mix_ResumeMusic.argtypes = []
|
|
_mix_lib.Mix_ResumeMusic.restype = None
|
|
|
|
if hasattr(_mix_lib, 'Mix_HaltChannel'):
|
|
_mix_lib.Mix_HaltChannel.argtypes = [c_int]
|
|
_mix_lib.Mix_HaltChannel.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_HaltMusic'):
|
|
_mix_lib.Mix_HaltMusic.argtypes = []
|
|
_mix_lib.Mix_HaltMusic.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_SetMusicPosition'):
|
|
_mix_lib.Mix_SetMusicPosition.argtypes = [c_double]
|
|
_mix_lib.Mix_SetMusicPosition.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_MusicDuration'):
|
|
_mix_lib.Mix_MusicDuration.argtypes = [c_void_p]
|
|
_mix_lib.Mix_MusicDuration.restype = c_float
|
|
|
|
if hasattr(_mix_lib, 'Mix_Volume'):
|
|
_mix_lib.Mix_Volume.argtypes = [c_int, c_int]
|
|
_mix_lib.Mix_Volume.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_VolumeMusic'):
|
|
_mix_lib.Mix_VolumeMusic.argtypes = [c_int]
|
|
_mix_lib.Mix_VolumeMusic.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_AllocateChannels'):
|
|
_mix_lib.Mix_AllocateChannels.argtypes = [c_int]
|
|
_mix_lib.Mix_AllocateChannels.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_GetMusicType'):
|
|
_mix_lib.Mix_GetMusicType.argtypes = [c_void_p]
|
|
_mix_lib.Mix_GetMusicType.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_FadingMusic'):
|
|
_mix_lib.Mix_FadingMusic.argtypes = []
|
|
_mix_lib.Mix_FadingMusic.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_FadeInMusic'):
|
|
_mix_lib.Mix_FadeInMusic.argtypes = [c_void_p, c_int, c_int]
|
|
_mix_lib.Mix_FadeInMusic.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_FadeOutMusic'):
|
|
_mix_lib.Mix_FadeOutMusic.argtypes = [c_int]
|
|
_mix_lib.Mix_FadeOutMusic.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_FadeInChannel'):
|
|
_mix_lib.Mix_FadeInChannel.argtypes = [c_int, POINTER(Mix_Chunk), c_int, c_int]
|
|
_mix_lib.Mix_FadeInChannel.restype = c_int
|
|
elif hasattr(_mix_lib, 'Mix_FadeInChannelTimed'):
|
|
_mix_lib.Mix_FadeInChannelTimed.argtypes = [c_int, POINTER(Mix_Chunk), c_int, c_int, c_int]
|
|
_mix_lib.Mix_FadeInChannelTimed.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_FadeOutChannel'):
|
|
_mix_lib.Mix_FadeOutChannel.argtypes = [c_int, c_int]
|
|
_mix_lib.Mix_FadeOutChannel.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_Playing'):
|
|
_mix_lib.Mix_Playing.argtypes = [c_int]
|
|
_mix_lib.Mix_Playing.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_PlayingMusic'):
|
|
_mix_lib.Mix_PlayingMusic.argtypes = []
|
|
_mix_lib.Mix_PlayingMusic.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_Paused'):
|
|
_mix_lib.Mix_Paused.argtypes = [c_int]
|
|
_mix_lib.Mix_Paused.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_PausedMusic'):
|
|
_mix_lib.Mix_PausedMusic.argtypes = []
|
|
_mix_lib.Mix_PausedMusic.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_SetPanning'):
|
|
_mix_lib.Mix_SetPanning.argtypes = [c_int, c_uint8, c_uint8]
|
|
_mix_lib.Mix_SetPanning.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_SetDistance'):
|
|
_mix_lib.Mix_SetDistance.argtypes = [c_int, c_uint8]
|
|
_mix_lib.Mix_SetDistance.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_SetPosition'):
|
|
_mix_lib.Mix_SetPosition.argtypes = [c_int, c_uint16, c_uint8]
|
|
_mix_lib.Mix_SetPosition.restype = c_int
|
|
|
|
if hasattr(_mix_lib, 'Mix_SetReverseStereo'):
|
|
_mix_lib.Mix_SetReverseStereo.argtypes = [c_int, c_int]
|
|
_mix_lib.Mix_SetReverseStereo.restype = c_int
|
|
|
|
|
|
# ============================================================
|
|
# Initialize: Load SDL2 and set up prototypes
|
|
# ============================================================
|
|
|
|
# Load SDL2 libraries
|
|
_sdl_lib, _mix_lib = import_sdl2()
|
|
|
|
# Set up function prototypes
|
|
_setup_prototypes()
|