diff --git a/core_info.c b/core_info.c index 0cfd561497..a5acb74c23 100644 --- a/core_info.c +++ b/core_info.c @@ -506,6 +506,42 @@ static core_info_list_t *core_info_list_new(const char *path, return core_info_list; } +/* Shallow-copies internal state. + * + * Data in *info is invalidated when the + * core_info_list is freed. */ +bool core_info_list_get_info(core_info_list_t *core_info_list, + core_info_t *out_info, const char *path) +{ + size_t i; + const char *core_filename = NULL; + + if (!core_info_list || !out_info || string_is_empty(path)) + return false; + + core_filename = path_basename(path); + if (string_is_empty(core_filename)) + return false; + + memset(out_info, 0, sizeof(*out_info)); + + for (i = 0; i < core_info_list->count; i++) + { + const core_info_t *info = &core_info_list->list[i]; + + if (!info || (info->core_file_id.len == 0)) + continue; + + if (!strncmp(info->core_file_id.str, core_filename, info->core_file_id.len)) + { + *out_info = *info; + return true; + } + } + + return false; +} + #ifdef HAVE_COMPRESSION static bool core_info_does_support_any_file(const core_info_t *core, const struct string_list *list) @@ -627,125 +663,6 @@ static bool core_info_list_update_missing_firmware_internal( return true; } -static int core_info_qsort_func_path(const core_info_t *a, - const core_info_t *b) -{ - if (!a || !b) - return 0; - - if (string_is_empty(a->path) || string_is_empty(b->path)) - return 0; - - return strcasecmp(a->path, b->path); -} - -static int core_info_qsort_func_display_name(const core_info_t *a, - const core_info_t *b) -{ - if (!a || !b) - return 0; - - if (string_is_empty(a->display_name) || string_is_empty(b->display_name)) - return 0; - - return strcasecmp(a->display_name, b->display_name); -} - -static int core_info_qsort_func_core_name(const core_info_t *a, - const core_info_t *b) -{ - if (!a || !b) - return 0; - - if (string_is_empty(a->core_name) || string_is_empty(b->core_name)) - return 0; - - return strcasecmp(a->core_name, b->core_name); -} - -static int core_info_qsort_func_system_name(const core_info_t *a, - const core_info_t *b) -{ - if (!a || !b) - return 0; - - if (string_is_empty(a->systemname) || string_is_empty(b->systemname)) - return 0; - - return strcasecmp(a->systemname, b->systemname); -} - -static bool core_info_compare_api_version(int sys_major, int sys_minor, int major, int minor, enum compare_op op) -{ - switch (op) - { - case COMPARE_OP_EQUAL: - if (sys_major == major && sys_minor == minor) - return true; - break; - case COMPARE_OP_NOT_EQUAL: - if (!(sys_major == major && sys_minor == minor)) - return true; - break; - case COMPARE_OP_LESS: - if (sys_major < major || (sys_major == major && sys_minor < minor)) - return true; - break; - case COMPARE_OP_LESS_EQUAL: - if (sys_major < major || (sys_major == major && sys_minor <= minor)) - return true; - break; - case COMPARE_OP_GREATER: - if (sys_major > major || (sys_major == major && sys_minor > minor)) - return true; - break; - case COMPARE_OP_GREATER_EQUAL: - if (sys_major > major || (sys_major == major && sys_minor >= minor)) - return true; - break; - default: - break; - } - - return false; -} - -/* Shallow-copies internal state. - * - * Data in *info is invalidated when the - * core_info_list is freed. */ -bool core_info_list_get_info(core_info_list_t *core_info_list, - core_info_t *out_info, const char *path) -{ - size_t i; - const char *core_filename = NULL; - - if (!core_info_list || !out_info || string_is_empty(path)) - return false; - - core_filename = path_basename(path); - if (string_is_empty(core_filename)) - return false; - - memset(out_info, 0, sizeof(*out_info)); - - for (i = 0; i < core_info_list->count; i++) - { - const core_info_t *info = &core_info_list->list[i]; - - if (!info || (info->core_file_id.len == 0)) - continue; - - if (!strncmp(info->core_file_id.str, core_filename, info->core_file_id.len)) - { - *out_info = *info; - return true; - } - } - - return false; -} - void core_info_free_current_core(core_info_state_t *p_coreinfo) { if (p_coreinfo->current) @@ -1195,6 +1112,54 @@ void core_info_free_core_updater_info(core_updater_info_t *info) info = NULL; } +static int core_info_qsort_func_path(const core_info_t *a, + const core_info_t *b) +{ + if (!a || !b) + return 0; + + if (string_is_empty(a->path) || string_is_empty(b->path)) + return 0; + + return strcasecmp(a->path, b->path); +} + +static int core_info_qsort_func_display_name(const core_info_t *a, + const core_info_t *b) +{ + if (!a || !b) + return 0; + + if (string_is_empty(a->display_name) || string_is_empty(b->display_name)) + return 0; + + return strcasecmp(a->display_name, b->display_name); +} + +static int core_info_qsort_func_core_name(const core_info_t *a, + const core_info_t *b) +{ + if (!a || !b) + return 0; + + if (string_is_empty(a->core_name) || string_is_empty(b->core_name)) + return 0; + + return strcasecmp(a->core_name, b->core_name); +} + +static int core_info_qsort_func_system_name(const core_info_t *a, + const core_info_t *b) +{ + if (!a || !b) + return 0; + + if (string_is_empty(a->systemname) || string_is_empty(b->systemname)) + return 0; + + return strcasecmp(a->systemname, b->systemname); +} + void core_info_qsort(core_info_list_t *core_info_list, enum core_info_list_qsort_type qsort_type) { @@ -1239,6 +1204,41 @@ void core_info_qsort(core_info_list_t *core_info_list, } } +static bool core_info_compare_api_version(int sys_major, int sys_minor, int major, int minor, enum compare_op op) +{ + switch (op) + { + case COMPARE_OP_EQUAL: + if (sys_major == major && sys_minor == minor) + return true; + break; + case COMPARE_OP_NOT_EQUAL: + if (!(sys_major == major && sys_minor == minor)) + return true; + break; + case COMPARE_OP_LESS: + if (sys_major < major || (sys_major == major && sys_minor < minor)) + return true; + break; + case COMPARE_OP_LESS_EQUAL: + if (sys_major < major || (sys_major == major && sys_minor <= minor)) + return true; + break; + case COMPARE_OP_GREATER: + if (sys_major > major || (sys_major == major && sys_minor > minor)) + return true; + break; + case COMPARE_OP_GREATER_EQUAL: + if (sys_major > major || (sys_major == major && sys_minor >= minor)) + return true; + break; + default: + break; + } + + return false; +} + bool core_info_hw_api_supported(core_info_t *info) { #ifdef RARCH_INTERNAL diff --git a/gfx/gfx_animation.c b/gfx/gfx_animation.c index d8ab4e6b40..13819f871b 100644 --- a/gfx/gfx_animation.c +++ b/gfx/gfx_animation.c @@ -1042,187 +1042,6 @@ static void gfx_delayed_animation_cb(void *userdata) free(delayed_animation); } -static void gfx_animation_update_time_default( - float *ticker_pixel_increment, - unsigned video_width, unsigned video_height) -{ - /* By default, this should be a NOOP */ -} - -static void gfx_animation_update_time( - gfx_animation_t *p_anim, - retro_time_t current_time, - bool timedate_enable, - unsigned video_width, unsigned video_height, - float _ticker_speed) -{ - const bool ticker_is_active = p_anim->ticker_is_active; - - static retro_time_t last_clock_update = 0; - static retro_time_t last_ticker_update = 0; - static retro_time_t last_ticker_slow_update = 0; - - /* Horizontal smooth ticker parameters */ - static float ticker_pixel_accumulator = 0.0f; - unsigned ticker_pixel_accumulator_uint = 0; - float ticker_pixel_increment = 0.0f; - - /* Vertical (line) smooth ticker parameters */ - static float ticker_pixel_line_accumulator = 0.0f; - unsigned ticker_pixel_line_accumulator_uint = 0; - float ticker_pixel_line_increment = 0.0f; - - /* Adjust ticker speed */ - float speed_factor = - (_ticker_speed > 0.0001f) ? _ticker_speed : 1.0f; - unsigned ticker_speed = - (unsigned)(((float)TICKER_SPEED / speed_factor) + 0.5); - unsigned ticker_slow_speed = - (unsigned)(((float)TICKER_SLOW_SPEED / speed_factor) + 0.5); - - /* Note: cur_time & old_time are in us (microseconds), - * delta_time is in ms */ - p_anim->cur_time = current_time; - p_anim->delta_time = (p_anim->old_time == 0) - ? 0.0f - : (float)(p_anim->cur_time - p_anim->old_time) / 1000.0f; - p_anim->old_time = p_anim->cur_time; - - if (((p_anim->cur_time - last_clock_update) > 1000000) /* 1000000 us == 1 second */ - && timedate_enable) - { - p_anim->animation_is_active = true; - last_clock_update = p_anim->cur_time; - } - - if (ticker_is_active) - { - /* Update non-smooth ticker indices */ - if (p_anim->cur_time - last_ticker_update >= ticker_speed) - { - p_anim->ticker_idx++; - last_ticker_update = p_anim->cur_time; - } - - if (p_anim->cur_time - last_ticker_slow_update >= ticker_slow_speed) - { - p_anim->ticker_slow_idx++; - last_ticker_slow_update = p_anim->cur_time; - } - - /* Pixel tickers (horizontal + vertical/line) update - * every frame (regardless of time delta), so require - * special handling */ - - /* > Get base increment size (+1 every ticker_pixel_period ms) */ - ticker_pixel_increment = p_anim->delta_time / ticker_pixel_period; - - /* > Apply ticker speed adjustment */ - ticker_pixel_increment *= speed_factor; - - /* At this point we diverge: - * > Vertical (line) ticker is based upon text - * characteristics (number of characters per - * line) - it is therefore independent of display - * size/scaling, so speed-adjusted pixel increment - * is used directly */ - ticker_pixel_line_increment = ticker_pixel_increment; - - /* > Horizontal ticker is based upon physical line - * width - it is therefore very much dependent upon - * display size/scaling. Each menu driver is free - * to handle video scaling as it pleases - a callback - * function set by the menu driver is thus used to - * perform menu-specific scaling adjustments */ - update_time_callback(&ticker_pixel_increment, - video_width, video_height); - - /* > Update accumulators */ - ticker_pixel_accumulator += ticker_pixel_increment; - ticker_pixel_accumulator_uint = (unsigned)ticker_pixel_accumulator; - - ticker_pixel_line_accumulator += ticker_pixel_line_increment; - ticker_pixel_line_accumulator_uint = (unsigned)ticker_pixel_line_accumulator; - - /* > Check whether we've accumulated enough - * for an idx update */ - if (ticker_pixel_accumulator_uint > 0) - { - p_anim->ticker_pixel_idx += ticker_pixel_accumulator_uint; - ticker_pixel_accumulator -= (float)ticker_pixel_accumulator_uint; - } - - if (ticker_pixel_accumulator_uint > 0) - { - p_anim->ticker_pixel_line_idx += ticker_pixel_line_accumulator_uint; - ticker_pixel_line_accumulator -= (float)ticker_pixel_line_accumulator_uint; - } - } -} - -static void build_ticker_loop_string( - const char* src_str, const char *spacer, - unsigned char_offset1, unsigned num_chars1, - unsigned char_offset2, unsigned num_chars2, - unsigned char_offset3, unsigned num_chars3, - char *dest_str, size_t dest_str_len) -{ - char tmp[PATH_MAX_LENGTH]; - - tmp[0] = '\0'; - dest_str[0] = '\0'; - - /* Copy 'trailing' chunk of source string, if required */ - if (num_chars1 > 0) - utf8cpy( - dest_str, dest_str_len, - utf8skip(src_str, char_offset1), num_chars1); - - /* Copy chunk of spacer string, if required */ - if (num_chars2 > 0) - { - utf8cpy( - tmp, sizeof(tmp), - utf8skip(spacer, char_offset2), num_chars2); - - strlcat(dest_str, tmp, dest_str_len); - } - - /* Copy 'leading' chunk of source string, if required */ - if (num_chars3 > 0) - { - utf8cpy( - tmp, sizeof(tmp), - utf8skip(src_str, char_offset3), num_chars3); - - strlcat(dest_str, tmp, dest_str_len); - } -} - -static void build_line_ticker_string( - size_t num_display_lines, size_t line_offset, - struct string_list *lines, - char *dest_str, size_t dest_str_len) -{ - size_t i; - - for (i = 0; i < num_display_lines; i++) - { - size_t offset = i + line_offset; - size_t line_index = offset % (lines->size + 1); - bool line_valid = true; - - if (line_index >= lines->size) - line_valid = false; - - if (line_valid) - strlcat(dest_str, lines->elems[line_index].data, dest_str_len); - - if (i < num_display_lines - 1) - strlcat(dest_str, "\n", dest_str_len); - } -} - void gfx_animation_push_delayed( unsigned delay, gfx_animation_ctx_entry_t *entry) { @@ -1387,6 +1206,13 @@ bool gfx_animation_push(gfx_animation_ctx_entry_t *entry) return true; } +static void gfx_animation_update_time_default( + float *ticker_pixel_increment, + unsigned video_width, unsigned video_height) +{ + /* By default, this should be a NOOP */ +} + void gfx_animation_set_update_time_cb(update_time_cb cb) { update_time_callback = cb; @@ -1397,6 +1223,117 @@ void gfx_animation_unset_update_time_cb(void) update_time_callback = gfx_animation_update_time_default; } +static void gfx_animation_update_time( + gfx_animation_t *p_anim, + retro_time_t current_time, + bool timedate_enable, + unsigned video_width, unsigned video_height, + float _ticker_speed) +{ + const bool ticker_is_active = p_anim->ticker_is_active; + + static retro_time_t last_clock_update = 0; + static retro_time_t last_ticker_update = 0; + static retro_time_t last_ticker_slow_update = 0; + + /* Horizontal smooth ticker parameters */ + static float ticker_pixel_accumulator = 0.0f; + unsigned ticker_pixel_accumulator_uint = 0; + float ticker_pixel_increment = 0.0f; + + /* Vertical (line) smooth ticker parameters */ + static float ticker_pixel_line_accumulator = 0.0f; + unsigned ticker_pixel_line_accumulator_uint = 0; + float ticker_pixel_line_increment = 0.0f; + + /* Adjust ticker speed */ + float speed_factor = + (_ticker_speed > 0.0001f) ? _ticker_speed : 1.0f; + unsigned ticker_speed = + (unsigned)(((float)TICKER_SPEED / speed_factor) + 0.5); + unsigned ticker_slow_speed = + (unsigned)(((float)TICKER_SLOW_SPEED / speed_factor) + 0.5); + + /* Note: cur_time & old_time are in us (microseconds), + * delta_time is in ms */ + p_anim->cur_time = current_time; + p_anim->delta_time = (p_anim->old_time == 0) + ? 0.0f + : (float)(p_anim->cur_time - p_anim->old_time) / 1000.0f; + p_anim->old_time = p_anim->cur_time; + + if (((p_anim->cur_time - last_clock_update) > 1000000) /* 1000000 us == 1 second */ + && timedate_enable) + { + p_anim->animation_is_active = true; + last_clock_update = p_anim->cur_time; + } + + if (ticker_is_active) + { + /* Update non-smooth ticker indices */ + if (p_anim->cur_time - last_ticker_update >= ticker_speed) + { + p_anim->ticker_idx++; + last_ticker_update = p_anim->cur_time; + } + + if (p_anim->cur_time - last_ticker_slow_update >= ticker_slow_speed) + { + p_anim->ticker_slow_idx++; + last_ticker_slow_update = p_anim->cur_time; + } + + /* Pixel tickers (horizontal + vertical/line) update + * every frame (regardless of time delta), so require + * special handling */ + + /* > Get base increment size (+1 every ticker_pixel_period ms) */ + ticker_pixel_increment = p_anim->delta_time / ticker_pixel_period; + + /* > Apply ticker speed adjustment */ + ticker_pixel_increment *= speed_factor; + + /* At this point we diverge: + * > Vertical (line) ticker is based upon text + * characteristics (number of characters per + * line) - it is therefore independent of display + * size/scaling, so speed-adjusted pixel increment + * is used directly */ + ticker_pixel_line_increment = ticker_pixel_increment; + + /* > Horizontal ticker is based upon physical line + * width - it is therefore very much dependent upon + * display size/scaling. Each menu driver is free + * to handle video scaling as it pleases - a callback + * function set by the menu driver is thus used to + * perform menu-specific scaling adjustments */ + update_time_callback(&ticker_pixel_increment, + video_width, video_height); + + /* > Update accumulators */ + ticker_pixel_accumulator += ticker_pixel_increment; + ticker_pixel_accumulator_uint = (unsigned)ticker_pixel_accumulator; + + ticker_pixel_line_accumulator += ticker_pixel_line_increment; + ticker_pixel_line_accumulator_uint = (unsigned)ticker_pixel_line_accumulator; + + /* > Check whether we've accumulated enough + * for an idx update */ + if (ticker_pixel_accumulator_uint > 0) + { + p_anim->ticker_pixel_idx += ticker_pixel_accumulator_uint; + ticker_pixel_accumulator -= (float)ticker_pixel_accumulator_uint; + } + + if (ticker_pixel_accumulator_uint > 0) + { + p_anim->ticker_pixel_line_idx += ticker_pixel_line_accumulator_uint; + ticker_pixel_line_accumulator -= (float)ticker_pixel_line_accumulator_uint; + } + } +} + bool gfx_animation_update( retro_time_t current_time, bool timedate_enable, @@ -1472,6 +1409,45 @@ bool gfx_animation_update( return p_anim->animation_is_active; } +static void build_ticker_loop_string( + const char* src_str, const char *spacer, + unsigned char_offset1, unsigned num_chars1, + unsigned char_offset2, unsigned num_chars2, + unsigned char_offset3, unsigned num_chars3, + char *dest_str, size_t dest_str_len) +{ + char tmp[PATH_MAX_LENGTH]; + + tmp[0] = '\0'; + dest_str[0] = '\0'; + + /* Copy 'trailing' chunk of source string, if required */ + if (num_chars1 > 0) + utf8cpy( + dest_str, dest_str_len, + utf8skip(src_str, char_offset1), num_chars1); + + /* Copy chunk of spacer string, if required */ + if (num_chars2 > 0) + { + utf8cpy( + tmp, sizeof(tmp), + utf8skip(spacer, char_offset2), num_chars2); + + strlcat(dest_str, tmp, dest_str_len); + } + + /* Copy 'leading' chunk of source string, if required */ + if (num_chars3 > 0) + { + utf8cpy( + tmp, sizeof(tmp), + utf8skip(src_str, char_offset3), num_chars3); + + strlcat(dest_str, tmp, dest_str_len); + } +} + bool gfx_animation_ticker(gfx_animation_ctx_ticker_t *ticker) { gfx_animation_t *p_anim = anim_get_ptr(); @@ -1937,6 +1913,30 @@ end: return is_active; } +static void build_line_ticker_string( + size_t num_display_lines, size_t line_offset, + struct string_list *lines, + char *dest_str, size_t dest_str_len) +{ + size_t i; + + for (i = 0; i < num_display_lines; i++) + { + size_t offset = i + line_offset; + size_t line_index = offset % (lines->size + 1); + bool line_valid = true; + + if (line_index >= lines->size) + line_valid = false; + + if (line_valid) + strlcat(dest_str, lines->elems[line_index].data, dest_str_len); + + if (i < num_display_lines - 1) + strlcat(dest_str, "\n", dest_str_len); + } +} + bool gfx_animation_line_ticker(gfx_animation_ctx_line_ticker_t *line_ticker) { char *wrapped_str = NULL; diff --git a/gfx/gfx_display.c b/gfx/gfx_display.c index 95e7145d3f..143f6560e6 100644 --- a/gfx/gfx_display.c +++ b/gfx/gfx_display.c @@ -265,6 +265,7 @@ static bool gfx_display_check_compatibility( return false; } + void gfx_display_set_driver_id(enum menu_driver_id_type type) { gfx_display_t *p_disp = disp_get_ptr(); diff --git a/gfx/gfx_widgets.c b/gfx/gfx_widgets.c index 0579455e75..97f363e59f 100644 --- a/gfx/gfx_widgets.c +++ b/gfx/gfx_widgets.c @@ -108,11 +108,91 @@ static const char }; /* Forward declarations */ -static void gfx_widgets_hourglass_tick(void *userdata); #ifdef HAVE_MENU bool menu_driver_get_load_content_animation_data( uintptr_t *icon, char **playlist_name); #endif +static void gfx_widgets_context_reset( + dispgfx_widget_t *p_dispwidget, + bool is_threaded, + unsigned width, unsigned height, bool fullscreen, + const char *dir_assets, char *font_path); +static void gfx_widgets_context_destroy(dispgfx_widget_t *p_dispwidget); +static void gfx_widgets_free(dispgfx_widget_t *p_dispwidget); +static void gfx_widgets_layout(dispgfx_widget_t *p_dispwidget, + bool is_threaded, const char *dir_assets, char *font_path); + +gfx_widget_font_data_t* gfx_widgets_get_font_regular(void *data) +{ + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + return &p_dispwidget->gfx_widget_fonts.regular; +} + +gfx_widget_font_data_t* gfx_widgets_get_font_bold(void *data) +{ + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + return &p_dispwidget->gfx_widget_fonts.bold; +} + +gfx_widget_font_data_t* gfx_widgets_get_font_msg_queue(void *data) +{ + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + return &p_dispwidget->gfx_widget_fonts.msg_queue; +} + +float* gfx_widgets_get_pure_white(void) +{ + return gfx_widgets_pure_white; +} + +float* gfx_widgets_get_backdrop_orig(void) +{ + return gfx_widgets_backdrop_orig; +} + +/* Messages queue */ + +uintptr_t gfx_widgets_get_generic_tag(void *data) +{ + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + return p_dispwidget->gfx_widgets_generic_tag; +} + +unsigned gfx_widgets_get_padding(void *data) +{ + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + return p_dispwidget->simple_widget_padding; +} + +unsigned gfx_widgets_get_height(void *data) +{ + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + return p_dispwidget->simple_widget_height; +} + +unsigned gfx_widgets_get_generic_message_height(void *data) +{ + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + return p_dispwidget->generic_message_height; +} + +unsigned gfx_widgets_get_last_video_width(void *data) +{ + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + return p_dispwidget->last_video_width; +} + +unsigned gfx_widgets_get_last_video_height(void *data) +{ + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + return p_dispwidget->last_video_height; +} + +size_t gfx_widgets_get_msg_queue_size(void *data) +{ + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + return p_dispwidget->current_msgs_size; +} /* Widgets list */ const static gfx_widget_t* const widgets[] = { @@ -125,160 +205,6 @@ const static gfx_widget_t* const widgets[] = { &gfx_widget_libretro_message }; -static INLINE void gfx_widgets_font_bind(gfx_widget_font_data_t *font_data) -{ - font_driver_bind_block(font_data->font, &font_data->raster_block); - font_data->raster_block.carr.coords.vertices = 0; - font_data->usage_count = 0; -} - -static INLINE void gfx_widgets_font_unbind(gfx_widget_font_data_t *font_data) -{ - font_driver_bind_block(font_data->font, NULL); -} - -static INLINE void gfx_widgets_font_free(gfx_widget_font_data_t *font_data) -{ - if (font_data->font) - gfx_display_font_free(font_data->font); - - font_data->font = NULL; - font_data->usage_count = 0; -} - -static void gfx_widgets_msg_queue_free( - dispgfx_widget_t *p_dispwidget, - menu_widget_msg_t *msg) -{ - uintptr_t tag = (uintptr_t)msg; - - if (msg->task_ptr) - { - /* remove the reference the task has of ourself - only if the task is not finished already - (finished tasks are freed before the widget) */ - if (!msg->task_finished && !msg->task_error && !msg->task_cancelled) - msg->task_ptr->frontend_userdata = NULL; - - /* update tasks count */ - p_dispwidget->msg_queue_tasks_count--; - } - - /* Kill all animations */ - gfx_timer_kill(&msg->hourglass_timer); - gfx_animation_kill_by_tag(&tag); - - /* Kill all timers */ - if (msg->expiration_timer_started) - gfx_timer_kill(&msg->expiration_timer); - - /* Free it */ - if (msg->msg) - free(msg->msg); - - if (msg->msg_new) - free(msg->msg_new); - - p_dispwidget->widgets_moving = false; -} - -static void gfx_widgets_free(dispgfx_widget_t *p_dispwidget) -{ - size_t i; - - p_dispwidget->widgets_inited = false; - - for (i = 0; i < ARRAY_SIZE(widgets); i++) - { - const gfx_widget_t* widget = widgets[i]; - - if (widget->free) - widget->free(); - } - - /* Kill all running animations */ - gfx_animation_kill_by_tag( - &p_dispwidget->gfx_widgets_generic_tag); - - /* Purge everything from the fifo */ - if (p_dispwidget->msg_queue) - { - while (fifo_read_avail(p_dispwidget->msg_queue) > 0) - { - menu_widget_msg_t *msg_widget; - - fifo_read(p_dispwidget->msg_queue, - &msg_widget, sizeof(msg_widget)); - - /* Note: gfx_widgets_free() is only called when - * main_exit() is invoked. At this stage, we cannot - * guarantee that any task pointers are valid (the - * task may have been free()'d, but we can't know - * that here) - so all we can do is unset the task - * pointer associated with each message - * > If we don't do this, gfx_widgets_msg_queue_free() - * will generate heap-use-after-free errors */ - msg_widget->task_ptr = NULL; - - gfx_widgets_msg_queue_free(p_dispwidget, msg_widget); - free(msg_widget); - } - - fifo_free(p_dispwidget->msg_queue); - } - p_dispwidget->msg_queue = NULL; - - /* Purge everything from the list */ - SLOCK_LOCK(p_dispwidget->current_msgs_lock); - - p_dispwidget->current_msgs_size = 0; - for (i = 0; i < ARRAY_SIZE(p_dispwidget->current_msgs); i++) - { - menu_widget_msg_t *msg = p_dispwidget->current_msgs[i]; - if (!msg) - continue; - - /* Note: gfx_widgets_free() is only called when - * main_exit() is invoked. At this stage, we cannot - * guarantee that any task pointers are valid (the - * task may have been free()'d, but we can't know - * that here) - so all we can do is unset the task - * pointer associated with each message - * > If we don't do this, gfx_widgets_msg_queue_free() - * will generate heap-use-after-free errors */ - msg->task_ptr = NULL; - - gfx_widgets_msg_queue_free(p_dispwidget, msg); - } - SLOCK_UNLOCK(p_dispwidget->current_msgs_lock); - -#ifdef HAVE_THREADS - slock_free(p_dispwidget->current_msgs_lock); - p_dispwidget->current_msgs_lock = NULL; -#endif - - p_dispwidget->msg_queue_tasks_count = 0; - - /* Font */ - video_coord_array_free( - &p_dispwidget->gfx_widget_fonts.regular.raster_block.carr); - video_coord_array_free( - &p_dispwidget->gfx_widget_fonts.bold.raster_block.carr); - video_coord_array_free( - &p_dispwidget->gfx_widget_fonts.msg_queue.raster_block.carr); - - font_driver_bind_block(NULL, NULL); -} - -#ifdef HAVE_MENU -static void gfx_widgets_end_load_content_animation(void *userdata) -{ -#if 0 - task_load_content_resume(); /* TODO: Restore that */ -#endif -} -#endif - static void msg_widget_msg_transition_animation_done(void *userdata) { menu_widget_msg_t *msg = (menu_widget_msg_t*)userdata; @@ -293,6 +219,196 @@ static void msg_widget_msg_transition_animation_done(void *userdata) msg->msg_transition_animation = 0.0f; } +void gfx_widgets_msg_queue_push( + void *data, + retro_task_t *task, + const char *msg, + unsigned duration, + char *title, + enum message_queue_icon icon, + enum message_queue_category category, + unsigned prio, bool flush, + bool menu_is_alive) +{ + menu_widget_msg_t *msg_widget = NULL; + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + + if (fifo_write_avail(p_dispwidget->msg_queue) > 0) + { + /* Get current msg if it exists */ + if (task && task->frontend_userdata) + { + msg_widget = (menu_widget_msg_t*)task->frontend_userdata; + /* msg_widgets can be passed between tasks */ + msg_widget->task_ptr = task; + } + + /* Spawn a new notification */ + if (!msg_widget) + { + const char *title = msg; + + msg_widget = (menu_widget_msg_t*)calloc(1, sizeof(*msg_widget)); + + if (task) + title = task->title; + + msg_widget->duration = duration; + msg_widget->offset_y = 0; + msg_widget->alpha = 1.0f; + + msg_widget->dying = false; + msg_widget->expired = false; + + msg_widget->expiration_timer = 0; + msg_widget->task_ptr = task; + msg_widget->expiration_timer_started = false; + + msg_widget->msg_new = NULL; + msg_widget->msg_transition_animation = 0.0f; + + msg_widget->text_height = 0; + + if (p_dispwidget->msg_queue_has_icons) + { + msg_widget->unfolded = false; + msg_widget->unfolding = false; + msg_widget->unfold = 0.0f; + } + else + { + msg_widget->unfolded = true; + msg_widget->unfolding = false; + msg_widget->unfold = 1.0f; + } + + if (task) + { + msg_widget->msg = strdup(title); + msg_widget->msg_new = strdup(title); + msg_widget->msg_len = (unsigned)strlen(title); + + msg_widget->task_error = !string_is_empty(task->error); + msg_widget->task_cancelled = task->cancelled; + msg_widget->task_finished = task->finished; + msg_widget->task_progress = task->progress; + msg_widget->task_ident = task->ident; + msg_widget->task_title_ptr = task->title; + msg_widget->task_count = 1; + + msg_widget->unfolded = true; + + msg_widget->width = font_driver_get_message_width( + p_dispwidget->gfx_widget_fonts.msg_queue.font, + title, + msg_widget->msg_len, 1) + + p_dispwidget->simple_widget_padding / 2; + + task->frontend_userdata = msg_widget; + + msg_widget->hourglass_rotation = 0; + } + else + { + /* Compute rect width, wrap if necessary */ + /* Single line text > two lines text > two lines + * text with expanded width */ + unsigned title_length = (unsigned)strlen(title); + char *msg = strdup(title); + unsigned width = menu_is_alive + ? p_dispwidget->msg_queue_default_rect_width_menu_alive + : p_dispwidget->msg_queue_default_rect_width; + unsigned text_width = font_driver_get_message_width( + p_dispwidget->gfx_widget_fonts.msg_queue.font, + title, + title_length, + 1); + msg_widget->text_height = p_dispwidget->gfx_widget_fonts.msg_queue.line_height; + + /* Text is too wide, split it into two lines */ + if (text_width > width) + { + /* If the second line is too short, the widget may + * look unappealing - ensure that second line is at + * least 25% of the total width */ + if ((text_width - (text_width >> 2)) < width) + width = text_width - (text_width >> 2); + + word_wrap(msg, msg, (title_length * width) / text_width, + false, 2); + + msg_widget->text_height *= 2; + } + else + width = text_width; + + msg_widget->msg = msg; + msg_widget->msg_len = (unsigned)strlen(msg); + msg_widget->width = width + + p_dispwidget->simple_widget_padding / 2; + } + + fifo_write(p_dispwidget->msg_queue, &msg_widget, sizeof(msg_widget)); + } + /* Update task info */ + else + { + if (msg_widget->expiration_timer_started) + { + gfx_timer_kill(&msg_widget->expiration_timer); + msg_widget->expiration_timer_started = false; + } + + if (!string_is_equal(task->title, msg_widget->msg_new)) + { + unsigned len = (unsigned)strlen(task->title); + unsigned new_width = font_driver_get_message_width( + p_dispwidget->gfx_widget_fonts.msg_queue.font, + task->title, + len, + 1); + + if (msg_widget->msg_new) + { + free(msg_widget->msg_new); + msg_widget->msg_new = NULL; + } + + msg_widget->msg_new = strdup(task->title); + msg_widget->msg_len = len; + msg_widget->task_title_ptr = task->title; + msg_widget->msg_transition_animation = 0; + + if (!task->alternative_look) + { + gfx_animation_ctx_entry_t entry; + + entry.easing_enum = EASING_OUT_QUAD; + entry.tag = (uintptr_t)msg_widget; + entry.duration = MSG_QUEUE_ANIMATION_DURATION*2; + entry.target_value = p_dispwidget->msg_queue_height / 2.0f; + entry.subject = &msg_widget->msg_transition_animation; + entry.cb = msg_widget_msg_transition_animation_done; + entry.userdata = msg_widget; + + gfx_animation_push(&entry); + } + else + msg_widget_msg_transition_animation_done(msg_widget); + + msg_widget->task_count++; + + msg_widget->width = new_width; + } + + msg_widget->task_error = !string_is_empty(task->error); + msg_widget->task_cancelled = task->cancelled; + msg_widget->task_finished = task->finished; + msg_widget->task_progress = task->progress; + } + } +} + static void gfx_widgets_unfold_end(void *userdata) { menu_widget_msg_t *unfold = (menu_widget_msg_t*)userdata; @@ -379,6 +495,42 @@ static void gfx_widgets_msg_queue_move(dispgfx_widget_t *p_dispwidget) SLOCK_UNLOCK(p_dispwidget->current_msgs_lock); } +static void gfx_widgets_msg_queue_free( + dispgfx_widget_t *p_dispwidget, + menu_widget_msg_t *msg) +{ + uintptr_t tag = (uintptr_t)msg; + + if (msg->task_ptr) + { + /* remove the reference the task has of ourself + only if the task is not finished already + (finished tasks are freed before the widget) */ + if (!msg->task_finished && !msg->task_error && !msg->task_cancelled) + msg->task_ptr->frontend_userdata = NULL; + + /* update tasks count */ + p_dispwidget->msg_queue_tasks_count--; + } + + /* Kill all animations */ + gfx_timer_kill(&msg->hourglass_timer); + gfx_animation_kill_by_tag(&tag); + + /* Kill all timers */ + if (msg->expiration_timer_started) + gfx_timer_kill(&msg->expiration_timer); + + /* Free it */ + if (msg->msg) + free(msg->msg); + + if (msg->msg_new) + free(msg->msg_new); + + p_dispwidget->widgets_moving = false; +} + static void gfx_widgets_msg_queue_kill_end(void *userdata) { dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)dispwidget_get_ptr(); @@ -446,307 +598,332 @@ static void gfx_widgets_msg_queue_kill( gfx_widgets_msg_queue_move(p_dispwidget); } -static void gfx_widgets_font_init( - dispgfx_widget_t *p_dispwidget, - gfx_widget_font_data_t *font_data, - bool is_threaded, char *font_path, float font_size) +void gfx_widgets_draw_icon( + void *userdata, + unsigned video_width, + unsigned video_height, + unsigned icon_width, + unsigned icon_height, + uintptr_t texture, + float x, float y, + unsigned width, unsigned height, + float rotation, float scale_factor, + float *color) { - int glyph_width = 0; - float scaled_size = font_size * - p_dispwidget->last_scale_factor; + gfx_display_ctx_rotate_draw_t rotate_draw; + gfx_display_ctx_draw_t draw; + struct video_coords coords; + math_matrix_4x4 mymat; - /* Free existing font */ - if (font_data->font) - { - gfx_display_font_free(font_data->font); - font_data->font = NULL; - } + if (!texture) + return; - /* Get approximate glyph width */ - font_data->glyph_width = scaled_size * (3.0f / 4.0f); + rotate_draw.matrix = &mymat; + rotate_draw.rotation = rotation; + rotate_draw.scale_x = scale_factor; + rotate_draw.scale_y = scale_factor; + rotate_draw.scale_z = 1; + rotate_draw.scale_enable = true; - /* Create font */ - font_data->font = gfx_display_font_file(font_path, scaled_size, is_threaded); + gfx_display_rotate_z(&rotate_draw, userdata); - /* Get font metadata */ - glyph_width = font_driver_get_message_width(font_data->font, "a", 1, 1.0f); - if (glyph_width > 0) - font_data->glyph_width = (float)glyph_width; - font_data->line_height = (float)font_driver_get_line_height(font_data->font, 1.0f); - font_data->line_ascender = (float)font_driver_get_line_ascender(font_data->font, 1.0f); - font_data->line_descender = (float)font_driver_get_line_descender(font_data->font, 1.0f); - font_data->line_centre_offset = (float)font_driver_get_line_centre_offset(font_data->font, 1.0f); + coords.vertices = 4; + coords.vertex = NULL; + coords.tex_coord = NULL; + coords.lut_tex_coord = NULL; + coords.color = color; - font_data->usage_count = 0; + draw.x = x; + draw.y = height - y - icon_height; + draw.width = icon_width; + draw.height = icon_height; + draw.scale_factor = scale_factor; + draw.rotation = rotation; + draw.coords = &coords; + draw.matrix_data = &mymat; + draw.texture = texture; + draw.prim_type = GFX_DISPLAY_PRIM_TRIANGLESTRIP; + draw.pipeline.id = 0; + + gfx_display_draw(&draw, userdata, + video_width, video_height); } -static void gfx_widgets_layout( - dispgfx_widget_t *p_dispwidget, - bool is_threaded, const char *dir_assets, char *font_path) +#ifdef HAVE_TRANSLATE +static void gfx_widgets_draw_icon_blend( + void *userdata, + unsigned video_width, + unsigned video_height, + unsigned icon_width, + unsigned icon_height, + uintptr_t texture, + float x, float y, + unsigned width, unsigned height, + float rotation, float scale_factor, + float *color) { - size_t i; + gfx_display_ctx_rotate_draw_t rotate_draw; + gfx_display_ctx_draw_t draw; + struct video_coords coords; + math_matrix_4x4 mymat; - /* Initialise fonts */ - if (string_is_empty(font_path)) - { - char ozone_path[PATH_MAX_LENGTH]; - char font_file[PATH_MAX_LENGTH]; + if (!texture) + return; - ozone_path[0] = '\0'; - font_file[0] = '\0'; + rotate_draw.matrix = &mymat; + rotate_draw.rotation = rotation; + rotate_draw.scale_x = scale_factor; + rotate_draw.scale_y = scale_factor; + rotate_draw.scale_z = 1; + rotate_draw.scale_enable = true; - /* Base path */ - fill_pathname_join(ozone_path, dir_assets, "ozone", sizeof(ozone_path)); + gfx_display_rotate_z(&rotate_draw, userdata); - /* Create regular font */ - fill_pathname_join(font_file, ozone_path, "regular.ttf", sizeof(font_file)); - gfx_widgets_font_init(p_dispwidget, - &p_dispwidget->gfx_widget_fonts.regular, - is_threaded, font_file, BASE_FONT_SIZE); + coords.vertices = 4; + coords.vertex = NULL; + coords.tex_coord = NULL; + coords.lut_tex_coord = NULL; + coords.color = color; - /* Create bold font */ - fill_pathname_join(font_file, ozone_path, "bold.ttf", sizeof(font_file)); - gfx_widgets_font_init(p_dispwidget, - &p_dispwidget->gfx_widget_fonts.bold, - is_threaded, font_file, BASE_FONT_SIZE); + draw.x = x; + draw.y = height - y - icon_height; + draw.width = icon_width; + draw.height = icon_height; + draw.scale_factor = scale_factor; + draw.rotation = rotation; + draw.coords = &coords; + draw.matrix_data = &mymat; + draw.texture = texture; + draw.prim_type = GFX_DISPLAY_PRIM_TRIANGLESTRIP; + draw.pipeline.id = 0; - /* Create msg_queue font */ - fill_pathname_join(font_file, ozone_path, "regular.ttf", sizeof(font_file)); - gfx_widgets_font_init(p_dispwidget, - &p_dispwidget->gfx_widget_fonts.msg_queue, - is_threaded, font_file, MSG_QUEUE_FONT_SIZE); - } - else - { - /* Load fonts from user-supplied path */ - gfx_widgets_font_init(p_dispwidget, - &p_dispwidget->gfx_widget_fonts.regular, - is_threaded, font_path, BASE_FONT_SIZE); - gfx_widgets_font_init(p_dispwidget, - &p_dispwidget->gfx_widget_fonts.bold, - is_threaded, font_path, BASE_FONT_SIZE); - gfx_widgets_font_init(p_dispwidget, - &p_dispwidget->gfx_widget_fonts.msg_queue, - is_threaded, font_path, MSG_QUEUE_FONT_SIZE); - } - - /* Calculate dimensions */ - p_dispwidget->simple_widget_padding = p_dispwidget->gfx_widget_fonts.regular.line_height * 2.0f/3.0f; - p_dispwidget->simple_widget_height = p_dispwidget->gfx_widget_fonts.regular.line_height + p_dispwidget->simple_widget_padding; - - p_dispwidget->msg_queue_height = p_dispwidget->gfx_widget_fonts.msg_queue.line_height * 2.5f * (BASE_FONT_SIZE / MSG_QUEUE_FONT_SIZE); - - if (p_dispwidget->msg_queue_has_icons) - { - p_dispwidget->msg_queue_icon_size_y = p_dispwidget->msg_queue_height - * 1.2347826087f; /* original image is 280x284 */ - p_dispwidget->msg_queue_icon_size_x = 0.98591549295f * p_dispwidget->msg_queue_icon_size_y; - } - else - { - p_dispwidget->msg_queue_icon_size_x = 0; - p_dispwidget->msg_queue_icon_size_y = 0; - } - - p_dispwidget->msg_queue_spacing = p_dispwidget->msg_queue_height / 3; - p_dispwidget->msg_queue_rect_start_x = p_dispwidget->msg_queue_spacing + p_dispwidget->msg_queue_icon_size_x; - p_dispwidget->msg_queue_internal_icon_size = p_dispwidget->msg_queue_icon_size_y; - p_dispwidget->msg_queue_internal_icon_offset = (p_dispwidget->msg_queue_icon_size_y - p_dispwidget->msg_queue_internal_icon_size) / 2; - p_dispwidget->msg_queue_icon_offset_y = (p_dispwidget->msg_queue_icon_size_y - p_dispwidget->msg_queue_height) / 2; - p_dispwidget->msg_queue_scissor_start_x = p_dispwidget->msg_queue_spacing + p_dispwidget->msg_queue_icon_size_x - (p_dispwidget->msg_queue_icon_size_x * 0.28928571428f); - - if (p_dispwidget->msg_queue_has_icons) - p_dispwidget->msg_queue_regular_padding_x = p_dispwidget->simple_widget_padding / 2; - else - p_dispwidget->msg_queue_regular_padding_x = p_dispwidget->simple_widget_padding; - - p_dispwidget->msg_queue_task_rect_start_x = p_dispwidget->msg_queue_rect_start_x - p_dispwidget->msg_queue_icon_size_x; - - p_dispwidget->msg_queue_task_text_start_x = p_dispwidget->msg_queue_task_rect_start_x + p_dispwidget->msg_queue_height / 2; - - if (!p_dispwidget->gfx_widgets_icons_textures[MENU_WIDGETS_ICON_HOURGLASS]) - p_dispwidget->msg_queue_task_text_start_x -= - p_dispwidget->gfx_widget_fonts.msg_queue.glyph_width * 2.0f; - - p_dispwidget->msg_queue_regular_text_start = p_dispwidget->msg_queue_rect_start_x + p_dispwidget->msg_queue_regular_padding_x; - - p_dispwidget->msg_queue_task_hourglass_x = p_dispwidget->msg_queue_rect_start_x - p_dispwidget->msg_queue_icon_size_x; - - p_dispwidget->generic_message_height = p_dispwidget->gfx_widget_fonts.regular.line_height * 2.0f; - - p_dispwidget->msg_queue_default_rect_width_menu_alive = p_dispwidget - ->gfx_widget_fonts.msg_queue.glyph_width * 40.0f; - p_dispwidget->msg_queue_default_rect_width = p_dispwidget->last_video_width - - p_dispwidget->msg_queue_regular_text_start - (2 * p_dispwidget->simple_widget_padding); - -#ifdef HAVE_MENU - p_dispwidget->load_content_animation_icon_size_initial = - LOAD_CONTENT_ANIMATION_INITIAL_ICON_SIZE * - p_dispwidget->last_scale_factor; - p_dispwidget->load_content_animation_icon_size_target = - LOAD_CONTENT_ANIMATION_TARGET_ICON_SIZE * - p_dispwidget->last_scale_factor; + gfx_display_draw_blend(&draw, userdata, + video_width, video_height); +} #endif - p_dispwidget->divider_width_1px = 1; - if (p_dispwidget->last_scale_factor > 1.0f) - p_dispwidget->divider_width_1px = (unsigned)(p_dispwidget->last_scale_factor + 0.5f); +void gfx_widgets_draw_text( + gfx_widget_font_data_t* font_data, + const char *text, + float x, float y, + int width, int height, + uint32_t color, + enum text_alignment text_align, + bool draw_outside) +{ + if (!font_data || string_is_empty(text)) + return; - for (i = 0; i < ARRAY_SIZE(widgets); i++) - { - const gfx_widget_t* widget = widgets[i]; + gfx_display_draw_text( + font_data->font, + text, + x, y, + width, height, + color, + text_align, + 1.0f, + false, + 0.0f, + draw_outside); - if (widget->layout) - widget->layout(p_dispwidget, - is_threaded, dir_assets, font_path); - } + font_data->usage_count++; } -static void gfx_widgets_context_reset( - dispgfx_widget_t *p_dispwidget, - bool is_threaded, +void gfx_widgets_flush_text( + unsigned video_width, unsigned video_height, + gfx_widget_font_data_t* font_data) +{ + /* Flushing is slow - only do it if font + * has actually been used */ + if (!font_data || (font_data->usage_count == 0)) + return; + + font_driver_flush(video_width, video_height, font_data->font); + font_data->raster_block.carr.coords.vertices = 0; + font_data->usage_count = 0; +} + +float gfx_widgets_get_thumbnail_scale_factor( + const float dst_width, const float dst_height, + const float image_width, const float image_height) +{ + float dst_ratio = dst_width / dst_height; + float image_ratio = image_width / image_height; + + if (dst_ratio > image_ratio) + return (dst_height / image_height); + return (dst_width / image_width); +} + +static void gfx_widgets_start_msg_expiration_timer( + menu_widget_msg_t *msg_widget, unsigned duration) +{ + gfx_timer_ctx_entry_t timer; + + timer.cb = gfx_widgets_msg_queue_expired; + timer.duration = duration; + timer.userdata = msg_widget; + + gfx_timer_start(&msg_widget->expiration_timer, &timer); + + msg_widget->expiration_timer_started = true; +} + +static void gfx_widgets_hourglass_tick(void *userdata); + +static void gfx_widgets_hourglass_end(void *userdata) +{ + gfx_timer_ctx_entry_t timer; + menu_widget_msg_t *msg = (menu_widget_msg_t*)userdata; + + msg->hourglass_rotation = 0.0f; + + timer.cb = gfx_widgets_hourglass_tick; + timer.duration = HOURGLASS_INTERVAL; + timer.userdata = msg; + + gfx_timer_start(&msg->hourglass_timer, &timer); +} + +static void gfx_widgets_hourglass_tick(void *userdata) +{ + gfx_animation_ctx_entry_t entry; + menu_widget_msg_t *msg = (menu_widget_msg_t*)userdata; + uintptr_t tag = (uintptr_t)msg; + + entry.easing_enum = EASING_OUT_QUAD; + entry.tag = tag; + entry.duration = HOURGLASS_DURATION; + entry.target_value = -(2 * M_PI); + entry.subject = &msg->hourglass_rotation; + entry.cb = gfx_widgets_hourglass_end; + entry.userdata = msg; + + gfx_animation_push(&entry); +} + +void gfx_widgets_iterate( + void *data, unsigned width, unsigned height, bool fullscreen, - const char *dir_assets, char *font_path) + const char *dir_assets, char *font_path, + bool is_threaded) { size_t i; - char xmb_path[PATH_MAX_LENGTH]; - char monochrome_png_path[PATH_MAX_LENGTH]; - char gfx_widgets_path[PATH_MAX_LENGTH]; - char theme_path[PATH_MAX_LENGTH]; + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + /* Check whether screen dimensions or menu scale + * factor have changed */ + float scale_factor = ( + gfx_display_get_driver_id() == MENU_DRIVER_ID_XMB) + ? gfx_display_get_widget_pixel_scale(width, height, fullscreen) + : gfx_display_get_widget_dpi_scale(width, height, fullscreen); - xmb_path[0] = '\0'; - monochrome_png_path[0] = '\0'; - gfx_widgets_path[0] = '\0'; - theme_path[0] = '\0'; - - /* Textures paths */ - fill_pathname_join( - gfx_widgets_path, - dir_assets, - "menu_widgets", - sizeof(gfx_widgets_path) - ); - - fill_pathname_join( - xmb_path, - dir_assets, - "xmb", - sizeof(xmb_path) - ); - - /* Monochrome */ - fill_pathname_join( - theme_path, - xmb_path, - "monochrome", - sizeof(theme_path) - ); - - fill_pathname_join( - monochrome_png_path, - theme_path, - "png", - sizeof(monochrome_png_path) - ); - - /* Load textures */ - /* Icons */ - for (i = 0; i < MENU_WIDGETS_ICON_LAST; i++) + if ((scale_factor != p_dispwidget->last_scale_factor) || + (width != p_dispwidget->last_video_width) || + (height != p_dispwidget->last_video_height)) { - gfx_display_reset_textures_list( - gfx_widgets_icons_names[i], - monochrome_png_path, - &p_dispwidget->gfx_widgets_icons_textures[i], - TEXTURE_FILTER_MIPMAP_LINEAR, - NULL, - NULL); + p_dispwidget->last_scale_factor = scale_factor; + p_dispwidget->last_video_width = width; + p_dispwidget->last_video_height = height; + + /* Note: We don't need a full context reset here + * > Just rescale layout, and reset frame time counter */ + gfx_widgets_layout(p_dispwidget, + is_threaded, dir_assets, font_path); + video_driver_monitor_reset(); } - /* Message queue */ - gfx_display_reset_textures_list( - "msg_queue_icon.png", - gfx_widgets_path, - &p_dispwidget->msg_queue_icon, - TEXTURE_FILTER_LINEAR, - NULL, - NULL); - gfx_display_reset_textures_list( - "msg_queue_icon_outline.png", - gfx_widgets_path, - &p_dispwidget->msg_queue_icon_outline, - TEXTURE_FILTER_LINEAR, - NULL, - NULL); - gfx_display_reset_textures_list( - "msg_queue_icon_rect.png", - gfx_widgets_path, - &p_dispwidget->msg_queue_icon_rect, - TEXTURE_FILTER_NEAREST, - NULL, - NULL); - - p_dispwidget->msg_queue_has_icons = - p_dispwidget->msg_queue_icon && - p_dispwidget->msg_queue_icon_outline && - p_dispwidget->msg_queue_icon_rect; - for (i = 0; i < ARRAY_SIZE(widgets); i++) { const gfx_widget_t* widget = widgets[i]; - if (widget->context_reset) - widget->context_reset(is_threaded, width, height, - fullscreen, dir_assets, font_path, - monochrome_png_path, gfx_widgets_path); + if (widget->iterate) + widget->iterate(p_dispwidget, + width, height, fullscreen, + dir_assets, font_path, is_threaded); } - /* Update scaling/dimensions */ - p_dispwidget->last_video_width = width; - p_dispwidget->last_video_height = height; - p_dispwidget->last_scale_factor = ( - gfx_display_get_driver_id() == MENU_DRIVER_ID_XMB) ? - gfx_display_get_widget_pixel_scale(p_dispwidget->last_video_width, - p_dispwidget->last_video_height, fullscreen) : - gfx_display_get_widget_dpi_scale( - p_dispwidget->last_video_width, - p_dispwidget->last_video_height, - fullscreen); + /* Messages queue */ - gfx_widgets_layout(p_dispwidget, - is_threaded, dir_assets, font_path); - video_driver_monitor_reset(); -} - -static void gfx_widgets_context_destroy(dispgfx_widget_t *p_dispwidget) -{ - size_t i; - - for (i = 0; i < ARRAY_SIZE(widgets); i++) + /* Consume one message if available */ + if ((fifo_read_avail(p_dispwidget->msg_queue) > 0) + && !p_dispwidget->widgets_moving + && (p_dispwidget->current_msgs_size < ARRAY_SIZE(p_dispwidget->current_msgs))) { - const gfx_widget_t* widget = widgets[i]; + menu_widget_msg_t *msg_widget = NULL; - if (widget->context_destroy) - widget->context_destroy(); + SLOCK_LOCK(p_dispwidget->current_msgs_lock); + + if (p_dispwidget->current_msgs_size < ARRAY_SIZE(p_dispwidget->current_msgs)) + { + if (fifo_read_avail(p_dispwidget->msg_queue) > 0) + fifo_read(p_dispwidget->msg_queue, &msg_widget, sizeof(msg_widget)); + + if (msg_widget) + { + /* Task messages always appear from the bottom of the screen, append it */ + if (p_dispwidget->msg_queue_tasks_count == 0 || msg_widget->task_ptr) + { + p_dispwidget->current_msgs[p_dispwidget->current_msgs_size] = msg_widget; + } + /* Regular messages are always above tasks, make room and insert it */ + else + { + unsigned idx = (unsigned)(p_dispwidget->current_msgs_size - + p_dispwidget->msg_queue_tasks_count); + for (i = p_dispwidget->current_msgs_size; i > idx; i--) + p_dispwidget->current_msgs[i] = p_dispwidget->current_msgs[i - 1]; + p_dispwidget->current_msgs[idx] = msg_widget; + } + + p_dispwidget->current_msgs_size++; + } + } + + SLOCK_UNLOCK(p_dispwidget->current_msgs_lock); + + if (msg_widget) + { + /* Start expiration timer if not associated to a task */ + if (!msg_widget->task_ptr) + { + if (!msg_widget->expiration_timer_started) + gfx_widgets_start_msg_expiration_timer( + msg_widget, MSG_QUEUE_ANIMATION_DURATION * 2 + + msg_widget->duration); + } + /* Else, start hourglass animation timer */ + else + { + p_dispwidget->msg_queue_tasks_count++; + gfx_widgets_hourglass_end(msg_widget); + } + + if (p_dispwidget->current_msgs_size != 0) + gfx_widgets_msg_queue_move(p_dispwidget); + } } - /* TODO: Dismiss onscreen notifications that have been freed */ + /* Kill first expired message */ + /* Start expiration timer of dead tasks */ + for (i = 0; i < p_dispwidget->current_msgs_size; i++) + { + menu_widget_msg_t *msg_widget = p_dispwidget->current_msgs[i]; - /* Textures */ - for (i = 0; i < MENU_WIDGETS_ICON_LAST; i++) - video_driver_texture_unload(&p_dispwidget->gfx_widgets_icons_textures[i]); + if (!msg_widget) + continue; - video_driver_texture_unload(&p_dispwidget->msg_queue_icon); - video_driver_texture_unload(&p_dispwidget->msg_queue_icon_outline); - video_driver_texture_unload(&p_dispwidget->msg_queue_icon_rect); + if (msg_widget->task_ptr && (msg_widget->task_finished + || msg_widget->task_cancelled)) + if (!msg_widget->expiration_timer_started) + gfx_widgets_start_msg_expiration_timer(msg_widget, TASK_FINISHED_DURATION); - p_dispwidget->msg_queue_icon = 0; - p_dispwidget->msg_queue_icon_outline = 0; - p_dispwidget->msg_queue_icon_rect = 0; - - /* Fonts */ - gfx_widgets_font_free(&p_dispwidget->gfx_widget_fonts.regular); - gfx_widgets_font_free(&p_dispwidget->gfx_widget_fonts.bold); - gfx_widgets_font_free(&p_dispwidget->gfx_widget_fonts.msg_queue); + if (msg_widget->expired && !p_dispwidget->widgets_moving) + { + gfx_widgets_msg_queue_kill(p_dispwidget, + (unsigned)i); + break; + } + } } static int gfx_widgets_draw_indicator( @@ -1205,597 +1382,16 @@ static void gfx_widgets_draw_load_content_animation( } #endif -#ifdef HAVE_TRANSLATE -static void gfx_widgets_draw_icon_blend( - void *userdata, - unsigned video_width, - unsigned video_height, - unsigned icon_width, - unsigned icon_height, - uintptr_t texture, - float x, float y, - unsigned width, unsigned height, - float rotation, float scale_factor, - float *color) +static void INLINE gfx_widgets_font_bind(gfx_widget_font_data_t *font_data) { - gfx_display_ctx_rotate_draw_t rotate_draw; - gfx_display_ctx_draw_t draw; - struct video_coords coords; - math_matrix_4x4 mymat; - - if (!texture) - return; - - rotate_draw.matrix = &mymat; - rotate_draw.rotation = rotation; - rotate_draw.scale_x = scale_factor; - rotate_draw.scale_y = scale_factor; - rotate_draw.scale_z = 1; - rotate_draw.scale_enable = true; - - gfx_display_rotate_z(&rotate_draw, userdata); - - coords.vertices = 4; - coords.vertex = NULL; - coords.tex_coord = NULL; - coords.lut_tex_coord = NULL; - coords.color = color; - - draw.x = x; - draw.y = height - y - icon_height; - draw.width = icon_width; - draw.height = icon_height; - draw.scale_factor = scale_factor; - draw.rotation = rotation; - draw.coords = &coords; - draw.matrix_data = &mymat; - draw.texture = texture; - draw.prim_type = GFX_DISPLAY_PRIM_TRIANGLESTRIP; - draw.pipeline.id = 0; - - gfx_display_draw_blend(&draw, userdata, - video_width, video_height); -} -#endif - -static void gfx_widgets_start_msg_expiration_timer( - menu_widget_msg_t *msg_widget, unsigned duration) -{ - gfx_timer_ctx_entry_t timer; - - timer.cb = gfx_widgets_msg_queue_expired; - timer.duration = duration; - timer.userdata = msg_widget; - - gfx_timer_start(&msg_widget->expiration_timer, &timer); - - msg_widget->expiration_timer_started = true; -} - -static void gfx_widgets_hourglass_end(void *userdata) -{ - gfx_timer_ctx_entry_t timer; - menu_widget_msg_t *msg = (menu_widget_msg_t*)userdata; - - msg->hourglass_rotation = 0.0f; - - timer.cb = gfx_widgets_hourglass_tick; - timer.duration = HOURGLASS_INTERVAL; - timer.userdata = msg; - - gfx_timer_start(&msg->hourglass_timer, &timer); -} - -static void gfx_widgets_hourglass_tick(void *userdata) -{ - gfx_animation_ctx_entry_t entry; - menu_widget_msg_t *msg = (menu_widget_msg_t*)userdata; - uintptr_t tag = (uintptr_t)msg; - - entry.easing_enum = EASING_OUT_QUAD; - entry.tag = tag; - entry.duration = HOURGLASS_DURATION; - entry.target_value = -(2 * M_PI); - entry.subject = &msg->hourglass_rotation; - entry.cb = gfx_widgets_hourglass_end; - entry.userdata = msg; - - gfx_animation_push(&entry); -} - - - - - - -gfx_widget_font_data_t* gfx_widgets_get_font_regular(void *data) -{ - dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; - return &p_dispwidget->gfx_widget_fonts.regular; -} - -gfx_widget_font_data_t* gfx_widgets_get_font_bold(void *data) -{ - dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; - return &p_dispwidget->gfx_widget_fonts.bold; -} - -gfx_widget_font_data_t* gfx_widgets_get_font_msg_queue(void *data) -{ - dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; - return &p_dispwidget->gfx_widget_fonts.msg_queue; -} - -float* gfx_widgets_get_pure_white(void) -{ - return gfx_widgets_pure_white; -} - -float* gfx_widgets_get_backdrop_orig(void) -{ - return gfx_widgets_backdrop_orig; -} - -/* Messages queue */ - -uintptr_t gfx_widgets_get_generic_tag(void *data) -{ - dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; - return p_dispwidget->gfx_widgets_generic_tag; -} - -unsigned gfx_widgets_get_padding(void *data) -{ - dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; - return p_dispwidget->simple_widget_padding; -} - -unsigned gfx_widgets_get_height(void *data) -{ - dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; - return p_dispwidget->simple_widget_height; -} - -unsigned gfx_widgets_get_generic_message_height(void *data) -{ - dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; - return p_dispwidget->generic_message_height; -} - -unsigned gfx_widgets_get_last_video_width(void *data) -{ - dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; - return p_dispwidget->last_video_width; -} - -unsigned gfx_widgets_get_last_video_height(void *data) -{ - dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; - return p_dispwidget->last_video_height; -} - -size_t gfx_widgets_get_msg_queue_size(void *data) -{ - dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; - return p_dispwidget->current_msgs_size; -} - -void gfx_widgets_msg_queue_push( - void *data, - retro_task_t *task, - const char *msg, - unsigned duration, - char *title, - enum message_queue_icon icon, - enum message_queue_category category, - unsigned prio, bool flush, - bool menu_is_alive) -{ - menu_widget_msg_t *msg_widget = NULL; - dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; - - if (fifo_write_avail(p_dispwidget->msg_queue) > 0) - { - /* Get current msg if it exists */ - if (task && task->frontend_userdata) - { - msg_widget = (menu_widget_msg_t*)task->frontend_userdata; - /* msg_widgets can be passed between tasks */ - msg_widget->task_ptr = task; - } - - /* Spawn a new notification */ - if (!msg_widget) - { - const char *title = msg; - - msg_widget = (menu_widget_msg_t*)calloc(1, sizeof(*msg_widget)); - - if (task) - title = task->title; - - msg_widget->duration = duration; - msg_widget->offset_y = 0; - msg_widget->alpha = 1.0f; - - msg_widget->dying = false; - msg_widget->expired = false; - - msg_widget->expiration_timer = 0; - msg_widget->task_ptr = task; - msg_widget->expiration_timer_started = false; - - msg_widget->msg_new = NULL; - msg_widget->msg_transition_animation = 0.0f; - - msg_widget->text_height = 0; - - if (p_dispwidget->msg_queue_has_icons) - { - msg_widget->unfolded = false; - msg_widget->unfolding = false; - msg_widget->unfold = 0.0f; - } - else - { - msg_widget->unfolded = true; - msg_widget->unfolding = false; - msg_widget->unfold = 1.0f; - } - - if (task) - { - msg_widget->msg = strdup(title); - msg_widget->msg_new = strdup(title); - msg_widget->msg_len = (unsigned)strlen(title); - - msg_widget->task_error = !string_is_empty(task->error); - msg_widget->task_cancelled = task->cancelled; - msg_widget->task_finished = task->finished; - msg_widget->task_progress = task->progress; - msg_widget->task_ident = task->ident; - msg_widget->task_title_ptr = task->title; - msg_widget->task_count = 1; - - msg_widget->unfolded = true; - - msg_widget->width = font_driver_get_message_width( - p_dispwidget->gfx_widget_fonts.msg_queue.font, - title, - msg_widget->msg_len, 1) + - p_dispwidget->simple_widget_padding / 2; - - task->frontend_userdata = msg_widget; - - msg_widget->hourglass_rotation = 0; - } - else - { - /* Compute rect width, wrap if necessary */ - /* Single line text > two lines text > two lines - * text with expanded width */ - unsigned title_length = (unsigned)strlen(title); - char *msg = strdup(title); - unsigned width = menu_is_alive - ? p_dispwidget->msg_queue_default_rect_width_menu_alive - : p_dispwidget->msg_queue_default_rect_width; - unsigned text_width = font_driver_get_message_width( - p_dispwidget->gfx_widget_fonts.msg_queue.font, - title, - title_length, - 1); - msg_widget->text_height = p_dispwidget->gfx_widget_fonts.msg_queue.line_height; - - /* Text is too wide, split it into two lines */ - if (text_width > width) - { - /* If the second line is too short, the widget may - * look unappealing - ensure that second line is at - * least 25% of the total width */ - if ((text_width - (text_width >> 2)) < width) - width = text_width - (text_width >> 2); - - word_wrap(msg, msg, (title_length * width) / text_width, - false, 2); - - msg_widget->text_height *= 2; - } - else - width = text_width; - - msg_widget->msg = msg; - msg_widget->msg_len = (unsigned)strlen(msg); - msg_widget->width = width + - p_dispwidget->simple_widget_padding / 2; - } - - fifo_write(p_dispwidget->msg_queue, &msg_widget, sizeof(msg_widget)); - } - /* Update task info */ - else - { - if (msg_widget->expiration_timer_started) - { - gfx_timer_kill(&msg_widget->expiration_timer); - msg_widget->expiration_timer_started = false; - } - - if (!string_is_equal(task->title, msg_widget->msg_new)) - { - unsigned len = (unsigned)strlen(task->title); - unsigned new_width = font_driver_get_message_width( - p_dispwidget->gfx_widget_fonts.msg_queue.font, - task->title, - len, - 1); - - if (msg_widget->msg_new) - { - free(msg_widget->msg_new); - msg_widget->msg_new = NULL; - } - - msg_widget->msg_new = strdup(task->title); - msg_widget->msg_len = len; - msg_widget->task_title_ptr = task->title; - msg_widget->msg_transition_animation = 0; - - if (!task->alternative_look) - { - gfx_animation_ctx_entry_t entry; - - entry.easing_enum = EASING_OUT_QUAD; - entry.tag = (uintptr_t)msg_widget; - entry.duration = MSG_QUEUE_ANIMATION_DURATION*2; - entry.target_value = p_dispwidget->msg_queue_height / 2.0f; - entry.subject = &msg_widget->msg_transition_animation; - entry.cb = msg_widget_msg_transition_animation_done; - entry.userdata = msg_widget; - - gfx_animation_push(&entry); - } - else - msg_widget_msg_transition_animation_done(msg_widget); - - msg_widget->task_count++; - - msg_widget->width = new_width; - } - - msg_widget->task_error = !string_is_empty(task->error); - msg_widget->task_cancelled = task->cancelled; - msg_widget->task_finished = task->finished; - msg_widget->task_progress = task->progress; - } - } -} - -void gfx_widgets_draw_icon( - void *userdata, - unsigned video_width, - unsigned video_height, - unsigned icon_width, - unsigned icon_height, - uintptr_t texture, - float x, float y, - unsigned width, unsigned height, - float rotation, float scale_factor, - float *color) -{ - gfx_display_ctx_rotate_draw_t rotate_draw; - gfx_display_ctx_draw_t draw; - struct video_coords coords; - math_matrix_4x4 mymat; - - if (!texture) - return; - - rotate_draw.matrix = &mymat; - rotate_draw.rotation = rotation; - rotate_draw.scale_x = scale_factor; - rotate_draw.scale_y = scale_factor; - rotate_draw.scale_z = 1; - rotate_draw.scale_enable = true; - - gfx_display_rotate_z(&rotate_draw, userdata); - - coords.vertices = 4; - coords.vertex = NULL; - coords.tex_coord = NULL; - coords.lut_tex_coord = NULL; - coords.color = color; - - draw.x = x; - draw.y = height - y - icon_height; - draw.width = icon_width; - draw.height = icon_height; - draw.scale_factor = scale_factor; - draw.rotation = rotation; - draw.coords = &coords; - draw.matrix_data = &mymat; - draw.texture = texture; - draw.prim_type = GFX_DISPLAY_PRIM_TRIANGLESTRIP; - draw.pipeline.id = 0; - - gfx_display_draw(&draw, userdata, - video_width, video_height); -} - -void gfx_widgets_draw_text( - gfx_widget_font_data_t* font_data, - const char *text, - float x, float y, - int width, int height, - uint32_t color, - enum text_alignment text_align, - bool draw_outside) -{ - if (!font_data || string_is_empty(text)) - return; - - gfx_display_draw_text( - font_data->font, - text, - x, y, - width, height, - color, - text_align, - 1.0f, - false, - 0.0f, - draw_outside); - - font_data->usage_count++; -} - -void gfx_widgets_flush_text( - unsigned video_width, unsigned video_height, - gfx_widget_font_data_t* font_data) -{ - /* Flushing is slow - only do it if font - * has actually been used */ - if (!font_data || (font_data->usage_count == 0)) - return; - - font_driver_flush(video_width, video_height, font_data->font); + font_driver_bind_block(font_data->font, &font_data->raster_block); font_data->raster_block.carr.coords.vertices = 0; font_data->usage_count = 0; } -float gfx_widgets_get_thumbnail_scale_factor( - const float dst_width, const float dst_height, - const float image_width, const float image_height) +static void INLINE gfx_widgets_font_unbind(gfx_widget_font_data_t *font_data) { - float dst_ratio = dst_width / dst_height; - float image_ratio = image_width / image_height; - - if (dst_ratio > image_ratio) - return (dst_height / image_height); - return (dst_width / image_width); -} - -void gfx_widgets_iterate( - void *data, - unsigned width, unsigned height, bool fullscreen, - const char *dir_assets, char *font_path, - bool is_threaded) -{ - size_t i; - dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; - /* Check whether screen dimensions or menu scale - * factor have changed */ - float scale_factor = ( - gfx_display_get_driver_id() == MENU_DRIVER_ID_XMB) - ? gfx_display_get_widget_pixel_scale(width, height, fullscreen) - : gfx_display_get_widget_dpi_scale(width, height, fullscreen); - - if ((scale_factor != p_dispwidget->last_scale_factor) || - (width != p_dispwidget->last_video_width) || - (height != p_dispwidget->last_video_height)) - { - p_dispwidget->last_scale_factor = scale_factor; - p_dispwidget->last_video_width = width; - p_dispwidget->last_video_height = height; - - /* Note: We don't need a full context reset here - * > Just rescale layout, and reset frame time counter */ - gfx_widgets_layout(p_dispwidget, - is_threaded, dir_assets, font_path); - video_driver_monitor_reset(); - } - - for (i = 0; i < ARRAY_SIZE(widgets); i++) - { - const gfx_widget_t* widget = widgets[i]; - - if (widget->iterate) - widget->iterate(p_dispwidget, - width, height, fullscreen, - dir_assets, font_path, is_threaded); - } - - /* Messages queue */ - - /* Consume one message if available */ - if ((fifo_read_avail(p_dispwidget->msg_queue) > 0) - && !p_dispwidget->widgets_moving - && (p_dispwidget->current_msgs_size < ARRAY_SIZE(p_dispwidget->current_msgs))) - { - menu_widget_msg_t *msg_widget = NULL; - - SLOCK_LOCK(p_dispwidget->current_msgs_lock); - - if (p_dispwidget->current_msgs_size < ARRAY_SIZE(p_dispwidget->current_msgs)) - { - if (fifo_read_avail(p_dispwidget->msg_queue) > 0) - fifo_read(p_dispwidget->msg_queue, &msg_widget, sizeof(msg_widget)); - - if (msg_widget) - { - /* Task messages always appear from the bottom of the screen, append it */ - if (p_dispwidget->msg_queue_tasks_count == 0 || msg_widget->task_ptr) - { - p_dispwidget->current_msgs[p_dispwidget->current_msgs_size] = msg_widget; - } - /* Regular messages are always above tasks, make room and insert it */ - else - { - unsigned idx = (unsigned)(p_dispwidget->current_msgs_size - - p_dispwidget->msg_queue_tasks_count); - for (i = p_dispwidget->current_msgs_size; i > idx; i--) - p_dispwidget->current_msgs[i] = p_dispwidget->current_msgs[i - 1]; - p_dispwidget->current_msgs[idx] = msg_widget; - } - - p_dispwidget->current_msgs_size++; - } - } - - SLOCK_UNLOCK(p_dispwidget->current_msgs_lock); - - if (msg_widget) - { - /* Start expiration timer if not associated to a task */ - if (!msg_widget->task_ptr) - { - if (!msg_widget->expiration_timer_started) - gfx_widgets_start_msg_expiration_timer( - msg_widget, MSG_QUEUE_ANIMATION_DURATION * 2 - + msg_widget->duration); - } - /* Else, start hourglass animation timer */ - else - { - p_dispwidget->msg_queue_tasks_count++; - gfx_widgets_hourglass_end(msg_widget); - } - - if (p_dispwidget->current_msgs_size != 0) - gfx_widgets_msg_queue_move(p_dispwidget); - } - } - - /* Kill first expired message */ - /* Start expiration timer of dead tasks */ - for (i = 0; i < p_dispwidget->current_msgs_size; i++) - { - menu_widget_msg_t *msg_widget = p_dispwidget->current_msgs[i]; - - if (!msg_widget) - continue; - - if (msg_widget->task_ptr && (msg_widget->task_finished - || msg_widget->task_cancelled)) - if (!msg_widget->expiration_timer_started) - gfx_widgets_start_msg_expiration_timer(msg_widget, TASK_FINISHED_DURATION); - - if (msg_widget->expired && !p_dispwidget->widgets_moving) - { - gfx_widgets_msg_queue_kill(p_dispwidget, - (unsigned)i); - break; - } - } + font_driver_bind_block(font_data->font, NULL); } void gfx_widgets_frame(void *data) @@ -2133,6 +1729,406 @@ bool gfx_widgets_deinit(bool widgets_persisting) return true; } +static void gfx_widgets_font_init( + dispgfx_widget_t *p_dispwidget, + gfx_widget_font_data_t *font_data, + bool is_threaded, char *font_path, float font_size) +{ + int glyph_width = 0; + float scaled_size = font_size * + p_dispwidget->last_scale_factor; + + /* Free existing font */ + if (font_data->font) + { + gfx_display_font_free(font_data->font); + font_data->font = NULL; + } + + /* Get approximate glyph width */ + font_data->glyph_width = scaled_size * (3.0f / 4.0f); + + /* Create font */ + font_data->font = gfx_display_font_file(font_path, scaled_size, is_threaded); + + /* Get font metadata */ + glyph_width = font_driver_get_message_width(font_data->font, "a", 1, 1.0f); + if (glyph_width > 0) + font_data->glyph_width = (float)glyph_width; + font_data->line_height = (float)font_driver_get_line_height(font_data->font, 1.0f); + font_data->line_ascender = (float)font_driver_get_line_ascender(font_data->font, 1.0f); + font_data->line_descender = (float)font_driver_get_line_descender(font_data->font, 1.0f); + font_data->line_centre_offset = (float)font_driver_get_line_centre_offset(font_data->font, 1.0f); + + font_data->usage_count = 0; +} + +static void gfx_widgets_layout( + dispgfx_widget_t *p_dispwidget, + bool is_threaded, const char *dir_assets, char *font_path) +{ + size_t i; + + /* Initialise fonts */ + if (string_is_empty(font_path)) + { + char ozone_path[PATH_MAX_LENGTH]; + char font_file[PATH_MAX_LENGTH]; + + ozone_path[0] = '\0'; + font_file[0] = '\0'; + + /* Base path */ + fill_pathname_join(ozone_path, dir_assets, "ozone", sizeof(ozone_path)); + + /* Create regular font */ + fill_pathname_join(font_file, ozone_path, "regular.ttf", sizeof(font_file)); + gfx_widgets_font_init(p_dispwidget, + &p_dispwidget->gfx_widget_fonts.regular, + is_threaded, font_file, BASE_FONT_SIZE); + + /* Create bold font */ + fill_pathname_join(font_file, ozone_path, "bold.ttf", sizeof(font_file)); + gfx_widgets_font_init(p_dispwidget, + &p_dispwidget->gfx_widget_fonts.bold, + is_threaded, font_file, BASE_FONT_SIZE); + + /* Create msg_queue font */ + fill_pathname_join(font_file, ozone_path, "regular.ttf", sizeof(font_file)); + gfx_widgets_font_init(p_dispwidget, + &p_dispwidget->gfx_widget_fonts.msg_queue, + is_threaded, font_file, MSG_QUEUE_FONT_SIZE); + } + else + { + /* Load fonts from user-supplied path */ + gfx_widgets_font_init(p_dispwidget, + &p_dispwidget->gfx_widget_fonts.regular, + is_threaded, font_path, BASE_FONT_SIZE); + gfx_widgets_font_init(p_dispwidget, + &p_dispwidget->gfx_widget_fonts.bold, + is_threaded, font_path, BASE_FONT_SIZE); + gfx_widgets_font_init(p_dispwidget, + &p_dispwidget->gfx_widget_fonts.msg_queue, + is_threaded, font_path, MSG_QUEUE_FONT_SIZE); + } + + /* Calculate dimensions */ + p_dispwidget->simple_widget_padding = p_dispwidget->gfx_widget_fonts.regular.line_height * 2.0f/3.0f; + p_dispwidget->simple_widget_height = p_dispwidget->gfx_widget_fonts.regular.line_height + p_dispwidget->simple_widget_padding; + + p_dispwidget->msg_queue_height = p_dispwidget->gfx_widget_fonts.msg_queue.line_height * 2.5f * (BASE_FONT_SIZE / MSG_QUEUE_FONT_SIZE); + + if (p_dispwidget->msg_queue_has_icons) + { + p_dispwidget->msg_queue_icon_size_y = p_dispwidget->msg_queue_height + * 1.2347826087f; /* original image is 280x284 */ + p_dispwidget->msg_queue_icon_size_x = 0.98591549295f * p_dispwidget->msg_queue_icon_size_y; + } + else + { + p_dispwidget->msg_queue_icon_size_x = 0; + p_dispwidget->msg_queue_icon_size_y = 0; + } + + p_dispwidget->msg_queue_spacing = p_dispwidget->msg_queue_height / 3; + p_dispwidget->msg_queue_rect_start_x = p_dispwidget->msg_queue_spacing + p_dispwidget->msg_queue_icon_size_x; + p_dispwidget->msg_queue_internal_icon_size = p_dispwidget->msg_queue_icon_size_y; + p_dispwidget->msg_queue_internal_icon_offset = (p_dispwidget->msg_queue_icon_size_y - p_dispwidget->msg_queue_internal_icon_size) / 2; + p_dispwidget->msg_queue_icon_offset_y = (p_dispwidget->msg_queue_icon_size_y - p_dispwidget->msg_queue_height) / 2; + p_dispwidget->msg_queue_scissor_start_x = p_dispwidget->msg_queue_spacing + p_dispwidget->msg_queue_icon_size_x - (p_dispwidget->msg_queue_icon_size_x * 0.28928571428f); + + if (p_dispwidget->msg_queue_has_icons) + p_dispwidget->msg_queue_regular_padding_x = p_dispwidget->simple_widget_padding / 2; + else + p_dispwidget->msg_queue_regular_padding_x = p_dispwidget->simple_widget_padding; + + p_dispwidget->msg_queue_task_rect_start_x = p_dispwidget->msg_queue_rect_start_x - p_dispwidget->msg_queue_icon_size_x; + + p_dispwidget->msg_queue_task_text_start_x = p_dispwidget->msg_queue_task_rect_start_x + p_dispwidget->msg_queue_height / 2; + + if (!p_dispwidget->gfx_widgets_icons_textures[MENU_WIDGETS_ICON_HOURGLASS]) + p_dispwidget->msg_queue_task_text_start_x -= + p_dispwidget->gfx_widget_fonts.msg_queue.glyph_width * 2.0f; + + p_dispwidget->msg_queue_regular_text_start = p_dispwidget->msg_queue_rect_start_x + p_dispwidget->msg_queue_regular_padding_x; + + p_dispwidget->msg_queue_task_hourglass_x = p_dispwidget->msg_queue_rect_start_x - p_dispwidget->msg_queue_icon_size_x; + + p_dispwidget->generic_message_height = p_dispwidget->gfx_widget_fonts.regular.line_height * 2.0f; + + p_dispwidget->msg_queue_default_rect_width_menu_alive = p_dispwidget + ->gfx_widget_fonts.msg_queue.glyph_width * 40.0f; + p_dispwidget->msg_queue_default_rect_width = p_dispwidget->last_video_width + - p_dispwidget->msg_queue_regular_text_start - (2 * p_dispwidget->simple_widget_padding); + +#ifdef HAVE_MENU + p_dispwidget->load_content_animation_icon_size_initial = + LOAD_CONTENT_ANIMATION_INITIAL_ICON_SIZE * + p_dispwidget->last_scale_factor; + p_dispwidget->load_content_animation_icon_size_target = + LOAD_CONTENT_ANIMATION_TARGET_ICON_SIZE * + p_dispwidget->last_scale_factor; +#endif + + p_dispwidget->divider_width_1px = 1; + if (p_dispwidget->last_scale_factor > 1.0f) + p_dispwidget->divider_width_1px = (unsigned)(p_dispwidget->last_scale_factor + 0.5f); + + for (i = 0; i < ARRAY_SIZE(widgets); i++) + { + const gfx_widget_t* widget = widgets[i]; + + if (widget->layout) + widget->layout(p_dispwidget, + is_threaded, dir_assets, font_path); + } +} + +static void gfx_widgets_context_reset( + dispgfx_widget_t *p_dispwidget, + bool is_threaded, + unsigned width, unsigned height, bool fullscreen, + const char *dir_assets, char *font_path) +{ + size_t i; + char xmb_path[PATH_MAX_LENGTH]; + char monochrome_png_path[PATH_MAX_LENGTH]; + char gfx_widgets_path[PATH_MAX_LENGTH]; + char theme_path[PATH_MAX_LENGTH]; + + xmb_path[0] = '\0'; + monochrome_png_path[0] = '\0'; + gfx_widgets_path[0] = '\0'; + theme_path[0] = '\0'; + + /* Textures paths */ + fill_pathname_join( + gfx_widgets_path, + dir_assets, + "menu_widgets", + sizeof(gfx_widgets_path) + ); + + fill_pathname_join( + xmb_path, + dir_assets, + "xmb", + sizeof(xmb_path) + ); + + /* Monochrome */ + fill_pathname_join( + theme_path, + xmb_path, + "monochrome", + sizeof(theme_path) + ); + + fill_pathname_join( + monochrome_png_path, + theme_path, + "png", + sizeof(monochrome_png_path) + ); + + /* Load textures */ + /* Icons */ + for (i = 0; i < MENU_WIDGETS_ICON_LAST; i++) + { + gfx_display_reset_textures_list( + gfx_widgets_icons_names[i], + monochrome_png_path, + &p_dispwidget->gfx_widgets_icons_textures[i], + TEXTURE_FILTER_MIPMAP_LINEAR, + NULL, + NULL); + } + + /* Message queue */ + gfx_display_reset_textures_list( + "msg_queue_icon.png", + gfx_widgets_path, + &p_dispwidget->msg_queue_icon, + TEXTURE_FILTER_LINEAR, + NULL, + NULL); + gfx_display_reset_textures_list( + "msg_queue_icon_outline.png", + gfx_widgets_path, + &p_dispwidget->msg_queue_icon_outline, + TEXTURE_FILTER_LINEAR, + NULL, + NULL); + gfx_display_reset_textures_list( + "msg_queue_icon_rect.png", + gfx_widgets_path, + &p_dispwidget->msg_queue_icon_rect, + TEXTURE_FILTER_NEAREST, + NULL, + NULL); + + p_dispwidget->msg_queue_has_icons = + p_dispwidget->msg_queue_icon && + p_dispwidget->msg_queue_icon_outline && + p_dispwidget->msg_queue_icon_rect; + + for (i = 0; i < ARRAY_SIZE(widgets); i++) + { + const gfx_widget_t* widget = widgets[i]; + + if (widget->context_reset) + widget->context_reset(is_threaded, width, height, + fullscreen, dir_assets, font_path, + monochrome_png_path, gfx_widgets_path); + } + + /* Update scaling/dimensions */ + p_dispwidget->last_video_width = width; + p_dispwidget->last_video_height = height; + p_dispwidget->last_scale_factor = ( + gfx_display_get_driver_id() == MENU_DRIVER_ID_XMB) ? + gfx_display_get_widget_pixel_scale(p_dispwidget->last_video_width, + p_dispwidget->last_video_height, fullscreen) : + gfx_display_get_widget_dpi_scale( + p_dispwidget->last_video_width, + p_dispwidget->last_video_height, + fullscreen); + + gfx_widgets_layout(p_dispwidget, + is_threaded, dir_assets, font_path); + video_driver_monitor_reset(); +} + +static void INLINE gfx_widgets_font_free(gfx_widget_font_data_t *font_data) +{ + if (font_data->font) + gfx_display_font_free(font_data->font); + + font_data->font = NULL; + font_data->usage_count = 0; +} + +static void gfx_widgets_context_destroy(dispgfx_widget_t *p_dispwidget) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(widgets); i++) + { + const gfx_widget_t* widget = widgets[i]; + + if (widget->context_destroy) + widget->context_destroy(); + } + + /* TODO: Dismiss onscreen notifications that have been freed */ + + /* Textures */ + for (i = 0; i < MENU_WIDGETS_ICON_LAST; i++) + video_driver_texture_unload(&p_dispwidget->gfx_widgets_icons_textures[i]); + + video_driver_texture_unload(&p_dispwidget->msg_queue_icon); + video_driver_texture_unload(&p_dispwidget->msg_queue_icon_outline); + video_driver_texture_unload(&p_dispwidget->msg_queue_icon_rect); + + p_dispwidget->msg_queue_icon = 0; + p_dispwidget->msg_queue_icon_outline = 0; + p_dispwidget->msg_queue_icon_rect = 0; + + /* Fonts */ + gfx_widgets_font_free(&p_dispwidget->gfx_widget_fonts.regular); + gfx_widgets_font_free(&p_dispwidget->gfx_widget_fonts.bold); + gfx_widgets_font_free(&p_dispwidget->gfx_widget_fonts.msg_queue); +} + +static void gfx_widgets_free(dispgfx_widget_t *p_dispwidget) +{ + size_t i; + + p_dispwidget->widgets_inited = false; + + for (i = 0; i < ARRAY_SIZE(widgets); i++) + { + const gfx_widget_t* widget = widgets[i]; + + if (widget->free) + widget->free(); + } + + /* Kill all running animations */ + gfx_animation_kill_by_tag( + &p_dispwidget->gfx_widgets_generic_tag); + + /* Purge everything from the fifo */ + if (p_dispwidget->msg_queue) + { + while (fifo_read_avail(p_dispwidget->msg_queue) > 0) + { + menu_widget_msg_t *msg_widget; + + fifo_read(p_dispwidget->msg_queue, + &msg_widget, sizeof(msg_widget)); + + /* Note: gfx_widgets_free() is only called when + * main_exit() is invoked. At this stage, we cannot + * guarantee that any task pointers are valid (the + * task may have been free()'d, but we can't know + * that here) - so all we can do is unset the task + * pointer associated with each message + * > If we don't do this, gfx_widgets_msg_queue_free() + * will generate heap-use-after-free errors */ + msg_widget->task_ptr = NULL; + + gfx_widgets_msg_queue_free(p_dispwidget, msg_widget); + free(msg_widget); + } + + fifo_free(p_dispwidget->msg_queue); + } + p_dispwidget->msg_queue = NULL; + + /* Purge everything from the list */ + SLOCK_LOCK(p_dispwidget->current_msgs_lock); + + p_dispwidget->current_msgs_size = 0; + for (i = 0; i < ARRAY_SIZE(p_dispwidget->current_msgs); i++) + { + menu_widget_msg_t *msg = p_dispwidget->current_msgs[i]; + if (!msg) + continue; + + /* Note: gfx_widgets_free() is only called when + * main_exit() is invoked. At this stage, we cannot + * guarantee that any task pointers are valid (the + * task may have been free()'d, but we can't know + * that here) - so all we can do is unset the task + * pointer associated with each message + * > If we don't do this, gfx_widgets_msg_queue_free() + * will generate heap-use-after-free errors */ + msg->task_ptr = NULL; + + gfx_widgets_msg_queue_free(p_dispwidget, msg); + } + SLOCK_UNLOCK(p_dispwidget->current_msgs_lock); + +#ifdef HAVE_THREADS + slock_free(p_dispwidget->current_msgs_lock); + p_dispwidget->current_msgs_lock = NULL; +#endif + + p_dispwidget->msg_queue_tasks_count = 0; + + /* Font */ + video_coord_array_free( + &p_dispwidget->gfx_widget_fonts.regular.raster_block.carr); + video_coord_array_free( + &p_dispwidget->gfx_widget_fonts.bold.raster_block.carr); + video_coord_array_free( + &p_dispwidget->gfx_widget_fonts.msg_queue.raster_block.carr); + + font_driver_bind_block(NULL, NULL); +} + bool gfx_widgets_set_fps_text( void *data, const char *new_fps_text) @@ -2178,6 +2174,13 @@ void gfx_widgets_ai_service_overlay_unload(dispgfx_widget_t *p_dispwidget) #endif #ifdef HAVE_MENU +static void gfx_widgets_end_load_content_animation(void *userdata) +{ +#if 0 + task_load_content_resume(); /* TODO: Restore that */ +#endif +} + void gfx_widgets_cleanup_load_content_animation(void) { dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)dispwidget_get_ptr(); diff --git a/gfx/video_crt_switch.c b/gfx/video_crt_switch.c index 945be7312d..2e9ddaa960 100644 --- a/gfx/video_crt_switch.c +++ b/gfx/video_crt_switch.c @@ -30,130 +30,7 @@ #if defined(HAVE_VIDEOCORE) #include "include/userland/interface/vmcs_host/vc_vchi_gencmd.h" - -static void crt_rpi_switch(int width, int height, float hz, int xoffset) -{ - char buffer[1024]; - VCHI_INSTANCE_T vchi_instance; - VCHI_CONNECTION_T *vchi_connection = NULL; - static char output[250] = {0}; - static char output1[250] = {0}; - static char output2[250] = {0}; - static char set_hdmi[250] = {0}; - static char set_hdmi_timing[250] = {0}; - int i = 0; - int hfp = 0; - int hsp = 0; - int hbp = 0; - int vfp = 0; - int vsp = 0; - int vbp = 0; - int hmax = 0; - int vmax = 0; - int pdefault = 8; - int pwidth = 0; - int ip_flag = 0; - float roundw = 0.0f; - float roundh = 0.0f; - float pixel_clock = 0.0f; - - /* set core refresh from hz */ - video_monitor_set_refresh_rate(hz); - - /* following code is the mode line generator */ - hsp = (width * 0.117) - (xoffset*4); - if (width < 700) - { - hfp = (width * 0.065); - hbp = width * 0.35-hsp-hfp; - } - else - { - hfp = (width * 0.033) + (width / 112); - hbp = (width * 0.225) + (width /58); - xoffset = xoffset*2; - } - - hmax = hbp; - - if (height < 241) - vmax = 261; - if (height < 241 && hz > 56 && hz < 58) - vmax = 280; - if (height < 241 && hz < 55) - vmax = 313; - if (height > 250 && height < 260 && hz > 54) - vmax = 296; - if (height > 250 && height < 260 && hz > 52 && hz < 54) - vmax = 285; - if (height > 250 && height < 260 && hz < 52) - vmax = 313; - if (height > 260 && height < 300) - vmax = 318; - - if (height > 400 && hz > 56) - vmax = 533; - if (height > 520 && hz < 57) - vmax = 580; - - if (height > 300 && hz < 56) - vmax = 615; - if (height > 500 && hz < 56) - vmax = 624; - if (height > 300) - pdefault = pdefault * 2; - - vfp = (height + ((vmax - height) / 2) - pdefault) - height; - - if (height < 300) - vsp = vfp + 3; /* needs to be 3 for progressive */ - if (height > 300) - vsp = vfp + 6; /* needs to be 6 for interlaced */ - - vsp = 3; - vbp = (vmax-height)-vsp-vfp; - hmax = width+hfp+hsp+hbp; - - if (height < 300) - { - pixel_clock = (hmax * vmax * hz) ; - ip_flag = 0; - } - - if (height > 300) - { - pixel_clock = (hmax * vmax * (hz/2)) /2 ; - ip_flag = 1; - } - /* above code is the modeline generator */ - - snprintf(set_hdmi_timing, sizeof(set_hdmi_timing), - "hdmi_timings %d 1 %d %d %d %d 1 %d %d %d 0 0 0 %f %d %f 1 ", - width, hfp, hsp, hbp, height, vfp,vsp, vbp, - hz, ip_flag, pixel_clock); - - vcos_init(); - - vchi_initialise(&vchi_instance); - - vchi_connect(NULL, 0, vchi_instance); - - vc_vchi_gencmd_init(vchi_instance, &vchi_connection, 1); - - vc_gencmd(buffer, sizeof(buffer), set_hdmi_timing); - - vc_gencmd_stop(); - - vchi_disconnect(vchi_instance); - - snprintf(output1, sizeof(output1), - "tvservice -e \"DMT 87\" > /dev/null"); - system(output1); - snprintf(output2, sizeof(output1), - "fbset -g %d %d %d %d 24 > /dev/null", - width, height, width, height); - system(output2); -} +static void crt_rpi_switch(int width, int height, float hz, int xoffset); #endif static void switch_crt_hz(videocrt_switch_t *p_switch) @@ -370,3 +247,129 @@ void crt_switch_res_core( video_driver_apply_state_changes(); } } + +#if defined(HAVE_VIDEOCORE) +static void crt_rpi_switch(int width, int height, float hz, int xoffset) +{ + char buffer[1024]; + VCHI_INSTANCE_T vchi_instance; + VCHI_CONNECTION_T *vchi_connection = NULL; + static char output[250] = {0}; + static char output1[250] = {0}; + static char output2[250] = {0}; + static char set_hdmi[250] = {0}; + static char set_hdmi_timing[250] = {0}; + int i = 0; + int hfp = 0; + int hsp = 0; + int hbp = 0; + int vfp = 0; + int vsp = 0; + int vbp = 0; + int hmax = 0; + int vmax = 0; + int pdefault = 8; + int pwidth = 0; + int ip_flag = 0; + float roundw = 0.0f; + float roundh = 0.0f; + float pixel_clock = 0.0f; + + /* set core refresh from hz */ + video_monitor_set_refresh_rate(hz); + + /* following code is the mode line generator */ + hsp = (width * 0.117) - (xoffset*4); + if (width < 700) + { + hfp = (width * 0.065); + hbp = width * 0.35-hsp-hfp; + } + else + { + hfp = (width * 0.033) + (width / 112); + hbp = (width * 0.225) + (width /58); + xoffset = xoffset*2; + } + + hmax = hbp; + + if (height < 241) + vmax = 261; + if (height < 241 && hz > 56 && hz < 58) + vmax = 280; + if (height < 241 && hz < 55) + vmax = 313; + if (height > 250 && height < 260 && hz > 54) + vmax = 296; + if (height > 250 && height < 260 && hz > 52 && hz < 54) + vmax = 285; + if (height > 250 && height < 260 && hz < 52) + vmax = 313; + if (height > 260 && height < 300) + vmax = 318; + + if (height > 400 && hz > 56) + vmax = 533; + if (height > 520 && hz < 57) + vmax = 580; + + if (height > 300 && hz < 56) + vmax = 615; + if (height > 500 && hz < 56) + vmax = 624; + if (height > 300) + pdefault = pdefault * 2; + + vfp = (height + ((vmax - height) / 2) - pdefault) - height; + + if (height < 300) + vsp = vfp + 3; /* needs to be 3 for progressive */ + if (height > 300) + vsp = vfp + 6; /* needs to be 6 for interlaced */ + + vsp = 3; + vbp = (vmax-height)-vsp-vfp; + hmax = width+hfp+hsp+hbp; + + if (height < 300) + { + pixel_clock = (hmax * vmax * hz) ; + ip_flag = 0; + } + + if (height > 300) + { + pixel_clock = (hmax * vmax * (hz/2)) /2 ; + ip_flag = 1; + } + /* above code is the modeline generator */ + + snprintf(set_hdmi_timing, sizeof(set_hdmi_timing), + "hdmi_timings %d 1 %d %d %d %d 1 %d %d %d 0 0 0 %f %d %f 1 ", + width, hfp, hsp, hbp, height, vfp,vsp, vbp, + hz, ip_flag, pixel_clock); + + vcos_init(); + + vchi_initialise(&vchi_instance); + + vchi_connect(NULL, 0, vchi_instance); + + vc_vchi_gencmd_init(vchi_instance, &vchi_connection, 1); + + vc_gencmd(buffer, sizeof(buffer), set_hdmi_timing); + + vc_gencmd_stop(); + + vchi_disconnect(vchi_instance); + + snprintf(output1, sizeof(output1), + "tvservice -e \"DMT 87\" > /dev/null"); + system(output1); + snprintf(output2, sizeof(output1), + "fbset -g %d %d %d %d 24 > /dev/null", + width, height, width, height); + system(output2); +} +#endif diff --git a/gfx/video_layout.c b/gfx/video_layout.c index ed9f0b241c..8c85d6cce0 100644 --- a/gfx/video_layout.c +++ b/gfx/video_layout.c @@ -32,12 +32,16 @@ #include "../retroarch.h" #include "../verbosity.h" +bool video_layout_load_internal(view_array_t *view_array, + rxml_document_t *doc); + typedef struct io { char *name; int base_value; int value; -} io_t; +} +io_t; typedef struct video_layout_state { @@ -59,79 +63,12 @@ typedef struct video_layout_state bool is_archive; bool view_changed; -} video_layout_state_t; +} +video_layout_state_t; /* TODO/FIXME - global state - perhaps move outside this file */ static video_layout_state_t *video_layout_state = NULL; -/* Forward declarations */ -bool video_layout_load_internal(view_array_t *view_array, - rxml_document_t *doc); - -static int video_layout_load_image(const char *path) -{ - struct texture_image image; - void *handle; - int index; - - image.supports_rgba = video_driver_supports_rgba(); - - if (video_layout_state->is_archive) - { - void *buf; - int64_t len; - char respath[PATH_MAX_LENGTH]; - - strlcpy(respath, video_layout_state->base_path, sizeof(respath)); - strlcat(respath, path, sizeof(respath)); - - if (!file_archive_compressed_read(respath, &buf, NULL, &len)) - { - RARCH_LOG("video_layout: failed to decompress image: %s\n", respath); - return 0; - } - - if (!image_texture_load_buffer(&image, - image_texture_get_type(path), buf, (size_t)len)) - { - free(buf); - - RARCH_LOG("video_layout: failed to load image: %s\n", respath); - return 0; - } - - free(buf); - } - else - { - char respath[PATH_MAX_LENGTH]; - - strlcpy(respath, video_layout_state->base_path, sizeof(respath)); - strlcat(respath, path, sizeof(respath)); - - if (!image_texture_load(&image, respath)) - { - RARCH_LOG("video_layout: failed to load image: %s\n", respath); - return 0; - } - } - - handle = video_layout_state->render->take_image( - video_layout_state->render_info.video_driver_data, image); - - if (!handle) - return 0; - - index = video_layout_state->images_count; - - vec_size((void**)&video_layout_state->images, - sizeof(void*), ++video_layout_state->images_count); - - video_layout_state->images[index] = handle; - - return index; -} - void video_layout_init(void *video_driver_data, const video_layout_render_interface_t *render) { @@ -276,6 +213,70 @@ bool video_layout_valid(void) return video_layout_state && video_layout_state->view; } +static int video_layout_load_image(const char *path) +{ + struct texture_image image; + void *handle; + int index; + + image.supports_rgba = video_driver_supports_rgba(); + + if (video_layout_state->is_archive) + { + void *buf; + int64_t len; + char respath[PATH_MAX_LENGTH]; + + strlcpy(respath, video_layout_state->base_path, sizeof(respath)); + strlcat(respath, path, sizeof(respath)); + + if (!file_archive_compressed_read(respath, &buf, NULL, &len)) + { + RARCH_LOG("video_layout: failed to decompress image: %s\n", respath); + return 0; + } + + if (!image_texture_load_buffer(&image, + image_texture_get_type(path), buf, (size_t)len)) + { + free(buf); + + RARCH_LOG("video_layout: failed to load image: %s\n", respath); + return 0; + } + + free(buf); + } + else + { + char respath[PATH_MAX_LENGTH]; + + strlcpy(respath, video_layout_state->base_path, sizeof(respath)); + strlcat(respath, path, sizeof(respath)); + + if (!image_texture_load(&image, respath)) + { + RARCH_LOG("video_layout: failed to load image: %s\n", respath); + return 0; + } + } + + handle = video_layout_state->render->take_image( + video_layout_state->render_info.video_driver_data, image); + + if (!handle) + return 0; + + index = video_layout_state->images_count; + + vec_size((void**)&video_layout_state->images, + sizeof(void*), ++video_layout_state->images_count); + + video_layout_state->images[index] = handle; + + return index; +} + int video_layout_view_count(void) { return video_layout_state->view_array.views_count; diff --git a/gfx/video_shader_parse.c b/gfx/video_shader_parse.c index a1d3716326..2c0cbc3752 100644 --- a/gfx/video_shader_parse.c +++ b/gfx/video_shader_parse.c @@ -408,76 +408,6 @@ static struct video_shader_parameter *video_shader_parse_find_parameter( return NULL; } -/* CGP store */ -static const char *scale_type_to_str(enum gfx_scale_type type) -{ - switch (type) - { - case RARCH_SCALE_INPUT: - return "source"; - case RARCH_SCALE_VIEWPORT: - return "viewport"; - case RARCH_SCALE_ABSOLUTE: - return "absolute"; - default: - break; - } - - return "?"; -} - -static void shader_write_scale_dim(config_file_t *conf, - const char *dim, - enum gfx_scale_type type, float scale, - unsigned absolute, unsigned i) -{ - char key[64]; - - key[0] = '\0'; - - snprintf(key, sizeof(key), "scale_type_%s%u", dim, i); - config_set_string(conf, key, scale_type_to_str(type)); - - snprintf(key, sizeof(key), "scale_%s%u", dim, i); - if (type == RARCH_SCALE_ABSOLUTE) - config_set_int(conf, key, absolute); - else - config_set_float(conf, key, scale); -} - -static void shader_write_fbo(config_file_t *conf, - const struct gfx_fbo_scale *fbo, unsigned i) -{ - char key[64]; - - key[0] = '\0'; - - snprintf(key, sizeof(key), "float_framebuffer%u", i); - config_set_bool(conf, key, fbo->fp_fbo); - snprintf(key, sizeof(key), "srgb_framebuffer%u", i); - config_set_bool(conf, key, fbo->srgb_fbo); - - if (!fbo->valid) - return; - - shader_write_scale_dim(conf, "x", fbo->type_x, - fbo->scale_x, fbo->abs_x, i); - shader_write_scale_dim(conf, "y", fbo->type_y, - fbo->scale_y, fbo->abs_y, i); -} - -#ifdef _WIN32 -static void make_relative_path_portable(char *path) -{ - /* use '/' instead of '\' for maximum portability */ - char *p; - for (p = path; *p; p++) - if (*p == '\\') - *p = '/'; -} -#endif - - /** * video_shader_set_current_parameters: * @conf : Preset file to read from. @@ -957,6 +887,75 @@ bool video_shader_read_conf_preset(config_file_t *conf, return video_shader_parse_textures(conf, shader); } +/* CGP store */ +static const char *scale_type_to_str(enum gfx_scale_type type) +{ + switch (type) + { + case RARCH_SCALE_INPUT: + return "source"; + case RARCH_SCALE_VIEWPORT: + return "viewport"; + case RARCH_SCALE_ABSOLUTE: + return "absolute"; + default: + break; + } + + return "?"; +} + +static void shader_write_scale_dim(config_file_t *conf, + const char *dim, + enum gfx_scale_type type, float scale, + unsigned absolute, unsigned i) +{ + char key[64]; + + key[0] = '\0'; + + snprintf(key, sizeof(key), "scale_type_%s%u", dim, i); + config_set_string(conf, key, scale_type_to_str(type)); + + snprintf(key, sizeof(key), "scale_%s%u", dim, i); + if (type == RARCH_SCALE_ABSOLUTE) + config_set_int(conf, key, absolute); + else + config_set_float(conf, key, scale); +} + +static void shader_write_fbo(config_file_t *conf, + const struct gfx_fbo_scale *fbo, unsigned i) +{ + char key[64]; + + key[0] = '\0'; + + snprintf(key, sizeof(key), "float_framebuffer%u", i); + config_set_bool(conf, key, fbo->fp_fbo); + snprintf(key, sizeof(key), "srgb_framebuffer%u", i); + config_set_bool(conf, key, fbo->srgb_fbo); + + if (!fbo->valid) + return; + + shader_write_scale_dim(conf, "x", fbo->type_x, + fbo->scale_x, fbo->abs_x, i); + shader_write_scale_dim(conf, "y", fbo->type_y, + fbo->scale_y, fbo->abs_y, i); +} + +#ifdef _WIN32 +static void make_relative_path_portable(char *path) +{ + /* use '/' instead of '\' for maximum portability */ + char *p; + for (p = path; *p; p++) + if (*p == '\\') + *p = '/'; +} +#endif + /** * video_shader_write_conf_preset: * @conf : Preset file to write to. diff --git a/playlist.c b/playlist.c index 81f33f4d7a..c55a547000 100644 --- a/playlist.c +++ b/playlist.c @@ -223,6 +223,40 @@ static bool playlist_core_path_equal(const char *real_core_path, const char *ent return false; } +uint32_t playlist_get_size(playlist_t *playlist) +{ + if (!playlist) + return 0; + return (uint32_t)playlist->size; +} + +char *playlist_get_conf_path(playlist_t *playlist) +{ + if (!playlist) + return NULL; + return playlist->conf_path; +} + +/** + * playlist_get_index: + * @playlist : Playlist handle. + * @idx : Index of playlist entry. + * @path : Path of playlist entry. + * @core_path : Core path of playlist entry. + * @core_name : Core name of playlist entry. + * + * Gets values of playlist index: + **/ +void playlist_get_index(playlist_t *playlist, + size_t idx, + const struct playlist_entry **entry) +{ + if (!playlist || !entry) + return; + + *entry = &playlist->entries[idx]; +} + /** * playlist_free_entry: * @entry : Playlist entry handle. @@ -280,1982 +314,6 @@ static void playlist_free_entry(struct playlist_entry *entry) entry->last_played_second = 0; } -static JSON_Writer_HandlerResult JSONOutputHandler(JSON_Writer writer, const char *pBytes, size_t length) -{ - JSONContext *context = (JSONContext*)JSON_Writer_GetUserData(writer); - - (void)writer; /* unused */ - return intfstream_write(context->file, pBytes, length) == length ? JSON_Writer_Continue : JSON_Writer_Abort; -} - -static void JSONLogError(JSONContext *pCtx) -{ - if (pCtx->parser && JSON_Parser_GetError(pCtx->parser) != JSON_Error_AbortedByHandler) - { - JSON_Error error = JSON_Parser_GetError(pCtx->parser); - JSON_Location errorLocation = { 0, 0, 0 }; - - (void)JSON_Parser_GetErrorLocation(pCtx->parser, &errorLocation); - RARCH_WARN("Error: Invalid JSON at line %d, column %d (input byte %d) - %s.\n", - (int)errorLocation.line + 1, - (int)errorLocation.column + 1, - (int)errorLocation.byte, - JSON_ErrorString(error)); - } - else if (pCtx->writer && JSON_Writer_GetError(pCtx->writer) != JSON_Error_AbortedByHandler) - { - RARCH_WARN("Error: could not write output - %s.\n", JSON_ErrorString(JSON_Writer_GetError(pCtx->writer))); - } -} - -/* No-op versions of JSON whitespace writers, - * used when generating compressed output */ -static JSON_Status JSON_CALL JSON_Writer_WriteNewLine_NULL(JSON_Writer writer) -{ - return JSON_Success; -} - -static JSON_Status JSON_CALL JSON_Writer_WriteSpace_NULL(JSON_Writer writer, size_t numberOfSpaces) -{ - return JSON_Success; -} - -static JSON_Status (JSON_CALL *json_write_new_line)(JSON_Writer writer) = JSON_Writer_WriteNewLine; -static JSON_Status (JSON_CALL *json_write_space)(JSON_Writer writer, size_t numberOfSpaces) = JSON_Writer_WriteSpace; - -static JSON_Parser_HandlerResult JSONStartArrayHandler(JSON_Parser parser) -{ - JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); - - pCtx->array_depth++; - - if (pCtx->object_depth == 1) - { - if (string_is_equal(pCtx->current_meta_string, "items") && pCtx->array_depth == 1) - pCtx->in_items = true; - } - else if (pCtx->object_depth == 2) - { - if (pCtx->array_depth == 2) - if (string_is_equal(pCtx->current_items_string, "subsystem_roms")) - pCtx->in_subsystem_roms = true; - } - - return JSON_Parser_Continue; -} - -static JSON_Parser_HandlerResult JSONEndArrayHandler(JSON_Parser parser) -{ - JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); - - retro_assert(pCtx->array_depth > 0); - - pCtx->array_depth--; - - if (pCtx->object_depth == 1) - { - if (pCtx->in_items && string_is_equal(pCtx->current_meta_string, "items") && pCtx->array_depth == 0) - { - free(pCtx->current_meta_string); - pCtx->current_meta_string = NULL; - pCtx->in_items = false; - - if (pCtx->current_items_string) - { - free(pCtx->current_items_string); - pCtx->current_items_string = NULL; - } - } - } - else if (pCtx->object_depth == 2) - { - if (pCtx->in_subsystem_roms && string_is_equal(pCtx->current_items_string, "subsystem_roms") && pCtx->array_depth == 1) - pCtx->in_subsystem_roms = false; - } - - return JSON_Parser_Continue; -} - -static JSON_Parser_HandlerResult JSONStartObjectHandler(JSON_Parser parser) -{ - JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); - - pCtx->object_depth++; - - if (pCtx->in_items && pCtx->object_depth == 2) - { - if ((pCtx->array_depth == 1) && !pCtx->capacity_exceeded) - { - if (pCtx->playlist->size < pCtx->playlist->cap) - pCtx->current_entry = &pCtx->playlist->entries[pCtx->playlist->size]; - else - { - /* Hit max item limit. - * Note: We can't just abort here, since there may - * be more metadata to read at the end of the file... */ - RARCH_WARN("JSON file contains more entries than current playlist capacity. Excess entries will be discarded.\n"); - pCtx->capacity_exceeded = true; - pCtx->current_entry = NULL; - /* In addition, since we are discarding excess entries, - * the playlist must be flagged as being modified - * (i.e. the playlist is not the same as when it was - * last saved to disk...) */ - pCtx->playlist->modified = true; - } - } - } - - return JSON_Parser_Continue; -} - -static JSON_Parser_HandlerResult JSONEndObjectHandler(JSON_Parser parser) -{ - JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); - - if (pCtx->in_items && pCtx->object_depth == 2) - { - if ((pCtx->array_depth == 1) && !pCtx->capacity_exceeded) - pCtx->playlist->size++; - } - - retro_assert(pCtx->object_depth > 0); - - pCtx->object_depth--; - - return JSON_Parser_Continue; -} - -static JSON_Parser_HandlerResult JSONStringHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes) -{ - JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); - (void)attributes; /* unused */ - - if (pCtx->in_items && pCtx->in_subsystem_roms && pCtx->object_depth == 2 && pCtx->array_depth == 2) - { - if (pCtx->current_entry_string_list_val && length && !string_is_empty(pValue)) - { - union string_list_elem_attr attr = {0}; - - if (!*pCtx->current_entry_string_list_val) - *pCtx->current_entry_string_list_val = string_list_new(); - - string_list_append(*pCtx->current_entry_string_list_val, pValue, attr); - } - } - else if (pCtx->in_items && pCtx->object_depth == 2) - { - if (pCtx->array_depth == 1) - { - if (pCtx->current_entry_val && length && !string_is_empty(pValue)) - { - if (*pCtx->current_entry_val) - free(*pCtx->current_entry_val); - *pCtx->current_entry_val = strdup(pValue); - } - } - } - else if (pCtx->object_depth == 1) - { - if (pCtx->array_depth == 0) - { - if (pCtx->current_meta_val && length && !string_is_empty(pValue)) - { - /* handle any top-level playlist metadata here */ -#if 0 - RARCH_LOG("[Playlist]: Found meta: %s = %s\n", pCtx->current_meta_string, pValue); -#endif - - free(pCtx->current_meta_string); - pCtx->current_meta_string = NULL; - - if (*pCtx->current_meta_val) - free(*pCtx->current_meta_val); - - *pCtx->current_meta_val = strdup(pValue); - } - } - } - - pCtx->current_entry_val = NULL; - pCtx->current_meta_val = NULL; - - return JSON_Parser_Continue; -} - -static JSON_Parser_HandlerResult JSONNumberHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes) -{ - JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); - (void)attributes; /* unused */ - - if (pCtx->in_items && pCtx->object_depth == 2) - { - if (pCtx->array_depth == 1) - { - if (pCtx->current_entry_int_val && length && !string_is_empty(pValue)) - *pCtx->current_entry_int_val = (int)strtoul(pValue, NULL, 10); - else if (pCtx->current_entry_uint_val && length && !string_is_empty(pValue)) - *pCtx->current_entry_uint_val = (unsigned)strtoul(pValue, NULL, 10); - } - } - else if (pCtx->object_depth == 1) - { - if (pCtx->array_depth == 0) - { - if (pCtx->current_meta_string && length && !string_is_empty(pValue)) - { - /* handle any top-level playlist metadata here */ -#if 0 - RARCH_LOG("[Playlist]: Found meta: %s = %s\n", pCtx->current_meta_string, pValue); -#endif - - free(pCtx->current_meta_string); - pCtx->current_meta_string = NULL; - - if (pCtx->current_meta_label_display_mode_val) - *pCtx->current_meta_label_display_mode_val = (enum playlist_label_display_mode)strtoul(pValue, NULL, 10); - else if (pCtx->current_meta_thumbnail_mode_val) - *pCtx->current_meta_thumbnail_mode_val = (enum playlist_thumbnail_mode)strtoul(pValue, NULL, 10); - else if (pCtx->current_meta_sort_mode_val) - *pCtx->current_meta_sort_mode_val = (enum playlist_sort_mode)strtoul(pValue, NULL, 10); - } - } - } - - pCtx->current_entry_int_val = NULL; - pCtx->current_entry_uint_val = NULL; - pCtx->current_meta_label_display_mode_val = NULL; - pCtx->current_meta_thumbnail_mode_val = NULL; - pCtx->current_meta_sort_mode_val = NULL; - - return JSON_Parser_Continue; -} - -static JSON_Parser_HandlerResult JSONObjectMemberHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes) -{ - JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); - (void)attributes; /* unused */ - - if (pCtx->in_items && pCtx->object_depth == 2) - { - if (pCtx->array_depth == 1) - { - if (pCtx->current_entry_val) - { - /* something went wrong */ - RARCH_WARN("JSON parsing failed at line %d.\n", __LINE__); - return JSON_Parser_Abort; - } - - if (length) - { - if (!string_is_empty(pValue)) - { - if (!string_is_empty(pCtx->current_items_string)) - free(pCtx->current_items_string); - pCtx->current_items_string = strdup(pValue); - } - - if (!pCtx->capacity_exceeded) - { - if (string_is_equal(pValue, "path")) - pCtx->current_entry_val = &pCtx->current_entry->path; - else if (string_is_equal(pValue, "label")) - pCtx->current_entry_val = &pCtx->current_entry->label; - else if (string_is_equal(pValue, "core_path")) - pCtx->current_entry_val = &pCtx->current_entry->core_path; - else if (string_is_equal(pValue, "core_name")) - pCtx->current_entry_val = &pCtx->current_entry->core_name; - else if (string_is_equal(pValue, "crc32")) - pCtx->current_entry_val = &pCtx->current_entry->crc32; - else if (string_is_equal(pValue, "db_name")) - pCtx->current_entry_val = &pCtx->current_entry->db_name; - else if (string_starts_with_size(pValue, "subsystem_", STRLEN_CONST("subsystem_"))) - { - if (string_is_equal(pValue, "subsystem_ident")) - pCtx->current_entry_val = &pCtx->current_entry->subsystem_ident; - else if (string_is_equal(pValue, "subsystem_name")) - pCtx->current_entry_val = &pCtx->current_entry->subsystem_name; - else if (string_is_equal(pValue, "subsystem_roms")) - pCtx->current_entry_string_list_val = &pCtx->current_entry->subsystem_roms; - } - else if (string_starts_with_size(pValue, "runtime_", STRLEN_CONST("runtime_"))) - { - if (string_is_equal(pValue, "runtime_hours")) - pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_hours; - else if (string_is_equal(pValue, "runtime_minutes")) - pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_minutes; - else if (string_is_equal(pValue, "runtime_seconds")) - pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_seconds; - } - else if (string_starts_with_size(pValue, "last_played_", STRLEN_CONST("last_played_"))) - { - if (string_is_equal(pValue, "last_played_year")) - pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_year; - else if (string_is_equal(pValue, "last_played_month")) - pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_month; - else if (string_is_equal(pValue, "last_played_day")) - pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_day; - else if (string_is_equal(pValue, "last_played_hour")) - pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_hour; - else if (string_is_equal(pValue, "last_played_minute")) - pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_minute; - else if (string_is_equal(pValue, "last_played_second")) - pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_second; - } - } - else - { - pCtx->current_entry_val = NULL; - pCtx->current_entry_uint_val = NULL; - pCtx->current_entry_string_list_val = NULL; - } - } - } - } - else if (pCtx->object_depth == 1) - { - if (pCtx->array_depth == 0) - { - if (pCtx->current_meta_val) - { - /* something went wrong */ - RARCH_WARN("JSON parsing failed at line %d.\n", __LINE__); - return JSON_Parser_Abort; - } - - if (length) - { - if (pCtx->current_meta_string) - free(pCtx->current_meta_string); - pCtx->current_meta_string = strdup(pValue); - - if (string_is_equal(pValue, "default_core_path")) - pCtx->current_meta_val = &pCtx->playlist->default_core_path; - else if (string_is_equal(pValue, "default_core_name")) - pCtx->current_meta_val = &pCtx->playlist->default_core_name; - else if (string_is_equal(pValue, "label_display_mode")) - pCtx->current_meta_label_display_mode_val = &pCtx->playlist->label_display_mode; - else if (string_is_equal(pValue, "right_thumbnail_mode")) - pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->right_thumbnail_mode; - else if (string_is_equal(pValue, "left_thumbnail_mode")) - pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->left_thumbnail_mode; - else if (string_is_equal(pValue, "sort_mode")) - pCtx->current_meta_sort_mode_val = &pCtx->playlist->sort_mode; - } - } - } - - return JSON_Parser_Continue; -} - -static void get_old_format_metadata_value( - char *metadata_line, char *value, size_t len) -{ - char *end = NULL; - char *start = strchr(metadata_line, '\"'); - - if (!start) - return; - - start++; - end = strchr(start, '\"'); - - if (!end) - return; - - *end = '\0'; - strlcpy(value, start, len); -} - -static bool playlist_read_file( - playlist_t *playlist, const char *path) -{ - unsigned i; - int test_char; -#if defined(HAVE_ZLIB) - /* Always use RZIP interface when reading playlists - * > this will automatically handle uncompressed - * data */ - intfstream_t *file = intfstream_open_rzip_file(path, - RETRO_VFS_FILE_ACCESS_READ); -#else - intfstream_t *file = intfstream_open_file(path, - RETRO_VFS_FILE_ACCESS_READ, - RETRO_VFS_FILE_ACCESS_HINT_NONE); -#endif - - playlist->compressed = intfstream_is_compressed(file); - - /* If playlist file does not exist, - * create an empty playlist instead */ - if (!file) - return true; - - /* Detect format of playlist - * > Read file until we find the first printable - * non-whitespace ASCII character */ - do - { - test_char = intfstream_getc(file); - - if (test_char == EOF) /* read error or end of file */ - goto end; - }while (!isgraph(test_char) || test_char > 0x7F); - - if (test_char == '{') - { - /* New playlist format detected */ -#if 0 - RARCH_LOG("[Playlist]: New playlist format detected.\n"); -#endif - playlist->old_format = false; - } - else - { - /* old playlist format detected */ -#if 0 - RARCH_LOG("[Playlist]: Old playlist format detected.\n"); -#endif - playlist->old_format = true; - } - - /* Reset file to start */ - intfstream_rewind(file); - - if (!playlist->old_format) - { - JSONContext context = {0}; - context.parser = JSON_Parser_Create(NULL); - context.file = file; - context.playlist = playlist; - - if (!context.parser) - { - RARCH_ERR("Failed to create JSON parser\n"); - goto end; - } - -#if 0 - JSON_Parser_SetTrackObjectMembers(context.parser, JSON_True); -#endif - JSON_Parser_SetAllowBOM(context.parser, JSON_True); - JSON_Parser_SetAllowComments(context.parser, JSON_True); - JSON_Parser_SetAllowSpecialNumbers(context.parser, JSON_True); - JSON_Parser_SetAllowHexNumbers(context.parser, JSON_True); - JSON_Parser_SetAllowUnescapedControlCharacters(context.parser, JSON_True); - JSON_Parser_SetReplaceInvalidEncodingSequences(context.parser, JSON_True); - -#if 0 - JSON_Parser_SetNullHandler(context.parser, &JSONNullHandler); - JSON_Parser_SetBooleanHandler(context.parser, &JSONBooleanHandler); - JSON_Parser_SetSpecialNumberHandler(context.parser, &JSONSpecialNumberHandler); - JSON_Parser_SetArrayItemHandler(context.parser, &JSONArrayItemHandler); -#endif - - JSON_Parser_SetNumberHandler(context.parser, &JSONNumberHandler); - JSON_Parser_SetStringHandler(context.parser, &JSONStringHandler); - JSON_Parser_SetStartObjectHandler(context.parser, &JSONStartObjectHandler); - JSON_Parser_SetEndObjectHandler(context.parser, &JSONEndObjectHandler); - JSON_Parser_SetObjectMemberHandler(context.parser, &JSONObjectMemberHandler); - JSON_Parser_SetStartArrayHandler(context.parser, &JSONStartArrayHandler); - JSON_Parser_SetEndArrayHandler(context.parser, &JSONEndArrayHandler); - JSON_Parser_SetUserData(context.parser, &context); - - while (!intfstream_eof(file)) - { - char chunk[4096] = {0}; - int64_t length = intfstream_read(file, chunk, sizeof(chunk)); - - if (!length && !intfstream_eof(file)) - { - RARCH_WARN("Could not read JSON input.\n"); - goto json_cleanup; - } - - if (!JSON_Parser_Parse(context.parser, chunk, length, JSON_False)) - { - /* Note: Chunk may not be null-terminated. - * It is therefore dangerous to print its contents. - * Setting a size limit here mitigates the issue, but - * in general this is not good practice... - * Addendum: RARCH_WARN() actually limits the printed - * buffer size anyway, so this warning message is most - * likely worthless... */ - RARCH_WARN("Error parsing chunk:\n---snip---\n%.*s\n---snip---\n", 4096, chunk); - JSONLogError(&context); - goto json_cleanup; - } - } - - if (!JSON_Parser_Parse(context.parser, NULL, 0, JSON_True)) - { - RARCH_WARN("Error parsing JSON.\n"); - JSONLogError(&context); - goto json_cleanup; - } - -json_cleanup: - - JSON_Parser_Free(context.parser); - - if (context.current_meta_string) - free(context.current_meta_string); - - if (context.current_items_string) - free(context.current_items_string); - } - else - { - char line_buf[PLAYLIST_ENTRIES][PATH_MAX_LENGTH] = {{0}}; - - /* Unnecessary, but harmless */ - for (i = 0; i < PLAYLIST_ENTRIES; i++) - line_buf[i][0] = '\0'; - - /* Read playlist entries */ - playlist->size = 0; - while (playlist->size < playlist->cap) - { - size_t i; - size_t lines_read = 0; - - /* Attempt to read the next 'PLAYLIST_ENTRIES' - * lines from the file */ - for (i = 0; i < PLAYLIST_ENTRIES; i++) - { - *line_buf[i] = '\0'; - - if (intfstream_gets(file, line_buf[i], sizeof(line_buf[i]))) - { - /* Ensure line is NUL terminated, regardless of - * Windows or Unix line endings */ - string_replace_all_chars(line_buf[i], '\r', '\0'); - string_replace_all_chars(line_buf[i], '\n', '\0'); - - lines_read++; - } - else - break; - } - - /* If a 'full set' of lines were read, then this - * is a valid playlist entry */ - if (lines_read >= PLAYLIST_ENTRIES) - { - struct playlist_entry *entry = - &playlist->entries[playlist->size]; - - if (!entry) - continue; - - /* path */ - if (!string_is_empty(line_buf[0])) - entry->path = strdup(line_buf[0]); - - /* label */ - if (!string_is_empty(line_buf[1])) - entry->label = strdup(line_buf[1]); - - /* core_path */ - if (!string_is_empty(line_buf[2])) - entry->core_path = strdup(line_buf[2]); - - /* core_name */ - if (!string_is_empty(line_buf[3])) - entry->core_name = strdup(line_buf[3]); - - /* crc32 */ - if (!string_is_empty(line_buf[4])) - entry->crc32 = strdup(line_buf[4]); - - /* db_name */ - if (!string_is_empty(line_buf[5])) - entry->db_name = strdup(line_buf[5]); - - playlist->size++; - } - /* If fewer than 'PLAYLIST_ENTRIES' lines were - * read, then this is metadata */ - else - { - char default_core_path[PATH_MAX_LENGTH]; - char default_core_name[PATH_MAX_LENGTH]; - - default_core_path[0] = '\0'; - default_core_name[0] = '\0'; - - /* Get default_core_path */ - if (lines_read < 1) - break; - - if (strncmp("default_core_path", - line_buf[0], - STRLEN_CONST("default_core_path")) == 0) - get_old_format_metadata_value( - line_buf[0], default_core_path, sizeof(default_core_path)); - - /* Get default_core_name */ - if (lines_read < 2) - break; - - if (strncmp("default_core_name", - line_buf[1], - STRLEN_CONST("default_core_name")) == 0) - get_old_format_metadata_value( - line_buf[1], default_core_name, sizeof(default_core_name)); - - /* > Populate default core path/name, if required - * (if one is empty, the other should be ignored) */ - if (!string_is_empty(default_core_path) && - !string_is_empty(default_core_name)) - { - playlist->default_core_path = strdup(default_core_path); - playlist->default_core_name = strdup(default_core_name); - } - - /* Get label_display_mode */ - if (lines_read < 3) - break; - - if (strncmp("label_display_mode", - line_buf[2], - STRLEN_CONST("label_display_mode")) == 0) - { - unsigned display_mode; - char display_mode_str[4] = {0}; - - get_old_format_metadata_value( - line_buf[2], display_mode_str, sizeof(display_mode_str)); - - display_mode = string_to_unsigned(display_mode_str); - - if (display_mode <= LABEL_DISPLAY_MODE_KEEP_REGION_AND_DISC_INDEX) - playlist->label_display_mode = (enum playlist_label_display_mode)display_mode; - } - - /* Get thumbnail modes */ - if (lines_read < 4) - break; - - if (strncmp("thumbnail_mode", - line_buf[3], - STRLEN_CONST("thumbnail_mode")) == 0) - { - char thumbnail_mode_str[8] = {0}; - struct string_list *thumbnail_modes = NULL; - - get_old_format_metadata_value( - line_buf[3], thumbnail_mode_str, sizeof(thumbnail_mode_str)); - - thumbnail_modes = string_split(thumbnail_mode_str, "|"); - - if (thumbnail_modes) - { - if (thumbnail_modes->size == 2) - { - unsigned thumbnail_mode; - - /* Right thumbnail mode */ - thumbnail_mode = string_to_unsigned(thumbnail_modes->elems[0].data); - if (thumbnail_mode <= PLAYLIST_THUMBNAIL_MODE_BOXARTS) - playlist->right_thumbnail_mode = (enum playlist_thumbnail_mode)thumbnail_mode; - - /* Left thumbnail mode */ - thumbnail_mode = string_to_unsigned(thumbnail_modes->elems[1].data); - if (thumbnail_mode <= PLAYLIST_THUMBNAIL_MODE_BOXARTS) - playlist->left_thumbnail_mode = (enum playlist_thumbnail_mode)thumbnail_mode; - } - - string_list_free(thumbnail_modes); - } - } - - /* Get sort_mode */ - if (lines_read < 5) - break; - - if (strncmp("sort_mode", - line_buf[4], - STRLEN_CONST("sort_mode")) == 0) - { - unsigned sort_mode; - char sort_mode_str[4] = {0}; - - get_old_format_metadata_value( - line_buf[4], sort_mode_str, sizeof(sort_mode_str)); - - sort_mode = string_to_unsigned(sort_mode_str); - - if (sort_mode <= PLAYLIST_SORT_MODE_OFF) - playlist->sort_mode = (enum playlist_sort_mode)sort_mode; - } - - /* All metadata parsed -> end of file */ - break; - } - } - } - -end: - intfstream_close(file); - free(file); - return true; -} - -static int playlist_qsort_func(const struct playlist_entry *a, - const struct playlist_entry *b) -{ - char *a_str = NULL; - char *b_str = NULL; - char *a_fallback_label = NULL; - char *b_fallback_label = NULL; - int ret = 0; - - if (!a || !b) - goto end; - - a_str = a->label; - b_str = b->label; - - /* It is quite possible for playlist labels - * to be blank. If that is the case, have to use - * filename as a fallback (this is slow, but we - * have no other option...) */ - if (string_is_empty(a_str)) - { - a_fallback_label = (char*)calloc(PATH_MAX_LENGTH, sizeof(char)); - - if (!a_fallback_label) - goto end; - - if (!string_is_empty(a->path)) - fill_short_pathname_representation(a_fallback_label, a->path, PATH_MAX_LENGTH * sizeof(char)); - /* If filename is also empty, use core name - * instead -> this matches the behaviour of - * menu_displaylist_parse_playlist() */ - else if (!string_is_empty(a->core_name)) - strlcpy(a_fallback_label, a->core_name, PATH_MAX_LENGTH * sizeof(char)); - - /* If both filename and core name are empty, - * then have to compare an empty string - * -> again, this is to match the behaviour of - * menu_displaylist_parse_playlist() */ - - a_str = a_fallback_label; - } - - if (string_is_empty(b_str)) - { - b_fallback_label = (char*)calloc(PATH_MAX_LENGTH, sizeof(char)); - - if (!b_fallback_label) - goto end; - - if (!string_is_empty(b->path)) - fill_short_pathname_representation(b_fallback_label, b->path, PATH_MAX_LENGTH * sizeof(char)); - else if (!string_is_empty(b->core_name)) - strlcpy(b_fallback_label, b->core_name, PATH_MAX_LENGTH * sizeof(char)); - - b_str = b_fallback_label; - } - - ret = strcasecmp(a_str, b_str); - -end: - - a_str = NULL; - b_str = NULL; - - if (a_fallback_label) - { - free(a_fallback_label); - a_fallback_label = NULL; - } - - if (b_fallback_label) - { - free(b_fallback_label); - b_fallback_label = NULL; - } - - return ret; -} - -void playlist_free_cached(void) -{ - playlist_free(playlist_cached); - playlist_cached = NULL; -} - -playlist_t *playlist_get_cached(void) -{ - if (playlist_cached) - return playlist_cached; - return NULL; -} - -bool playlist_init_cached( - const char *path, size_t size, - bool use_old_format, bool compress) -{ - playlist_t *playlist = playlist_init(path, size); - if (!playlist) - return false; - - /* If playlist format/compression state - * does not match requested settings, update - * file on disk immediately */ - if ( -#if defined(HAVE_ZLIB) - (playlist->compressed != compress) || -#endif - (playlist->old_format != use_old_format)) - playlist_write_file(playlist, use_old_format, compress); - - playlist_cached = playlist; - return true; -} - -/** - * playlist_init: - * @path : Path to playlist contents file. - * @size : Maximum capacity of playlist size. - * - * Creates and initializes a playlist. - * - * Returns: handle to new playlist if successful, otherwise NULL - **/ -playlist_t *playlist_init(const char *path, size_t size) -{ - struct playlist_entry *entries = NULL; - playlist_t *playlist = (playlist_t*)malloc(sizeof(*playlist)); - if (!playlist) - return NULL; - - entries = (struct playlist_entry*)calloc(size, sizeof(*entries)); - if (!entries) - { - free(playlist); - return NULL; - } - - playlist->modified = false; - playlist->old_format = false; - playlist->compressed = false; - playlist->size = 0; - playlist->cap = size; - playlist->conf_path = strdup(path); - playlist->default_core_name = NULL; - playlist->default_core_path = NULL; - playlist->entries = entries; - playlist->label_display_mode = LABEL_DISPLAY_MODE_DEFAULT; - playlist->right_thumbnail_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT; - playlist->left_thumbnail_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT; - playlist->sort_mode = PLAYLIST_SORT_MODE_DEFAULT; - - playlist_read_file(playlist, path); - - return playlist; -} - -void playlist_qsort(playlist_t *playlist) -{ - /* Avoid inadvertent sorting if 'sort mode' - * has been set explicitly to PLAYLIST_SORT_MODE_OFF */ - if (!playlist || - (playlist->sort_mode == PLAYLIST_SORT_MODE_OFF)) - return; - - qsort(playlist->entries, playlist->size, - sizeof(struct playlist_entry), - (int (*)(const void *, const void *))playlist_qsort_func); -} - -void command_playlist_push_write( - playlist_t *playlist, - const struct playlist_entry *entry, - bool fuzzy_archive_match, - bool use_old_format, - bool compress) -{ - if (!playlist) - return; - - if (playlist_push(playlist, entry, fuzzy_archive_match)) - playlist_write_file(playlist, use_old_format, compress); -} - -void command_playlist_update_write( - playlist_t *plist, - size_t idx, - const struct playlist_entry *entry, - bool use_old_format, - bool compress) -{ - playlist_t *playlist = plist ? plist : playlist_get_cached(); - - if (!playlist) - return; - - playlist_update( - playlist, - idx, - entry); - - playlist_write_file(playlist, use_old_format, compress); -} - -bool playlist_index_is_valid(playlist_t *playlist, size_t idx, - const char *path, const char *core_path) -{ - if (!playlist) - return false; - - if (idx >= playlist->size) - return false; - - return string_is_equal(playlist->entries[idx].path, path) && - string_is_equal(path_basename(playlist->entries[idx].core_path), path_basename(core_path)); -} - -bool playlist_entries_are_equal( - const struct playlist_entry *entry_a, - const struct playlist_entry *entry_b, - bool fuzzy_archive_match) -{ - char real_path_a[PATH_MAX_LENGTH]; - char real_core_path_a[PATH_MAX_LENGTH]; - - real_path_a[0] = '\0'; - real_core_path_a[0] = '\0'; - - /* Sanity check */ - if (!entry_a || !entry_b) - return false; - - if (string_is_empty(entry_a->path) && - string_is_empty(entry_a->core_path) && - string_is_empty(entry_b->path) && - string_is_empty(entry_b->core_path)) - return true; - - /* Check content paths */ - if (!string_is_empty(entry_a->path)) - { - strlcpy(real_path_a, entry_a->path, sizeof(real_path_a)); - path_resolve_realpath(real_path_a, sizeof(real_path_a), true); - } - - if (!playlist_path_equal( - real_path_a, entry_b->path, fuzzy_archive_match)) - return false; - - /* Check core paths */ - if (!string_is_empty(entry_a->core_path)) - { - strlcpy(real_core_path_a, entry_a->core_path, sizeof(real_core_path_a)); - if (!string_is_equal(real_core_path_a, "DETECT") && - !string_is_equal(real_core_path_a, "builtin")) - path_resolve_realpath(real_core_path_a, sizeof(real_core_path_a), true); - } - - return playlist_core_path_equal(real_core_path_a, entry_b->core_path); -} - -void playlist_get_crc32(playlist_t *playlist, size_t idx, - const char **crc32) -{ - if (!playlist) - return; - - if (crc32) - *crc32 = playlist->entries[idx].crc32; -} - -void playlist_get_db_name(playlist_t *playlist, size_t idx, - const char **db_name) -{ - if (!playlist) - return; - - if (db_name) - { - if (!string_is_empty(playlist->entries[idx].db_name)) - *db_name = playlist->entries[idx].db_name; - else - { - const char *conf_path_basename = path_basename(playlist->conf_path); - - /* Only use file basename if this is a 'collection' playlist - * (i.e. ignore history/favourites) */ - if ( - !string_is_empty(conf_path_basename) - && !string_ends_with_size(playlist->conf_path, "_history.lpl", - strlen(playlist->conf_path), STRLEN_CONST("_history.lpl")) - && !string_is_equal(conf_path_basename, - file_path_str(FILE_PATH_CONTENT_FAVORITES)) - ) - *db_name = conf_path_basename; - } - } -} - -char *playlist_get_default_core_path(playlist_t *playlist) -{ - if (!playlist) - return NULL; - return playlist->default_core_path; -} - -char *playlist_get_default_core_name(playlist_t *playlist) -{ - if (!playlist) - return NULL; - return playlist->default_core_name; -} - -enum playlist_label_display_mode playlist_get_label_display_mode(playlist_t *playlist) -{ - if (!playlist) - return LABEL_DISPLAY_MODE_DEFAULT; - return playlist->label_display_mode; -} - -enum playlist_thumbnail_mode playlist_get_thumbnail_mode( - playlist_t *playlist, enum playlist_thumbnail_id thumbnail_id) -{ - if (!playlist) - return PLAYLIST_THUMBNAIL_MODE_DEFAULT; - - if (thumbnail_id == PLAYLIST_THUMBNAIL_RIGHT) - return playlist->right_thumbnail_mode; - else if (thumbnail_id == PLAYLIST_THUMBNAIL_LEFT) - return playlist->left_thumbnail_mode; - - /* Fallback */ - return PLAYLIST_THUMBNAIL_MODE_DEFAULT; -} - -enum playlist_sort_mode playlist_get_sort_mode(playlist_t *playlist) -{ - if (!playlist) - return PLAYLIST_SORT_MODE_DEFAULT; - return playlist->sort_mode; -} - -void playlist_set_default_core_path(playlist_t *playlist, const char *core_path) -{ - char real_core_path[PATH_MAX_LENGTH]; - - real_core_path[0] = '\0'; - - if (!playlist || string_is_empty(core_path)) - return; - - /* Get 'real' core path */ - strlcpy(real_core_path, core_path, sizeof(real_core_path)); - if (!string_is_equal(real_core_path, "DETECT") && - !string_is_equal(real_core_path, "builtin")) - playlist_resolve_path(PLAYLIST_SAVE, - real_core_path, sizeof(real_core_path)); - - if (string_is_empty(real_core_path)) - return; - - if (!string_is_equal(playlist->default_core_path, real_core_path)) - { - if (playlist->default_core_path) - free(playlist->default_core_path); - playlist->default_core_path = strdup(real_core_path); - playlist->modified = true; - } -} - -void playlist_set_default_core_name(playlist_t *playlist, const char *core_name) -{ - if (!playlist || string_is_empty(core_name)) - return; - - if (!string_is_equal(playlist->default_core_name, core_name)) - { - if (playlist->default_core_name) - free(playlist->default_core_name); - playlist->default_core_name = strdup(core_name); - playlist->modified = true; - } -} - -void playlist_set_label_display_mode(playlist_t *playlist, - enum playlist_label_display_mode label_display_mode) -{ - if (!playlist) - return; - - if (playlist->label_display_mode != label_display_mode) - { - playlist->label_display_mode = label_display_mode; - playlist->modified = true; - } -} - -void playlist_set_thumbnail_mode( - playlist_t *playlist, enum playlist_thumbnail_id thumbnail_id, - enum playlist_thumbnail_mode thumbnail_mode) -{ - if (!playlist) - return; - - switch (thumbnail_id) - { - case PLAYLIST_THUMBNAIL_RIGHT: - playlist->right_thumbnail_mode = thumbnail_mode; - playlist->modified = true; - break; - case PLAYLIST_THUMBNAIL_LEFT: - playlist->left_thumbnail_mode = thumbnail_mode; - playlist->modified = true; - break; - } -} - -void playlist_set_sort_mode(playlist_t *playlist, enum playlist_sort_mode sort_mode) -{ - if (!playlist) - return; - - if (playlist->sort_mode != sort_mode) - { - playlist->sort_mode = sort_mode; - playlist->modified = true; - } -} - -/* Returns true if specified entry has a valid - * core association (i.e. a non-empty string - * other than DETECT) */ -bool playlist_entry_has_core(const struct playlist_entry *entry) -{ - if (!entry || - string_is_empty(entry->core_path) || - string_is_empty(entry->core_name) || - string_is_equal(entry->core_path, "DETECT") || - string_is_equal(entry->core_name, "DETECT")) - return false; - - return true; -} - -/* Fetches core info object corresponding to the - * currently associated core of the specified - * playlist entry. - * Returns NULL if entry does not have a valid - * core association */ -core_info_t *playlist_entry_get_core_info(const struct playlist_entry* entry) -{ - core_info_ctx_find_t core_info; - - if (!playlist_entry_has_core(entry)) - return NULL; - - /* Search for associated core */ - core_info.inf = NULL; - core_info.path = entry->core_path; - - if (core_info_find(&core_info)) - return core_info.inf; - - return NULL; -} - -/* Fetches core info object corresponding to the - * currently associated default core of the - * specified playlist. - * Returns NULL if playlist does not have a valid - * default core association */ -core_info_t *playlist_get_default_core_info(playlist_t* playlist) -{ - core_info_ctx_find_t core_info; - - if (!playlist || - string_is_empty(playlist->default_core_path) || - string_is_empty(playlist->default_core_name) || - string_is_equal(playlist->default_core_path, "DETECT") || - string_is_equal(playlist->default_core_name, "DETECT")) - return NULL; - - /* Search for associated core */ - core_info.inf = NULL; - core_info.path = playlist->default_core_path; - - if (core_info_find(&core_info)) - return core_info.inf; - - return NULL; -} - -void playlist_write_file( - playlist_t *playlist, - bool use_old_format, bool compress) -{ - size_t i; - intfstream_t *file = NULL; - bool compressed = false; - - /* Playlist will be written if any of the - * following are true: - * > 'modified' flag is set - * > Current playlist format (old/new) does not - * match requested - * > Current playlist compression status does - * not match requested */ - if (!playlist || - !(playlist->modified || -#if defined(HAVE_ZLIB) - (playlist->compressed != compress) || -#endif - (playlist->old_format != use_old_format))) - return; - -#if defined(HAVE_ZLIB) - if (compress) - file = intfstream_open_rzip_file(playlist->conf_path, - RETRO_VFS_FILE_ACCESS_WRITE); - else -#endif - file = intfstream_open_file(playlist->conf_path, - RETRO_VFS_FILE_ACCESS_WRITE, - RETRO_VFS_FILE_ACCESS_HINT_NONE); - - if (!file) - { - RARCH_ERR("Failed to write to playlist file: %s\n", playlist->conf_path); - return; - } - - /* Get current file compression state */ - compressed = intfstream_is_compressed(file); - -#ifdef RARCH_INTERNAL - if (use_old_format) - { - for (i = 0; i < playlist->size; i++) - intfstream_printf(file, "%s\n%s\n%s\n%s\n%s\n%s\n", - playlist->entries[i].path ? playlist->entries[i].path : "", - playlist->entries[i].label ? playlist->entries[i].label : "", - playlist->entries[i].core_path ? playlist->entries[i].core_path : "", - playlist->entries[i].core_name ? playlist->entries[i].core_name : "", - playlist->entries[i].crc32 ? playlist->entries[i].crc32 : "", - playlist->entries[i].db_name ? playlist->entries[i].db_name : "" - ); - - /* Add metadata lines - * > We add these at the end of the file to prevent - * breakage if the playlist is loaded with an older - * version of RetroArch */ - intfstream_printf( - file, - "default_core_path = \"%s\"\n" - "default_core_name = \"%s\"\n" - "label_display_mode = \"%d\"\n" - "thumbnail_mode = \"%d|%d\"\n" - "sort_mode = \"%d\"\n", - playlist->default_core_path ? playlist->default_core_path : "", - playlist->default_core_name ? playlist->default_core_name : "", - playlist->label_display_mode, - playlist->right_thumbnail_mode, playlist->left_thumbnail_mode, - playlist->sort_mode); - - playlist->old_format = true; - } - else -#endif - { - char uint_str[4]; - JSONContext context = {0}; - context.writer = JSON_Writer_Create(NULL); - context.file = file; - - if (!context.writer) - { - RARCH_ERR("Failed to create JSON writer\n"); - goto end; - } - - /* Assign JSON whitespace functions - * > When compressing playlists, human readability - * is not a factor - can skip all indentation - * and new line characters */ - json_write_new_line = compressed ? - JSON_Writer_WriteNewLine_NULL : - JSON_Writer_WriteNewLine; - json_write_space = compressed ? - JSON_Writer_WriteSpace_NULL : - JSON_Writer_WriteSpace; - - JSON_Writer_SetOutputEncoding(context.writer, JSON_UTF8); - JSON_Writer_SetOutputHandler(context.writer, &JSONOutputHandler); - JSON_Writer_SetUserData(context.writer, &context); - - JSON_Writer_WriteStartObject(context.writer); - json_write_new_line(context.writer); - - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "version", - STRLEN_CONST("version"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, "1.4", - STRLEN_CONST("1.4"), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "default_core_path", - STRLEN_CONST("default_core_path"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->default_core_path - ? playlist->default_core_path - : "", - playlist->default_core_path - ? strlen(playlist->default_core_path) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "default_core_name", - STRLEN_CONST("default_core_name"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->default_core_name - ? playlist->default_core_name - : "", - playlist->default_core_name - ? strlen(playlist->default_core_name) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - - uint_str[0] = '\0'; - snprintf(uint_str, sizeof(uint_str), "%u", playlist->label_display_mode); - - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "label_display_mode", - STRLEN_CONST("label_display_mode"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, uint_str, - strlen(uint_str), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - - uint_str[0] = '\0'; - snprintf(uint_str, sizeof(uint_str), "%u", playlist->right_thumbnail_mode); - - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "right_thumbnail_mode", - STRLEN_CONST("right_thumbnail_mode"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, uint_str, - strlen(uint_str), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - - uint_str[0] = '\0'; - snprintf(uint_str, sizeof(uint_str), "%u", playlist->left_thumbnail_mode); - - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "left_thumbnail_mode", - STRLEN_CONST("left_thumbnail_mode"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, uint_str, - strlen(uint_str), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - - uint_str[0] = '\0'; - snprintf(uint_str, sizeof(uint_str), "%u", playlist->sort_mode); - - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "sort_mode", - STRLEN_CONST("sort_mode"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, uint_str, - strlen(uint_str), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - - json_write_space(context.writer, 2); - JSON_Writer_WriteString(context.writer, "items", - STRLEN_CONST("items"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteStartArray(context.writer); - json_write_new_line(context.writer); - - for (i = 0; i < playlist->size; i++) - { - json_write_space(context.writer, 4); - JSON_Writer_WriteStartObject(context.writer); - - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "path", - STRLEN_CONST("path"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->entries[i].path - ? playlist->entries[i].path - : "", - playlist->entries[i].path - ? strlen(playlist->entries[i].path) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "label", - STRLEN_CONST("label"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->entries[i].label - ? playlist->entries[i].label - : "", - playlist->entries[i].label - ? strlen(playlist->entries[i].label) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "core_path", - STRLEN_CONST("core_path"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->entries[i].core_path - ? playlist->entries[i].core_path - : "", - playlist->entries[i].core_path - ? strlen(playlist->entries[i].core_path) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "core_name", - STRLEN_CONST("core_name"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->entries[i].core_name - ? playlist->entries[i].core_name - : "", - playlist->entries[i].core_name - ? strlen(playlist->entries[i].core_name) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "crc32", - STRLEN_CONST("crc32"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, playlist->entries[i].crc32 ? playlist->entries[i].crc32 : "", - playlist->entries[i].crc32 - ? strlen(playlist->entries[i].crc32) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "db_name", - STRLEN_CONST("db_name"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, playlist->entries[i].db_name ? playlist->entries[i].db_name : "", - playlist->entries[i].db_name - ? strlen(playlist->entries[i].db_name) - : 0, - JSON_UTF8); - - if (!string_is_empty(playlist->entries[i].subsystem_ident)) - { - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "subsystem_ident", - STRLEN_CONST("subsystem_ident"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, playlist->entries[i].subsystem_ident ? playlist->entries[i].subsystem_ident : "", - playlist->entries[i].subsystem_ident - ? strlen(playlist->entries[i].subsystem_ident) - : 0, - JSON_UTF8); - } - - if (!string_is_empty(playlist->entries[i].subsystem_name)) - { - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "subsystem_name", - STRLEN_CONST("subsystem_name"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->entries[i].subsystem_name - ? playlist->entries[i].subsystem_name - : "", - playlist->entries[i].subsystem_name - ? strlen(playlist->entries[i].subsystem_name) - : 0, JSON_UTF8); - } - - if ( playlist->entries[i].subsystem_roms && - playlist->entries[i].subsystem_roms->size > 0) - { - unsigned j; - - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteString(context.writer, "subsystem_roms", - STRLEN_CONST("subsystem_roms"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - json_write_space(context.writer, 1); - JSON_Writer_WriteStartArray(context.writer); - json_write_new_line(context.writer); - - for (j = 0; j < playlist->entries[i].subsystem_roms->size; j++) - { - const struct string_list *roms = playlist->entries[i].subsystem_roms; - json_write_space(context.writer, 8); - JSON_Writer_WriteString(context.writer, - !string_is_empty(roms->elems[j].data) - ? roms->elems[j].data - : "", - !string_is_empty(roms->elems[j].data) - ? strlen(roms->elems[j].data) - : 0, - JSON_UTF8); - - if (j < playlist->entries[i].subsystem_roms->size - 1) - { - JSON_Writer_WriteComma(context.writer); - json_write_new_line(context.writer); - } - } - - json_write_new_line(context.writer); - json_write_space(context.writer, 6); - JSON_Writer_WriteEndArray(context.writer); - } - - json_write_new_line(context.writer); - - json_write_space(context.writer, 4); - JSON_Writer_WriteEndObject(context.writer); - - if (i < playlist->size - 1) - JSON_Writer_WriteComma(context.writer); - - json_write_new_line(context.writer); - } - - json_write_space(context.writer, 2); - JSON_Writer_WriteEndArray(context.writer); - json_write_new_line(context.writer); - JSON_Writer_WriteEndObject(context.writer); - json_write_new_line(context.writer); - JSON_Writer_Free(context.writer); - - playlist->old_format = false; - } - - playlist->modified = false; - playlist->compressed = compressed; - - RARCH_LOG("[Playlist]: Written to playlist file: %s\n", playlist->conf_path); -end: - intfstream_close(file); - free(file); -} - -/** - * playlist_free: - * @playlist : Playlist handle. - * - * Frees playlist handle. - */ -void playlist_free(playlist_t *playlist) -{ - size_t i; - - if (!playlist) - return; - - if (playlist->conf_path != NULL) - free(playlist->conf_path); - playlist->conf_path = NULL; - - if (playlist->default_core_path != NULL) - free(playlist->default_core_path); - playlist->default_core_path = NULL; - - if (playlist->default_core_name != NULL) - free(playlist->default_core_name); - playlist->default_core_name = NULL; - - for (i = 0; i < playlist->size; i++) - { - struct playlist_entry *entry = &playlist->entries[i]; - - if (entry) - playlist_free_entry(entry); - } - - free(playlist->entries); - playlist->entries = NULL; - - free(playlist); -} - -/** - * playlist_clear: - * @playlist : Playlist handle. - * - * Clears all playlist entries in playlist. - **/ -void playlist_clear(playlist_t *playlist) -{ - size_t i; - if (!playlist) - return; - - for (i = 0; i < playlist->size; i++) - { - struct playlist_entry *entry = &playlist->entries[i]; - - if (entry) - playlist_free_entry(entry); - } - playlist->size = 0; -} - -/** - * playlist_size: - * @playlist : Playlist handle. - * - * Gets size of playlist. - * Returns: size of playlist. - **/ -size_t playlist_size(playlist_t *playlist) -{ - if (!playlist) - return 0; - return playlist->size; -} - -/** - * playlist_capacity: - * @playlist : Playlist handle. - * - * Gets maximum capacity of playlist. - * Returns: maximum capacity of playlist. - **/ -size_t playlist_capacity(playlist_t *playlist) -{ - if (!playlist) - return 0; - return playlist->cap; -} - -void playlist_write_runtime_file(playlist_t *playlist) -{ - size_t i; - intfstream_t *file = NULL; - JSONContext context = {0}; - - if (!playlist || !playlist->modified) - return; - - file = intfstream_open_file(playlist->conf_path, - RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE); - - if (!file) - { - RARCH_ERR("Failed to write to playlist file: %s\n", playlist->conf_path); - return; - } - - context.writer = JSON_Writer_Create(NULL); - context.file = file; - - if (!context.writer) - { - RARCH_ERR("Failed to create JSON writer\n"); - goto end; - } - - JSON_Writer_SetOutputEncoding(context.writer, JSON_UTF8); - JSON_Writer_SetOutputHandler(context.writer, &JSONOutputHandler); - JSON_Writer_SetUserData(context.writer, &context); - - JSON_Writer_WriteStartObject(context.writer); - JSON_Writer_WriteNewLine(context.writer); - JSON_Writer_WriteSpace(context.writer, 2); - JSON_Writer_WriteString(context.writer, "version", - STRLEN_CONST("version"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteString(context.writer, "1.0", - STRLEN_CONST("1.0"), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); - JSON_Writer_WriteSpace(context.writer, 2); - JSON_Writer_WriteString(context.writer, "items", - STRLEN_CONST("items"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteStartArray(context.writer); - JSON_Writer_WriteNewLine(context.writer); - - for (i = 0; i < playlist->size; i++) - { - JSON_Writer_WriteSpace(context.writer, 4); - JSON_Writer_WriteStartObject(context.writer); - - JSON_Writer_WriteNewLine(context.writer); - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "path", - STRLEN_CONST("path"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->entries[i].path - ? playlist->entries[i].path - : "", - playlist->entries[i].path - ? strlen(playlist->entries[i].path) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - - JSON_Writer_WriteNewLine(context.writer); - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "core_path", - STRLEN_CONST("core_path"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteString(context.writer, - playlist->entries[i].core_path - ? playlist->entries[i].core_path - : "", - playlist->entries[i].core_path - ? strlen(playlist->entries[i].core_path) - : 0, - JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); - - { - char tmp[32] = {0}; - - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].runtime_hours); - - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "runtime_hours", - STRLEN_CONST("runtime_hours"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); - - memset(tmp, 0, sizeof(tmp)); - - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].runtime_minutes); - - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "runtime_minutes", - STRLEN_CONST("runtime_minutes"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); - - memset(tmp, 0, sizeof(tmp)); - - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].runtime_seconds); - - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "runtime_seconds", - STRLEN_CONST("runtime_seconds"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); - - memset(tmp, 0, sizeof(tmp)); - - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_year); - - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "last_played_year", - STRLEN_CONST("last_played_year"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); - - memset(tmp, 0, sizeof(tmp)); - - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_month); - - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "last_played_month", - STRLEN_CONST("last_played_month"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); - - memset(tmp, 0, sizeof(tmp)); - - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_day); - - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "last_played_day", - STRLEN_CONST("last_played_day"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, - strlen(tmp), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); - - memset(tmp, 0, sizeof(tmp)); - - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_hour); - - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "last_played_hour", - STRLEN_CONST("last_played_hour"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); - - memset(tmp, 0, sizeof(tmp)); - - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_minute); - - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "last_played_minute", - STRLEN_CONST("last_played_minute"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); - JSON_Writer_WriteComma(context.writer); - JSON_Writer_WriteNewLine(context.writer); - - memset(tmp, 0, sizeof(tmp)); - - snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_second); - - JSON_Writer_WriteSpace(context.writer, 6); - JSON_Writer_WriteString(context.writer, "last_played_second", - STRLEN_CONST("last_played_second"), JSON_UTF8); - JSON_Writer_WriteColon(context.writer); - JSON_Writer_WriteSpace(context.writer, 1); - JSON_Writer_WriteNumber(context.writer, tmp, - strlen(tmp), JSON_UTF8); - JSON_Writer_WriteNewLine(context.writer); - } - - JSON_Writer_WriteSpace(context.writer, 4); - JSON_Writer_WriteEndObject(context.writer); - - if (i < playlist->size - 1) - JSON_Writer_WriteComma(context.writer); - - JSON_Writer_WriteNewLine(context.writer); - } - - JSON_Writer_WriteSpace(context.writer, 2); - JSON_Writer_WriteEndArray(context.writer); - JSON_Writer_WriteNewLine(context.writer); - JSON_Writer_WriteEndObject(context.writer); - JSON_Writer_WriteNewLine(context.writer); - JSON_Writer_Free(context.writer); - - playlist->modified = false; - playlist->old_format = false; - playlist->compressed = false; - - RARCH_LOG("[Playlist]: Written to playlist file: %s\n", playlist->conf_path); -end: - intfstream_close(file); - free(file); -} - -uint32_t playlist_get_size(playlist_t *playlist) -{ - if (!playlist) - return 0; - return (uint32_t)playlist->size; -} - -char *playlist_get_conf_path(playlist_t *playlist) -{ - if (!playlist) - return NULL; - return playlist->conf_path; -} - -/** - * playlist_get_index: - * @playlist : Playlist handle. - * @idx : Index of playlist entry. - * @path : Path of playlist entry. - * @core_path : Core path of playlist entry. - * @core_name : Core name of playlist entry. - * - * Gets values of playlist index: - **/ -void playlist_get_index(playlist_t *playlist, - size_t idx, - const struct playlist_entry **entry) -{ - if (!playlist || !entry) - return; - - *entry = &playlist->entries[idx]; -} - /** * playlist_delete_index: * @playlist : Playlist handle. @@ -2958,3 +1016,1944 @@ success: return true; } +static JSON_Writer_HandlerResult JSONOutputHandler(JSON_Writer writer, const char *pBytes, size_t length) +{ + JSONContext *context = (JSONContext*)JSON_Writer_GetUserData(writer); + + (void)writer; /* unused */ + return intfstream_write(context->file, pBytes, length) == length ? JSON_Writer_Continue : JSON_Writer_Abort; +} + +static void JSONLogError(JSONContext *pCtx) +{ + if (pCtx->parser && JSON_Parser_GetError(pCtx->parser) != JSON_Error_AbortedByHandler) + { + JSON_Error error = JSON_Parser_GetError(pCtx->parser); + JSON_Location errorLocation = { 0, 0, 0 }; + + (void)JSON_Parser_GetErrorLocation(pCtx->parser, &errorLocation); + RARCH_WARN("Error: Invalid JSON at line %d, column %d (input byte %d) - %s.\n", + (int)errorLocation.line + 1, + (int)errorLocation.column + 1, + (int)errorLocation.byte, + JSON_ErrorString(error)); + } + else if (pCtx->writer && JSON_Writer_GetError(pCtx->writer) != JSON_Error_AbortedByHandler) + { + RARCH_WARN("Error: could not write output - %s.\n", JSON_ErrorString(JSON_Writer_GetError(pCtx->writer))); + } +} + +void playlist_write_runtime_file(playlist_t *playlist) +{ + size_t i; + intfstream_t *file = NULL; + JSONContext context = {0}; + + if (!playlist || !playlist->modified) + return; + + file = intfstream_open_file(playlist->conf_path, + RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE); + + if (!file) + { + RARCH_ERR("Failed to write to playlist file: %s\n", playlist->conf_path); + return; + } + + context.writer = JSON_Writer_Create(NULL); + context.file = file; + + if (!context.writer) + { + RARCH_ERR("Failed to create JSON writer\n"); + goto end; + } + + JSON_Writer_SetOutputEncoding(context.writer, JSON_UTF8); + JSON_Writer_SetOutputHandler(context.writer, &JSONOutputHandler); + JSON_Writer_SetUserData(context.writer, &context); + + JSON_Writer_WriteStartObject(context.writer); + JSON_Writer_WriteNewLine(context.writer); + JSON_Writer_WriteSpace(context.writer, 2); + JSON_Writer_WriteString(context.writer, "version", + STRLEN_CONST("version"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + JSON_Writer_WriteSpace(context.writer, 1); + JSON_Writer_WriteString(context.writer, "1.0", + STRLEN_CONST("1.0"), JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + JSON_Writer_WriteNewLine(context.writer); + JSON_Writer_WriteSpace(context.writer, 2); + JSON_Writer_WriteString(context.writer, "items", + STRLEN_CONST("items"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + JSON_Writer_WriteSpace(context.writer, 1); + JSON_Writer_WriteStartArray(context.writer); + JSON_Writer_WriteNewLine(context.writer); + + for (i = 0; i < playlist->size; i++) + { + JSON_Writer_WriteSpace(context.writer, 4); + JSON_Writer_WriteStartObject(context.writer); + + JSON_Writer_WriteNewLine(context.writer); + JSON_Writer_WriteSpace(context.writer, 6); + JSON_Writer_WriteString(context.writer, "path", + STRLEN_CONST("path"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + JSON_Writer_WriteSpace(context.writer, 1); + JSON_Writer_WriteString(context.writer, + playlist->entries[i].path + ? playlist->entries[i].path + : "", + playlist->entries[i].path + ? strlen(playlist->entries[i].path) + : 0, + JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + + JSON_Writer_WriteNewLine(context.writer); + JSON_Writer_WriteSpace(context.writer, 6); + JSON_Writer_WriteString(context.writer, "core_path", + STRLEN_CONST("core_path"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + JSON_Writer_WriteSpace(context.writer, 1); + JSON_Writer_WriteString(context.writer, + playlist->entries[i].core_path + ? playlist->entries[i].core_path + : "", + playlist->entries[i].core_path + ? strlen(playlist->entries[i].core_path) + : 0, + JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + JSON_Writer_WriteNewLine(context.writer); + + { + char tmp[32] = {0}; + + snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].runtime_hours); + + JSON_Writer_WriteSpace(context.writer, 6); + JSON_Writer_WriteString(context.writer, "runtime_hours", + STRLEN_CONST("runtime_hours"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + JSON_Writer_WriteSpace(context.writer, 1); + JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + JSON_Writer_WriteNewLine(context.writer); + + memset(tmp, 0, sizeof(tmp)); + + snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].runtime_minutes); + + JSON_Writer_WriteSpace(context.writer, 6); + JSON_Writer_WriteString(context.writer, "runtime_minutes", + STRLEN_CONST("runtime_minutes"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + JSON_Writer_WriteSpace(context.writer, 1); + JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + JSON_Writer_WriteNewLine(context.writer); + + memset(tmp, 0, sizeof(tmp)); + + snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].runtime_seconds); + + JSON_Writer_WriteSpace(context.writer, 6); + JSON_Writer_WriteString(context.writer, "runtime_seconds", + STRLEN_CONST("runtime_seconds"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + JSON_Writer_WriteSpace(context.writer, 1); + JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + JSON_Writer_WriteNewLine(context.writer); + + memset(tmp, 0, sizeof(tmp)); + + snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_year); + + JSON_Writer_WriteSpace(context.writer, 6); + JSON_Writer_WriteString(context.writer, "last_played_year", + STRLEN_CONST("last_played_year"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + JSON_Writer_WriteSpace(context.writer, 1); + JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + JSON_Writer_WriteNewLine(context.writer); + + memset(tmp, 0, sizeof(tmp)); + + snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_month); + + JSON_Writer_WriteSpace(context.writer, 6); + JSON_Writer_WriteString(context.writer, "last_played_month", + STRLEN_CONST("last_played_month"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + JSON_Writer_WriteSpace(context.writer, 1); + JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + JSON_Writer_WriteNewLine(context.writer); + + memset(tmp, 0, sizeof(tmp)); + + snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_day); + + JSON_Writer_WriteSpace(context.writer, 6); + JSON_Writer_WriteString(context.writer, "last_played_day", + STRLEN_CONST("last_played_day"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + JSON_Writer_WriteSpace(context.writer, 1); + JSON_Writer_WriteNumber(context.writer, tmp, + strlen(tmp), JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + JSON_Writer_WriteNewLine(context.writer); + + memset(tmp, 0, sizeof(tmp)); + + snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_hour); + + JSON_Writer_WriteSpace(context.writer, 6); + JSON_Writer_WriteString(context.writer, "last_played_hour", + STRLEN_CONST("last_played_hour"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + JSON_Writer_WriteSpace(context.writer, 1); + JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + JSON_Writer_WriteNewLine(context.writer); + + memset(tmp, 0, sizeof(tmp)); + + snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_minute); + + JSON_Writer_WriteSpace(context.writer, 6); + JSON_Writer_WriteString(context.writer, "last_played_minute", + STRLEN_CONST("last_played_minute"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + JSON_Writer_WriteSpace(context.writer, 1); + JSON_Writer_WriteNumber(context.writer, tmp, strlen(tmp), JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + JSON_Writer_WriteNewLine(context.writer); + + memset(tmp, 0, sizeof(tmp)); + + snprintf(tmp, sizeof(tmp), "%u", playlist->entries[i].last_played_second); + + JSON_Writer_WriteSpace(context.writer, 6); + JSON_Writer_WriteString(context.writer, "last_played_second", + STRLEN_CONST("last_played_second"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + JSON_Writer_WriteSpace(context.writer, 1); + JSON_Writer_WriteNumber(context.writer, tmp, + strlen(tmp), JSON_UTF8); + JSON_Writer_WriteNewLine(context.writer); + } + + JSON_Writer_WriteSpace(context.writer, 4); + JSON_Writer_WriteEndObject(context.writer); + + if (i < playlist->size - 1) + JSON_Writer_WriteComma(context.writer); + + JSON_Writer_WriteNewLine(context.writer); + } + + JSON_Writer_WriteSpace(context.writer, 2); + JSON_Writer_WriteEndArray(context.writer); + JSON_Writer_WriteNewLine(context.writer); + JSON_Writer_WriteEndObject(context.writer); + JSON_Writer_WriteNewLine(context.writer); + JSON_Writer_Free(context.writer); + + playlist->modified = false; + playlist->old_format = false; + playlist->compressed = false; + + RARCH_LOG("[Playlist]: Written to playlist file: %s\n", playlist->conf_path); +end: + intfstream_close(file); + free(file); +} + +/* No-op versions of JSON whitespace writers, + * used when generating compressed output */ +static JSON_Status JSON_CALL JSON_Writer_WriteNewLine_NULL(JSON_Writer writer) +{ + return JSON_Success; +} + +static JSON_Status JSON_CALL JSON_Writer_WriteSpace_NULL(JSON_Writer writer, size_t numberOfSpaces) +{ + return JSON_Success; +} + +static JSON_Status (JSON_CALL *json_write_new_line)(JSON_Writer writer) = JSON_Writer_WriteNewLine; +static JSON_Status (JSON_CALL *json_write_space)(JSON_Writer writer, size_t numberOfSpaces) = JSON_Writer_WriteSpace; + +void playlist_write_file( + playlist_t *playlist, + bool use_old_format, bool compress) +{ + size_t i; + intfstream_t *file = NULL; + bool compressed = false; + + /* Playlist will be written if any of the + * following are true: + * > 'modified' flag is set + * > Current playlist format (old/new) does not + * match requested + * > Current playlist compression status does + * not match requested */ + if (!playlist || + !(playlist->modified || +#if defined(HAVE_ZLIB) + (playlist->compressed != compress) || +#endif + (playlist->old_format != use_old_format))) + return; + +#if defined(HAVE_ZLIB) + if (compress) + file = intfstream_open_rzip_file(playlist->conf_path, + RETRO_VFS_FILE_ACCESS_WRITE); + else +#endif + file = intfstream_open_file(playlist->conf_path, + RETRO_VFS_FILE_ACCESS_WRITE, + RETRO_VFS_FILE_ACCESS_HINT_NONE); + + if (!file) + { + RARCH_ERR("Failed to write to playlist file: %s\n", playlist->conf_path); + return; + } + + /* Get current file compression state */ + compressed = intfstream_is_compressed(file); + +#ifdef RARCH_INTERNAL + if (use_old_format) + { + for (i = 0; i < playlist->size; i++) + intfstream_printf(file, "%s\n%s\n%s\n%s\n%s\n%s\n", + playlist->entries[i].path ? playlist->entries[i].path : "", + playlist->entries[i].label ? playlist->entries[i].label : "", + playlist->entries[i].core_path ? playlist->entries[i].core_path : "", + playlist->entries[i].core_name ? playlist->entries[i].core_name : "", + playlist->entries[i].crc32 ? playlist->entries[i].crc32 : "", + playlist->entries[i].db_name ? playlist->entries[i].db_name : "" + ); + + /* Add metadata lines + * > We add these at the end of the file to prevent + * breakage if the playlist is loaded with an older + * version of RetroArch */ + intfstream_printf( + file, + "default_core_path = \"%s\"\n" + "default_core_name = \"%s\"\n" + "label_display_mode = \"%d\"\n" + "thumbnail_mode = \"%d|%d\"\n" + "sort_mode = \"%d\"\n", + playlist->default_core_path ? playlist->default_core_path : "", + playlist->default_core_name ? playlist->default_core_name : "", + playlist->label_display_mode, + playlist->right_thumbnail_mode, playlist->left_thumbnail_mode, + playlist->sort_mode); + + playlist->old_format = true; + } + else +#endif + { + char uint_str[4]; + JSONContext context = {0}; + context.writer = JSON_Writer_Create(NULL); + context.file = file; + + if (!context.writer) + { + RARCH_ERR("Failed to create JSON writer\n"); + goto end; + } + + /* Assign JSON whitespace functions + * > When compressing playlists, human readability + * is not a factor - can skip all indentation + * and new line characters */ + json_write_new_line = compressed ? + JSON_Writer_WriteNewLine_NULL : + JSON_Writer_WriteNewLine; + json_write_space = compressed ? + JSON_Writer_WriteSpace_NULL : + JSON_Writer_WriteSpace; + + JSON_Writer_SetOutputEncoding(context.writer, JSON_UTF8); + JSON_Writer_SetOutputHandler(context.writer, &JSONOutputHandler); + JSON_Writer_SetUserData(context.writer, &context); + + JSON_Writer_WriteStartObject(context.writer); + json_write_new_line(context.writer); + + json_write_space(context.writer, 2); + JSON_Writer_WriteString(context.writer, "version", + STRLEN_CONST("version"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteString(context.writer, "1.4", + STRLEN_CONST("1.4"), JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + json_write_new_line(context.writer); + + json_write_space(context.writer, 2); + JSON_Writer_WriteString(context.writer, "default_core_path", + STRLEN_CONST("default_core_path"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteString(context.writer, + playlist->default_core_path + ? playlist->default_core_path + : "", + playlist->default_core_path + ? strlen(playlist->default_core_path) + : 0, + JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + json_write_new_line(context.writer); + + json_write_space(context.writer, 2); + JSON_Writer_WriteString(context.writer, "default_core_name", + STRLEN_CONST("default_core_name"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteString(context.writer, + playlist->default_core_name + ? playlist->default_core_name + : "", + playlist->default_core_name + ? strlen(playlist->default_core_name) + : 0, + JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + json_write_new_line(context.writer); + + uint_str[0] = '\0'; + snprintf(uint_str, sizeof(uint_str), "%u", playlist->label_display_mode); + + json_write_space(context.writer, 2); + JSON_Writer_WriteString(context.writer, "label_display_mode", + STRLEN_CONST("label_display_mode"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteNumber(context.writer, uint_str, + strlen(uint_str), JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + json_write_new_line(context.writer); + + uint_str[0] = '\0'; + snprintf(uint_str, sizeof(uint_str), "%u", playlist->right_thumbnail_mode); + + json_write_space(context.writer, 2); + JSON_Writer_WriteString(context.writer, "right_thumbnail_mode", + STRLEN_CONST("right_thumbnail_mode"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteNumber(context.writer, uint_str, + strlen(uint_str), JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + json_write_new_line(context.writer); + + uint_str[0] = '\0'; + snprintf(uint_str, sizeof(uint_str), "%u", playlist->left_thumbnail_mode); + + json_write_space(context.writer, 2); + JSON_Writer_WriteString(context.writer, "left_thumbnail_mode", + STRLEN_CONST("left_thumbnail_mode"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteNumber(context.writer, uint_str, + strlen(uint_str), JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + json_write_new_line(context.writer); + + uint_str[0] = '\0'; + snprintf(uint_str, sizeof(uint_str), "%u", playlist->sort_mode); + + json_write_space(context.writer, 2); + JSON_Writer_WriteString(context.writer, "sort_mode", + STRLEN_CONST("sort_mode"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteNumber(context.writer, uint_str, + strlen(uint_str), JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + json_write_new_line(context.writer); + + json_write_space(context.writer, 2); + JSON_Writer_WriteString(context.writer, "items", + STRLEN_CONST("items"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteStartArray(context.writer); + json_write_new_line(context.writer); + + for (i = 0; i < playlist->size; i++) + { + json_write_space(context.writer, 4); + JSON_Writer_WriteStartObject(context.writer); + + json_write_new_line(context.writer); + json_write_space(context.writer, 6); + JSON_Writer_WriteString(context.writer, "path", + STRLEN_CONST("path"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteString(context.writer, + playlist->entries[i].path + ? playlist->entries[i].path + : "", + playlist->entries[i].path + ? strlen(playlist->entries[i].path) + : 0, + JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + + json_write_new_line(context.writer); + json_write_space(context.writer, 6); + JSON_Writer_WriteString(context.writer, "label", + STRLEN_CONST("label"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteString(context.writer, + playlist->entries[i].label + ? playlist->entries[i].label + : "", + playlist->entries[i].label + ? strlen(playlist->entries[i].label) + : 0, + JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + + json_write_new_line(context.writer); + json_write_space(context.writer, 6); + JSON_Writer_WriteString(context.writer, "core_path", + STRLEN_CONST("core_path"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteString(context.writer, + playlist->entries[i].core_path + ? playlist->entries[i].core_path + : "", + playlist->entries[i].core_path + ? strlen(playlist->entries[i].core_path) + : 0, + JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + + json_write_new_line(context.writer); + json_write_space(context.writer, 6); + JSON_Writer_WriteString(context.writer, "core_name", + STRLEN_CONST("core_name"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteString(context.writer, + playlist->entries[i].core_name + ? playlist->entries[i].core_name + : "", + playlist->entries[i].core_name + ? strlen(playlist->entries[i].core_name) + : 0, + JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + + json_write_new_line(context.writer); + json_write_space(context.writer, 6); + JSON_Writer_WriteString(context.writer, "crc32", + STRLEN_CONST("crc32"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteString(context.writer, playlist->entries[i].crc32 ? playlist->entries[i].crc32 : "", + playlist->entries[i].crc32 + ? strlen(playlist->entries[i].crc32) + : 0, + JSON_UTF8); + JSON_Writer_WriteComma(context.writer); + + json_write_new_line(context.writer); + json_write_space(context.writer, 6); + JSON_Writer_WriteString(context.writer, "db_name", + STRLEN_CONST("db_name"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteString(context.writer, playlist->entries[i].db_name ? playlist->entries[i].db_name : "", + playlist->entries[i].db_name + ? strlen(playlist->entries[i].db_name) + : 0, + JSON_UTF8); + + if (!string_is_empty(playlist->entries[i].subsystem_ident)) + { + JSON_Writer_WriteComma(context.writer); + json_write_new_line(context.writer); + json_write_space(context.writer, 6); + JSON_Writer_WriteString(context.writer, "subsystem_ident", + STRLEN_CONST("subsystem_ident"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteString(context.writer, playlist->entries[i].subsystem_ident ? playlist->entries[i].subsystem_ident : "", + playlist->entries[i].subsystem_ident + ? strlen(playlist->entries[i].subsystem_ident) + : 0, + JSON_UTF8); + } + + if (!string_is_empty(playlist->entries[i].subsystem_name)) + { + JSON_Writer_WriteComma(context.writer); + json_write_new_line(context.writer); + json_write_space(context.writer, 6); + JSON_Writer_WriteString(context.writer, "subsystem_name", + STRLEN_CONST("subsystem_name"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteString(context.writer, + playlist->entries[i].subsystem_name + ? playlist->entries[i].subsystem_name + : "", + playlist->entries[i].subsystem_name + ? strlen(playlist->entries[i].subsystem_name) + : 0, JSON_UTF8); + } + + if ( playlist->entries[i].subsystem_roms && + playlist->entries[i].subsystem_roms->size > 0) + { + unsigned j; + + JSON_Writer_WriteComma(context.writer); + json_write_new_line(context.writer); + json_write_space(context.writer, 6); + JSON_Writer_WriteString(context.writer, "subsystem_roms", + STRLEN_CONST("subsystem_roms"), JSON_UTF8); + JSON_Writer_WriteColon(context.writer); + json_write_space(context.writer, 1); + JSON_Writer_WriteStartArray(context.writer); + json_write_new_line(context.writer); + + for (j = 0; j < playlist->entries[i].subsystem_roms->size; j++) + { + const struct string_list *roms = playlist->entries[i].subsystem_roms; + json_write_space(context.writer, 8); + JSON_Writer_WriteString(context.writer, + !string_is_empty(roms->elems[j].data) + ? roms->elems[j].data + : "", + !string_is_empty(roms->elems[j].data) + ? strlen(roms->elems[j].data) + : 0, + JSON_UTF8); + + if (j < playlist->entries[i].subsystem_roms->size - 1) + { + JSON_Writer_WriteComma(context.writer); + json_write_new_line(context.writer); + } + } + + json_write_new_line(context.writer); + json_write_space(context.writer, 6); + JSON_Writer_WriteEndArray(context.writer); + } + + json_write_new_line(context.writer); + + json_write_space(context.writer, 4); + JSON_Writer_WriteEndObject(context.writer); + + if (i < playlist->size - 1) + JSON_Writer_WriteComma(context.writer); + + json_write_new_line(context.writer); + } + + json_write_space(context.writer, 2); + JSON_Writer_WriteEndArray(context.writer); + json_write_new_line(context.writer); + JSON_Writer_WriteEndObject(context.writer); + json_write_new_line(context.writer); + JSON_Writer_Free(context.writer); + + playlist->old_format = false; + } + + playlist->modified = false; + playlist->compressed = compressed; + + RARCH_LOG("[Playlist]: Written to playlist file: %s\n", playlist->conf_path); +end: + intfstream_close(file); + free(file); +} + +/** + * playlist_free: + * @playlist : Playlist handle. + * + * Frees playlist handle. + */ +void playlist_free(playlist_t *playlist) +{ + size_t i; + + if (!playlist) + return; + + if (playlist->conf_path != NULL) + free(playlist->conf_path); + playlist->conf_path = NULL; + + if (playlist->default_core_path != NULL) + free(playlist->default_core_path); + playlist->default_core_path = NULL; + + if (playlist->default_core_name != NULL) + free(playlist->default_core_name); + playlist->default_core_name = NULL; + + for (i = 0; i < playlist->size; i++) + { + struct playlist_entry *entry = &playlist->entries[i]; + + if (entry) + playlist_free_entry(entry); + } + + free(playlist->entries); + playlist->entries = NULL; + + free(playlist); +} + +/** + * playlist_clear: + * @playlist : Playlist handle. + * + * Clears all playlist entries in playlist. + **/ +void playlist_clear(playlist_t *playlist) +{ + size_t i; + if (!playlist) + return; + + for (i = 0; i < playlist->size; i++) + { + struct playlist_entry *entry = &playlist->entries[i]; + + if (entry) + playlist_free_entry(entry); + } + playlist->size = 0; +} + +/** + * playlist_size: + * @playlist : Playlist handle. + * + * Gets size of playlist. + * Returns: size of playlist. + **/ +size_t playlist_size(playlist_t *playlist) +{ + if (!playlist) + return 0; + return playlist->size; +} + +/** + * playlist_capacity: + * @playlist : Playlist handle. + * + * Gets maximum capacity of playlist. + * Returns: maximum capacity of playlist. + **/ +size_t playlist_capacity(playlist_t *playlist) +{ + if (!playlist) + return 0; + return playlist->cap; +} + +static JSON_Parser_HandlerResult JSONStartArrayHandler(JSON_Parser parser) +{ + JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); + + pCtx->array_depth++; + + if (pCtx->object_depth == 1) + { + if (string_is_equal(pCtx->current_meta_string, "items") && pCtx->array_depth == 1) + pCtx->in_items = true; + } + else if (pCtx->object_depth == 2) + { + if (pCtx->array_depth == 2) + if (string_is_equal(pCtx->current_items_string, "subsystem_roms")) + pCtx->in_subsystem_roms = true; + } + + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSONEndArrayHandler(JSON_Parser parser) +{ + JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); + + retro_assert(pCtx->array_depth > 0); + + pCtx->array_depth--; + + if (pCtx->object_depth == 1) + { + if (pCtx->in_items && string_is_equal(pCtx->current_meta_string, "items") && pCtx->array_depth == 0) + { + free(pCtx->current_meta_string); + pCtx->current_meta_string = NULL; + pCtx->in_items = false; + + if (pCtx->current_items_string) + { + free(pCtx->current_items_string); + pCtx->current_items_string = NULL; + } + } + } + else if (pCtx->object_depth == 2) + { + if (pCtx->in_subsystem_roms && string_is_equal(pCtx->current_items_string, "subsystem_roms") && pCtx->array_depth == 1) + pCtx->in_subsystem_roms = false; + } + + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSONStartObjectHandler(JSON_Parser parser) +{ + JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); + + pCtx->object_depth++; + + if (pCtx->in_items && pCtx->object_depth == 2) + { + if ((pCtx->array_depth == 1) && !pCtx->capacity_exceeded) + { + if (pCtx->playlist->size < pCtx->playlist->cap) + pCtx->current_entry = &pCtx->playlist->entries[pCtx->playlist->size]; + else + { + /* Hit max item limit. + * Note: We can't just abort here, since there may + * be more metadata to read at the end of the file... */ + RARCH_WARN("JSON file contains more entries than current playlist capacity. Excess entries will be discarded.\n"); + pCtx->capacity_exceeded = true; + pCtx->current_entry = NULL; + /* In addition, since we are discarding excess entries, + * the playlist must be flagged as being modified + * (i.e. the playlist is not the same as when it was + * last saved to disk...) */ + pCtx->playlist->modified = true; + } + } + } + + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSONEndObjectHandler(JSON_Parser parser) +{ + JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); + + if (pCtx->in_items && pCtx->object_depth == 2) + { + if ((pCtx->array_depth == 1) && !pCtx->capacity_exceeded) + pCtx->playlist->size++; + } + + retro_assert(pCtx->object_depth > 0); + + pCtx->object_depth--; + + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSONStringHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes) +{ + JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); + (void)attributes; /* unused */ + + if (pCtx->in_items && pCtx->in_subsystem_roms && pCtx->object_depth == 2 && pCtx->array_depth == 2) + { + if (pCtx->current_entry_string_list_val && length && !string_is_empty(pValue)) + { + union string_list_elem_attr attr = {0}; + + if (!*pCtx->current_entry_string_list_val) + *pCtx->current_entry_string_list_val = string_list_new(); + + string_list_append(*pCtx->current_entry_string_list_val, pValue, attr); + } + } + else if (pCtx->in_items && pCtx->object_depth == 2) + { + if (pCtx->array_depth == 1) + { + if (pCtx->current_entry_val && length && !string_is_empty(pValue)) + { + if (*pCtx->current_entry_val) + free(*pCtx->current_entry_val); + *pCtx->current_entry_val = strdup(pValue); + } + } + } + else if (pCtx->object_depth == 1) + { + if (pCtx->array_depth == 0) + { + if (pCtx->current_meta_val && length && !string_is_empty(pValue)) + { + /* handle any top-level playlist metadata here */ +#if 0 + RARCH_LOG("[Playlist]: Found meta: %s = %s\n", pCtx->current_meta_string, pValue); +#endif + + free(pCtx->current_meta_string); + pCtx->current_meta_string = NULL; + + if (*pCtx->current_meta_val) + free(*pCtx->current_meta_val); + + *pCtx->current_meta_val = strdup(pValue); + } + } + } + + pCtx->current_entry_val = NULL; + pCtx->current_meta_val = NULL; + + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSONNumberHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes) +{ + JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); + (void)attributes; /* unused */ + + if (pCtx->in_items && pCtx->object_depth == 2) + { + if (pCtx->array_depth == 1) + { + if (pCtx->current_entry_int_val && length && !string_is_empty(pValue)) + *pCtx->current_entry_int_val = (int)strtoul(pValue, NULL, 10); + else if (pCtx->current_entry_uint_val && length && !string_is_empty(pValue)) + *pCtx->current_entry_uint_val = (unsigned)strtoul(pValue, NULL, 10); + } + } + else if (pCtx->object_depth == 1) + { + if (pCtx->array_depth == 0) + { + if (pCtx->current_meta_string && length && !string_is_empty(pValue)) + { + /* handle any top-level playlist metadata here */ +#if 0 + RARCH_LOG("[Playlist]: Found meta: %s = %s\n", pCtx->current_meta_string, pValue); +#endif + + free(pCtx->current_meta_string); + pCtx->current_meta_string = NULL; + + if (pCtx->current_meta_label_display_mode_val) + *pCtx->current_meta_label_display_mode_val = (enum playlist_label_display_mode)strtoul(pValue, NULL, 10); + else if (pCtx->current_meta_thumbnail_mode_val) + *pCtx->current_meta_thumbnail_mode_val = (enum playlist_thumbnail_mode)strtoul(pValue, NULL, 10); + else if (pCtx->current_meta_sort_mode_val) + *pCtx->current_meta_sort_mode_val = (enum playlist_sort_mode)strtoul(pValue, NULL, 10); + } + } + } + + pCtx->current_entry_int_val = NULL; + pCtx->current_entry_uint_val = NULL; + pCtx->current_meta_label_display_mode_val = NULL; + pCtx->current_meta_thumbnail_mode_val = NULL; + pCtx->current_meta_sort_mode_val = NULL; + + return JSON_Parser_Continue; +} + +static JSON_Parser_HandlerResult JSONObjectMemberHandler(JSON_Parser parser, char *pValue, size_t length, JSON_StringAttributes attributes) +{ + JSONContext *pCtx = (JSONContext*)JSON_Parser_GetUserData(parser); + (void)attributes; /* unused */ + + if (pCtx->in_items && pCtx->object_depth == 2) + { + if (pCtx->array_depth == 1) + { + if (pCtx->current_entry_val) + { + /* something went wrong */ + RARCH_WARN("JSON parsing failed at line %d.\n", __LINE__); + return JSON_Parser_Abort; + } + + if (length) + { + if (!string_is_empty(pValue)) + { + if (!string_is_empty(pCtx->current_items_string)) + free(pCtx->current_items_string); + pCtx->current_items_string = strdup(pValue); + } + + if (!pCtx->capacity_exceeded) + { + if (string_is_equal(pValue, "path")) + pCtx->current_entry_val = &pCtx->current_entry->path; + else if (string_is_equal(pValue, "label")) + pCtx->current_entry_val = &pCtx->current_entry->label; + else if (string_is_equal(pValue, "core_path")) + pCtx->current_entry_val = &pCtx->current_entry->core_path; + else if (string_is_equal(pValue, "core_name")) + pCtx->current_entry_val = &pCtx->current_entry->core_name; + else if (string_is_equal(pValue, "crc32")) + pCtx->current_entry_val = &pCtx->current_entry->crc32; + else if (string_is_equal(pValue, "db_name")) + pCtx->current_entry_val = &pCtx->current_entry->db_name; + else if (string_starts_with_size(pValue, "subsystem_", STRLEN_CONST("subsystem_"))) + { + if (string_is_equal(pValue, "subsystem_ident")) + pCtx->current_entry_val = &pCtx->current_entry->subsystem_ident; + else if (string_is_equal(pValue, "subsystem_name")) + pCtx->current_entry_val = &pCtx->current_entry->subsystem_name; + else if (string_is_equal(pValue, "subsystem_roms")) + pCtx->current_entry_string_list_val = &pCtx->current_entry->subsystem_roms; + } + else if (string_starts_with_size(pValue, "runtime_", STRLEN_CONST("runtime_"))) + { + if (string_is_equal(pValue, "runtime_hours")) + pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_hours; + else if (string_is_equal(pValue, "runtime_minutes")) + pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_minutes; + else if (string_is_equal(pValue, "runtime_seconds")) + pCtx->current_entry_uint_val = &pCtx->current_entry->runtime_seconds; + } + else if (string_starts_with_size(pValue, "last_played_", STRLEN_CONST("last_played_"))) + { + if (string_is_equal(pValue, "last_played_year")) + pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_year; + else if (string_is_equal(pValue, "last_played_month")) + pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_month; + else if (string_is_equal(pValue, "last_played_day")) + pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_day; + else if (string_is_equal(pValue, "last_played_hour")) + pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_hour; + else if (string_is_equal(pValue, "last_played_minute")) + pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_minute; + else if (string_is_equal(pValue, "last_played_second")) + pCtx->current_entry_uint_val = &pCtx->current_entry->last_played_second; + } + } + else + { + pCtx->current_entry_val = NULL; + pCtx->current_entry_uint_val = NULL; + pCtx->current_entry_string_list_val = NULL; + } + } + } + } + else if (pCtx->object_depth == 1) + { + if (pCtx->array_depth == 0) + { + if (pCtx->current_meta_val) + { + /* something went wrong */ + RARCH_WARN("JSON parsing failed at line %d.\n", __LINE__); + return JSON_Parser_Abort; + } + + if (length) + { + if (pCtx->current_meta_string) + free(pCtx->current_meta_string); + pCtx->current_meta_string = strdup(pValue); + + if (string_is_equal(pValue, "default_core_path")) + pCtx->current_meta_val = &pCtx->playlist->default_core_path; + else if (string_is_equal(pValue, "default_core_name")) + pCtx->current_meta_val = &pCtx->playlist->default_core_name; + else if (string_is_equal(pValue, "label_display_mode")) + pCtx->current_meta_label_display_mode_val = &pCtx->playlist->label_display_mode; + else if (string_is_equal(pValue, "right_thumbnail_mode")) + pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->right_thumbnail_mode; + else if (string_is_equal(pValue, "left_thumbnail_mode")) + pCtx->current_meta_thumbnail_mode_val = &pCtx->playlist->left_thumbnail_mode; + else if (string_is_equal(pValue, "sort_mode")) + pCtx->current_meta_sort_mode_val = &pCtx->playlist->sort_mode; + } + } + } + + return JSON_Parser_Continue; +} + +static void get_old_format_metadata_value( + char *metadata_line, char *value, size_t len) +{ + char *end = NULL; + char *start = strchr(metadata_line, '\"'); + + if (!start) + return; + + start++; + end = strchr(start, '\"'); + + if (!end) + return; + + *end = '\0'; + strlcpy(value, start, len); +} + +static bool playlist_read_file( + playlist_t *playlist, const char *path) +{ + unsigned i; + int test_char; +#if defined(HAVE_ZLIB) + /* Always use RZIP interface when reading playlists + * > this will automatically handle uncompressed + * data */ + intfstream_t *file = intfstream_open_rzip_file(path, + RETRO_VFS_FILE_ACCESS_READ); +#else + intfstream_t *file = intfstream_open_file(path, + RETRO_VFS_FILE_ACCESS_READ, + RETRO_VFS_FILE_ACCESS_HINT_NONE); +#endif + + playlist->compressed = intfstream_is_compressed(file); + + /* If playlist file does not exist, + * create an empty playlist instead */ + if (!file) + return true; + + /* Detect format of playlist + * > Read file until we find the first printable + * non-whitespace ASCII character */ + do + { + test_char = intfstream_getc(file); + + if (test_char == EOF) /* read error or end of file */ + goto end; + }while (!isgraph(test_char) || test_char > 0x7F); + + if (test_char == '{') + { + /* New playlist format detected */ +#if 0 + RARCH_LOG("[Playlist]: New playlist format detected.\n"); +#endif + playlist->old_format = false; + } + else + { + /* old playlist format detected */ +#if 0 + RARCH_LOG("[Playlist]: Old playlist format detected.\n"); +#endif + playlist->old_format = true; + } + + /* Reset file to start */ + intfstream_rewind(file); + + if (!playlist->old_format) + { + JSONContext context = {0}; + context.parser = JSON_Parser_Create(NULL); + context.file = file; + context.playlist = playlist; + + if (!context.parser) + { + RARCH_ERR("Failed to create JSON parser\n"); + goto end; + } + +#if 0 + JSON_Parser_SetTrackObjectMembers(context.parser, JSON_True); +#endif + JSON_Parser_SetAllowBOM(context.parser, JSON_True); + JSON_Parser_SetAllowComments(context.parser, JSON_True); + JSON_Parser_SetAllowSpecialNumbers(context.parser, JSON_True); + JSON_Parser_SetAllowHexNumbers(context.parser, JSON_True); + JSON_Parser_SetAllowUnescapedControlCharacters(context.parser, JSON_True); + JSON_Parser_SetReplaceInvalidEncodingSequences(context.parser, JSON_True); + +#if 0 + JSON_Parser_SetNullHandler(context.parser, &JSONNullHandler); + JSON_Parser_SetBooleanHandler(context.parser, &JSONBooleanHandler); + JSON_Parser_SetSpecialNumberHandler(context.parser, &JSONSpecialNumberHandler); + JSON_Parser_SetArrayItemHandler(context.parser, &JSONArrayItemHandler); +#endif + + JSON_Parser_SetNumberHandler(context.parser, &JSONNumberHandler); + JSON_Parser_SetStringHandler(context.parser, &JSONStringHandler); + JSON_Parser_SetStartObjectHandler(context.parser, &JSONStartObjectHandler); + JSON_Parser_SetEndObjectHandler(context.parser, &JSONEndObjectHandler); + JSON_Parser_SetObjectMemberHandler(context.parser, &JSONObjectMemberHandler); + JSON_Parser_SetStartArrayHandler(context.parser, &JSONStartArrayHandler); + JSON_Parser_SetEndArrayHandler(context.parser, &JSONEndArrayHandler); + JSON_Parser_SetUserData(context.parser, &context); + + while (!intfstream_eof(file)) + { + char chunk[4096] = {0}; + int64_t length = intfstream_read(file, chunk, sizeof(chunk)); + + if (!length && !intfstream_eof(file)) + { + RARCH_WARN("Could not read JSON input.\n"); + goto json_cleanup; + } + + if (!JSON_Parser_Parse(context.parser, chunk, length, JSON_False)) + { + /* Note: Chunk may not be null-terminated. + * It is therefore dangerous to print its contents. + * Setting a size limit here mitigates the issue, but + * in general this is not good practice... + * Addendum: RARCH_WARN() actually limits the printed + * buffer size anyway, so this warning message is most + * likely worthless... */ + RARCH_WARN("Error parsing chunk:\n---snip---\n%.*s\n---snip---\n", 4096, chunk); + JSONLogError(&context); + goto json_cleanup; + } + } + + if (!JSON_Parser_Parse(context.parser, NULL, 0, JSON_True)) + { + RARCH_WARN("Error parsing JSON.\n"); + JSONLogError(&context); + goto json_cleanup; + } + +json_cleanup: + + JSON_Parser_Free(context.parser); + + if (context.current_meta_string) + free(context.current_meta_string); + + if (context.current_items_string) + free(context.current_items_string); + } + else + { + char line_buf[PLAYLIST_ENTRIES][PATH_MAX_LENGTH] = {{0}}; + + /* Unnecessary, but harmless */ + for (i = 0; i < PLAYLIST_ENTRIES; i++) + line_buf[i][0] = '\0'; + + /* Read playlist entries */ + playlist->size = 0; + while (playlist->size < playlist->cap) + { + size_t i; + size_t lines_read = 0; + + /* Attempt to read the next 'PLAYLIST_ENTRIES' + * lines from the file */ + for (i = 0; i < PLAYLIST_ENTRIES; i++) + { + *line_buf[i] = '\0'; + + if (intfstream_gets(file, line_buf[i], sizeof(line_buf[i]))) + { + /* Ensure line is NUL terminated, regardless of + * Windows or Unix line endings */ + string_replace_all_chars(line_buf[i], '\r', '\0'); + string_replace_all_chars(line_buf[i], '\n', '\0'); + + lines_read++; + } + else + break; + } + + /* If a 'full set' of lines were read, then this + * is a valid playlist entry */ + if (lines_read >= PLAYLIST_ENTRIES) + { + struct playlist_entry *entry = + &playlist->entries[playlist->size]; + + if (!entry) + continue; + + /* path */ + if (!string_is_empty(line_buf[0])) + entry->path = strdup(line_buf[0]); + + /* label */ + if (!string_is_empty(line_buf[1])) + entry->label = strdup(line_buf[1]); + + /* core_path */ + if (!string_is_empty(line_buf[2])) + entry->core_path = strdup(line_buf[2]); + + /* core_name */ + if (!string_is_empty(line_buf[3])) + entry->core_name = strdup(line_buf[3]); + + /* crc32 */ + if (!string_is_empty(line_buf[4])) + entry->crc32 = strdup(line_buf[4]); + + /* db_name */ + if (!string_is_empty(line_buf[5])) + entry->db_name = strdup(line_buf[5]); + + playlist->size++; + } + /* If fewer than 'PLAYLIST_ENTRIES' lines were + * read, then this is metadata */ + else + { + char default_core_path[PATH_MAX_LENGTH]; + char default_core_name[PATH_MAX_LENGTH]; + + default_core_path[0] = '\0'; + default_core_name[0] = '\0'; + + /* Get default_core_path */ + if (lines_read < 1) + break; + + if (strncmp("default_core_path", + line_buf[0], + STRLEN_CONST("default_core_path")) == 0) + get_old_format_metadata_value( + line_buf[0], default_core_path, sizeof(default_core_path)); + + /* Get default_core_name */ + if (lines_read < 2) + break; + + if (strncmp("default_core_name", + line_buf[1], + STRLEN_CONST("default_core_name")) == 0) + get_old_format_metadata_value( + line_buf[1], default_core_name, sizeof(default_core_name)); + + /* > Populate default core path/name, if required + * (if one is empty, the other should be ignored) */ + if (!string_is_empty(default_core_path) && + !string_is_empty(default_core_name)) + { + playlist->default_core_path = strdup(default_core_path); + playlist->default_core_name = strdup(default_core_name); + } + + /* Get label_display_mode */ + if (lines_read < 3) + break; + + if (strncmp("label_display_mode", + line_buf[2], + STRLEN_CONST("label_display_mode")) == 0) + { + unsigned display_mode; + char display_mode_str[4] = {0}; + + get_old_format_metadata_value( + line_buf[2], display_mode_str, sizeof(display_mode_str)); + + display_mode = string_to_unsigned(display_mode_str); + + if (display_mode <= LABEL_DISPLAY_MODE_KEEP_REGION_AND_DISC_INDEX) + playlist->label_display_mode = (enum playlist_label_display_mode)display_mode; + } + + /* Get thumbnail modes */ + if (lines_read < 4) + break; + + if (strncmp("thumbnail_mode", + line_buf[3], + STRLEN_CONST("thumbnail_mode")) == 0) + { + char thumbnail_mode_str[8] = {0}; + struct string_list *thumbnail_modes = NULL; + + get_old_format_metadata_value( + line_buf[3], thumbnail_mode_str, sizeof(thumbnail_mode_str)); + + thumbnail_modes = string_split(thumbnail_mode_str, "|"); + + if (thumbnail_modes) + { + if (thumbnail_modes->size == 2) + { + unsigned thumbnail_mode; + + /* Right thumbnail mode */ + thumbnail_mode = string_to_unsigned(thumbnail_modes->elems[0].data); + if (thumbnail_mode <= PLAYLIST_THUMBNAIL_MODE_BOXARTS) + playlist->right_thumbnail_mode = (enum playlist_thumbnail_mode)thumbnail_mode; + + /* Left thumbnail mode */ + thumbnail_mode = string_to_unsigned(thumbnail_modes->elems[1].data); + if (thumbnail_mode <= PLAYLIST_THUMBNAIL_MODE_BOXARTS) + playlist->left_thumbnail_mode = (enum playlist_thumbnail_mode)thumbnail_mode; + } + + string_list_free(thumbnail_modes); + } + } + + /* Get sort_mode */ + if (lines_read < 5) + break; + + if (strncmp("sort_mode", + line_buf[4], + STRLEN_CONST("sort_mode")) == 0) + { + unsigned sort_mode; + char sort_mode_str[4] = {0}; + + get_old_format_metadata_value( + line_buf[4], sort_mode_str, sizeof(sort_mode_str)); + + sort_mode = string_to_unsigned(sort_mode_str); + + if (sort_mode <= PLAYLIST_SORT_MODE_OFF) + playlist->sort_mode = (enum playlist_sort_mode)sort_mode; + } + + /* All metadata parsed -> end of file */ + break; + } + } + } + +end: + intfstream_close(file); + free(file); + return true; +} + +void playlist_free_cached(void) +{ + playlist_free(playlist_cached); + playlist_cached = NULL; +} + +playlist_t *playlist_get_cached(void) +{ + if (playlist_cached) + return playlist_cached; + return NULL; +} + +bool playlist_init_cached( + const char *path, size_t size, + bool use_old_format, bool compress) +{ + playlist_t *playlist = playlist_init(path, size); + if (!playlist) + return false; + + /* If playlist format/compression state + * does not match requested settings, update + * file on disk immediately */ + if ( +#if defined(HAVE_ZLIB) + (playlist->compressed != compress) || +#endif + (playlist->old_format != use_old_format)) + playlist_write_file(playlist, use_old_format, compress); + + playlist_cached = playlist; + return true; +} + +/** + * playlist_init: + * @path : Path to playlist contents file. + * @size : Maximum capacity of playlist size. + * + * Creates and initializes a playlist. + * + * Returns: handle to new playlist if successful, otherwise NULL + **/ +playlist_t *playlist_init(const char *path, size_t size) +{ + struct playlist_entry *entries = NULL; + playlist_t *playlist = (playlist_t*)malloc(sizeof(*playlist)); + if (!playlist) + return NULL; + + entries = (struct playlist_entry*)calloc(size, sizeof(*entries)); + if (!entries) + { + free(playlist); + return NULL; + } + + playlist->modified = false; + playlist->old_format = false; + playlist->compressed = false; + playlist->size = 0; + playlist->cap = size; + playlist->conf_path = strdup(path); + playlist->default_core_name = NULL; + playlist->default_core_path = NULL; + playlist->entries = entries; + playlist->label_display_mode = LABEL_DISPLAY_MODE_DEFAULT; + playlist->right_thumbnail_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT; + playlist->left_thumbnail_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT; + playlist->sort_mode = PLAYLIST_SORT_MODE_DEFAULT; + + playlist_read_file(playlist, path); + + return playlist; +} + +static int playlist_qsort_func(const struct playlist_entry *a, + const struct playlist_entry *b) +{ + char *a_str = NULL; + char *b_str = NULL; + char *a_fallback_label = NULL; + char *b_fallback_label = NULL; + int ret = 0; + + if (!a || !b) + goto end; + + a_str = a->label; + b_str = b->label; + + /* It is quite possible for playlist labels + * to be blank. If that is the case, have to use + * filename as a fallback (this is slow, but we + * have no other option...) */ + if (string_is_empty(a_str)) + { + a_fallback_label = (char*)calloc(PATH_MAX_LENGTH, sizeof(char)); + + if (!a_fallback_label) + goto end; + + if (!string_is_empty(a->path)) + fill_short_pathname_representation(a_fallback_label, a->path, PATH_MAX_LENGTH * sizeof(char)); + /* If filename is also empty, use core name + * instead -> this matches the behaviour of + * menu_displaylist_parse_playlist() */ + else if (!string_is_empty(a->core_name)) + strlcpy(a_fallback_label, a->core_name, PATH_MAX_LENGTH * sizeof(char)); + + /* If both filename and core name are empty, + * then have to compare an empty string + * -> again, this is to match the behaviour of + * menu_displaylist_parse_playlist() */ + + a_str = a_fallback_label; + } + + if (string_is_empty(b_str)) + { + b_fallback_label = (char*)calloc(PATH_MAX_LENGTH, sizeof(char)); + + if (!b_fallback_label) + goto end; + + if (!string_is_empty(b->path)) + fill_short_pathname_representation(b_fallback_label, b->path, PATH_MAX_LENGTH * sizeof(char)); + else if (!string_is_empty(b->core_name)) + strlcpy(b_fallback_label, b->core_name, PATH_MAX_LENGTH * sizeof(char)); + + b_str = b_fallback_label; + } + + ret = strcasecmp(a_str, b_str); + +end: + + a_str = NULL; + b_str = NULL; + + if (a_fallback_label) + { + free(a_fallback_label); + a_fallback_label = NULL; + } + + if (b_fallback_label) + { + free(b_fallback_label); + b_fallback_label = NULL; + } + + return ret; +} + +void playlist_qsort(playlist_t *playlist) +{ + /* Avoid inadvertent sorting if 'sort mode' + * has been set explicitly to PLAYLIST_SORT_MODE_OFF */ + if (!playlist || + (playlist->sort_mode == PLAYLIST_SORT_MODE_OFF)) + return; + + qsort(playlist->entries, playlist->size, + sizeof(struct playlist_entry), + (int (*)(const void *, const void *))playlist_qsort_func); +} + +void command_playlist_push_write( + playlist_t *playlist, + const struct playlist_entry *entry, + bool fuzzy_archive_match, + bool use_old_format, + bool compress) +{ + if (!playlist) + return; + + if (playlist_push(playlist, entry, fuzzy_archive_match)) + playlist_write_file(playlist, use_old_format, compress); +} + +void command_playlist_update_write( + playlist_t *plist, + size_t idx, + const struct playlist_entry *entry, + bool use_old_format, + bool compress) +{ + playlist_t *playlist = plist ? plist : playlist_get_cached(); + + if (!playlist) + return; + + playlist_update( + playlist, + idx, + entry); + + playlist_write_file(playlist, use_old_format, compress); +} + +bool playlist_index_is_valid(playlist_t *playlist, size_t idx, + const char *path, const char *core_path) +{ + if (!playlist) + return false; + + if (idx >= playlist->size) + return false; + + return string_is_equal(playlist->entries[idx].path, path) && + string_is_equal(path_basename(playlist->entries[idx].core_path), path_basename(core_path)); +} + +bool playlist_entries_are_equal( + const struct playlist_entry *entry_a, + const struct playlist_entry *entry_b, + bool fuzzy_archive_match) +{ + char real_path_a[PATH_MAX_LENGTH]; + char real_core_path_a[PATH_MAX_LENGTH]; + + real_path_a[0] = '\0'; + real_core_path_a[0] = '\0'; + + /* Sanity check */ + if (!entry_a || !entry_b) + return false; + + if (string_is_empty(entry_a->path) && + string_is_empty(entry_a->core_path) && + string_is_empty(entry_b->path) && + string_is_empty(entry_b->core_path)) + return true; + + /* Check content paths */ + if (!string_is_empty(entry_a->path)) + { + strlcpy(real_path_a, entry_a->path, sizeof(real_path_a)); + path_resolve_realpath(real_path_a, sizeof(real_path_a), true); + } + + if (!playlist_path_equal( + real_path_a, entry_b->path, fuzzy_archive_match)) + return false; + + /* Check core paths */ + if (!string_is_empty(entry_a->core_path)) + { + strlcpy(real_core_path_a, entry_a->core_path, sizeof(real_core_path_a)); + if (!string_is_equal(real_core_path_a, "DETECT") && + !string_is_equal(real_core_path_a, "builtin")) + path_resolve_realpath(real_core_path_a, sizeof(real_core_path_a), true); + } + + return playlist_core_path_equal(real_core_path_a, entry_b->core_path); +} + +void playlist_get_crc32(playlist_t *playlist, size_t idx, + const char **crc32) +{ + if (!playlist) + return; + + if (crc32) + *crc32 = playlist->entries[idx].crc32; +} + +void playlist_get_db_name(playlist_t *playlist, size_t idx, + const char **db_name) +{ + if (!playlist) + return; + + if (db_name) + { + if (!string_is_empty(playlist->entries[idx].db_name)) + *db_name = playlist->entries[idx].db_name; + else + { + const char *conf_path_basename = path_basename(playlist->conf_path); + + /* Only use file basename if this is a 'collection' playlist + * (i.e. ignore history/favourites) */ + if ( + !string_is_empty(conf_path_basename) + && !string_ends_with_size(playlist->conf_path, "_history.lpl", + strlen(playlist->conf_path), STRLEN_CONST("_history.lpl")) + && !string_is_equal(conf_path_basename, + file_path_str(FILE_PATH_CONTENT_FAVORITES)) + ) + *db_name = conf_path_basename; + } + } +} + +char *playlist_get_default_core_path(playlist_t *playlist) +{ + if (!playlist) + return NULL; + return playlist->default_core_path; +} + +char *playlist_get_default_core_name(playlist_t *playlist) +{ + if (!playlist) + return NULL; + return playlist->default_core_name; +} + +enum playlist_label_display_mode playlist_get_label_display_mode(playlist_t *playlist) +{ + if (!playlist) + return LABEL_DISPLAY_MODE_DEFAULT; + return playlist->label_display_mode; +} + +enum playlist_thumbnail_mode playlist_get_thumbnail_mode( + playlist_t *playlist, enum playlist_thumbnail_id thumbnail_id) +{ + if (!playlist) + return PLAYLIST_THUMBNAIL_MODE_DEFAULT; + + if (thumbnail_id == PLAYLIST_THUMBNAIL_RIGHT) + return playlist->right_thumbnail_mode; + else if (thumbnail_id == PLAYLIST_THUMBNAIL_LEFT) + return playlist->left_thumbnail_mode; + + /* Fallback */ + return PLAYLIST_THUMBNAIL_MODE_DEFAULT; +} + +enum playlist_sort_mode playlist_get_sort_mode(playlist_t *playlist) +{ + if (!playlist) + return PLAYLIST_SORT_MODE_DEFAULT; + return playlist->sort_mode; +} + +void playlist_set_default_core_path(playlist_t *playlist, const char *core_path) +{ + char real_core_path[PATH_MAX_LENGTH]; + + real_core_path[0] = '\0'; + + if (!playlist || string_is_empty(core_path)) + return; + + /* Get 'real' core path */ + strlcpy(real_core_path, core_path, sizeof(real_core_path)); + if (!string_is_equal(real_core_path, "DETECT") && + !string_is_equal(real_core_path, "builtin")) + playlist_resolve_path(PLAYLIST_SAVE, + real_core_path, sizeof(real_core_path)); + + if (string_is_empty(real_core_path)) + return; + + if (!string_is_equal(playlist->default_core_path, real_core_path)) + { + if (playlist->default_core_path) + free(playlist->default_core_path); + playlist->default_core_path = strdup(real_core_path); + playlist->modified = true; + } +} + +void playlist_set_default_core_name(playlist_t *playlist, const char *core_name) +{ + if (!playlist || string_is_empty(core_name)) + return; + + if (!string_is_equal(playlist->default_core_name, core_name)) + { + if (playlist->default_core_name) + free(playlist->default_core_name); + playlist->default_core_name = strdup(core_name); + playlist->modified = true; + } +} + +void playlist_set_label_display_mode(playlist_t *playlist, + enum playlist_label_display_mode label_display_mode) +{ + if (!playlist) + return; + + if (playlist->label_display_mode != label_display_mode) + { + playlist->label_display_mode = label_display_mode; + playlist->modified = true; + } +} + +void playlist_set_thumbnail_mode( + playlist_t *playlist, enum playlist_thumbnail_id thumbnail_id, + enum playlist_thumbnail_mode thumbnail_mode) +{ + if (!playlist) + return; + + switch (thumbnail_id) + { + case PLAYLIST_THUMBNAIL_RIGHT: + playlist->right_thumbnail_mode = thumbnail_mode; + playlist->modified = true; + break; + case PLAYLIST_THUMBNAIL_LEFT: + playlist->left_thumbnail_mode = thumbnail_mode; + playlist->modified = true; + break; + } +} + +void playlist_set_sort_mode(playlist_t *playlist, enum playlist_sort_mode sort_mode) +{ + if (!playlist) + return; + + if (playlist->sort_mode != sort_mode) + { + playlist->sort_mode = sort_mode; + playlist->modified = true; + } +} + +/* Returns true if specified entry has a valid + * core association (i.e. a non-empty string + * other than DETECT) */ +bool playlist_entry_has_core(const struct playlist_entry *entry) +{ + if (!entry || + string_is_empty(entry->core_path) || + string_is_empty(entry->core_name) || + string_is_equal(entry->core_path, "DETECT") || + string_is_equal(entry->core_name, "DETECT")) + return false; + + return true; +} + +/* Fetches core info object corresponding to the + * currently associated core of the specified + * playlist entry. + * Returns NULL if entry does not have a valid + * core association */ +core_info_t *playlist_entry_get_core_info(const struct playlist_entry* entry) +{ + core_info_ctx_find_t core_info; + + if (!playlist_entry_has_core(entry)) + return NULL; + + /* Search for associated core */ + core_info.inf = NULL; + core_info.path = entry->core_path; + + if (core_info_find(&core_info)) + return core_info.inf; + + return NULL; +} + +/* Fetches core info object corresponding to the + * currently associated default core of the + * specified playlist. + * Returns NULL if playlist does not have a valid + * default core association */ +core_info_t *playlist_get_default_core_info(playlist_t* playlist) +{ + core_info_ctx_find_t core_info; + + if (!playlist || + string_is_empty(playlist->default_core_path) || + string_is_empty(playlist->default_core_name) || + string_is_equal(playlist->default_core_path, "DETECT") || + string_is_equal(playlist->default_core_name, "DETECT")) + return NULL; + + /* Search for associated core */ + core_info.inf = NULL; + core_info.path = playlist->default_core_path; + + if (core_info_find(&core_info)) + return core_info.inf; + + return NULL; +} diff --git a/runtime_file.c b/runtime_file.c index 69769a3c21..7be5643ccb 100644 --- a/runtime_file.c +++ b/runtime_file.c @@ -265,39 +265,6 @@ end: filestream_close(file); } -static void last_played_strftime(runtime_log_t *runtime_log, char *str, size_t len, const char *format) -{ - struct tm time_info; - char *local = NULL; - - if (!runtime_log) - return; - - /* Get time */ - runtime_log_get_last_played_time(runtime_log, &time_info); - - /* Ensure correct locale is set */ - setlocale(LC_TIME, ""); - - /* Generate string */ -#if defined(__linux__) && !defined(ANDROID) - strftime(str, len, format, &time_info); -#else - strftime(str, len, format, &time_info); - local = local_to_utf8_string_alloc(str); - - if (!string_is_empty(local)) - strlcpy(str, local, len); - - if (local) - { - free(local); - local = NULL; - } -#endif -} - - /* Initialise runtime log, loading current parameters * if log file exists. Returned object must be free()'d. * Returns NULL if content_path and/or core_path are invalid */ @@ -667,6 +634,38 @@ void runtime_log_get_last_played_time(runtime_log_t *runtime_log, struct tm *tim mktime(time_info); } +static void last_played_strftime(runtime_log_t *runtime_log, char *str, size_t len, const char *format) +{ + struct tm time_info; + char *local = NULL; + + if (!runtime_log) + return; + + /* Get time */ + runtime_log_get_last_played_time(runtime_log, &time_info); + + /* Ensure correct locale is set */ + setlocale(LC_TIME, ""); + + /* Generate string */ +#if defined(__linux__) && !defined(ANDROID) + strftime(str, len, format, &time_info); +#else + strftime(str, len, format, &time_info); + local = local_to_utf8_string_alloc(str); + + if (!string_is_empty(local)) + strlcpy(str, local, len); + + if (local) + { + free(local); + local = NULL; + } +#endif +} + /* Gets last played entry value as a pre-formatted string */ void runtime_log_get_last_played_str(runtime_log_t *runtime_log, char *str, size_t len,