Refactor retro_frontend
Add support for multiple controllers and device types. Make Genode to Libretro input mapping configurable, which relieves the need to externally remap Genode keycodes to conform to static Libretro mapping. Send keyboard input to a optional keyboard callback set by the core to complete keyboard support. Ref #89
This commit is contained in:
committed by
Norman Feske
parent
50076302b6
commit
a2f2c74fcf
2
recipes/src/retro_frontend/content.mk
Normal file
2
recipes/src/retro_frontend/content.mk
Normal file
@@ -0,0 +1,2 @@
|
||||
SRC_DIR := src/app/retro_frontend
|
||||
include $(GENODE_DIR)/repos/base/recipes/src/content.inc
|
||||
1
recipes/src/retro_frontend/hash
Normal file
1
recipes/src/retro_frontend/hash
Normal file
@@ -0,0 +1 @@
|
||||
2017-11-16 0224fb049a40facef7e0a7460936ee1b0bbf267d
|
||||
10
recipes/src/retro_frontend/used_apis
Normal file
10
recipes/src/retro_frontend/used_apis
Normal file
@@ -0,0 +1,10 @@
|
||||
audio_out_session
|
||||
base
|
||||
framebuffer_session
|
||||
input_session
|
||||
libc
|
||||
libretro
|
||||
os
|
||||
report_session
|
||||
timer_session
|
||||
vfs
|
||||
@@ -21,7 +21,6 @@ set build_components {
|
||||
drivers/timer
|
||||
libretro/fceumm
|
||||
server/fb_upscale
|
||||
server/input_remap
|
||||
}
|
||||
|
||||
source ${genode_dir}/repos/base/run/platform_drv.inc
|
||||
@@ -115,30 +114,6 @@ append config {
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
<start name="input_remap">
|
||||
<resource name="RAM" quantum="2M"/>
|
||||
<provides> <service name="Input"/> </provides>
|
||||
<config>
|
||||
<!-- Retroarch mappings -->
|
||||
<map from="KEY_Z" to="BTN_B"/>
|
||||
<map from="KEY_A" to="BTN_Y"/>
|
||||
<map from="KEY_X" to="BTN_A"/>
|
||||
<map from="KEY_S" to="BTN_X"/>
|
||||
<map from="KEY_Q" to="BTN_TL"/>
|
||||
<map from="KEY_W" to="BTN_TR"/>
|
||||
<map from="KEY_ENTER" to="BTN_START"/>
|
||||
<map from="KEY_RIGHTSHIFT" to="BTN_SELECT"/>
|
||||
<map from="KEY_LEFT" to="BTN_LEFT"/>
|
||||
<map from="KEY_RIGHT" to="BTN_RIGHT"/>
|
||||
<map from="KEY_UP" to="BTN_FORWARD"/>
|
||||
<map from="KEY_DOWN" to="BTN_BACK"/>
|
||||
</config>
|
||||
<route>
|
||||
<service name="Input">
|
||||
<child name="input_drv"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
<start name="report_rom">
|
||||
<resource name="RAM" quantum="4M"/>
|
||||
<provides> <service name="Report"/> </provides>
|
||||
@@ -149,16 +124,25 @@ append config {
|
||||
<config core="fceumm_libretro.lib.so">
|
||||
<game rom="Driar.nes"/>
|
||||
<libc stdout="/log" stderr="/log"/>
|
||||
<vfs>
|
||||
<log/>
|
||||
<!--<rom name="disksys.rom"/>-->
|
||||
</vfs>
|
||||
<vfs> <log/> </vfs>
|
||||
<controller port="0" device="257">
|
||||
<map from="KEY_LEFT" to="LEFT"/>
|
||||
<map from="KEY_RIGHT" to="RIGHT"/>
|
||||
<map from="KEY_UP" to="UP"/>
|
||||
<map from="KEY_DOWN" to="DOWN"/>
|
||||
|
||||
<map from="KEY_Z" to="B"/>
|
||||
<map from="KEY_X" to="A"/>
|
||||
|
||||
<map from="KEY_ENTER" to="START"/>
|
||||
<map from="KEY_RIGHTSHIFT" to="SELECT"/>
|
||||
</controller>
|
||||
</config>
|
||||
<route>
|
||||
<service name="Framebuffer">
|
||||
<child name="fb_upscale"/> </service>
|
||||
<service name="Input">
|
||||
<child name="input_remap"/> </service>
|
||||
<child name="input_drv"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
@@ -183,7 +167,6 @@ append boot_modules {
|
||||
} [audio_drv_binary] {
|
||||
fb_upscale
|
||||
fceumm_libretro.lib.so
|
||||
input_remap
|
||||
libc.lib.so
|
||||
libm.lib.so
|
||||
report_rom
|
||||
|
||||
@@ -115,30 +115,6 @@ append config {
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
<start name="input_remap">
|
||||
<resource name="RAM" quantum="2M"/>
|
||||
<provides> <service name="Input"/> </provides>
|
||||
<config>
|
||||
<!-- Retroarch mappings -->
|
||||
<map from="KEY_Z" to="BTN_B"/>
|
||||
<map from="KEY_A" to="BTN_Y"/>
|
||||
<map from="KEY_X" to="BTN_A"/>
|
||||
<map from="KEY_S" to="BTN_X"/>
|
||||
<map from="KEY_Q" to="BTN_TL"/>
|
||||
<map from="KEY_W" to="BTN_TR"/>
|
||||
<map from="KEY_ENTER" to="BTN_START"/>
|
||||
<map from="KEY_RIGHTSHIFT" to="BTN_SELECT"/>
|
||||
<map from="KEY_LEFT" to="BTN_LEFT"/>
|
||||
<map from="KEY_RIGHT" to="BTN_RIGHT"/>
|
||||
<map from="KEY_UP" to="BTN_FORWARD"/>
|
||||
<map from="KEY_DOWN" to="BTN_BACK"/>
|
||||
</config>
|
||||
<route>
|
||||
<service name="Input">
|
||||
<child name="input_drv"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
<start name="report_rom">
|
||||
<resource name="RAM" quantum="4M"/>
|
||||
<provides> <service name="Report"/> </provides>
|
||||
@@ -148,9 +124,22 @@ append config {
|
||||
<resource name="RAM" quantum="64M"/>
|
||||
<config core="meteor_libretro.lib.so">
|
||||
<game rom="game.gba"/>
|
||||
<controller port="0" device="1">
|
||||
<map from="KEY_LEFT" to="LEFT"/>
|
||||
<map from="KEY_RIGHT" to="RIGHT"/>
|
||||
<map from="KEY_UP" to="UP"/>
|
||||
<map from="KEY_DOWN" to="DOWN"/>
|
||||
|
||||
<map from="KEY_A" to="L"/>
|
||||
<map from="KEY_S" to="R"/>
|
||||
<map from="KEY_Z" to="B"/>
|
||||
<map from="KEY_X" to="A"/>
|
||||
|
||||
<map from="KEY_ENTER" to="START"/>
|
||||
<map from="KEY_RIGHTSHIFT" to="SELECT"/>
|
||||
</controller>
|
||||
<libc stdout="/log" stderr="/log"/>
|
||||
<vfs>
|
||||
<ram/>
|
||||
<log/>
|
||||
</vfs>
|
||||
</config>
|
||||
@@ -158,7 +147,7 @@ append config {
|
||||
<service name="Framebuffer">
|
||||
<child name="fb_upscale"/> </service>
|
||||
<service name="Input">
|
||||
<child name="input_remap"/> </service>
|
||||
<child name="input_drv"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
|
||||
@@ -21,7 +21,7 @@ set build_components {
|
||||
drivers/timer
|
||||
libretro/snes9x
|
||||
server/fb_upscale
|
||||
server/input_remap
|
||||
server/report_rom
|
||||
}
|
||||
|
||||
source ${genode_dir}/repos/base/run/platform_drv.inc
|
||||
@@ -116,29 +116,10 @@ append config {
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
<start name="input_remap">
|
||||
<resource name="RAM" quantum="2M"/>
|
||||
<provides> <service name="Input"/> </provides>
|
||||
<config>
|
||||
<!-- Retroarch mappings -->
|
||||
<map from="KEY_Z" to="BTN_B"/>
|
||||
<map from="KEY_A" to="BTN_Y"/>
|
||||
<map from="KEY_X" to="BTN_A"/>
|
||||
<map from="KEY_S" to="BTN_X"/>
|
||||
<map from="KEY_Q" to="BTN_TL"/>
|
||||
<map from="KEY_W" to="BTN_TR"/>
|
||||
<map from="KEY_ENTER" to="BTN_START"/>
|
||||
<map from="KEY_RIGHTSHIFT" to="BTN_SELECT"/>
|
||||
<map from="KEY_LEFT" to="BTN_LEFT"/>
|
||||
<map from="KEY_RIGHT" to="BTN_RIGHT"/>
|
||||
<map from="KEY_UP" to="BTN_FORWARD"/>
|
||||
<map from="KEY_DOWN" to="BTN_BACK"/>
|
||||
</config>
|
||||
<route>
|
||||
<service name="Input">
|
||||
<child name="input_drv"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
<start name="report_rom">
|
||||
<resource name="RAM" quantum="4M"/>
|
||||
<provides> <service name="Report"/> </provides>
|
||||
<config verbose="yes"/>
|
||||
</start>
|
||||
<start name="retro_frontend">
|
||||
<resource name="RAM" quantum="64M"/>
|
||||
@@ -146,12 +127,24 @@ append config {
|
||||
<game rom="superbossgaiden.sfc"/>
|
||||
<libc stdout="/log" stderr="/log"/>
|
||||
<vfs> <log/> </vfs>
|
||||
<controller port="0" device="1">
|
||||
<map from="KEY_LEFT" to="LEFT"/>
|
||||
<map from="KEY_RIGHT" to="RIGHT"/>
|
||||
<map from="KEY_UP" to="UP"/>
|
||||
<map from="KEY_DOWN" to="DOWN"/>
|
||||
|
||||
<map from="KEY_A" to="L"/>
|
||||
<map from="KEY_S" to="R"/>
|
||||
<map from="KEY_Z" to="B"/>
|
||||
<map from="KEY_X" to="A"/>
|
||||
|
||||
<map from="KEY_ENTER" to="START"/>
|
||||
<map from="KEY_RIGHTSHIFT" to="SELECT"/>
|
||||
</controller>
|
||||
</config>
|
||||
<route>
|
||||
<service name="Framebuffer">
|
||||
<child name="fb_upscale"/> </service>
|
||||
<service name="Input">
|
||||
<child name="input_remap"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
@@ -183,9 +176,9 @@ append boot_modules {
|
||||
core init ld.lib.so
|
||||
audio_drv
|
||||
fb_upscale
|
||||
input_remap
|
||||
libc.lib.so
|
||||
libm.lib.so
|
||||
report_rom
|
||||
retro_frontend
|
||||
stdcxx.lib.so
|
||||
snes9x_libretro.lib.so
|
||||
|
||||
@@ -24,7 +24,6 @@ build {
|
||||
app/retro_frontend
|
||||
libretro/tyrquake
|
||||
server/fb_upscale
|
||||
server/input_remap
|
||||
}
|
||||
|
||||
#
|
||||
@@ -76,45 +75,6 @@ append config {
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
<start name="input_remap">
|
||||
<resource name="RAM" quantum="2M"/>
|
||||
<provides> <service name="Input"/> </provides>
|
||||
<config>
|
||||
<!-- Map Genode keys to libretro keys.
|
||||
This is really awkward, keycodes from the 'retro_frontend -> input'
|
||||
report must be cross-referenced from libretro.h to the retro_frontend
|
||||
translation table. In the future the frontend may use logical
|
||||
names in the 'input' report or feature a built-in remapper.
|
||||
-->
|
||||
<map from="KEY_A" to="BTN_LEFT"/>
|
||||
<map from="KEY_W" to="BTN_FORWARD"/>
|
||||
<map from="KEY_S" to="BTN_BACK"/>
|
||||
<map from="KEY_D" to="BTN_RIGHT"/>
|
||||
|
||||
<map from="KEY_RIGHT" to="BTN_B"/>
|
||||
<map from="KEY_DOWN" to="BTN_A"/>
|
||||
<map from="KEY_LEFT" to="BTN_X"/>
|
||||
<map from="KEY_UP" to="BTN_Y"/>
|
||||
|
||||
<map from="KEY_Q" to="BTN_TL"/>
|
||||
<map from="KEY_E" to="BTN_TR"/>
|
||||
|
||||
<map from="KEY_LEFTSHIFT" to="BTN_THUMBL"/>
|
||||
|
||||
<map from="KEY_LEFTCTRL" to="BTN_TR2"/>
|
||||
<map from="KEY_SPACE" to="BTN_TL2"/>
|
||||
|
||||
|
||||
<map from="KEY_GRAVE" to="BTN_SELECT"/>
|
||||
<map from="KEY_ESC" to="BTN_START"/>
|
||||
<map from="KEY_ENTER" to="BTN_A"/>
|
||||
</config>
|
||||
<route>
|
||||
<service name="Input">
|
||||
<child name="drivers"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
</route>
|
||||
</start>
|
||||
<start name="report_rom">
|
||||
<resource name="RAM" quantum="4M"/>
|
||||
<provides> <service name="Report"/> </provides>
|
||||
@@ -130,12 +90,17 @@ append config {
|
||||
<dir name="id1"> <tar name="quake.tar"/> </dir>
|
||||
<ram/>
|
||||
</vfs>
|
||||
<variables>
|
||||
<variable key="tyrquake_colored_lighting" value="disabled"/>
|
||||
<variable key="tyrquake_resolution" value="640x400"/>
|
||||
</variables>
|
||||
<controller port="0" device="3"/>
|
||||
</config>
|
||||
<route>
|
||||
<service name="Framebuffer">
|
||||
<child name="fb_upscale"/> </service>
|
||||
<service name="Input">
|
||||
<child name="input_remap"/> </service>
|
||||
<child name="drivers"/> </service>
|
||||
<service name="Report">
|
||||
<child name="report_rom"/> </service>
|
||||
<any-service> <parent/> <any-child/> </any-service>
|
||||
@@ -164,34 +129,17 @@ if {![file exist "bin/quake.tar"]} {
|
||||
exec rm -rf bin/tmp
|
||||
}
|
||||
|
||||
#
|
||||
# This core requires that some variables be set by the frontend
|
||||
#
|
||||
set variables_fd [open "bin/variables" w]
|
||||
puts $variables_fd {
|
||||
<variables>
|
||||
<variable key="tyrquake_colored_lighting" value="disabled"/>
|
||||
<variable key="tyrquake_resolution" value="640x400"/>
|
||||
<variable key="tyrquake_retropad_layout" value="1: New layout"/>
|
||||
</variables>
|
||||
}
|
||||
close $variables_fd
|
||||
|
||||
# generic modules
|
||||
build_boot_image {
|
||||
fb_upscale
|
||||
input_remap
|
||||
libc.lib.so
|
||||
libm.lib.so
|
||||
quake.tar
|
||||
retro_frontend
|
||||
stdcxx.lib.so
|
||||
tyrquake_libretro.lib.so
|
||||
variables
|
||||
}
|
||||
|
||||
append qemu_args " -soundhw es1370 "
|
||||
|
||||
run_genode_until forever
|
||||
|
||||
rm bin/variables
|
||||
|
||||
222
src/app/retro_frontend/README
Normal file
222
src/app/retro_frontend/README
Normal file
@@ -0,0 +1,222 @@
|
||||
The `retro_frontend` component is a runtime for Libretro.
|
||||
It is the Genode analogue to the `Retroarch` reference frontend.
|
||||
|
||||
A Libretro core is a game or other application that implements the Libretro API,
|
||||
and in this case, is linked as a dynamic library. The runtime frontend loads a
|
||||
core and installs callbacks that allow the core to draw to a framebuffer, poll
|
||||
input, play audio, and reconfigure itself. The core executes not by its own main
|
||||
loop but is driven by the fronted to match a framerate recommended by the core.
|
||||
This architecture allows for game engines and emulators to further abstract
|
||||
their operating system interactions and hopefully improve their long-term binary
|
||||
compatibility.
|
||||
|
||||
|
||||
Shortcomings
|
||||
############
|
||||
|
||||
To begin with, most features present in Retroarch are not found in this
|
||||
frontend. Some are appropriate to internalize and others are best
|
||||
implemented as additional components.
|
||||
|
||||
To list a few :
|
||||
- Analog input axes
|
||||
- Mouse input axes
|
||||
- Hardware accelerated rendering
|
||||
- Pixel format conversion
|
||||
- Shaders and pixel upscalers
|
||||
- Time acceleration and dilation
|
||||
- State rewinding
|
||||
- Remote input over IP
|
||||
- High-quality audio resampling
|
||||
- User interface for frontend management
|
||||
|
||||
Patches are welcome!
|
||||
|
||||
|
||||
Required environment
|
||||
####################
|
||||
|
||||
'retro_frontend' requires at minimum 'Framebuffer' and 'Timer' services.
|
||||
'Input' and 'Audio_out' are optional. For use with 'Nitpicker' the 'nit_fb'
|
||||
component will provide 'Framebuffer' and 'Input'. The frontend uses
|
||||
core-specific framebuffer dimensions so use of the 'fb_upscale' component
|
||||
is recommended to zoom framebuffer dimensions to a match. Multiple 'Input'
|
||||
sessions are requested when the frontend is configured with multiple
|
||||
controllers.
|
||||
|
||||
|
||||
Configuration
|
||||
#############
|
||||
|
||||
The most important configuration option is the core, this is an attribute on
|
||||
the top-level configuration XML node. The filename specified here is loaded
|
||||
via a ROM session as a dynamic library.
|
||||
! <config core="core_libretro.lib.so">
|
||||
! ...
|
||||
! </config>
|
||||
|
||||
The second most import option is game data. A `game` node within the configuration
|
||||
specifies game to be loaded by a ROM image or by path. A third 'meta' option is
|
||||
present, its meaning is core-specific and is seldom used. When no 'game' node is
|
||||
present a game path of "/" is assumed.
|
||||
|
||||
Load by ROM:
|
||||
! <config ...>
|
||||
! <game rom="cartridge.bin"/>
|
||||
! ...
|
||||
! </config>
|
||||
|
||||
Load by path:
|
||||
! <config ...>
|
||||
! <game path="/assets"/>
|
||||
! ...
|
||||
! </config>
|
||||
|
||||
|
||||
Input
|
||||
=====
|
||||
|
||||
The frontend supports multiple controllers and maps input codes from Genode to
|
||||
Libretro. Controllers are specified by 'controller' nodes within the configuration.
|
||||
|
||||
For each controller it is recommended to specify a device type. The frontend and
|
||||
core will assume by default that the controller is a joypad and will likely only
|
||||
poll for joypad input. Defining a device type at the frontend does not restrict
|
||||
or mask controller input from other types, but determines which inputs the core
|
||||
will poll and how inputs are interpreted. There are several basic device types
|
||||
and cores may define special device types by subclassing from a basic type. A
|
||||
ore will publish its device types and descriptions through the frontend via the
|
||||
'controllers' report described later in this document. Cores may additionally
|
||||
publish textual descriptions of each button on each port via an 'input' report.
|
||||
|
||||
Base supported device types:
|
||||
1 | Joypad
|
||||
2 | Mouse (axes not supported)
|
||||
3 | Keyboard
|
||||
|
||||
Mapping input to a Libretro keyboard on port 0, a joypad with remapping to port 1,
|
||||
and a joypad derived device on port 2:
|
||||
! <config ...>
|
||||
! <controller port="0" device="3"/>
|
||||
! <controller port="1" device="1">
|
||||
! <map from="BTN_TR" to="R2"/>
|
||||
! <map from="BTN_TR2" to="R"/>
|
||||
! <map from="BTN_TL" to="L2"/>
|
||||
! <map from="BTN_TL2" to="L"/>
|
||||
! </controller>
|
||||
! <controller port="2" device="257"/>
|
||||
! </config>
|
||||
|
||||
Port 1 in the example above port show four key remappings. In this example
|
||||
the four trigger or bumper buttons on a gamepad are vertically reversed.
|
||||
The 'from' attribute on a 'map' node indicates a Genode input code and the
|
||||
'to' attribute indicates a Libretro input code. Genode input codes may be
|
||||
determined externally using the 'test-input' component and Libretro input
|
||||
codes may be found in 'input' report described later. Mappings may only
|
||||
be made within the base device. Mapping keyboards to joypads is supported
|
||||
but remapping keyboard keys is not. The 'input_merger' or 'input_remap'
|
||||
component may be used to externally remap the Libretro keyboard type.
|
||||
|
||||
|
||||
Variables
|
||||
=========
|
||||
|
||||
Cores usually request their own configuration options from the frontend
|
||||
and these are in turn resolved from the XML configuration provided to
|
||||
the frontend. These options are published by the core via the frontend
|
||||
'variables' report described later. A 'variable' node within the
|
||||
configuration binds a configuration value to a configuration key.
|
||||
|
||||
! <config ... >
|
||||
! <variable key="region" value="PAL"/>
|
||||
! ...
|
||||
! </config>
|
||||
|
||||
|
||||
C runtime
|
||||
=========
|
||||
|
||||
The frontend provides a POSIX environment for cores like any other
|
||||
component linked to the Genode C runtime. In this regard the frontend
|
||||
should be configured with a VFS and connections for standard I/O.
|
||||
|
||||
! <config core="...">
|
||||
! <vfs>
|
||||
! <tar name="..."/>
|
||||
! <dir name="dev"> <log/> </dir>
|
||||
! </vfs>
|
||||
! <libc stdout="/dev/log" stderr="/dev/log"/>
|
||||
! </config>
|
||||
|
||||
|
||||
Reports
|
||||
#######
|
||||
|
||||
The frontend published information from the core in the form of 'Report'
|
||||
sessions. The reports are 'variables' 'controllers', and 'inputs'.
|
||||
|
||||
|
||||
Variables
|
||||
=========
|
||||
|
||||
The variables report contains pairs of keys and possible values. These inform
|
||||
what variables should be included in the configuration passed to the frontend.
|
||||
Valid options for a given variable are seperated by the '|' character.
|
||||
|
||||
! <variables>
|
||||
! <variable key="nospritelimit" value="No Sprite Limit; disabled|enabled"/>
|
||||
! <variable key="overscan_v" value="Crop Overscan (Vertical); enabled|disabled"/>
|
||||
! <variable key="region" value="Region Override; Auto|NTSC|PAL|Dendy"/>
|
||||
! </variables>
|
||||
|
||||
|
||||
Controllers
|
||||
===========
|
||||
|
||||
The controllers report conveys which device types a core recognizes or expects
|
||||
and informs how inputs to the frontend should be configured.
|
||||
|
||||
! <controllers>
|
||||
! <controller port="0">
|
||||
! <type desc="Gamepad" id="1"/>
|
||||
! <type desc="Keyboard/Mouse" id="3"/>
|
||||
! <type desc="Zapper" id="258"/>
|
||||
! </controller>
|
||||
! </controllers>
|
||||
|
||||
|
||||
Inputs
|
||||
======
|
||||
|
||||
The input report describes the function of different inputs over different
|
||||
controller ports. The value of an 'id' attribute on a descriptor node may
|
||||
be used to map from a Genode input to a Libretro input as described
|
||||
previously.
|
||||
|
||||
! <inputs>
|
||||
! <descriptor port="0" device="JOYPAD" index="0" id="LEFT" description="D-Pad Left"/>
|
||||
! <descriptor port="0" device="JOYPAD" index="0" id="UP" description="D-Pad Up"/>
|
||||
! <.../>
|
||||
! <descriptor port="0" device="JOYPAD" index="0" id="SELECT" description="Toggle console"/>
|
||||
! <descriptor port="0" device="JOYPAD" index="0" id="START" description="Menu"/>
|
||||
! </inputs>
|
||||
|
||||
|
||||
Runtime
|
||||
#######
|
||||
|
||||
Pausing
|
||||
========
|
||||
|
||||
The frontend recognizes the *Pause* key found on standard keyboards and
|
||||
acts accordingly. When the frontend pauses the core is halted and
|
||||
save RAM is dumped to file for games that are loaded from ROM. When a
|
||||
game warns "do not turn of the power" it is recommended to tap pause
|
||||
twice after the warning to dump the RAM. When unpausing the frontend
|
||||
will load back save RAM and resynchronize the framebuffer and audio-out
|
||||
buffer, so giving *Pause* a double tap can also fix laggy audio.
|
||||
|
||||
Please note that the *Pause* key is usually only found on keyboards.
|
||||
When connected directly to gamepad input drivers an additional
|
||||
controller port would need to be defined and routed to an 'Input'
|
||||
session where a keyboard is present to enable pause support.
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 Genode Labs GmbH
|
||||
* Copyright (C) 2016-2017 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
@@ -17,12 +17,12 @@
|
||||
/* Genode includes */
|
||||
#include <audio_out_session/connection.h>
|
||||
#include <base/attached_ram_dataspace.h>
|
||||
#include <util/reconstructible.h>
|
||||
#include <base/thread.h>
|
||||
#include <base/log.h>
|
||||
#include <util/reconstructible.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
namespace Retro_frontend {
|
||||
#include <libretro.h>
|
||||
|
||||
template <typename TYPE, Genode::size_t CAPACITY>
|
||||
struct Ring_buffer;
|
||||
@@ -48,8 +48,6 @@ struct Retro_frontend::Ring_buffer : Genode::Lock
|
||||
{
|
||||
enum { BUFFER_SIZE = sizeof(TYPE)*CAPACITY };
|
||||
|
||||
Genode::Env &env;
|
||||
|
||||
Genode::addr_t map_first;
|
||||
Genode::addr_t map_second;
|
||||
|
||||
@@ -58,21 +56,21 @@ struct Retro_frontend::Ring_buffer : Genode::Lock
|
||||
Genode::size_t wpos = 0;
|
||||
Genode::size_t rpos = 0;
|
||||
|
||||
Genode::Ram_dataspace_capability buffer_ds = env.ram().alloc(BUFFER_SIZE);
|
||||
Genode::Ram_dataspace_capability buffer_ds = genv->ram().alloc(BUFFER_SIZE);
|
||||
|
||||
Ring_buffer(Genode::Env &env) : env(env)
|
||||
Ring_buffer()
|
||||
{
|
||||
{
|
||||
/* a hack to find the right sized void in the address space */
|
||||
Genode::Attached_ram_dataspace filler(env.ram(), env.rm(), BUFFER_SIZE*2);
|
||||
Genode::Attached_ram_dataspace filler(genv->ram(), genv->rm(), BUFFER_SIZE*2);
|
||||
map_first = (Genode::addr_t)filler.local_addr<TYPE>();
|
||||
}
|
||||
|
||||
map_second = map_first+BUFFER_SIZE;
|
||||
|
||||
/* attach the buffer in two consecutive regions */
|
||||
map_first = env.rm().attach_at(buffer_ds, map_first, BUFFER_SIZE);
|
||||
map_second = env.rm().attach_at(buffer_ds, map_second, BUFFER_SIZE);
|
||||
map_first = genv->rm().attach_at(buffer_ds, map_first, BUFFER_SIZE);
|
||||
map_second = genv->rm().attach_at(buffer_ds, map_second, BUFFER_SIZE);
|
||||
if ((map_first+BUFFER_SIZE) != map_second) {
|
||||
Genode::error("failed to map ring buffer to consecutive regions");
|
||||
throw Genode::Exception();
|
||||
@@ -83,9 +81,9 @@ struct Retro_frontend::Ring_buffer : Genode::Lock
|
||||
|
||||
~Ring_buffer()
|
||||
{
|
||||
env.rm().detach(map_second);
|
||||
env.rm().detach(map_first);
|
||||
env.ram().free(buffer_ds);
|
||||
genv->rm().detach(map_second);
|
||||
genv->rm().detach(map_first);
|
||||
genv->ram().free(buffer_ds);
|
||||
}
|
||||
|
||||
Genode::size_t read_avail() const
|
||||
@@ -160,15 +158,14 @@ struct Retro_frontend::Stereo_out : Genode::Thread
|
||||
|
||||
void entry() override;
|
||||
|
||||
Stereo_out(Genode::Env &env)
|
||||
Stereo_out()
|
||||
:
|
||||
Genode::Thread(env, "audio-sync", 8*1024,
|
||||
env.cpu().affinity_space().location_of_index(1),
|
||||
Genode::Thread(*genv, "audio-sync", 8*1024,
|
||||
genv->cpu().affinity_space().location_of_index(1),
|
||||
Weight(Genode::Cpu_session::Weight::DEFAULT_WEIGHT-1),
|
||||
env.cpu()),
|
||||
left( env, "left", false, true),
|
||||
right(env, "right", false, true),
|
||||
buffer(env)
|
||||
genv->cpu()),
|
||||
left( *genv, "left", false, true),
|
||||
right(*genv, "right", false, true)
|
||||
{
|
||||
start();
|
||||
}
|
||||
@@ -189,11 +186,10 @@ struct Retro_frontend::Stereo_out : Genode::Thread
|
||||
static Genode::Constructible<Retro_frontend::Stereo_out> stereo_out;
|
||||
|
||||
|
||||
static
|
||||
void audio_sample_noop(int16_t left, int16_t right) { }
|
||||
|
||||
|
||||
static /* not called in pratice */
|
||||
/* not called in pratice */
|
||||
void audio_sample_callback(int16_t left, int16_t right)
|
||||
{
|
||||
stereo_out->buffer.lock();
|
||||
@@ -203,11 +199,9 @@ void audio_sample_callback(int16_t left, int16_t right)
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
size_t audio_sample_batch_noop(const int16_t *data, size_t frames) { return 0; }
|
||||
|
||||
|
||||
static
|
||||
size_t audio_sample_batch_callback(const int16_t *data, size_t frames)
|
||||
{
|
||||
Genode::Lock::Guard guard(stereo_out->buffer);
|
||||
|
||||
@@ -5,143 +5,32 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 Genode Labs GmbH
|
||||
* Copyright (C) 2016-2017 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
/* libc includes */
|
||||
/* Genode includes */
|
||||
#include <libc/component.h>
|
||||
|
||||
#include "frontend.h"
|
||||
#include "callbacks.h"
|
||||
/* Local includes */
|
||||
#include "config.h"
|
||||
#include "environment.h"
|
||||
#include "dispatcher.h"
|
||||
|
||||
Retro_frontend::Frontend::Frontend(Libc::Env &env) : env(env)
|
||||
{
|
||||
/* set the global frontend pointer for callbacks */
|
||||
global_frontend = this;
|
||||
namespace Retro_frontend {
|
||||
|
||||
/****************
|
||||
** Initialize **
|
||||
****************/
|
||||
static Dispatcher *dispatcher;
|
||||
|
||||
retro_system_info sys_info;
|
||||
void toggle_pause() {
|
||||
dispatcher->toggle_pause(); }
|
||||
|
||||
shared_object.lookup<Retro_get_system_info>
|
||||
("retro_get_system_info")(&sys_info);
|
||||
|
||||
Genode::log("Name: ", sys_info.library_name,
|
||||
"\nVersion: ", sys_info.library_version,
|
||||
"\nExtensions: ", sys_info.valid_extensions ?
|
||||
sys_info.valid_extensions : "");
|
||||
|
||||
shared_object.lookup<Retro_set_environment>
|
||||
("retro_set_environment")(environment_callback);
|
||||
|
||||
shared_object.lookup<Retro_init>("retro_init")();
|
||||
|
||||
|
||||
/*******************
|
||||
** Set callbacks **
|
||||
*******************/
|
||||
|
||||
shared_object.lookup<Retro_set_video_refresh>
|
||||
("retro_set_video_refresh")(video_refresh_callback);
|
||||
|
||||
try {
|
||||
stereo_out.construct(env);
|
||||
|
||||
shared_object.lookup<Retro_set_audio_sample>
|
||||
("retro_set_audio_sample")(audio_sample_callback);
|
||||
|
||||
shared_object.lookup<Retro_set_audio_sample_batch>
|
||||
("retro_set_audio_sample_batch")(audio_sample_batch_callback);
|
||||
} catch (...) {
|
||||
Genode::error("failed to initialize audio");
|
||||
|
||||
shared_object.lookup<Retro_set_audio_sample>
|
||||
("retro_set_audio_sample")(audio_sample_noop);
|
||||
|
||||
shared_object.lookup<Retro_set_audio_sample_batch>
|
||||
("retro_set_audio_sample_batch")(audio_sample_batch_noop);
|
||||
void shutdown()
|
||||
{
|
||||
dispatcher->deinit();
|
||||
genv->parent().exit(0);
|
||||
}
|
||||
|
||||
shared_object.lookup<Retro_set_input_poll>
|
||||
("retro_set_input_poll")(input_poll_callback);
|
||||
|
||||
shared_object.lookup<Retro_set_input_state>
|
||||
("retro_set_input_state")(input_state_callback);
|
||||
|
||||
|
||||
/********************
|
||||
** Load game data **
|
||||
********************/
|
||||
|
||||
game_info.path = NULL;
|
||||
game_info.data = NULL;
|
||||
game_info.size = 0;
|
||||
|
||||
game_info.meta = "";
|
||||
|
||||
try {
|
||||
Genode::Xml_node game_node = config_rom.xml().sub_node("game");
|
||||
|
||||
rom_name = game_node.attribute_value("rom", Rom_name());
|
||||
game_path = game_node.attribute_value("path", Game_path());
|
||||
rom_meta = game_node.attribute_value("name", Rom_name());
|
||||
|
||||
if (rom_name != "") {
|
||||
game_rom.construct(env, rom_name.string());
|
||||
game_info.data = game_rom->local_addr<const void>();
|
||||
game_info.size = game_rom->size();
|
||||
game_info.path = "";
|
||||
Genode::log("loading game from ROM '", rom_name, "'");
|
||||
} else if (game_path != "") {
|
||||
game_info.path = game_path.string();
|
||||
Genode::log("loading game from path '", game_path, "'");
|
||||
}
|
||||
game_info.meta =
|
||||
(rom_meta != "") ? rom_meta.string() :
|
||||
(rom_name != "") ? rom_name.string() :
|
||||
game_path.string();
|
||||
}
|
||||
catch (...) { }
|
||||
|
||||
if (sys_info.need_fullpath && (game_info.path == NULL))
|
||||
game_info.path = "/";
|
||||
|
||||
if (!(game_info.path || game_info.data)) {
|
||||
/* some cores don't need game data */
|
||||
Genode::error("no game content loaded");
|
||||
}
|
||||
|
||||
if (!(retro_load_game(&game_info))) {
|
||||
Genode::error("failed to load game data");
|
||||
retro_deinit();
|
||||
throw ~0;
|
||||
}
|
||||
|
||||
load_memory();
|
||||
|
||||
|
||||
/******************
|
||||
** Get A/V info **
|
||||
******************/
|
||||
|
||||
retro_system_av_info av_info;
|
||||
shared_object.lookup<Retro_get_system_av_info>
|
||||
("retro_get_system_av_info")(&av_info);
|
||||
|
||||
Genode::log("FPS of video content: ", (int)av_info.timing.fps);
|
||||
Genode::log("Sampling rate of audio: ", (int)av_info.timing.sample_rate);
|
||||
|
||||
set_av_info(av_info);
|
||||
|
||||
framebuffer->session.mode_sigh(mode_handler);
|
||||
|
||||
/* flush out the input events that may have piled up */
|
||||
flush_input();
|
||||
}
|
||||
|
||||
|
||||
@@ -150,22 +39,15 @@ Genode::size_t Component::stack_size() { return 64*1024*sizeof(Genode::addr_t);
|
||||
|
||||
void Libc::Component::construct(Libc::Env &env)
|
||||
{
|
||||
using namespace Genode;
|
||||
using namespace Retro_frontend;
|
||||
|
||||
init_keyboard_map();
|
||||
genv = &env;
|
||||
|
||||
/* run the frontend and core initialization in application context */
|
||||
try { Libc::with_libc([&env] () { static Frontend inst(env); }); }
|
||||
config_rom.construct(env, "config");
|
||||
|
||||
catch (Shared_object::Invalid_rom_module) {
|
||||
error("failed to load core"); }
|
||||
static Dispatcher inst;
|
||||
dispatcher = &inst;
|
||||
|
||||
catch (Shared_object::Invalid_symbol) {
|
||||
error("failed to load required symbols from core"); }
|
||||
|
||||
catch (...) {
|
||||
error("failed to init core");
|
||||
env.parent().exit(-1);
|
||||
}
|
||||
/* load and initialize core, configure signal handlers */
|
||||
dispatcher->handle_config();
|
||||
}
|
||||
|
||||
36
src/app/retro_frontend/config.h
Normal file
36
src/app/retro_frontend/config.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* \brief Libretro configuration
|
||||
* \author Emery Hemingway
|
||||
* \date 2017-11-03
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2017 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _RETRO_FRONTEND__CONFIG_H_
|
||||
#define _RETRO_FRONTEND__CONFIG_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/attached_rom_dataspace.h>
|
||||
|
||||
namespace Retro_frontend {
|
||||
|
||||
static unsigned config_version = 0;
|
||||
|
||||
static Genode::Constructible<Genode::Attached_rom_dataspace> config_rom;
|
||||
|
||||
Genode::Xml_node config_variables()
|
||||
{
|
||||
try {
|
||||
return config_rom->xml().sub_node("variables");
|
||||
} catch (...) {
|
||||
return Genode::Xml_node("<variables/>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
174
src/app/retro_frontend/core.h
Normal file
174
src/app/retro_frontend/core.h
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* \brief Libretro frontend
|
||||
* \author Emery Hemingway
|
||||
* \date 2017-11-04
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2017 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _RETRO_FRONTEND__CORE_H_
|
||||
#define _RETRO_FRONTEND__CORE_H_
|
||||
|
||||
#include <base/debug.h>
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/shared_object.h>
|
||||
#include <base/component.h>
|
||||
#include <base/log.h>
|
||||
|
||||
#include <libretro.h>
|
||||
|
||||
namespace Retro_frontend {
|
||||
|
||||
static Genode::Env *genv;
|
||||
|
||||
typedef void (*Retro_set_environment)(retro_environment_t);
|
||||
typedef void (*Retro_set_video_refresh)(retro_video_refresh_t);
|
||||
typedef void (*Retro_set_audio_sample)(retro_audio_sample_t);
|
||||
typedef void (*Retro_set_audio_sample_batch)(retro_audio_sample_batch_t);
|
||||
typedef void (*Retro_set_input_poll)(retro_input_poll_t);
|
||||
typedef void (*Retro_set_input_state)(retro_input_state_t);
|
||||
typedef void (*Retro_init)(void);
|
||||
typedef void (*Retro_deinit)(void);
|
||||
typedef unsigned (*Retro_api_version)(void);
|
||||
typedef void (*Retro_get_system_info)(struct retro_system_info *info);
|
||||
typedef void (*Retro_get_system_av_info)(struct retro_system_av_info *info);
|
||||
typedef void (*Retro_set_controller_port_device)(unsigned port, unsigned device);
|
||||
typedef void (*Retro_reset)(void);
|
||||
typedef void (*Retro_run)(void);
|
||||
typedef size_t (*Retro_serialize_size)(void);
|
||||
typedef bool (*Retro_serialize)(void *data, size_t size);
|
||||
typedef bool (*Retro_unserialize)(const void *data, size_t size);
|
||||
typedef void (*Retro_cheat_reset)(void);
|
||||
typedef void (*Retro_cheat_set)(unsigned index, bool enabled, const char *code);
|
||||
typedef bool (*Retro_load_game)(const struct retro_game_info *game);
|
||||
typedef bool (*Retro_load_game_special)(
|
||||
unsigned game_type,
|
||||
const struct retro_game_info *info, size_t num_info
|
||||
);
|
||||
typedef void (*Retro_unload_game)(void);
|
||||
typedef unsigned (*Retro_get_region)(void);
|
||||
typedef void *(*Retro_get_memory_data)(unsigned id);
|
||||
typedef size_t (*Retro_get_memory_size)(unsigned id);
|
||||
|
||||
Retro_init retro_init;
|
||||
Retro_deinit retro_deinit;
|
||||
Retro_load_game retro_load_game;
|
||||
Retro_unload_game retro_unload_game;
|
||||
|
||||
Retro_get_system_av_info retro_get_system_av_info;
|
||||
|
||||
Retro_get_memory_data retro_get_memory_data;
|
||||
Retro_get_memory_size retro_get_memory_size;
|
||||
|
||||
Retro_set_controller_port_device retro_set_controller_port_device;
|
||||
|
||||
Retro_get_system_info retro_get_system_info;
|
||||
|
||||
Retro_set_environment retro_set_environment;
|
||||
|
||||
Retro_set_video_refresh retro_set_video_refresh;
|
||||
|
||||
Retro_set_audio_sample retro_set_audio_sample;
|
||||
Retro_set_audio_sample_batch retro_set_audio_sample_batch;
|
||||
|
||||
Retro_set_input_poll retro_set_input_poll;
|
||||
Retro_set_input_state retro_set_input_state;
|
||||
|
||||
Retro_run retro_run;
|
||||
|
||||
struct Core;
|
||||
};
|
||||
|
||||
|
||||
/*************************
|
||||
** callback prototypes **
|
||||
*************************/
|
||||
|
||||
bool environment_callback(unsigned cmd, void *data);
|
||||
|
||||
void input_poll_callback();
|
||||
|
||||
int16_t input_state_callback(unsigned port, unsigned device,
|
||||
unsigned index, unsigned id);
|
||||
|
||||
void video_refresh_callback(const void *data,
|
||||
unsigned src_width, unsigned src_height,
|
||||
size_t src_pitch);
|
||||
|
||||
void audio_sample_noop(int16_t left, int16_t right);
|
||||
void audio_sample_callback(int16_t left, int16_t right);
|
||||
size_t audio_sample_batch_noop(const int16_t *data, size_t frames);
|
||||
size_t audio_sample_batch_callback(const int16_t *data, size_t frames);
|
||||
|
||||
void log_printf_callback(retro_log_level level, const char *fmt, ...);
|
||||
|
||||
|
||||
struct Retro_frontend::Core
|
||||
{
|
||||
typedef Genode::String<128> Name;
|
||||
|
||||
Genode::Shared_object so;
|
||||
|
||||
Name const name;
|
||||
|
||||
Core(Genode::Allocator &alloc, Name const &name)
|
||||
: so(*genv, alloc, name.string(),
|
||||
Genode::Shared_object::BIND_LAZY,
|
||||
Genode::Shared_object::DONT_KEEP),
|
||||
name(name)
|
||||
{
|
||||
unsigned api_version = so.lookup<Retro_api_version>("retro_api_version")();
|
||||
if (api_version != RETRO_API_VERSION) {
|
||||
Genode::error("core ", name,
|
||||
" uses unsupported API version ", api_version);
|
||||
throw Genode::Shared_object::Invalid_rom_module();
|
||||
}
|
||||
|
||||
retro_init = so.lookup<Retro_init>("retro_init");
|
||||
retro_deinit = so.lookup<Retro_deinit>("retro_deinit");
|
||||
|
||||
retro_load_game = so.lookup<Retro_load_game>("retro_load_game");
|
||||
retro_unload_game = so.lookup<Retro_unload_game>("retro_unload_game");
|
||||
|
||||
retro_get_system_av_info = so.lookup<Retro_get_system_av_info>("retro_get_system_av_info");
|
||||
|
||||
retro_get_memory_data = so.lookup<Retro_get_memory_data>("retro_get_memory_data");
|
||||
retro_get_memory_size = so.lookup<Retro_get_memory_size>("retro_get_memory_size");
|
||||
|
||||
retro_set_controller_port_device = so.lookup<Retro_set_controller_port_device>("retro_set_controller_port_device");
|
||||
retro_run = so.lookup<Retro_run>("retro_run");
|
||||
|
||||
retro_get_system_info = so.lookup<Retro_get_system_info>
|
||||
("retro_get_system_info");
|
||||
|
||||
retro_set_environment = so.lookup<Retro_set_environment>
|
||||
("retro_set_environment");
|
||||
|
||||
retro_init = so.lookup<Retro_init>("retro_init");
|
||||
|
||||
retro_set_video_refresh = so.lookup<Retro_set_video_refresh>("retro_set_video_refresh");
|
||||
|
||||
retro_set_audio_sample = so.lookup<Retro_set_audio_sample>("retro_set_audio_sample");
|
||||
retro_set_audio_sample_batch = so.lookup<Retro_set_audio_sample_batch>("retro_set_audio_sample_batch");
|
||||
|
||||
retro_set_input_poll = so.lookup<Retro_set_input_poll>("retro_set_input_poll");
|
||||
|
||||
retro_set_input_state = so.lookup<Retro_set_input_state>("retro_set_input_state");
|
||||
}
|
||||
};
|
||||
|
||||
namespace Retro_frontend {
|
||||
|
||||
static Genode::Constructible<Core> core;
|
||||
|
||||
void toggle_pause();
|
||||
void shutdown();
|
||||
}
|
||||
|
||||
#endif
|
||||
419
src/app/retro_frontend/dispatcher.h
Normal file
419
src/app/retro_frontend/dispatcher.h
Normal file
@@ -0,0 +1,419 @@
|
||||
/*
|
||||
* \brief Libretro signal dispatcher
|
||||
* \author Emery Hemingway
|
||||
* \date 2017-11-03
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2017 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _RETRO_FRONTEND__DISPATCHER_H_
|
||||
#define _RETRO_FRONTEND__DISPATCHER_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <timer_session/connection.h>
|
||||
#include <base/heap.h>
|
||||
#include <libc/component.h>
|
||||
|
||||
/* Local includes */
|
||||
#include "audio.h"
|
||||
#include "config.h"
|
||||
#include "input.h"
|
||||
#include "framebuffer.h"
|
||||
#include "memory.h"
|
||||
|
||||
namespace Retro_frontend { struct Dispatcher; }
|
||||
|
||||
|
||||
struct Retro_frontend::Dispatcher
|
||||
{
|
||||
/***************
|
||||
** Game data **
|
||||
***************/
|
||||
|
||||
struct Game_failed { };
|
||||
|
||||
retro_game_info game_info;
|
||||
|
||||
typedef Genode::String<64> Rom_name;
|
||||
typedef Genode::String<128> Game_path;
|
||||
Rom_name rom_name;
|
||||
Game_path game_path;
|
||||
Game_path game_meta;
|
||||
|
||||
/**
|
||||
* Class to contain game ROM and RAM
|
||||
*/
|
||||
struct Cartridge {
|
||||
|
||||
Genode::Attached_rom_dataspace rom;
|
||||
|
||||
typedef Genode::String<128> Filename;
|
||||
|
||||
Filename const save_filename;
|
||||
Filename const rtc_filename;
|
||||
|
||||
Memory_file save_file {
|
||||
RETRO_MEMORY_SAVE_RAM, save_filename.string() };
|
||||
Memory_file rtc_file {
|
||||
RETRO_MEMORY_RTC, rtc_filename.string() };
|
||||
|
||||
Cartridge(Rom_name const &rom_name)
|
||||
:
|
||||
rom(*genv, rom_name.string()),
|
||||
save_filename( "/", rom_name.string(), ".save"),
|
||||
rtc_filename( "/", rom_name.string(), ".rtc")
|
||||
{
|
||||
load_memory();
|
||||
}
|
||||
|
||||
~Cartridge() { save_memory(); }
|
||||
|
||||
void refresh(Memory_file &memory)
|
||||
{
|
||||
void *data = retro_get_memory_data(memory.id);
|
||||
auto size = retro_get_memory_size(memory.id);
|
||||
if (data && size) {
|
||||
memory.data = data;
|
||||
memory.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
void load_memory()
|
||||
{
|
||||
Genode::log("loading RAM from ", save_filename);
|
||||
refresh(save_file);
|
||||
refresh(rtc_file);
|
||||
|
||||
save_file.read();
|
||||
rtc_file.read();
|
||||
}
|
||||
|
||||
void save_memory()
|
||||
{
|
||||
Genode::log("saving RAM to ", save_filename);
|
||||
refresh(save_file);
|
||||
save_file.write();
|
||||
|
||||
refresh(rtc_file);
|
||||
rtc_file.write();
|
||||
}
|
||||
};
|
||||
|
||||
Genode::Constructible<Cartridge> cartridge;
|
||||
|
||||
|
||||
/**
|
||||
* Run core initialization
|
||||
*/
|
||||
void init_core(Genode::Xml_node const &config)
|
||||
{
|
||||
if (framebuffer.constructed())
|
||||
framebuffer.destruct();
|
||||
|
||||
if (stereo_out.constructed())
|
||||
stereo_out.destruct();
|
||||
|
||||
retro_system_info sys_info;
|
||||
|
||||
/****************
|
||||
** Initialize **
|
||||
****************/
|
||||
{
|
||||
retro_get_system_info(&sys_info);
|
||||
|
||||
Genode::log("Name: ", sys_info.library_name,
|
||||
"\nVersion: ", sys_info.library_version,
|
||||
"\nExtensions: ", sys_info.valid_extensions ?
|
||||
sys_info.valid_extensions : "");
|
||||
|
||||
/* reset keyboard callback */
|
||||
keyboard_callback = nullptr;
|
||||
retro_set_environment(environment_callback);
|
||||
|
||||
retro_init();
|
||||
}
|
||||
|
||||
|
||||
/***********************
|
||||
** Install callbacks **
|
||||
***********************/
|
||||
{
|
||||
retro_set_video_refresh(video_refresh_callback);
|
||||
|
||||
retro_set_audio_sample(audio_sample_noop);
|
||||
retro_set_audio_sample_batch(audio_sample_batch_noop);
|
||||
|
||||
retro_set_input_poll(input_poll_callback);
|
||||
retro_set_input_state(input_state_callback);
|
||||
}
|
||||
|
||||
|
||||
/******************************
|
||||
** Load game data into core **
|
||||
******************************/
|
||||
|
||||
{
|
||||
game_info.path = "";
|
||||
game_info.data = NULL;
|
||||
game_info.size = 0;
|
||||
game_info.meta = "";
|
||||
|
||||
try {
|
||||
Genode::Xml_node game_node = config.sub_node("game");
|
||||
|
||||
rom_name = game_node.attribute_value("rom", Rom_name());
|
||||
game_path = game_node.attribute_value("path", Game_path());
|
||||
game_meta = game_node.attribute_value("meta", Rom_name());
|
||||
} catch (Genode::Xml_node::Nonexistent_sub_node) {}
|
||||
|
||||
if (rom_name != "") {
|
||||
Genode::log("loading game from ROM '", rom_name, "'");
|
||||
cartridge.construct(rom_name);
|
||||
game_info.data = cartridge->rom.local_addr<const void>();
|
||||
game_info.size = cartridge->rom.size();
|
||||
} else if (game_path != "") {
|
||||
Genode::log("loading game from path '", game_path, "'");
|
||||
game_info.path = game_path.string();
|
||||
} else {
|
||||
game_info.path = "/";
|
||||
}
|
||||
|
||||
game_info.meta = game_meta.string();
|
||||
|
||||
if (sys_info.need_fullpath && (game_info.path == NULL))
|
||||
game_info.path = "/";
|
||||
|
||||
if (!(game_info.path || game_info.data)) {
|
||||
/* some cores don't need game data */
|
||||
Genode::error("no game content loaded");
|
||||
}
|
||||
|
||||
if (!(retro_load_game(&game_info))) {
|
||||
Genode::error("failed to load game data");
|
||||
throw Game_failed();
|
||||
}
|
||||
}
|
||||
|
||||
/******************
|
||||
** Get A/V info **
|
||||
******************/
|
||||
{
|
||||
retro_system_av_info av_info;
|
||||
retro_get_system_av_info(&av_info);
|
||||
|
||||
Genode::log("game geometry: ", av_info.geometry.max_width, "x", av_info.geometry.max_height);
|
||||
|
||||
Genode::log("FPS of video content: ", av_info.timing.fps, "Hz");
|
||||
Genode::log("Sampling rate of audio: ", av_info.timing.sample_rate, "Hz");
|
||||
|
||||
framebuffer.construct(av_info.geometry);
|
||||
|
||||
if (av_info.timing.sample_rate > 0.0) try {
|
||||
stereo_out.construct();
|
||||
retro_set_audio_sample(audio_sample_callback);
|
||||
retro_set_audio_sample_batch(audio_sample_batch_callback);
|
||||
|
||||
double ratio = (double)Audio_out::SAMPLE_RATE / av_info.timing.sample_rate;
|
||||
|
||||
audio_shift_factor = SHIFT_ONE / ratio;
|
||||
audio_input_period = Audio_out::PERIOD * (1.0f / ratio);
|
||||
} catch (...) {
|
||||
Genode::error("failed to initialize Audio_out sessions");
|
||||
}
|
||||
|
||||
quarter_fps = av_info.timing.fps / 4;
|
||||
fb_sync_sample_count = 0;
|
||||
|
||||
framebuffer->session.sync_sigh(fb_sync_sampler);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***********************
|
||||
** Deinitialize core **
|
||||
***********************/
|
||||
|
||||
void deinit()
|
||||
{
|
||||
/* XXX: what happens to SAVE_RAM as the game is unloaded? */
|
||||
if (cartridge.constructed())
|
||||
cartridge->save_memory();
|
||||
retro_unload_game();
|
||||
if (cartridge.constructed())
|
||||
cartridge.destruct();
|
||||
retro_deinit();
|
||||
}
|
||||
|
||||
Genode::Heap heap { genv->ram(), genv->rm() };
|
||||
|
||||
|
||||
/************
|
||||
** Config **
|
||||
************/
|
||||
|
||||
Genode::Signal_handler<Dispatcher> config_handler {
|
||||
genv->ep(), *this, &Dispatcher::handle_config };
|
||||
|
||||
void handle_config()
|
||||
{
|
||||
using namespace Genode;
|
||||
|
||||
++config_version;
|
||||
auto const config = config_rom->xml();
|
||||
|
||||
|
||||
/***********************
|
||||
** Load and map core **
|
||||
***********************/
|
||||
{
|
||||
auto const core_name = config.attribute_value("core", Core::Name());
|
||||
|
||||
if (core.constructed() && core->name != core_name) {
|
||||
deinit();
|
||||
core.destruct();
|
||||
}
|
||||
|
||||
if (!core.constructed())
|
||||
core.construct(heap, core_name);
|
||||
|
||||
Libc::with_libc([&] () { init_core(config); });
|
||||
}
|
||||
|
||||
/* initialize controller sessions and mappings */
|
||||
initialize_controllers(heap, config);
|
||||
}
|
||||
|
||||
|
||||
/*************************************************
|
||||
** Signal handler to advance core by one frame **
|
||||
*************************************************/
|
||||
|
||||
Timer::Connection timer { *genv };
|
||||
|
||||
/* switch to application context and advance the core */
|
||||
void run() { Libc::with_libc([&] () { retro_run(); }); }
|
||||
|
||||
Genode::Signal_handler<Dispatcher> core_runner
|
||||
{ genv->ep(), *this, &Dispatcher::run };
|
||||
|
||||
|
||||
/***********************************
|
||||
** Frame counting signal handler **
|
||||
***********************************/
|
||||
|
||||
int quarter_fps = 0;
|
||||
unsigned long sample_start;
|
||||
int fb_sync_sample_count = 0;
|
||||
|
||||
/**
|
||||
* Sample the framebuffer sync for a quarter-second
|
||||
* to determine if it matches the FPS
|
||||
*/
|
||||
void fb_sync_sample()
|
||||
{
|
||||
++fb_sync_sample_count;
|
||||
|
||||
if (fb_sync_sample_count == 1) {
|
||||
sample_start = timer.elapsed_ms();
|
||||
} else if (fb_sync_sample_count == quarter_fps) {
|
||||
fb_sync_sample_count = 0;
|
||||
|
||||
float sync_ms = (timer.elapsed_ms() - sample_start)/quarter_fps;
|
||||
float want_ms = 250.0 / quarter_fps;
|
||||
float diff = want_ms - sync_ms;
|
||||
|
||||
/* allow a generous two millisecond difference in FPS */
|
||||
if (diff > 2.0 || diff < -2.0) {
|
||||
framebuffer->session.sync_sigh(Genode::Signal_context_capability());
|
||||
Genode::warning("framebuffer sync unsuitable, "
|
||||
"using alternative timing source");
|
||||
timer.sigh(core_runner);
|
||||
timer.trigger_periodic(250000 / quarter_fps);
|
||||
} else {
|
||||
Genode::log("using framebuffer sync as timing source");
|
||||
framebuffer->session.sync_sigh(core_runner);
|
||||
}
|
||||
|
||||
/* start the audio streams */
|
||||
if (stereo_out.constructed()) {
|
||||
stereo_out->start_stream();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Genode::Signal_handler<Dispatcher> fb_sync_sampler
|
||||
{ genv->ep(), *this, &Dispatcher::fb_sync_sample };
|
||||
|
||||
|
||||
/***********
|
||||
** Pause **
|
||||
***********/
|
||||
|
||||
bool paused = false;
|
||||
|
||||
void poll_controllers()
|
||||
{
|
||||
/* this callback pauses and unpauses the frontend */
|
||||
Libc::with_libc([] () { input_poll_callback(); });
|
||||
}
|
||||
|
||||
Genode::Signal_handler<Dispatcher> unpause_handler
|
||||
{ genv->ep(), *this, &Dispatcher::poll_controllers };
|
||||
|
||||
void pause()
|
||||
{
|
||||
paused = true;
|
||||
|
||||
/* save the game RAM */
|
||||
if (cartridge.constructed())
|
||||
cartridge->save_memory();
|
||||
|
||||
/* let the audio buffer drain and stop */
|
||||
if (stereo_out.constructed())
|
||||
stereo_out->stop_stream();
|
||||
|
||||
/* disable the framebuffer signals */
|
||||
framebuffer->session.sync_sigh(
|
||||
Genode::Signal_context_capability());
|
||||
|
||||
/* disable the timer signals */
|
||||
timer.sigh(Genode::Signal_context_capability());
|
||||
|
||||
/* install a handler to unpause from the controllers */
|
||||
input_sigh(unpause_handler);
|
||||
}
|
||||
|
||||
void unpause()
|
||||
{
|
||||
paused = false;
|
||||
|
||||
if (cartridge.constructed())
|
||||
cartridge->load_memory();
|
||||
|
||||
/* disable the unpause signaling */
|
||||
input_sigh(Genode::Signal_context_capability());
|
||||
|
||||
/* resync and run */
|
||||
framebuffer->session.sync_sigh(fb_sync_sampler);
|
||||
}
|
||||
|
||||
void toggle_pause()
|
||||
{
|
||||
if (paused)
|
||||
unpause();
|
||||
else
|
||||
pause();
|
||||
}
|
||||
|
||||
Dispatcher()
|
||||
{
|
||||
config_rom->sigh(config_handler);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -5,50 +5,31 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 Genode Labs GmbH
|
||||
* Copyright (C) 2016-2017 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _RETRO_FRONTEND__CALLBACKS_H_
|
||||
#define _RETRO_FRONTEND__CALLBACKS_H_
|
||||
#ifndef _RETRO_FRONTEND__ENVIRONMENT_H_
|
||||
#define _RETRO_FRONTEND__ENVIRONMENT_H_
|
||||
|
||||
/* local includes */
|
||||
#include "frontend.h"
|
||||
#include "framebuffer.h"
|
||||
#include "input.h"
|
||||
#include "log.h"
|
||||
#include "core.h"
|
||||
|
||||
/* Genode includes */
|
||||
#include <os/reporter.h>
|
||||
#include <base/sleep.h>
|
||||
|
||||
/* vsnprintf */
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
static
|
||||
void log_printf_callback(Retro_frontend::retro_log_level level, const char *fmt, ...)
|
||||
{
|
||||
using namespace Retro_frontend;
|
||||
|
||||
char buf[Genode::Log_session::MAX_STRING_LEN];
|
||||
|
||||
va_list vp;
|
||||
va_start(vp, fmt);
|
||||
::vsnprintf(buf, sizeof(buf), fmt, vp);
|
||||
va_end(vp);
|
||||
|
||||
char const *msg = buf;
|
||||
|
||||
switch (level) {
|
||||
case RETRO_LOG_DEBUG: Genode::log("Debug: ", msg); return;
|
||||
case RETRO_LOG_INFO: Genode::log(msg); return;
|
||||
case RETRO_LOG_WARN: Genode::warning(msg); return;
|
||||
case RETRO_LOG_ERROR: Genode::error(msg); return;
|
||||
case RETRO_LOG_DUMMY: Genode::log("Dummy: ", msg); return;
|
||||
}
|
||||
namespace Retro_frontend {
|
||||
static unsigned variables_version = 0;
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
bool environment_callback(unsigned cmd, void *data)
|
||||
{
|
||||
using namespace Retro_frontend;
|
||||
@@ -103,24 +84,42 @@ bool environment_callback(unsigned cmd, void *data)
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS:
|
||||
{
|
||||
try {
|
||||
global_frontend->input_reporter.enabled(true);
|
||||
} catch (...) {
|
||||
static Genode::Reporter input_reporter { *genv, "inputs", "inputs", 8192 };
|
||||
|
||||
try { input_reporter.enabled(true); }
|
||||
catch (...) {
|
||||
Genode::error("input descriptors not reported");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* TODO: translate device, index, and id to a string descriptor */
|
||||
|
||||
Genode::Reporter::Xml_generator gen(global_frontend->input_reporter, [&] () {
|
||||
Genode::Reporter::Xml_generator gen(input_reporter, [&] () {
|
||||
for (const struct retro_input_descriptor *desc = (retro_input_descriptor*)data;
|
||||
(desc && (desc->description != NULL)); ++desc)
|
||||
{
|
||||
char const *device_str = "UNKNOWN";
|
||||
switch (desc->device) {
|
||||
case RETRO_DEVICE_JOYPAD:
|
||||
device_str = "JOYPAD"; break;
|
||||
case RETRO_DEVICE_MOUSE:
|
||||
device_str = "MOUSE"; break;
|
||||
case RETRO_DEVICE_KEYBOARD:
|
||||
device_str = "KEYBOARD"; break;
|
||||
case RETRO_DEVICE_LIGHTGUN:
|
||||
device_str = "LIGHTGUN"; break;
|
||||
case RETRO_DEVICE_ANALOG:
|
||||
device_str = "ANALOG"; break;
|
||||
case RETRO_DEVICE_POINTER:
|
||||
device_str = "POINTER"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
gen.node("descriptor", [&] () {
|
||||
gen.attribute("port", desc->port);
|
||||
gen.attribute("device", desc->device);
|
||||
gen.attribute("device", device_str);
|
||||
gen.attribute("index", desc->index);
|
||||
gen.attribute("id", desc->id);
|
||||
gen.attribute("id", lookup_input_text(desc->device, desc->id));
|
||||
gen.attribute("description", desc->description);
|
||||
});
|
||||
}
|
||||
@@ -130,11 +129,17 @@ bool environment_callback(unsigned cmd, void *data)
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT_SHUTDOWN:
|
||||
global_frontend->exit();
|
||||
global_frontend->env.parent().exit(0);
|
||||
shutdown();
|
||||
/* can't return here, this is a callback */
|
||||
Genode::sleep_forever();
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK:
|
||||
{
|
||||
auto *out = (retro_keyboard_callback const *)data;
|
||||
keyboard_callback = out->callback;
|
||||
return true;
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_VARIABLE:
|
||||
{
|
||||
retro_variable *out = (retro_variable*)data;
|
||||
@@ -154,8 +159,9 @@ bool environment_callback(unsigned cmd, void *data)
|
||||
}
|
||||
};
|
||||
|
||||
global_frontend->variables().for_each_sub_node("variable", f);
|
||||
config_variables().for_each_sub_node("variable", f);
|
||||
|
||||
variables_version = config_version;
|
||||
return out->value != NULL;
|
||||
}
|
||||
|
||||
@@ -165,14 +171,14 @@ bool environment_callback(unsigned cmd, void *data)
|
||||
** Report variables set by core **
|
||||
**********************************/
|
||||
|
||||
try {
|
||||
global_frontend->variable_reporter.enabled(true);
|
||||
} catch (...) {
|
||||
static Genode::Reporter variable_reporter { *genv, "variables", "variables", 8192 };
|
||||
try { variable_reporter.enabled(true); }
|
||||
catch (...) {
|
||||
Genode::error("core variables not reported");
|
||||
return false;
|
||||
}
|
||||
|
||||
Genode::Reporter::Xml_generator gen(global_frontend->variable_reporter, [&] () {
|
||||
Genode::Reporter::Xml_generator gen(variable_reporter, [&] () {
|
||||
for (const struct retro_variable *var = (retro_variable*)data;
|
||||
(var && (var->key != NULL) && (var->value != NULL)); ++var)
|
||||
{
|
||||
@@ -187,14 +193,13 @@ bool environment_callback(unsigned cmd, void *data)
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE:
|
||||
*((bool *)data) = (variables_version != config_version);
|
||||
return true;
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME:
|
||||
return false;
|
||||
|
||||
//case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME:
|
||||
// global_frontend->core.supports_null_load = data;
|
||||
// return true;
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE:
|
||||
Genode::warning("Genode rumble interface not implemented");
|
||||
return false;
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_LOG_INTERFACE:
|
||||
@@ -206,14 +211,14 @@ bool environment_callback(unsigned cmd, void *data)
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO:
|
||||
{
|
||||
try {
|
||||
global_frontend->subsystem_reporter.enabled(true);
|
||||
} catch (...) {
|
||||
static Genode::Reporter subsystem_reporter { *genv, "subsystems", "subsystems", 8192 };
|
||||
try { subsystem_reporter.enabled(true); }
|
||||
catch (...) {
|
||||
Genode::error("core subsystems not reported");
|
||||
return false;
|
||||
}
|
||||
|
||||
Genode::Reporter::Xml_generator gen(global_frontend->subsystem_reporter, [&] () {
|
||||
Genode::Reporter::Xml_generator gen(subsystem_reporter, [&] () {
|
||||
for (const retro_subsystem_info *info = (retro_subsystem_info*)data;
|
||||
(info && (info->desc)); ++info)
|
||||
{
|
||||
@@ -248,14 +253,16 @@ bool environment_callback(unsigned cmd, void *data)
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO:
|
||||
{
|
||||
try {
|
||||
global_frontend->controller_reporter.enabled(true);
|
||||
} catch (...) {
|
||||
Genode::error("core controllers not reported");
|
||||
return false;
|
||||
controller_info = (const retro_controller_info*)data;
|
||||
|
||||
static Genode::Reporter controller_reporter { *genv, "controllers" };
|
||||
try { controller_reporter.enabled(true); }
|
||||
catch (...) {
|
||||
Genode::error("controller info not reported");
|
||||
return true;
|
||||
}
|
||||
|
||||
Genode::Reporter::Xml_generator gen(global_frontend->controller_reporter, [&] () {
|
||||
Genode::Reporter::Xml_generator gen(controller_reporter, [&] () {
|
||||
unsigned index = 0;
|
||||
for (const retro_controller_info *info = (retro_controller_info*)data;
|
||||
(info && (info->types)); ++info)
|
||||
@@ -307,50 +314,4 @@ bool environment_callback(unsigned cmd, void *data)
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
void video_refresh_callback(const void *data,
|
||||
unsigned src_width, unsigned src_height,
|
||||
size_t src_pitch)
|
||||
{
|
||||
if (!framebuffer.constructed())
|
||||
return; /* tyrquake does this during 'retro_load_game' */
|
||||
|
||||
using namespace Retro_frontend;
|
||||
using namespace Genode;
|
||||
|
||||
if (data == NULL) /* frame duping? */ {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t const *src = (uint8_t const*)data;
|
||||
uint8_t *dst = framebuffer->ds.local_addr<uint8_t>();
|
||||
|
||||
unsigned const dst_width = framebuffer->mode.width();
|
||||
unsigned const dst_height = framebuffer->mode.height();
|
||||
|
||||
unsigned const width = min(src_width, dst_width);
|
||||
unsigned const height = min(src_height, dst_height);
|
||||
|
||||
if (dst != src) {
|
||||
::size_t dst_pitch = dst_width<<1; /* x2 */
|
||||
|
||||
for (unsigned y = 0; y < height; ++y)
|
||||
memcpy(&dst[y*dst_pitch], &src[y*src_pitch], dst_pitch);
|
||||
}
|
||||
|
||||
framebuffer->session.refresh(0, 0, width, height);
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
void input_poll_callback() { global_frontend->flush_input(); }
|
||||
|
||||
|
||||
static
|
||||
int16_t input_state_callback(unsigned port, unsigned device,
|
||||
unsigned index, unsigned id)
|
||||
{
|
||||
return global_frontend->controller.event(device, index, id);
|
||||
}
|
||||
|
||||
#endif
|
||||
90
src/app/retro_frontend/framebuffer.h
Normal file
90
src/app/retro_frontend/framebuffer.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* \brief Libretro framebuffer
|
||||
* \author Emery Hemingway
|
||||
* \date 2017-11-03
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2017 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _RETRO_FRONTEND__FRAMEBUFFER_H_
|
||||
#define _RETRO_FRONTEND__FRAMEBUFFER_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <framebuffer_session/connection.h>
|
||||
#include <base/attached_dataspace.h>
|
||||
#include <log_session/log_session.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
namespace Retro_frontend { struct Framebuffer; }
|
||||
|
||||
struct Retro_frontend::Framebuffer
|
||||
{
|
||||
::Framebuffer::Connection session;
|
||||
|
||||
::Framebuffer::Mode mode;
|
||||
|
||||
Genode::Attached_dataspace ds;
|
||||
|
||||
Genode::Signal_handler<Framebuffer> mode_handler
|
||||
{ genv->ep(), *this, &Framebuffer::update_mode };
|
||||
|
||||
void update_mode()
|
||||
{
|
||||
mode = session.mode();
|
||||
|
||||
/* shutdown if the framebuffer is reduced to nil */
|
||||
if ((mode.width() == 0) && (mode.height() == 0))
|
||||
Libc::with_libc([&] () { shutdown(); });
|
||||
}
|
||||
|
||||
Framebuffer(retro_game_geometry geom)
|
||||
: session(*genv, ::Framebuffer::Mode(
|
||||
geom.base_width, geom.base_height,
|
||||
::Framebuffer::Mode::RGB565)),
|
||||
ds(genv->rm(), session.dataspace())
|
||||
{
|
||||
session.mode_sigh(mode_handler);
|
||||
update_mode();
|
||||
}
|
||||
};
|
||||
|
||||
namespace Retro_frontend {
|
||||
static Genode::Constructible<Framebuffer> framebuffer;
|
||||
}
|
||||
|
||||
|
||||
void video_refresh_callback(const void *data,
|
||||
unsigned src_width, unsigned src_height,
|
||||
size_t src_pitch)
|
||||
{
|
||||
using namespace Retro_frontend;
|
||||
using namespace Genode;
|
||||
|
||||
if (!framebuffer.constructed() || data == NULL) return;
|
||||
|
||||
uint8_t const *src = (uint8_t const*)data;
|
||||
uint8_t *dst = framebuffer->ds.local_addr<uint8_t>();
|
||||
|
||||
unsigned const dst_width = framebuffer->mode.width();
|
||||
unsigned const dst_height = framebuffer->mode.height();
|
||||
|
||||
unsigned const width = min(src_width, dst_width);
|
||||
unsigned const height = min(src_height, dst_height);
|
||||
|
||||
if (dst != src) {
|
||||
::size_t dst_pitch = dst_width<<1; /* x2 */
|
||||
|
||||
for (unsigned y = 0; y < height; ++y)
|
||||
memcpy(&dst[y*dst_pitch], &src[y*src_pitch], dst_pitch);
|
||||
}
|
||||
|
||||
framebuffer->session.refresh(0, 0, width, height);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,450 +0,0 @@
|
||||
/*
|
||||
* \brief Interface to Genode services
|
||||
* \author Emery Hemingway
|
||||
* \date 2016-07-14
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _RETRO_FRONTEND__FRONTEND_H_
|
||||
#define _RETRO_FRONTEND__FRONTEND_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <audio_out_session/connection.h>
|
||||
#include <input_session/connection.h>
|
||||
#include <input/event_queue.h>
|
||||
#include <framebuffer_session/connection.h>
|
||||
#include <timer_session/connection.h>
|
||||
#include <os/reporter.h>
|
||||
#include <base/attached_rom_dataspace.h>
|
||||
#include <base/shared_object.h>
|
||||
#include <base/heap.h>
|
||||
#include <base/component.h>
|
||||
#include <base/log.h>
|
||||
#include <log_session/log_session.h>
|
||||
|
||||
/* Libc includes */
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Local includes */
|
||||
#include "audio.h"
|
||||
#include "input.h"
|
||||
|
||||
namespace Retro_frontend {
|
||||
#include <libretro.h>
|
||||
struct Frontend;
|
||||
struct Framebuffer;
|
||||
}
|
||||
|
||||
/* Global frontend instance */
|
||||
static Retro_frontend::Frontend *global_frontend = nullptr;
|
||||
|
||||
struct Retro_frontend::Framebuffer
|
||||
{
|
||||
::Framebuffer::Connection session;
|
||||
|
||||
::Framebuffer::Mode mode;
|
||||
|
||||
Genode::Attached_dataspace ds;
|
||||
|
||||
Framebuffer(Genode::Env &env, ::Framebuffer::Mode mode)
|
||||
: session(env, mode), ds(env.rm(), session.dataspace())
|
||||
{ update_mode(); }
|
||||
|
||||
void update_mode() { mode = session.mode(); }
|
||||
};
|
||||
|
||||
|
||||
static Genode::Constructible<Retro_frontend::Framebuffer> framebuffer;
|
||||
|
||||
|
||||
/**
|
||||
* Object to encapsulate Genode services
|
||||
*/
|
||||
struct Retro_frontend::Frontend
|
||||
{
|
||||
typedef void (*Retro_set_environment)(retro_environment_t);
|
||||
typedef void (*Retro_set_video_refresh)(retro_video_refresh_t);
|
||||
typedef void (*Retro_set_audio_sample)(retro_audio_sample_t);
|
||||
typedef void (*Retro_set_audio_sample_batch)(retro_audio_sample_batch_t);
|
||||
typedef void (*Retro_set_input_poll)(retro_input_poll_t);
|
||||
typedef void (*Retro_set_input_state)(retro_input_state_t);
|
||||
typedef void (*Retro_init)(void);
|
||||
typedef void (*Retro_deinit)(void);
|
||||
typedef unsigned (*Retro_api_version)(void);
|
||||
typedef void (*Retro_get_system_info)(struct retro_system_info *info);
|
||||
typedef void (*Retro_get_system_av_info)(struct retro_system_av_info *info);
|
||||
typedef void (*Retro_set_controller_port_device)(unsigned port, unsigned device);
|
||||
typedef void (*Retro_reset)(void);
|
||||
typedef void (*Retro_run)(void);
|
||||
typedef size_t (*Retro_serialize_size)(void);
|
||||
typedef bool (*Retro_serialize)(void *data, size_t size);
|
||||
typedef bool (*Retro_unserialize)(const void *data, size_t size);
|
||||
typedef void (*Retro_cheat_reset)(void);
|
||||
typedef void (*Retro_cheat_set)(unsigned index, bool enabled, const char *code);
|
||||
typedef bool (*Retro_load_game)(const struct retro_game_info *game);
|
||||
typedef bool (*Retro_load_game_special)(
|
||||
unsigned game_type,
|
||||
const struct retro_game_info *info, size_t num_info
|
||||
);
|
||||
typedef void (*Retro_unload_game)(void);
|
||||
typedef unsigned (*Retro_get_region)(void);
|
||||
typedef void *(*Retro_get_memory_data)(unsigned id);
|
||||
typedef size_t (*Retro_get_memory_size)(unsigned id);
|
||||
|
||||
Libc::Env &env;
|
||||
|
||||
Genode::Attached_rom_dataspace config_rom { env, "config" };
|
||||
|
||||
Genode::Constructible<Genode::Attached_rom_dataspace> var_rom;
|
||||
|
||||
Genode::Heap heap { env.ram(), env.rm() };
|
||||
|
||||
typedef Genode::String<128> Name;
|
||||
|
||||
Name const name { config_rom.xml().attribute_value("core", Name()) };
|
||||
|
||||
struct Dynamic_core : Genode::Shared_object
|
||||
{
|
||||
|
||||
Dynamic_core(Genode::Env &env, Genode::Allocator &alloc, Name const &name)
|
||||
: Genode::Shared_object(env, alloc, name.string(), BIND_LAZY, DONT_KEEP)
|
||||
{
|
||||
unsigned api_version = lookup<Retro_api_version>
|
||||
("retro_api_version")();
|
||||
if (api_version != RETRO_API_VERSION) {
|
||||
Genode::error("core ", name.string(),
|
||||
" uses unsupported API version ", api_version);
|
||||
throw Genode::Shared_object::Invalid_rom_module();
|
||||
}
|
||||
}
|
||||
|
||||
} shared_object { env, heap, name };
|
||||
|
||||
Retro_init retro_init =
|
||||
shared_object.lookup<Retro_init>("retro_init");
|
||||
Retro_deinit retro_deinit =
|
||||
shared_object.lookup<Retro_deinit>("retro_deinit");
|
||||
|
||||
Retro_load_game retro_load_game =
|
||||
shared_object.lookup<Retro_load_game>("retro_load_game");
|
||||
Retro_unload_game retro_unload_game =
|
||||
shared_object.lookup<Retro_unload_game>("retro_unload_game");
|
||||
|
||||
Retro_get_memory_data retro_get_memory_data =
|
||||
shared_object.lookup<Retro_get_memory_data>("retro_get_memory_data");
|
||||
Retro_get_memory_size retro_get_memory_size =
|
||||
shared_object.lookup<Retro_get_memory_size>("retro_get_memory_size");
|
||||
|
||||
|
||||
/***************
|
||||
** Game data **
|
||||
***************/
|
||||
|
||||
typedef Genode::String<64> Rom_name;
|
||||
typedef Genode::String<128> Game_path;
|
||||
Rom_name rom_name;
|
||||
Game_path game_path;
|
||||
Rom_name rom_meta;
|
||||
|
||||
Genode::Constructible<Genode::Attached_rom_dataspace> game_rom;
|
||||
|
||||
retro_game_info game_info;
|
||||
|
||||
/******************************
|
||||
** Persistent memory states **
|
||||
******************************/
|
||||
|
||||
struct Memory_file
|
||||
{
|
||||
void *data = nullptr;
|
||||
size_t size = 0;
|
||||
unsigned const id;
|
||||
char const *path;
|
||||
int fd = -1;
|
||||
|
||||
Genode::size_t file_size()
|
||||
{
|
||||
struct stat s;
|
||||
s.st_size = 0;
|
||||
stat(path, &s);
|
||||
return s.st_size;
|
||||
}
|
||||
|
||||
bool open_file_for_data()
|
||||
{
|
||||
if (!(data && size))
|
||||
return false;
|
||||
|
||||
if (fd == -1)
|
||||
fd = ::open(path, O_RDWR|O_CREAT);
|
||||
return fd != -1;
|
||||
}
|
||||
|
||||
Memory_file(unsigned id, char const *filename)
|
||||
: id(id), path(filename)
|
||||
{ }
|
||||
|
||||
~Memory_file() { if (fd != -1) close(fd); }
|
||||
|
||||
void read()
|
||||
{
|
||||
if (open_file_for_data()) {
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
size_t remain = Genode::min(size, file_size());
|
||||
size_t offset = 0;
|
||||
do {
|
||||
ssize_t n = ::read(fd, ((char*)data)+offset, remain);
|
||||
if (n == -1) {
|
||||
Genode::error("failed to read from ", path);
|
||||
break;
|
||||
}
|
||||
remain -= n;
|
||||
offset += n;
|
||||
} while (remain);
|
||||
}
|
||||
}
|
||||
|
||||
void write()
|
||||
{
|
||||
if (open_file_for_data()) {
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
ftruncate(fd, size);
|
||||
size_t remain = size;
|
||||
size_t offset = 0;
|
||||
do {
|
||||
ssize_t n = ::write(fd, ((char const *)data)+offset, remain);
|
||||
if (n == -1) {
|
||||
Genode::error("failed to write to ", path);
|
||||
break;
|
||||
}
|
||||
remain -= n;
|
||||
offset += n;
|
||||
} while (remain);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Memory_file save_file { RETRO_MEMORY_SAVE_RAM, "save" };
|
||||
Memory_file rtc_file { RETRO_MEMORY_RTC, "rtc" };
|
||||
Memory_file system_file { RETRO_MEMORY_SYSTEM_RAM, "system" };
|
||||
|
||||
void refresh(Memory_file &memory)
|
||||
{
|
||||
memory.data = retro_get_memory_data(memory.id);
|
||||
memory.size = retro_get_memory_size(memory.id);
|
||||
}
|
||||
|
||||
void load_memory()
|
||||
{
|
||||
refresh(save_file);
|
||||
refresh(rtc_file);
|
||||
refresh(system_file);
|
||||
|
||||
save_file.read();
|
||||
rtc_file.read();
|
||||
system_file.read();
|
||||
}
|
||||
|
||||
void save_memory()
|
||||
{
|
||||
refresh(save_file);
|
||||
save_file.write();
|
||||
|
||||
refresh(rtc_file);
|
||||
rtc_file.write();
|
||||
|
||||
refresh(system_file);
|
||||
system_file.write();
|
||||
}
|
||||
|
||||
/******************
|
||||
** Fronted exit **
|
||||
******************/
|
||||
|
||||
void exit()
|
||||
{
|
||||
save_memory();
|
||||
retro_unload_game();
|
||||
retro_deinit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit when the framebuffer is resized to zero
|
||||
*/
|
||||
void handle_mode()
|
||||
{
|
||||
framebuffer->update_mode();
|
||||
if ((framebuffer->mode.width() == 0) &&
|
||||
(framebuffer->mode.height() == 0))
|
||||
{
|
||||
Genode::error("zero framebuffer mode received, exiting");
|
||||
Libc::with_libc([&] () { exit(); });
|
||||
env.parent().exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
Genode::Signal_handler<Frontend> mode_handler
|
||||
{ env.ep(), *this, &Frontend::handle_mode };
|
||||
|
||||
/*************************************************
|
||||
** Signal handler to advance core by one frame **
|
||||
*************************************************/
|
||||
|
||||
Retro_run retro_run =
|
||||
shared_object.lookup<Retro_run>("retro_run");
|
||||
|
||||
/* switch to application context and advance the core */
|
||||
void run() { Libc::with_libc([&] () { retro_run(); }); }
|
||||
|
||||
Genode::Signal_handler<Frontend> core_runner
|
||||
{ env.ep(), *this, &Frontend::run };
|
||||
|
||||
Timer::Connection timer { env };
|
||||
|
||||
Genode::Reporter input_reporter { env, "input" };
|
||||
Genode::Reporter variable_reporter { env, "variables" };
|
||||
Genode::Reporter subsystem_reporter { env, "subsystems" };
|
||||
Genode::Reporter controller_reporter { env, "controllers" };
|
||||
|
||||
Controller controller { env };
|
||||
|
||||
int quarter_fps = 0;
|
||||
unsigned long sample_start;
|
||||
|
||||
|
||||
/***********************************
|
||||
** Frame counting signal handler **
|
||||
***********************************/
|
||||
|
||||
int fb_sync_sample_count = 0;
|
||||
|
||||
/**
|
||||
* Sample the framebuffer sync for a quarter-second
|
||||
* to determine if it matches the FPS
|
||||
*/
|
||||
void fb_sync_sample()
|
||||
{
|
||||
++fb_sync_sample_count;
|
||||
|
||||
if (fb_sync_sample_count == 1) {
|
||||
sample_start = timer.elapsed_ms();
|
||||
} else if (fb_sync_sample_count == quarter_fps) {
|
||||
fb_sync_sample_count = 0;
|
||||
|
||||
float sync_ms = (timer.elapsed_ms() - sample_start)/quarter_fps;
|
||||
float want_ms = 250.0 / quarter_fps;
|
||||
float diff = want_ms - sync_ms;
|
||||
|
||||
/* allow a generous two millisecond difference in FPS */
|
||||
if (diff > 2.0 || diff < -2.0) {
|
||||
framebuffer->session.sync_sigh(Genode::Signal_context_capability());
|
||||
Genode::warning("framebuffer sync unsuitable, "
|
||||
"using alternative timing source");
|
||||
timer.sigh(core_runner);
|
||||
timer.trigger_periodic(250000 / quarter_fps);
|
||||
} else {
|
||||
Genode::log("using framebuffer sync as timing source");
|
||||
framebuffer->session.sync_sigh(core_runner);
|
||||
}
|
||||
|
||||
/* start the audio streams */
|
||||
if (stereo_out.constructed())
|
||||
stereo_out->start_stream();
|
||||
}
|
||||
}
|
||||
|
||||
Genode::Signal_handler<Frontend> fb_sync_sampler
|
||||
{ env.ep(), *this, &Frontend::fb_sync_sample };
|
||||
|
||||
|
||||
/***************
|
||||
** Unpausing **
|
||||
***************/
|
||||
|
||||
void handle_input()
|
||||
{
|
||||
if (controller.unpaused()) {
|
||||
controller.input.sigh(Genode::Signal_context_capability());
|
||||
fb_sync_sample_count = 0;
|
||||
framebuffer->session.sync_sigh(fb_sync_sampler);
|
||||
}
|
||||
}
|
||||
|
||||
Genode::Signal_handler<Frontend> resume_handler
|
||||
{ env.ep(), *this, &Frontend::handle_input };
|
||||
|
||||
Frontend(Libc::Env &env);
|
||||
|
||||
~Frontend()
|
||||
{
|
||||
exit();
|
||||
global_frontend = nullptr;
|
||||
}
|
||||
|
||||
void set_av_info(retro_system_av_info &av_info)
|
||||
{
|
||||
using namespace Retro_frontend;
|
||||
|
||||
framebuffer.construct(
|
||||
env, ::Framebuffer::Mode(av_info.geometry.base_width,
|
||||
av_info.geometry.base_height,
|
||||
::Framebuffer::Mode::RGB565));
|
||||
|
||||
framebuffer->session.mode_sigh(mode_handler);
|
||||
|
||||
if (stereo_out.constructed()) {
|
||||
double ratio = (double)Audio_out::SAMPLE_RATE / av_info.timing.sample_rate;
|
||||
|
||||
audio_shift_factor = SHIFT_ONE / ratio;
|
||||
audio_input_period = Audio_out::PERIOD * (1.0f / ratio);
|
||||
}
|
||||
|
||||
quarter_fps = av_info.timing.fps / 4;
|
||||
fb_sync_sample_count = 0;
|
||||
|
||||
framebuffer->session.sync_sigh(fb_sync_sampler);
|
||||
}
|
||||
|
||||
void flush_input()
|
||||
{
|
||||
controller.flush();
|
||||
if (controller.paused) {
|
||||
/* unset signal handler for rendering */
|
||||
framebuffer->session.sync_sigh(Genode::Signal_context_capability());
|
||||
timer.sigh(Genode::Signal_context_capability());
|
||||
|
||||
/* stop playpack */
|
||||
if (stereo_out.constructed())
|
||||
stereo_out->stop_stream();
|
||||
|
||||
/* set signal handler for unpausing */
|
||||
controller.input.sigh(resume_handler);
|
||||
|
||||
/* a good time to flush memory to the FS */
|
||||
Libc::with_libc([&] () { save_memory(); });
|
||||
}
|
||||
}
|
||||
|
||||
Genode::Xml_node variables()
|
||||
{
|
||||
try {
|
||||
if (!var_rom.constructed())
|
||||
var_rom.construct(env, "variables");
|
||||
return var_rom->xml();
|
||||
} catch (...) {
|
||||
return Genode::Xml_node("<variables/>");
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -14,273 +14,375 @@
|
||||
#ifndef _RETRO_FRONTEND__INPUT_H_
|
||||
#define _RETRO_FRONTEND__INPUT_H_
|
||||
|
||||
/* local includes */
|
||||
#include "core.h"
|
||||
|
||||
/* Genode includes */
|
||||
#include <input_session/connection.h>
|
||||
#include <input/event_queue.h>
|
||||
|
||||
#include "input_maps.h"
|
||||
|
||||
namespace Retro_frontend {
|
||||
|
||||
#include <libretro.h>
|
||||
|
||||
struct Controller;
|
||||
const retro_controller_info *controller_info = nullptr;
|
||||
|
||||
static unsigned genode_map[Input::Keycode::KEY_MAX];
|
||||
/* callback to feed keyboard events to core */
|
||||
static retro_keyboard_event_t keyboard_callback;
|
||||
|
||||
/* Genode keyboard mapped to Libretro keyboard */
|
||||
static retro_key key_map[Input::Keycode::KEY_MAX];
|
||||
|
||||
/* array of currently configured controllers */
|
||||
enum { PORT_MAX = 7 };
|
||||
struct Controller;
|
||||
Controller *controllers[PORT_MAX+1] = { nullptr, };
|
||||
|
||||
typedef Genode::String<32> Keyname;
|
||||
|
||||
int lookup_genode_code(Keyname const &name)
|
||||
{
|
||||
using namespace Input;
|
||||
|
||||
/* not the fastest way to do this, just the most terse */
|
||||
for (int code = 0; code < Input::Keycode::KEY_MAX; ++code)
|
||||
if (name == key_name((Input::Keycode)code)) return code;
|
||||
return Input::KEY_UNKNOWN;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct Retro_frontend::Controller
|
||||
{
|
||||
enum {
|
||||
RETRO_JOYPAD_MAX = RETRO_DEVICE_ID_JOYPAD_R3+1,
|
||||
RETRO_MOUSE_MAX = RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN
|
||||
};
|
||||
bool input_state[Input::Keycode::KEY_MAX];
|
||||
|
||||
int16_t joypad_state[RETRO_JOYPAD_MAX];
|
||||
int16_t mouse_state[RETRO_MOUSE_MAX];
|
||||
int16_t keyboard_state[RETROK_LAST];
|
||||
enum { JOYPAD_MAP_LEN = RETRO_DEVICE_ID_JOYPAD_R3 + 1 };
|
||||
int16_t joypad_map[JOYPAD_MAP_LEN];
|
||||
|
||||
Input::Connection input;
|
||||
enum { MOUSE_MAP_LEN = RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN + 1 };
|
||||
int16_t mouse_map[MOUSE_MAP_LEN];
|
||||
int16_t mouse_x = 0;
|
||||
int16_t mouse_y = 0;
|
||||
|
||||
/* TODO: analog axes */
|
||||
|
||||
Input::Connection input_conn;
|
||||
|
||||
Genode::Attached_dataspace input_ds;
|
||||
|
||||
Input::Event const * const events =
|
||||
input_ds.local_addr<Input::Event>();
|
||||
|
||||
bool paused = false;
|
||||
uint16_t keymods = RETROKMOD_NONE;
|
||||
|
||||
Controller(Genode::Env &env)
|
||||
: input(env), input_ds(env.rm(), input.dataspace())
|
||||
/**
|
||||
* Use the 'label' attribute on an input configuration as a session
|
||||
* label with a fallback to the port number.
|
||||
*/
|
||||
typedef Genode::String<Genode::Session_label::capacity()> Label;
|
||||
static Label port_label(Genode::Xml_node const &config)
|
||||
{
|
||||
Genode::memset( joypad_state, 0x00, sizeof( joypad_state));
|
||||
Genode::memset( mouse_state, 0x00, sizeof( mouse_state));
|
||||
Genode::memset(keyboard_state, 0x00, sizeof(keyboard_state));
|
||||
if (config.has_attribute("label"))
|
||||
return config.attribute_value("label", Label());
|
||||
else
|
||||
return config.attribute_value("port", Label());
|
||||
}
|
||||
|
||||
bool unpaused()
|
||||
Controller(Genode::Xml_node const &config, unsigned port)
|
||||
: input_conn(*genv, port_label(config).string()),
|
||||
input_ds(genv->rm(), input_conn.dataspace())
|
||||
{
|
||||
using namespace Input;
|
||||
|
||||
unsigned num_events = input.flush();
|
||||
Genode::memset(input_state, 0x00, sizeof(input_state));
|
||||
|
||||
for (unsigned i = 0; i < num_events; ++i) {
|
||||
Input::Event const &ev = events[i];
|
||||
if ((ev.code() == KEY_PAUSE) && (ev.type() == Event::Type::RELEASE)) {
|
||||
paused = false;
|
||||
return true;
|
||||
unsigned device = config.attribute_value("device", 0UL);
|
||||
unsigned device_class = RETRO_DEVICE_MASK & device;
|
||||
|
||||
if (controller_info) {
|
||||
/* validate that the controller conforms to the core expectations */
|
||||
bool valid = false;
|
||||
for (const retro_controller_info *info = controller_info;
|
||||
(info && (info->types)); ++info)
|
||||
{
|
||||
for (unsigned i = 0; i < info->num_types; ++i) {
|
||||
const retro_controller_description *type = &info->types[i];
|
||||
if (type->id == device) {
|
||||
valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (valid)
|
||||
retro_set_controller_port_device(port, device);
|
||||
else
|
||||
Genode::error("controller ", port, " device ", device, " is not valid for core");
|
||||
}
|
||||
return false;
|
||||
|
||||
/* set default joypad mapping */
|
||||
joypad_map[RETRO_DEVICE_ID_JOYPAD_B] = Keycode::BTN_B;
|
||||
joypad_map[RETRO_DEVICE_ID_JOYPAD_Y] = Keycode::BTN_Y;
|
||||
joypad_map[RETRO_DEVICE_ID_JOYPAD_SELECT] = Keycode::BTN_SELECT;
|
||||
joypad_map[RETRO_DEVICE_ID_JOYPAD_START] = Keycode::BTN_START;
|
||||
joypad_map[RETRO_DEVICE_ID_JOYPAD_UP] = Keycode::BTN_FORWARD;
|
||||
joypad_map[RETRO_DEVICE_ID_JOYPAD_DOWN] = Keycode::BTN_BACK;
|
||||
joypad_map[RETRO_DEVICE_ID_JOYPAD_LEFT] = Keycode::BTN_LEFT;
|
||||
joypad_map[RETRO_DEVICE_ID_JOYPAD_RIGHT] = Keycode::BTN_RIGHT;
|
||||
joypad_map[RETRO_DEVICE_ID_JOYPAD_A] = Keycode::BTN_A;
|
||||
joypad_map[RETRO_DEVICE_ID_JOYPAD_X] = Keycode::BTN_X;
|
||||
joypad_map[RETRO_DEVICE_ID_JOYPAD_L] = Keycode::BTN_TL;
|
||||
joypad_map[RETRO_DEVICE_ID_JOYPAD_R] = Keycode::BTN_TR;
|
||||
joypad_map[RETRO_DEVICE_ID_JOYPAD_L2] = Keycode::BTN_TL2;
|
||||
joypad_map[RETRO_DEVICE_ID_JOYPAD_R2] = Keycode::BTN_TR2;
|
||||
joypad_map[RETRO_DEVICE_ID_JOYPAD_L3] = Keycode::BTN_THUMBL;
|
||||
joypad_map[RETRO_DEVICE_ID_JOYPAD_R3] = Keycode::BTN_THUMBR;
|
||||
|
||||
/* set default mouse mapping */;
|
||||
mouse_map[RETRO_DEVICE_ID_MOUSE_LEFT] = Keycode::BTN_LEFT;
|
||||
mouse_map[RETRO_DEVICE_ID_MOUSE_RIGHT] = Keycode::BTN_RIGHT;
|
||||
mouse_map[RETRO_DEVICE_ID_MOUSE_WHEELUP] = Keycode::BTN_GEAR_UP;
|
||||
mouse_map[RETRO_DEVICE_ID_MOUSE_WHEELDOWN] = Keycode::BTN_GEAR_DOWN;
|
||||
mouse_map[RETRO_DEVICE_ID_MOUSE_MIDDLE] = Keycode::BTN_MIDDLE;
|
||||
|
||||
auto map_fn = [&] (Genode::Xml_node map_node) {
|
||||
using namespace Genode;
|
||||
using namespace Input;
|
||||
|
||||
Keyname const from = map_node.attribute_value("from", Keyname());
|
||||
Keyname const to = map_node.attribute_value("to", Keyname());
|
||||
|
||||
if ((from == "") || (to == "")) {
|
||||
error("ignoring ", map_node);
|
||||
return;
|
||||
}
|
||||
|
||||
int from_code = lookup_genode_code(from);
|
||||
int to_code = lookup_input_code(device_class, to.string());
|
||||
|
||||
if (from_code == KEY_UNKNOWN) {
|
||||
error("unknown key ", from.string());
|
||||
return;
|
||||
}
|
||||
if (to_code == RETROK_LAST) {
|
||||
error("unknown key ", to.string());
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* to_code and from_code are mapped reversed from the
|
||||
* frontend and oriented from the core perspective
|
||||
*/
|
||||
switch (device_class) {
|
||||
case RETRO_DEVICE_JOYPAD:
|
||||
if (to_code < JOYPAD_MAP_LEN) {
|
||||
joypad_map[to_code] = from_code;
|
||||
} else {
|
||||
Genode::error(to, " is not mapped for joypads");
|
||||
}
|
||||
break;
|
||||
case RETRO_DEVICE_MOUSE:
|
||||
if (to_code < MOUSE_MAP_LEN) {
|
||||
mouse_map[to_code] = from_code;
|
||||
} else {
|
||||
Genode::error(to, " is not mapped for mice");
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
Genode::error(map_node, " is invalid for device ",
|
||||
device, " class ", device_class);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
config.for_each_sub_node("map", map_fn);
|
||||
}
|
||||
|
||||
void flush()
|
||||
/* TODO: analog axes */
|
||||
|
||||
void poll()
|
||||
{
|
||||
using namespace Input;
|
||||
|
||||
mouse_state[RETRO_DEVICE_ID_MOUSE_X] =
|
||||
mouse_state[RETRO_DEVICE_ID_MOUSE_Y] = 0;
|
||||
|
||||
unsigned num_events = input.flush();
|
||||
unsigned num_events = input_conn.flush();
|
||||
for (unsigned i = 0; i < num_events; ++i) {
|
||||
Input::Event const &ev = events[i];
|
||||
|
||||
uint16_t v = 0; /* assume a release */
|
||||
bool v = 0; /* assume a release */
|
||||
|
||||
switch (ev.type()) {
|
||||
|
||||
case Event::Type::MOTION:
|
||||
if (ev.relative_motion()) {
|
||||
mouse_state[RETRO_DEVICE_ID_MOUSE_X] += ev.rx();
|
||||
mouse_state[RETRO_DEVICE_ID_MOUSE_Y] += ev.ry();
|
||||
} else if (ev.absolute_motion()) {
|
||||
mouse_state[RETRO_DEVICE_ID_MOUSE_X] += ev.ax();
|
||||
mouse_state[RETRO_DEVICE_ID_MOUSE_Y] += ev.ay();
|
||||
} break;
|
||||
/* TODO */
|
||||
continue;
|
||||
|
||||
case Event::Type::PRESS:
|
||||
v = 1; /* not a release */
|
||||
case Event::Type::RELEASE:
|
||||
switch (ev.code()) {
|
||||
case BTN_MIDDLE: mouse_state[RETRO_DEVICE_ID_MOUSE_MIDDLE] = v; break;
|
||||
case BTN_GEAR_DOWN: mouse_state[RETRO_DEVICE_ID_MOUSE_WHEELDOWN] = v; break;
|
||||
case BTN_GEAR_UP: mouse_state[RETRO_DEVICE_ID_MOUSE_WHEELUP] = v; break;
|
||||
|
||||
case BTN_B: joypad_state[RETRO_DEVICE_ID_JOYPAD_B] = v; break;
|
||||
case BTN_Y: joypad_state[RETRO_DEVICE_ID_JOYPAD_Y] = v; break;
|
||||
case BTN_SELECT: joypad_state[RETRO_DEVICE_ID_JOYPAD_SELECT] = v; break;
|
||||
case BTN_START: joypad_state[RETRO_DEVICE_ID_JOYPAD_START] = v; break;
|
||||
case BTN_FORWARD: joypad_state[RETRO_DEVICE_ID_JOYPAD_UP] = v; break;
|
||||
case BTN_BACK: joypad_state[RETRO_DEVICE_ID_JOYPAD_DOWN] = v; break;
|
||||
case BTN_LEFT: joypad_state[RETRO_DEVICE_ID_JOYPAD_LEFT] =
|
||||
mouse_state[RETRO_DEVICE_ID_MOUSE_LEFT] = v; break;
|
||||
case BTN_RIGHT: joypad_state[RETRO_DEVICE_ID_JOYPAD_RIGHT] =
|
||||
mouse_state[RETRO_DEVICE_ID_MOUSE_RIGHT] = v; break;
|
||||
case BTN_A: joypad_state[RETRO_DEVICE_ID_JOYPAD_A] = v; break;
|
||||
case BTN_X: joypad_state[RETRO_DEVICE_ID_JOYPAD_X] = v; break;
|
||||
case BTN_TL: joypad_state[RETRO_DEVICE_ID_JOYPAD_L] = v; break;
|
||||
case BTN_TR: joypad_state[RETRO_DEVICE_ID_JOYPAD_R] = v; break;
|
||||
case BTN_TL2: joypad_state[RETRO_DEVICE_ID_JOYPAD_L2] = v; break;
|
||||
case BTN_TR2: joypad_state[RETRO_DEVICE_ID_JOYPAD_R2] = v; break;
|
||||
case BTN_THUMBL: joypad_state[RETRO_DEVICE_ID_JOYPAD_L3] = v; break;
|
||||
case BTN_THUMBR: joypad_state[RETRO_DEVICE_ID_JOYPAD_R3] = v; break;
|
||||
case Event::Type::RELEASE: {
|
||||
int code = ev.code();
|
||||
if (code >= 0 && code < Input::KEY_MAX) {
|
||||
retro_mod mod = RETROKMOD_NONE;
|
||||
|
||||
case KEY_PAUSE: paused = true; return; /* stop handling events */
|
||||
switch (code) {
|
||||
case Input::KEY_PAUSE:
|
||||
if (!v) /* pause/unpause on key release */
|
||||
toggle_pause();
|
||||
break;
|
||||
|
||||
default:
|
||||
keyboard_state[genode_map[ev.code()]] = v; break;
|
||||
} break;
|
||||
case Input::KEY_LEFTSHIFT:
|
||||
case Input::KEY_RIGHTSHIFT:
|
||||
mod = RETROKMOD_SHIFT; break;
|
||||
|
||||
/*
|
||||
case Event::Type::FOCUS:
|
||||
if (ev.code() == 0) {
|
||||
paused = true;
|
||||
return;
|
||||
case Input::KEY_LEFTCTRL:
|
||||
case Input::KEY_RIGHTCTRL:
|
||||
mod = RETROKMOD_CTRL; break;
|
||||
|
||||
case Input::KEY_LEFTALT:
|
||||
case Input::KEY_RIGHTALT:
|
||||
mod = RETROKMOD_ALT; break;
|
||||
|
||||
case Input::KEY_LEFTMETA:
|
||||
case Input::KEY_RIGHTMETA:
|
||||
mod = RETROKMOD_META; break;
|
||||
|
||||
case Input::KEY_NUMLOCK:
|
||||
mod = RETROKMOD_NUMLOCK; break;
|
||||
|
||||
case Input::KEY_CAPSLOCK:
|
||||
mod = RETROKMOD_CAPSLOCK; break;
|
||||
|
||||
case Input::KEY_SCROLLLOCK:
|
||||
mod = RETROKMOD_SCROLLOCK; break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (mod)
|
||||
keymods = (keymods | mod) - (v ? 0 : mod);
|
||||
|
||||
input_state[code] = v;
|
||||
|
||||
if (keyboard_callback) {
|
||||
/*
|
||||
* the keyboard_callback may drive the core
|
||||
* but this context is a libretro callback
|
||||
* so 'with_libc' must be in effect
|
||||
*/
|
||||
unsigned rk = key_map[code];
|
||||
if (rk != RETROK_UNKNOWN) {
|
||||
keyboard_callback(v, rk, 0, keymods);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
case Event::Type::CHARACTER:
|
||||
if (keyboard_callback) {
|
||||
/* event only supports UTF-8? */
|
||||
keyboard_callback(v, RETROK_UNKNOWN, ev.utf8().b0, RETROKMOD_NONE);
|
||||
}
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int16_t event(unsigned device, unsigned index, unsigned id)
|
||||
{
|
||||
switch (device) {
|
||||
case RETRO_DEVICE_JOYPAD: return joypad_state[id];
|
||||
case RETRO_DEVICE_MOUSE: return mouse_state[id];
|
||||
case RETRO_DEVICE_KEYBOARD: return keyboard_state[id];
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
namespace Retro_frontend {
|
||||
|
||||
void init_keyboard_map()
|
||||
{
|
||||
using namespace Input;
|
||||
for (unsigned i = 0; i < Input::Keycode::KEY_MAX; ++i) switch (i) {
|
||||
case KEY_ESC: genode_map[i] = RETROK_ESCAPE; break;
|
||||
case KEY_1: genode_map[i] = RETROK_1; break;
|
||||
case KEY_2: genode_map[i] = RETROK_2; break;
|
||||
case KEY_3: genode_map[i] = RETROK_3; break;
|
||||
case KEY_4: genode_map[i] = RETROK_4; break;
|
||||
case KEY_5: genode_map[i] = RETROK_5; break;
|
||||
case KEY_6: genode_map[i] = RETROK_6; break;
|
||||
case KEY_7: genode_map[i] = RETROK_7; break;
|
||||
case KEY_8: genode_map[i] = RETROK_8; break;
|
||||
case KEY_9: genode_map[i] = RETROK_9; break;
|
||||
case KEY_0: genode_map[i] = RETROK_0; break;
|
||||
case KEY_MINUS: genode_map[i] = RETROK_MINUS; break;
|
||||
case KEY_EQUAL: genode_map[i] = RETROK_EQUALS; break;
|
||||
case KEY_BACKSPACE: genode_map[i] = RETROK_BACKSPACE; break;
|
||||
case KEY_TAB: genode_map[i] = RETROK_TAB; break;
|
||||
case KEY_Q: genode_map[i] = RETROK_q; break;
|
||||
case KEY_W: genode_map[i] = RETROK_w; break;
|
||||
case KEY_E: genode_map[i] = RETROK_e; break;
|
||||
case KEY_R: genode_map[i] = RETROK_r; break;
|
||||
case KEY_T: genode_map[i] = RETROK_t; break;
|
||||
case KEY_Y: genode_map[i] = RETROK_y; break;
|
||||
case KEY_U: genode_map[i] = RETROK_u; break;
|
||||
case KEY_I: genode_map[i] = RETROK_i; break;
|
||||
case KEY_O: genode_map[i] = RETROK_o; break;
|
||||
case KEY_P: genode_map[i] = RETROK_p; break;
|
||||
case KEY_LEFTBRACE: genode_map[i] = RETROK_LEFTBRACKET; break;
|
||||
case KEY_RIGHTBRACE: genode_map[i] = RETROK_RIGHTBRACKET; break;
|
||||
case KEY_ENTER: genode_map[i] = RETROK_RETURN; break;
|
||||
case KEY_LEFTCTRL: genode_map[i] = RETROK_LCTRL; break;
|
||||
case KEY_A: genode_map[i] = RETROK_a; break;
|
||||
case KEY_S: genode_map[i] = RETROK_s; break;
|
||||
case KEY_D: genode_map[i] = RETROK_d; break;
|
||||
case KEY_F: genode_map[i] = RETROK_f; break;
|
||||
case KEY_G: genode_map[i] = RETROK_g; break;
|
||||
case KEY_H: genode_map[i] = RETROK_h; break;
|
||||
case KEY_J: genode_map[i] = RETROK_j; break;
|
||||
case KEY_K: genode_map[i] = RETROK_k; break;
|
||||
case KEY_L: genode_map[i] = RETROK_l; break;
|
||||
case KEY_SEMICOLON: genode_map[i] = RETROK_SEMICOLON; break;
|
||||
case KEY_APOSTROPHE: genode_map[i] = RETROK_QUOTE; break;
|
||||
case KEY_GRAVE: genode_map[i] = RETROK_BACKQUOTE; break;
|
||||
case KEY_LEFTSHIFT: genode_map[i] = RETROK_LSHIFT; break;
|
||||
case KEY_BACKSLASH: genode_map[i] = RETROK_BACKSLASH; break;
|
||||
case KEY_Z: genode_map[i] = RETROK_z; break;
|
||||
case KEY_X: genode_map[i] = RETROK_x; break;
|
||||
case KEY_C: genode_map[i] = RETROK_c; break;
|
||||
case KEY_V: genode_map[i] = RETROK_v; break;
|
||||
case KEY_B: genode_map[i] = RETROK_b; break;
|
||||
case KEY_N: genode_map[i] = RETROK_n; break;
|
||||
case KEY_M: genode_map[i] = RETROK_m; break;
|
||||
case KEY_COMMA: genode_map[i] = RETROK_COMMA; break;
|
||||
case KEY_DOT: genode_map[i] = RETROK_PERIOD; break;
|
||||
case KEY_SLASH: genode_map[i] = RETROK_SLASH; break;
|
||||
case KEY_RIGHTSHIFT: genode_map[i] = RETROK_RSHIFT; break;
|
||||
case KEY_KPASTERISK: genode_map[i] = RETROK_KP_MULTIPLY; break;
|
||||
case KEY_LEFTALT: genode_map[i] = RETROK_LALT; break;
|
||||
case KEY_SPACE: genode_map[i] = RETROK_SPACE; break;
|
||||
case KEY_CAPSLOCK: genode_map[i] = RETROK_CAPSLOCK; break;
|
||||
case KEY_F1: genode_map[i] = RETROK_F1; break;
|
||||
case KEY_F2: genode_map[i] = RETROK_F2; break;
|
||||
case KEY_F3: genode_map[i] = RETROK_F3; break;
|
||||
case KEY_F4: genode_map[i] = RETROK_F4; break;
|
||||
case KEY_F5: genode_map[i] = RETROK_F5; break;
|
||||
case KEY_F6: genode_map[i] = RETROK_F6; break;
|
||||
case KEY_F7: genode_map[i] = RETROK_F7; break;
|
||||
case KEY_F8: genode_map[i] = RETROK_F8; break;
|
||||
case KEY_F9: genode_map[i] = RETROK_F9; break;
|
||||
case KEY_F10: genode_map[i] = RETROK_F10; break;
|
||||
case KEY_NUMLOCK: genode_map[i] = RETROK_NUMLOCK; break;
|
||||
case KEY_SCROLLLOCK: genode_map[i] = RETROK_SCROLLOCK; break;
|
||||
case KEY_KP7: genode_map[i] = RETROK_KP7; break;
|
||||
case KEY_KP8: genode_map[i] = RETROK_KP8; break;
|
||||
case KEY_KP9: genode_map[i] = RETROK_KP9; break;
|
||||
case KEY_KPMINUS: genode_map[i] = RETROK_KP_MINUS; break;
|
||||
case KEY_KP4: genode_map[i] = RETROK_KP4; break;
|
||||
case KEY_KP5: genode_map[i] = RETROK_KP5; break;
|
||||
case KEY_KP6: genode_map[i] = RETROK_KP6; break;
|
||||
case KEY_KPPLUS: genode_map[i] = RETROK_KP_PLUS; break;
|
||||
case KEY_KP1: genode_map[i] = RETROK_KP1; break;
|
||||
case KEY_KP2: genode_map[i] = RETROK_KP2; break;
|
||||
case KEY_KP3: genode_map[i] = RETROK_KP3; break;
|
||||
case KEY_KP0: genode_map[i] = RETROK_KP0; break;
|
||||
case KEY_KPDOT: genode_map[i] = RETROK_KP_PERIOD; break;
|
||||
void initialize_controllers(Genode::Allocator &alloc,
|
||||
Genode::Xml_node config)
|
||||
{
|
||||
using namespace Genode;
|
||||
|
||||
case KEY_F11: genode_map[i] = RETROK_F11; break;
|
||||
case KEY_F12: genode_map[i] = RETROK_F12; break;
|
||||
case KEY_KPENTER: genode_map[i] = RETROK_KP_ENTER; break;
|
||||
case KEY_RIGHTCTRL: genode_map[i] = RETROK_RCTRL; break;
|
||||
case KEY_KPSLASH: genode_map[i] = RETROK_KP_DIVIDE; break;
|
||||
case KEY_SYSRQ: genode_map[i] = RETROK_SYSREQ; break;
|
||||
case KEY_RIGHTALT: genode_map[i] = RETROK_RALT; break;
|
||||
case KEY_HOME: genode_map[i] = RETROK_HOME; break;
|
||||
case KEY_UP: genode_map[i] = RETROK_UP; break;
|
||||
case KEY_PAGEUP: genode_map[i] = RETROK_PAGEUP; break;
|
||||
case KEY_LEFT: genode_map[i] = RETROK_LEFT; break;
|
||||
case KEY_RIGHT: genode_map[i] = RETROK_RIGHT; break;
|
||||
case KEY_END: genode_map[i] = RETROK_END; break;
|
||||
case KEY_DOWN: genode_map[i] = RETROK_DOWN; break;
|
||||
case KEY_PAGEDOWN: genode_map[i] = RETROK_PAGEDOWN; break;
|
||||
case KEY_INSERT: genode_map[i] = RETROK_INSERT; break;
|
||||
case KEY_DELETE: genode_map[i] = RETROK_DELETE; break;
|
||||
case KEY_POWER: genode_map[i] = RETROK_POWER; break;
|
||||
case KEY_KPEQUAL: genode_map[i] = RETROK_KP_EQUALS; break;
|
||||
case KEY_KPPLUSMINUS: genode_map[i] = RETROK_KP_MINUS; break;
|
||||
Genode::memset(key_map, 0x00, sizeof(key_map));
|
||||
for (int i = 0; default_keymap[i].gk < Input::KEY_MAX; ++i) {
|
||||
key_map[default_keymap[i].gk] = (retro_key)default_keymap[i].rk;
|
||||
}
|
||||
|
||||
case KEY_LEFTMETA: genode_map[i] = RETROK_LMETA; break;
|
||||
case KEY_RIGHTMETA: genode_map[i] = RETROK_RMETA; break;
|
||||
case KEY_COMPOSE: genode_map[i] = RETROK_COMPOSE; break;
|
||||
|
||||
case KEY_UNDO: genode_map[i] = RETROK_UNDO; break;
|
||||
case KEY_HELP: genode_map[i] = RETROK_HELP; break;
|
||||
case KEY_MENU: genode_map[i] = RETROK_MENU; break;
|
||||
|
||||
case KEY_F13: genode_map[i] = RETROK_F13; break;
|
||||
case KEY_F14: genode_map[i] = RETROK_F14; break;
|
||||
case KEY_F15: genode_map[i] = RETROK_F15; break;
|
||||
|
||||
case KEY_BREAK: genode_map[i] = RETROK_BREAK; break;
|
||||
|
||||
default: genode_map[i] = RETROK_UNKNOWN; break;
|
||||
for (int i = 0; i <= PORT_MAX; ++i) {
|
||||
if (controllers[i] != nullptr) {
|
||||
destroy(alloc, controllers[i]);
|
||||
controllers[i] = nullptr;
|
||||
retro_set_controller_port_device(i, RETRO_DEVICE_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
config.for_each_sub_node("controller", [&] (Xml_node const &input_node) {
|
||||
unsigned port = input_node.attribute_value("port", 0U);
|
||||
unsigned device = input_node.attribute_value("device", 0U);
|
||||
|
||||
if (controllers[port] != nullptr) {
|
||||
error("controller port ", port, " is already configured");
|
||||
return;
|
||||
}
|
||||
|
||||
controllers[port] = new (alloc) Controller(input_node, port);
|
||||
if (device != RETRO_DEVICE_NONE)
|
||||
retro_set_controller_port_device(port, device);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void input_poll_callback()
|
||||
{
|
||||
for (int i = 0; i <= PORT_MAX; ++i) {
|
||||
if (controllers[i])
|
||||
controllers[i]->poll();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int16_t input_state_callback(unsigned port, unsigned device,
|
||||
unsigned index, unsigned id)
|
||||
{
|
||||
Controller *ctrl = (port < PORT_MAX) ? controllers[port] : 0;
|
||||
|
||||
if (ctrl) switch (RETRO_DEVICE_MASK & device) {
|
||||
case RETRO_DEVICE_JOYPAD:
|
||||
if (id < Controller::JOYPAD_MAP_LEN) {
|
||||
auto code = ctrl->joypad_map[id];
|
||||
if (code < Input::KEY_MAX) {
|
||||
return ctrl->input_state[code];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RETRO_DEVICE_MOUSE:
|
||||
switch (id) {
|
||||
case RETRO_DEVICE_ID_MOUSE_X:
|
||||
return ctrl->mouse_x;
|
||||
case RETRO_DEVICE_ID_MOUSE_Y:
|
||||
return ctrl->mouse_y;
|
||||
default:
|
||||
if (id < Controller::MOUSE_MAP_LEN) {
|
||||
auto code = ctrl->mouse_map[id];
|
||||
if (code < Input::KEY_MAX) {
|
||||
return ctrl->input_state[code];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* install a handler to wake the frontend from input events */
|
||||
void input_sigh(Genode::Signal_context_capability sig)
|
||||
{
|
||||
for (int i = 0; i <= PORT_MAX; ++i) {
|
||||
if (controllers[i])
|
||||
controllers[i]->input_conn.sigh(sig);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
383
src/app/retro_frontend/input_maps.h
Normal file
383
src/app/retro_frontend/input_maps.h
Normal file
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
* \brief Libretro input mapping
|
||||
* \author Emery Hemingway
|
||||
* \date 2016-07-08
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _RETRO_FRONTEND__INPUT_MAPS_H_
|
||||
#define _RETRO_FRONTEND__INPUT_MAPS_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <input/keycodes.h>
|
||||
|
||||
namespace Retro_frontend {
|
||||
#include <libretro.h>
|
||||
|
||||
struct retro_input_text_map
|
||||
{
|
||||
int code;
|
||||
char const *text;
|
||||
};
|
||||
|
||||
static const struct retro_input_text_map joypad_text_map[] = {
|
||||
{ RETRO_DEVICE_ID_JOYPAD_B, "B" },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_Y, "Y" },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_SELECT, "SELECT" },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_START, "START" },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_UP, "UP" },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_DOWN, "DOWN" },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_LEFT, "LEFT" },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_RIGHT, "RIGHT" },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_A, "A" },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_X, "X" },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_L, "L" },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_R, "R" },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_L2, "L2" },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_R2, "R2" },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_L3, "L3" },
|
||||
{ RETRO_DEVICE_ID_JOYPAD_R3, "R3" },
|
||||
{ -1, "" },
|
||||
};
|
||||
|
||||
static const struct retro_input_text_map mouse_text_map[] = {
|
||||
{ RETRO_DEVICE_ID_MOUSE_LEFT, "LEFT" },
|
||||
{ RETRO_DEVICE_ID_MOUSE_RIGHT, "RIGHT" },
|
||||
{ RETRO_DEVICE_ID_MOUSE_WHEELUP, "WHEELUP" },
|
||||
{ RETRO_DEVICE_ID_MOUSE_WHEELDOWN, "WHEELDOWN" },
|
||||
{ RETRO_DEVICE_ID_MOUSE_MIDDLE, "MIDDLE" },
|
||||
{ RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELUP, "HORIZ_WHEELUP" },
|
||||
{ RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN, "HORIZ_WHEELDOWN" },
|
||||
{ -1, "" },
|
||||
};
|
||||
|
||||
static const struct retro_input_text_map keyboard_text_map[] = {
|
||||
{ RETROK_UNKNOWN, "UNKNOWN" },
|
||||
{ RETROK_FIRST, "FIRST" },
|
||||
{ RETROK_BACKSPACE, "BACKSPACE" },
|
||||
{ RETROK_TAB, "TAB" },
|
||||
{ RETROK_CLEAR, "CLEAR" },
|
||||
{ RETROK_RETURN, "RETURN" },
|
||||
{ RETROK_PAUSE, "PAUSE" },
|
||||
{ RETROK_ESCAPE, "ESCAPE" },
|
||||
{ RETROK_SPACE, "SPACE" },
|
||||
{ RETROK_EXCLAIM, "EXCLAIM" },
|
||||
{ RETROK_QUOTEDBL, "QUOTEDBL" },
|
||||
{ RETROK_HASH, "HASH" },
|
||||
{ RETROK_DOLLAR, "DOLLAR" },
|
||||
{ RETROK_AMPERSAND, "AMPERSAND" },
|
||||
{ RETROK_QUOTE, "QUOTE" },
|
||||
{ RETROK_LEFTPAREN, "LEFTPAREN" },
|
||||
{ RETROK_RIGHTPAREN, "RIGHTPAREN" },
|
||||
{ RETROK_ASTERISK, "ASTERISK" },
|
||||
{ RETROK_PLUS, "PLUS" },
|
||||
{ RETROK_COMMA, "COMMA" },
|
||||
{ RETROK_MINUS, "MINUS" },
|
||||
{ RETROK_PERIOD, "PERIOD" },
|
||||
{ RETROK_SLASH, "SLASH" },
|
||||
{ RETROK_0, "0" },
|
||||
{ RETROK_1, "1" },
|
||||
{ RETROK_2, "2" },
|
||||
{ RETROK_3, "3" },
|
||||
{ RETROK_4, "4" },
|
||||
{ RETROK_5, "5" },
|
||||
{ RETROK_6, "6" },
|
||||
{ RETROK_7, "7" },
|
||||
{ RETROK_8, "8" },
|
||||
{ RETROK_9, "9" },
|
||||
{ RETROK_COLON, "COLON" },
|
||||
{ RETROK_SEMICOLON, "SEMICOLON" },
|
||||
{ RETROK_LESS, "LESS" },
|
||||
{ RETROK_EQUALS, "EQUALS" },
|
||||
{ RETROK_GREATER, "GREATER" },
|
||||
{ RETROK_QUESTION, "QUESTION" },
|
||||
{ RETROK_AT, "AT" },
|
||||
{ RETROK_LEFTBRACKET, "LEFTBRACKET" },
|
||||
{ RETROK_BACKSLASH, "BACKSLASH" },
|
||||
{ RETROK_RIGHTBRACKET, "RIGHTBRACKET" },
|
||||
{ RETROK_CARET, "CARET" },
|
||||
{ RETROK_UNDERSCORE, "UNDERSCORE" },
|
||||
{ RETROK_BACKQUOTE, "BACKQUOTE" },
|
||||
{ RETROK_a, "a" },
|
||||
{ RETROK_b, "b" },
|
||||
{ RETROK_c, "c" },
|
||||
{ RETROK_d, "d" },
|
||||
{ RETROK_e, "e" },
|
||||
{ RETROK_f, "f" },
|
||||
{ RETROK_g, "g" },
|
||||
{ RETROK_h, "h" },
|
||||
{ RETROK_i, "i" },
|
||||
{ RETROK_j, "j" },
|
||||
{ RETROK_k, "k" },
|
||||
{ RETROK_l, "l" },
|
||||
{ RETROK_m, "m" },
|
||||
{ RETROK_n, "n" },
|
||||
{ RETROK_o, "o" },
|
||||
{ RETROK_p, "p" },
|
||||
{ RETROK_q, "q" },
|
||||
{ RETROK_r, "r" },
|
||||
{ RETROK_s, "s" },
|
||||
{ RETROK_t, "t" },
|
||||
{ RETROK_u, "u" },
|
||||
{ RETROK_v, "v" },
|
||||
{ RETROK_w, "w" },
|
||||
{ RETROK_x, "x" },
|
||||
{ RETROK_y, "y" },
|
||||
{ RETROK_z, "z" },
|
||||
{ RETROK_DELETE, "DELETE" },
|
||||
|
||||
{ RETROK_KP0, "KP0" },
|
||||
{ RETROK_KP1, "KP1" },
|
||||
{ RETROK_KP2, "KP2" },
|
||||
{ RETROK_KP3, "KP3" },
|
||||
{ RETROK_KP4, "KP4" },
|
||||
{ RETROK_KP5, "KP5" },
|
||||
{ RETROK_KP6, "KP6" },
|
||||
{ RETROK_KP7, "KP7" },
|
||||
{ RETROK_KP8, "KP8" },
|
||||
{ RETROK_KP9, "KP9" },
|
||||
{ RETROK_KP_PERIOD, "KP_PERIOD" },
|
||||
{ RETROK_KP_DIVIDE, "KP_DIVIDE" },
|
||||
{ RETROK_KP_MULTIPLY, "KP_MULTIPLY" },
|
||||
{ RETROK_KP_MINUS, "KP_MINUS" },
|
||||
{ RETROK_KP_PLUS, "KP_PLUS" },
|
||||
{ RETROK_KP_ENTER, "KP_ENTER" },
|
||||
{ RETROK_KP_EQUALS, "KP_EQUALS" },
|
||||
|
||||
{ RETROK_UP, "UP" },
|
||||
{ RETROK_DOWN, "DOWN" },
|
||||
{ RETROK_RIGHT, "RIGHT" },
|
||||
{ RETROK_LEFT, "LEFT" },
|
||||
{ RETROK_INSERT, "INSERT" },
|
||||
{ RETROK_HOME, "HOME" },
|
||||
{ RETROK_END, "END" },
|
||||
{ RETROK_PAGEUP, "PAGEUP" },
|
||||
{ RETROK_PAGEDOWN, "PAGEDOWN" },
|
||||
|
||||
{ RETROK_F1, "F1" },
|
||||
{ RETROK_F2, "F2" },
|
||||
{ RETROK_F3, "F3" },
|
||||
{ RETROK_F4, "F4" },
|
||||
{ RETROK_F5, "F5" },
|
||||
{ RETROK_F6, "F6" },
|
||||
{ RETROK_F7, "F7" },
|
||||
{ RETROK_F8, "F8" },
|
||||
{ RETROK_F9, "F9" },
|
||||
{ RETROK_F10, "F10" },
|
||||
{ RETROK_F11, "F11" },
|
||||
{ RETROK_F12, "F12" },
|
||||
{ RETROK_F13, "F13" },
|
||||
{ RETROK_F14, "F14" },
|
||||
{ RETROK_F15, "F15" },
|
||||
|
||||
{ RETROK_NUMLOCK, "NUMLOCK" },
|
||||
{ RETROK_CAPSLOCK, "CAPSLOCK" },
|
||||
{ RETROK_SCROLLOCK, "SCROLLOCK" },
|
||||
{ RETROK_RSHIFT, "RSHIFT" },
|
||||
{ RETROK_LSHIFT, "LSHIFT" },
|
||||
{ RETROK_RCTRL, "RCTRL" },
|
||||
{ RETROK_LCTRL, "LCTRL" },
|
||||
{ RETROK_RALT, "RALT" },
|
||||
{ RETROK_LALT, "LALT" },
|
||||
{ RETROK_RMETA, "RMETA" },
|
||||
{ RETROK_LMETA, "LMETA" },
|
||||
{ RETROK_LSUPER, "LSUPER" },
|
||||
{ RETROK_RSUPER, "RSUPER" },
|
||||
{ RETROK_MODE, "MODE" },
|
||||
{ RETROK_COMPOSE, "COMPOSE" },
|
||||
|
||||
{ RETROK_HELP, "HELP" },
|
||||
{ RETROK_PRINT, "PRINT" },
|
||||
{ RETROK_SYSREQ, "SYSREQ" },
|
||||
{ RETROK_BREAK, "BREAK" },
|
||||
{ RETROK_MENU, "MENU" },
|
||||
{ RETROK_POWER, "POWER" },
|
||||
{ RETROK_EURO, "EURO" },
|
||||
{ RETROK_UNDO, "UNDO" },
|
||||
{ -1, "" },
|
||||
};
|
||||
|
||||
char const *lookup_input_text(int device, int code)
|
||||
{
|
||||
retro_input_text_map const *map;
|
||||
char const *placeholder = "UNKNOWN";
|
||||
|
||||
switch (device) {
|
||||
case RETRO_DEVICE_JOYPAD:
|
||||
map = &joypad_text_map[0]; break;
|
||||
case RETRO_DEVICE_MOUSE:
|
||||
map = &mouse_text_map[0]; break;
|
||||
case RETRO_DEVICE_KEYBOARD:
|
||||
map = &keyboard_text_map[0]; break;
|
||||
default:
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
for (int i = 0; *map[i].text; ++i) {
|
||||
if (map[i].code == code)
|
||||
return map[i].text;
|
||||
}
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
|
||||
unsigned lookup_input_code(int device, char const *text)
|
||||
{
|
||||
retro_input_text_map const *map;
|
||||
|
||||
switch (device) {
|
||||
case RETRO_DEVICE_JOYPAD:
|
||||
map = &joypad_text_map[0]; break;
|
||||
case RETRO_DEVICE_MOUSE:
|
||||
map = &mouse_text_map[0]; break;
|
||||
case RETRO_DEVICE_KEYBOARD:
|
||||
map = &keyboard_text_map[0]; break;
|
||||
default: return RETROK_LAST;
|
||||
}
|
||||
|
||||
for (int i = 0; *map[i].text; ++i) {
|
||||
if (Genode::strcmp(map[i].text, text) == 0)
|
||||
return map[i].code;
|
||||
}
|
||||
return RETROK_LAST;
|
||||
}
|
||||
|
||||
|
||||
struct genode_retro_map
|
||||
{
|
||||
enum Input::Keycode gk;
|
||||
unsigned rk;
|
||||
};
|
||||
|
||||
static const struct genode_retro_map default_keymap[] = {
|
||||
{ Input::KEY_ESC, RETROK_ESCAPE },
|
||||
{ Input::KEY_1, RETROK_1 },
|
||||
{ Input::KEY_2, RETROK_2 },
|
||||
{ Input::KEY_3, RETROK_3 },
|
||||
{ Input::KEY_4, RETROK_4 },
|
||||
{ Input::KEY_5, RETROK_5 },
|
||||
{ Input::KEY_6, RETROK_6 },
|
||||
{ Input::KEY_7, RETROK_7 },
|
||||
{ Input::KEY_8, RETROK_8 },
|
||||
{ Input::KEY_9, RETROK_9 },
|
||||
{ Input::KEY_0, RETROK_0 },
|
||||
{ Input::KEY_MINUS, RETROK_MINUS },
|
||||
{ Input::KEY_EQUAL, RETROK_EQUALS },
|
||||
{ Input::KEY_BACKSPACE, RETROK_BACKSPACE },
|
||||
{ Input::KEY_TAB, RETROK_TAB },
|
||||
{ Input::KEY_Q, RETROK_q },
|
||||
{ Input::KEY_W, RETROK_w },
|
||||
{ Input::KEY_E, RETROK_e },
|
||||
{ Input::KEY_R, RETROK_r },
|
||||
{ Input::KEY_T, RETROK_t },
|
||||
{ Input::KEY_Y, RETROK_y },
|
||||
{ Input::KEY_U, RETROK_u },
|
||||
{ Input::KEY_I, RETROK_i },
|
||||
{ Input::KEY_O, RETROK_o },
|
||||
{ Input::KEY_P, RETROK_p },
|
||||
{ Input::KEY_LEFTBRACE, RETROK_LEFTBRACKET },
|
||||
{ Input::KEY_RIGHTBRACE, RETROK_RIGHTBRACKET },
|
||||
{ Input::KEY_ENTER, RETROK_RETURN },
|
||||
{ Input::KEY_LEFTCTRL, RETROK_LCTRL },
|
||||
{ Input::KEY_A, RETROK_a },
|
||||
{ Input::KEY_S, RETROK_s },
|
||||
{ Input::KEY_D, RETROK_d },
|
||||
{ Input::KEY_F, RETROK_f },
|
||||
{ Input::KEY_G, RETROK_g },
|
||||
{ Input::KEY_H, RETROK_h },
|
||||
{ Input::KEY_J, RETROK_j },
|
||||
{ Input::KEY_K, RETROK_k },
|
||||
{ Input::KEY_L, RETROK_l },
|
||||
{ Input::KEY_SEMICOLON, RETROK_SEMICOLON },
|
||||
{ Input::KEY_APOSTROPHE, RETROK_QUOTE },
|
||||
{ Input::KEY_GRAVE, RETROK_BACKQUOTE },
|
||||
{ Input::KEY_LEFTSHIFT, RETROK_LSHIFT },
|
||||
{ Input::KEY_BACKSLASH, RETROK_BACKSLASH },
|
||||
{ Input::KEY_Z, RETROK_z },
|
||||
{ Input::KEY_X, RETROK_x },
|
||||
{ Input::KEY_C, RETROK_c },
|
||||
{ Input::KEY_V, RETROK_v },
|
||||
{ Input::KEY_B, RETROK_b },
|
||||
{ Input::KEY_N, RETROK_n },
|
||||
{ Input::KEY_M, RETROK_m },
|
||||
{ Input::KEY_COMMA, RETROK_COMMA },
|
||||
{ Input::KEY_DOT, RETROK_PERIOD },
|
||||
{ Input::KEY_SLASH, RETROK_SLASH },
|
||||
{ Input::KEY_RIGHTSHIFT, RETROK_RSHIFT },
|
||||
{ Input::KEY_KPASTERISK, RETROK_KP_MULTIPLY },
|
||||
{ Input::KEY_LEFTALT, RETROK_LALT },
|
||||
{ Input::KEY_SPACE, RETROK_SPACE },
|
||||
{ Input::KEY_CAPSLOCK, RETROK_CAPSLOCK },
|
||||
{ Input::KEY_F1, RETROK_F1 },
|
||||
{ Input::KEY_F2, RETROK_F2 },
|
||||
{ Input::KEY_F3, RETROK_F3 },
|
||||
{ Input::KEY_F4, RETROK_F4 },
|
||||
{ Input::KEY_F5, RETROK_F5 },
|
||||
{ Input::KEY_F6, RETROK_F6 },
|
||||
{ Input::KEY_F7, RETROK_F7 },
|
||||
{ Input::KEY_F8, RETROK_F8 },
|
||||
{ Input::KEY_F9, RETROK_F9 },
|
||||
{ Input::KEY_F10, RETROK_F10 },
|
||||
{ Input::KEY_NUMLOCK, RETROK_NUMLOCK },
|
||||
{ Input::KEY_SCROLLLOCK, RETROK_SCROLLOCK },
|
||||
{ Input::KEY_KP7, RETROK_KP7 },
|
||||
{ Input::KEY_KP8, RETROK_KP8 },
|
||||
{ Input::KEY_KP9, RETROK_KP9 },
|
||||
{ Input::KEY_KPMINUS, RETROK_KP_MINUS },
|
||||
{ Input::KEY_KP4, RETROK_KP4 },
|
||||
{ Input::KEY_KP5, RETROK_KP5 },
|
||||
{ Input::KEY_KP6, RETROK_KP6 },
|
||||
{ Input::KEY_KPPLUS, RETROK_KP_PLUS },
|
||||
{ Input::KEY_KP1, RETROK_KP1 },
|
||||
{ Input::KEY_KP2, RETROK_KP2 },
|
||||
{ Input::KEY_KP3, RETROK_KP3 },
|
||||
{ Input::KEY_KP0, RETROK_KP0 },
|
||||
{ Input::KEY_KPDOT, RETROK_KP_PERIOD },
|
||||
|
||||
{ Input::KEY_F11, RETROK_F11 },
|
||||
{ Input::KEY_F12, RETROK_F12 },
|
||||
{ Input::KEY_KPENTER, RETROK_KP_ENTER },
|
||||
{ Input::KEY_RIGHTCTRL, RETROK_RCTRL },
|
||||
{ Input::KEY_KPSLASH, RETROK_KP_DIVIDE },
|
||||
{ Input::KEY_SYSRQ, RETROK_SYSREQ },
|
||||
{ Input::KEY_RIGHTALT, RETROK_RALT },
|
||||
{ Input::KEY_HOME, RETROK_HOME },
|
||||
{ Input::KEY_UP, RETROK_UP },
|
||||
{ Input::KEY_PAGEUP, RETROK_PAGEUP },
|
||||
{ Input::KEY_LEFT, RETROK_LEFT },
|
||||
{ Input::KEY_RIGHT, RETROK_RIGHT },
|
||||
{ Input::KEY_END, RETROK_END },
|
||||
{ Input::KEY_DOWN, RETROK_DOWN },
|
||||
{ Input::KEY_PAGEDOWN, RETROK_PAGEDOWN },
|
||||
{ Input::KEY_INSERT, RETROK_INSERT },
|
||||
{ Input::KEY_DELETE, RETROK_DELETE },
|
||||
{ Input::KEY_POWER, RETROK_POWER },
|
||||
{ Input::KEY_KPEQUAL, RETROK_KP_EQUALS },
|
||||
{ Input::KEY_KPPLUSMINUS, RETROK_KP_MINUS },
|
||||
|
||||
{ Input::KEY_LEFTMETA, RETROK_LMETA },
|
||||
{ Input::KEY_RIGHTMETA, RETROK_RMETA },
|
||||
{ Input::KEY_COMPOSE, RETROK_COMPOSE },
|
||||
|
||||
{ Input::KEY_UNDO, RETROK_UNDO },
|
||||
{ Input::KEY_HELP, RETROK_HELP },
|
||||
{ Input::KEY_MENU, RETROK_MENU },
|
||||
|
||||
{ Input::KEY_F13, RETROK_F13 },
|
||||
{ Input::KEY_F14, RETROK_F14 },
|
||||
{ Input::KEY_F15, RETROK_F15 },
|
||||
|
||||
{ Input::KEY_BREAK, RETROK_BREAK },
|
||||
{ Input::KEY_MAX, RETROK_LAST },
|
||||
{ Input::KEY_MAX, RETROK_LAST },
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
49
src/app/retro_frontend/log.h
Normal file
49
src/app/retro_frontend/log.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* \brief Libretro frontend
|
||||
* \author Emery Hemingway
|
||||
* \date 2017-11-04
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2017 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _RETRO_FRONTEND__LOG_H_
|
||||
#define _RETRO_FRONTEND__LOG_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/log.h>
|
||||
|
||||
/* vsnprintf */
|
||||
#include <stdio.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
void log_printf_callback(retro_log_level level, const char *fmt, ...)
|
||||
{
|
||||
using namespace Retro_frontend;
|
||||
|
||||
char buf[Genode::Log_session::MAX_STRING_LEN];
|
||||
|
||||
va_list vp;
|
||||
va_start(vp, fmt);
|
||||
int n = ::vsnprintf(buf, sizeof(buf), fmt, vp);
|
||||
va_end(vp);
|
||||
|
||||
if (n)
|
||||
buf[n-1] = '\0'; /* trim newline */
|
||||
char const *msg = buf;
|
||||
|
||||
switch (level) {
|
||||
case RETRO_LOG_DEBUG: Genode::log("Debug: ", msg); return;
|
||||
case RETRO_LOG_INFO: Genode::log(msg); return;
|
||||
case RETRO_LOG_WARN: Genode::warning(msg); return;
|
||||
case RETRO_LOG_ERROR: Genode::error(msg); return;
|
||||
case RETRO_LOG_DUMMY: Genode::log("Dummy: ", msg); return;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
96
src/app/retro_frontend/memory.h
Normal file
96
src/app/retro_frontend/memory.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* \brief Libretro persistent memory file
|
||||
* \author Emery Hemingway
|
||||
* \date 2017-11-17
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2017 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _RETRO_FRONTEND__MEMORY_H_
|
||||
#define _RETRO_FRONTEND__MEMORY_H_
|
||||
|
||||
/* Libc includes */
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace Retro_frontend {
|
||||
|
||||
struct Memory_file
|
||||
{
|
||||
void *data = nullptr;
|
||||
size_t size = 0;
|
||||
unsigned const id;
|
||||
char const *path;
|
||||
int fd = -1;
|
||||
|
||||
Genode::size_t file_size()
|
||||
{
|
||||
struct stat s;
|
||||
s.st_size = 0;
|
||||
stat(path, &s);
|
||||
return s.st_size;
|
||||
}
|
||||
|
||||
bool open_file_for_data()
|
||||
{
|
||||
if (!(data && size))
|
||||
return false;
|
||||
|
||||
if (fd == -1)
|
||||
fd = ::open(path, O_RDWR|O_CREAT);
|
||||
return fd != -1;
|
||||
}
|
||||
|
||||
Memory_file(unsigned id, char const *filename)
|
||||
: id(id), path(filename)
|
||||
{ }
|
||||
|
||||
~Memory_file() { if (fd != -1) close(fd); }
|
||||
|
||||
void read()
|
||||
{
|
||||
if (open_file_for_data()) {
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
size_t remain = Genode::min(size, file_size());
|
||||
size_t offset = 0;
|
||||
do {
|
||||
ssize_t n = ::read(fd, ((char*)data)+offset, remain);
|
||||
if (n == -1) {
|
||||
Genode::error("failed to read from ", path);
|
||||
break;
|
||||
}
|
||||
remain -= n;
|
||||
offset += n;
|
||||
} while (remain);
|
||||
}
|
||||
}
|
||||
|
||||
void write()
|
||||
{
|
||||
if (open_file_for_data()) {
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
ftruncate(fd, size);
|
||||
size_t remain = size;
|
||||
size_t offset = 0;
|
||||
do {
|
||||
ssize_t n = ::write(fd, ((char const *)data)+offset, remain);
|
||||
if (n == -1) {
|
||||
Genode::error("failed to write to ", path);
|
||||
break;
|
||||
}
|
||||
remain -= n;
|
||||
offset += n;
|
||||
} while (remain);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -6,4 +6,3 @@ LIBRETRO_INC := $(call select_from_ports,libretro)/include
|
||||
|
||||
INC_DIR += $(LIBRETRO_INC)
|
||||
|
||||
component.cc: $(LIBRETRO_INC)/libretro.h
|
||||
|
||||
Reference in New Issue
Block a user