Skip to content

Commit a142d2a

Browse files
committed
Add batocera-launch emulator launcher framework
Introduce new Python packages: - batocera-launch: A new async-based emulator launcher framework with modules for configuration loading, device management (controllers, evmapy, guns, hotkeys, mouse, video, wheels), emulator abstraction with plugin discovery via entry points, command execution, EPIPE-tolerant logging, and shared path constants. - batocera-launch-flycast: Flycast emulator plugin for the new launcher framework. All packages are registered as workspace members in pyproject.toml and uv.lock. batocera-common and batoceara-launch have been added as buildroot packages with appropriate dependencies. configgen and batocera-es-system have been updated to depend on the new launcher framework.
1 parent 9717aa6 commit a142d2a

136 files changed

Lines changed: 15687 additions & 101 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Config.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ menu "batocera.linux"
33
menu "System"
44
source "$BR2_EXTERNAL_BATOCERA_PATH/package/batocera/core/batocera-system/Config.in"
55
source "$BR2_EXTERNAL_BATOCERA_PATH/package/batocera/core/batocera-configgen/Config.in"
6+
source "$BR2_EXTERNAL_BATOCERA_PATH/package/batocera/core/batocera-launch/Config.in"
67
source "$BR2_EXTERNAL_BATOCERA_PATH/package/batocera/core/batocera-desktopapps/Config.in"
78
source "$BR2_EXTERNAL_BATOCERA_PATH/package/batocera/core/batocera-splash/Config.in"
89
source "$BR2_EXTERNAL_BATOCERA_PATH/package/batocera/core/batocera-scripts/Config.in"

package/batocera/core/batocera-configgen/Config.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ config BR2_PACKAGE_BATOCERA_CONFIGGEN
22
bool "batocera configgen"
33
select BR2_PACKAGE_PYTHON3
44
select BR2_PACKAGE_PYTHON_BATOCERA_COMMON
5+
select BR2_PACKAGE_BATOCERA_LAUNCH
56
select BR2_PACKAGE_PYTHON_PYYAML
67
select BR2_PACKAGE_PYTHON_RUAMEL_YAML
78
select BR2_PACKAGE_PYTHON_TOML

package/batocera/core/batocera-configgen/batocera-configgen.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ BATOCERA_CONFIGGEN_SOURCE=
99
BATOCERA_CONFIGGEN_SETUP_TYPE = hatch
1010
BATOCERA_CONFIGGEN_DEPENDENCIES = \
1111
python-batocera-common \
12+
batocera-launch \
1213
python-toml \
1314
python-evdev \
1415
python-pyudev \

package/batocera/core/batocera-configgen/configgen/configgen/batoceraPaths.py

Lines changed: 28 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
from __future__ import annotations
22

33
from contextlib import contextmanager
4-
from pathlib import Path
5-
from typing import IO, TYPE_CHECKING, Any, Final, overload
6-
7-
if TYPE_CHECKING:
8-
from _typeshed import OpenBinaryModeUpdating, OpenBinaryModeWriting, OpenTextModeUpdating, OpenTextModeWriting
9-
from collections.abc import Generator
10-
from io import BufferedRandom, BufferedWriter, TextIOWrapper
4+
from typing import IO, TYPE_CHECKING, Any, overload
115

126
from batocera_common.paths import (
137
BATOCERA_CONF as BATOCERA_CONF,
@@ -25,40 +19,34 @@
2519
SCREENSHOTS as SCREENSHOTS,
2620
USERDATA as USERDATA,
2721
)
22+
from batocera_launch.paths import (
23+
BATOCERA_ES_DIR as BATOCERA_ES_DIR,
24+
BATOCERA_SHADERS as BATOCERA_SHADERS,
25+
CONF_INIT as CONF_INIT,
26+
CONFIGGEN_DATA_DIR as CONFIGGEN_DATA_DIR,
27+
DATAINIT_DIR as DATAINIT_DIR,
28+
DEFAULTS_DIR as DEFAULTS_DIR,
29+
ES_GAMES_METADATA as ES_GAMES_METADATA,
30+
ES_GUNS_ART_METADATA as ES_GUNS_ART_METADATA,
31+
ES_GUNS_METADATA as ES_GUNS_METADATA,
32+
ES_SETTINGS as ES_SETTINGS,
33+
ES_WHEELS_METADATA as ES_WHEELS_METADATA,
34+
EVMAPY as EVMAPY,
35+
HOME_INIT as HOME_INIT,
36+
SYSTEM_DECORATIONS as SYSTEM_DECORATIONS,
37+
SYSTEM_SCRIPTS as SYSTEM_SCRIPTS,
38+
USER_DECORATIONS as USER_DECORATIONS,
39+
USER_ES_DIR as USER_ES_DIR,
40+
USER_SCRIPTS as USER_SCRIPTS,
41+
USER_SHADERS as USER_SHADERS,
42+
configure_emulator as configure_emulator,
43+
)
2844

