app/retro_frontend: Libretro frontend
Native fronted for Libretro cores. https://www.libretro.com/ Ref #52
This commit is contained in:
committed by
Norman Feske
parent
26926d2687
commit
f3e052f65f
470
src/app/retro_frontend/callbacks.h
Normal file
470
src/app/retro_frontend/callbacks.h
Normal file
@@ -0,0 +1,470 @@
|
||||
/*
|
||||
* \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__CALLBACKS_H_
|
||||
#define _RETRO_FRONTEND__CALLBACKS_H_
|
||||
|
||||
#include "frontend.h"
|
||||
|
||||
namespace Libretro {
|
||||
|
||||
#include <libretro.h>
|
||||
|
||||
/**
|
||||
* Doesn't work
|
||||
|
||||
static
|
||||
void log_callback(enum retro_log_level level, const char *fmt, ...)
|
||||
{
|
||||
char buf[Genode::Log_session::MAX_STRING_LEN];
|
||||
|
||||
va_list vp;
|
||||
va_start(vp, fmt);
|
||||
Genode::snprintf(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;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
static
|
||||
bool environment_callback(unsigned cmd, void *data)
|
||||
{
|
||||
switch (cmd) {
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_OVERSCAN:
|
||||
Genode::warning("instructing core not to overscan");
|
||||
*((bool *)data) = false;
|
||||
return true;
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_CAN_DUPE:
|
||||
{
|
||||
bool *b = (bool*)data;
|
||||
*b = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL:
|
||||
{
|
||||
const unsigned *level = (const unsigned*)data;
|
||||
Genode::warning("frontend reports a suggested performance level of \"", *level, "\"");
|
||||
return true;
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
|
||||
case RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY:
|
||||
case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
|
||||
{
|
||||
char **path = (char **)data;
|
||||
*path = (char *)"/";
|
||||
return true;
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT:
|
||||
{
|
||||
const retro_pixel_format *format = (retro_pixel_format *)data;
|
||||
if (*format == RETRO_PIXEL_FORMAT_RGB565)
|
||||
return true;
|
||||
else {
|
||||
char const *s = "";
|
||||
switch (*format) {
|
||||
case RETRO_PIXEL_FORMAT_0RGB1555: s = "0RGB1555"; break;
|
||||
case RETRO_PIXEL_FORMAT_XRGB8888: s = "XRGB8888"; break;
|
||||
case RETRO_PIXEL_FORMAT_RGB565: s = "RGB565"; break;
|
||||
case RETRO_PIXEL_FORMAT_UNKNOWN: break;
|
||||
}
|
||||
Genode::error("core uses unsupported pixel format ", s);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT_SHUTDOWN:
|
||||
global_frontend->env.parent().exit(0);
|
||||
return true;
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_VARIABLE:
|
||||
{
|
||||
/********************************************
|
||||
** TODO: Retrieve variables set by parent **
|
||||
********************************************/
|
||||
|
||||
const retro_variable *var = (retro_variable*)data;
|
||||
Genode::warning("RETRO_ENVIRONMENT_GET_VARIABLE ", var->key);
|
||||
return false;
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_VARIABLES:
|
||||
{
|
||||
/**********************************
|
||||
** Report variables set by core **
|
||||
**********************************/
|
||||
|
||||
try {
|
||||
global_frontend->variable_reporter.enabled(true);
|
||||
} catch (...) {
|
||||
Genode::error("core variables not reported");
|
||||
return false;
|
||||
}
|
||||
|
||||
Genode::Reporter::Xml_generator gen(global_frontend->variable_reporter, [&] () {
|
||||
for (const struct retro_variable *var = (retro_variable*)data;
|
||||
(var && (var->key != NULL) && (var->value != NULL)); ++var)
|
||||
{
|
||||
gen.node("variable", [&] () {
|
||||
gen.attribute("key", var->key);
|
||||
gen.attribute("value", var->value);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE:
|
||||
return false;
|
||||
|
||||
//case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME:
|
||||
// global_frontend->core.supports_null_load = data;
|
||||
// return true;
|
||||
|
||||
/*
|
||||
case RETRO_ENVIRONMENT_GET_LOG_INTERFACE:
|
||||
{
|
||||
retro_log_callback *cb = ( retro_log_callback*)data;
|
||||
cb->log = log_callback;
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO:
|
||||
{
|
||||
try {
|
||||
global_frontend->subsystem_reporter.enabled(true);
|
||||
} catch (...) {
|
||||
Genode::error("core subsystems not reported");
|
||||
return false;
|
||||
}
|
||||
|
||||
Genode::Reporter::Xml_generator gen(global_frontend->subsystem_reporter, [&] () {
|
||||
for (const retro_subsystem_info *info = (retro_subsystem_info*)data;
|
||||
(info && (info->desc)); ++info)
|
||||
{
|
||||
gen.node("subsystem", [&] () {
|
||||
gen.attribute("desc", info->desc);
|
||||
gen.attribute("num_roms", info->num_roms);
|
||||
gen.attribute("id", info->id);
|
||||
for (unsigned i = 0; i < info->num_roms; ++i) {
|
||||
const retro_subsystem_rom_info *rom_info =
|
||||
&info->roms[i];
|
||||
gen.node("rom", [&] () {
|
||||
gen.attribute("desc", rom_info->desc);
|
||||
gen.attribute("valid_extensions", rom_info->valid_extensions);
|
||||
gen.attribute("need_fullpath", rom_info->need_fullpath);
|
||||
gen.attribute("block_extract", rom_info->block_extract);
|
||||
gen.attribute("required", rom_info->required);
|
||||
for (unsigned j = 0; j < rom_info->num_memory; ++j) {
|
||||
const retro_subsystem_memory_info *memory =
|
||||
&rom_info->memory[i];
|
||||
gen.node("memory", [&] () {
|
||||
gen.attribute("extension", memory->extension);
|
||||
gen.attribute("type", memory->type);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO:
|
||||
{
|
||||
try {
|
||||
global_frontend->controller_reporter.enabled(true);
|
||||
} catch (...) {
|
||||
Genode::error("core controllers not reported");
|
||||
return false;
|
||||
}
|
||||
|
||||
Genode::Reporter::Xml_generator gen(global_frontend->controller_reporter, [&] () {
|
||||
unsigned index = 0;
|
||||
for (const retro_controller_info *info = (retro_controller_info*)data;
|
||||
(info && (info->types)); ++info)
|
||||
{
|
||||
gen.node("controller", [&] () {
|
||||
gen.attribute("port", index);
|
||||
for (unsigned i = 0; i < info->num_types; ++i) {
|
||||
const retro_controller_description *type =
|
||||
&info->types[i];
|
||||
gen.node("type", [&] () {
|
||||
gen.attribute("desc", type->desc);
|
||||
gen.attribute("id", type->id);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case RETRO_ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER:
|
||||
{
|
||||
retro_framebuffer *fb = (retro_framebuffer*)data;
|
||||
|
||||
if (!global_frontend->framebuffer.constructed())
|
||||
return false;
|
||||
|
||||
Framebuffer::Mode mode = global_frontend->framebuffer->mode;
|
||||
|
||||
fb->width = (unsigned)mode.width();
|
||||
fb->height = (unsigned)mode.height();
|
||||
fb->data = global_frontend->framebuffer->ds.local_addr<void>();
|
||||
fb->pitch = mode.width() * 2;
|
||||
fb->format = RETRO_PIXEL_FORMAT_RGB565;
|
||||
fb->memory_flags = RETRO_MEMORY_TYPE_CACHED;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Genode::warning(__func__, "(",cmd,") is unhandled");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
void video_refresh_callback(const void *data,
|
||||
unsigned src_width, unsigned src_height,
|
||||
size_t src_pitch)
|
||||
{
|
||||
using namespace Genode;
|
||||
if (data == NULL) /* frame duping? */
|
||||
return;
|
||||
|
||||
uint8_t const *src = (uint8_t const*)data;
|
||||
uint8_t *dst = global_frontend->framebuffer->ds.local_addr<uint8_t>();
|
||||
|
||||
unsigned const dst_width = global_frontend->framebuffer->mode.width();
|
||||
unsigned const dst_height = global_frontend->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);
|
||||
}
|
||||
|
||||
global_frontend->framebuffer->session.refresh(0, 0, width, height);
|
||||
}
|
||||
|
||||
|
||||
template <Genode::size_t CAPACITY>
|
||||
struct Ring_buffer
|
||||
{
|
||||
Genode::size_t wpos { 0 };
|
||||
Genode::size_t rpos { 0 };
|
||||
|
||||
char _data[CAPACITY];
|
||||
|
||||
Ring_buffer() { }
|
||||
|
||||
Genode::size_t read_avail() const
|
||||
{
|
||||
if (wpos > rpos) return wpos - rpos;
|
||||
else return (wpos - rpos + CAPACITY) % CAPACITY;
|
||||
}
|
||||
|
||||
Genode::size_t write_avail() const
|
||||
{
|
||||
if (wpos > rpos) return ((rpos - wpos + CAPACITY) % CAPACITY) - 2;
|
||||
else if (wpos < rpos) return rpos - wpos;
|
||||
else return CAPACITY - 2;
|
||||
}
|
||||
|
||||
Genode::size_t write(void const *src, Genode::size_t len)
|
||||
{
|
||||
Genode::size_t const avail = write_avail();
|
||||
if (avail == 0) return 0;
|
||||
|
||||
Genode::size_t const limit_len = len > avail ? avail : len;
|
||||
Genode::size_t const total = wpos + len;
|
||||
Genode::size_t first, rest;
|
||||
|
||||
if (total > CAPACITY) {
|
||||
first = CAPACITY - wpos;
|
||||
rest = total % CAPACITY;
|
||||
} else {
|
||||
first = limit_len;
|
||||
rest = 0;
|
||||
}
|
||||
|
||||
Genode::memcpy(&_data[wpos], src, first);
|
||||
wpos = (wpos + first) % CAPACITY;
|
||||
|
||||
if (rest) {
|
||||
Genode::memcpy(&_data[wpos], ((char const*)src) + first, rest);
|
||||
wpos = (wpos + rest) % CAPACITY;
|
||||
}
|
||||
|
||||
return limit_len;
|
||||
}
|
||||
|
||||
Genode::size_t read(void *dst, Genode::size_t len)
|
||||
{
|
||||
Genode::size_t const avail = read_avail();
|
||||
if (avail == 0) return 0;
|
||||
|
||||
Genode::size_t const limit_len = len > avail ? avail : len;
|
||||
Genode::size_t const total = rpos + len;
|
||||
Genode::size_t first, rest;
|
||||
|
||||
if (total > CAPACITY) {
|
||||
first = CAPACITY - rpos;
|
||||
rest = total % CAPACITY;
|
||||
} else {
|
||||
first = limit_len;
|
||||
rest = 0;
|
||||
}
|
||||
|
||||
Genode::memcpy(dst, &_data[rpos], first);
|
||||
rpos = (rpos + first) % CAPACITY;
|
||||
|
||||
if (rest) {
|
||||
Genode::memcpy(((char*)dst) + first, &_data[rpos], rest);
|
||||
rpos = (rpos + rest) % CAPACITY;
|
||||
}
|
||||
|
||||
return limit_len;
|
||||
}
|
||||
};
|
||||
|
||||
static Ring_buffer<4*1024> audio_frame_buffer;
|
||||
static Audio_out::Packet *audio_alloc_pos;
|
||||
|
||||
static
|
||||
void audio_sample_noop(int16_t left, int16_t right) { }
|
||||
|
||||
static
|
||||
void audio_sample_callback(int16_t left, int16_t right)
|
||||
{
|
||||
Audio_out::Stream *sl = global_frontend->stereo_out->left.stream();
|
||||
Audio_out::Stream *sr = global_frontend->stereo_out->right.stream();
|
||||
Audio_out::Packet *pl = 0;
|
||||
Audio_out::Packet *pr = 0;
|
||||
|
||||
if (audio_alloc_pos == nullptr)
|
||||
audio_alloc_pos = global_frontend->stereo_out->left.stream()->next();
|
||||
|
||||
try {
|
||||
int16_t tmp[Audio_out::PERIOD * 2];
|
||||
size_t const n = audio_frame_buffer.read(tmp, sizeof(tmp));
|
||||
(void)n;
|
||||
|
||||
pl = sl->next(audio_alloc_pos);
|
||||
pr = sr->next(audio_alloc_pos);
|
||||
|
||||
float *left_content = pl->content();
|
||||
float *right_content = pr->content();
|
||||
|
||||
for (int i = 0; i < Audio_out::PERIOD; i++) {
|
||||
left_content[i] = (float)(left) / 32768.0f;
|
||||
right_content[i] = (float)(right) / 32768.0f;
|
||||
}
|
||||
|
||||
global_frontend->stereo_out->left.submit(pl);
|
||||
global_frontend->stereo_out->right.submit(pr);
|
||||
|
||||
audio_alloc_pos = pl;
|
||||
|
||||
} catch (...) {
|
||||
Genode::error(__func__, " failed to submit audio frames");
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
audio_frame_buffer.write(data, frames * 2 * 2);
|
||||
|
||||
if (audio_frame_buffer.read_avail() > Audio_out::PERIOD * 2 * 2) {
|
||||
|
||||
Audio_out::Stream *sl = global_frontend->stereo_out->left.stream();
|
||||
Audio_out::Stream *sr = global_frontend->stereo_out->right.stream();
|
||||
Audio_out::Packet *pl = 0;
|
||||
Audio_out::Packet *pr = 0;
|
||||
|
||||
if (audio_alloc_pos == nullptr) {
|
||||
audio_alloc_pos = global_frontend->stereo_out->left.stream()->next();
|
||||
}
|
||||
|
||||
try {
|
||||
int16_t tmp[Audio_out::PERIOD * 2];
|
||||
size_t const n = audio_frame_buffer.read(tmp, sizeof(tmp));
|
||||
(void)n;
|
||||
|
||||
pl = sl->next(audio_alloc_pos);
|
||||
pr = sr->next(audio_alloc_pos);
|
||||
|
||||
float *left_content = pl->content();
|
||||
float *right_content = pr->content();
|
||||
|
||||
for (int i = 0; i < Audio_out::PERIOD; i++) {
|
||||
left_content[i] = (float)(tmp[i * 2 + 0]) / 32768.0f;
|
||||
right_content[i] = (float)(tmp[i * 2 + 1]) / 32768.0f;
|
||||
}
|
||||
|
||||
global_frontend->stereo_out->left.submit(pl);
|
||||
global_frontend->stereo_out->right.submit(pr);
|
||||
|
||||
audio_alloc_pos = pl;
|
||||
|
||||
} catch (...) {
|
||||
Genode::error(__func__, " failed to submit audio frames");
|
||||
}
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
void input_poll_callback() { global_frontend->flush_input(); }
|
||||
|
||||
|
||||
static
|
||||
int16_t input_state_callback(unsigned port, unsigned device,
|
||||
unsigned index, unsigned id)
|
||||
{
|
||||
Controller *controller = &global_frontend->controller;
|
||||
return controller ? controller->event(device, index, id) : 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
167
src/app/retro_frontend/component.cc
Normal file
167
src/app/retro_frontend/component.cc
Normal file
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* \brief Libretro frontend
|
||||
* \author Emery Hemingway
|
||||
* \date 2016-07-03
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "frontend.h"
|
||||
#include "callbacks.h"
|
||||
|
||||
Libretro::Frontend::Frontend(Genode::Env &env) : env(env)
|
||||
{
|
||||
/* set the global frontend pointer for callbacks */
|
||||
global_frontend = this;
|
||||
|
||||
|
||||
/****************
|
||||
** Initialize **
|
||||
****************/
|
||||
|
||||
retro_system_info sys_info;
|
||||
|
||||
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 (...) {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
Genode::size_t Component::stack_size() { return 16*1024*sizeof(Genode::addr_t); }
|
||||
|
||||
void Component::construct(Genode::Env &env)
|
||||
{
|
||||
using namespace Genode;
|
||||
|
||||
Libretro::init_keyboard_map();
|
||||
|
||||
try {
|
||||
static Libretro::Frontend inst(env);
|
||||
return;
|
||||
}
|
||||
|
||||
catch (Shared_object::Invalid_rom_module) {
|
||||
error("failed to load core"); }
|
||||
|
||||
catch (Shared_object::Invalid_symbol) {
|
||||
error("failed to load required symbols from core"); }
|
||||
|
||||
catch (...) { error("failed to init core"); }
|
||||
env.parent().exit(-1);
|
||||
}
|
||||
432
src/app/retro_frontend/frontend.h
Normal file
432
src/app/retro_frontend/frontend.h
Normal file
@@ -0,0 +1,432 @@
|
||||
/*
|
||||
* \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 <gems/file.h>
|
||||
#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 "input.h"
|
||||
|
||||
namespace Libretro {
|
||||
#include <libretro.h>
|
||||
struct Frontend;
|
||||
}
|
||||
|
||||
/* Global frontend instance */
|
||||
static Libretro::Frontend *global_frontend = (Libretro::Frontend*)(Genode::addr_t)666;
|
||||
|
||||
/**
|
||||
* Object to encapsulate Genode services
|
||||
*/
|
||||
struct Libretro::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);
|
||||
|
||||
Genode::Env &env;
|
||||
Genode::Heap heap { env.ram(), env.rm() };
|
||||
|
||||
Genode::Attached_rom_dataspace config_rom { env, "config" };
|
||||
|
||||
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_NOW, 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::Lazy_volatile_object<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");
|
||||
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");
|
||||
|
||||
void run() { retro_run(); }
|
||||
|
||||
Genode::Signal_handler<Frontend> core_runner
|
||||
{ env.ep(), *this, &Frontend::run };
|
||||
|
||||
Timer::Connection timer { env };
|
||||
|
||||
struct 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(); }
|
||||
};
|
||||
|
||||
Genode::Lazy_volatile_object<Framebuffer> framebuffer;
|
||||
|
||||
struct Stereo_out
|
||||
{
|
||||
Audio_out::Connection left;
|
||||
Audio_out::Connection right;
|
||||
|
||||
Stereo_out(Genode::Env &env)
|
||||
:
|
||||
left(env, "left", false, false),
|
||||
right(env, "right", false, false)
|
||||
{ }
|
||||
};
|
||||
|
||||
Genode::Lazy_volatile_object<Stereo_out> stereo_out;
|
||||
|
||||
Genode::Reporter variable_reporter { "variables" };
|
||||
Genode::Reporter subsystem_reporter { "subsystems" };
|
||||
Genode::Reporter controller_reporter { "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;
|
||||
|
||||
int sync_ms = (timer.elapsed_ms() - sample_start)/quarter_fps;
|
||||
int want_ms = 250.0 / quarter_fps;
|
||||
|
||||
int diff = want_ms - sync_ms;
|
||||
if (diff > 10 || diff < -10) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(Genode::Env &env);
|
||||
|
||||
~Frontend()
|
||||
{
|
||||
exit();
|
||||
global_frontend = nullptr;
|
||||
}
|
||||
|
||||
void set_av_info(retro_system_av_info &av_info)
|
||||
{
|
||||
framebuffer.construct(
|
||||
env, ::Framebuffer::Mode(av_info.geometry.base_width,
|
||||
av_info.geometry.base_height,
|
||||
::Framebuffer::Mode::RGB565));
|
||||
|
||||
framebuffer->session.mode_sigh(mode_handler);
|
||||
|
||||
/* start audio streams */
|
||||
if (stereo_out.constructed()) {
|
||||
stereo_out->left.start();
|
||||
stereo_out->right.start();
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
/* set signal handler for unpausing */
|
||||
controller.input.sigh(resume_handler);
|
||||
|
||||
/* a good time to flush memory to the FS */
|
||||
save_memory();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
286
src/app/retro_frontend/input.h
Normal file
286
src/app/retro_frontend/input.h
Normal file
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* \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_H_
|
||||
#define _RETRO_FRONTEND__INPUT_H_
|
||||
|
||||
/* Genode includes */
|
||||
#include <input_session/connection.h>
|
||||
#include <input/event_queue.h>
|
||||
|
||||
namespace Libretro {
|
||||
#include <libretro.h>
|
||||
|
||||
struct Controller;
|
||||
|
||||
static unsigned genode_map[Input::Keycode::KEY_MAX];
|
||||
|
||||
}
|
||||
|
||||
struct Libretro::Controller
|
||||
{
|
||||
enum {
|
||||
RETRO_JOYPAD_MAX = RETRO_DEVICE_ID_JOYPAD_R3+1,
|
||||
RETRO_MOUSE_MAX = RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN
|
||||
};
|
||||
|
||||
int16_t joypad_state[RETRO_JOYPAD_MAX];
|
||||
int16_t mouse_state[RETRO_MOUSE_MAX];
|
||||
int16_t keyboard_state[RETROK_LAST];
|
||||
|
||||
Input::Connection input;
|
||||
|
||||
Genode::Attached_dataspace input_ds;
|
||||
|
||||
Input::Event const * const events =
|
||||
input_ds.local_addr<Input::Event>();
|
||||
|
||||
bool paused = false;
|
||||
|
||||
Controller(Genode::Env &env)
|
||||
: input(env), input_ds(env.rm(), input.dataspace())
|
||||
{
|
||||
Genode::memset( joypad_state, 0x00, sizeof( joypad_state));
|
||||
Genode::memset( mouse_state, 0x00, sizeof( mouse_state));
|
||||
Genode::memset(keyboard_state, 0x00, sizeof(keyboard_state));
|
||||
}
|
||||
|
||||
bool unpaused()
|
||||
{
|
||||
using namespace Input;
|
||||
|
||||
unsigned num_events = input.flush();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void flush()
|
||||
{
|
||||
using namespace Input;
|
||||
|
||||
mouse_state[RETRO_DEVICE_ID_MOUSE_X] =
|
||||
mouse_state[RETRO_DEVICE_ID_MOUSE_Y] = 0;
|
||||
|
||||
unsigned num_events = input.flush();
|
||||
for (unsigned i = 0; i < num_events; ++i) {
|
||||
Input::Event const &ev = events[i];
|
||||
|
||||
uint16_t 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;
|
||||
|
||||
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 KEY_PAUSE: paused = true; return; /* stop handling events */
|
||||
|
||||
default:
|
||||
keyboard_state[genode_map[ev.code()]] = v; break;
|
||||
} break;
|
||||
|
||||
/*
|
||||
case Event::Type::FOCUS:
|
||||
if (ev.code() == 0) {
|
||||
paused = true;
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
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 Libretro {
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
3
src/app/retro_frontend/target.mk
Normal file
3
src/app/retro_frontend/target.mk
Normal file
@@ -0,0 +1,3 @@
|
||||
TARGET = retro_frontend
|
||||
SRC_CC = component.cc
|
||||
LIBS = base libretro libc file
|
||||
Reference in New Issue
Block a user