RetroArch/driver.c

911 lines
27 KiB
C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2021 - Daniel De Matteis
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "core_info.h"
#include "driver.h"
#include "retroarch.h"
#include "runloop.h"
#include "verbosity.h"
#include "bluetooth/bluetooth_driver.h"
#include "wifi/wifi_driver.h"
#include "led/led_driver.h"
#include "midi_driver.h"
#include "gfx/video_driver.h"
#include "gfx/video_display_server.h"
#include "audio/audio_driver.h"
#include "camera/camera_driver.h"
#include "record/record_driver.h"
#include "location_driver.h"
#ifdef HAVE_GFX_WIDGETS
#include "gfx/gfx_widgets.h"
#endif
#ifdef HAVE_MENU
#include "menu/menu_driver.h"
#endif
static bluetooth_driver_t bluetooth_null = {
NULL, /* init */
NULL, /* free */
NULL, /* scan */
NULL, /* get_devices */
NULL, /* device_is_connected */
NULL, /* device_get_sublabel */
NULL, /* connect_device */
"null",
};
const bluetooth_driver_t *bluetooth_drivers[] = {
#ifdef HAVE_BLUETOOTH
&bluetooth_bluetoothctl,
#ifdef HAVE_DBUS
&bluetooth_bluez,
#endif
#endif
&bluetooth_null,
NULL,
};
wifi_driver_t wifi_null = {
NULL, /* init */
NULL, /* free */
NULL, /* start */
NULL, /* stop */
NULL, /* enable */
NULL, /* connection_info */
NULL, /* scan */
NULL, /* get_ssids */
NULL, /* ssid_is_online */
NULL, /* connect_ssid */
NULL, /* disconnect_ssid */
NULL, /* tether_start_stop */
"null",
};
const wifi_driver_t *wifi_drivers[] = {
#ifdef HAVE_LAKKA
&wifi_connmanctl,
#endif
#ifdef HAVE_WIFI
&wifi_nmcli,
#endif
&wifi_null,
NULL,
};
static void retro_frame_null(const void *data, unsigned width,
unsigned height, size_t pitch) { }
void retro_input_poll_null(void) { }
/**
* find_driver_nonempty:
* @label : string of driver type to be found.
* @i : index of driver.
* @str : identifier name of the found driver
* gets written to this string.
* @len : size of @str.
*
* Find driver based on @label.
*
* Returns: NULL if no driver based on @label found, otherwise
* pointer to driver.
**/
static const void *find_driver_nonempty(
const char *label, int i,
char *s, size_t len)
{
if (string_is_equal(label, "camera_driver"))
{
if (camera_drivers[i])
{
const char *ident = camera_drivers[i]->ident;
strlcpy(s, ident, len);
return camera_drivers[i];
}
}
else if (string_is_equal(label, "location_driver"))
{
if (location_drivers[i])
{
const char *ident = location_drivers[i]->ident;
strlcpy(s, ident, len);
return location_drivers[i];
}
}
#ifdef HAVE_MENU
else if (string_is_equal(label, "menu_driver"))
{
if (menu_ctx_drivers[i])
{
const char *ident = menu_ctx_drivers[i]->ident;
strlcpy(s, ident, len);
return menu_ctx_drivers[i];
}
}
#endif
else if (string_is_equal(label, "input_driver"))
{
if (input_drivers[i])
{
const char *ident = input_drivers[i]->ident;
strlcpy(s, ident, len);
return input_drivers[i];
}
}
else if (string_is_equal(label, "input_joypad_driver"))
{
if (joypad_drivers[i])
{
const char *ident = joypad_drivers[i]->ident;
strlcpy(s, ident, len);
return joypad_drivers[i];
}
}
else if (string_is_equal(label, "video_driver"))
{
if (video_drivers[i])
{
const char *ident = video_drivers[i]->ident;
strlcpy(s, ident, len);
return video_drivers[i];
}
}
else if (string_is_equal(label, "audio_driver"))
{
if (audio_drivers[i])
{
const char *ident = audio_drivers[i]->ident;
strlcpy(s, ident, len);
return audio_drivers[i];
}
}
else if (string_is_equal(label, "record_driver"))
{
if (record_drivers[i])
{
const char *ident = record_drivers[i]->ident;
strlcpy(s, ident, len);
return record_drivers[i];
}
}
else if (string_is_equal(label, "midi_driver"))
{
if (midi_driver_find_handle(i))
{
const char *ident = midi_drivers[i]->ident;
strlcpy(s, ident, len);
return midi_drivers[i];
}
}
else if (string_is_equal(label, "audio_resampler_driver"))
{
if (audio_resampler_driver_find_handle(i))
{
const char *ident = audio_resampler_driver_find_ident(i);
strlcpy(s, ident, len);
return audio_resampler_driver_find_handle(i);
}
}
else if (string_is_equal(label, "bluetooth_driver"))
{
if (bluetooth_drivers[i])
{
const char *ident = bluetooth_drivers[i]->ident;
strlcpy(s, ident, len);
return bluetooth_drivers[i];
}
}
else if (string_is_equal(label, "wifi_driver"))
{
if (wifi_drivers[i])
{
const char *ident = wifi_drivers[i]->ident;
strlcpy(s, ident, len);
return wifi_drivers[i];
}
}
return NULL;
}
int driver_find_index(const char *label, const char *drv)
{
unsigned i;
char str[NAME_MAX_LENGTH];
str[0] = '\0';
for (i = 0;
find_driver_nonempty(label, i, str, sizeof(str)) != NULL; i++)
{
if (string_is_empty(str))
break;
if (string_is_equal_noncase(drv, str))
return i;
}
return -1;
}
/**
* driver_find_last:
* @label : string of driver type to be found.
* @s : identifier of driver to be found.
* @len : size of @s.
*
* Find last driver in driver array.
**/
static void driver_find_last(const char *label, char *s, size_t len)
{
unsigned i;
for (i = 0;
find_driver_nonempty(label, i, s, len) != NULL; i++) { }
if (i)
i = i - 1;
else
i = 0;
find_driver_nonempty(label, i, s, len);
}
/**
* driver_find_prev:
* @label : string of driver type to be found.
* @s : identifier of driver to be found.
* @len : size of @s.
*
* Find previous driver in driver array.
**/
static bool driver_find_prev(const char *label, char *s, size_t len)
{
int i = driver_find_index(label, s);
if (i > 0)
{
find_driver_nonempty(label, i - 1, s, len);
return true;
}
RARCH_WARN(
"Couldn't find any previous driver (current one: \"%s\").\n", s);
return false;
}
/**
* driver_find_next:
* @label : string of driver type to be found.
* @s : identifier of driver to be found.
* @len : size of @s.
*
* Find next driver in driver array.
**/
static bool driver_find_next(const char *label, char *s, size_t len)
{
int i = driver_find_index(label, s);
if (i >= 0 && string_is_not_equal(s, "null"))
{
find_driver_nonempty(label, i + 1, s, len);
return true;
}
RARCH_WARN("%s (current one: \"%s\").\n",
msg_hash_to_str(MSG_COULD_NOT_FIND_ANY_NEXT_DRIVER),
s);
return false;
}
static void driver_adjust_system_rates(
bool vrr_runloop_enable,
float video_refresh_rate,
float audio_max_timing_skew,
bool video_adaptive_vsync,
unsigned video_swap_interval)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
video_driver_state_t *video_st = video_state_get_ptr();
struct retro_system_av_info *av_info = &video_st->av_info;
const struct retro_system_timing *info =
(const struct retro_system_timing*)&av_info->timing;
double input_sample_rate = info->sample_rate;
double input_fps = info->fps;
if (input_sample_rate > 0.0)
{
audio_driver_state_t *audio_st = audio_state_get_ptr();
if (vrr_runloop_enable)
audio_st->input = input_sample_rate;
else
audio_st->input =
audio_driver_monitor_adjust_system_rates(
input_sample_rate,
input_fps,
video_refresh_rate,
video_swap_interval,
audio_max_timing_skew);
RARCH_LOG("[Audio]: Set audio input rate to: %.2f Hz.\n",
audio_st->input);
}
runloop_st->force_nonblock = false;
if (input_fps > 0.0)
{
float timing_skew_hz = video_refresh_rate;
if (video_st->crt_switching_active)
timing_skew_hz = input_fps;
video_st->core_hz = input_fps;
if (!video_driver_monitor_adjust_system_rates(
timing_skew_hz,
video_refresh_rate,
vrr_runloop_enable,
audio_max_timing_skew,
input_fps))
{
/* We won't be able to do VSync reliably
when game FPS > monitor FPS. */
runloop_st->force_nonblock = true;
RARCH_LOG("[Video]: Game FPS > Monitor FPS. Cannot rely on VSync.\n");
if (VIDEO_DRIVER_GET_PTR_INTERNAL(video_st))
{
if (video_st->current_video->set_nonblock_state)
video_st->current_video->set_nonblock_state(
video_st->data, true,
video_driver_test_all_flags(GFX_CTX_FLAGS_ADAPTIVE_VSYNC) &&
video_adaptive_vsync,
video_swap_interval
);
}
return;
}
}
if (VIDEO_DRIVER_GET_PTR_INTERNAL(video_st))
driver_set_nonblock_state();
}
/**
* driver_set_nonblock_state:
*
* Sets audio and video drivers to nonblock state (if enabled).
*
* If nonblock state is false, sets
* blocking state for both audio and video drivers instead.
**/
void driver_set_nonblock_state(void)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
input_driver_state_t
*input_st = input_state_get_ptr();
audio_driver_state_t
*audio_st = audio_state_get_ptr();
video_driver_state_t
*video_st = video_state_get_ptr();
bool enable = input_st ?
input_st->nonblocking_flag : false;
settings_t *settings = config_get_ptr();
bool audio_sync = settings->bools.audio_sync;
bool video_vsync = settings->bools.video_vsync;
bool adaptive_vsync = settings->bools.video_adaptive_vsync;
unsigned swap_interval = settings->uints.video_swap_interval;
bool video_driver_active = video_st->active;
bool audio_driver_active = audio_st->active;
bool runloop_force_nonblock = runloop_st->force_nonblock;
/* Only apply non-block-state for video if we're using vsync. */
if (video_driver_active && VIDEO_DRIVER_GET_PTR_INTERNAL(video_st))
{
if (video_st->current_video->set_nonblock_state)
{
bool video_nonblock = enable;
if (!video_vsync || runloop_force_nonblock)
video_nonblock = true;
video_st->current_video->set_nonblock_state(video_st->data,
video_nonblock,
video_driver_test_all_flags(GFX_CTX_FLAGS_ADAPTIVE_VSYNC) &&
adaptive_vsync, swap_interval);
}
}
if (audio_driver_active && audio_st->context_audio_data)
audio_st->current_audio->set_nonblock_state(
audio_st->context_audio_data,
audio_sync ? enable : true);
audio_st->chunk_size = enable
? audio_st->chunk_nonblock_size
: audio_st->chunk_block_size;
}
void drivers_init(
settings_t *settings,
int flags,
bool verbosity_enabled)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
audio_driver_state_t
*audio_st = audio_state_get_ptr();
input_driver_state_t
*input_st = input_state_get_ptr();
video_driver_state_t
*video_st = video_state_get_ptr();
#ifdef HAVE_MENU
struct menu_state *menu_st = menu_state_get_ptr();
#endif
camera_driver_state_t
*camera_st = camera_state_get_ptr();
location_driver_state_t
*location_st = location_state_get_ptr();
bool video_is_threaded = VIDEO_DRIVER_IS_THREADED_INTERNAL(video_st);
gfx_display_t *p_disp = disp_get_ptr();
#if defined(HAVE_GFX_WIDGETS)
bool video_font_enable = settings->bools.video_font_enable;
bool menu_enable_widgets = settings->bools.menu_enable_widgets;
/* By default, we want display widgets to persist through driver reinits. */
dispwidget_get_ptr()->persisting = true;
#endif
#ifdef HAVE_MENU
/* By default, we want the menu to persist through driver reinits. */
if (menu_st)
menu_st->data_own = true;
#endif
if (flags & (DRIVER_VIDEO_MASK | DRIVER_AUDIO_MASK))
driver_adjust_system_rates(
settings->bools.vrr_runloop_enable,
settings->floats.video_refresh_rate,
settings->floats.audio_max_timing_skew,
settings->bools.video_adaptive_vsync,
settings->uints.video_swap_interval
);
/* Initialize video driver */
if (flags & DRIVER_VIDEO_MASK)
{
struct retro_hw_render_callback *hwr =
VIDEO_DRIVER_GET_HW_CONTEXT_INTERNAL(video_st);
video_st->frame_time_count = 0;
video_driver_lock_new();
#ifdef HAVE_VIDEO_FILTER
video_driver_filter_free();
#endif
video_driver_set_cached_frame_ptr(NULL);
if (!video_driver_init_internal(&video_is_threaded,
verbosity_enabled))
retroarch_fail(1, "video_driver_init_internal()");
if (!video_st->cache_context_ack
&& hwr->context_reset)
hwr->context_reset();
video_st->cache_context_ack = false;
runloop_st->frame_time_last = 0;
}
/* Initialize audio driver */
if (flags & DRIVER_AUDIO_MASK)
{
audio_driver_init_internal(
settings,
audio_st->callback.callback != NULL);
if ( audio_st->current_audio &&
audio_st->current_audio->device_list_new &&
audio_st->context_audio_data)
audio_st->devices_list = (struct string_list*)
audio_st->current_audio->device_list_new(
audio_st->context_audio_data);
}
if (flags & DRIVER_CAMERA_MASK)
{
/* Only initialize camera driver if we're ever going to use it. */
if (camera_st->active)
{
/* Resource leaks will follow if camera is initialized twice. */
if (!camera_st->data)
{
if (!camera_driver_find_driver("camera driver",
verbosity_enabled))
retroarch_fail(1, "find_camera_driver()");
if (camera_st->driver)
{
camera_st->data = camera_st->driver->init(
*settings->arrays.camera_device ?
settings->arrays.camera_device : NULL,
camera_st->cb.caps,
settings->uints.camera_width ?
settings->uints.camera_width : camera_st->cb.width,
settings->uints.camera_height ?
settings->uints.camera_height : camera_st->cb.height);
if (!camera_st->data)
{
RARCH_ERR("Failed to initialize camera driver. Will continue without camera.\n");
camera_st->active = false;
}
if (camera_st->cb.initialized)
camera_st->cb.initialized();
}
}
}
}
if (flags & DRIVER_BLUETOOTH_MASK)
bluetooth_driver_ctl(RARCH_BLUETOOTH_CTL_INIT, NULL);
if ((flags & DRIVER_WIFI_MASK))
wifi_driver_ctl(RARCH_WIFI_CTL_INIT, NULL);
if (flags & DRIVER_LOCATION_MASK)
{
/* Only initialize location driver if we're ever going to use it. */
if (location_st->active)
if (!init_location(&runloop_state_get_ptr()->system,
settings, verbosity_is_enabled()))
location_st->active = false;
}
core_info_init_current_core();
#if defined(HAVE_GFX_WIDGETS)
/* Note that we only enable widgets if 'video_font_enable'
* is true. 'video_font_enable' corresponds to the generic
* 'On-Screen Notifications' setting, which should serve as
* a global notifications on/off toggle switch */
if (video_font_enable &&
menu_enable_widgets &&
video_driver_has_widgets())
{
bool rarch_force_fullscreen = video_st->force_fullscreen;
bool video_is_fullscreen = settings->bools.video_fullscreen ||
rarch_force_fullscreen;
dispwidget_get_ptr()->active= gfx_widgets_init(
p_disp,
anim_get_ptr(),
settings,
(uintptr_t)&dispwidget_get_ptr()->active,
video_is_threaded,
video_st->width,
video_st->height,
video_is_fullscreen,
settings->paths.directory_assets,
settings->paths.path_font);
}
else
#endif
{
gfx_display_init_first_driver(p_disp, video_is_threaded);
}
#ifdef HAVE_MENU
if (flags & DRIVER_VIDEO_MASK)
{
/* Initialize menu driver */
if (flags & DRIVER_MENU_MASK)
{
if (!menu_driver_init(video_is_threaded))
RARCH_ERR("Unable to init menu driver.\n");
#ifdef HAVE_LIBRETRODB
menu_explore_context_init();
#endif
}
}
/* Initialising the menu driver will also initialise
* core info - if we are not initialising the menu
* driver, must initialise core info 'by hand' */
if (!(flags & DRIVER_VIDEO_MASK) ||
!(flags & DRIVER_MENU_MASK))
{
command_event(CMD_EVENT_CORE_INFO_INIT, NULL);
command_event(CMD_EVENT_LOAD_CORE_PERSIST, NULL);
}
#else
/* Qt uses core info, even if the menu is disabled */
command_event(CMD_EVENT_CORE_INFO_INIT, NULL);
command_event(CMD_EVENT_LOAD_CORE_PERSIST, NULL);
#endif
/* Keep non-throttled state as good as possible. */
if (flags & (DRIVER_VIDEO_MASK | DRIVER_AUDIO_MASK))
if (input_st && input_st->nonblocking_flag)
driver_set_nonblock_state();
/* Initialize LED driver */
if (flags & DRIVER_LED_MASK)
led_driver_init(settings->arrays.led_driver);
/* Initialize MIDI driver */
if (flags & DRIVER_MIDI_MASK)
midi_driver_init(settings);
#ifdef HAVE_LAKKA
cpu_scaling_driver_init();
#endif
}
void driver_uninit(int flags)
{
runloop_state_t *runloop_st = runloop_state_get_ptr();
video_driver_state_t
*video_st = video_state_get_ptr();
camera_driver_state_t
*camera_st = camera_state_get_ptr();
core_info_deinit_list();
core_info_free_current_core();
#if defined(HAVE_GFX_WIDGETS)
/* This absolutely has to be done before video_driver_free_internal()
* is called/completes, otherwise certain menu drivers
* (e.g. Vulkan) will segfault */
if (dispwidget_get_ptr()->inited)
{
gfx_widgets_deinit(dispwidget_get_ptr()->persisting);
dispwidget_get_ptr()->active = false;
}
#endif
#ifdef HAVE_MENU
if (flags & DRIVER_MENU_MASK)
{
#ifdef HAVE_LIBRETRODB
menu_explore_context_deinit();
#endif
menu_driver_ctl(RARCH_MENU_CTL_DEINIT, NULL);
}
#endif
if ((flags & DRIVER_LOCATION_MASK))
uninit_location(&runloop_st->system);
if ((flags & DRIVER_CAMERA_MASK))
{
if (camera_st->data && camera_st->driver)
{
if (camera_st->cb.deinitialized)
camera_st->cb.deinitialized();
if (camera_st->driver->free)
camera_st->driver->free(camera_st->data);
}
camera_st->data = NULL;
}
if ((flags & DRIVER_BLUETOOTH_MASK))
bluetooth_driver_ctl(RARCH_BLUETOOTH_CTL_DEINIT, NULL);
if ((flags & DRIVER_WIFI_MASK))
wifi_driver_ctl(RARCH_WIFI_CTL_DEINIT, NULL);
if (flags & DRIVER_LED)
led_driver_free();
if (flags & DRIVERS_VIDEO_INPUT)
{
video_driver_free_internal();
VIDEO_DRIVER_LOCK_FREE(video_st);
video_st->data = NULL;
video_driver_set_cached_frame_ptr(NULL);
}
if (flags & DRIVER_AUDIO_MASK)
audio_driver_deinit();
if ((flags & DRIVER_VIDEO_MASK))
video_st->data = NULL;
if ((flags & DRIVER_INPUT_MASK))
input_state_get_ptr()->current_data = NULL;
if ((flags & DRIVER_AUDIO_MASK))
audio_state_get_ptr()->context_audio_data = NULL;
if (flags & DRIVER_MIDI_MASK)
midi_driver_free();
#ifdef HAVE_LAKKA
cpu_scaling_driver_free();
#endif
}
void retroarch_deinit_drivers(struct retro_callbacks *cbs)
{
input_driver_state_t *input_st = input_state_get_ptr();
video_driver_state_t *video_st = video_state_get_ptr();
camera_driver_state_t *camera_st= camera_state_get_ptr();
location_driver_state_t
*location_st = location_state_get_ptr();
runloop_state_t *runloop_st = runloop_state_get_ptr();
#if defined(HAVE_GFX_WIDGETS)
/* Tear down display widgets no matter what
* in case the handle is lost in the threaded
* video driver in the meantime
* (breaking video_driver_has_widgets) */
if (dispwidget_get_ptr()->inited)
{
gfx_widgets_deinit(
dispwidget_get_ptr()->persisting);
dispwidget_get_ptr()->active = false;
}
#endif
#if defined(HAVE_CRTSWITCHRES)
/* Switchres deinit */
if (video_st->crt_switching_active)
{
#if defined(DEBUG)
RARCH_LOG("[CRT]: Getting video info\n");
RARCH_LOG("[CRT]: About to destroy SR\n");
#endif
crt_destroy_modes(&video_st->crt_switch_st);
}
#endif
/* Video */
video_display_server_destroy();
video_st->use_rgba = false;
video_st->hdr_support = false;
video_st->active = false;
video_st->cache_context = false;
video_st->cache_context_ack = false;
video_st->record_gpu_buffer = NULL;
video_st->current_video = NULL;
video_driver_set_cached_frame_ptr(NULL);
/* Audio */
audio_state_get_ptr()->active = false;
audio_state_get_ptr()->current_audio = NULL;
/* Input */
input_st->keyboard_linefeed_enable = false;
input_st->block_hotkey = false;
input_st->block_libretro_input = false;
if (input_st)
input_st->nonblocking_flag = false;
memset(&input_st->turbo_btns, 0, sizeof(turbo_buttons_t));
memset(&input_st->analog_requested, 0,
sizeof(input_st->analog_requested));
input_st->current_driver = NULL;
#ifdef HAVE_MENU
menu_driver_destroy(
menu_state_get_ptr());
#endif
location_st->active = false;
destroy_location();
/* Camera */
camera_st->active = false;
camera_st->driver = NULL;
camera_st->data = NULL;
bluetooth_driver_ctl(RARCH_BLUETOOTH_CTL_DESTROY, NULL);
wifi_driver_ctl(RARCH_WIFI_CTL_DESTROY, NULL);
cbs->frame_cb = retro_frame_null;
cbs->poll_cb = retro_input_poll_null;
cbs->sample_cb = NULL;
cbs->sample_batch_cb = NULL;
cbs->state_cb = NULL;
runloop_st->current_core.inited = false;
}
bool driver_ctl(enum driver_ctl_state state, void *data)
{
driver_ctx_info_t *drv = (driver_ctx_info_t*)data;
switch (state)
{
case RARCH_DRIVER_CTL_SET_REFRESH_RATE:
{
float *hz = (float*)data;
audio_driver_state_t
*audio_st = audio_state_get_ptr();
settings_t *settings = config_get_ptr();
unsigned
audio_output_sample_rate = settings->uints.audio_output_sample_rate;
bool vrr_runloop_enable = settings->bools.vrr_runloop_enable;
float video_refresh_rate = settings->floats.video_refresh_rate;
float audio_max_timing_skew = settings->floats.audio_max_timing_skew;
bool video_adaptive_vsync = settings->bools.video_adaptive_vsync;
unsigned video_swap_interval = settings->uints.video_swap_interval;
video_monitor_set_refresh_rate(*hz);
/* Sets audio monitor rate to new value. */
audio_st->source_ratio_original =
audio_st->source_ratio_current =
(double)audio_output_sample_rate / audio_st->input;
driver_adjust_system_rates(
vrr_runloop_enable,
video_refresh_rate,
audio_max_timing_skew,
video_adaptive_vsync,
video_swap_interval
);
}
break;
case RARCH_DRIVER_CTL_FIND_FIRST:
if (!drv)
return false;
find_driver_nonempty(drv->label, 0, drv->s, drv->len);
break;
case RARCH_DRIVER_CTL_FIND_LAST:
if (!drv)
return false;
driver_find_last(drv->label, drv->s, drv->len);
break;
case RARCH_DRIVER_CTL_FIND_PREV:
if (!drv)
return false;
return driver_find_prev(drv->label, drv->s, drv->len);
case RARCH_DRIVER_CTL_FIND_NEXT:
if (!drv)
return false;
return driver_find_next(drv->label, drv->s, drv->len);
case RARCH_DRIVER_CTL_NONE:
default:
break;
}
return true;
}