29-
DATAINIT_DIR: Final = BATOCERA_SHARE_DIR / 'datainit'
30-
31-
HOME_INIT: Final = DATAINIT_DIR / 'system'
32-
CONF_INIT: Final = HOME_INIT / 'configs'
33-
34-
EVMAPY: Final = CONFIGS / 'evmapy'
35-
36-
USER_ES_DIR: Final = CONFIGS / 'emulationstation'
37-
BATOCERA_ES_DIR: Final = Path('/usr/share/emulationstation')
38-
CONFIGGEN_DATA_DIR: Final = Path('/usr/share/batocera/configgen/data')
39-
40-
_ES_RESOURCES_DIR: Final = BATOCERA_ES_DIR / 'resources'
41-
42-
ES_SETTINGS: Final = USER_ES_DIR / 'es_settings.cfg'
43-
ES_GUNS_METADATA: Final = _ES_RESOURCES_DIR / 'gungames.xml'
44-
ES_WHEELS_METADATA: Final = _ES_RESOURCES_DIR / 'wheelgames.xml'
45-
ES_GAMES_METADATA: Final = _ES_RESOURCES_DIR / 'gamesdb.xml'
46-
ES_GUNS_ART_METADATA: Final = CONFIGGEN_DATA_DIR / 'gamesbuttonsdb.xml'
47-
48-
DEFAULTS_DIR: Final = BATOCERA_SHARE_DIR / 'configgen'
49-
50-
USER_SHADERS: Final = USERDATA / 'shaders'
51-
BATOCERA_SHADERS: Final = BATOCERA_SHARE_DIR / 'shaders'
52-
53-
USER_DECORATIONS: Final = USERDATA / 'decorations'
54-
SYSTEM_DECORATIONS: Final = DATAINIT_DIR / 'decorations'
55-
56-
USER_SCRIPTS: Final = HOME / 'scripts'
57-
SYSTEM_SCRIPTS: Final = DEFAULTS_DIR / 'scripts'
58-
59-
60-
def configure_emulator(rom: Path, /) -> bool:
61-
return str(rom) == 'config'
45+
if TYPE_CHECKING:
46+
from _typeshed import OpenBinaryModeUpdating, OpenBinaryModeWriting, OpenTextModeUpdating, OpenTextModeWriting
47+
from collections.abc import Generator
48+
from io import BufferedRandom, BufferedWriter, TextIOWrapper
49+
from pathlib import Path
6250

6351

6452
def mkdir_if_not_exists(dir: Path, /) -> None:
Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,3 @@
11
from __future__ import annotations
22

3-
from collections.abc import Iterator, Mapping
4-
from dataclasses import dataclass, replace
5-
from typing import TYPE_CHECKING, Self, TypedDict, Unpack, cast
6-
7-
if TYPE_CHECKING:
8-
import xml.etree.ElementTree as ET
9-
10-
11-
class _InputChanges(TypedDict, total=False):
12-
name: str
13-
type: str
14-
id: str
15-
value: str
16-
code: str | None
17-
18-
19-
@dataclass(slots=True, kw_only=True)
20-
class Input:
21-
name: str
22-
type: str
23-
id: str
24-
value: str
25-
code: str | None = None
26-
27-
def replace(self, /, **changes: Unpack[_InputChanges]) -> Self:
28-
return replace(self, **changes)
29-
30-
@classmethod
31-
def from_element(cls, element: ET.Element, /) -> Self:
32-
return cls(
33-
name=cast("str", element.get("name")),
34-
type=cast("str", element.get("type")),
35-
id=cast("str", element.get("id")),
36-
value=cast("str", element.get("value")),
37-
code=element.get("code")
38-
)
39-
40-
@classmethod
41-
def from_parent_element(cls, parent_element: ET.Element, /) -> Iterator[tuple[str, Self]]:
42-
for element in parent_element.iterfind('./input'):
43-
input = cls.from_element(element)
44-
yield input.name, input
45-
46-
47-
type InputMapping = Mapping[str, Input]
48-
type InputDict = dict[str, Input]
3+
from batocera_launch.devices.input import Input as Input, InputDict as InputDict, InputMapping as InputMapping
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import asdict, dataclass
4+
from typing import TYPE_CHECKING, Self
5+
6+
from batocera_launch.command import Command
7+
from batocera_launch.emulator import Emulator
8+
from batocera_launch.functools import cached_property
9+
from configgen.config import Config as _ConfiggenConfig, SystemConfig as _ConfiggenSystemConfig
10+
from configgen.Emulator import Emulator as _ConfiggenEmulator
11+
from configgen.generators.importer import get_generator
12+
13+
if TYPE_CHECKING:
14+
from argparse import Namespace
15+
from pathlib import Path
16+
17+
from batocera_launch.config.config import SystemConfig
18+
from batocera_launch.devices.controller import Controllers
19+
from batocera_launch.devices.device import DeviceInfo, DeviceInfoMapping
20+
from batocera_launch.devices.gun import Guns
21+
from batocera_launch.types import HotkeysContext, Resolution
22+
from configgen.generators.Generator import Generator
23+
from configgen.types import DeviceInfo as _ConfiggenDeviceInfo
24+
25+
26+
def _convert_device_info(device_info: DeviceInfo, /) -> _ConfiggenDeviceInfo:
27+
result: _ConfiggenDeviceInfo = {
28+
'eventId': device_info.event_id,
29+
'sysfs_path': device_info.sysfs_path,
30+
'isJoystick': device_info.is_joystick,
31+
'isWheel': device_info.is_wheel,
32+
'isMouse': device_info.is_mouse,
33+
'associatedDevices': device_info.associated_devices,
34+
'joystick_index': device_info.joystick_index,
35+
'mouse_index': device_info.mouse_index,
36+
}
37+
38+
if device_info.wheel_rotation is not None:
39+
result['wheel_rotation'] = device_info.wheel_rotation
40+
41+
return result
42+
43+
44+
@dataclass(slots=True)
45+
class ConfiggenEmulator(_ConfiggenEmulator):
46+
system_config: SystemConfig
47+
48+
def __post_init__(self, args: Namespace, rom: Path, /) -> None:
49+
self.name = args.system
50+
self.game_info_xml = str(args.gameinfoxml)
51+
self.config = _ConfiggenSystemConfig(dict(self.system_config.data))
52+
self.renderconfig = _ConfiggenConfig(dict(self.system_config.render_config.data))
53+
54+
55+
@dataclass(slots=True)
56+
class Configgen(Emulator):
57+
generator: Generator
58+
configgen_emulator: ConfiggenEmulator
59+
60+
@property
61+
def hotkeygen_context(self) -> HotkeysContext:
62+
return self.generator.getHotkeysContext()
63+
64+
@property
65+
def execution_path(self) -> Path | None:
66+
return self.generator.executionDirectory(self.configgen_emulator.config, self.rom)
67+
68+
@property
69+
def video_mode(self) -> str:
70+
return self.generator.getResolutionMode(self.configgen_emulator.config)
71+
72+
@property
73+
def needs_mouse(self) -> bool:
74+
return self.generator.getMouseMode(self.configgen_emulator.config, self.rom)
75+
76+
@property
77+
def needs_bezels(self) -> bool:
78+
return not self.generator.supportsInternalBezels()
79+
80+
@cached_property
81+
def guns_borders_size(self) -> str | None:
82+
return self.configgen_emulator.guns_borders_size_name(self.guns)
83+
84+
def configure(self, prepared_rom: Path, /) -> Command:
85+
configgen_command = self.generator.generate(
86+
self.configgen_emulator,
87+
prepared_rom,
88+
self.controllers, # pyright: ignore
89+
self.metadata,
90+
self.guns, # pyright: ignore
91+
{key: _convert_device_info(wheel) for key, wheel in self.wheels.items()},
92+
asdict(self.resolution), # pyright: ignore
93+
)
94+
95+
return Command(
96+
configgen_command.array,
97+
configgen_command.env,
98+
)
99+
100+
@classmethod
101+
def create(
102+
cls,
103+
args: Namespace,
104+
system_config: SystemConfig,
105+
metadata: dict[str, str],
106+
controllers: Controllers,
107+
guns: Guns,
108+
wheels: DeviceInfoMapping,
109+
resolution: Resolution,
110+
/,
111+
) -> Self:
112+
generator = get_generator(system_config.emulator, system_config.core)
113+
emulator = ConfiggenEmulator(args, args.rom, system_config)
114+
115+
return cls(
116+
args.system,
117+
args.systemname,
118+
system_config,
119+
metadata,
120+
controllers,
121+
guns,
122+
wheels,
123+
resolution,
124+
generator,
125+
emulator,
126+
)

package/batocera/core/batocera-configgen/configgen/pyproject.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
[project]
22
name = "batocera-configgen"
3-
version = '1.5'
3+
version = '44.0'
44
dependencies = [
5-
'batocera-common',
6-
'PyYAML',
5+
'batocera-launch',
76
'toml',
87
'evdev; sys_platform == "linux"',
98
'pyudev; sys_platform == "linux"',
109
'configobj',
1110
'ffmpeg-python',
1211
'pillow',
13-
'ruamel.yaml',
1412
'requests',
1513
'qrcode',
1614
'pysdl2',
@@ -19,6 +17,9 @@ dependencies = [
1917
[project.scripts]
2018
emulatorlauncher = "configgen.emulatorlauncher:launch"
2119

20+
[project.entry-points."batocera_launch.emulators"]
21+
configgen = 'configgen.launch:Configgen'
22+
2223
[build-system]
2324
requires = ["hatchling"]
2425
build-backend = "hatchling.build"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
config BR2_PACKAGE_BATOCERA_LAUNCH
2+
bool "batocera launch"
3+
select BR2_PACKAGE_PYTHON3
4+
select BR2_PACKAGE_PYTHON_PYYAML
5+
select BR2_PACKAGE_PYTHON_RUAMEL_YAML
6+
select BR2_PACKAGE_PYTHON_TYPING_EXTENSIONS
7+
select BR2_PACKAGE_PYTHON_BATOCERA_COMMON
8+
select BR2_PACKAGE_PYTHON_EVDEV
9+
select BR2_PACKAGE_PYTHON_PYUDEV # for guns
10+
select BR2_PACKAGE_PYSDL2
11+
select BR2_PACKAGE_PYTHON_TOML
12+
select BR2_PACKAGE_PYTHON_PILLOW
13+
select BR2_PACKAGE_PYTHON_QRCODE # for retroachievement urls
14+
select BR2_PACKAGE_MANGOHUD if (BR2_PACKAGE_WAYLAND || BR2_PACKAGE_XORG7 || BR2_PACKAGE_LIBDRM) && !BR2_PACKAGE_BATOCERA_TARGET_S905
15+
help
16+
The emulator launcher for batocera.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
################################################################################
2+
#
3+
# batocera-launch
4+
#
5+
################################################################################
6+
7+
BATOCERA_LAUNCH_SOURCE=
8+
BATOCERA_LAUNCH_OVERRIDE_SRCDIR=$(BR2_EXTERNAL_BATOCERA_PATH)/python-src/batocera-launch
9+
BATOCERA_LAUNCH_OVERRIDE_SRCDIR_RSYNC_EXCLUSIONS=--exclude=".*" --exclude="**/__pycache__/" --exclude="dist"
10+
BATOCERA_LAUNCH_SETUP_TYPE=hatch
11+
BATOCERA_LAUNCH_DEPENDENCIES = \
12+
python-batocera-common \
13+
python-evdev \
14+
python-pyudev \
15+
pysdl2 \
16+
python-toml \
17+
python-pillow \
18+
python-qrcode
19+
20+
$(eval $(python-package))

package/batocera/core/batocera-system/Config.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ config BR2_PACKAGE_BATOCERA_SYSTEM
171171
select BR2_PACKAGE_PYTHON_BATOCERA_COMMON # common python functionality
172172
select BR2_PACKAGE_BATOCERA_ES_SYSTEM # emulation station es_systems.cfg generator + default roms
173173
select BR2_PACKAGE_BATOCERA_CONFIGGEN # emulation station external command launcher
174+
select BR2_PACKAGE_BATOCERA_LAUNCH # new emulation station external command launcher
174175
select BR2_PACKAGE_BATOCERA_TRIGGERHAPPY # multimedia/power buttons
175176
select BR2_PACKAGE_BATOCERA_UDEV_RULES # extra udev rules for specifics joysticks/devices
176177
select BR2_PACKAGE_BATOCERA_USERDATAINIT # userdata init files

0 commit comments

Comments
 (0)