diff options
Diffstat (limited to 'src')
265 files changed, 29653 insertions, 25451 deletions
diff --git a/src/Makefile b/src/Makefile index 482f7c0..105caa1 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,6 +1,14 @@ include ../extra.mk -SUBDIRS := audacious libaudcore libaudgui libaudtag +SUBDIRS := audacious libaudcore libaudtag + +ifeq ($(USE_GTK),yes) +SUBDIRS += libaudgui +endif + +ifeq ($(USE_QT),yes) +SUBDIRS += libaudqt +endif ifeq ($(USE_DBUS),yes) SUBDIRS := dbus audtool ${SUBDIRS} @@ -8,8 +16,16 @@ endif include ../buildsys.mk -audacious libaudgui libaudtag: libaudcore -audacious: libaudgui libaudtag +audacious: libaudcore +libaudtag: libaudcore + +ifeq ($(USE_GTK),yes) +libaudgui: libaudcore +endif + +ifeq ($(USE_QT),yes) +libaudqt: libaudcore +endif ifeq ($(USE_DBUS),yes) audacious audtool: dbus diff --git a/src/audacious/Makefile b/src/audacious/Makefile index bccf836..89a017e 100644 --- a/src/audacious/Makefile +++ b/src/audacious/Makefile @@ -1,79 +1,17 @@ include ../../extra.mk PROG = audacious${PROG_SUFFIX} -SRCS = adder.c \ - art.c \ - chardet.c \ - config.c \ - drct.c \ - effect.c \ - equalizer.c \ - equalizer_preset.c \ - fft.c \ - general.c \ - history.c \ - interface.c \ - main.c \ - output.c \ - playback.c \ - playlist-files.c \ - playlist-new.c \ - playlist-utils.c \ - pluginenum.c \ - plugin-preferences.c \ - plugin-registry.c \ - plugin-init.c \ - plugin-view.c \ - preferences.c \ - probe.c \ - probe-buffer.c \ - scanner.c \ - signals.c \ - ui_plugin_menu.c \ - ui_preferences.c \ - util.c \ - vis_runner.c \ - visualization.c \ - ui_albumart.c + +SRCS = main.cc \ + signals.cc \ + util.cc ifeq ($(HAVE_MSWINDOWS),yes) SRCS += audacious.rc endif -INCLUDES = api.h \ - api-alias-begin.h \ - api-alias-end.h \ - api-define-begin.h \ - api-define-end.h \ - debug.h \ - drct.h \ - drct-api.h \ - i18n.h \ - input.h \ - input-api.h \ - misc.h \ - misc-api.h \ - playlist.h \ - playlist-api.h \ - plugin.h \ - plugins.h \ - plugins-api.h \ - preferences.h \ - types.h - -DATA = images/about-logo.png \ - images/album.png \ - images/appearance.png \ - images/audio.png \ - images/connectivity.png \ - images/info.png \ - images/playlist.png \ - images/plugins.png - -CLEAN = build_stamp.c - ifeq ($(USE_DBUS),yes) -SRCS += dbus-server.c +SRCS += dbus-server.cc EXT_DEPS += ../dbus/aud-dbus.a endif @@ -84,30 +22,16 @@ CPPFLAGS := -I../dbus ${CPPFLAGS} ${GIO_CFLAGS} LIBS := ../dbus/aud-dbus.a ${LIBS} ${GIO_LIBS} endif +LD = ${CXX} + CPPFLAGS := -I.. -I../.. \ ${CPPFLAGS} \ - ${GLIB_CFLAGS} \ - ${GMODULE_CFLAGS} \ - ${GTK_CFLAGS} \ - ${LIBGUESS_CFLAGS} + ${GLIB_CFLAGS} -CPPFLAGS := ${CPPFLAGS} \ - -D_AUDACIOUS_CORE \ - -DHARDCODE_BINDIR=\"${bindir}\" \ - -DHARDCODE_DATADIR=\"${datadir}/audacious\" \ - -DHARDCODE_PLUGINDIR=\"${plugindir}\" \ - -DHARDCODE_LOCALEDIR=\"${localedir}\" \ - -DHARDCODE_DESKTOPFILE=\"${datarootdir}/applications/audacious.desktop\" \ - -DHARDCODE_ICONFILE=\"${datarootdir}/pixmaps/audacious.png\" +CPPFLAGS += -D_AUDACIOUS_CORE LIBS := -L../libaudcore -laudcore \ - -L../libaudgui -laudgui \ - -L../libaudtag -laudtag \ ${LIBS} -lm \ - ${LIBINTL} \ - ${GLIB_LIBS} \ - ${GMODULE_LIBS} \ - ${GTK_LIBS} + ${LIBINTL} \ + ${GLIB_LIBS} -desktop_DATA = audacious.desktop -desktopdir = ${datarootdir}/applications diff --git a/src/audacious/adder.c b/src/audacious/adder.c deleted file mode 100644 index 509f363..0000000 --- a/src/audacious/adder.c +++ /dev/null @@ -1,573 +0,0 @@ -/* - * adder.c - * Copyright 2011-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <pthread.h> -#include <string.h> -#include <sys/stat.h> - -#include <glib/gstdio.h> -#include <gtk/gtk.h> - -#include <libaudcore/audstrings.h> -#include <libaudcore/hook.h> - -#include "drct.h" -#include "i18n.h" -#include "playlist.h" -#include "plugins.h" -#include "main.h" -#include "misc.h" -#include "util.h" - -typedef struct { - int playlist_id, at; - bool_t play; - Index * filenames, * tuples; - PlaylistFilterFunc filter; - void * user; -} AddTask; - -typedef struct { - int playlist_id, at; - bool_t play; - char * title; - Index * filenames, * tuples, * decoders; -} AddResult; - -static GList * add_tasks = NULL; -static GList * add_results = NULL; -static int current_playlist_id = -1; - -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; -static bool_t add_quit; -static pthread_t add_thread; -static int add_source = 0; - -static int status_source = 0; -static char status_path[512]; -static int status_count; -static GtkWidget * status_window = NULL, * status_path_label, - * status_count_label; - -static bool_t status_cb (void * unused) -{ - if (! headless_mode () && ! status_window) - { - status_window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_window_set_type_hint ((GtkWindow *) status_window, - GDK_WINDOW_TYPE_HINT_DIALOG); - gtk_window_set_title ((GtkWindow *) status_window, _("Searching ...")); - gtk_window_set_resizable ((GtkWindow *) status_window, FALSE); - gtk_container_set_border_width ((GtkContainer *) status_window, 6); - - GtkWidget * vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); - gtk_container_add ((GtkContainer *) status_window, vbox); - - status_path_label = gtk_label_new (NULL); - gtk_label_set_width_chars ((GtkLabel *) status_path_label, 40); - gtk_label_set_max_width_chars ((GtkLabel *) status_path_label, 40); - gtk_label_set_ellipsize ((GtkLabel *) status_path_label, - PANGO_ELLIPSIZE_MIDDLE); - gtk_box_pack_start ((GtkBox *) vbox, status_path_label, FALSE, FALSE, 0); - - status_count_label = gtk_label_new (NULL); - gtk_label_set_width_chars ((GtkLabel *) status_count_label, 40); - gtk_label_set_max_width_chars ((GtkLabel *) status_count_label, 40); - gtk_box_pack_start ((GtkBox *) vbox, status_count_label, FALSE, FALSE, 0); - - gtk_widget_show_all (status_window); - - g_signal_connect (status_window, "destroy", (GCallback) - gtk_widget_destroyed, & status_window); - } - - pthread_mutex_lock (& mutex); - - char scratch[128]; - snprintf (scratch, sizeof scratch, dngettext (PACKAGE, "%d file found", - "%d files found", status_count), status_count); - - if (headless_mode ()) - { - printf ("Searching, %s ...\r", scratch); - fflush (stdout); - } - else - { - gtk_label_set_text ((GtkLabel *) status_path_label, status_path); - gtk_label_set_text ((GtkLabel *) status_count_label, scratch); - } - - pthread_mutex_unlock (& mutex); - return TRUE; -} - -static void status_update (const char * filename, int found) -{ - pthread_mutex_lock (& mutex); - - snprintf (status_path, sizeof status_path, "%s", filename); - status_count = found; - - if (! status_source) - status_source = g_timeout_add (250, status_cb, NULL); - - pthread_mutex_unlock (& mutex); -} - -static void status_done_locked (void) -{ - if (status_source) - { - g_source_remove (status_source); - status_source = 0; - } - - if (headless_mode ()) - printf ("\n"); - else if (status_window) - gtk_widget_destroy (status_window); -} - -static AddTask * add_task_new (int playlist_id, int at, bool_t play, - Index * filenames, Index * tuples, PlaylistFilterFunc filter, - void * user) -{ - AddTask * task = g_slice_new (AddTask); - task->playlist_id = playlist_id; - task->at = at; - task->play = play; - task->filenames = filenames; - task->tuples = tuples; - task->filter = filter; - task->user = user; - return task; -} - -static void add_task_free (AddTask * task) -{ - if (task->filenames) - index_free_full (task->filenames, (IndexFreeFunc) str_unref); - if (task->tuples) - index_free_full (task->tuples, (IndexFreeFunc) tuple_unref); - - g_slice_free (AddTask, task); -} - -static AddResult * add_result_new (int playlist_id, int at, bool_t play) -{ - AddResult * result = g_slice_new (AddResult); - result->playlist_id = playlist_id; - result->at = at; - result->play = play; - result->title = NULL; - result->filenames = index_new (); - result->tuples = index_new (); - result->decoders = index_new (); - return result; -} - -static void add_result_free (AddResult * result) -{ - str_unref (result->title); - - if (result->filenames) - index_free_full (result->filenames, (IndexFreeFunc) str_unref); - if (result->tuples) - index_free_full (result->tuples, (IndexFreeFunc) tuple_unref); - if (result->decoders) - index_free (result->decoders); - - g_slice_free (AddResult, result); -} - -static void add_file (char * filename, Tuple * tuple, PluginHandle * decoder, - PlaylistFilterFunc filter, void * user, AddResult * result, bool_t validate) -{ - g_return_if_fail (filename); - if (filter && ! filter (filename, user)) - { - str_unref (filename); - return; - } - - status_update (filename, index_count (result->filenames)); - - if (! tuple && ! decoder) - { - decoder = file_find_decoder (filename, TRUE); - if (validate && ! decoder) - { - str_unref (filename); - return; - } - } - - if (! tuple && decoder && input_plugin_has_subtunes (decoder) && ! strchr - (filename, '?')) - tuple = file_read_tuple (filename, decoder); - - int n_subtunes = tuple ? tuple_get_n_subtunes (tuple) : 0; - - if (n_subtunes) - { - for (int sub = 0; sub < n_subtunes; sub ++) - { - char * subname = str_printf ("%s?%d", filename, - tuple_get_nth_subtune (tuple, sub)); - add_file (subname, NULL, decoder, filter, user, result, FALSE); - } - - str_unref (filename); - tuple_unref (tuple); - return; - } - - index_insert (result->filenames, -1, filename); - index_insert (result->tuples, -1, tuple); - index_insert (result->decoders, -1, decoder); -} - -static void add_folder (char * filename, PlaylistFilterFunc filter, - void * user, AddResult * result, bool_t is_single) -{ - char * path = NULL; - - g_return_if_fail (filename); - - if (filter && ! filter (filename, user)) - goto DONE; - - status_update (filename, index_count (result->filenames)); - - if (! (path = uri_to_filename (filename))) - goto DONE; - - GList * files = NULL; - GDir * folder = g_dir_open (path, 0, NULL); - if (! folder) - goto DONE; - - const char * name; - while ((name = g_dir_read_name (folder))) - { - char * filepath = filename_build (path, name); - files = g_list_prepend (files, filepath); - } - - g_dir_close (folder); - - if (files && is_single) - { - char * last = last_path_element (path); - result->title = str_get (last ? last : path); - } - - files = g_list_sort (files, (GCompareFunc) str_compare); - - while (files) - { - GStatBuf info; - if (g_lstat (files->data, & info) < 0) - goto NEXT; - - if (S_ISREG (info.st_mode)) - { - char * item_name = filename_to_uri (files->data); - if (item_name) - add_file (item_name, NULL, NULL, filter, user, result, TRUE); - } - else if (S_ISDIR (info.st_mode)) - { - char * item_name = filename_to_uri (files->data); - if (item_name) - add_folder (item_name, filter, user, result, FALSE); - } - - NEXT: - str_unref (files->data); - files = g_list_delete_link (files, files); - } - -DONE: - str_unref (filename); - str_unref (path); -} - -static void add_playlist (char * filename, PlaylistFilterFunc filter, - void * user, AddResult * result, bool_t is_single) -{ - g_return_if_fail (filename); - if (filter && ! filter (filename, user)) - { - str_unref (filename); - return; - } - - status_update (filename, index_count (result->filenames)); - - char * title = NULL; - Index * filenames, * tuples; - if (! playlist_load (filename, & title, & filenames, & tuples)) - { - str_unref (filename); - return; - } - - if (is_single) - result->title = title; - else - str_unref (title); - - int count = index_count (filenames); - for (int i = 0; i < count; i ++) - add_file (index_get (filenames, i), tuples ? index_get (tuples, i) : - NULL, NULL, filter, user, result, FALSE); - - str_unref (filename); - index_free (filenames); - if (tuples) - index_free (tuples); -} - -static void add_generic (char * filename, Tuple * tuple, - PlaylistFilterFunc filter, void * user, AddResult * result, bool_t is_single) -{ - g_return_if_fail (filename); - - if (tuple) - add_file (filename, tuple, NULL, filter, user, result, FALSE); - else if (vfs_file_test (filename, G_FILE_TEST_IS_DIR)) - add_folder (filename, filter, user, result, is_single); - else if (filename_is_playlist (filename)) - add_playlist (filename, filter, user, result, is_single); - else - add_file (filename, NULL, NULL, filter, user, result, FALSE); -} - -static bool_t add_finish (void * unused) -{ - pthread_mutex_lock (& mutex); - - while (add_results) - { - AddResult * result = add_results->data; - add_results = g_list_delete_link (add_results, add_results); - - int playlist = playlist_by_unique_id (result->playlist_id); - if (playlist < 0) /* playlist deleted */ - goto FREE; - - int count = playlist_entry_count (playlist); - if (result->at < 0 || result->at > count) - result->at = count; - - if (result->title && ! count) - { - char * old_title = playlist_get_title (playlist); - - if (! strcmp (old_title, N_("New Playlist"))) - playlist_set_title (playlist, result->title); - - str_unref (old_title); - } - - playlist_entry_insert_batch_raw (playlist, result->at, - result->filenames, result->tuples, result->decoders); - result->filenames = NULL; - result->tuples = NULL; - result->decoders = NULL; - - if (result->play && playlist_entry_count (playlist) > count) - { - if (! get_bool (NULL, "shuffle")) - playlist_set_position (playlist, result->at); - - drct_play_playlist (playlist); - } - - FREE: - add_result_free (result); - } - - if (add_source) - { - g_source_remove (add_source); - add_source = 0; - } - - if (! add_tasks) - status_done_locked (); - - pthread_mutex_unlock (& mutex); - - hook_call ("playlist add complete", NULL); - return FALSE; -} - -static void * add_worker (void * unused) -{ - pthread_mutex_lock (& mutex); - - while (! add_quit) - { - if (! add_tasks) - { - pthread_cond_wait (& cond, & mutex); - continue; - } - - AddTask * task = add_tasks->data; - add_tasks = g_list_delete_link (add_tasks, add_tasks); - - current_playlist_id = task->playlist_id; - pthread_mutex_unlock (& mutex); - - AddResult * result = add_result_new (task->playlist_id, task->at, - task->play); - - int count = index_count (task->filenames); - if (task->tuples) - count = MIN (count, index_count (task->tuples)); - - for (int i = 0; i < count; i ++) - { - add_generic (index_get (task->filenames, i), task->tuples ? - index_get (task->tuples, i) : NULL, task->filter, task->user, - result, (count == 1)); - - index_set (task->filenames, i, NULL); - if (task->tuples) - index_set (task->tuples, i, NULL); - } - - add_task_free (task); - - pthread_mutex_lock (& mutex); - current_playlist_id = -1; - - add_results = g_list_append (add_results, result); - - if (! add_source) - add_source = g_timeout_add (0, add_finish, NULL); - } - - pthread_mutex_unlock (& mutex); - return NULL; -} - -void adder_init (void) -{ - pthread_mutex_lock (& mutex); - add_quit = FALSE; - pthread_create (& add_thread, NULL, add_worker, NULL); - pthread_mutex_unlock (& mutex); -} - -void adder_cleanup (void) -{ - pthread_mutex_lock (& mutex); - add_quit = TRUE; - pthread_cond_broadcast (& cond); - pthread_mutex_unlock (& mutex); - pthread_join (add_thread, NULL); - - g_list_free_full (add_tasks, (GDestroyNotify) add_task_free); - add_tasks = NULL; - g_list_free_full (add_results, (GDestroyNotify) add_result_free); - add_results = NULL; - - if (add_source) - { - g_source_remove (add_source); - add_source = 0; - } - - status_done_locked (); -} - -void playlist_entry_insert (int playlist, int at, const char * filename, - Tuple * tuple, bool_t play) -{ - Index * filenames = index_new (); - Index * tuples = index_new (); - index_insert (filenames, -1, str_get (filename)); - index_insert (tuples, -1, tuple); - - playlist_entry_insert_batch (playlist, at, filenames, tuples, play); -} - -void playlist_entry_insert_batch (int playlist, int at, - Index * filenames, Index * tuples, bool_t play) -{ - playlist_entry_insert_filtered (playlist, at, filenames, tuples, NULL, NULL, play); -} - -void playlist_entry_insert_filtered (int playlist, int at, - Index * filenames, Index * tuples, PlaylistFilterFunc filter, - void * user, bool_t play) -{ - int playlist_id = playlist_get_unique_id (playlist); - g_return_if_fail (playlist_id >= 0); - - AddTask * task = add_task_new (playlist_id, at, play, filenames, tuples, filter, user); - - pthread_mutex_lock (& mutex); - add_tasks = g_list_append (add_tasks, task); - pthread_cond_broadcast (& cond); - pthread_mutex_unlock (& mutex); -} - -bool_t playlist_add_in_progress (int playlist) -{ - pthread_mutex_lock (& mutex); - - if (playlist >= 0) - { - int playlist_id = playlist_get_unique_id (playlist); - - for (GList * node = add_tasks; node; node = node->next) - { - if (((AddTask *) node->data)->playlist_id == playlist_id) - goto YES; - } - - if (current_playlist_id == playlist_id) - goto YES; - - for (GList * node = add_results; node; node = node->next) - { - if (((AddResult *) node->data)->playlist_id == playlist_id) - goto YES; - } - } - else - { - if (add_tasks || current_playlist_id >= 0 || add_results) - goto YES; - } - - pthread_mutex_unlock (& mutex); - return FALSE; - -YES: - pthread_mutex_unlock (& mutex); - return TRUE; -} diff --git a/src/audacious/api-alias-begin.h b/src/audacious/api-alias-begin.h deleted file mode 100644 index 457d29d..0000000 --- a/src/audacious/api-alias-begin.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * api-alias-begin.h - * Copyright 2010-2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#if ! defined AUD_API_NAME || ! defined AUD_API_SYMBOL || defined AUD_API_ALIAS -#error Bad usage of api-alias-begin.h -#endif - -#define AUD_API_ALIAS - -extern AudAPITable * _aud_api_table; - -#define AUD_FUNC0(t,n) static inline t aud_##n(void) {return _aud_api_table->AUD_API_SYMBOL->n();} -#define AUD_FUNC1(t,n,t1,n1) static inline t aud_##n(t1 n1) {return _aud_api_table->AUD_API_SYMBOL->n(n1);} -#define AUD_FUNC2(t,n,t1,n1,t2,n2) static inline t aud_##n(t1 n1, t2 n2) {return _aud_api_table->AUD_API_SYMBOL->n(n1,n2);} -#define AUD_FUNC3(t,n,t1,n1,t2,n2,t3,n3) static inline t aud_##n(t1 n1, t2 n2, t3 n3) {return _aud_api_table->AUD_API_SYMBOL->n(n1,n2,n3);} -#define AUD_FUNC4(t,n,t1,n1,t2,n2,t3,n3,t4,n4) static inline t aud_##n(t1 n1, t2 n2, t3 n3, t4 n4) {return _aud_api_table->AUD_API_SYMBOL->n(n1,n2,n3,n4);} -#define AUD_FUNC5(t,n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5) static inline t aud_##n(t1 n1, t2 n2, t3 n3, t4 n4, t5 n5) {return _aud_api_table->AUD_API_SYMBOL->n(n1,n2,n3,n4,n5);} -#define AUD_FUNC6(t,n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6) static inline t aud_##n(t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6) {return _aud_api_table->AUD_API_SYMBOL->n(n1,n2,n3,n4,n5,n6);} -#define AUD_FUNC7(t,n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6,t7,n7) static inline t aud_##n(t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6, t7 n7) {return _aud_api_table->AUD_API_SYMBOL->n(n1,n2,n3,n4,n5,n6,n7);} -#define AUD_FUNC8(t,n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6,t7,n7,t8,n8) static inline t aud_##n(t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6, t7 n7, t8 n8) {return _aud_api_table->AUD_API_SYMBOL->n(n1,n2,n3,n4,n5,n6,n7,n8);} - -#define AUD_VFUNC0(n) static inline void aud_##n(void) {_aud_api_table->AUD_API_SYMBOL->n();} -#define AUD_VFUNC1(n,t1,n1) static inline void aud_##n(t1 n1) {_aud_api_table->AUD_API_SYMBOL->n(n1);} -#define AUD_VFUNC2(n,t1,n1,t2,n2) static inline void aud_##n(t1 n1, t2 n2) {_aud_api_table->AUD_API_SYMBOL->n(n1,n2);} -#define AUD_VFUNC3(n,t1,n1,t2,n2,t3,n3) static inline void aud_##n(t1 n1, t2 n2, t3 n3) {_aud_api_table->AUD_API_SYMBOL->n(n1,n2,n3);} -#define AUD_VFUNC4(n,t1,n1,t2,n2,t3,n3,t4,n4) static inline void aud_##n(t1 n1, t2 n2, t3 n3, t4 n4) {_aud_api_table->AUD_API_SYMBOL->n(n1,n2,n3,n4);} -#define AUD_VFUNC5(n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5) static inline void aud_##n(t1 n1, t2 n2, t3 n3, t4 n4, t5 n5) {_aud_api_table->AUD_API_SYMBOL->n(n1,n2,n3,n4,n5);} -#define AUD_VFUNC6(n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6) static inline void aud_##n(t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6) {_aud_api_table->AUD_API_SYMBOL->n(n1,n2,n3,n4,n5,n6);} -#define AUD_VFUNC7(n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6,t7,n7) static inline void aud_##n(t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6, t7 n7) {_aud_api_table->AUD_API_SYMBOL->n(n1,n2,n3,n4,n5,n6,n7);} -#define AUD_VFUNC8(n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6,t7,n7,t8,n8) static inline void aud_##n(t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6, t7 n7, t8 n8) {_aud_api_table->AUD_API_SYMBOL->n(n1,n2,n3,n4,n5,n6,n7,n8);} diff --git a/src/audacious/api-alias-end.h b/src/audacious/api-alias-end.h deleted file mode 100644 index a003cde..0000000 --- a/src/audacious/api-alias-end.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * api-alias-end.h - * Copyright 2010-2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#if ! defined AUD_API_NAME || ! defined AUD_API_SYMBOL || ! defined AUD_API_ALIAS -#error Bad usage of api-alias-end.h -#endif - -#undef AUD_API_ALIAS - -#undef AUD_FUNC0 -#undef AUD_FUNC1 -#undef AUD_FUNC2 -#undef AUD_FUNC3 -#undef AUD_FUNC4 -#undef AUD_FUNC5 -#undef AUD_FUNC6 -#undef AUD_FUNC7 -#undef AUD_FUNC8 - -#undef AUD_VFUNC0 -#undef AUD_VFUNC1 -#undef AUD_VFUNC2 -#undef AUD_VFUNC3 -#undef AUD_VFUNC4 -#undef AUD_VFUNC5 -#undef AUD_VFUNC6 -#undef AUD_VFUNC7 -#undef AUD_VFUNC8 diff --git a/src/audacious/api-declare-begin.h b/src/audacious/api-declare-begin.h deleted file mode 100644 index defc557..0000000 --- a/src/audacious/api-declare-begin.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * api-declare-begin.h - * Copyright 2010-2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#if ! defined AUD_API_NAME || ! defined AUD_API_SYMBOL || defined AUD_API_DECLARE_H -#error Bad usage of api-declare-begin.h -#endif - -#define AUD_API_DECLARE_H - -#define AUD_FUNC0(t,n) .n = n, -#define AUD_FUNC1(t,n,t1,n1) .n = n, -#define AUD_FUNC2(t,n,t1,n1,t2,n2) .n = n, -#define AUD_FUNC3(t,n,t1,n1,t2,n2,t3,n3) .n = n, -#define AUD_FUNC4(t,n,t1,n1,t2,n2,t3,n3,t4,n4) .n = n, -#define AUD_FUNC5(t,n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5) .n = n, -#define AUD_FUNC6(t,n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6) .n = n, -#define AUD_FUNC7(t,n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6,t7,n7) .n = n, -#define AUD_FUNC8(t,n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6,t7,n7,t8,n8) .n = n, - -#define AUD_VFUNC0(n) .n = n, -#define AUD_VFUNC1(n,t1,n1) .n = n, -#define AUD_VFUNC2(n,t1,n1,t2,n2) .n = n, -#define AUD_VFUNC3(n,t1,n1,t2,n2,t3,n3) .n = n, -#define AUD_VFUNC4(n,t1,n1,t2,n2,t3,n3,t4,n4) .n = n, -#define AUD_VFUNC5(n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5) .n = n, -#define AUD_VFUNC6(n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6) .n = n, -#define AUD_VFUNC7(n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6,t7,n7) .n = n, -#define AUD_VFUNC8(n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6,t7,n7,t8,n8) .n = n, - -const struct AUD_API_NAME AUD_API_SYMBOL = { diff --git a/src/audacious/api-define-begin.h b/src/audacious/api-define-begin.h deleted file mode 100644 index 5fe32cd..0000000 --- a/src/audacious/api-define-begin.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * api-define-begin.h - * Copyright 2010-2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#if ! defined AUD_API_NAME || ! defined AUD_API_SYMBOL || defined AUD_API_DEFINE_H -#error Bad usage of api-define-begin.h -#endif - -#define AUD_API_DEFINE_H - -#define AUD_FUNC0(t,n) t (* n) (void); -#define AUD_FUNC1(t,n,t1,n1) t (* n) (t1 n1); -#define AUD_FUNC2(t,n,t1,n1,t2,n2) t (* n) (t1 n1, t2 n2); -#define AUD_FUNC3(t,n,t1,n1,t2,n2,t3,n3) t (* n) (t1 n1, t2 n2, t3 n3); -#define AUD_FUNC4(t,n,t1,n1,t2,n2,t3,n3,t4,n4) t (* n) (t1 n1, t2 n2, t3 n3, t4 n4); -#define AUD_FUNC5(t,n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5) t (* n) (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5); -#define AUD_FUNC6(t,n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6) t (* n) (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6); -#define AUD_FUNC7(t,n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6,t7,n7) t (* n) (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6, t7 n7); -#define AUD_FUNC8(t,n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6,t7,n7,t8,n8) t (* n) (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6, t7 n7, t8 n8); - -#define AUD_VFUNC0(n) void (* n) (void); -#define AUD_VFUNC1(n,t1,n1) void (* n) (t1 n1); -#define AUD_VFUNC2(n,t1,n1,t2,n2) void (* n) (t1 n1, t2 n2); -#define AUD_VFUNC3(n,t1,n1,t2,n2,t3,n3) void (* n) (t1 n1, t2 n2, t3 n3); -#define AUD_VFUNC4(n,t1,n1,t2,n2,t3,n3,t4,n4) void (* n) (t1 n1, t2 n2, t3 n3, t4 n4); -#define AUD_VFUNC5(n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5) void (* n) (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5); -#define AUD_VFUNC6(n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6) void (* n) (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6); -#define AUD_VFUNC7(n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6,t7,n7) void (* n) (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6, t7 n7); -#define AUD_VFUNC8(n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6,t7,n7,t8,n8) void (* n) (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6, t7 n7, t8 n8); - -struct AUD_API_NAME { diff --git a/src/audacious/api-define-end.h b/src/audacious/api-define-end.h deleted file mode 100644 index 91bd85f..0000000 --- a/src/audacious/api-define-end.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * api-define-end.h - * Copyright 2010-2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#if ! defined AUD_API_NAME || ! defined AUD_API_SYMBOL || ! defined AUD_API_DEFINE_H -#error Bad usage of api-define-end.h -#endif - -}; - -#undef AUD_API_DEFINE_H - -#undef AUD_FUNC0 -#undef AUD_FUNC1 -#undef AUD_FUNC2 -#undef AUD_FUNC3 -#undef AUD_FUNC4 -#undef AUD_FUNC5 -#undef AUD_FUNC6 -#undef AUD_FUNC7 -#undef AUD_FUNC8 - -#undef AUD_VFUNC0 -#undef AUD_VFUNC1 -#undef AUD_VFUNC2 -#undef AUD_VFUNC3 -#undef AUD_VFUNC4 -#undef AUD_VFUNC5 -#undef AUD_VFUNC6 -#undef AUD_VFUNC7 -#undef AUD_VFUNC8 diff --git a/src/audacious/api-local-begin.h b/src/audacious/api-local-begin.h deleted file mode 100644 index b32eeb1..0000000 --- a/src/audacious/api-local-begin.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * api-local-begin.h - * Copyright 2010-2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#if ! defined AUD_API_NAME || ! defined AUD_API_SYMBOL || defined AUD_API_LOCAL_H -#error Bad usage of api-local-begin.h -#endif - -#define AUD_API_LOCAL_H - -#define AUD_FUNC0(t,n) t n (void); -#define AUD_FUNC1(t,n,t1,n1) t n (t1 n1); -#define AUD_FUNC2(t,n,t1,n1,t2,n2) t n (t1 n1, t2 n2); -#define AUD_FUNC3(t,n,t1,n1,t2,n2,t3,n3) t n (t1 n1, t2 n2, t3 n3); -#define AUD_FUNC4(t,n,t1,n1,t2,n2,t3,n3,t4,n4) t n (t1 n1, t2 n2, t3 n3, t4 n4); -#define AUD_FUNC5(t,n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5) t n (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5); -#define AUD_FUNC6(t,n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6) t n (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6); -#define AUD_FUNC7(t,n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6,t7,n7) t n (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6, t7 n7); -#define AUD_FUNC8(t,n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6,t7,n7,t8,n8) t n (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6, t7 n7, t8 n8); - -#define AUD_VFUNC0(n) void n (void); -#define AUD_VFUNC1(n,t1,n1) void n (t1 n1); -#define AUD_VFUNC2(n,t1,n1,t2,n2) void n (t1 n1, t2 n2); -#define AUD_VFUNC3(n,t1,n1,t2,n2,t3,n3) void n (t1 n1, t2 n2, t3 n3); -#define AUD_VFUNC4(n,t1,n1,t2,n2,t3,n3,t4,n4) void n (t1 n1, t2 n2, t3 n3, t4 n4); -#define AUD_VFUNC5(n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5) void n (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5); -#define AUD_VFUNC6(n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6) void n (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6); -#define AUD_VFUNC7(n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6,t7,n7) void n (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6, t7 n7); -#define AUD_VFUNC8(n,t1,n1,t2,n2,t3,n3,t4,n4,t5,n5,t6,n6,t7,n7,t8,n8) void n (t1 n1, t2 n2, t3 n3, t4 n4, t5 n5, t6 n6, t7 n7, t8 n8); diff --git a/src/audacious/api-local-end.h b/src/audacious/api-local-end.h deleted file mode 100644 index 81ec43d..0000000 --- a/src/audacious/api-local-end.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * api-local-end.h - * Copyright 2010-2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#if ! defined AUD_API_NAME || ! defined AUD_API_SYMBOL || ! defined AUD_API_LOCAL_H -#error Bad usage of api-local-end.h -#endif - -#undef AUD_API_LOCAL_H - -#undef AUD_FUNC0 -#undef AUD_FUNC1 -#undef AUD_FUNC2 -#undef AUD_FUNC3 -#undef AUD_FUNC4 -#undef AUD_FUNC5 -#undef AUD_FUNC6 -#undef AUD_FUNC7 -#undef AUD_FUNC8 - -#undef AUD_VFUNC0 -#undef AUD_VFUNC1 -#undef AUD_VFUNC2 -#undef AUD_VFUNC3 -#undef AUD_VFUNC4 -#undef AUD_VFUNC5 -#undef AUD_VFUNC6 -#undef AUD_VFUNC7 -#undef AUD_VFUNC8 diff --git a/src/audacious/api.h b/src/audacious/api.h deleted file mode 100644 index f68987a..0000000 --- a/src/audacious/api.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * api.h - * Copyright 2010-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_API_H -#define AUDACIOUS_API_H - -/* API version. Plugins are marked with this number at compile time. - * - * _AUD_PLUGIN_VERSION is the current version; _AUD_PLUGIN_VERSION_MIN is - * the oldest one we are backward compatible with. Plugins marked older than - * _AUD_PLUGIN_VERSION_MIN or newer than _AUD_PLUGIN_VERSION are not loaded. - * - * Before releases that add new pointers to the end of the API tables, increment - * _AUD_PLUGIN_VERSION but leave _AUD_PLUGIN_VERSION_MIN the same. - * - * Before releases that break backward compatibility (e.g. remove pointers from - * the API tables), increment _AUD_PLUGIN_VERSION *and* set - * _AUD_PLUGIN_VERSION_MIN to the same value. */ - -#define _AUD_PLUGIN_VERSION_MIN 45 /* 3.5-devel */ -#define _AUD_PLUGIN_VERSION 45 /* 3.5-devel */ - -typedef const struct { - const struct ConfigDBAPI * configdb_api; - const struct DRCTAPI * drct_api; - const struct InputAPI * input_api; - const struct MiscAPI * misc_api; - const struct PlaylistAPI * playlist_api; - const struct PluginsAPI * plugins_api; - char * verbose; -} AudAPITable; - -#ifdef _AUDACIOUS_CORE -extern char verbose; -#else -extern AudAPITable * _aud_api_table; -#endif - -#endif diff --git a/src/audacious/art.c b/src/audacious/art.c deleted file mode 100644 index 24658a0..0000000 --- a/src/audacious/art.c +++ /dev/null @@ -1,284 +0,0 @@ -/* - * art.c - * Copyright 2011-2012 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <assert.h> -#include <errno.h> -#include <pthread.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include <glib.h> -#include <glib/gstdio.h> - -#include <libaudcore/audstrings.h> -#include <libaudcore/hook.h> - -#include "main.h" -#include "misc.h" -#include "playlist.h" -#include "scanner.h" -#include "util.h" - -#define FLAG_DONE 1 -#define FLAG_SENT 2 - -typedef struct { - int refcount; - int flag; - - /* album art as JPEG or PNG data */ - void * data; - int64_t len; - - /* album art as (possibly a temporary) file */ - char * art_file; /* pooled */ - bool_t is_temp; -} ArtItem; - -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - -static GHashTable * art_items; /* of ArtItem */ -static char * current_ref; /* pooled */ -static int send_source; - -static void art_item_free (ArtItem * item) -{ - /* delete temporary file */ - if (item->art_file && item->is_temp) - { - char * unixname = uri_to_filename (item->art_file); - if (unixname) - { - g_unlink (unixname); - str_unref (unixname); - } - } - - g_free (item->data); - str_unref (item->art_file); - g_slice_free (ArtItem, item); -} - -static bool_t send_requests (void * unused) -{ - pthread_mutex_lock (& mutex); - - GQueue queue = G_QUEUE_INIT; - - GHashTableIter iter; - void * ptr1, * ptr2; - - g_hash_table_iter_init (& iter, art_items); - while (g_hash_table_iter_next (& iter, & ptr1, & ptr2)) - { - char * file = ptr1; - ArtItem * item = ptr2; - - if (item->flag == FLAG_DONE) - { - g_queue_push_tail (& queue, str_ref (file)); - item->flag = FLAG_SENT; - } - } - - if (send_source) - { - g_source_remove (send_source); - send_source = 0; - } - - pthread_mutex_unlock (& mutex); - - char * current = NULL; - if (! current_ref) - current = playback_entry_get_filename (); - - char * file; - while ((file = g_queue_pop_head (& queue))) - { - hook_call ("art ready", file); - - if (current && ! strcmp (file, current)) - { - hook_call ("current art ready", file); - current_ref = file; - } - else - { - art_unref (file); /* release temporary reference */ - str_unref (file); - } - } - - str_unref (current); - return FALSE; -} - -static void request_callback (ScanRequest * request) -{ - pthread_mutex_lock (& mutex); - - const char * file = scan_request_get_filename (request); - ArtItem * item = g_hash_table_lookup (art_items, file); - assert (item != NULL && ! item->flag); - - scan_request_get_image_data (request, & item->data, & item->len); - item->art_file = str_get (scan_request_get_image_file (request)); - item->flag = FLAG_DONE; - - if (! send_source) - send_source = g_idle_add (send_requests, NULL); - - pthread_mutex_unlock (& mutex); -} - -static ArtItem * art_item_get (const char * file) -{ - ArtItem * item = g_hash_table_lookup (art_items, file); - - if (item && item->flag) - { - item->refcount ++; - return item; - } - - if (! item) - { - item = g_slice_new0 (ArtItem); - g_hash_table_insert (art_items, str_get (file), item); - item->refcount = 1; /* temporary reference */ - - scan_request (file, SCAN_IMAGE, NULL, request_callback); - } - - return NULL; -} - -static void art_item_unref (const char * file, ArtItem * item) -{ - if (! -- item->refcount) - g_hash_table_remove (art_items, file); -} - -static void release_current (void) -{ - if (current_ref) - { - art_unref (current_ref); - str_unref (current_ref); - current_ref = NULL; - } -} - -void art_init (void) -{ - art_items = g_hash_table_new_full (g_str_hash, g_str_equal, - (GDestroyNotify) str_unref, (GDestroyNotify) art_item_free); - - hook_associate ("playlist position", (HookFunction) release_current, NULL); - hook_associate ("playlist set playing", (HookFunction) release_current, NULL); -} - -void art_cleanup (void) -{ - hook_dissociate ("playlist position", (HookFunction) release_current); - hook_dissociate ("playlist set playing", (HookFunction) release_current); - - if (send_source) - { - g_source_remove (send_source); - send_source = 0; - } - - release_current (); - - g_hash_table_destroy (art_items); - art_items = NULL; -} - -void art_request_data (const char * file, const void * * data, int64_t * len) -{ - * data = NULL; - * len = 0; - - pthread_mutex_lock (& mutex); - - ArtItem * item = art_item_get (file); - if (! item) - goto UNLOCK; - - /* load data from external image file */ - if (! item->data && item->art_file) - vfs_file_get_contents (item->art_file, & item->data, & item->len); - - if (item->data) - { - * data = item->data; - * len = item->len; - } - else - art_item_unref (file, item); - -UNLOCK: - pthread_mutex_unlock (& mutex); -} - -const char * art_request_file (const char * file) -{ - const char * art_file = NULL; - pthread_mutex_lock (& mutex); - - ArtItem * item = art_item_get (file); - if (! item) - goto UNLOCK; - - /* save data to temporary file */ - if (item->data && ! item->art_file) - { - char * unixname = write_temp_file (item->data, item->len); - if (unixname) - { - item->art_file = filename_to_uri (unixname); - item->is_temp = TRUE; - str_unref (unixname); - } - } - - if (item->art_file) - art_file = item->art_file; - else - art_item_unref (file, item); - -UNLOCK: - pthread_mutex_unlock (& mutex); - return art_file; -} - -void art_unref (const char * file) -{ - pthread_mutex_lock (& mutex); - - ArtItem * item = g_hash_table_lookup (art_items, file); - assert (item != NULL); - - art_item_unref (file, item); - - pthread_mutex_unlock (& mutex); -} diff --git a/src/audacious/audacious.rc b/src/audacious/audacious.rc index b37beeb..c7b9839 100644 --- a/src/audacious/audacious.rc +++ b/src/audacious/audacious.rc @@ -4,4 +4,4 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL #define IDI_ICON_AUDACIOUS 102 -IDI_ICON_AUDACIOUS ICON "../../pixmaps/audacious.ico" +IDI_ICON_AUDACIOUS ICON "../../images/audacious.ico" diff --git a/src/audacious/chardet.c b/src/audacious/chardet.c deleted file mode 100644 index 08fe97e..0000000 --- a/src/audacious/chardet.c +++ /dev/null @@ -1,52 +0,0 @@ -/* - * chardet.c - * Copyright 2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <libaudcore/audstrings.h> -#include <libaudcore/hook.h> - -#include "main.h" -#include "misc.h" - -static void chardet_update (void) -{ - char * region = get_str (NULL, "chardet_detector"); - char * fallbacks = get_str (NULL, "chardet_fallback"); - - Index * list = str_list_to_index (fallbacks, ", "); - str_set_charsets (region[0] ? region : NULL, list); - - str_unref (region); - str_unref (fallbacks); -} - -void chardet_init (void) -{ - chardet_update (); - - hook_associate ("set chardet_detector", (HookFunction) chardet_update, NULL); - hook_associate ("set chardet_fallback", (HookFunction) chardet_update, NULL); -} - -void chardet_cleanup (void) -{ - hook_dissociate ("set chardet_detector", (HookFunction) chardet_update); - hook_dissociate ("set chardet_fallback", (HookFunction) chardet_update); - - str_set_charsets (NULL, NULL); -} diff --git a/src/audacious/config.c b/src/audacious/config.c deleted file mode 100644 index 0bc4966..0000000 --- a/src/audacious/config.c +++ /dev/null @@ -1,453 +0,0 @@ -/* - * config.c - * Copyright 2011-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <string.h> - -#include <libaudcore/audstrings.h> -#include <libaudcore/hook.h> -#include <libaudcore/inifile.h> -#include <libaudcore/multihash.h> - -#include "main.h" -#include "misc.h" - -#define DEFAULT_SECTION "audacious" - -static const char * const core_defaults[] = { - - /* general */ - "advance_on_delete", "FALSE", - "clear_playlist", "TRUE", - "open_to_temporary", "TRUE", - "resume_playback_on_startup", "FALSE", - "show_interface", "TRUE", - - /* equalizer */ - "eqpreset_default_file", "", - "eqpreset_extension", "", - "equalizer_active", "FALSE", - "equalizer_autoload", "FALSE", - "equalizer_bands", "0,0,0,0,0,0,0,0,0,0", - "equalizer_preamp", "0", - - /* info popup / info window */ - "cover_name_exclude", "back", - "cover_name_include", "album,cover,front,folder", - "filepopup_delay", "5", - "filepopup_showprogressbar", "TRUE", - "recurse_for_cover", "FALSE", - "recurse_for_cover_depth", "0", - "show_filepopup_for_tuple", "TRUE", - "use_file_cover", "FALSE", - - /* network */ - "use_proxy", "FALSE", - "use_proxy_auth", "FALSE", - - /* output */ - "default_gain", "0", - "enable_replay_gain", "TRUE", - "enable_clipping_prevention", "TRUE", - "output_bit_depth", "16", - "output_buffer_size", "500", - "replay_gain_album", "FALSE", - "replay_gain_preamp", "0", - "soft_clipping", "FALSE", - "software_volume_control", "FALSE", - "sw_volume_left", "100", - "sw_volume_right", "100", - - /* playback */ - "no_playlist_advance", "FALSE", - "repeat", "FALSE", - "shuffle", "FALSE", - "stop_after_current_song", "FALSE", - - /* playlist */ - "chardet_fallback", "ISO-8859-1", -#ifdef _WIN32 - "convert_backslash", "TRUE", -#else - "convert_backslash", "FALSE", -#endif - "generic_title_format", "${?artist:${artist} - }${?album:${album} - }${title}", - "leading_zero", "FALSE", - "metadata_on_play", "FALSE", - "show_numbers_in_pl", "FALSE", - - NULL}; - -typedef enum { - OP_IS_DEFAULT, - OP_GET, - OP_SET, - OP_SET_NO_FLAG, - OP_CLEAR, - OP_CLEAR_NO_FLAG -} OpType; - -typedef struct { - const char * section; - const char * key; - const char * value; -} ConfigItem; - -typedef struct { - MultihashNode node; - ConfigItem item; -} ConfigNode; - -typedef struct { - OpType type; - ConfigItem item; - unsigned hash; - bool_t result; -} ConfigOp; - -typedef struct { - char * section; -} LoadState; - -typedef struct { - GArray * list; -} SaveState; - -static unsigned item_hash (const ConfigItem * item) -{ - return g_str_hash (item->section) + g_str_hash (item->key); -} - -/* assumes pooled strings */ -static int item_compare (const ConfigItem * a, const ConfigItem * b) -{ - if (str_equal (a->section, b->section)) - return strcmp (a->key, b->key); - else - return strcmp (a->section, b->section); -} - -/* assumes pooled strings */ -static void item_clear (ConfigItem * item) -{ - str_unref ((char *) item->section); - str_unref ((char *) item->key); - str_unref ((char *) item->value); -} - -static unsigned config_node_hash (const MultihashNode * node0) -{ - const ConfigNode * node = (const ConfigNode *) node0; - - return item_hash (& node->item); -} - -static bool_t config_node_match (const MultihashNode * node0, const void * data, unsigned hash) -{ - const ConfigNode * node = (const ConfigNode *) node0; - const ConfigItem * item = data; - - return ! strcmp (node->item.section, item->section) && ! strcmp (node->item.key, item->key); -} - -static MultihashTable defaults = { - .hash_func = config_node_hash, - .match_func = config_node_match -}; - -static MultihashTable config = { - .hash_func = config_node_hash, - .match_func = config_node_match -}; - -static volatile bool_t modified; - -static MultihashNode * add_cb (const void * data, unsigned hash, void * state) -{ - ConfigOp * op = state; - - switch (op->type) - { - case OP_IS_DEFAULT: - op->result = ! op->item.value[0]; /* empty string is default */ - return NULL; - - case OP_SET: - op->result = TRUE; - modified = TRUE; - - case OP_SET_NO_FLAG:; - ConfigNode * node = g_slice_new (ConfigNode); - node->item.section = str_get (op->item.section); - node->item.key = str_get (op->item.key); - node->item.value = str_get (op->item.value); - return (MultihashNode *) node; - - default: - return NULL; - } -} - -static bool_t action_cb (MultihashNode * node0, void * state) -{ - ConfigNode * node = (ConfigNode *) node0; - ConfigOp * op = state; - - switch (op->type) - { - case OP_IS_DEFAULT: - op->result = ! strcmp (node->item.value, op->item.value); - return FALSE; - - case OP_GET: - op->item.value = str_ref (node->item.value); - return FALSE; - - case OP_SET: - op->result = !! strcmp (node->item.value, op->item.value); - if (op->result) - modified = TRUE; - - case OP_SET_NO_FLAG: - str_unref ((char *) node->item.value); - node->item.value = str_get (op->item.value); - return FALSE; - - case OP_CLEAR: - op->result = TRUE; - modified = TRUE; - - case OP_CLEAR_NO_FLAG: - item_clear (& node->item); - g_slice_free (ConfigNode, node); - return TRUE; - - default: - return FALSE; - } -} - -static bool_t config_op_run (ConfigOp * op, OpType type, MultihashTable * table) -{ - if (! op->hash) - op->hash = item_hash (& op->item); - - op->type = type; - op->result = FALSE; - multihash_lookup (table, & op->item, op->hash, add_cb, action_cb, op); - return op->result; -} - -static void load_heading (const char * section, void * data) -{ - LoadState * state = data; - - str_unref (state->section); - state->section = str_get (section); -} - -static void load_entry (const char * key, const char * value, void * data) -{ - LoadState * state = data; - g_return_if_fail (state->section); - - ConfigOp op = {.item = {state->section, key, value}}; - config_op_run (& op, OP_SET_NO_FLAG, & config); -} - -void config_load (void) -{ - char * folder = filename_to_uri (get_path (AUD_PATH_USER_DIR)); - SCONCAT2 (path, folder, "/config"); - str_unref (folder); - - if (vfs_file_test (path, VFS_EXISTS)) - { - VFSFile * file = vfs_fopen (path, "r"); - - if (file) - { - LoadState state = {0}; - - inifile_parse (file, load_heading, load_entry, & state); - - str_unref (state.section); - vfs_fclose (file); - } - } - - config_set_defaults (NULL, core_defaults); -} - -static bool_t add_to_save_list (MultihashNode * node0, void * state0) -{ - ConfigNode * node = (ConfigNode *) node0; - SaveState * state = state0; - - int pos = state->list->len; - g_array_set_size (state->list, pos + 1); - - ConfigItem * copy = & g_array_index (state->list, ConfigItem, pos); - copy->section = str_ref (node->item.section); - copy->key = str_ref (node->item.key); - copy->value = str_ref (node->item.value); - - modified = FALSE; - - return FALSE; -} - -void config_save (void) -{ - if (! modified) - return; - - SaveState state = {.list = g_array_new (FALSE, FALSE, sizeof (ConfigItem))}; - - multihash_iterate (& config, add_to_save_list, & state); - g_array_sort (state.list, (GCompareFunc) item_compare); - - char * folder = filename_to_uri (get_path (AUD_PATH_USER_DIR)); - SCONCAT2 (path, folder, "/config"); - str_unref (folder); - - VFSFile * file = vfs_fopen (path, "w"); - - if (file) - { - const char * current_heading = NULL; - - for (int i = 0; i < state.list->len; i ++) - { - ConfigItem * item = & g_array_index (state.list, ConfigItem, i); - - if (! str_equal (item->section, current_heading)) - { - inifile_write_heading (file, item->section); - current_heading = item->section; - } - - inifile_write_entry (file, item->key, item->value); - } - - vfs_fclose (file); - } - - g_array_set_clear_func (state.list, (GDestroyNotify) item_clear); - g_array_free (state.list, TRUE); -} - -void config_set_defaults (const char * section, const char * const * entries) -{ - if (! section) - section = DEFAULT_SECTION; - - while (1) - { - const char * name = * entries ++; - const char * value = * entries ++; - if (! name || ! value) - break; - - ConfigOp op = {.item = {section, name, value}}; - config_op_run (& op, OP_SET_NO_FLAG, & defaults); - } -} - -void config_cleanup (void) -{ - ConfigOp op = {.type = OP_CLEAR_NO_FLAG}; - multihash_iterate (& config, action_cb, & op); - multihash_iterate (& defaults, action_cb, & op); -} - -void set_str (const char * section, const char * name, const char * value) -{ - g_return_if_fail (name && value); - - ConfigOp op = {.item = {section ? section : DEFAULT_SECTION, name, value}}; - - bool_t is_default = config_op_run (& op, OP_IS_DEFAULT, & defaults); - bool_t changed = config_op_run (& op, is_default ? OP_CLEAR : OP_SET, & config); - - if (changed && ! section) - { - SCONCAT2 (event, "set ", name); - event_queue (event, NULL); - } -} - -char * get_str (const char * section, const char * name) -{ - g_return_val_if_fail (name, NULL); - - ConfigOp op = {.item = {section ? section : DEFAULT_SECTION, name, NULL}}; - - config_op_run (& op, OP_GET, & config); - - if (! op.item.value) - config_op_run (& op, OP_GET, & defaults); - - return op.item.value ? (char *) op.item.value : str_get (""); -} - -void set_bool (const char * section, const char * name, bool_t value) -{ - set_str (section, name, value ? "TRUE" : "FALSE"); -} - -bool_t get_bool (const char * section, const char * name) -{ - char * string = get_str (section, name); - bool_t value = ! strcmp (string, "TRUE"); - str_unref (string); - return value; -} - -void set_int (const char * section, const char * name, int value) -{ - char * string = int_to_str (value); - g_return_if_fail (string); - set_str (section, name, string); - str_unref (string); -} - -int get_int (const char * section, const char * name) -{ - char * string = get_str (section, name); - int value = str_to_int (string); - str_unref (string); - return value; -} - -void set_double (const char * section, const char * name, double value) -{ - char * string = double_to_str (value); - g_return_if_fail (string); - set_str (section, name, string); - str_unref (string); -} - -double get_double (const char * section, const char * name) -{ - char * string = get_str (section, name); - double value = str_to_double (string); - str_unref (string); - return value; -} diff --git a/src/audacious/dbus-server.c b/src/audacious/dbus-server.c deleted file mode 100644 index af55f20..0000000 --- a/src/audacious/dbus-server.c +++ /dev/null @@ -1,779 +0,0 @@ -/* - * dbus-server.c - * Copyright 2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <stdio.h> - -#include <libaudgui/libaudgui.h> - -#include "aud-dbus.h" -#include "drct.h" -#include "main.h" -#include "misc.h" -#include "playlist.h" -#include "ui_preferences.h" - -typedef ObjAudacious Obj; -typedef GDBusMethodInvocation Invoc; - -#define FINISH(name) \ - obj_audacious_complete_##name (obj, invoc) - -#define FINISH2(name, ...) \ - obj_audacious_complete_##name (obj, invoc, __VA_ARGS__) - -static Index * strv_to_index (const char * const * strv) -{ - Index * index = index_new (); - while (* strv) - index_insert (index, -1, str_get (* strv ++)); - - return index; -} - -static bool_t do_add (Obj * obj, Invoc * invoc, const char * file) -{ - playlist_entry_insert (playlist_get_active (), -1, file, NULL, FALSE); - FINISH (add); - return TRUE; -} - -static bool_t do_add_list (Obj * obj, Invoc * invoc, const char * const * filenames) -{ - playlist_entry_insert_batch (playlist_get_active (), -1, - strv_to_index (filenames), NULL, FALSE); - FINISH (add_list); - return TRUE; -} - -static bool_t do_add_url (Obj * obj, Invoc * invoc, const char * url) -{ - playlist_entry_insert (playlist_get_active (), -1, url, NULL, FALSE); - FINISH (add_url); - return TRUE; -} - -static bool_t do_advance (Obj * obj, Invoc * invoc) -{ - drct_pl_next (); - FINISH (advance); - return TRUE; -} - -static bool_t do_auto_advance (Obj * obj, Invoc * invoc) -{ - FINISH2 (auto_advance, ! get_bool (NULL, "no_playlist_advance")); - return TRUE; -} - -static bool_t do_balance (Obj * obj, Invoc * invoc) -{ - int balance; - drct_get_volume_balance (& balance); - FINISH2 (balance, balance); - return TRUE; -} - -static bool_t do_clear (Obj * obj, Invoc * invoc) -{ - int playlist = playlist_get_active (); - playlist_entry_delete (playlist, 0, playlist_entry_count (playlist)); - FINISH (clear); - return TRUE; -} - -static bool_t do_delete (Obj * obj, Invoc * invoc, unsigned pos) -{ - playlist_entry_delete (playlist_get_active (), pos, 1); - FINISH (delete); - return TRUE; -} - -static bool_t do_delete_active_playlist (Obj * obj, Invoc * invoc) -{ - playlist_delete (playlist_get_active ()); - FINISH (delete_active_playlist); - return TRUE; -} - -static bool_t do_eject (Obj * obj, Invoc * invoc) -{ - if (! headless_mode ()) - audgui_run_filebrowser (TRUE); - - FINISH (eject); - return TRUE; -} - -static bool_t do_equalizer_activate (Obj * obj, Invoc * invoc, bool_t active) -{ - set_bool (NULL, "equalizer_active", active); - FINISH (equalizer_activate); - return TRUE; -} - -static bool_t do_get_active_playlist (Obj * obj, Invoc * invoc) -{ - FINISH2 (get_active_playlist, playlist_get_active ()); - return TRUE; -} - -static bool_t do_get_active_playlist_name (Obj * obj, Invoc * invoc) -{ - char * title = playlist_get_title (playlist_get_active ()); - FINISH2 (get_active_playlist_name, title ? title : ""); - str_unref (title); - return TRUE; -} - -static bool_t do_get_eq (Obj * obj, Invoc * invoc) -{ - double preamp = get_double (NULL, "equalizer_preamp"); - double bands[AUD_EQUALIZER_NBANDS]; - eq_get_bands (bands); - - GVariant * var = g_variant_new_fixed_array (G_VARIANT_TYPE_DOUBLE, bands, - AUD_EQUALIZER_NBANDS, sizeof (double)); - FINISH2 (get_eq, preamp, var); - return TRUE; -} - -static bool_t do_get_eq_band (Obj * obj, Invoc * invoc, int band) -{ - FINISH2 (get_eq_band, eq_get_band (band)); - return TRUE; -} - -static bool_t do_get_eq_preamp (Obj * obj, Invoc * invoc) -{ - FINISH2 (get_eq_preamp, get_double (NULL, "equalizer_preamp")); - return TRUE; -} - -static bool_t do_get_info (Obj * obj, Invoc * invoc) -{ - int bitrate, samplerate, channels; - drct_get_info (& bitrate, & samplerate, & channels); - FINISH2 (get_info, bitrate, samplerate, channels); - return TRUE; -} - -static bool_t do_get_playqueue_length (Obj * obj, Invoc * invoc) -{ - FINISH2 (get_playqueue_length, playlist_queue_count (playlist_get_active ())); - return TRUE; -} - -static bool_t do_get_tuple_fields (Obj * obj, Invoc * invoc) -{ - const char * fields[TUPLE_FIELDS + 1]; - - for (int i = 0; i < TUPLE_FIELDS; i ++) - fields[i] = tuple_field_get_name (i); - - fields[TUPLE_FIELDS] = NULL; - - FINISH2 (get_tuple_fields, fields); - return TRUE; -} - -static bool_t do_info (Obj * obj, Invoc * invoc) -{ - int bitrate, samplerate, channels; - drct_get_info (& bitrate, & samplerate, & channels); - FINISH2 (info, bitrate, samplerate, channels); - return TRUE; -} - -static bool_t do_jump (Obj * obj, Invoc * invoc, unsigned pos) -{ - playlist_set_position (playlist_get_active (), pos); - FINISH (jump); - return TRUE; -} - -static bool_t do_length (Obj * obj, Invoc * invoc) -{ - FINISH2 (length, playlist_entry_count (playlist_get_active ())); - return TRUE; -} - -static bool_t do_main_win_visible (Obj * obj, Invoc * invoc) -{ - FINISH2 (main_win_visible, ! headless_mode () && interface_is_shown ()); - return TRUE; -} - -static bool_t do_new_playlist (Obj * obj, Invoc * invoc) -{ - playlist_insert (-1); - playlist_set_active (playlist_count () - 1); - FINISH (new_playlist); - return TRUE; -} - -static bool_t do_number_of_playlists (Obj * obj, Invoc * invoc) -{ - FINISH2 (number_of_playlists, playlist_count ()); - return TRUE; -} - -static bool_t do_open_list (Obj * obj, Invoc * invoc, const char * const * filenames) -{ - drct_pl_open_list (strv_to_index (filenames)); - FINISH (open_list); - return TRUE; -} - -static bool_t do_open_list_to_temp (Obj * obj, Invoc * invoc, const char * const * filenames) -{ - drct_pl_open_temp_list (strv_to_index (filenames)); - FINISH (open_list_to_temp); - return TRUE; -} - -static bool_t do_pause (Obj * obj, Invoc * invoc) -{ - drct_pause (); - FINISH (pause); - return TRUE; -} - -static bool_t do_paused (Obj * obj, Invoc * invoc) -{ - FINISH2 (paused, drct_get_paused ()); - return TRUE; -} - -static bool_t do_play (Obj * obj, Invoc * invoc) -{ - drct_play (); - FINISH (play); - return TRUE; -} - -static bool_t do_play_active_playlist (Obj * obj, Invoc * invoc) -{ - drct_play_playlist (playlist_get_active ()); - FINISH (play_active_playlist); - return TRUE; -} - -static bool_t do_play_pause (Obj * obj, Invoc * invoc) -{ - drct_play_pause (); - FINISH (play_pause); - return TRUE; -} - -static bool_t do_playing (Obj * obj, Invoc * invoc) -{ - FINISH2 (playing, drct_get_playing ()); - return TRUE; -} - -static bool_t do_playlist_add (Obj * obj, Invoc * invoc, const char * list) -{ - playlist_entry_insert (playlist_get_active (), -1, list, NULL, FALSE); - FINISH (playlist_add); - return TRUE; -} - -static bool_t do_playlist_enqueue_to_temp (Obj * obj, Invoc * invoc, const char * url) -{ - drct_pl_open_temp (url); - FINISH (playlist_enqueue_to_temp); - return TRUE; -} - -static bool_t do_playlist_ins_url_string (Obj * obj, Invoc * invoc, const char * url, int pos) -{ - playlist_entry_insert (playlist_get_active (), pos, url, NULL, FALSE); - FINISH (playlist_ins_url_string); - return TRUE; -} - -static bool_t do_playqueue_add (Obj * obj, Invoc * invoc, int pos) -{ - playlist_queue_insert (playlist_get_active (), -1, pos); - FINISH (playqueue_add); - return TRUE; -} - -static bool_t do_playqueue_clear (Obj * obj, Invoc * invoc) -{ - int playlist = playlist_get_active (); - playlist_queue_delete (playlist, 0, playlist_queue_count (playlist)); - FINISH (playqueue_clear); - return TRUE; -} - -static bool_t do_playqueue_is_queued (Obj * obj, Invoc * invoc, int pos) -{ - bool_t queued = (playlist_queue_find_entry (playlist_get_active (), pos) >= 0); - FINISH2 (playqueue_is_queued, queued); - return TRUE; -} - -static bool_t do_playqueue_remove (Obj * obj, Invoc * invoc, int pos) -{ - int playlist = playlist_get_active (); - int qpos = playlist_queue_find_entry (playlist, pos); - - if (qpos >= 0) - playlist_queue_delete (playlist, qpos, 1); - - FINISH (playqueue_remove); - return TRUE; -} - -static bool_t do_position (Obj * obj, Invoc * invoc) -{ - FINISH2 (position, playlist_get_position (playlist_get_active ())); - return TRUE; -} - -static bool_t do_queue_get_list_pos (Obj * obj, Invoc * invoc, unsigned qpos) -{ - FINISH2 (queue_get_list_pos, playlist_queue_get_entry (playlist_get_active (), qpos)); - return TRUE; -} - -static bool_t do_queue_get_queue_pos (Obj * obj, Invoc * invoc, unsigned pos) -{ - FINISH2 (queue_get_queue_pos, playlist_queue_find_entry (playlist_get_active (), pos)); - return TRUE; -} - -static bool_t do_quit (Obj * obj, Invoc * invoc) -{ - drct_quit (); - FINISH (quit); - return TRUE; -} - -static bool_t do_repeat (Obj * obj, Invoc * invoc) -{ - FINISH2 (repeat, get_bool (NULL, "repeat")); - return TRUE; -} - -static bool_t do_reverse (Obj * obj, Invoc * invoc) -{ - drct_pl_prev (); - FINISH (reverse); - return TRUE; -} - -static bool_t do_seek (Obj * obj, Invoc * invoc, unsigned pos) -{ - drct_seek (pos); - FINISH (seek); - return TRUE; -} - -static bool_t do_set_active_playlist (Obj * obj, Invoc * invoc, int playlist) -{ - playlist_set_active (playlist); - FINISH (set_active_playlist); - return TRUE; -} - -static bool_t do_set_active_playlist_name (Obj * obj, Invoc * invoc, const char * title) -{ - playlist_set_title (playlist_get_active (), title); - FINISH (set_active_playlist_name); - return TRUE; -} - -static bool_t do_set_eq (Obj * obj, Invoc * invoc, double preamp, GVariant * var) -{ - if (! g_variant_is_of_type (var, G_VARIANT_TYPE ("ad"))) - return FALSE; - - size_t nbands = 0; - const double * bands = g_variant_get_fixed_array (var, & nbands, sizeof (double)); - - if (nbands != AUD_EQUALIZER_NBANDS) - return FALSE; - - set_double (NULL, "equalizer_preamp", preamp); - eq_set_bands (bands); - FINISH (set_eq); - return TRUE; -} - -static bool_t do_set_eq_band (Obj * obj, Invoc * invoc, int band, double value) -{ - eq_set_band (band, value); - FINISH (set_eq_band); - return TRUE; -} - -static bool_t do_set_eq_preamp (Obj * obj, Invoc * invoc, double preamp) -{ - set_double (NULL, "equalizer_preamp", preamp); - FINISH (set_eq_preamp); - return TRUE; -} - -static bool_t do_set_volume (Obj * obj, Invoc * invoc, int vl, int vr) -{ - drct_set_volume (vl, vr); - FINISH (set_volume); - return TRUE; -} - -static bool_t do_show_about_box (Obj * obj, Invoc * invoc, bool_t show) -{ - if (! headless_mode ()) - { - if (show) - audgui_show_about_window (); - else - audgui_hide_about_window (); - } - - FINISH (show_about_box); - return TRUE; -} - -static bool_t do_show_filebrowser (Obj * obj, Invoc * invoc, bool_t show) -{ - if (! headless_mode ()) - { - if (show) - audgui_run_filebrowser (FALSE); - else - audgui_hide_filebrowser (); - } - - FINISH (show_filebrowser); - return TRUE; -} - -static bool_t do_show_jtf_box (Obj * obj, Invoc * invoc, bool_t show) -{ - if (! headless_mode ()) - { - if (show) - audgui_jump_to_track (); - else - audgui_jump_to_track_hide (); - } - - FINISH (show_jtf_box); - return TRUE; -} - -static bool_t do_show_main_win (Obj * obj, Invoc * invoc, bool_t show) -{ - if (! headless_mode ()) - interface_show (show); - - FINISH (show_main_win); - return TRUE; -} - -static bool_t do_show_prefs_box (Obj * obj, Invoc * invoc, bool_t show) -{ - if (! headless_mode ()) - { - if (show) - show_prefs_window (); - else - hide_prefs_window (); - } - - FINISH (show_prefs_box); - return TRUE; -} - -static bool_t do_shuffle (Obj * obj, Invoc * invoc) -{ - FINISH2 (shuffle, get_bool (NULL, "shuffle")); - return TRUE; -} - -static bool_t do_song_filename (Obj * obj, Invoc * invoc, unsigned pos) -{ - char * filename = playlist_entry_get_filename (playlist_get_active (), pos); - FINISH2 (song_filename, filename ? filename : ""); - str_unref (filename); - return TRUE; -} - -static bool_t do_song_frames (Obj * obj, Invoc * invoc, unsigned pos) -{ - FINISH2 (song_frames, playlist_entry_get_length (playlist_get_active (), pos, FALSE)); - return TRUE; -} - -static bool_t do_song_length (Obj * obj, Invoc * invoc, unsigned pos) -{ - int length = playlist_entry_get_length (playlist_get_active (), pos, FALSE); - FINISH2 (song_length, length >= 0 ? length / 1000 : -1); - return TRUE; -} - -static bool_t do_song_title (Obj * obj, Invoc * invoc, unsigned pos) -{ - char * title = playlist_entry_get_title (playlist_get_active (), pos, FALSE); - FINISH2 (song_title, title ? title : ""); - str_unref (title); - return TRUE; -} - -static bool_t do_song_tuple (Obj * obj, Invoc * invoc, unsigned pos, const char * key) -{ - int field = tuple_field_by_name (key); - Tuple * tuple = NULL; - GVariant * var = NULL; - - if (field >= 0) - tuple = playlist_entry_get_tuple (playlist_get_active (), pos, FALSE); - - if (tuple) - { - char * str; - - switch (tuple_get_value_type (tuple, field)) - { - case TUPLE_STRING: - str = tuple_get_str (tuple, field); - var = g_variant_new_string (str); - str_unref (str); - break; - - case TUPLE_INT: - var = g_variant_new_int32 (tuple_get_int (tuple, field)); - break; - - default: - break; - } - - tuple_unref (tuple); - } - - if (! var) - var = g_variant_new_string (""); - - FINISH2 (song_tuple, g_variant_new_variant (var)); - return TRUE; -} - -static bool_t do_status (Obj * obj, Invoc * invoc) -{ - const char * status = "stopped"; - if (drct_get_playing ()) - status = drct_get_paused () ? "paused" : "playing"; - - FINISH2 (status, status); - return TRUE; -} - -static bool_t do_stop (Obj * obj, Invoc * invoc) -{ - drct_stop (); - FINISH (stop); - return TRUE; -} - -static bool_t do_stop_after (Obj * obj, Invoc * invoc) -{ - FINISH2 (stop_after, get_bool (NULL, "stop_after_current_song")); - return TRUE; -} - -static bool_t do_stopped (Obj * obj, Invoc * invoc) -{ - FINISH2 (stopped, ! drct_get_playing ()); - return TRUE; -} - -static bool_t do_time (Obj * obj, Invoc * invoc) -{ - FINISH2 (time, drct_get_time ()); - return TRUE; -} - -static bool_t do_toggle_auto_advance (Obj * obj, Invoc * invoc) -{ - set_bool (NULL, "no_playlist_advance", ! get_bool (NULL, "no_playlist_advance")); - FINISH (toggle_auto_advance); - return TRUE; -} - -static bool_t do_toggle_repeat (Obj * obj, Invoc * invoc) -{ - set_bool (NULL, "repeat", ! get_bool (NULL, "repeat")); - FINISH (toggle_repeat); - return TRUE; -} - -static bool_t do_toggle_shuffle (Obj * obj, Invoc * invoc) -{ - set_bool (NULL, "shuffle", ! get_bool (NULL, "shuffle")); - FINISH (toggle_shuffle); - return TRUE; -} - -static bool_t do_toggle_stop_after (Obj * obj, Invoc * invoc) -{ - set_bool (NULL, "stop_after_current_song", ! get_bool (NULL, "stop_after_current_song")); - FINISH (toggle_stop_after); - return TRUE; -} - -static bool_t do_version (Obj * obj, Invoc * invoc) -{ - FINISH2 (version, VERSION); - return TRUE; -} - -static bool_t do_volume (Obj * obj, Invoc * invoc) -{ - int left, right; - drct_get_volume (& left, & right); - FINISH2 (volume, left, right); - return TRUE; -} - -static const struct -{ - const char * signal; - GCallback callback; -} -handlers[] = -{ - {"handle-add", (GCallback) do_add}, - {"handle-add-list", (GCallback) do_add_list}, - {"handle-add-url", (GCallback) do_add_url}, - {"handle-advance", (GCallback) do_advance}, - {"handle-auto-advance", (GCallback) do_auto_advance}, - {"handle-balance", (GCallback) do_balance}, - {"handle-clear", (GCallback) do_clear}, - {"handle-delete", (GCallback) do_delete}, - {"handle-delete-active-playlist", (GCallback) do_delete_active_playlist}, - {"handle-eject", (GCallback) do_eject}, - {"handle-equalizer-activate", (GCallback) do_equalizer_activate}, - {"handle-get-active-playlist", (GCallback) do_get_active_playlist}, - {"handle-get-active-playlist-name", (GCallback) do_get_active_playlist_name}, - {"handle-get-eq", (GCallback) do_get_eq}, - {"handle-get-eq-band", (GCallback) do_get_eq_band}, - {"handle-get-eq-preamp", (GCallback) do_get_eq_preamp}, - {"handle-get-info", (GCallback) do_get_info}, - {"handle-get-playqueue-length", (GCallback) do_get_playqueue_length}, - {"handle-get-tuple-fields", (GCallback) do_get_tuple_fields}, - {"handle-info", (GCallback) do_info}, - {"handle-jump", (GCallback) do_jump}, - {"handle-length", (GCallback) do_length}, - {"handle-main-win-visible", (GCallback) do_main_win_visible}, - {"handle-new-playlist", (GCallback) do_new_playlist}, - {"handle-number-of-playlists", (GCallback) do_number_of_playlists}, - {"handle-open-list", (GCallback) do_open_list}, - {"handle-open-list-to-temp", (GCallback) do_open_list_to_temp}, - {"handle-pause", (GCallback) do_pause}, - {"handle-paused", (GCallback) do_paused}, - {"handle-play", (GCallback) do_play}, - {"handle-play-active-playlist", (GCallback) do_play_active_playlist}, - {"handle-play-pause", (GCallback) do_play_pause}, - {"handle-playing", (GCallback) do_playing}, - {"handle-playlist-add", (GCallback) do_playlist_add}, - {"handle-playlist-enqueue-to-temp", (GCallback) do_playlist_enqueue_to_temp}, - {"handle-playlist-ins-url-string", (GCallback) do_playlist_ins_url_string}, - {"handle-playqueue-add", (GCallback) do_playqueue_add}, - {"handle-playqueue-clear", (GCallback) do_playqueue_clear}, - {"handle-playqueue-is-queued", (GCallback) do_playqueue_is_queued}, - {"handle-playqueue-remove", (GCallback) do_playqueue_remove}, - {"handle-position", (GCallback) do_position}, - {"handle-queue-get-list-pos", (GCallback) do_queue_get_list_pos}, - {"handle-queue-get-queue-pos", (GCallback) do_queue_get_queue_pos}, - {"handle-quit", (GCallback) do_quit}, - {"handle-repeat", (GCallback) do_repeat}, - {"handle-reverse", (GCallback) do_reverse}, - {"handle-seek", (GCallback) do_seek}, - {"handle-set-active-playlist", (GCallback) do_set_active_playlist}, - {"handle-set-active-playlist-name", (GCallback) do_set_active_playlist_name}, - {"handle-set-eq", (GCallback) do_set_eq}, - {"handle-set-eq-band", (GCallback) do_set_eq_band}, - {"handle-set-eq-preamp", (GCallback) do_set_eq_preamp}, - {"handle-set-volume", (GCallback) do_set_volume}, - {"handle-show-about-box", (GCallback) do_show_about_box}, - {"handle-show-filebrowser", (GCallback) do_show_filebrowser}, - {"handle-show-jtf-box", (GCallback) do_show_jtf_box}, - {"handle-show-main-win", (GCallback) do_show_main_win}, - {"handle-show-prefs-box", (GCallback) do_show_prefs_box}, - {"handle-shuffle", (GCallback) do_shuffle}, - {"handle-song-filename", (GCallback) do_song_filename}, - {"handle-song-frames", (GCallback) do_song_frames}, - {"handle-song-length", (GCallback) do_song_length}, - {"handle-song-title", (GCallback) do_song_title}, - {"handle-song-tuple", (GCallback) do_song_tuple}, - {"handle-status", (GCallback) do_status}, - {"handle-stop", (GCallback) do_stop}, - {"handle-stop-after", (GCallback) do_stop_after}, - {"handle-stopped", (GCallback) do_stopped}, - {"handle-time", (GCallback) do_time}, - {"handle-toggle-auto-advance", (GCallback) do_toggle_auto_advance}, - {"handle-toggle-repeat", (GCallback) do_toggle_repeat}, - {"handle-toggle-shuffle", (GCallback) do_toggle_shuffle}, - {"handle-toggle-stop-after", (GCallback) do_toggle_stop_after}, - {"handle-version", (GCallback) do_version}, - {"handle-volume", (GCallback) do_volume} -}; - -static GDBusInterfaceSkeleton * skeleton = NULL; - -void dbus_server_init (void) -{ - GError * error = NULL; - GDBusConnection * bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, & error); - - if (! bus) - goto ERROR; - - g_bus_own_name_on_connection (bus, "org.atheme.audacious", 0, NULL, NULL, NULL, NULL); - - skeleton = (GDBusInterfaceSkeleton *) obj_audacious_skeleton_new (); - - for (int i = 0; i < ARRAY_LEN (handlers); i ++) - g_signal_connect (skeleton, handlers[i].signal, handlers[i].callback, NULL); - - if (! g_dbus_interface_skeleton_export (skeleton, bus, "/org/atheme/audacious", & error)) - goto ERROR; - - return; - -ERROR: - if (error) - { - fprintf (stderr, "D-Bus error: %s\n", error->message); - g_error_free (error); - } -} - -void dbus_server_cleanup (void) -{ - if (skeleton) - { - g_object_unref (skeleton); - skeleton = NULL; - } -} diff --git a/src/audacious/dbus-server.cc b/src/audacious/dbus-server.cc new file mode 100644 index 0000000..90f464e --- /dev/null +++ b/src/audacious/dbus-server.cc @@ -0,0 +1,815 @@ +/* + * dbus-server.c + * Copyright 2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <libaudcore/drct.h> +#include <libaudcore/equalizer.h> +#include <libaudcore/interface.h> +#include <libaudcore/playlist.h> +#include <libaudcore/runtime.h> +#include <libaudcore/tuple.h> + +#include "aud-dbus.h" +#include "main.h" + +typedef ObjAudacious Obj; +typedef GDBusMethodInvocation Invoc; + +#define FINISH(name) \ + obj_audacious_complete_##name (obj, invoc) + +#define FINISH2(name, ...) \ + obj_audacious_complete_##name (obj, invoc, __VA_ARGS__) + +static Index<PlaylistAddItem> strv_to_index (const char * const * strv) +{ + Index<PlaylistAddItem> index; + while (* strv) + index.append (String (* strv ++)); + + return index; +} + +static gboolean do_add (Obj * obj, Invoc * invoc, const char * file) +{ + aud_playlist_entry_insert (aud_playlist_get_active (), -1, file, Tuple (), false); + FINISH (add); + return true; +} + +static gboolean do_add_list (Obj * obj, Invoc * invoc, const char * const * filenames) +{ + aud_playlist_entry_insert_batch (aud_playlist_get_active (), -1, + strv_to_index (filenames), false); + FINISH (add_list); + return true; +} + +static gboolean do_add_url (Obj * obj, Invoc * invoc, const char * url) +{ + aud_playlist_entry_insert (aud_playlist_get_active (), -1, url, Tuple (), false); + FINISH (add_url); + return true; +} + +static gboolean do_advance (Obj * obj, Invoc * invoc) +{ + aud_drct_pl_next (); + FINISH (advance); + return true; +} + +static gboolean do_auto_advance (Obj * obj, Invoc * invoc) +{ + FINISH2 (auto_advance, ! aud_get_bool (nullptr, "no_playlist_advance")); + return true; +} + +static gboolean do_balance (Obj * obj, Invoc * invoc) +{ + FINISH2 (balance, aud_drct_get_volume_balance ()); + return true; +} + +static gboolean do_clear (Obj * obj, Invoc * invoc) +{ + int playlist = aud_playlist_get_active (); + aud_playlist_entry_delete (playlist, 0, aud_playlist_entry_count (playlist)); + FINISH (clear); + return true; +} + +static gboolean do_delete (Obj * obj, Invoc * invoc, unsigned pos) +{ + aud_playlist_entry_delete (aud_playlist_get_active (), pos, 1); + FINISH (delete); + return true; +} + +static gboolean do_delete_active_playlist (Obj * obj, Invoc * invoc) +{ + aud_playlist_delete (aud_playlist_get_active ()); + FINISH (delete_active_playlist); + return true; +} + +static gboolean do_eject (Obj * obj, Invoc * invoc) +{ + if (! aud_get_headless_mode ()) + aud_ui_show_filebrowser (true); + + FINISH (eject); + return true; +} + +static gboolean do_equalizer_activate (Obj * obj, Invoc * invoc, gboolean active) +{ + aud_set_bool (nullptr, "equalizer_active", active); + FINISH (equalizer_activate); + return true; +} + +static gboolean do_get_active_playlist (Obj * obj, Invoc * invoc) +{ + FINISH2 (get_active_playlist, aud_playlist_get_active ()); + return true; +} + +static gboolean do_get_active_playlist_name (Obj * obj, Invoc * invoc) +{ + String title = aud_playlist_get_title (aud_playlist_get_active ()); + FINISH2 (get_active_playlist_name, title ? title : ""); + return true; +} + +static gboolean do_get_eq (Obj * obj, Invoc * invoc) +{ + double preamp = aud_get_double (nullptr, "equalizer_preamp"); + double bands[AUD_EQ_NBANDS]; + aud_eq_get_bands (bands); + + GVariant * var = g_variant_new_fixed_array (G_VARIANT_TYPE_DOUBLE, bands, + AUD_EQ_NBANDS, sizeof (double)); + FINISH2 (get_eq, preamp, var); + return true; +} + +static gboolean do_get_eq_band (Obj * obj, Invoc * invoc, int band) +{ + FINISH2 (get_eq_band, aud_eq_get_band (band)); + return true; +} + +static gboolean do_get_eq_preamp (Obj * obj, Invoc * invoc) +{ + FINISH2 (get_eq_preamp, aud_get_double (nullptr, "equalizer_preamp")); + return true; +} + +static gboolean do_get_info (Obj * obj, Invoc * invoc) +{ + int bitrate, samplerate, channels; + aud_drct_get_info (bitrate, samplerate, channels); + FINISH2 (get_info, bitrate, samplerate, channels); + return true; +} + +static gboolean do_get_playqueue_length (Obj * obj, Invoc * invoc) +{ + FINISH2 (get_playqueue_length, aud_playlist_queue_count (aud_playlist_get_active ())); + return true; +} + +static gboolean do_get_tuple_fields (Obj * obj, Invoc * invoc) +{ + const char * fields[Tuple::n_fields + 1]; + + for (auto f : Tuple::all_fields ()) + fields[f] = Tuple::field_get_name (f); + + fields[Tuple::n_fields] = nullptr; + + FINISH2 (get_tuple_fields, fields); + return true; +} + +static gboolean do_info (Obj * obj, Invoc * invoc) +{ + int bitrate, samplerate, channels; + aud_drct_get_info (bitrate, samplerate, channels); + FINISH2 (info, bitrate, samplerate, channels); + return true; +} + +static gboolean do_jump (Obj * obj, Invoc * invoc, unsigned pos) +{ + aud_playlist_set_position (aud_playlist_get_active (), pos); + FINISH (jump); + return true; +} + +static gboolean do_length (Obj * obj, Invoc * invoc) +{ + FINISH2 (length, aud_playlist_entry_count (aud_playlist_get_active ())); + return true; +} + +static gboolean do_main_win_visible (Obj * obj, Invoc * invoc) +{ + FINISH2 (main_win_visible, ! aud_get_headless_mode () && aud_ui_is_shown ()); + return true; +} + +static gboolean do_new_playlist (Obj * obj, Invoc * invoc) +{ + aud_playlist_insert (-1); + aud_playlist_set_active (aud_playlist_count () - 1); + FINISH (new_playlist); + return true; +} + +static gboolean do_number_of_playlists (Obj * obj, Invoc * invoc) +{ + FINISH2 (number_of_playlists, aud_playlist_count ()); + return true; +} + +static gboolean do_open_list (Obj * obj, Invoc * invoc, const char * const * filenames) +{ + aud_drct_pl_open_list (strv_to_index (filenames)); + FINISH (open_list); + return true; +} + +static gboolean do_open_list_to_temp (Obj * obj, Invoc * invoc, const char * const * filenames) +{ + aud_drct_pl_open_temp_list (strv_to_index (filenames)); + FINISH (open_list_to_temp); + return true; +} + +static gboolean do_pause (Obj * obj, Invoc * invoc) +{ + aud_drct_pause (); + FINISH (pause); + return true; +} + +static gboolean do_paused (Obj * obj, Invoc * invoc) +{ + FINISH2 (paused, aud_drct_get_paused ()); + return true; +} + +static gboolean do_play (Obj * obj, Invoc * invoc) +{ + aud_drct_play (); + FINISH (play); + return true; +} + +static gboolean do_play_active_playlist (Obj * obj, Invoc * invoc) +{ + aud_playlist_play (aud_playlist_get_active ()); + FINISH (play_active_playlist); + return true; +} + +static gboolean do_play_pause (Obj * obj, Invoc * invoc) +{ + aud_drct_play_pause (); + FINISH (play_pause); + return true; +} + +static gboolean do_playing (Obj * obj, Invoc * invoc) +{ + FINISH2 (playing, aud_drct_get_playing ()); + return true; +} + +static gboolean do_playlist_add (Obj * obj, Invoc * invoc, const char * list) +{ + aud_playlist_entry_insert (aud_playlist_get_active (), -1, list, Tuple (), false); + FINISH (playlist_add); + return true; +} + +static gboolean do_playlist_enqueue_to_temp (Obj * obj, Invoc * invoc, const char * url) +{ + aud_drct_pl_open_temp (url); + FINISH (playlist_enqueue_to_temp); + return true; +} + +static gboolean do_playlist_ins_url_string (Obj * obj, Invoc * invoc, const char * url, int pos) +{ + aud_playlist_entry_insert (aud_playlist_get_active (), pos, url, Tuple (), false); + FINISH (playlist_ins_url_string); + return true; +} + +static gboolean do_playqueue_add (Obj * obj, Invoc * invoc, int pos) +{ + aud_playlist_queue_insert (aud_playlist_get_active (), -1, pos); + FINISH (playqueue_add); + return true; +} + +static gboolean do_playqueue_clear (Obj * obj, Invoc * invoc) +{ + int playlist = aud_playlist_get_active (); + aud_playlist_queue_delete (playlist, 0, aud_playlist_queue_count (playlist)); + FINISH (playqueue_clear); + return true; +} + +static gboolean do_playqueue_is_queued (Obj * obj, Invoc * invoc, int pos) +{ + bool queued = (aud_playlist_queue_find_entry (aud_playlist_get_active (), pos) >= 0); + FINISH2 (playqueue_is_queued, queued); + return true; +} + +static gboolean do_playqueue_remove (Obj * obj, Invoc * invoc, int pos) +{ + int playlist = aud_playlist_get_active (); + int qpos = aud_playlist_queue_find_entry (playlist, pos); + + if (qpos >= 0) + aud_playlist_queue_delete (playlist, qpos, 1); + + FINISH (playqueue_remove); + return true; +} + +static gboolean do_position (Obj * obj, Invoc * invoc) +{ + FINISH2 (position, aud_playlist_get_position (aud_playlist_get_active ())); + return true; +} + +static gboolean do_queue_get_list_pos (Obj * obj, Invoc * invoc, unsigned qpos) +{ + FINISH2 (queue_get_list_pos, aud_playlist_queue_get_entry (aud_playlist_get_active (), qpos)); + return true; +} + +static gboolean do_queue_get_queue_pos (Obj * obj, Invoc * invoc, unsigned pos) +{ + FINISH2 (queue_get_queue_pos, aud_playlist_queue_find_entry (aud_playlist_get_active (), pos)); + return true; +} + +static gboolean do_quit (Obj * obj, Invoc * invoc) +{ + aud_quit (); + FINISH (quit); + return true; +} + +static gboolean do_repeat (Obj * obj, Invoc * invoc) +{ + FINISH2 (repeat, aud_get_bool (nullptr, "repeat")); + return true; +} + +static gboolean do_reverse (Obj * obj, Invoc * invoc) +{ + aud_drct_pl_prev (); + FINISH (reverse); + return true; +} + +static gboolean do_seek (Obj * obj, Invoc * invoc, unsigned pos) +{ + aud_drct_seek (pos); + FINISH (seek); + return true; +} + +static gboolean do_set_active_playlist (Obj * obj, Invoc * invoc, int playlist) +{ + aud_playlist_set_active (playlist); + FINISH (set_active_playlist); + return true; +} + +static gboolean do_set_active_playlist_name (Obj * obj, Invoc * invoc, const char * title) +{ + aud_playlist_set_title (aud_playlist_get_active (), title); + FINISH (set_active_playlist_name); + return true; +} + +static gboolean do_set_eq (Obj * obj, Invoc * invoc, double preamp, GVariant * var) +{ + if (! g_variant_is_of_type (var, G_VARIANT_TYPE ("ad"))) + return false; + + size_t nbands = 0; + const double * bands = (double *) g_variant_get_fixed_array (var, & nbands, sizeof (double)); + + if (nbands != AUD_EQ_NBANDS) + return false; + + aud_set_double (nullptr, "equalizer_preamp", preamp); + aud_eq_set_bands (bands); + FINISH (set_eq); + return true; +} + +static gboolean do_set_eq_band (Obj * obj, Invoc * invoc, int band, double value) +{ + aud_eq_set_band (band, value); + FINISH (set_eq_band); + return true; +} + +static gboolean do_set_eq_preamp (Obj * obj, Invoc * invoc, double preamp) +{ + aud_set_double (nullptr, "equalizer_preamp", preamp); + FINISH (set_eq_preamp); + return true; +} + +static gboolean do_set_volume (Obj * obj, Invoc * invoc, int vl, int vr) +{ + aud_drct_set_volume ({vl, vr}); + FINISH (set_volume); + return true; +} + +static gboolean do_show_about_box (Obj * obj, Invoc * invoc, gboolean show) +{ + if (! aud_get_headless_mode ()) + { + if (show) + aud_ui_show_about_window (); + else + aud_ui_hide_about_window (); + } + + FINISH (show_about_box); + return true; +} + +static gboolean do_show_filebrowser (Obj * obj, Invoc * invoc, gboolean show) +{ + if (! aud_get_headless_mode ()) + { + if (show) + aud_ui_show_filebrowser (false); + else + aud_ui_hide_filebrowser (); + } + + FINISH (show_filebrowser); + return true; +} + +static gboolean do_show_jtf_box (Obj * obj, Invoc * invoc, gboolean show) +{ + if (! aud_get_headless_mode ()) + { + if (show) + aud_ui_show_jump_to_song (); + else + aud_ui_hide_jump_to_song (); + } + + FINISH (show_jtf_box); + return true; +} + +static gboolean do_show_main_win (Obj * obj, Invoc * invoc, gboolean show) +{ + if (! aud_get_headless_mode ()) + aud_ui_show (show); + + FINISH (show_main_win); + return true; +} + +static gboolean do_show_prefs_box (Obj * obj, Invoc * invoc, gboolean show) +{ + if (! aud_get_headless_mode ()) + { + if (show) + aud_ui_show_prefs_window (); + else + aud_ui_hide_prefs_window (); + } + + FINISH (show_prefs_box); + return true; +} + +static gboolean do_shuffle (Obj * obj, Invoc * invoc) +{ + FINISH2 (shuffle, aud_get_bool (nullptr, "shuffle")); + return true; +} + +static gboolean do_song_filename (Obj * obj, Invoc * invoc, unsigned pos) +{ + String filename = aud_playlist_entry_get_filename (aud_playlist_get_active (), pos); + FINISH2 (song_filename, filename ? filename : ""); + return true; +} + +static gboolean do_song_frames (Obj * obj, Invoc * invoc, unsigned pos) +{ + Tuple tuple = aud_playlist_entry_get_tuple (aud_playlist_get_active (), pos); + FINISH2 (song_frames, aud::max (0, tuple.get_int (Tuple::Length))); + return true; +} + +static gboolean do_song_length (Obj * obj, Invoc * invoc, unsigned pos) +{ + Tuple tuple = aud_playlist_entry_get_tuple (aud_playlist_get_active (), pos); + int length = aud::max (0, tuple.get_int (Tuple::Length)); + FINISH2 (song_length, length / 1000); + return true; +} + +static gboolean do_song_title (Obj * obj, Invoc * invoc, unsigned pos) +{ + Tuple tuple = aud_playlist_entry_get_tuple (aud_playlist_get_active (), pos); + String title = tuple.get_str (Tuple::FormattedTitle); + FINISH2 (song_title, title ? title : ""); + return true; +} + +static gboolean do_song_tuple (Obj * obj, Invoc * invoc, unsigned pos, const char * key) +{ + Tuple::Field field = Tuple::field_by_name (key); + Tuple tuple; + GVariant * var = nullptr; + + if (field >= 0) + tuple = aud_playlist_entry_get_tuple (aud_playlist_get_active (), pos); + + if (tuple) + { + switch (tuple.get_value_type (field)) + { + case Tuple::String: + var = g_variant_new_string (tuple.get_str (field)); + break; + + case Tuple::Int: + var = g_variant_new_int32 (tuple.get_int (field)); + break; + + default: + break; + } + } + + if (! var) + var = g_variant_new_string (""); + + FINISH2 (song_tuple, g_variant_new_variant (var)); + return true; +} + +static gboolean do_status (Obj * obj, Invoc * invoc) +{ + const char * status = "stopped"; + if (aud_drct_get_playing ()) + status = aud_drct_get_paused () ? "paused" : "playing"; + + FINISH2 (status, status); + return true; +} + +static gboolean do_stop (Obj * obj, Invoc * invoc) +{ + aud_drct_stop (); + FINISH (stop); + return true; +} + +static gboolean do_stop_after (Obj * obj, Invoc * invoc) +{ + FINISH2 (stop_after, aud_get_bool (nullptr, "stop_after_current_song")); + return true; +} + +static gboolean do_stopped (Obj * obj, Invoc * invoc) +{ + FINISH2 (stopped, ! aud_drct_get_playing ()); + return true; +} + +static gboolean do_time (Obj * obj, Invoc * invoc) +{ + FINISH2 (time, aud_drct_get_time ()); + return true; +} + +static gboolean do_toggle_auto_advance (Obj * obj, Invoc * invoc) +{ + aud_set_bool (nullptr, "no_playlist_advance", ! aud_get_bool (nullptr, "no_playlist_advance")); + FINISH (toggle_auto_advance); + return true; +} + +static gboolean do_toggle_repeat (Obj * obj, Invoc * invoc) +{ + aud_set_bool (nullptr, "repeat", ! aud_get_bool (nullptr, "repeat")); + FINISH (toggle_repeat); + return true; +} + +static gboolean do_toggle_shuffle (Obj * obj, Invoc * invoc) +{ + aud_set_bool (nullptr, "shuffle", ! aud_get_bool (nullptr, "shuffle")); + FINISH (toggle_shuffle); + return true; +} + +static gboolean do_toggle_stop_after (Obj * obj, Invoc * invoc) +{ + aud_set_bool (nullptr, "stop_after_current_song", ! aud_get_bool (nullptr, "stop_after_current_song")); + FINISH (toggle_stop_after); + return true; +} + +static gboolean do_version (Obj * obj, Invoc * invoc) +{ + FINISH2 (version, VERSION); + return true; +} + +static gboolean do_volume (Obj * obj, Invoc * invoc) +{ + StereoVolume volume = aud_drct_get_volume (); + FINISH2 (volume, volume.left, volume.right); + return true; +} + +static const struct +{ + const char * signal; + GCallback callback; +} +handlers[] = +{ + {"handle-add", (GCallback) do_add}, + {"handle-add-list", (GCallback) do_add_list}, + {"handle-add-url", (GCallback) do_add_url}, + {"handle-advance", (GCallback) do_advance}, + {"handle-auto-advance", (GCallback) do_auto_advance}, + {"handle-balance", (GCallback) do_balance}, + {"handle-clear", (GCallback) do_clear}, + {"handle-delete", (GCallback) do_delete}, + {"handle-delete-active-playlist", (GCallback) do_delete_active_playlist}, + {"handle-eject", (GCallback) do_eject}, + {"handle-equalizer-activate", (GCallback) do_equalizer_activate}, + {"handle-get-active-playlist", (GCallback) do_get_active_playlist}, + {"handle-get-active-playlist-name", (GCallback) do_get_active_playlist_name}, + {"handle-get-eq", (GCallback) do_get_eq}, + {"handle-get-eq-band", (GCallback) do_get_eq_band}, + {"handle-get-eq-preamp", (GCallback) do_get_eq_preamp}, + {"handle-get-info", (GCallback) do_get_info}, + {"handle-get-playqueue-length", (GCallback) do_get_playqueue_length}, + {"handle-get-tuple-fields", (GCallback) do_get_tuple_fields}, + {"handle-info", (GCallback) do_info}, + {"handle-jump", (GCallback) do_jump}, + {"handle-length", (GCallback) do_length}, + {"handle-main-win-visible", (GCallback) do_main_win_visible}, + {"handle-new-playlist", (GCallback) do_new_playlist}, + {"handle-number-of-playlists", (GCallback) do_number_of_playlists}, + {"handle-open-list", (GCallback) do_open_list}, + {"handle-open-list-to-temp", (GCallback) do_open_list_to_temp}, + {"handle-pause", (GCallback) do_pause}, + {"handle-paused", (GCallback) do_paused}, + {"handle-play", (GCallback) do_play}, + {"handle-play-active-playlist", (GCallback) do_play_active_playlist}, + {"handle-play-pause", (GCallback) do_play_pause}, + {"handle-playing", (GCallback) do_playing}, + {"handle-playlist-add", (GCallback) do_playlist_add}, + {"handle-playlist-enqueue-to-temp", (GCallback) do_playlist_enqueue_to_temp}, + {"handle-playlist-ins-url-string", (GCallback) do_playlist_ins_url_string}, + {"handle-playqueue-add", (GCallback) do_playqueue_add}, + {"handle-playqueue-clear", (GCallback) do_playqueue_clear}, + {"handle-playqueue-is-queued", (GCallback) do_playqueue_is_queued}, + {"handle-playqueue-remove", (GCallback) do_playqueue_remove}, + {"handle-position", (GCallback) do_position}, + {"handle-queue-get-list-pos", (GCallback) do_queue_get_list_pos}, + {"handle-queue-get-queue-pos", (GCallback) do_queue_get_queue_pos}, + {"handle-quit", (GCallback) do_quit}, + {"handle-repeat", (GCallback) do_repeat}, + {"handle-reverse", (GCallback) do_reverse}, + {"handle-seek", (GCallback) do_seek}, + {"handle-set-active-playlist", (GCallback) do_set_active_playlist}, + {"handle-set-active-playlist-name", (GCallback) do_set_active_playlist_name}, + {"handle-set-eq", (GCallback) do_set_eq}, + {"handle-set-eq-band", (GCallback) do_set_eq_band}, + {"handle-set-eq-preamp", (GCallback) do_set_eq_preamp}, + {"handle-set-volume", (GCallback) do_set_volume}, + {"handle-show-about-box", (GCallback) do_show_about_box}, + {"handle-show-filebrowser", (GCallback) do_show_filebrowser}, + {"handle-show-jtf-box", (GCallback) do_show_jtf_box}, + {"handle-show-main-win", (GCallback) do_show_main_win}, + {"handle-show-prefs-box", (GCallback) do_show_prefs_box}, + {"handle-shuffle", (GCallback) do_shuffle}, + {"handle-song-filename", (GCallback) do_song_filename}, + {"handle-song-frames", (GCallback) do_song_frames}, + {"handle-song-length", (GCallback) do_song_length}, + {"handle-song-title", (GCallback) do_song_title}, + {"handle-song-tuple", (GCallback) do_song_tuple}, + {"handle-status", (GCallback) do_status}, + {"handle-stop", (GCallback) do_stop}, + {"handle-stop-after", (GCallback) do_stop_after}, + {"handle-stopped", (GCallback) do_stopped}, + {"handle-time", (GCallback) do_time}, + {"handle-toggle-auto-advance", (GCallback) do_toggle_auto_advance}, + {"handle-toggle-repeat", (GCallback) do_toggle_repeat}, + {"handle-toggle-shuffle", (GCallback) do_toggle_shuffle}, + {"handle-toggle-stop-after", (GCallback) do_toggle_stop_after}, + {"handle-version", (GCallback) do_version}, + {"handle-volume", (GCallback) do_volume} +}; + +static GMainLoop * mainloop = nullptr; +static unsigned owner_id = 0; + +static GDBusInterfaceSkeleton * skeleton = nullptr; + +static void name_acquired (GDBusConnection *, const char *, void *) +{ + AUDINFO ("Owned D-Bus name (org.atheme.audacious) on session bus.\n"); + + g_main_loop_quit (mainloop); +} + +static void name_lost (GDBusConnection *, const char *, void *) +{ + AUDINFO ("Owning D-Bus name (org.atheme.audacious) failed, already taken?\n"); + + g_bus_unown_name (owner_id); + owner_id = 0; + + g_main_loop_quit (mainloop); +} + +StartupType dbus_server_init (void) +{ + GError * error = nullptr; + GDBusConnection * bus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, & error); + GMainContext * context; + + if (! bus) + goto ERROR; + + skeleton = (GDBusInterfaceSkeleton *) obj_audacious_skeleton_new (); + + for (auto & handler : handlers) + g_signal_connect (skeleton, handler.signal, handler.callback, nullptr); + + if (! g_dbus_interface_skeleton_export (skeleton, bus, "/org/atheme/audacious", & error)) + goto ERROR; + + context = g_main_context_new (); + g_main_context_push_thread_default (context); + + owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, "org.atheme.audacious", + (GBusNameOwnerFlags) 0, nullptr, name_acquired, name_lost, nullptr, nullptr); + + mainloop = g_main_loop_new (context, true); + g_main_loop_run (mainloop); + g_main_loop_unref (mainloop); + mainloop = nullptr; + + g_main_context_pop_thread_default (context); + g_main_context_unref (context); + + if (owner_id) + return StartupType::Server; + + dbus_server_cleanup (); + return StartupType::Client; + +ERROR: + if (error) + { + AUDERR ("D-Bus error: %s\n", error->message); + g_error_free (error); + } + + dbus_server_cleanup (); + return StartupType::Unknown; +} + +void dbus_server_cleanup (void) +{ + if (owner_id) + { + g_bus_unown_name (owner_id); + owner_id = 0; + } + + if (skeleton) + { + g_object_unref (skeleton); + skeleton = nullptr; + } +} diff --git a/src/audacious/drct-api.h b/src/audacious/drct-api.h deleted file mode 100644 index cffaf4b..0000000 --- a/src/audacious/drct-api.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * drct-api.h - * Copyright 2010-2012 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -/* Do not include this file directly; use drct.h instead. */ - -/* CAUTION: These functions are not thread safe. */ - -/* --- PROGRAM CONTROL --- */ - -AUD_VFUNC0 (drct_quit) - -/* --- PLAYBACK CONTROL --- */ - -/* The strings returned by drct_get_filename() and drct_get_title() are pooled - * and must be freed with str_unref(). */ - -AUD_VFUNC0 (drct_play) -AUD_VFUNC0 (drct_play_pause) -AUD_VFUNC1 (drct_play_playlist, int, playlist) -AUD_VFUNC0 (drct_pause) -AUD_VFUNC0 (drct_stop) -AUD_FUNC0 (bool_t, drct_get_playing) -AUD_FUNC0 (bool_t, drct_get_ready) -AUD_FUNC0 (bool_t, drct_get_paused) -AUD_FUNC0 (char *, drct_get_filename) -AUD_FUNC0 (char *, drct_get_title) -AUD_VFUNC3 (drct_get_info, int *, bitrate, int *, samplerate, int *, channels) -AUD_FUNC0 (int, drct_get_time) -AUD_FUNC0 (int, drct_get_length) -AUD_VFUNC1 (drct_seek, int, time) - -/* "A-B repeat": when playback reaches point B, it returns to point A (where A - * and B are in milliseconds). The value -1 is interpreted as the beginning of - * the song (for A) or the end of the song (for B). A-B repeat is disabled - * entirely by setting both A and B to -1. */ -AUD_VFUNC2 (drct_set_ab_repeat, int, a, int, b) -AUD_VFUNC2 (drct_get_ab_repeat, int *, a, int *, b) - -/* --- VOLUME CONTROL --- */ - -AUD_VFUNC2 (drct_get_volume, int *, left, int *, right) -AUD_VFUNC2 (drct_set_volume, int, left, int, right) -AUD_VFUNC1 (drct_get_volume_main, int *, volume) -AUD_VFUNC1 (drct_set_volume_main, int, volume) -AUD_VFUNC1 (drct_get_volume_balance, int *, balance) -AUD_VFUNC1 (drct_set_volume_balance, int, balance) - -/* --- PLAYLIST CONTROL --- */ - -/* The indexes passed to drct_pl_add_list(), drct_pl_open_list(), and - * drct_pl_open_temp_list() contain pooled strings to which the caller gives up - * one reference. The indexes themselves are freed by these functions. */ - -AUD_VFUNC0 (drct_pl_next) -AUD_VFUNC0 (drct_pl_prev) - -AUD_VFUNC2 (drct_pl_add, const char *, filename, int, at) -AUD_VFUNC2 (drct_pl_add_list, Index *, filenames, int, at) -AUD_VFUNC1 (drct_pl_open, const char *, filename) -AUD_VFUNC1 (drct_pl_open_list, Index *, filenames) -AUD_VFUNC1 (drct_pl_open_temp, const char *, filename) -AUD_VFUNC1 (drct_pl_open_temp_list, Index *, filenames) diff --git a/src/audacious/drct.c b/src/audacious/drct.c deleted file mode 100644 index 5fbef8f..0000000 --- a/src/audacious/drct.c +++ /dev/null @@ -1,195 +0,0 @@ -/* - * drct.c - * Copyright 2009-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <libaudcore/hook.h> -#include <libaudcore/vfs.h> - -#include "drct.h" -#include "i18n.h" -#include "misc.h" -#include "playlist.h" - -/* --- PLAYBACK CONTROL --- */ - -void drct_play (void) -{ - if (drct_get_playing ()) - { - if (drct_get_paused ()) - drct_pause (); - else - { - int a, b; - drct_get_ab_repeat (& a, & b); - drct_seek (MAX (a, 0)); - } - } - else - { - int playlist = playlist_get_active (); - playlist_set_position (playlist, playlist_get_position (playlist)); - drct_play_playlist (playlist); - } -} - -void drct_play_pause (void) -{ - if (drct_get_playing ()) - drct_pause (); - else - drct_play (); -} - -void drct_play_playlist (int playlist) -{ - playlist_set_playing (playlist); - if (drct_get_paused ()) - drct_pause (); -} - -void drct_stop (void) -{ - playlist_set_playing (-1); -} - -/* --- VOLUME CONTROL --- */ - -void drct_get_volume_main (int * volume) -{ - int left, right; - drct_get_volume (& left, & right); - * volume = MAX (left, right); -} - -void drct_set_volume_main (int volume) -{ - int left, right, current; - drct_get_volume (& left, & right); - current = MAX (left, right); - - if (current > 0) - drct_set_volume (volume * left / current, volume * right / current); - else - drct_set_volume (volume, volume); -} - -void drct_get_volume_balance (int * balance) -{ - int left, right; - drct_get_volume (& left, & right); - - if (left == right) - * balance = 0; - else if (left > right) - * balance = -100 + right * 100 / left; - else - * balance = 100 - left * 100 / right; -} - -void drct_set_volume_balance (int balance) -{ - int left, right; - drct_get_volume_main (& left); - - if (balance < 0) - right = left * (100 + balance) / 100; - else - { - right = left; - left = right * (100 - balance) / 100; - } - - drct_set_volume (left, right); -} - -/* --- PLAYLIST CONTROL --- */ - -void drct_pl_next (void) -{ - int playlist = playlist_get_playing (); - if (playlist < 0) - playlist = playlist_get_active (); - - playlist_next_song (playlist, get_bool (NULL, "repeat")); -} - -void drct_pl_prev (void) -{ - int playlist = playlist_get_playing (); - if (playlist < 0) - playlist = playlist_get_active (); - - playlist_prev_song (playlist); -} - -static void add_list (Index * filenames, int at, bool_t to_temp, bool_t play) -{ - if (to_temp) - playlist_set_active (playlist_get_temporary ()); - - int playlist = playlist_get_active (); - - /* queue the new entries before deleting the old ones */ - /* this is to avoid triggering the --quit-after-play condition */ - playlist_entry_insert_batch (playlist, at, filenames, NULL, play); - - if (play) - { - if (get_bool (NULL, "clear_playlist")) - playlist_entry_delete (playlist, 0, playlist_entry_count (playlist)); - else - playlist_queue_delete (playlist, 0, playlist_queue_count (playlist)); - } -} - -void drct_pl_add (const char * filename, int at) -{ - Index * filenames = index_new (); - index_insert (filenames, -1, str_get (filename)); - add_list (filenames, at, FALSE, FALSE); -} - -void drct_pl_add_list (Index * filenames, int at) -{ - add_list (filenames, at, FALSE, FALSE); -} - -void drct_pl_open (const char * filename) -{ - Index * filenames = index_new (); - index_insert (filenames, -1, str_get (filename)); - add_list (filenames, -1, get_bool (NULL, "open_to_temporary"), TRUE); -} - -void drct_pl_open_list (Index * filenames) -{ - add_list (filenames, -1, get_bool (NULL, "open_to_temporary"), TRUE); -} - -void drct_pl_open_temp (const char * filename) -{ - Index * filenames = index_new (); - index_insert (filenames, -1, str_get (filename)); - add_list (filenames, -1, TRUE, TRUE); -} - -void drct_pl_open_temp_list (Index * filenames) -{ - add_list (filenames, -1, TRUE, TRUE); -} diff --git a/src/audacious/drct.h b/src/audacious/drct.h deleted file mode 100644 index 836a9bc..0000000 --- a/src/audacious/drct.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * drct.h - * Copyright 2010 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_DRCT_H -#define AUDACIOUS_DRCT_H - -#include <audacious/api.h> -#include <libaudcore/core.h> -#include <libaudcore/index.h> - -#define AUD_API_NAME DRCTAPI -#define AUD_API_SYMBOL drct_api - -#ifdef _AUDACIOUS_CORE - -#include "api-local-begin.h" -#include "drct-api.h" -#include "api-local-end.h" - -#else - -#include <audacious/api-define-begin.h> -#include <audacious/drct-api.h> -#include <audacious/api-define-end.h> - -#include <audacious/api-alias-begin.h> -#include <audacious/drct-api.h> -#include <audacious/api-alias-end.h> - -#endif - -#undef AUD_API_NAME -#undef AUD_API_SYMBOL - -#endif - -#ifdef AUD_API_DECLARE - -#define AUD_API_NAME DRCTAPI -#define AUD_API_SYMBOL drct_api - -#include "api-define-begin.h" -#include "drct-api.h" -#include "api-define-end.h" - -#include "api-declare-begin.h" -#include "drct-api.h" -#include "api-declare-end.h" - -#undef AUD_API_NAME -#undef AUD_API_SYMBOL - -#endif diff --git a/src/audacious/effect.c b/src/audacious/effect.c deleted file mode 100644 index 7d34368..0000000 --- a/src/audacious/effect.c +++ /dev/null @@ -1,260 +0,0 @@ -/* - * effect.c - * Copyright 2010-2012 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <pthread.h> - -#include "debug.h" -#include "drct.h" -#include "effect.h" -#include "misc.h" -#include "plugin.h" -#include "plugins.h" - -typedef struct { - PluginHandle * plugin; - EffectPlugin * header; - int channels_returned, rate_returned; - bool_t remove_flag; -} RunningEffect; - -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -static GList * running_effects = NULL; /* (RunningEffect *) */ -static int input_channels, input_rate; - -typedef struct { - int * channels, * rate; -} EffectStartState; - -static bool_t effect_start_cb (PluginHandle * plugin, EffectStartState * state) -{ - AUDDBG ("Starting %s at %d channels, %d Hz.\n", plugin_get_name (plugin), - * state->channels, * state->rate); - EffectPlugin * header = plugin_get_header (plugin); - g_return_val_if_fail (header != NULL, TRUE); - header->start (state->channels, state->rate); - - RunningEffect * effect = g_slice_new (RunningEffect); - effect->plugin = plugin; - effect->header = header; - effect->channels_returned = * state->channels; - effect->rate_returned = * state->rate; - effect->remove_flag = FALSE; - - running_effects = g_list_prepend (running_effects, effect); - return TRUE; -} - -void effect_start (int * channels, int * rate) -{ - pthread_mutex_lock (& mutex); - - AUDDBG ("Starting effects.\n"); - - for (GList * node = running_effects; node; node = node->next) - g_slice_free (RunningEffect, node->data); - - g_list_free (running_effects); - running_effects = NULL; - - input_channels = * channels; - input_rate = * rate; - - EffectStartState state = {channels, rate}; - plugin_for_enabled (PLUGIN_TYPE_EFFECT, (PluginForEachFunc) effect_start_cb, - & state); - running_effects = g_list_reverse (running_effects); - - pthread_mutex_unlock (& mutex); -} - -typedef struct { - float * * data; - int * samples; -} EffectProcessState; - -static void effect_process_cb (RunningEffect * effect, EffectProcessState * - state) -{ - if (effect->remove_flag) - { - /* call finish twice to completely drain buffers */ - effect->header->finish (state->data, state->samples); - effect->header->finish (state->data, state->samples); - - running_effects = g_list_remove (running_effects, effect); - g_slice_free (RunningEffect, effect); - } - else - effect->header->process (state->data, state->samples); -} - -void effect_process (float * * data, int * samples) -{ - pthread_mutex_lock (& mutex); - - EffectProcessState state = {data, samples}; - g_list_foreach (running_effects, (GFunc) effect_process_cb, & state); - - pthread_mutex_unlock (& mutex); -} - -void effect_flush (void) -{ - pthread_mutex_lock (& mutex); - - for (GList * node = running_effects; node != NULL; node = node->next) - { - if (PLUGIN_HAS_FUNC (((RunningEffect *) node->data)->header, flush)) - ((RunningEffect *) node->data)->header->flush (); - } - - pthread_mutex_unlock (& mutex); -} - -void effect_finish (float * * data, int * samples) -{ - pthread_mutex_lock (& mutex); - - for (GList * node = running_effects; node != NULL; node = node->next) - ((RunningEffect *) node->data)->header->finish (data, samples); - - pthread_mutex_unlock (& mutex); -} - -int effect_adjust_delay (int delay) -{ - pthread_mutex_lock (& mutex); - - for (GList * node = g_list_last (running_effects); node != NULL; node = node->prev) - { - if (PLUGIN_HAS_FUNC (((RunningEffect *) node->data)->header, adjust_delay)) - delay = ((RunningEffect *) node->data)->header->adjust_delay (delay); - } - - pthread_mutex_unlock (& mutex); - return delay; -} - -static int effect_find_cb (RunningEffect * effect, PluginHandle * plugin) -{ - return (effect->plugin == plugin) ? 0 : -1; -} - -static int effect_compare (RunningEffect * a, RunningEffect * b) -{ - return plugin_compare (a->plugin, b->plugin); -} - -static void effect_insert (PluginHandle * plugin, EffectPlugin * header) -{ - GList * node = g_list_find_custom (running_effects, plugin, (GCompareFunc) effect_find_cb); - - if (node) - { - ((RunningEffect *) node->data)->remove_flag = FALSE; - return; - } - - AUDDBG ("Adding %s without reset.\n", plugin_get_name (plugin)); - RunningEffect * effect = g_slice_new (RunningEffect); - effect->plugin = plugin; - effect->header = header; - effect->remove_flag = FALSE; - - running_effects = g_list_insert_sorted (running_effects, effect, - (GCompareFunc) effect_compare); - node = g_list_find (running_effects, effect); - - int channels, rate; - if (node->prev != NULL) - { - RunningEffect * prev = node->prev->data; - AUDDBG ("Added %s after %s.\n", plugin_get_name (plugin), - plugin_get_name (prev->plugin)); - channels = prev->channels_returned; - rate = prev->rate_returned; - } - else - { - AUDDBG ("Added %s as first effect.\n", plugin_get_name (plugin)); - channels = input_channels; - rate = input_rate; - } - - AUDDBG ("Starting %s at %d channels, %d Hz.\n", plugin_get_name (plugin), - channels, rate); - header->start (& channels, & rate); - effect->channels_returned = channels; - effect->rate_returned = rate; -} - -static void effect_remove (PluginHandle * plugin) -{ - GList * node = g_list_find_custom (running_effects, plugin, (GCompareFunc) - effect_find_cb); - if (node == NULL) - return; - - AUDDBG ("Removing %s without reset.\n", plugin_get_name (plugin)); - ((RunningEffect *) node->data)->remove_flag = TRUE; -} - -static void effect_enable (PluginHandle * plugin, EffectPlugin * ep, bool_t - enable) -{ - if (ep->preserves_format) - { - pthread_mutex_lock (& mutex); - - if (enable) - effect_insert (plugin, ep); - else - effect_remove (plugin); - - pthread_mutex_unlock (& mutex); - } - else - { - AUDDBG ("Reset to add/remove %s.\n", plugin_get_name (plugin)); - output_reset (OUTPUT_RESET_EFFECTS_ONLY); - } -} - -bool_t effect_plugin_start (PluginHandle * plugin) -{ - if (drct_get_playing ()) - { - EffectPlugin * ep = plugin_get_header (plugin); - g_return_val_if_fail (ep != NULL, FALSE); - effect_enable (plugin, ep, TRUE); - } - - return TRUE; -} - -void effect_plugin_stop (PluginHandle * plugin) -{ - if (drct_get_playing ()) - { - EffectPlugin * ep = plugin_get_header (plugin); - g_return_if_fail (ep != NULL); - effect_enable (plugin, ep, FALSE); - } -} diff --git a/src/audacious/effect.h b/src/audacious/effect.h deleted file mode 100644 index 2a0add9..0000000 --- a/src/audacious/effect.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * effect.h - * Copyright 2010-2012 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_EFFECT_H -#define AUDACIOUS_EFFECT_H - -#include <libaudcore/core.h> - -#include "types.h" - -void effect_start (int * channels, int * rate); -void effect_process (float * * data, int * samples); -void effect_flush (void); -void effect_finish (float * * data, int * samples); -int effect_adjust_delay (int delay); - -bool_t effect_plugin_start (PluginHandle * plugin); -void effect_plugin_stop (PluginHandle * plugin); - -#endif diff --git a/src/audacious/equalizer.h b/src/audacious/equalizer.h deleted file mode 100644 index 3acb472..0000000 --- a/src/audacious/equalizer.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * equalizer.h - * Copyright 2010 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_EQUALIZER_H -#define AUDACIOUS_EQUALIZER_H - -void eq_init (void); -void eq_cleanup (void); -void eq_set_format (int new_channels, int new_rate); -void eq_filter (float * data, int samples); - -#endif diff --git a/src/audacious/equalizer_preset.c b/src/audacious/equalizer_preset.c deleted file mode 100644 index 6c70eb8..0000000 --- a/src/audacious/equalizer_preset.c +++ /dev/null @@ -1,242 +0,0 @@ -/* - * equalizer_preset.c - * Copyright 2003-2013 Eugene Zagidullin, William Pitcock, John Lindgren, and - * Thomas Lange - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <math.h> -#include <string.h> - -#include <libaudcore/audstrings.h> - -#include "debug.h" -#include "i18n.h" -#include "misc.h" - -EqualizerPreset * equalizer_preset_new (const char * name) -{ - EqualizerPreset * preset = g_slice_new0 (EqualizerPreset); - preset->name = str_get (name); - return preset; -} - -void equalizer_preset_free (EqualizerPreset * preset) -{ - str_unref (preset->name); - g_slice_free (EqualizerPreset, preset); -} - -Index * equalizer_read_presets (const char * basename) -{ - GKeyFile * rcfile = g_key_file_new (); - - char * filename = filename_build (get_path (AUD_PATH_USER_DIR), basename); - - if (! g_key_file_load_from_file (rcfile, filename, G_KEY_FILE_NONE, NULL)) - { - str_unref (filename); - filename = filename_build (get_path (AUD_PATH_DATA_DIR), basename); - - if (! g_key_file_load_from_file (rcfile, filename, G_KEY_FILE_NONE, NULL)) - { - str_unref (filename); - g_key_file_free (rcfile); - return NULL; - } - } - - str_unref (filename); - - Index * list = index_new (); - - for (int p = 0;; p ++) - { - SPRINTF (section, "Preset%d", p); - - char * name = g_key_file_get_string (rcfile, "Presets", section, NULL); - if (! name) - break; - - EqualizerPreset * preset = equalizer_preset_new (name); - preset->preamp = g_key_file_get_double (rcfile, name, "Preamp", NULL); - - for (int i = 0; i < AUD_EQUALIZER_NBANDS; i++) - { - SPRINTF (band, "Band%d", i); - preset->bands[i] = g_key_file_get_double (rcfile, name, band, NULL); - } - - index_insert (list, -1, preset); - - g_free (name); - } - - g_key_file_free (rcfile); - - return list; -} - -bool_t equalizer_write_presets (Index * list, const char * basename) -{ - GKeyFile * rcfile = g_key_file_new (); - - for (int p = 0; p < index_count (list); p ++) - { - EqualizerPreset * preset = index_get (list, p); - - SPRINTF (tmp, "Preset%d", p); - g_key_file_set_string (rcfile, "Presets", tmp, preset->name); - g_key_file_set_double (rcfile, preset->name, "Preamp", preset->preamp); - - for (int i = 0; i < AUD_EQUALIZER_NBANDS; i ++) - { - SPRINTF (tmp, "Band%d", i); - g_key_file_set_double (rcfile, preset->name, tmp, preset->bands[i]); - } - } - - size_t len; - char * data = g_key_file_to_data (rcfile, & len, NULL); - - char * filename = filename_build (get_path (AUD_PATH_USER_DIR), basename); - bool_t success = g_file_set_contents (filename, data, len, NULL); - str_unref (filename); - - g_key_file_free (rcfile); - g_free (data); - - return success; -} - -/* Note: Winamp 2.x had a +/- 20 dB range. - * Winamp 5.x had a +/- 12 dB range, which we use here. */ -#define FROM_WINAMP_VAL(x) ((31.5 - (x)) * (12.0 / 31.5)) -#define TO_WINAMP_VAL(x) (round (31.5 - (x) * (31.5 / 12.0))) - -Index * import_winamp_presets (VFSFile * file) -{ - char header[31]; - char bands[11]; - char preset_name[181]; - - if (vfs_fread (header, 1, sizeof header, file) != sizeof header || - strncmp (header, "Winamp EQ library file v1.1", 27)) - return NULL; - - Index * list = index_new (); - - while (vfs_fread (preset_name, 1, 180, file) == 180) - { - preset_name[180] = 0; /* protect against buffer overflow */ - - if (vfs_fseek (file, 77, SEEK_CUR)) /* unknown crap --asphyx */ - break; - - if (vfs_fread (bands, 1, 11, file) != 11) - break; - - EqualizerPreset * preset = equalizer_preset_new (preset_name); - preset->preamp = FROM_WINAMP_VAL (bands[10]); - - for (int i = 0; i < AUD_EQUALIZER_NBANDS; i ++) - preset->bands[i] = FROM_WINAMP_VAL (bands[i]); - - index_insert (list, -1, preset); - } - - return list; -} - -bool_t export_winamp_preset (EqualizerPreset * preset, VFSFile * file) -{ - char name[257]; - char bands[11]; - - if (vfs_fwrite ("Winamp EQ library file v1.1\x1a!--", 1, 31, file) != 31) - return FALSE; - - strncpy (name, preset->name, 257); - - if (vfs_fwrite (name, 1, 257, file) != 257) - return FALSE; - - for (int i = 0; i < AUD_EQUALIZER_NBANDS; i ++) - bands[i] = TO_WINAMP_VAL (preset->bands[i]); - - bands[10] = TO_WINAMP_VAL (preset->preamp); - - if (vfs_fwrite (bands, 1, 11, file) != 11) - return FALSE; - - return TRUE; -} - -bool_t save_preset_file (EqualizerPreset * preset, const char * filename) -{ - GKeyFile * rcfile = g_key_file_new (); - - g_key_file_set_double (rcfile, "Equalizer preset", "Preamp", preset->preamp); - - for (int i = 0; i < AUD_EQUALIZER_NBANDS; i ++) - { - SPRINTF (tmp, "Band%d", i); - g_key_file_set_double (rcfile, "Equalizer preset", tmp, preset->bands[i]); - } - - size_t len; - char * data = g_key_file_to_data (rcfile, & len, NULL); - - VFSFile * file = vfs_fopen (filename, "w"); - bool_t success = FALSE; - - if (file) - { - success = (vfs_fwrite (data, 1, len, file) == len); - vfs_fclose (file); - } - - g_key_file_free (rcfile); - g_free (data); - - return success; -} - -EqualizerPreset * load_preset_file (const char * filename) -{ - GKeyFile * rcfile = g_key_file_new (); - - if (! g_key_file_load_from_file (rcfile, filename, G_KEY_FILE_NONE, NULL)) - { - g_key_file_free (rcfile); - return NULL; - } - - EqualizerPreset * preset = equalizer_preset_new (""); - - preset->preamp = g_key_file_get_double (rcfile, "Equalizer preset", "Preamp", NULL); - - for (int i = 0; i < AUD_EQUALIZER_NBANDS; i ++) - { - SPRINTF (tmp, "Band%d", i); - preset->bands[i] = g_key_file_get_double (rcfile, "Equalizer preset", tmp, NULL); - } - - g_key_file_free (rcfile); - - return preset; -} diff --git a/src/audacious/fft.h b/src/audacious/fft.h deleted file mode 100644 index bb8cba3..0000000 --- a/src/audacious/fft.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * fft.h - * Copyright 2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_FFT_H -#define AUDACIOUS_FFT_H - -void calc_freq (const float data[512], float freq[256]); - -#endif diff --git a/src/audacious/general.c b/src/audacious/general.c deleted file mode 100644 index e0b9ea2..0000000 --- a/src/audacious/general.c +++ /dev/null @@ -1,160 +0,0 @@ -/* - * general.c - * Copyright 2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <gtk/gtk.h> - -#include "debug.h" -#include "general.h" -#include "interface.h" -#include "plugin.h" -#include "plugins.h" -#include "ui_preferences.h" - -typedef struct { - PluginHandle * plugin; - GeneralPlugin * gp; - GtkWidget * widget; -} LoadedGeneral; - -static int running = FALSE; -static GList * loaded_general_plugins = NULL; - -static int general_find_cb (LoadedGeneral * general, PluginHandle * plugin) -{ - return (general->plugin == plugin) ? 0 : -1; -} - -static void general_load (PluginHandle * plugin) -{ - GList * node = g_list_find_custom (loaded_general_plugins, plugin, - (GCompareFunc) general_find_cb); - if (node != NULL) - return; - - AUDDBG ("Loading %s.\n", plugin_get_name (plugin)); - GeneralPlugin * gp = plugin_get_header (plugin); - g_return_if_fail (gp != NULL); - - LoadedGeneral * general = g_slice_new (LoadedGeneral); - general->plugin = plugin; - general->gp = gp; - general->widget = NULL; - - if (gp->get_widget != NULL) - general->widget = gp->get_widget (); - - if (general->widget != NULL) - { - AUDDBG ("Adding %s to interface.\n", plugin_get_name (plugin)); - g_signal_connect (general->widget, "destroy", (GCallback) - gtk_widget_destroyed, & general->widget); - interface_add_plugin_widget (plugin, general->widget); - } - - loaded_general_plugins = g_list_prepend (loaded_general_plugins, general); -} - -static void general_unload (PluginHandle * plugin) -{ - GList * node = g_list_find_custom (loaded_general_plugins, plugin, - (GCompareFunc) general_find_cb); - if (node == NULL) - return; - - AUDDBG ("Unloading %s.\n", plugin_get_name (plugin)); - LoadedGeneral * general = node->data; - loaded_general_plugins = g_list_delete_link (loaded_general_plugins, node); - - if (general->widget != NULL) - { - AUDDBG ("Removing %s from interface.\n", plugin_get_name (plugin)); - interface_remove_plugin_widget (plugin, general->widget); - g_return_if_fail (general->widget == NULL); /* not destroyed? */ - } - - g_slice_free (LoadedGeneral, general); -} - -static bool_t general_init_cb (PluginHandle * plugin) -{ - general_load (plugin); - return TRUE; -} - -void general_init (void) -{ - g_return_if_fail (! running); - running = TRUE; - - plugin_for_enabled (PLUGIN_TYPE_GENERAL, (PluginForEachFunc) - general_init_cb, NULL); -} - -static void general_cleanup_cb (LoadedGeneral * general) -{ - general_unload (general->plugin); -} - -void general_cleanup (void) -{ - g_return_if_fail (running); - running = FALSE; - - g_list_foreach (loaded_general_plugins, (GFunc) general_cleanup_cb, NULL); -} - -bool_t general_plugin_start (PluginHandle * plugin) -{ - GeneralPlugin * gp = plugin_get_header (plugin); - g_return_val_if_fail (gp != NULL, FALSE); - - if (gp->init != NULL && ! gp->init ()) - return FALSE; - - if (running) - general_load (plugin); - - return TRUE; -} - -void general_plugin_stop (PluginHandle * plugin) -{ - GeneralPlugin * gp = plugin_get_header (plugin); - g_return_if_fail (gp != NULL); - - if (running) - general_unload (plugin); - - if (gp->cleanup != NULL) - gp->cleanup (); -} - -PluginHandle * general_plugin_by_widget (/* GtkWidget * */ void * widget) -{ - g_return_val_if_fail (widget, NULL); - - for (GList * node = loaded_general_plugins; node; node = node->next) - { - LoadedGeneral * general = node->data; - if (general->widget == widget) - return general->plugin; - } - - return NULL; -} diff --git a/src/audacious/history.c b/src/audacious/history.c deleted file mode 100644 index 4dde368..0000000 --- a/src/audacious/history.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * history.c - * Copyright 2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <stdio.h> -#include <string.h> - -#include <libaudcore/hook.h> - -#include "main.h" -#include "misc.h" - -#define MAX_ENTRIES 30 - -static GQueue history = G_QUEUE_INIT; -static bool_t loaded, modified; - -static void history_save (void) -{ - if (! modified) - return; - - GList * node = history.head; - for (int i = 0; i < MAX_ENTRIES; i ++) - { - if (! node) - break; - - char name[32]; - snprintf (name, sizeof name, "entry%d", i); - set_str ("history", name, node->data); - - node = node->next; - } - - modified = FALSE; -} - -static void history_load (void) -{ - if (loaded) - return; - - for (int i = 0; ; i ++) - { - char name[32]; - snprintf (name, sizeof name, "entry%d", i); - char * path = get_str ("history", name); - - if (! path[0]) - { - str_unref (path); - break; - } - - g_queue_push_tail (& history, path); - } - - loaded = TRUE; - hook_associate ("config save", (HookFunction) history_save, NULL); -} - -void history_cleanup (void) -{ - if (! loaded) - return; - - hook_dissociate ("config save", (HookFunction) history_save); - - g_queue_foreach (& history, (GFunc) str_unref, NULL); - g_queue_clear (& history); - - loaded = FALSE; - modified = FALSE; -} - -const char * history_get (int entry) -{ - history_load (); - return g_queue_peek_nth (& history, entry); -} - -void history_add (const char * path) -{ - history_load (); - - GList * next; - for (GList * node = history.head; node; node = next) - { - next = node->next; - if (! strcmp (node->data, path)) - { - str_unref (node->data); - g_queue_delete_link (& history, node); - } - } - - g_queue_push_head (& history, str_get (path)); - modified = TRUE; -} diff --git a/src/audacious/images/about-logo.png b/src/audacious/images/about-logo.png Binary files differdeleted file mode 100644 index 32fb69f..0000000 --- a/src/audacious/images/about-logo.png +++ /dev/null diff --git a/src/audacious/images/album.png b/src/audacious/images/album.png Binary files differdeleted file mode 100644 index a47bc7d..0000000 --- a/src/audacious/images/album.png +++ /dev/null diff --git a/src/audacious/images/appearance.png b/src/audacious/images/appearance.png Binary files differdeleted file mode 100644 index f73239a..0000000 --- a/src/audacious/images/appearance.png +++ /dev/null diff --git a/src/audacious/images/audio.png b/src/audacious/images/audio.png Binary files differdeleted file mode 100644 index a41d51a..0000000 --- a/src/audacious/images/audio.png +++ /dev/null diff --git a/src/audacious/images/connectivity.png b/src/audacious/images/connectivity.png Binary files differdeleted file mode 100644 index 2d80e79..0000000 --- a/src/audacious/images/connectivity.png +++ /dev/null diff --git a/src/audacious/images/info.png b/src/audacious/images/info.png Binary files differdeleted file mode 100644 index 91e2c17..0000000 --- a/src/audacious/images/info.png +++ /dev/null diff --git a/src/audacious/images/playlist.png b/src/audacious/images/playlist.png Binary files differdeleted file mode 100644 index 2574ef0..0000000 --- a/src/audacious/images/playlist.png +++ /dev/null diff --git a/src/audacious/images/plugins.png b/src/audacious/images/plugins.png Binary files differdeleted file mode 100644 index da9f9e1..0000000 --- a/src/audacious/images/plugins.png +++ /dev/null diff --git a/src/audacious/input-api.h b/src/audacious/input-api.h deleted file mode 100644 index 7c49b0e..0000000 --- a/src/audacious/input-api.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * input-api.h - * Copyright 2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -/* Do not include this file directly; use input.h instead. */ - -/* These functions are to be used only from the play() function of an input plugin. */ - -/* Prepares the output system for playback in the specified format. Returns - * TRUE on success, FALSE if the selected format is not supported. */ -AUD_FUNC3 (bool_t, input_open_audio, int, format, int, rate, int, channels) - -/* Informs the output system of replay gain values for the current song so - * that volume levels can be adjusted accordingly, if the user so desires. - * This may be called at any time during playback should the values change. */ -AUD_VFUNC1 (input_set_gain, const ReplayGainInfo *, info) - -/* Passes audio data to the output system for playback. The data must be in - * the format passed to open_audio, and the length (in bytes) must be an - * integral number of frames. This function blocks until all the data has - * been written (though it may not yet be heard by the user). */ -AUD_VFUNC2 (input_write_audio, void *, data, int, length) - -/* Returns the time counter. Note that this represents the amount of audio - * data passed to the output system, not the amount actually heard by the - * user. */ -AUD_FUNC0 (int, input_written_time) - -/* Returns a reference to the current tuple for the stream. */ -AUD_FUNC0 (Tuple *, input_get_tuple) - -/* Updates the tuple for the stream. The caller gives up ownership of one - * reference to the tuple. */ -AUD_VFUNC1 (input_set_tuple, Tuple *, tuple) - -/* Updates the displayed bitrate, in bits per second. */ -AUD_VFUNC1 (input_set_bitrate, int, bitrate) - -/* Checks whether playback is to be stopped. The play() function should poll - * check_stop() periodically and return as soon as check_stop() returns TRUE. */ -AUD_FUNC0 (bool_t, input_check_stop) - -/* Checks whether a seek has been requested. If so, discards any buffered audio - * and returns the position to seek to, in milliseconds. Otherwise, returns -1. */ -AUD_FUNC0 (int, input_check_seek) diff --git a/src/audacious/input.h b/src/audacious/input.h deleted file mode 100644 index 4935483..0000000 --- a/src/audacious/input.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * input.h - * Copyright 2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_INPUT_H -#define AUDACIOUS_INPUT_H - -#include <audacious/api.h> -#include <audacious/types.h> -#include <libaudcore/tuple.h> - -#define AUD_API_NAME InputAPI -#define AUD_API_SYMBOL input_api - -#ifdef _AUDACIOUS_CORE - -#include "api-local-begin.h" -#include "input-api.h" -#include "api-local-end.h" - -#else - -#include <audacious/api-define-begin.h> -#include <audacious/input-api.h> -#include <audacious/api-define-end.h> - -#include <audacious/api-alias-begin.h> -#include <audacious/input-api.h> -#include <audacious/api-alias-end.h> - -#endif - -#undef AUD_API_NAME -#undef AUD_API_SYMBOL - -#endif - -#ifdef AUD_API_DECLARE - -#define AUD_API_NAME InputAPI -#define AUD_API_SYMBOL input_api - -#include "api-define-begin.h" -#include "input-api.h" -#include "api-define-end.h" - -#include "api-declare-begin.h" -#include "input-api.h" -#include "api-declare-end.h" - -#undef AUD_API_NAME -#undef AUD_API_SYMBOL - -#endif diff --git a/src/audacious/interface.c b/src/audacious/interface.c deleted file mode 100644 index 5c8045f..0000000 --- a/src/audacious/interface.c +++ /dev/null @@ -1,216 +0,0 @@ -/* - * interface.c - * Copyright 2010-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <gtk/gtk.h> -#include <pthread.h> - -#include <libaudcore/hook.h> -#include <libaudgui/libaudgui-gtk.h> - -#include "debug.h" -#include "general.h" -#include "i18n.h" -#include "interface.h" -#include "misc.h" -#include "plugin.h" -#include "plugins.h" -#include "visualization.h" - -static IfacePlugin * current_interface = NULL; - -static pthread_mutex_t error_mutex = PTHREAD_MUTEX_INITIALIZER; -static GQueue error_queue = G_QUEUE_INIT; -static int error_source; -static GtkWidget * error_win; - -bool_t interface_load (PluginHandle * plugin) -{ - IfacePlugin * i = plugin_get_header (plugin); - g_return_val_if_fail (i, FALSE); - - if (PLUGIN_HAS_FUNC (i, init) && ! i->init ()) - return FALSE; - - current_interface = i; - return TRUE; -} - -void interface_unload (void) -{ - g_return_if_fail (current_interface); - - if (PLUGIN_HAS_FUNC (current_interface, cleanup)) - current_interface->cleanup (); - - current_interface = NULL; -} - -void interface_show (bool_t show) -{ - g_return_if_fail (current_interface); - - set_bool (NULL, "show_interface", show); - - if (PLUGIN_HAS_FUNC (current_interface, show)) - current_interface->show (show); -} - -bool_t interface_is_shown (void) -{ - g_return_val_if_fail (current_interface, FALSE); - - return get_bool (NULL, "show_interface"); -} - -static bool_t error_idle_func (void * unused) -{ - pthread_mutex_lock (& error_mutex); - - char * message; - while ((message = g_queue_pop_head (& error_queue))) - { - pthread_mutex_unlock (& error_mutex); - - if (headless_mode ()) - fprintf (stderr, "ERROR: %s\n", message); - else - audgui_simple_message (& error_win, GTK_MESSAGE_ERROR, _("Error"), message); - - str_unref (message); - - pthread_mutex_lock (& error_mutex); - } - - error_source = 0; - - pthread_mutex_unlock (& error_mutex); - return FALSE; -} - -void interface_show_error (const char * message) -{ - pthread_mutex_lock (& error_mutex); - - g_queue_push_tail (& error_queue, str_get (message)); - - if (! error_source) - error_source = g_idle_add (error_idle_func, NULL); - - pthread_mutex_unlock (& error_mutex); -} - -static bool_t delete_cb (GtkWidget * window, GdkEvent * event, PluginHandle * - plugin) -{ - plugin_enable (plugin, FALSE); - return TRUE; -} - -void interface_add_plugin_widget (PluginHandle * plugin, GtkWidget * widget) -{ - g_return_if_fail (current_interface); - - if (PLUGIN_HAS_FUNC (current_interface, run_gtk_plugin)) - current_interface->run_gtk_plugin (widget, plugin_get_name (plugin)); - else - { - GtkWidget * window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_window_set_title ((GtkWindow *) window, plugin_get_name (plugin)); - gtk_window_set_default_size ((GtkWindow *) window, 300, 200); - gtk_window_set_has_resize_grip ((GtkWindow *) window, FALSE); - gtk_container_add ((GtkContainer *) window, widget); - g_signal_connect (window, "delete-event", (GCallback) delete_cb, plugin); - gtk_widget_show_all (window); - } -} - -void interface_remove_plugin_widget (PluginHandle * plugin, GtkWidget * widget) -{ - g_return_if_fail (current_interface); - - if (PLUGIN_HAS_FUNC (current_interface, stop_gtk_plugin)) - current_interface->stop_gtk_plugin (widget); - else - gtk_widget_destroy (gtk_widget_get_parent (widget)); -} - -static bool_t probe_cb (PluginHandle * p, PluginHandle * * pp) -{ - * pp = p; - return FALSE; -} - -PluginHandle * iface_plugin_probe (void) -{ - PluginHandle * p = NULL; - plugin_for_each (PLUGIN_TYPE_IFACE, (PluginForEachFunc) probe_cb, & p); - return p; -} - -static PluginHandle * current_plugin = NULL; - -PluginHandle * iface_plugin_get_current (void) -{ - return current_plugin; -} - -bool_t iface_plugin_set_current (PluginHandle * plugin) -{ - hook_call ("config save", NULL); /* tell interface to save layout */ - - if (current_plugin != NULL) - { - if (get_bool (NULL, "show_interface") && current_interface && - PLUGIN_HAS_FUNC (current_interface, show)) - current_interface->show (FALSE); - - AUDDBG ("Unloading plugin widgets.\n"); - general_cleanup (); - - AUDDBG ("Unloading visualizers.\n"); - vis_cleanup (); - - AUDDBG ("Unloading %s.\n", plugin_get_name (current_plugin)); - interface_unload (); - - current_plugin = NULL; - } - - if (plugin != NULL) - { - AUDDBG ("Loading %s.\n", plugin_get_name (plugin)); - - if (! interface_load (plugin)) - return FALSE; - - current_plugin = plugin; - - AUDDBG ("Loading visualizers.\n"); - vis_init (); - - AUDDBG ("Loading plugin widgets.\n"); - general_init (); - - if (get_bool (NULL, "show_interface") && current_interface && - PLUGIN_HAS_FUNC (current_interface, show)) - current_interface->show (TRUE); - } - - return TRUE; -} diff --git a/src/audacious/interface.h b/src/audacious/interface.h deleted file mode 100644 index 8bc5727..0000000 --- a/src/audacious/interface.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * interface.h - * Copyright 2010-2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef __AUDACIOUS2_INTERFACE_H__ -#define __AUDACIOUS2_INTERFACE_H__ - -#include <gtk/gtk.h> -#include <audacious/plugins.h> - -bool_t interface_load (PluginHandle * plugin); -void interface_unload (void); - -void interface_add_plugin_widget (PluginHandle * plugin, GtkWidget * widget); -void interface_remove_plugin_widget (PluginHandle * plugin, GtkWidget * widget); - -PluginHandle * iface_plugin_probe (void); -PluginHandle * iface_plugin_get_current (void); -bool_t iface_plugin_set_current (PluginHandle * plugin); - -#endif diff --git a/src/audacious/main.c b/src/audacious/main.c deleted file mode 100644 index 8440969..0000000 --- a/src/audacious/main.c +++ /dev/null @@ -1,634 +0,0 @@ -/* - * main.c - * Copyright 2007-2013 William Pitcock and John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <errno.h> -#include <fcntl.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <unistd.h> -#include <locale.h> - -#include <gtk/gtk.h> - -#include <libaudcore/audstrings.h> -#include <libaudcore/hook.h> -#include <libaudgui/libaudgui.h> -#include <libaudtag/audtag.h> - -#ifdef USE_DBUS -#include "aud-dbus.h" -#endif - -#include "debug.h" -#include "drct.h" -#include "equalizer.h" -#include "i18n.h" -#include "interface.h" -#include "main.h" -#include "misc.h" -#include "playlist.h" -#include "plugins.h" -#include "scanner.h" -#include "util.h" - -#define AUTOSAVE_INTERVAL 300 /* seconds */ - -static struct { - bool_t help, version; - bool_t play, pause, play_pause, stop, fwd, rew; - bool_t enqueue, enqueue_to_temp; - bool_t mainwin, show_jump_box; - bool_t headless, quit_after_play; - bool_t verbose; -} options; - -static Index * filenames; - -static const struct { - const char * long_arg; - char short_arg; - bool_t * value; - const char * desc; -} arg_map[] = { - {"help", 'h', & options.help, N_("Show command-line help")}, - {"version", 'v', & options.version, N_("Show version")}, - {"play", 'p', & options.play, N_("Start playback")}, - {"pause", 'u', & options.pause, N_("Pause playback")}, - {"play-pause", 't', & options.play_pause, N_("Pause if playing, play otherwise")}, - {"stop", 's', & options.stop, N_("Stop playback")}, - {"rew", 'r', & options.rew, N_("Skip to previous song")}, - {"fwd", 'f', & options.fwd, N_("Skip to next song")}, - {"enqueue", 'e', & options.enqueue, N_("Add files to the playlist")}, - {"enqueue-to-temp", 'E', & options.enqueue_to_temp, N_("Add files to a temporary playlist")}, - {"show-main-window", 'm', & options.mainwin, N_("Display the main window")}, - {"show-jump-box", 'j', & options.show_jump_box, N_("Display the jump-to-song window")}, - {"headless", 'H', & options.headless, N_("Start without a graphical interface")}, - {"quit-after-play", 'q', & options.quit_after_play, N_("Quit on playback stop")}, - {"verbose", 'V', & options.verbose, N_("Print debugging messages")}, -}; - -static char * aud_paths[AUD_PATH_COUNT]; - -static void make_dirs(void) -{ -#ifdef S_IRGRP - const mode_t mode755 = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; -#else - const mode_t mode755 = S_IRWXU; -#endif - - make_directory(aud_paths[AUD_PATH_USER_DIR], mode755); - make_directory(aud_paths[AUD_PATH_PLAYLISTS_DIR], mode755); -} - -static char * relocate_path (const char * path, const char * old, const char * new) -{ - int oldlen = strlen (old); - int newlen = strlen (new); - - if (oldlen && old[oldlen - 1] == G_DIR_SEPARATOR) - oldlen --; - if (newlen && new[newlen - 1] == G_DIR_SEPARATOR) - newlen --; - -#ifdef _WIN32 - if (g_ascii_strncasecmp (path, old, oldlen) || (path[oldlen] && path[oldlen] != G_DIR_SEPARATOR)) -#else - if (strncmp (path, old, oldlen) || (path[oldlen] && path[oldlen] != G_DIR_SEPARATOR)) -#endif - { - fprintf (stderr, "Failed to relocate a data path. Falling back to " - "compile-time path: %s\n", path); - return str_get (path); - } - - return str_printf ("%.*s%s", newlen, new, path + oldlen); -} - -static void relocate_paths (void) -{ - char bindir[] = HARDCODE_BINDIR; - char datadir[] = HARDCODE_DATADIR; - char plugindir[] = HARDCODE_PLUGINDIR; - char localedir[] = HARDCODE_LOCALEDIR; - char desktopfile[] = HARDCODE_DESKTOPFILE; - char iconfile[] = HARDCODE_ICONFILE; - - filename_normalize (bindir); - filename_normalize (datadir); - filename_normalize (plugindir); - filename_normalize (localedir); - filename_normalize (desktopfile); - filename_normalize (iconfile); - - /* Compare the compile-time path to the executable and the actual path to - * see if we have been moved. */ - char * self = get_path_to_self (); - if (! self) - { -FALLBACK: - /* Fall back to compile-time paths. */ - aud_paths[AUD_PATH_BIN_DIR] = str_get (bindir); - aud_paths[AUD_PATH_DATA_DIR] = str_get (datadir); - aud_paths[AUD_PATH_PLUGIN_DIR] = str_get (plugindir); - aud_paths[AUD_PATH_LOCALE_DIR] = str_get (localedir); - aud_paths[AUD_PATH_DESKTOP_FILE] = str_get (desktopfile); - aud_paths[AUD_PATH_ICON_FILE] = str_get (iconfile); - - return; - } - - SCOPY (old, bindir); - SCOPY (new, self); - - str_unref (self); - - filename_normalize (new); - - /* Strip the name of the executable file, leaving the path. */ - char * base = last_path_element (new); - if (! base) - goto FALLBACK; - - cut_path_element (new, base); - - /* Strip innermost folder names from both paths as long as they match. This - * leaves a compile-time prefix and a run-time one to replace it with. */ - char * a, * b; - while ((a = last_path_element (old)) && (b = last_path_element (new)) && -#ifdef _WIN32 - ! g_ascii_strcasecmp (a, b)) -#else - ! strcmp (a, b)) -#endif - { - cut_path_element (old, a); - cut_path_element (new, b); - } - - /* Do the replacements. */ - aud_paths[AUD_PATH_BIN_DIR] = relocate_path (bindir, old, new); - aud_paths[AUD_PATH_DATA_DIR] = relocate_path (datadir, old, new); - aud_paths[AUD_PATH_PLUGIN_DIR] = relocate_path (plugindir, old, new); - aud_paths[AUD_PATH_LOCALE_DIR] = relocate_path (localedir, old, new); - aud_paths[AUD_PATH_DESKTOP_FILE] = relocate_path (desktopfile, old, new); - aud_paths[AUD_PATH_ICON_FILE] = relocate_path (iconfile, old, new); -} - -static void init_paths (void) -{ - relocate_paths (); - - const char * xdg_config_home = g_get_user_config_dir (); - - aud_paths[AUD_PATH_USER_DIR] = filename_build (xdg_config_home, "audacious"); - aud_paths[AUD_PATH_PLAYLISTS_DIR] = filename_build (aud_paths[AUD_PATH_USER_DIR], "playlists"); - -#ifdef _WIN32 - /* Some libraries (libmcs) and plugins (filewriter) use these variables, - * which are generally not set on Windows. */ - g_setenv ("HOME", g_get_home_dir (), TRUE); - g_setenv ("XDG_CONFIG_HOME", xdg_config_home, TRUE); - g_setenv ("XDG_DATA_HOME", g_get_user_data_dir (), TRUE); - g_setenv ("XDG_CACHE_HOME", g_get_user_cache_dir (), TRUE); -#endif -} - -const char * get_path (int id) -{ - g_return_val_if_fail (id >= 0 && id < AUD_PATH_COUNT, NULL); - return aud_paths[id]; -} - -static bool_t parse_options (int argc, char * * argv) -{ - char * cur = g_get_current_dir (); - bool_t success = TRUE; - -#ifdef _WIN32 - get_argv_utf8 (& argc, & argv); -#endif - - for (int n = 1; n < argc; n ++) - { - if (argv[n][0] != '-') /* filename */ - { - char * uri = NULL; - - if (strstr (argv[n], "://")) - uri = str_get (argv[n]); - else if (g_path_is_absolute (argv[n])) - uri = filename_to_uri (argv[n]); - else - { - char * tmp = filename_build (cur, argv[n]); - uri = filename_to_uri (tmp); - str_unref (tmp); - } - - if (uri) - { - if (! filenames) - filenames = index_new (); - - index_insert (filenames, -1, uri); - } - } - else if (argv[n][1] == '-') /* long option */ - { - int i; - - for (i = 0; i < ARRAY_LEN (arg_map); i ++) - { - if (! strcmp (argv[n] + 2, arg_map[i].long_arg)) - { - * arg_map[i].value = TRUE; - break; - } - } - - if (i == ARRAY_LEN (arg_map)) - { - fprintf (stderr, _("Unknown option: %s\n"), argv[n]); - success = FALSE; - goto OUT; - } - } - else /* short form */ - { - for (int c = 1; argv[n][c]; c ++) - { - int i; - - for (i = 0; i < ARRAY_LEN (arg_map); i ++) - { - if (argv[n][c] == arg_map[i].short_arg) - { - * arg_map[i].value = TRUE; - break; - } - } - - if (i == ARRAY_LEN (arg_map)) - { - fprintf (stderr, _("Unknown option: -%c\n"), argv[n][c]); - success = FALSE; - goto OUT; - } - } - } - } - - verbose = options.verbose; - -OUT: -#ifdef _WIN32 - free_argv_utf8 (& argc, & argv); -#endif - - g_free (cur); - return success; -} - -static void print_help (void) -{ - static const char pad[20] = " "; - - fprintf (stderr, _("Usage: audacious [OPTION] ... [FILE] ...\n\n")); - - for (int i = 0; i < ARRAY_LEN (arg_map); i ++) - fprintf (stderr, " -%c, --%s%.*s%s\n", arg_map[i].short_arg, - arg_map[i].long_arg, (int) (20 - strlen (arg_map[i].long_arg)), pad, - _(arg_map[i].desc)); - - fprintf (stderr, "\n"); -} - -bool_t headless_mode (void) -{ - return options.headless; -} - -#ifdef USE_DBUS -static void do_remote (void) -{ - GDBusConnection * bus = NULL; - ObjAudacious * obj = NULL; - GError * error = NULL; - - if (! (bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, & error))) - goto ERR; - - if (! (obj = obj_audacious_proxy_new_sync (bus, 0, "org.atheme.audacious", - "/org/atheme/audacious", NULL, & error))) - goto ERR; - - /* check whether remote is running */ - char * version = NULL; - obj_audacious_call_version_sync (obj, & version, NULL, NULL); - - if (! version) - goto DONE; - - AUDDBG ("Connected to remote version %s.\n", version); - - /* if no command line options, then present running instance */ - if (! (filenames || options.play || options.pause || options.play_pause || - options.stop || options.rew || options.fwd || options.show_jump_box || - options.mainwin)) - options.mainwin = TRUE; - - if (filenames) - { - int n_filenames = index_count (filenames); - const char * * list = g_new (const char *, n_filenames + 1); - - for (int i = 0; i < n_filenames; i ++) - list[i] = index_get (filenames, i); - - list[n_filenames] = NULL; - - if (options.enqueue_to_temp) - obj_audacious_call_open_list_to_temp_sync (obj, list, NULL, NULL); - else if (options.enqueue) - obj_audacious_call_add_list_sync (obj, list, NULL, NULL); - else - obj_audacious_call_open_list_sync (obj, list, NULL, NULL); - - g_free (list); - } - - if (options.play) - obj_audacious_call_play_sync (obj, NULL, NULL); - if (options.pause) - obj_audacious_call_pause_sync (obj, NULL, NULL); - if (options.play_pause) - obj_audacious_call_play_pause_sync (obj, NULL, NULL); - if (options.stop) - obj_audacious_call_stop_sync (obj, NULL, NULL); - if (options.rew) - obj_audacious_call_reverse_sync (obj, NULL, NULL); - if (options.fwd) - obj_audacious_call_advance_sync (obj, NULL, NULL); - if (options.show_jump_box) - obj_audacious_call_show_jtf_box_sync (obj, TRUE, NULL, NULL); - if (options.mainwin) - obj_audacious_call_show_main_win_sync (obj, TRUE, NULL, NULL); - - g_free (version); - g_object_unref (obj); - - exit (EXIT_SUCCESS); - -ERR: - fprintf (stderr, "D-Bus error: %s\n", error->message); - g_error_free (error); - -DONE: - if (obj) - g_object_unref (obj); - - return; -} -#endif - -static void do_commands (void) -{ - bool_t resume = get_bool (NULL, "resume_playback_on_startup"); - - if (filenames) - { - if (options.enqueue_to_temp) - { - drct_pl_open_temp_list (filenames); - resume = FALSE; - } - else if (options.enqueue) - drct_pl_add_list (filenames, -1); - else - { - drct_pl_open_list (filenames); - resume = FALSE; - } - - filenames = NULL; - } - - if (resume) - playlist_resume (); - - if (options.play || options.play_pause) - { - if (! drct_get_playing ()) - drct_play (); - else if (drct_get_paused ()) - drct_pause (); - } - - if (options.show_jump_box && ! options.headless) - audgui_jump_to_track (); - if (options.mainwin && ! options.headless) - interface_show (TRUE); -} - -static void main_cleanup (void) -{ - for (int i = 0; i < AUD_PATH_COUNT; i ++) - str_unref (aud_paths[i]); - - if (filenames) - index_free_full (filenames, (IndexFreeFunc) str_unref); - - strpool_shutdown (); -} - -static void init_one (void) -{ - atexit (main_cleanup); - -#ifdef HAVE_SIGWAIT - signals_init_one (); -#endif - - init_paths (); - make_dirs (); - - setlocale (LC_ALL, ""); - bindtextdomain (PACKAGE, aud_paths[AUD_PATH_LOCALE_DIR]); - bind_textdomain_codeset (PACKAGE, "UTF-8"); - bindtextdomain (PACKAGE "-plugins", aud_paths[AUD_PATH_LOCALE_DIR]); - bind_textdomain_codeset (PACKAGE "-plugins", "UTF-8"); - textdomain (PACKAGE); - -#if ! GLIB_CHECK_VERSION (2, 36, 0) - g_type_init (); -#endif -} - -static void init_two (void) -{ - if (! options.headless) - gtk_init (NULL, NULL); - -#ifdef HAVE_SIGWAIT - signals_init_two (); -#endif - - AUDDBG ("Loading configuration.\n"); - config_load (); - - AUDDBG ("Initializing.\n"); - art_init (); - chardet_init (); - eq_init (); - playlist_init (); - - tag_set_verbose (verbose); - vfs_set_verbose (verbose); - - AUDDBG ("Loading lowlevel plugins.\n"); - start_plugins_one (); - - AUDDBG ("Starting worker threads.\n"); - adder_init (); - scanner_init (); - - AUDDBG ("Restoring state.\n"); - load_playlists (); - - do_commands (); - - AUDDBG ("Loading highlevel plugins.\n"); - start_plugins_two (); - -#ifdef USE_DBUS - dbus_server_init (); -#endif -} - -static void shut_down (void) -{ - AUDDBG ("Saving playlist state.\n"); - save_playlists (TRUE); - - AUDDBG ("Unloading highlevel plugins.\n"); - stop_plugins_two (); - -#ifdef USE_DBUS - dbus_server_cleanup (); -#endif - - AUDDBG ("Stopping playback.\n"); - if (drct_get_playing ()) - drct_stop (); - - AUDDBG ("Stopping worker threads.\n"); - adder_cleanup (); - scanner_cleanup (); - - AUDDBG ("Unloading lowlevel plugins.\n"); - stop_plugins_one (); - - event_queue_cancel_all (); - - AUDDBG ("Saving configuration.\n"); - config_save (); - config_cleanup (); - - AUDDBG ("Cleaning up.\n"); - art_cleanup (); - chardet_cleanup (); - eq_cleanup (); - history_cleanup (); - playlist_end (); -} - -bool_t do_autosave (void) -{ - AUDDBG ("Saving configuration.\n"); - hook_call ("config save", NULL); - save_playlists (FALSE); - config_save (); - return TRUE; -} - -static bool_t check_should_quit (void) -{ - return options.quit_after_play && ! drct_get_playing () && ! playlist_add_in_progress (-1); -} - -static void maybe_quit (void) -{ - if (check_should_quit ()) - gtk_main_quit (); -} - -int main (int argc, char * * argv) -{ - init_one (); - - if (! parse_options (argc, argv)) - { - print_help (); - return EXIT_FAILURE; - } - - if (options.help) - { - print_help (); - return EXIT_SUCCESS; - } - - if (options.version) - { - printf ("%s %s (%s)\n", _("Audacious"), VERSION, BUILDSTAMP); - return EXIT_SUCCESS; - } - -#if USE_DBUS - do_remote (); /* may exit */ -#endif - - AUDDBG ("No remote session; starting up.\n"); - init_two (); - - AUDDBG ("Startup complete.\n"); - g_timeout_add_seconds (AUTOSAVE_INTERVAL, (GSourceFunc) do_autosave, NULL); - - if (check_should_quit ()) - goto QUIT; - - hook_associate ("playback stop", (HookFunction) maybe_quit, NULL); - hook_associate ("playlist add complete", (HookFunction) maybe_quit, NULL); - - gtk_main (); - - hook_dissociate ("playback stop", (HookFunction) maybe_quit); - hook_dissociate ("playlist add complete", (HookFunction) maybe_quit); - -QUIT: - shut_down (); - return EXIT_SUCCESS; -} - -void drct_quit (void) -{ - gtk_main_quit (); -} diff --git a/src/audacious/main.cc b/src/audacious/main.cc new file mode 100644 index 0000000..ca6526f --- /dev/null +++ b/src/audacious/main.cc @@ -0,0 +1,384 @@ +/* + * main.c + * Copyright 2007-2013 William Pitcock and John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <glib.h> /* for g_get_current_dir, g_path_is_absolute */ + +#include <libaudcore/audstrings.h> +#include <libaudcore/drct.h> +#include <libaudcore/hook.h> +#include <libaudcore/i18n.h> +#include <libaudcore/interface.h> +#include <libaudcore/playlist.h> +#include <libaudcore/runtime.h> +#include <libaudcore/tuple.h> + +#ifdef USE_DBUS +#include "aud-dbus.h" +#endif + +#include "main.h" +#include "util.h" + +static struct { + int help, version; + int play, pause, play_pause, stop, fwd, rew; + int enqueue, enqueue_to_temp; + int mainwin, show_jump_box; + int headless, quit_after_play; + int verbose; + int qt; +} options; + +static Index<PlaylistAddItem> filenames; + +static const struct { + const char * long_arg; + char short_arg; + int * value; + const char * desc; +} arg_map[] = { + {"help", 'h', & options.help, N_("Show command-line help")}, + {"version", 'v', & options.version, N_("Show version")}, + {"play", 'p', & options.play, N_("Start playback")}, + {"pause", 'u', & options.pause, N_("Pause playback")}, + {"play-pause", 't', & options.play_pause, N_("Pause if playing, play otherwise")}, + {"stop", 's', & options.stop, N_("Stop playback")}, + {"rew", 'r', & options.rew, N_("Skip to previous song")}, + {"fwd", 'f', & options.fwd, N_("Skip to next song")}, + {"enqueue", 'e', & options.enqueue, N_("Add files to the playlist")}, + {"enqueue-to-temp", 'E', & options.enqueue_to_temp, N_("Add files to a temporary playlist")}, + {"show-main-window", 'm', & options.mainwin, N_("Display the main window")}, + {"show-jump-box", 'j', & options.show_jump_box, N_("Display the jump-to-song window")}, + {"headless", 'H', & options.headless, N_("Start without a graphical interface")}, + {"quit-after-play", 'q', & options.quit_after_play, N_("Quit on playback stop")}, + {"verbose", 'V', & options.verbose, N_("Print debugging messages (may be used twice)")}, +#if defined(USE_QT) && defined(USE_GTK) + {"qt", 'Q', & options.qt, N_("Run in Qt mode")}, +#endif +}; + +static bool parse_options (int argc, char * * argv) +{ + char * cur = g_get_current_dir (); + bool success = true; + +#ifdef _WIN32 + Index<String> args = get_argv_utf8 (); + + for (int n = 1; n < args.len (); n ++) + { + const char * arg = args[n]; +#else + for (int n = 1; n < argc; n ++) + { + const char * arg = argv[n]; +#endif + + if (arg[0] != '-') /* filename */ + { + String uri; + + if (strstr (arg, "://")) + uri = String (arg); + else if (g_path_is_absolute (arg)) + uri = String (filename_to_uri (arg)); + else + uri = String (filename_to_uri (filename_build ({cur, arg}))); + + if (uri) + filenames.append (uri); + } + else if (arg[1] == '-') /* long option */ + { + bool found = false; + + for (auto & arg_info : arg_map) + { + if (! strcmp (arg + 2, arg_info.long_arg)) + { + (* arg_info.value) ++; + found = true; + break; + } + } + + if (! found) + { + fprintf (stderr, _("Unknown option: %s\n"), arg); + success = false; + goto OUT; + } + } + else /* short form */ + { + for (int c = 1; arg[c]; c ++) + { + bool found = false; + + for (auto & arg_info : arg_map) + { + if (arg[c] == arg_info.short_arg) + { + (* arg_info.value) ++; + found = true; + break; + } + } + + if (! found) + { + fprintf (stderr, _("Unknown option: -%c\n"), arg[c]); + success = false; + goto OUT; + } + } + } + } + + aud_set_headless_mode (options.headless); + + if (options.verbose >= 2) + audlog::set_stderr_level (audlog::Debug); + else if (options.verbose) + audlog::set_stderr_level (audlog::Info); + + if (options.qt) + aud_set_mainloop_type (MainloopType::Qt); + +OUT: + g_free (cur); + return success; +} + +static void print_help (void) +{ + static const char pad[21] = " "; + + fprintf (stderr, _("Usage: audacious [OPTION] ... [FILE] ...\n\n")); + + for (auto & arg_info : arg_map) + fprintf (stderr, " -%c, --%s%.*s%s\n", arg_info.short_arg, + arg_info.long_arg, (int) (20 - strlen (arg_info.long_arg)), pad, + _(arg_info.desc)); + + fprintf (stderr, "\n"); +} + +#ifdef USE_DBUS +static void do_remote (void) +{ + GDBusConnection * bus = nullptr; + ObjAudacious * obj = nullptr; + GError * error = nullptr; + +#if ! GLIB_CHECK_VERSION (2, 36, 0) + g_type_init (); +#endif + + /* check whether this is the first instance */ + if (dbus_server_init () != StartupType::Client) + return; + + if (! (bus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, & error))) + goto ERR; + + if (! (obj = obj_audacious_proxy_new_sync (bus, (GDBusProxyFlags) 0, + "org.atheme.audacious", "/org/atheme/audacious", nullptr, & error))) + goto ERR; + + AUDINFO ("Connected to remote session.\n"); + + /* if no command line options, then present running instance */ + if (! (filenames.len () || options.play || options.pause || + options.play_pause || options.stop || options.rew || options.fwd || + options.show_jump_box || options.mainwin)) + options.mainwin = true; + + if (filenames.len ()) + { + Index<const char *> list; + + for (auto & item : filenames) + list.append (item.filename); + + list.append (nullptr); + + if (options.enqueue_to_temp) + obj_audacious_call_open_list_to_temp_sync (obj, list.begin (), nullptr, nullptr); + else if (options.enqueue) + obj_audacious_call_add_list_sync (obj, list.begin (), nullptr, nullptr); + else + obj_audacious_call_open_list_sync (obj, list.begin (), nullptr, nullptr); + } + + if (options.play) + obj_audacious_call_play_sync (obj, nullptr, nullptr); + if (options.pause) + obj_audacious_call_pause_sync (obj, nullptr, nullptr); + if (options.play_pause) + obj_audacious_call_play_pause_sync (obj, nullptr, nullptr); + if (options.stop) + obj_audacious_call_stop_sync (obj, nullptr, nullptr); + if (options.rew) + obj_audacious_call_reverse_sync (obj, nullptr, nullptr); + if (options.fwd) + obj_audacious_call_advance_sync (obj, nullptr, nullptr); + if (options.show_jump_box) + obj_audacious_call_show_jtf_box_sync (obj, true, nullptr, nullptr); + if (options.mainwin) + obj_audacious_call_show_main_win_sync (obj, true, nullptr, nullptr); + + g_object_unref (obj); + + exit (EXIT_SUCCESS); + +ERR: + if (error) + { + AUDERR ("D-Bus error: %s\n", error->message); + g_error_free (error); + } +} +#endif + +static void do_commands (void) +{ + bool resume = aud_get_bool (nullptr, "resume_playback_on_startup"); + + if (filenames.len ()) + { + if (options.enqueue_to_temp) + { + aud_drct_pl_open_temp_list (std::move (filenames)); + resume = false; + } + else if (options.enqueue) + aud_drct_pl_add_list (std::move (filenames), -1); + else + { + aud_drct_pl_open_list (std::move (filenames)); + resume = false; + } + } + + if (resume) + aud_resume (); + + if (options.play || options.play_pause) + { + if (! aud_drct_get_playing ()) + aud_drct_play (); + else if (aud_drct_get_paused ()) + aud_drct_pause (); + } + + if (options.show_jump_box && ! options.headless) + aud_ui_show_jump_to_song (); + if (options.mainwin && ! options.headless) + aud_ui_show (true); +} + +static void main_cleanup (void) +{ + filenames.clear (); + aud_cleanup_paths (); + aud_leak_check (); +} + +static bool check_should_quit (void) +{ + return options.quit_after_play && ! aud_drct_get_playing () && + ! aud_playlist_add_in_progress (-1); +} + +static void maybe_quit (void) +{ + if (check_should_quit ()) + aud_quit (); +} + +int main (int argc, char * * argv) +{ + atexit (main_cleanup); + +#ifdef HAVE_SIGWAIT + signals_init_one (); +#endif + + aud_init_paths (); + aud_init_i18n (); + + if (! parse_options (argc, argv)) + { + print_help (); + return EXIT_FAILURE; + } + + if (options.help) + { + print_help (); + return EXIT_SUCCESS; + } + + if (options.version) + { + printf ("%s %s (%s)\n", _("Audacious"), VERSION, BUILDSTAMP); + return EXIT_SUCCESS; + } + +#if USE_DBUS + do_remote (); /* may exit */ +#endif + + AUDINFO ("No remote session; starting up.\n"); + +#ifdef HAVE_SIGWAIT + signals_init_two (); +#endif + + aud_init (); + + do_commands (); + + if (check_should_quit ()) + goto QUIT; + + hook_associate ("playback stop", (HookFunction) maybe_quit, nullptr); + hook_associate ("playlist add complete", (HookFunction) maybe_quit, nullptr); + hook_associate ("quit", (HookFunction) aud_quit, nullptr); + + aud_run (); + + hook_dissociate ("playback stop", (HookFunction) maybe_quit); + hook_dissociate ("playlist add complete", (HookFunction) maybe_quit); + hook_dissociate ("quit", (HookFunction) aud_quit); + +QUIT: +#ifdef USE_DBUS + dbus_server_cleanup (); +#endif + + aud_cleanup (); + + return EXIT_SUCCESS; +} diff --git a/src/audacious/main.h b/src/audacious/main.h index 0fa3432..88fd0de 100644 --- a/src/audacious/main.h +++ b/src/audacious/main.h @@ -17,41 +17,22 @@ * the use of this software. */ -/* Header for all those files that have just one or two public identifiers. */ - #ifndef _AUDACIOUS_MAIN_H #define _AUDACIOUS_MAIN_H -#include <libaudcore/core.h> - -/* adder.c */ -void adder_init (void); -void adder_cleanup (void); - -/* art.c */ -void art_init (void); -void art_cleanup (void); - /* dbus-server.c */ #ifdef USE_DBUS -void dbus_server_init (void); -void dbus_server_cleanup (void); -#endif - -/* chardet.c */ -void chardet_init (void); -void chardet_cleanup (void); -/* config.c */ -void config_load (void); -void config_save (void); -void config_cleanup (void); +enum class StartupType { + Server, + Client, + Unknown +}; -/* history.c */ -void history_cleanup (void); +StartupType dbus_server_init (void); +void dbus_server_cleanup (void); -/* main.c */ -bool_t do_autosave (void); +#endif /* signals.c */ #ifdef HAVE_SIGWAIT @@ -59,7 +40,4 @@ void signals_init_one (void); void signals_init_two (void); #endif -/* ui_albumart.c */ -char * get_associated_image_file (const char * filename); /* pooled */ - #endif diff --git a/src/audacious/misc-api.h b/src/audacious/misc-api.h deleted file mode 100644 index b9ea2a5..0000000 --- a/src/audacious/misc-api.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - * misc-api.h - * Copyright 2010-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -/* Do not include this file directly; use misc.h instead. */ - -/* all (char *) return values must be freed with str_unref() */ - -/* art.c (thread-safe) */ - -/* Gets album art for <file> (the URI of a song file) as JPEG or PNG data. If - * the album art is not yet loaded, sets <data> to NULL and begins to load the - * album art in the background. On completion, the "art ready" hook is called, - * with <file> as a parameter. The "current art ready" hook is also called if - * <file> is the currently playing song. */ -AUD_VFUNC3 (art_request_data, const char *, file, const void * *, data, int64_t *, len) - -/* Similar to art_request_data() but returns the URI of an image file. - * (A temporary file will be created if necessary.) */ -AUD_FUNC1 (const char *, art_request_file, const char *, file) - -/* Releases album art returned by art_request_data() or art_request_file(). */ -AUD_VFUNC1 (art_unref, const char *, file) - -/* config.c (thread-safe) */ - -AUD_VFUNC2 (config_set_defaults, const char *, section, const char * const *, entries) - -AUD_VFUNC3 (set_str, const char *, section, const char *, name, const char *, value) -AUD_FUNC2 (char *, get_str, const char *, section, const char *, name) -AUD_VFUNC3 (set_bool, const char *, section, const char *, name, bool_t, value) -AUD_FUNC2 (bool_t, get_bool, const char *, section, const char *, name) -AUD_VFUNC3 (set_int, const char *, section, const char *, name, int, value) -AUD_FUNC2 (int, get_int, const char *, section, const char *, name) -AUD_VFUNC3 (set_double, const char *, section, const char *, name, double, value) -AUD_FUNC2 (double, get_double, const char *, section, const char *, name) - -/* equalizer.c */ -AUD_VFUNC1 (eq_set_bands, const double *, values) -AUD_VFUNC1 (eq_get_bands, double *, values) -AUD_VFUNC2 (eq_set_band, int, band, double, value) -AUD_FUNC1 (double, eq_get_band, int, band) - -/* equalizer_preset.c */ -AUD_FUNC1 (EqualizerPreset *, equalizer_preset_new, const char *, name) -AUD_VFUNC1 (equalizer_preset_free, EqualizerPreset *, preset) -AUD_FUNC1 (Index *, equalizer_read_presets, const char *, basename) -AUD_FUNC2 (bool_t, equalizer_write_presets, Index *, list, const char *, basename) - -/* note: legacy code! these are local filenames, not URIs */ -AUD_FUNC1 (EqualizerPreset *, load_preset_file, const char *, filename) -AUD_FUNC2 (bool_t, save_preset_file, EqualizerPreset *, preset, const char *, filename) - -AUD_FUNC1 (Index *, import_winamp_presets, VFSFile *, file) -AUD_FUNC2 (bool_t, export_winamp_preset, EqualizerPreset *, preset, VFSFile *, file) - -/* history.c */ -AUD_FUNC1 (const char *, history_get, int, entry) -AUD_VFUNC1 (history_add, const char *, path) - -/* interface.c */ -AUD_VFUNC1 (interface_show, bool_t, show) -AUD_FUNC0 (bool_t, interface_is_shown) - -/* interface_show_error() is safe to call from any thread */ -AUD_VFUNC1 (interface_show_error, const char *, message) - -/* main.c */ -AUD_FUNC1 (const char *, get_path, int, path) -AUD_FUNC0 (bool_t, headless_mode) - -/* output.c */ -AUD_VFUNC1 (output_reset, int, type) - -/* probe.c */ -AUD_FUNC2 (PluginHandle *, file_find_decoder, const char *, filename, bool_t, - fast) -AUD_FUNC2 (Tuple *, file_read_tuple, const char *, filename, PluginHandle *, - decoder) -AUD_FUNC4 (bool_t, file_read_image, const char *, filename, PluginHandle *, - decoder, void * *, data, int64_t *, size) -AUD_FUNC2 (bool_t, file_can_write_tuple, const char *, filename, - PluginHandle *, decoder) -AUD_FUNC3 (bool_t, file_write_tuple, const char *, filename, PluginHandle *, - decoder, const Tuple *, tuple) -AUD_FUNC2 (bool_t, custom_infowin, const char *, filename, PluginHandle *, - decoder) - -/* ui_plugin_menu.c */ -AUD_FUNC1 (/* GtkWidget * */ void *, get_plugin_menu, int, id) -AUD_VFUNC4 (plugin_menu_add, int, id, MenuFunc, func, const char *, name, - const char *, icon) -AUD_VFUNC2 (plugin_menu_remove, int, id, MenuFunc, func) - -/* ui_preferences.c */ -AUD_VFUNC4 (create_widgets_with_domain, /* GtkWidget * */ void *, box, - const PreferencesWidget *, widgets, int, n_widgets, const char *, domain) -AUD_VFUNC0 (show_prefs_window) -AUD_VFUNC1 (show_prefs_for_plugin_type, int, type) - -/* util.c */ - -/* Constructs a full URI given: - * 1. path: one of the following: - * a. a full URI (returned unchanged) - * b. an absolute filename (in the system locale) - * c. a relative path (character set detected according to user settings) - * 2. reference: the full URI of the playlist containing <path> */ -AUD_FUNC2 (char *, construct_uri, const char *, path, const char *, reference) - -/* visualization.c */ -AUD_VFUNC2 (vis_func_add, int, type, VisFunc, func) -AUD_VFUNC1 (vis_func_remove, VisFunc, func) diff --git a/src/audacious/misc.h b/src/audacious/misc.h deleted file mode 100644 index 3d25517..0000000 --- a/src/audacious/misc.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * misc.h - * Copyright 2010-2012 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_MISC_H -#define AUDACIOUS_MISC_H - -#include <audacious/api.h> -#include <audacious/types.h> -#include <libaudcore/index.h> -#include <libaudcore/tuple.h> -#include <libaudcore/vfs.h> - -enum { - AUD_PATH_BIN_DIR, - AUD_PATH_DATA_DIR, - AUD_PATH_PLUGIN_DIR, - AUD_PATH_LOCALE_DIR, - AUD_PATH_DESKTOP_FILE, - AUD_PATH_ICON_FILE, - AUD_PATH_USER_DIR, - AUD_PATH_PLAYLISTS_DIR, - AUD_PATH_COUNT -}; - -enum {OUTPUT_RESET_EFFECTS_ONLY, OUTPUT_RESET_SOFT, OUTPUT_RESET_HARD}; - -enum { - AUD_MENU_MAIN, - AUD_MENU_PLAYLIST, - AUD_MENU_PLAYLIST_ADD, - AUD_MENU_PLAYLIST_REMOVE, - AUD_MENU_COUNT}; - -typedef void (* MenuFunc) (void); - -enum { - AUD_VIS_TYPE_CLEAR, /* like VisPlugin::clear() */ - AUD_VIS_TYPE_MONO_PCM, /* like VisPlugin::render_mono_pcm() */ - AUD_VIS_TYPE_MULTI_PCM, /* like VisPlugin::render_multi_pcm() */ - AUD_VIS_TYPE_FREQ, /* like VisPlugin::render_freq() */ - AUD_VIS_TYPES}; - -/* generic type; does not correspond to actual function types */ -typedef void (* VisFunc) (void); - -#define AUD_API_NAME MiscAPI -#define AUD_API_SYMBOL misc_api - -#ifdef _AUDACIOUS_CORE - -#include "api-local-begin.h" -#include "misc-api.h" -#include "api-local-end.h" - -#define create_widgets(b, w, a) create_widgets_with_domain (b, w, a, PACKAGE) - -#else - -#include <audacious/api-define-begin.h> -#include <audacious/misc-api.h> -#include <audacious/api-define-end.h> - -#include <audacious/api-alias-begin.h> -#include <audacious/misc-api.h> -#include <audacious/api-alias-end.h> - -#define aud_create_widgets(b, w, a) aud_create_widgets_with_domain (b, w, a, \ - PACKAGE) - -#endif - -#undef AUD_API_NAME -#undef AUD_API_SYMBOL - -#endif - -#ifdef AUD_API_DECLARE - -#define AUD_API_NAME MiscAPI -#define AUD_API_SYMBOL misc_api - -#include "api-define-begin.h" -#include "misc-api.h" -#include "api-define-end.h" - -#include "api-declare-begin.h" -#include "misc-api.h" -#include "api-declare-end.h" - -#undef AUD_API_NAME -#undef AUD_API_SYMBOL - -#endif diff --git a/src/audacious/output.c b/src/audacious/output.c deleted file mode 100644 index b6f862a..0000000 --- a/src/audacious/output.c +++ /dev/null @@ -1,642 +0,0 @@ -/* - * output.c - * Copyright 2009-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <math.h> -#include <pthread.h> -#include <stdlib.h> -#include <string.h> - -#include <glib.h> /* for g_usleep */ - -#include "debug.h" -#include "effect.h" -#include "equalizer.h" -#include "misc.h" -#include "output.h" -#include "plugin.h" -#include "plugins.h" -#include "vis_runner.h" - -#define SW_VOLUME_RANGE 40 /* decibels */ - -static pthread_mutex_t mutex_major = PTHREAD_MUTEX_INITIALIZER; -static pthread_mutex_t mutex_minor = PTHREAD_MUTEX_INITIALIZER; - -#define LOCK_MAJOR pthread_mutex_lock (& mutex_major) -#define UNLOCK_MAJOR pthread_mutex_unlock (& mutex_major) -#define LOCK_MINOR pthread_mutex_lock (& mutex_minor) -#define UNLOCK_MINOR pthread_mutex_unlock (& mutex_minor) -#define LOCK_ALL do { LOCK_MAJOR; LOCK_MINOR; } while (0) -#define UNLOCK_ALL do { UNLOCK_MINOR; UNLOCK_MAJOR; } while (0) - -/* State variables. State changes that are allowed between LOCK_MINOR and - * UNLOCK_MINOR (all others must take place between LOCK_ALL and UNLOCK_ALL): - * s_paused -> TRUE or FALSE, s_aborted -> TRUE, s_resetting -> TRUE */ - -static bool_t s_input; /* input plugin connected */ -static bool_t s_output; /* output plugin connected */ -static bool_t s_gain; /* replay gain info set */ -static bool_t s_paused; /* paused */ -static bool_t s_aborted; /* writes aborted */ -static bool_t s_resetting; /* resetting output system */ - -static OutputPlugin * cop; -static int seek_time; -static int in_format, in_channels, in_rate; -static int out_format, out_channels, out_rate; -static int64_t in_frames, out_frames; -static ReplayGainInfo gain_info; - -static bool_t change_op; -static OutputPlugin * new_op; - -static void * buffer1, * buffer2; -static int buffer1_size, buffer2_size; - -static inline int FR2MS (int64_t f, int r) - { return (f > 0) ? (f * 1000 + r / 2) / r : (f * 1000 - r / 2) / r; } -static inline int MS2FR (int64_t ms, int r) - { return (ms > 0) ? (ms * r + 500) / 1000 : (ms * r - 500) / 1000; } - -static inline int get_format (void) -{ - switch (get_int (NULL, "output_bit_depth")) - { - case 16: return FMT_S16_NE; - case 24: return FMT_S24_NE; - case 32: return FMT_S32_NE; - default: return FMT_FLOAT; - } -} - -static void ensure_buffer (void * * buffer, int * size, int newsize) -{ - if (newsize > * size) - { - g_free (* buffer); - * buffer = g_malloc (newsize); - * size = newsize; - } -} - -/* assumes LOCK_ALL, s_output */ -static void cleanup_output (void) -{ - if (! (s_paused || s_aborted) && PLUGIN_HAS_FUNC (cop, drain)) - { - UNLOCK_MINOR; - cop->drain (); - LOCK_MINOR; - } - - s_output = FALSE; - - g_free (buffer1); - g_free (buffer2); - buffer1 = NULL; - buffer2 = NULL; - buffer1_size = 0; - buffer2_size = 0; - - if (PLUGIN_HAS_FUNC (cop, close_audio)) - cop->close_audio (); - - effect_flush (); - vis_runner_start_stop (FALSE, FALSE); -} - -/* assumes LOCK_ALL, s_output */ -static void apply_pause (void) -{ - if (PLUGIN_HAS_FUNC (cop, pause)) - cop->pause (s_paused); - - vis_runner_start_stop (TRUE, s_paused); -} - -/* assumes LOCK_ALL, s_input */ -static void setup_output (void) -{ - int format = get_format (); - int channels = in_channels; - int rate = in_rate; - - effect_start (& channels, & rate); - eq_set_format (channels, rate); - - if (s_output && format == out_format && channels == out_channels && rate == - out_rate && ! PLUGIN_HAS_FUNC (cop, force_reopen)) - return; - - if (s_output) - cleanup_output (); - - if (! cop || ! PLUGIN_HAS_FUNC (cop, open_audio) || ! cop->open_audio (format, rate, channels)) - return; - - s_output = TRUE; - - out_format = format; - out_channels = channels; - out_rate = rate; - out_frames = 0; - - apply_pause (); -} - -/* assumes LOCK_MINOR, s_output */ -static void flush_output (void) -{ - if (PLUGIN_HAS_FUNC (cop, flush)) - { - cop->flush (0); - out_frames = 0; - } - - effect_flush (); - vis_runner_flush (); -} - -static void apply_replay_gain (float * data, int samples) -{ - if (! get_bool (NULL, "enable_replay_gain")) - return; - - float factor = powf (10, get_double (NULL, "replay_gain_preamp") / 20); - - if (s_gain) - { - float peak; - - if (get_bool (NULL, "replay_gain_album")) - { - factor *= powf (10, gain_info.album_gain / 20); - peak = gain_info.album_peak; - } - else - { - factor *= powf (10, gain_info.track_gain / 20); - peak = gain_info.track_peak; - } - - if (get_bool (NULL, "enable_clipping_prevention") && peak * factor > 1) - factor = 1 / peak; - } - else - factor *= powf (10, get_double (NULL, "default_gain") / 20); - - if (factor < 0.99 || factor > 1.01) - audio_amplify (data, 1, samples, & factor); -} - -static void apply_software_volume (float * data, int channels, int samples) -{ - if (! get_bool (NULL, "software_volume_control")) - return; - - int l = get_int (NULL, "sw_volume_left"); - int r = get_int (NULL, "sw_volume_right"); - - if (l == 100 && r == 100) - return; - - float lfactor = (l == 0) ? 0 : powf (10, (float) SW_VOLUME_RANGE * (l - 100) / 100 / 20); - float rfactor = (r == 0) ? 0 : powf (10, (float) SW_VOLUME_RANGE * (r - 100) / 100 / 20); - float factors[channels]; - - if (channels == 2) - { - factors[0] = lfactor; - factors[1] = rfactor; - } - else - { - for (int c = 0; c < channels; c ++) - factors[c] = MAX (lfactor, rfactor); - } - - audio_amplify (data, channels, samples / channels, factors); -} - -/* assumes LOCK_ALL, s_output */ -static void write_output_raw (void * data, int samples) -{ - vis_runner_pass_audio (FR2MS (out_frames, out_rate), data, samples, - out_channels, out_rate); - out_frames += samples / out_channels; - - eq_filter (data, samples); - apply_software_volume (data, out_channels, samples); - - if (get_bool (NULL, "soft_clipping")) - audio_soft_clip (data, samples); - - if (out_format != FMT_FLOAT) - { - ensure_buffer (& buffer2, & buffer2_size, FMT_SIZEOF (out_format) * samples); - audio_to_int (data, buffer2, out_format, samples); - data = buffer2; - } - - while (! (s_aborted || s_resetting)) - { - bool_t blocking = ! PLUGIN_HAS_FUNC (cop, buffer_free); - int ready; - - if (blocking) - ready = out_channels * (out_rate / 50); - else - ready = cop->buffer_free () / FMT_SIZEOF (out_format); - - ready = MIN (ready, samples); - - if (PLUGIN_HAS_FUNC (cop, write_audio)) - { - cop->write_audio (data, FMT_SIZEOF (out_format) * ready); - data = (char *) data + FMT_SIZEOF (out_format) * ready; - samples -= ready; - } - - if (samples == 0) - break; - - UNLOCK_MINOR; - - if (! blocking) - { - if (PLUGIN_HAS_FUNC (cop, period_wait)) - cop->period_wait (); - else - g_usleep (20000); - } - - LOCK_MINOR; - } -} - -/* assumes LOCK_ALL, s_input, s_output */ -static bool_t write_output (void * data, int size, int stop_time) -{ - bool_t stopped = FALSE; - - int64_t cur_frame = in_frames; - int samples = size / FMT_SIZEOF (in_format); - - /* always update in_frames, whether we use all the decoded frames or not */ - in_frames += samples / in_channels; - - if (stop_time != -1) - { - int64_t frames_left = MS2FR (stop_time - seek_time, in_rate) - cur_frame; - int64_t samples_left = in_channels * MAX (0, frames_left); - - if (samples >= samples_left) - { - samples = samples_left; - stopped = TRUE; - } - } - - if (s_aborted) - return ! stopped; - - if (in_format != FMT_FLOAT) - { - ensure_buffer (& buffer1, & buffer1_size, sizeof (float) * samples); - audio_from_int (data, in_format, buffer1, samples); - data = buffer1; - } - - float * fdata = data; - apply_replay_gain (fdata, samples); - effect_process (& fdata, & samples); - write_output_raw (fdata, samples); - - return ! stopped; -} - -/* assumes LOCK_ALL, s_output */ -static void finish_effects (void) -{ - float * data = NULL; - int samples = 0; - - effect_finish (& data, & samples); - write_output_raw (data, samples); -} - -bool_t output_open_audio (int format, int rate, int channels) -{ - /* prevent division by zero */ - if (rate < 1 || channels < 1) - return FALSE; - - LOCK_ALL; - - if (s_output && s_paused) - { - flush_output (); - s_paused = FALSE; - apply_pause (); - } - - s_input = TRUE; - s_gain = s_paused = s_aborted = FALSE; - seek_time = 0; - - in_format = format; - in_channels = channels; - in_rate = rate; - in_frames = 0; - - setup_output (); - - UNLOCK_ALL; - return TRUE; -} - -void output_set_replaygain_info (const ReplayGainInfo * info) -{ - LOCK_ALL; - - if (s_input) - { - memcpy (& gain_info, info, sizeof (ReplayGainInfo)); - s_gain = TRUE; - - AUDDBG ("Replay Gain info:\n"); - AUDDBG (" album gain: %f dB\n", info->album_gain); - AUDDBG (" album peak: %f\n", info->album_peak); - AUDDBG (" track gain: %f dB\n", info->track_gain); - AUDDBG (" track peak: %f\n", info->track_peak); - } - - UNLOCK_ALL; -} - -/* returns FALSE if stop_time is reached */ -bool_t output_write_audio (void * data, int size, int stop_time) -{ - LOCK_ALL; - bool_t good = FALSE; - - if (s_input) - { - while ((! s_output || s_resetting) && ! s_aborted) - { - UNLOCK_ALL; - g_usleep (20000); - LOCK_ALL; - } - - good = write_output (data, size, stop_time); - } - - UNLOCK_ALL; - return good; -} - -void output_abort_write (void) -{ - LOCK_MINOR; - - if (s_input) - { - s_aborted = TRUE; - - if (s_output) - flush_output (); - } - - UNLOCK_MINOR; -} - -void output_pause (bool_t pause) -{ - LOCK_MINOR; - - if (s_input) - { - s_paused = pause; - - if (s_output) - apply_pause (); - } - - UNLOCK_MINOR; -} - -int output_written_time (void) -{ - LOCK_MINOR; - int time = 0; - - if (s_input) - time = seek_time + FR2MS (in_frames, in_rate); - - UNLOCK_MINOR; - return time; -} - -void output_set_time (int time) -{ - LOCK_ALL; - - if (s_input) - { - s_aborted = FALSE; - seek_time = time; - in_frames = 0; - } - - UNLOCK_ALL; -} - -bool_t output_is_open (void) -{ - LOCK_MINOR; - bool_t is_open = s_input; - UNLOCK_MINOR; - return is_open; -} - -int output_get_time (void) -{ - LOCK_MINOR; - int time = 0, delay = 0; - - if (s_input) - { - if (s_output && PLUGIN_HAS_FUNC (cop, output_time)) - delay = FR2MS (out_frames, out_rate) - cop->output_time (); - - delay = effect_adjust_delay (delay); - time = FR2MS (in_frames, in_rate); - time = seek_time + MAX (time - delay, 0); - } - - UNLOCK_MINOR; - return time; -} - -int output_get_raw_time (void) -{ - LOCK_MINOR; - int time = 0; - - if (s_output && PLUGIN_HAS_FUNC (cop, output_time)) - time = cop->output_time (); - - UNLOCK_MINOR; - return time; -} - -void output_close_audio (void) -{ - LOCK_ALL; - - if (s_input) - { - s_input = FALSE; - - if (s_output && ! (s_paused || s_aborted || s_resetting)) - finish_effects (); /* first time for end of song */ - } - - UNLOCK_ALL; -} - -void output_drain (void) -{ - LOCK_ALL; - - if (! s_input && s_output) - { - finish_effects (); /* second time for end of playlist */ - cleanup_output (); - } - - UNLOCK_ALL; -} - -void output_reset (int type) -{ - LOCK_MINOR; - - s_resetting = TRUE; - - if (s_output) - flush_output (); - - UNLOCK_MINOR; - LOCK_ALL; - - if (s_output && type != OUTPUT_RESET_EFFECTS_ONLY) - cleanup_output (); - - if (type == OUTPUT_RESET_HARD) - { - if (cop && PLUGIN_HAS_FUNC (cop, cleanup)) - cop->cleanup (); - - if (change_op) - cop = new_op; - - if (cop && PLUGIN_HAS_FUNC (cop, init) && ! cop->init ()) - cop = NULL; - } - - if (s_input) - setup_output (); - - s_resetting = FALSE; - - UNLOCK_ALL; -} - -void output_get_volume (int * left, int * right) -{ - LOCK_MINOR; - - * left = * right = 0; - - if (get_bool (NULL, "software_volume_control")) - { - * left = get_int (NULL, "sw_volume_left"); - * right = get_int (NULL, "sw_volume_right"); - } - else if (cop && PLUGIN_HAS_FUNC (cop, get_volume)) - cop->get_volume (left, right); - - UNLOCK_MINOR; -} - -void output_set_volume (int left, int right) -{ - LOCK_MINOR; - - if (get_bool (NULL, "software_volume_control")) - { - set_int (NULL, "sw_volume_left", left); - set_int (NULL, "sw_volume_right", right); - } - else if (cop && PLUGIN_HAS_FUNC (cop, set_volume)) - cop->set_volume (left, right); - - UNLOCK_MINOR; -} - -static bool_t probe_cb (PluginHandle * p, PluginHandle * * pp) -{ - OutputPlugin * op = plugin_get_header (p); - - if (! op || (PLUGIN_HAS_FUNC (op, init) && ! op->init ())) - return TRUE; /* keep searching */ - - if (PLUGIN_HAS_FUNC (op, cleanup)) - op->cleanup (); - - * pp = p; - return FALSE; /* stop searching */ -} - -PluginHandle * output_plugin_probe (void) -{ - PluginHandle * p = NULL; - plugin_for_each (PLUGIN_TYPE_OUTPUT, (PluginForEachFunc) probe_cb, & p); - return p; -} - -PluginHandle * output_plugin_get_current (void) -{ - return cop ? plugin_by_header (cop) : NULL; -} - -bool_t output_plugin_set_current (PluginHandle * plugin) -{ - change_op = TRUE; - new_op = plugin ? plugin_get_header (plugin) : NULL; - output_reset (OUTPUT_RESET_HARD); - - bool_t success = (cop == new_op); - change_op = FALSE; - new_op = NULL; - - return success; -} diff --git a/src/audacious/output.h b/src/audacious/output.h deleted file mode 100644 index ac9ee48..0000000 --- a/src/audacious/output.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * output.h - * Copyright 2010-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_OUTPUT_H -#define AUDACIOUS_OUTPUT_H - -#include <libaudcore/core.h> -#include "types.h" - -bool_t output_open_audio (int format, int rate, int channels); -void output_set_replaygain_info (const ReplayGainInfo * info); -bool_t output_write_audio (void * data, int size, int stop_time); -void output_abort_write (void); -void output_pause (bool_t pause); -int output_written_time (void); -void output_set_time (int time); - -bool_t output_is_open (void); -int output_get_time (void); -int output_get_raw_time (void); -void output_close_audio (void); -void output_drain (void); - -void output_get_volume (int * left, int * right); -void output_set_volume (int left, int right); - -PluginHandle * output_plugin_probe (void); -PluginHandle * output_plugin_get_current (void); -bool_t output_plugin_set_current (PluginHandle * plugin); - -#endif /* AUDACIOUS_OUTPUT_H */ diff --git a/src/audacious/playback.c b/src/audacious/playback.c deleted file mode 100644 index 7d9c56f..0000000 --- a/src/audacious/playback.c +++ /dev/null @@ -1,652 +0,0 @@ -/* - * playback.c - * Copyright 2009-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <pthread.h> -#include <string.h> - -#include <libaudcore/audstrings.h> -#include <libaudcore/hook.h> -#include <libaudgui/libaudgui.h> - -#include "drct.h" -#include "i18n.h" -#include "input.h" -#include "interface.h" -#include "misc.h" -#include "output.h" -#include "playback.h" -#include "playlist.h" -#include "plugin.h" - -static pthread_t playback_thread_handle; -static int end_source = 0; - -static pthread_mutex_t ready_mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t ready_cond = PTHREAD_COND_INITIALIZER; - -static pthread_mutex_t control_mutex = PTHREAD_MUTEX_INITIALIZER; - -/* level 1 data (persists to end of song) */ -static bool_t playing = FALSE; -static int time_offset = 0; -static int stop_time = -1; -static bool_t paused = FALSE; -static bool_t ready_flag = FALSE; -static bool_t playback_error = FALSE; -static bool_t song_finished = FALSE; - -static int seek_request = -1; /* under control_mutex */ -static int repeat_a = -1; /* under control_mutex */ - -static volatile int repeat_b = -1; /* atomic */ -static volatile int stop_flag = FALSE; /* atomic */ - -static int current_bitrate = -1, current_samplerate = -1, current_channels = -1; - -static int current_entry = -1; -static char * current_filename = NULL; /* pooled */ -static char * current_title = NULL; /* pooled */ -static int current_length = -1; - -static InputPlugin * current_decoder = NULL; -static VFSFile * current_file = NULL; -static ReplayGainInfo current_gain; - -/* level 2 data (persists to end of playlist) */ -static bool_t stopped = TRUE; -static int failed_entries = 0; - -/* clears gain info if tuple == NULL */ -static void read_gain_from_tuple (const Tuple * tuple) -{ - memset (& current_gain, 0, sizeof current_gain); - - if (tuple == NULL) - return; - - int album_gain = tuple_get_int (tuple, FIELD_GAIN_ALBUM_GAIN); - int album_peak = tuple_get_int (tuple, FIELD_GAIN_ALBUM_PEAK); - int track_gain = tuple_get_int (tuple, FIELD_GAIN_TRACK_GAIN); - int track_peak = tuple_get_int (tuple, FIELD_GAIN_TRACK_PEAK); - int gain_unit = tuple_get_int (tuple, FIELD_GAIN_GAIN_UNIT); - int peak_unit = tuple_get_int (tuple, FIELD_GAIN_PEAK_UNIT); - - if (gain_unit) - { - current_gain.album_gain = album_gain / (float) gain_unit; - current_gain.track_gain = track_gain / (float) gain_unit; - } - - if (peak_unit) - { - current_gain.album_peak = album_peak / (float) peak_unit; - current_gain.track_peak = track_peak / (float) peak_unit; - } -} - -static bool_t update_from_playlist (void) -{ - int entry = playback_entry_get_position (); - char * title = playback_entry_get_title (); - int length = playback_entry_get_length (); - - if (entry == current_entry && str_equal (title, current_title) && length == current_length) - { - str_unref (title); - return FALSE; - } - - current_entry = entry; - str_unref (current_title); - current_title = title; - current_length = length; - return TRUE; -} - -bool_t drct_get_ready (void) -{ - if (! playing) - return FALSE; - - pthread_mutex_lock (& ready_mutex); - bool_t ready = ready_flag; - pthread_mutex_unlock (& ready_mutex); - return ready; -} - -static void set_ready (void) -{ - g_return_if_fail (playing); - - pthread_mutex_lock (& ready_mutex); - - update_from_playlist (); - event_queue ("playback ready", NULL); - ready_flag = TRUE; - - pthread_cond_signal (& ready_cond); - pthread_mutex_unlock (& ready_mutex); -} - -static void wait_until_ready (void) -{ - g_return_if_fail (playing); - pthread_mutex_lock (& ready_mutex); - - /* on restart, we still have to wait, but presumably not long */ - while (! ready_flag) - pthread_cond_wait (& ready_cond, & ready_mutex); - - pthread_mutex_unlock (& ready_mutex); -} - -static void update_cb (void * hook_data, void * user_data) -{ - g_return_if_fail (playing); - - if (GPOINTER_TO_INT (hook_data) < PLAYLIST_UPDATE_METADATA || ! drct_get_ready ()) - return; - - if (update_from_playlist ()) - event_queue ("title change", NULL); -} - -int drct_get_time (void) -{ - if (! playing) - return 0; - - wait_until_ready (); - - return output_get_time () - time_offset; -} - -void drct_pause (void) -{ - if (! playing) - return; - - wait_until_ready (); - - paused = ! paused; - - output_pause (paused); - - if (paused) - hook_call ("playback pause", NULL); - else - hook_call ("playback unpause", NULL); -} - -static void playback_cleanup (void) -{ - g_return_if_fail (playing); - wait_until_ready (); - - if (! song_finished) - { - g_atomic_int_set (& stop_flag, TRUE); - output_abort_write (); - } - - pthread_join (playback_thread_handle, NULL); - output_close_audio (); - - hook_dissociate ("playlist update", update_cb); - - event_queue_cancel ("playback ready", NULL); - event_queue_cancel ("playback seek", NULL); - event_queue_cancel ("info change", NULL); - event_queue_cancel ("title change", NULL); - - if (end_source) - { - g_source_remove (end_source); - end_source = 0; - } - - /* level 1 data cleanup */ - playing = FALSE; - time_offset = 0; - stop_time = -1; - paused = FALSE; - ready_flag = FALSE; - playback_error = FALSE; - song_finished = FALSE; - - seek_request = -1; - repeat_a = -1; - - g_atomic_int_set (& repeat_b, -1); - g_atomic_int_set (& stop_flag, FALSE); - - current_bitrate = current_samplerate = current_channels = -1; - - current_entry = -1; - str_unref (current_filename); - current_filename = NULL; - str_unref (current_title); - current_title = NULL; - current_length = -1; - - current_decoder = NULL; - - if (current_file) - { - vfs_fclose (current_file); - current_file = NULL; - } - - read_gain_from_tuple (NULL); - - set_bool (NULL, "stop_after_current_song", FALSE); -} - -void playback_stop (void) -{ - if (stopped) - return; - - if (playing) - playback_cleanup (); - - output_drain (); - - /* level 2 data cleanup */ - stopped = TRUE; - failed_entries = 0; - - hook_call ("playback stop", NULL); -} - -static void do_stop (int playlist) -{ - playlist_set_playing (-1); - playlist_set_position (playlist, playlist_get_position (playlist)); -} - -static void do_next (int playlist) -{ - if (! playlist_next_song (playlist, get_bool (NULL, "repeat"))) - { - playlist_set_position (playlist, -1); - hook_call ("playlist end reached", NULL); - } -} - -static bool_t end_cb (void * unused) -{ - g_return_val_if_fail (playing, FALSE); - - if (! playback_error) - song_finished = TRUE; - - hook_call ("playback end", NULL); - - if (playback_error) - failed_entries ++; - else - failed_entries = 0; - - int playlist = playlist_get_playing (); - - if (get_bool (NULL, "stop_after_current_song")) - { - do_stop (playlist); - - if (! get_bool (NULL, "no_playlist_advance")) - do_next (playlist); - } - else if (get_bool (NULL, "no_playlist_advance")) - { - if (get_bool (NULL, "repeat") && ! failed_entries) - playback_play (0, FALSE); - else - do_stop (playlist); - } - else - { - if (failed_entries < 10) - do_next (playlist); - else - do_stop (playlist); - } - - return FALSE; -} - -static bool_t open_file (void) -{ - /* no need to open a handle for custom URI schemes */ - if (current_decoder->schemes && current_decoder->schemes[0]) - return TRUE; - - current_file = vfs_fopen (current_filename, "r"); - return (current_file != NULL); -} - -static void * playback_thread (void * unused) -{ - if (! current_decoder) - { - PluginHandle * p = playback_entry_get_decoder (); - current_decoder = p ? plugin_get_header (p) : NULL; - - if (! current_decoder) - { - SPRINTF (error, _("No decoder found for %s."), current_filename); - interface_show_error (error); - playback_error = TRUE; - goto DONE; - } - } - - Tuple * tuple = playback_entry_get_tuple (); - int length = playback_entry_get_length (); - - if (length < 1) - seek_request = -1; - - if (tuple && length > 0) - { - if (tuple_get_value_type (tuple, FIELD_SEGMENT_START) == TUPLE_INT) - { - time_offset = tuple_get_int (tuple, FIELD_SEGMENT_START); - if (time_offset) - seek_request = time_offset + MAX (seek_request, 0); - } - - if (tuple_get_value_type (tuple, FIELD_SEGMENT_END) == TUPLE_INT) - stop_time = tuple_get_int (tuple, FIELD_SEGMENT_END); - } - - read_gain_from_tuple (tuple); - - if (tuple) - tuple_unref (tuple); - - if (! open_file ()) - { - SPRINTF (error, _("%s could not be opened."), current_filename); - interface_show_error (error); - playback_error = TRUE; - goto DONE; - } - - playback_error = ! current_decoder->play (current_filename, current_file); - -DONE: - if (! ready_flag) - set_ready (); - - end_source = g_timeout_add (0, end_cb, NULL); - return NULL; -} - -void playback_play (int seek_time, bool_t pause) -{ - char * new_filename = playback_entry_get_filename (); - g_return_if_fail (new_filename); - - if (playing) - playback_cleanup (); - - current_filename = new_filename; - - playing = TRUE; - paused = pause; - - seek_request = (seek_time > 0) ? seek_time : -1; - - stopped = FALSE; - - hook_associate ("playlist update", update_cb, NULL); - pthread_create (& playback_thread_handle, NULL, playback_thread, NULL); - - hook_call ("playback begin", NULL); -} - -bool_t drct_get_playing (void) -{ - return playing; -} - -bool_t drct_get_paused (void) -{ - return paused; -} - -void drct_seek (int time) -{ - if (! playing) - return; - - wait_until_ready (); - - if (current_length < 1) - return; - - pthread_mutex_lock (& control_mutex); - - seek_request = time_offset + CLAMP (time, 0, current_length); - output_abort_write (); - - pthread_mutex_unlock (& control_mutex); -} - -bool_t input_open_audio (int format, int rate, int channels) -{ - g_return_val_if_fail (playing, FALSE); - - if (! output_open_audio (format, rate, channels)) - return FALSE; - - output_set_replaygain_info (& current_gain); - - if (paused) - output_pause (TRUE); - - current_samplerate = rate; - current_channels = channels; - - if (ready_flag) - event_queue ("info change", NULL); - - return TRUE; -} - -void input_set_gain (const ReplayGainInfo * info) -{ - g_return_if_fail (playing); - memcpy (& current_gain, info, sizeof current_gain); - output_set_replaygain_info (& current_gain); -} - -void input_write_audio (void * data, int length) -{ - g_return_if_fail (playing); - - if (! ready_flag) - set_ready (); - - int b = g_atomic_int_get (& repeat_b); - - if (b >= 0) - { - if (! output_write_audio (data, length, b)) - { - pthread_mutex_lock (& control_mutex); - seek_request = MAX (repeat_a, time_offset); - pthread_mutex_unlock (& control_mutex); - } - } - else - { - if (! output_write_audio (data, length, stop_time)) - g_atomic_int_set (& stop_flag, TRUE); - } -} - -int input_written_time (void) -{ - g_return_val_if_fail (playing, -1); - return output_written_time (); -} - -Tuple * input_get_tuple (void) -{ - g_return_val_if_fail (playing, NULL); - return playback_entry_get_tuple (); -} - -void input_set_tuple (Tuple * tuple) -{ - g_return_if_fail (playing); - playback_entry_set_tuple (tuple); -} - -void input_set_bitrate (int bitrate) -{ - g_return_if_fail (playing); - current_bitrate = bitrate; - - if (ready_flag) - event_queue ("info change", NULL); -} - -bool_t input_check_stop (void) -{ - g_return_val_if_fail (playing, TRUE); - return g_atomic_int_get (& stop_flag); -} - -int input_check_seek (void) -{ - g_return_val_if_fail (playing, -1); - - pthread_mutex_lock (& control_mutex); - int seek = seek_request; - - if (seek != -1) - { - output_set_time (seek); - seek_request = -1; - - event_queue ("playback seek", NULL); - } - - pthread_mutex_unlock (& control_mutex); - return seek; -} - -char * drct_get_filename (void) -{ - if (! playing) - return NULL; - - return str_ref (current_filename); -} - -char * drct_get_title (void) -{ - if (! playing) - return NULL; - - wait_until_ready (); - - char s[32]; - - if (current_length > 0) - { - char t[16]; - audgui_format_time (t, sizeof t, current_length); - snprintf (s, sizeof s, " (%s)", t); - } - else - s[0] = 0; - - if (get_bool (NULL, "show_numbers_in_pl")) - return str_printf ("%d. %s%s", 1 + current_entry, current_title, s); - - return str_printf ("%s%s", current_title, s); -} - -int drct_get_length (void) -{ - if (playing) - wait_until_ready (); - - return current_length; -} - -void drct_get_info (int * bitrate, int * samplerate, int * channels) -{ - if (playing) - wait_until_ready (); - - * bitrate = current_bitrate; - * samplerate = current_samplerate; - * channels = current_channels; -} - -void drct_get_volume (int * l, int * r) -{ - output_get_volume (l, r); -} - -void drct_set_volume (int l, int r) -{ - output_set_volume (CLAMP (l, 0, 100), CLAMP (r, 0, 100)); -} - -void drct_set_ab_repeat (int a, int b) -{ - if (! playing) - return; - - wait_until_ready (); - - if (current_length < 1) - return; - - if (a >= 0) - a += time_offset; - if (b >= 0) - b += time_offset; - - pthread_mutex_lock (& control_mutex); - - repeat_a = a; - g_atomic_int_set (& repeat_b, b); - - if (b != -1 && output_get_time () >= b) - { - seek_request = MAX (a, time_offset); - output_abort_write (); - } - - pthread_mutex_unlock (& control_mutex); -} - -void drct_get_ab_repeat (int * a, int * b) -{ - * a = (playing && repeat_a != -1) ? repeat_a - time_offset : -1; - * b = (playing && repeat_b != -1) ? repeat_b - time_offset : -1; -} diff --git a/src/audacious/playback.h b/src/audacious/playback.h deleted file mode 100644 index 2937791..0000000 --- a/src/audacious/playback.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * playback.h - * Copyright 2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_PLAYBACK_H -#define AUDACIOUS_PLAYBACK_H - -#include <libaudcore/core.h> - -/* for use from playback.c and playlist-new.c ONLY */ -/* anywhere else, use drct_* and/or playlist_* functions */ -void playback_play (int seek_time, bool_t pause); -void playback_stop (void); - -#endif /* AUDACIOUS_PLAYBACK_H */ diff --git a/src/audacious/playlist-files.c b/src/audacious/playlist-files.c deleted file mode 100644 index 8ebe521..0000000 --- a/src/audacious/playlist-files.c +++ /dev/null @@ -1,194 +0,0 @@ -/* - * playlist-files.c - * Copyright 2010-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <libaudcore/audstrings.h> - -#include "debug.h" -#include "i18n.h" -#include "misc.h" -#include "playlist.h" -#include "plugin.h" -#include "plugins.h" - -typedef struct -{ - const char * filename; - char * title; - Index * filenames; - Index * tuples; - bool_t plugin_found; - bool_t success; -} -PlaylistData; - -static void plugin_for_filename (const char * filename, PluginForEachFunc func, void * data) -{ - char ext[32]; - if (uri_get_extension (filename, ext, sizeof ext)) - playlist_plugin_for_ext (ext, func, data); -} - -static bool_t plugin_found_cb (PluginHandle * plugin, void * data) -{ - * (PluginHandle * *) data = plugin; - return FALSE; /* stop when first plugin is found */ -} - -bool_t filename_is_playlist (const char * filename) -{ - PluginHandle * plugin = NULL; - plugin_for_filename (filename, plugin_found_cb, & plugin); - return (plugin != NULL); -} - -static bool_t playlist_load_cb (PluginHandle * plugin, void * data_) -{ - PlaylistData * data = (PlaylistData *) data_; - - PlaylistPlugin * pp = plugin_get_header (plugin); - if (! pp || ! PLUGIN_HAS_FUNC (pp, load)) - return TRUE; /* try another plugin */ - - data->plugin_found = TRUE; - - VFSFile * file = vfs_fopen (data->filename, "r"); - if (! file) - return FALSE; /* stop if we can't open file */ - - data->success = pp->load (data->filename, file, & data->title, data->filenames, data->tuples); - - vfs_fclose (file); - return ! data->success; /* stop when playlist is loaded */ -} - -bool_t playlist_load (const char * filename, char * * title, Index * * filenames, Index * * tuples) -{ - PlaylistData data = - { - .filename = filename, - .filenames = index_new (), - .tuples = index_new () - }; - - AUDDBG ("Loading playlist %s.\n", filename); - plugin_for_filename (filename, playlist_load_cb, & data); - - if (! data.plugin_found) - { - SPRINTF (error, _("Cannot load %s: unsupported file extension."), filename); - interface_show_error (error); - } - - if (! data.success) - { - str_unref (data.title); - index_free_full (data.filenames, (IndexFreeFunc) str_unref); - index_free_full (data.tuples, (IndexFreeFunc) tuple_unref); - return FALSE; - } - - if (index_count (data.tuples)) - g_return_val_if_fail (index_count (data.tuples) == index_count (data.filenames), FALSE); - else - { - index_free (data.tuples); - data.tuples = NULL; - } - - * title = data.title; - * filenames = data.filenames; - * tuples = data.tuples; - return TRUE; -} - -bool_t playlist_insert_playlist_raw (int list, int at, const char * filename) -{ - char * title = NULL; - Index * filenames, * tuples; - - if (! playlist_load (filename, & title, & filenames, & tuples)) - return FALSE; - - if (title && ! playlist_entry_count (list)) - playlist_set_title (list, title); - - playlist_entry_insert_batch_raw (list, at, filenames, tuples, NULL); - - str_unref (title); - return TRUE; -} - -static bool_t playlist_save_cb (PluginHandle * plugin, void * data_) -{ - PlaylistData * data = data_; - - PlaylistPlugin * pp = plugin_get_header (plugin); - if (! pp || ! PLUGIN_HAS_FUNC (pp, save)) - return TRUE; /* try another plugin */ - - data->plugin_found = TRUE; - - VFSFile * file = vfs_fopen (data->filename, "w"); - if (! file) - return FALSE; /* stop if we can't open file */ - - data->success = pp->save (data->filename, file, data->title, data->filenames, data->tuples); - - vfs_fclose (file); - return FALSE; /* stop after first attempt (successful or not) */ -} - -bool_t playlist_save (int list, const char * filename) -{ - PlaylistData data = - { - .filename = filename, - .title = playlist_get_title (list), - .filenames = index_new (), - .tuples = index_new () - }; - - int entries = playlist_entry_count (list); - bool_t fast = get_bool (NULL, "metadata_on_play"); - - index_allocate (data.filenames, entries); - index_allocate (data.tuples, entries); - - for (int i = 0; i < entries; i ++) - { - index_insert (data.filenames, -1, playlist_entry_get_filename (list, i)); - index_insert (data.tuples, -1, playlist_entry_get_tuple (list, i, fast)); - } - - AUDDBG ("Saving playlist %s.\n", filename); - plugin_for_filename (filename, playlist_save_cb, & data); - - if (! data.plugin_found) - { - SPRINTF (error, _("Cannot save %s: unsupported file extension."), filename); - interface_show_error (error); - } - - str_unref (data.title); - index_free_full (data.filenames, (IndexFreeFunc) str_unref); - index_free_full (data.tuples, (IndexFreeFunc) tuple_unref); - - return data.success; -} diff --git a/src/audacious/playlist-new.c b/src/audacious/playlist-new.c deleted file mode 100644 index 23696b0..0000000 --- a/src/audacious/playlist-new.c +++ /dev/null @@ -1,2401 +0,0 @@ -/* - * playlist-new.c - * Copyright 2009-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <pthread.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> - -#include <glib.h> -#include <glib/gstdio.h> - -#include <libaudcore/audstrings.h> -#include <libaudcore/hook.h> -#include <libaudcore/tuple.h> - -#include "drct.h" -#include "i18n.h" -#include "misc.h" -#include "playback.h" -#include "playlist.h" -#include "plugins.h" -#include "scanner.h" -#include "util.h" - -enum {RESUME_STOP, RESUME_PLAY, RESUME_PAUSE}; - -#define STATE_FILE "playlist-state" - -#define ENTER pthread_mutex_lock (& mutex) -#define LEAVE pthread_mutex_unlock (& mutex) - -#define RETURN(...) do { \ - pthread_mutex_unlock (& mutex); \ - return __VA_ARGS__; \ -} while (0) - -#define ENTER_GET_PLAYLIST(...) ENTER; \ - Playlist * playlist = lookup_playlist (playlist_num); \ - if (! playlist) \ - RETURN (__VA_ARGS__); - -#define ENTER_GET_ENTRY(...) ENTER_GET_PLAYLIST (__VA_ARGS__); \ - Entry * entry = lookup_entry (playlist, entry_num); \ - if (! entry) \ - RETURN (__VA_ARGS__); - -typedef struct { - int level, before, after; -} Update; - -typedef struct { - int number; - char * filename; - PluginHandle * decoder; - Tuple * tuple; - char * formatted, * title, * artist, * album; - int length; - bool_t failed; - bool_t selected; - int shuffle_num; - bool_t queued; -} Entry; - -typedef struct { - int number, unique_id; - char * filename, * title; - bool_t modified; - Index * entries; - Entry * position, * focus; - int selected_count; - int last_shuffle_num; - GList * queued; - int64_t total_length, selected_length; - bool_t scanning, scan_ending; - Update next_update, last_update; - bool_t resume_paused; - int resume_time; -} Playlist; - -static const char * const default_title = N_("New Playlist"); -static const char * const temp_title = N_("Now Playing"); - -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; - -/* The unique ID table contains pointers to Playlist for ID's in use and NULL - * for "dead" (previously used and therefore unavailable) ID's. */ -static GHashTable * unique_id_table = NULL; -static int next_unique_id = 1000; - -static Index * playlists = NULL; -static Playlist * active_playlist = NULL; -static Playlist * playing_playlist = NULL; -static int resume_playlist = -1; - -static int update_source = 0, update_level; - -typedef struct { - Playlist * playlist; - Entry * entry; - ScanRequest * request; -} ScanItem; - -static int scan_playlist, scan_row; -static GList * scan_list = NULL; - -static void scan_finish (ScanRequest * request); -static void scan_cancel (Entry * entry); -static void scan_restart (void); - -static bool_t next_song_locked (Playlist * playlist, bool_t repeat, int hint); - -static TupleFormatter * title_formatter; - -static void entry_set_tuple_real (Entry * entry, Tuple * tuple) -{ - /* Hack: We cannot refresh segmented entries (since their info is read from - * the cue sheet when it is first loaded), so leave them alone. -jlindgren */ - if (entry->tuple && tuple_get_value_type (entry->tuple, FIELD_SEGMENT_START) == TUPLE_INT) - { - if (tuple) - tuple_unref (tuple); - return; - } - - if (entry->tuple) - tuple_unref (entry->tuple); - - entry->tuple = tuple; - entry->failed = FALSE; - - str_unref (entry->formatted); - str_unref (entry->title); - str_unref (entry->artist); - str_unref (entry->album); - - describe_song (entry->filename, tuple, & entry->title, & entry->artist, & entry->album); - - if (! tuple) - { - entry->formatted = NULL; - entry->length = 0; - } - else - { - entry->formatted = tuple_format_title (title_formatter, tuple); - entry->length = tuple_get_int (tuple, FIELD_LENGTH); - if (entry->length < 0) - entry->length = 0; - } -} - -static void entry_set_tuple (Playlist * playlist, Entry * entry, Tuple * tuple) -{ - scan_cancel (entry); - - if (entry->tuple) - { - playlist->total_length -= entry->length; - if (entry->selected) - playlist->selected_length -= entry->length; - } - - entry_set_tuple_real (entry, tuple); - - if (tuple) - { - playlist->total_length += entry->length; - if (entry->selected) - playlist->selected_length += entry->length; - } -} - -static void entry_set_failed (Playlist * playlist, Entry * entry) -{ - entry_set_tuple (playlist, entry, tuple_new_from_filename (entry->filename)); - entry->failed = TRUE; -} - -static Entry * entry_new (char * filename, Tuple * tuple, PluginHandle * decoder) -{ - Entry * entry = g_slice_new (Entry); - - entry->filename = filename; - entry->decoder = decoder; - entry->tuple = NULL; - entry->formatted = NULL; - entry->title = NULL; - entry->artist = NULL; - entry->album = NULL; - entry->failed = FALSE; - entry->number = -1; - entry->selected = FALSE; - entry->shuffle_num = 0; - entry->queued = FALSE; - - entry_set_tuple_real (entry, tuple); - return entry; -} - -static void entry_free (Entry * entry) -{ - scan_cancel (entry); - - str_unref (entry->filename); - if (entry->tuple) - tuple_unref (entry->tuple); - - str_unref (entry->formatted); - str_unref (entry->title); - str_unref (entry->artist); - str_unref (entry->album); - g_slice_free (Entry, entry); -} - -static int new_unique_id (int preferred) -{ - if (preferred >= 0 && ! g_hash_table_lookup_extended (unique_id_table, - GINT_TO_POINTER (preferred), NULL, NULL)) - return preferred; - - while (g_hash_table_lookup_extended (unique_id_table, - GINT_TO_POINTER (next_unique_id), NULL, NULL)) - next_unique_id ++; - - return next_unique_id ++; -} - -static Playlist * playlist_new (int id) -{ - Playlist * playlist = g_slice_new (Playlist); - - playlist->number = -1; - playlist->unique_id = new_unique_id (id); - playlist->filename = NULL; - playlist->title = str_get (_(default_title)); - playlist->modified = TRUE; - playlist->entries = index_new(); - playlist->position = NULL; - playlist->focus = NULL; - playlist->selected_count = 0; - playlist->last_shuffle_num = 0; - playlist->queued = NULL; - playlist->total_length = 0; - playlist->selected_length = 0; - playlist->scanning = FALSE; - playlist->scan_ending = FALSE; - playlist->resume_paused = FALSE; - playlist->resume_time = 0; - - memset (& playlist->last_update, 0, sizeof (Update)); - memset (& playlist->next_update, 0, sizeof (Update)); - - g_hash_table_insert (unique_id_table, GINT_TO_POINTER (playlist->unique_id), playlist); - return playlist; -} - -static void playlist_free (Playlist * playlist) -{ - g_hash_table_insert (unique_id_table, GINT_TO_POINTER (playlist->unique_id), NULL); - - str_unref (playlist->filename); - str_unref (playlist->title); - index_free_full (playlist->entries, (IndexFreeFunc) entry_free); - g_list_free (playlist->queued); - g_slice_free (Playlist, playlist); -} - -static void number_playlists (int at, int length) -{ - for (int count = 0; count < length; count ++) - { - Playlist * playlist = index_get (playlists, at + count); - playlist->number = at + count; - } -} - -static Playlist * lookup_playlist (int playlist_num) -{ - return (playlists && playlist_num >= 0 && playlist_num < index_count - (playlists)) ? index_get (playlists, playlist_num) : NULL; -} - -static void number_entries (Playlist * playlist, int at, int length) -{ - for (int count = 0; count < length; count ++) - { - Entry * entry = index_get (playlist->entries, at + count); - entry->number = at + count; - } -} - -static Entry * lookup_entry (Playlist * playlist, int entry_num) -{ - return (entry_num >= 0 && entry_num < index_count (playlist->entries)) ? - index_get (playlist->entries, entry_num) : NULL; -} - -static bool_t update (void * unused) -{ - ENTER; - - for (int i = 0; i < index_count (playlists); i ++) - { - Playlist * p = index_get (playlists, i); - memcpy (& p->last_update, & p->next_update, sizeof (Update)); - memset (& p->next_update, 0, sizeof (Update)); - } - - int level = update_level; - update_level = 0; - - if (update_source) - { - g_source_remove (update_source); - update_source = 0; - } - - LEAVE; - - hook_call ("playlist update", GINT_TO_POINTER (level)); - return FALSE; -} - -static void queue_update (int level, int list, int at, int count) -{ - Playlist * p = lookup_playlist (list); - - if (p) - { - if (level >= PLAYLIST_UPDATE_METADATA) - { - p->modified = TRUE; - - if (! get_bool (NULL, "metadata_on_play")) - { - p->scanning = TRUE; - p->scan_ending = FALSE; - scan_restart (); - } - } - - if (p->next_update.level) - { - p->next_update.level = MAX (p->next_update.level, level); - p->next_update.before = MIN (p->next_update.before, at); - p->next_update.after = MIN (p->next_update.after, - index_count (p->entries) - at - count); - } - else - { - p->next_update.level = level; - p->next_update.before = at; - p->next_update.after = index_count (p->entries) - at - count; - } - } - - update_level = MAX (update_level, level); - - if (! update_source) - update_source = g_idle_add_full (G_PRIORITY_HIGH, update, NULL, NULL); -} - -bool_t playlist_update_pending (void) -{ - ENTER; - bool_t pending = update_level ? TRUE : FALSE; - RETURN (pending); -} - -int playlist_updated_range (int playlist_num, int * at, int * count) -{ - ENTER_GET_PLAYLIST (0); - - Update * u = & playlist->last_update; - - int level = u->level; - * at = u->before; - * count = index_count (playlist->entries) - u->before - u->after; - - RETURN (level); -} - -bool_t playlist_scan_in_progress (int playlist_num) -{ - if (playlist_num >= 0) - { - ENTER_GET_PLAYLIST (FALSE); - bool_t scanning = playlist->scanning || playlist->scan_ending; - RETURN (scanning); - } - else - { - ENTER; - - bool_t scanning = FALSE; - for (playlist_num = 0; playlist_num < index_count (playlists); playlist_num ++) - { - Playlist * playlist = index_get (playlists, playlist_num); - if (playlist->scanning || playlist->scan_ending) - scanning = TRUE; - } - - RETURN (scanning); - } -} - -static GList * scan_list_find_playlist (Playlist * playlist) -{ - for (GList * node = scan_list; node; node = node->next) - { - ScanItem * item = node->data; - if (item->playlist == playlist) - return node; - } - - return NULL; -} - -static GList * scan_list_find_entry (Entry * entry) -{ - for (GList * node = scan_list; node; node = node->next) - { - ScanItem * item = node->data; - if (item->entry == entry) - return node; - } - - return NULL; -} - -static GList * scan_list_find_request (ScanRequest * request) -{ - for (GList * node = scan_list; node; node = node->next) - { - ScanItem * item = node->data; - if (item->request == request) - return node; - } - - return NULL; -} - -static void scan_queue_entry (Playlist * playlist, Entry * entry) -{ - int flags = 0; - if (! entry->tuple) - flags |= SCAN_TUPLE; - - ScanItem * item = g_slice_new (ScanItem); - item->playlist = playlist; - item->entry = entry; - item->request = scan_request (entry->filename, flags, entry->decoder, scan_finish); - scan_list = g_list_prepend (scan_list, item); -} - -static void scan_check_complete (Playlist * playlist) -{ - if (! playlist->scan_ending || scan_list_find_playlist (playlist)) - return; - - playlist->scan_ending = FALSE; - event_queue_cancel ("playlist scan complete", NULL); - event_queue ("playlist scan complete", NULL); -} - -static bool_t scan_queue_next_entry (void) -{ - while (scan_playlist < index_count (playlists)) - { - Playlist * playlist = index_get (playlists, scan_playlist); - - if (playlist->scanning) - { - while (scan_row < index_count (playlist->entries)) - { - Entry * entry = index_get (playlist->entries, scan_row ++); - - if (! entry->tuple && ! scan_list_find_entry (entry)) - { - scan_queue_entry (playlist, entry); - return TRUE; - } - } - - playlist->scanning = FALSE; - playlist->scan_ending = TRUE; - scan_check_complete (playlist); - } - - scan_playlist ++; - scan_row = 0; - } - - return FALSE; -} - -static void scan_schedule (void) -{ - while (g_list_length (scan_list) < SCAN_THREADS) - { - if (! scan_queue_next_entry ()) - break; - } -} - -static void scan_finish (ScanRequest * request) -{ - ENTER; - - GList * node = scan_list_find_request (request); - if (! node) - RETURN (); - - ScanItem * item = node->data; - Playlist * playlist = item->playlist; - Entry * entry = item->entry; - bool_t changed = FALSE; - - scan_list = g_list_delete_link (scan_list, node); - g_slice_free (ScanItem, item); - - if (! entry->decoder) - entry->decoder = scan_request_get_decoder (request); - - if (! entry->tuple) - { - Tuple * tuple = scan_request_get_tuple (request); - if (tuple) - { - entry_set_tuple (playlist, entry, tuple); - changed = TRUE; - } - } - - if (! entry->decoder || ! entry->tuple) - entry_set_failed (playlist, entry); - - if (changed) - queue_update (PLAYLIST_UPDATE_METADATA, playlist->number, entry->number, 1); - - scan_check_complete (playlist); - scan_schedule (); - - pthread_cond_broadcast (& cond); - - LEAVE; -} - -static void scan_cancel (Entry * entry) -{ - GList * node = scan_list_find_entry (entry); - if (! node) - return; - - ScanItem * item = node->data; - scan_list = g_list_delete_link (scan_list, node); - g_slice_free (ScanItem, item); -} - -static void scan_restart (void) -{ - scan_playlist = 0; - scan_row = 0; - scan_schedule (); -} - -/* mutex may be unlocked during the call */ -static Entry * get_entry (int playlist_num, int entry_num, - bool_t need_decoder, bool_t need_tuple) -{ - while (1) - { - Playlist * playlist = lookup_playlist (playlist_num); - Entry * entry = playlist ? lookup_entry (playlist, entry_num) : NULL; - - if (! entry || entry->failed) - return entry; - - if ((need_decoder && ! entry->decoder) || (need_tuple && ! entry->tuple)) - { - if (! scan_list_find_entry (entry)) - scan_queue_entry (playlist, entry); - - pthread_cond_wait (& cond, & mutex); - continue; - } - - return entry; - } -} - -/* mutex may be unlocked during the call */ -static Entry * get_playback_entry (bool_t need_decoder, bool_t need_tuple) -{ - while (1) - { - Entry * entry = playing_playlist ? playing_playlist->position : NULL; - - if (! entry || entry->failed) - return entry; - - if ((need_decoder && ! entry->decoder) || (need_tuple && ! entry->tuple)) - { - if (! scan_list_find_entry (entry)) - scan_queue_entry (playing_playlist, entry); - - pthread_cond_wait (& cond, & mutex); - continue; - } - - return entry; - } -} - -void playlist_init (void) -{ - srand (time (NULL)); - - ENTER; - - unique_id_table = g_hash_table_new (g_direct_hash, g_direct_equal); - playlists = index_new (); - - update_level = 0; - scan_playlist = scan_row = 0; - - LEAVE; - - /* initialize title formatter */ - playlist_reformat_titles (); -} - -void playlist_end (void) -{ - ENTER; - - if (update_source) - { - g_source_remove (update_source); - update_source = 0; - } - - active_playlist = playing_playlist = NULL; - resume_playlist = -1; - - index_free_full (playlists, (IndexFreeFunc) playlist_free); - playlists = NULL; - - g_hash_table_destroy (unique_id_table); - unique_id_table = NULL; - - tuple_formatter_free (title_formatter); - title_formatter = NULL; - - LEAVE; -} - -int playlist_count (void) -{ - ENTER; - int count = index_count (playlists); - RETURN (count); -} - -void playlist_insert_with_id (int at, int id) -{ - ENTER; - - if (at < 0 || at > index_count (playlists)) - at = index_count (playlists); - - index_insert (playlists, at, playlist_new (id)); - number_playlists (at, index_count (playlists) - at); - - queue_update (PLAYLIST_UPDATE_STRUCTURE, -1, 0, 0); - LEAVE; -} - -void playlist_insert (int at) -{ - playlist_insert_with_id (at, -1); -} - -void playlist_reorder (int from, int to, int count) -{ - ENTER; - - if (from < 0 || from + count > index_count (playlists) || to < 0 || to + - count > index_count (playlists) || count < 0) - RETURN (); - - Index * displaced = index_new (); - - if (to < from) - index_copy_insert (playlists, to, displaced, -1, from - to); - else - index_copy_insert (playlists, from + count, displaced, -1, to - from); - - index_copy_set (playlists, from, playlists, to, count); - - if (to < from) - { - index_copy_set (displaced, 0, playlists, to + count, from - to); - number_playlists (to, from + count - to); - } - else - { - index_copy_set (displaced, 0, playlists, from, to - from); - number_playlists (from, to + count - from); - } - - index_free (displaced); - - queue_update (PLAYLIST_UPDATE_STRUCTURE, -1, 0, 0); - LEAVE; -} - -void playlist_delete (int playlist_num) -{ - ENTER_GET_PLAYLIST (); - - bool_t was_playing = (playlist == playing_playlist); - - index_delete_full (playlists, playlist_num, 1, (IndexFreeFunc) playlist_free); - - if (! index_count (playlists)) - index_insert (playlists, 0, playlist_new (-1)); - - number_playlists (playlist_num, index_count (playlists) - playlist_num); - - if (playlist == active_playlist) - active_playlist = index_get (playlists, MIN (playlist_num, index_count (playlists) - 1)); - if (playlist == playing_playlist) - playing_playlist = NULL; - - queue_update (PLAYLIST_UPDATE_STRUCTURE, -1, 0, 0); - LEAVE; - - if (was_playing) - playback_stop (); -} - -int playlist_get_unique_id (int playlist_num) -{ - ENTER_GET_PLAYLIST (-1); - int unique_id = playlist->unique_id; - RETURN (unique_id); -} - -int playlist_by_unique_id (int id) -{ - ENTER; - - Playlist * p = g_hash_table_lookup (unique_id_table, GINT_TO_POINTER (id)); - int num = p ? p->number : -1; - - RETURN (num); -} - -void playlist_set_filename (int playlist_num, const char * filename) -{ - ENTER_GET_PLAYLIST (); - - str_unref (playlist->filename); - playlist->filename = str_get (filename); - playlist->modified = TRUE; - - queue_update (PLAYLIST_UPDATE_METADATA, -1, 0, 0); - LEAVE; -} - -char * playlist_get_filename (int playlist_num) -{ - ENTER_GET_PLAYLIST (NULL); - char * filename = str_ref (playlist->filename); - RETURN (filename); -} - -void playlist_set_title (int playlist_num, const char * title) -{ - ENTER_GET_PLAYLIST (); - - str_unref (playlist->title); - playlist->title = str_get (title); - playlist->modified = TRUE; - - queue_update (PLAYLIST_UPDATE_METADATA, -1, 0, 0); - LEAVE; -} - -char * playlist_get_title (int playlist_num) -{ - ENTER_GET_PLAYLIST (NULL); - char * title = str_ref (playlist->title); - RETURN (title); -} - -void playlist_set_modified (int playlist_num, bool_t modified) -{ - ENTER_GET_PLAYLIST (); - playlist->modified = modified; - LEAVE; -} - -bool_t playlist_get_modified (int playlist_num) -{ - ENTER_GET_PLAYLIST (FALSE); - bool_t modified = playlist->modified; - RETURN (modified); -} - -void playlist_set_active (int playlist_num) -{ - ENTER_GET_PLAYLIST (); - - bool_t changed = FALSE; - - if (playlist != active_playlist) - { - changed = TRUE; - active_playlist = playlist; - } - - LEAVE; - - if (changed) - hook_call ("playlist activate", NULL); -} - -int playlist_get_active (void) -{ - ENTER; - int list = active_playlist ? active_playlist->number : -1; - RETURN (list); -} - -void playlist_set_playing (int playlist_num) -{ - /* get playback state before locking playlists */ - bool_t paused = drct_get_paused (); - int time = drct_get_time (); - - ENTER; - - Playlist * playlist = lookup_playlist (playlist_num); - bool_t can_play = FALSE; - bool_t position_changed = FALSE; - - if (playlist == playing_playlist) - RETURN (); - - if (playing_playlist) - { - playing_playlist->resume_paused = paused; - playing_playlist->resume_time = time; - } - - /* is there anything to play? */ - if (playlist && ! playlist->position) - { - if (next_song_locked (playlist, TRUE, 0)) - position_changed = TRUE; - else - playlist = NULL; - } - - if (playlist) - { - can_play = TRUE; - paused = playlist->resume_paused; - time = playlist->resume_time; - } - - playing_playlist = playlist; - - LEAVE; - - if (position_changed) - hook_call ("playlist position", GINT_TO_POINTER (playlist_num)); - - hook_call ("playlist set playing", NULL); - - /* start playback after unlocking playlists */ - if (can_play) - playback_play (time, paused); - else - playback_stop (); -} - -int playlist_get_playing (void) -{ - ENTER; - int list = playing_playlist ? playing_playlist->number: -1; - RETURN (list); -} - -int playlist_get_blank (void) -{ - int list = playlist_get_active (); - char * title = playlist_get_title (list); - - if (strcmp (title, _(default_title)) || playlist_entry_count (list) > 0) - { - list = playlist_count (); - playlist_insert (list); - } - - str_unref (title); - return list; -} - -int playlist_get_temporary (void) -{ - int list, count = playlist_count (); - bool_t found = FALSE; - - for (list = 0; list < count; list ++) - { - char * title = playlist_get_title (list); - found = ! strcmp (title, _(temp_title)); - str_unref (title); - - if (found) - break; - } - - if (! found) - { - list = playlist_get_blank (); - playlist_set_title (list, _(temp_title)); - } - - return list; -} - -static void set_position (Playlist * playlist, Entry * entry, bool_t update_shuffle) -{ - playlist->position = entry; - playlist->resume_time = 0; - - /* move entry to top of shuffle list */ - if (entry && update_shuffle) - entry->shuffle_num = ++ playlist->last_shuffle_num; -} - -/* unlocked */ -static void change_playback (bool_t can_play) -{ - if (can_play && drct_get_playing ()) - playback_play (0, drct_get_paused ()); - else - playlist_set_playing (-1); -} - -int playlist_entry_count (int playlist_num) -{ - ENTER_GET_PLAYLIST (0); - int count = index_count (playlist->entries); - RETURN (count); -} - -void playlist_entry_insert_batch_raw (int playlist_num, int at, - Index * filenames, Index * tuples, Index * decoders) -{ - ENTER_GET_PLAYLIST (); - - int entries = index_count (playlist->entries); - - if (at < 0 || at > entries) - at = entries; - - int number = index_count (filenames); - - Index * add = index_new (); - index_allocate (add, number); - - for (int i = 0; i < number; i ++) - { - char * filename = index_get (filenames, i); - Tuple * tuple = tuples ? index_get (tuples, i) : NULL; - PluginHandle * decoder = decoders ? index_get (decoders, i) : NULL; - index_insert (add, -1, entry_new (filename, tuple, decoder)); - } - - index_free (filenames); - if (decoders) - index_free (decoders); - if (tuples) - index_free (tuples); - - number = index_count (add); - index_copy_insert (add, 0, playlist->entries, at, -1); - index_free (add); - - number_entries (playlist, at, entries + number - at); - - for (int count = 0; count < number; count ++) - { - Entry * entry = index_get (playlist->entries, at + count); - playlist->total_length += entry->length; - } - - queue_update (PLAYLIST_UPDATE_STRUCTURE, playlist->number, at, number); - LEAVE; -} - -void playlist_entry_delete (int playlist_num, int at, int number) -{ - ENTER_GET_PLAYLIST (); - - int entries = index_count (playlist->entries); - bool_t position_changed = FALSE; - bool_t was_playing = FALSE; - bool_t can_play = FALSE; - - if (at < 0 || at > entries) - at = entries; - if (number < 0 || number > entries - at) - number = entries - at; - - if (playlist->position && playlist->position->number >= at && - playlist->position->number < at + number) - { - position_changed = TRUE; - was_playing = (playlist == playing_playlist); - - set_position (playlist, NULL, FALSE); - } - - if (playlist->focus && playlist->focus->number >= at && - playlist->focus->number < at + number) - { - if (at + number < entries) - playlist->focus = index_get (playlist->entries, at + number); - else if (at > 0) - playlist->focus = index_get (playlist->entries, at - 1); - else - playlist->focus = NULL; - } - - for (int count = 0; count < number; count ++) - { - Entry * entry = index_get (playlist->entries, at + count); - - if (entry->queued) - playlist->queued = g_list_remove (playlist->queued, entry); - - if (entry->selected) - { - playlist->selected_count --; - playlist->selected_length -= entry->length; - } - - playlist->total_length -= entry->length; - } - - index_delete_full (playlist->entries, at, number, (IndexFreeFunc) entry_free); - number_entries (playlist, at, entries - at - number); - - if (position_changed && get_bool (NULL, "advance_on_delete")) - can_play = next_song_locked (playlist, get_bool (NULL, "repeat"), at); - - queue_update (PLAYLIST_UPDATE_STRUCTURE, playlist->number, at, 0); - LEAVE; - - if (position_changed) - hook_call ("playlist position", GINT_TO_POINTER (playlist_num)); - if (was_playing) - change_playback (can_play); -} - -char * playlist_entry_get_filename (int playlist_num, int entry_num) -{ - ENTER_GET_ENTRY (NULL); - char * filename = str_ref (entry->filename); - RETURN (filename); -} - -PluginHandle * playlist_entry_get_decoder (int playlist_num, int entry_num, bool_t fast) -{ - ENTER; - - Entry * entry = get_entry (playlist_num, entry_num, ! fast, FALSE); - PluginHandle * decoder = entry ? entry->decoder : NULL; - - RETURN (decoder); -} - -Tuple * playlist_entry_get_tuple (int playlist_num, int entry_num, bool_t fast) -{ - ENTER; - - Entry * entry = get_entry (playlist_num, entry_num, FALSE, ! fast); - Tuple * tuple = entry ? entry->tuple : NULL; - - if (tuple) - tuple_ref (tuple); - - RETURN (tuple); -} - -char * playlist_entry_get_title (int playlist_num, int entry_num, bool_t fast) -{ - ENTER; - - Entry * entry = get_entry (playlist_num, entry_num, FALSE, ! fast); - char * title = entry ? str_ref (entry->formatted ? entry->formatted : entry->title) : NULL; - - RETURN (title); -} - -void playlist_entry_describe (int playlist_num, int entry_num, - char * * title, char * * artist, char * * album, bool_t fast) -{ - ENTER; - - Entry * entry = get_entry (playlist_num, entry_num, FALSE, ! fast); - - if (title) - * title = (entry && entry->title) ? str_ref (entry->title) : NULL; - if (artist) - * artist = (entry && entry->artist) ? str_ref (entry->artist) : NULL; - if (album) - * album = (entry && entry->album) ? str_ref (entry->album) : NULL; - - LEAVE; -} - -int playlist_entry_get_length (int playlist_num, int entry_num, bool_t fast) -{ - ENTER; - - Entry * entry = get_entry (playlist_num, entry_num, FALSE, ! fast); - int length = entry ? entry->length : 0; - - RETURN (length); -} - -void playlist_set_position (int playlist_num, int entry_num) -{ - ENTER_GET_PLAYLIST (); - - Entry * entry = lookup_entry (playlist, entry_num); - bool_t was_playing = (playlist == playing_playlist); - bool_t can_play = !! entry; - - set_position (playlist, entry, TRUE); - - LEAVE; - - hook_call ("playlist position", GINT_TO_POINTER (playlist_num)); - if (was_playing) - change_playback (can_play); -} - -int playlist_get_position (int playlist_num) -{ - ENTER_GET_PLAYLIST (-1); - int position = playlist->position ? playlist->position->number : -1; - RETURN (position); -} - -void playlist_set_focus (int playlist_num, int entry_num) -{ - ENTER_GET_PLAYLIST (); - - int first = INT_MAX; - int last = -1; - - if (playlist->focus) - { - first = MIN (first, playlist->focus->number); - last = MAX (last, playlist->focus->number); - } - - playlist->focus = lookup_entry (playlist, entry_num); - - if (playlist->focus) - { - first = MIN (first, playlist->focus->number); - last = MAX (last, playlist->focus->number); - } - - if (first <= last) - queue_update (PLAYLIST_UPDATE_SELECTION, playlist_num, first, last + 1 - first); - - LEAVE; -} - -int playlist_get_focus (int playlist_num) -{ - ENTER_GET_PLAYLIST (-1); - int focus = playlist->focus ? playlist->focus->number : -1; - RETURN (focus); -} - -void playlist_entry_set_selected (int playlist_num, int entry_num, - bool_t selected) -{ - ENTER_GET_ENTRY (); - - if (entry->selected == selected) - RETURN (); - - entry->selected = selected; - - if (selected) - { - playlist->selected_count++; - playlist->selected_length += entry->length; - } - else - { - playlist->selected_count--; - playlist->selected_length -= entry->length; - } - - queue_update (PLAYLIST_UPDATE_SELECTION, playlist->number, entry_num, 1); - LEAVE; -} - -bool_t playlist_entry_get_selected (int playlist_num, int entry_num) -{ - ENTER_GET_ENTRY (FALSE); - bool_t selected = entry->selected; - RETURN (selected); -} - -int playlist_selected_count (int playlist_num) -{ - ENTER_GET_PLAYLIST (0); - int selected_count = playlist->selected_count; - RETURN (selected_count); -} - -void playlist_select_all (int playlist_num, bool_t selected) -{ - ENTER_GET_PLAYLIST (); - - int entries = index_count (playlist->entries); - int first = entries, last = 0; - - for (int count = 0; count < entries; count ++) - { - Entry * entry = index_get (playlist->entries, count); - - if ((selected && ! entry->selected) || (entry->selected && ! selected)) - { - entry->selected = selected; - first = MIN (first, entry->number); - last = entry->number; - } - } - - if (selected) - { - playlist->selected_count = entries; - playlist->selected_length = playlist->total_length; - } - else - { - playlist->selected_count = 0; - playlist->selected_length = 0; - } - - if (first < entries) - queue_update (PLAYLIST_UPDATE_SELECTION, playlist->number, first, last + 1 - first); - - LEAVE; -} - -int playlist_shift (int playlist_num, int entry_num, int distance) -{ - ENTER_GET_ENTRY (0); - - if (! entry->selected || ! distance) - RETURN (0); - - int entries = index_count (playlist->entries); - int shift = 0, center, top, bottom; - - if (distance < 0) - { - for (center = entry_num; center > 0 && shift > distance; ) - { - entry = index_get (playlist->entries, -- center); - if (! entry->selected) - shift --; - } - } - else - { - for (center = entry_num + 1; center < entries && shift < distance; ) - { - entry = index_get (playlist->entries, center ++); - if (! entry->selected) - shift ++; - } - } - - top = bottom = center; - - for (int i = 0; i < top; i ++) - { - entry = index_get (playlist->entries, i); - if (entry->selected) - top = i; - } - - for (int i = entries; i > bottom; i --) - { - entry = index_get (playlist->entries, i - 1); - if (entry->selected) - bottom = i; - } - - Index * temp = index_new (); - - for (int i = top; i < center; i ++) - { - entry = index_get (playlist->entries, i); - if (! entry->selected) - index_insert (temp, -1, entry); - } - - for (int i = top; i < bottom; i ++) - { - entry = index_get (playlist->entries, i); - if (entry->selected) - index_insert (temp, -1, entry); - } - - for (int i = center; i < bottom; i ++) - { - entry = index_get (playlist->entries, i); - if (! entry->selected) - index_insert (temp, -1, entry); - } - - index_copy_set (temp, 0, playlist->entries, top, bottom - top); - - number_entries (playlist, top, bottom - top); - queue_update (PLAYLIST_UPDATE_STRUCTURE, playlist->number, top, bottom - top); - - RETURN (shift); -} - -static Entry * find_unselected_focus (Playlist * playlist) -{ - if (! playlist->focus || ! playlist->focus->selected) - return playlist->focus; - - int entries = index_count (playlist->entries); - - for (int search = playlist->focus->number + 1; search < entries; search ++) - { - Entry * entry = index_get (playlist->entries, search); - if (! entry->selected) - return entry; - } - - for (int search = playlist->focus->number; search --;) - { - Entry * entry = index_get (playlist->entries, search); - if (! entry->selected) - return entry; - } - - return NULL; -} - -void playlist_delete_selected (int playlist_num) -{ - ENTER_GET_PLAYLIST (); - - if (! playlist->selected_count) - RETURN (); - - int entries = index_count (playlist->entries); - bool_t position_changed = FALSE; - bool_t was_playing = FALSE; - bool_t can_play = FALSE; - - Index * others = index_new (); - index_allocate (others, entries - playlist->selected_count); - - if (playlist->position && playlist->position->selected) - { - position_changed = TRUE; - was_playing = (playlist == playing_playlist); - - set_position (playlist, NULL, FALSE); - } - - playlist->focus = find_unselected_focus (playlist); - - int before = 0, after = 0; - bool_t found = FALSE; - - for (int count = 0; count < entries; count++) - { - Entry * entry = index_get (playlist->entries, count); - - if (entry->selected) - { - if (entry->queued) - playlist->queued = g_list_remove (playlist->queued, entry); - - playlist->total_length -= entry->length; - entry_free (entry); - - found = TRUE; - after = 0; - } - else - { - index_insert (others, -1, entry); - - if (found) - after ++; - else - before ++; - } - } - - index_free (playlist->entries); - playlist->entries = others; - - playlist->selected_count = 0; - playlist->selected_length = 0; - - entries = index_count (playlist->entries); - number_entries (playlist, before, entries - before); - - if (position_changed && get_bool (NULL, "advance_on_delete")) - can_play = next_song_locked (playlist, get_bool (NULL, "repeat"), entries - after); - - queue_update (PLAYLIST_UPDATE_STRUCTURE, playlist->number, before, entries - after - before); - LEAVE; - - if (position_changed) - hook_call ("playlist position", GINT_TO_POINTER (playlist_num)); - if (was_playing) - change_playback (can_play); -} - -void playlist_reverse (int playlist_num) -{ - ENTER_GET_PLAYLIST (); - - int entries = index_count (playlist->entries); - - Index * reversed = index_new (); - index_allocate (reversed, entries); - - for (int count = entries; count --; ) - index_insert (reversed, -1, index_get (playlist->entries, count)); - - index_free (playlist->entries); - playlist->entries = reversed; - - number_entries (playlist, 0, entries); - queue_update (PLAYLIST_UPDATE_STRUCTURE, playlist->number, 0, entries); - LEAVE; -} - -void playlist_reverse_selected (int playlist_num) -{ - ENTER_GET_PLAYLIST (); - - int entries = index_count (playlist->entries); - - Index * reversed = index_new (); - index_allocate (reversed, playlist->selected_count); - - for (int count = entries; count --; ) - { - Entry * entry = index_get (playlist->entries, count); - if (entry->selected) - index_insert (reversed, -1, index_get (playlist->entries, count)); - } - - int count2 = 0; - for (int count = 0; count < entries; count++) - { - Entry * entry = index_get (playlist->entries, count); - if (entry->selected) - index_set (playlist->entries, count, index_get (reversed, count2 ++)); - } - - index_free (reversed); - - number_entries (playlist, 0, entries); - queue_update (PLAYLIST_UPDATE_STRUCTURE, playlist->number, 0, entries); - LEAVE; -} - -void playlist_randomize (int playlist_num) -{ - ENTER_GET_PLAYLIST (); - - int entries = index_count (playlist->entries); - - for (int i = 0; i < entries; i ++) - { - int j = i + rand () % (entries - i); - - Entry * entry = index_get (playlist->entries, j); - index_set (playlist->entries, j, index_get (playlist->entries, i)); - index_set (playlist->entries, i, entry); - } - - number_entries (playlist, 0, entries); - queue_update (PLAYLIST_UPDATE_STRUCTURE, playlist->number, 0, entries); - LEAVE; -} - -void playlist_randomize_selected (int playlist_num) -{ - ENTER_GET_PLAYLIST (); - - int entries = index_count (playlist->entries); - - Index * selected = index_new (); - index_allocate (selected, playlist->selected_count); - - for (int count = 0; count < entries; count++) - { - Entry * entry = index_get (playlist->entries, count); - if (entry->selected) - index_insert (selected, -1, entry); - } - - for (int i = 0; i < playlist->selected_count; i ++) - { - int j = i + rand () % (playlist->selected_count - i); - - Entry * entry = index_get (selected, j); - index_set (selected, j, index_get (selected, i)); - index_set (selected, i, entry); - } - - int count2 = 0; - for (int count = 0; count < entries; count++) - { - Entry * entry = index_get (playlist->entries, count); - if (entry->selected) - index_set (playlist->entries, count, index_get (selected, count2 ++)); - } - - index_free (selected); - - number_entries (playlist, 0, entries); - queue_update (PLAYLIST_UPDATE_STRUCTURE, playlist->number, 0, entries); - LEAVE; -} - -enum {COMPARE_TYPE_FILENAME, COMPARE_TYPE_TUPLE, COMPARE_TYPE_TITLE}; - -typedef int (* CompareFunc) (const void * a, const void * b); - -typedef struct { - int type; - CompareFunc func; -} CompareData; - -static int compare_cb (const void * _a, const void * _b, void * _data) -{ - const Entry * a = _a, * b = _b; - CompareData * data = _data; - - int diff = 0; - - if (data->type == COMPARE_TYPE_FILENAME) - diff = data->func (a->filename, b->filename); - else if (data->type == COMPARE_TYPE_TUPLE) - diff = data->func (a->tuple, b->tuple); - else if (data->type == COMPARE_TYPE_TITLE) - diff = data->func (a->formatted ? a->formatted : a->filename, - b->formatted ? b->formatted : b->filename); - - if (diff) - return diff; - - /* preserve order of "equal" entries */ - return a->number - b->number; -} - -static void sort (Playlist * playlist, CompareData * data) -{ - index_sort_with_data (playlist->entries, compare_cb, data); - number_entries (playlist, 0, index_count (playlist->entries)); - - queue_update (PLAYLIST_UPDATE_STRUCTURE, playlist->number, 0, index_count (playlist->entries)); -} - -static void sort_selected (Playlist * playlist, CompareData * data) -{ - int entries = index_count (playlist->entries); - - Index * selected = index_new (); - index_allocate (selected, playlist->selected_count); - - for (int count = 0; count < entries; count++) - { - Entry * entry = index_get (playlist->entries, count); - if (entry->selected) - index_insert (selected, -1, entry); - } - - index_sort_with_data (selected, compare_cb, data); - - int count2 = 0; - for (int count = 0; count < entries; count++) - { - Entry * entry = index_get (playlist->entries, count); - if (entry->selected) - index_set (playlist->entries, count, index_get (selected, count2 ++)); - } - - index_free (selected); - - number_entries (playlist, 0, entries); - queue_update (PLAYLIST_UPDATE_STRUCTURE, playlist->number, 0, entries); -} - -static bool_t entries_are_scanned (Playlist * playlist, bool_t selected) -{ - int entries = index_count (playlist->entries); - for (int count = 0; count < entries; count ++) - { - Entry * entry = index_get (playlist->entries, count); - if (selected && ! entry->selected) - continue; - - if (! entry->tuple) - { - interface_show_error (_("The playlist cannot be sorted because " - "metadata scanning is still in progress (or has been disabled).")); - return FALSE; - } - } - - return TRUE; -} - -void playlist_sort_by_filename (int playlist_num, int (* compare) - (const char * a, const char * b)) -{ - ENTER_GET_PLAYLIST (); - - CompareData data = {COMPARE_TYPE_FILENAME, (CompareFunc) compare}; - sort (playlist, & data); - - LEAVE; -} - -void playlist_sort_by_tuple (int playlist_num, int (* compare) - (const Tuple * a, const Tuple * b)) -{ - ENTER_GET_PLAYLIST (); - - CompareData data = {COMPARE_TYPE_TUPLE, (CompareFunc) compare}; - if (entries_are_scanned (playlist, FALSE)) - sort (playlist, & data); - - LEAVE; -} - -void playlist_sort_by_title (int playlist_num, int (* compare) (const char * - a, const char * b)) -{ - ENTER_GET_PLAYLIST (); - - CompareData data = {COMPARE_TYPE_TITLE, (CompareFunc) compare}; - if (entries_are_scanned (playlist, FALSE)) - sort (playlist, & data); - - LEAVE; -} - -void playlist_sort_selected_by_filename (int playlist_num, int (* compare) - (const char * a, const char * b)) -{ - ENTER_GET_PLAYLIST (); - - CompareData data = {COMPARE_TYPE_FILENAME, (CompareFunc) compare}; - sort_selected (playlist, & data); - - LEAVE; -} - -void playlist_sort_selected_by_tuple (int playlist_num, int (* compare) - (const Tuple * a, const Tuple * b)) -{ - ENTER_GET_PLAYLIST (); - - CompareData data = {COMPARE_TYPE_TUPLE, (CompareFunc) compare}; - if (entries_are_scanned (playlist, TRUE)) - sort_selected (playlist, & data); - - LEAVE; -} - -void playlist_sort_selected_by_title (int playlist_num, int (* compare) - (const char * a, const char * b)) -{ - ENTER_GET_PLAYLIST (); - - CompareData data = {COMPARE_TYPE_TITLE, (CompareFunc) compare}; - if (entries_are_scanned (playlist, TRUE)) - sort_selected (playlist, & data); - - LEAVE; -} - -void playlist_reformat_titles (void) -{ - ENTER; - - if (title_formatter) - tuple_formatter_free (title_formatter); - - char * format = get_str (NULL, "generic_title_format"); - title_formatter = tuple_formatter_new (format); - str_unref (format); - - for (int playlist_num = 0; playlist_num < index_count (playlists); playlist_num ++) - { - Playlist * playlist = index_get (playlists, playlist_num); - int entries = index_count (playlist->entries); - - for (int count = 0; count < entries; count++) - { - Entry * entry = index_get (playlist->entries, count); - str_unref (entry->formatted); - - if (entry->tuple) - entry->formatted = tuple_format_title (title_formatter, entry->tuple); - else - entry->formatted = NULL; - } - - queue_update (PLAYLIST_UPDATE_METADATA, playlist_num, 0, entries); - } - - LEAVE; -} - -void playlist_trigger_scan (void) -{ - ENTER; - - for (int i = 0; i < index_count (playlists); i ++) - { - Playlist * p = index_get (playlists, i); - p->scanning = TRUE; - } - - scan_restart (); - - LEAVE; -} - -static void playlist_rescan_real (int playlist_num, bool_t selected) -{ - ENTER_GET_PLAYLIST (); - - int entries = index_count (playlist->entries); - - for (int count = 0; count < entries; count ++) - { - Entry * entry = index_get (playlist->entries, count); - if (! selected || entry->selected) - entry_set_tuple (playlist, entry, NULL); - } - - queue_update (PLAYLIST_UPDATE_METADATA, playlist->number, 0, entries); - LEAVE; -} - -void playlist_rescan (int playlist_num) -{ - playlist_rescan_real (playlist_num, FALSE); -} - -void playlist_rescan_selected (int playlist_num) -{ - playlist_rescan_real (playlist_num, TRUE); -} - -void playlist_rescan_file (const char * filename) -{ - ENTER; - - int num_playlists = index_count (playlists); - - for (int playlist_num = 0; playlist_num < num_playlists; playlist_num ++) - { - Playlist * playlist = index_get (playlists, playlist_num); - int num_entries = index_count (playlist->entries); - - for (int entry_num = 0; entry_num < num_entries; entry_num ++) - { - Entry * entry = index_get (playlist->entries, entry_num); - - if (! strcmp (entry->filename, filename)) - { - entry_set_tuple (playlist, entry, NULL); - queue_update (PLAYLIST_UPDATE_METADATA, playlist_num, entry_num, 1); - } - } - } - - LEAVE; -} - -int64_t playlist_get_total_length (int playlist_num) -{ - ENTER_GET_PLAYLIST (0); - int64_t length = playlist->total_length; - RETURN (length); -} - -int64_t playlist_get_selected_length (int playlist_num) -{ - ENTER_GET_PLAYLIST (0); - int64_t length = playlist->selected_length; - RETURN (length); -} - -int playlist_queue_count (int playlist_num) -{ - ENTER_GET_PLAYLIST (0); - int count = g_list_length (playlist->queued); - RETURN (count); -} - -void playlist_queue_insert (int playlist_num, int at, int entry_num) -{ - ENTER_GET_ENTRY (); - - if (entry->queued) - RETURN (); - - if (at < 0) - playlist->queued = g_list_append (playlist->queued, entry); - else - playlist->queued = g_list_insert (playlist->queued, entry, at); - - entry->queued = TRUE; - - queue_update (PLAYLIST_UPDATE_SELECTION, playlist->number, entry_num, 1); - LEAVE; -} - -void playlist_queue_insert_selected (int playlist_num, int at) -{ - ENTER_GET_PLAYLIST (); - - int entries = index_count(playlist->entries); - int first = entries, last = 0; - - for (int count = 0; count < entries; count++) - { - Entry * entry = index_get (playlist->entries, count); - - if (! entry->selected || entry->queued) - continue; - - if (at < 0) - playlist->queued = g_list_append (playlist->queued, entry); - else - playlist->queued = g_list_insert (playlist->queued, entry, at++); - - entry->queued = TRUE; - first = MIN (first, entry->number); - last = entry->number; - } - - if (first < entries) - queue_update (PLAYLIST_UPDATE_SELECTION, playlist->number, first, last + 1 - first); - - LEAVE; -} - -int playlist_queue_get_entry (int playlist_num, int at) -{ - ENTER_GET_PLAYLIST (-1); - - GList * node = g_list_nth (playlist->queued, at); - int entry_num = node ? ((Entry *) node->data)->number : -1; - - RETURN (entry_num); -} - -int playlist_queue_find_entry (int playlist_num, int entry_num) -{ - ENTER_GET_ENTRY (-1); - int pos = entry->queued ? g_list_index (playlist->queued, entry) : -1; - RETURN (pos); -} - -void playlist_queue_delete (int playlist_num, int at, int number) -{ - ENTER_GET_PLAYLIST (); - - int entries = index_count (playlist->entries); - int first = entries, last = 0; - - if (at == 0) - { - while (playlist->queued && number --) - { - Entry * entry = playlist->queued->data; - entry->queued = FALSE; - first = MIN (first, entry->number); - last = entry->number; - - playlist->queued = g_list_delete_link (playlist->queued, playlist->queued); - } - } - else - { - GList * anchor = g_list_nth (playlist->queued, at - 1); - if (! anchor) - goto DONE; - - while (anchor->next && number --) - { - Entry * entry = anchor->next->data; - entry->queued = FALSE; - first = MIN (first, entry->number); - last = entry->number; - - playlist->queued = g_list_delete_link (playlist->queued, anchor->next); - } - } - -DONE: - if (first < entries) - queue_update (PLAYLIST_UPDATE_SELECTION, playlist->number, first, last + 1 - first); - - LEAVE; -} - -void playlist_queue_delete_selected (int playlist_num) -{ - ENTER_GET_PLAYLIST (); - - int entries = index_count (playlist->entries); - int first = entries, last = 0; - - for (GList * node = playlist->queued; node; ) - { - GList * next = node->next; - Entry * entry = node->data; - - if (entry->selected) - { - entry->queued = FALSE; - playlist->queued = g_list_delete_link (playlist->queued, node); - first = MIN (first, entry->number); - last = entry->number; - } - - node = next; - } - - if (first < entries) - queue_update (PLAYLIST_UPDATE_SELECTION, playlist->number, first, last + 1 - first); - - LEAVE; -} - -static bool_t shuffle_prev (Playlist * playlist) -{ - int entries = index_count (playlist->entries); - Entry * found = NULL; - - for (int count = 0; count < entries; count ++) - { - Entry * entry = index_get (playlist->entries, count); - - if (entry->shuffle_num && (! playlist->position || - entry->shuffle_num < playlist->position->shuffle_num) && (! found - || entry->shuffle_num > found->shuffle_num)) - found = entry; - } - - if (! found) - return FALSE; - - set_position (playlist, found, FALSE); - return TRUE; -} - -bool_t playlist_prev_song (int playlist_num) -{ - ENTER_GET_PLAYLIST (FALSE); - - bool_t was_playing = (playlist == playing_playlist); - - if (get_bool (NULL, "shuffle")) - { - if (! shuffle_prev (playlist)) - RETURN (FALSE); - } - else - { - if (! playlist->position || playlist->position->number == 0) - RETURN (FALSE); - - set_position (playlist, index_get (playlist->entries, - playlist->position->number - 1), TRUE); - } - - LEAVE; - - hook_call ("playlist position", GINT_TO_POINTER (playlist_num)); - if (was_playing) - change_playback (TRUE); - - return TRUE; -} - -static bool_t shuffle_next (Playlist * playlist) -{ - int entries = index_count (playlist->entries), choice = 0, count; - Entry * found = NULL; - - for (count = 0; count < entries; count ++) - { - Entry * entry = index_get (playlist->entries, count); - - if (! entry->shuffle_num) - choice ++; - else if (playlist->position && entry->shuffle_num > - playlist->position->shuffle_num && (! found || entry->shuffle_num - < found->shuffle_num)) - found = entry; - } - - if (found) - { - set_position (playlist, found, FALSE); - return TRUE; - } - - if (! choice) - return FALSE; - - choice = rand () % choice; - - for (count = 0; ; count ++) - { - Entry * entry = index_get (playlist->entries, count); - - if (! entry->shuffle_num) - { - if (! choice) - { - set_position (playlist, entry, TRUE); - return TRUE; - } - - choice --; - } - } -} - -static void shuffle_reset (Playlist * playlist) -{ - int entries = index_count (playlist->entries); - - playlist->last_shuffle_num = 0; - - for (int count = 0; count < entries; count ++) - { - Entry * entry = index_get (playlist->entries, count); - entry->shuffle_num = 0; - } -} - -static bool_t next_song_locked (Playlist * playlist, bool_t repeat, int hint) -{ - int entries = index_count (playlist->entries); - if (! entries) - return FALSE; - - if (playlist->queued) - { - set_position (playlist, playlist->queued->data, TRUE); - playlist->queued = g_list_remove (playlist->queued, playlist->position); - playlist->position->queued = FALSE; - } - else if (get_bool (NULL, "shuffle")) - { - if (! shuffle_next (playlist)) - { - if (! repeat) - return FALSE; - - shuffle_reset (playlist); - - if (! shuffle_next (playlist)) - return FALSE; - } - } - else - { - if (hint >= entries) - { - if (! repeat) - return FALSE; - - hint = 0; - } - - set_position (playlist, index_get (playlist->entries, hint), TRUE); - } - - return TRUE; -} - -bool_t playlist_next_song (int playlist_num, bool_t repeat) -{ - ENTER_GET_PLAYLIST (FALSE); - - int hint = playlist->position ? playlist->position->number + 1 : 0; - bool_t was_playing = (playlist == playing_playlist); - - if (! next_song_locked (playlist, repeat, hint)) - RETURN (FALSE); - - LEAVE; - - hook_call ("playlist position", GINT_TO_POINTER (playlist_num)); - if (was_playing) - change_playback (TRUE); - - return TRUE; -} - -int playback_entry_get_position (void) -{ - ENTER; - - Entry * entry = get_playback_entry (FALSE, FALSE); - int entry_num = entry ? entry->number : -1; - - RETURN (entry_num); -} - -char * playback_entry_get_filename (void) -{ - ENTER; - - Entry * entry = get_playback_entry (FALSE, FALSE); - char * filename = entry ? str_ref (entry->filename) : NULL; - - RETURN (filename); -} - -PluginHandle * playback_entry_get_decoder (void) -{ - ENTER; - - Entry * entry = get_playback_entry (TRUE, FALSE); - PluginHandle * decoder = entry ? entry->decoder : NULL; - - RETURN (decoder); -} - -Tuple * playback_entry_get_tuple (void) -{ - ENTER; - - Entry * entry = get_playback_entry (FALSE, TRUE); - Tuple * tuple = entry ? entry->tuple : NULL; - - if (tuple) - tuple_ref (tuple); - - RETURN (tuple); -} - -char * playback_entry_get_title (void) -{ - ENTER; - - Entry * entry = get_playback_entry (FALSE, TRUE); - char * title = entry ? str_ref (entry->formatted ? entry->formatted : entry->title) : NULL; - - RETURN (title); -} - -int playback_entry_get_length (void) -{ - ENTER; - - Entry * entry = get_playback_entry (FALSE, TRUE); - int length = entry ? entry->length : 0; - - RETURN (length); -} - -void playback_entry_set_tuple (Tuple * tuple) -{ - ENTER; - if (! playing_playlist || ! playing_playlist->position) - RETURN (); - - Entry * entry = playing_playlist->position; - entry_set_tuple (playing_playlist, entry, tuple); - - queue_update (PLAYLIST_UPDATE_METADATA, playing_playlist->number, entry->number, 1); - LEAVE; -} - -void playlist_save_state (void) -{ - /* get playback state before locking playlists */ - bool_t paused = drct_get_paused (); - int time = drct_get_time (); - - ENTER; - - const char * user_dir = get_path (AUD_PATH_USER_DIR); - SCONCAT2 (path, user_dir, "/" STATE_FILE); - - FILE * handle = g_fopen (path, "w"); - if (! handle) - RETURN (); - - fprintf (handle, "active %d\n", active_playlist ? active_playlist->number : -1); - fprintf (handle, "playing %d\n", playing_playlist ? playing_playlist->number : -1); - - for (int playlist_num = 0; playlist_num < index_count (playlists); - playlist_num ++) - { - Playlist * playlist = index_get (playlists, playlist_num); - - fprintf (handle, "playlist %d\n", playlist_num); - - if (playlist->filename) - fprintf (handle, "filename %s\n", playlist->filename); - - fprintf (handle, "position %d\n", playlist->position ? playlist->position->number : -1); - - if (playlist == playing_playlist) - { - playlist->resume_paused = paused; - playlist->resume_time = time; - } - - fprintf (handle, "resume-state %d\n", paused ? RESUME_PAUSE : RESUME_PLAY); - fprintf (handle, "resume-time %d\n", playlist->resume_time); - } - - fclose (handle); - LEAVE; -} - -static char parse_key[512]; -static char * parse_value; - -static void parse_next (FILE * handle) -{ - parse_value = NULL; - - if (! fgets (parse_key, sizeof parse_key, handle)) - return; - - char * space = strchr (parse_key, ' '); - if (! space) - return; - - * space = 0; - parse_value = space + 1; - - char * newline = strchr (parse_value, '\n'); - if (newline) - * newline = 0; -} - -static bool_t parse_integer (const char * key, int * value) -{ - return (parse_value && ! strcmp (parse_key, key) && sscanf (parse_value, "%d", value) == 1); -} - -static char * parse_string (const char * key) -{ - return (parse_value && ! strcmp (parse_key, key)) ? str_get (parse_value) : NULL; -} - -void playlist_load_state (void) -{ - ENTER; - int playlist_num; - - const char * user_dir = get_path (AUD_PATH_USER_DIR); - SCONCAT2 (path, user_dir, "/" STATE_FILE); - - FILE * handle = g_fopen (path, "r"); - if (! handle) - RETURN (); - - parse_next (handle); - - if (parse_integer ("active", & playlist_num)) - { - if (! (active_playlist = lookup_playlist (playlist_num))) - active_playlist = index_get (playlists, 0); - parse_next (handle); - } - - if (parse_integer ("playing", & resume_playlist)) - parse_next (handle); - - while (parse_integer ("playlist", & playlist_num) && playlist_num >= 0 && - playlist_num < index_count (playlists)) - { - Playlist * playlist = index_get (playlists, playlist_num); - int entries = index_count (playlist->entries); - - parse_next (handle); - - char * s; - if ((s = parse_string ("filename"))) - { - str_unref (playlist->filename); - playlist->filename = s; - parse_next (handle); - } - - int position = -1; - if (parse_integer ("position", & position)) - parse_next (handle); - - if (position >= 0 && position < entries) - set_position (playlist, index_get (playlist->entries, position), TRUE); - - int resume_state = RESUME_PLAY; - if (parse_integer ("resume-state", & resume_state)) - parse_next (handle); - - playlist->resume_paused = (resume_state == RESUME_PAUSE); - - if (parse_integer ("resume-time", & playlist->resume_time)) - parse_next (handle); - - /* compatibility with Audacious 3.3 */ - if (playlist_num == resume_playlist && resume_state == RESUME_STOP) - resume_playlist = -1; - } - - fclose (handle); - - /* clear updates queued during init sequence */ - - for (int i = 0; i < index_count (playlists); i ++) - { - Playlist * p = index_get (playlists, i); - memset (& p->last_update, 0, sizeof (Update)); - memset (& p->next_update, 0, sizeof (Update)); - } - - update_level = 0; - - if (update_source) - { - g_source_remove (update_source); - update_source = 0; - } - - LEAVE; -} - -void playlist_resume (void) -{ - playlist_set_playing (resume_playlist); -} diff --git a/src/audacious/playlist-utils.c b/src/audacious/playlist-utils.c deleted file mode 100644 index c51dfa2..0000000 --- a/src/audacious/playlist-utils.c +++ /dev/null @@ -1,507 +0,0 @@ -/* - * playlist-utils.c - * Copyright 2009-2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <glib.h> -#include <glib/gstdio.h> - -#include <libaudcore/audstrings.h> -#include <libaudcore/hook.h> - -#include "misc.h" -#include "playlist.h" - -static const char * get_basename (const char * filename) -{ - const char * slash = strrchr (filename, '/'); - - return (slash == NULL) ? filename : slash + 1; -} - -static int filename_compare_basename (const char * a, const char * b) -{ - return str_compare_encoded (get_basename (a), get_basename (b)); -} - -static int tuple_compare_string (const Tuple * a, const Tuple * b, int field) -{ - char * string_a = tuple_get_str (a, field); - char * string_b = tuple_get_str (b, field); - int ret; - - if (string_a == NULL) - ret = (string_b == NULL) ? 0 : -1; - else if (string_b == NULL) - ret = 1; - else - ret = str_compare (string_a, string_b); - - str_unref (string_a); - str_unref (string_b); - return ret; -} - -static int tuple_compare_int (const Tuple * a, const Tuple * b, int field) -{ - if (tuple_get_value_type (a, field) != TUPLE_INT) - return (tuple_get_value_type (b, field) != TUPLE_INT) ? 0 : -1; - if (tuple_get_value_type (b, field) != TUPLE_INT) - return 1; - - int int_a = tuple_get_int (a, field); - int int_b = tuple_get_int (b, field); - - return (int_a < int_b) ? -1 : (int_a > int_b); -} - -static int tuple_compare_title (const Tuple * a, const Tuple * b) -{ - return tuple_compare_string (a, b, FIELD_TITLE); -} - -static int tuple_compare_album (const Tuple * a, const Tuple * b) -{ - return tuple_compare_string (a, b, FIELD_ALBUM); -} - -static int tuple_compare_artist (const Tuple * a, const Tuple * b) -{ - return tuple_compare_string (a, b, FIELD_ARTIST); -} - -static int tuple_compare_date (const Tuple * a, const Tuple * b) -{ - return tuple_compare_int (a, b, FIELD_YEAR); -} - -static int tuple_compare_track (const Tuple * a, const Tuple * b) -{ - return tuple_compare_int (a, b, FIELD_TRACK_NUMBER); -} - -static int tuple_compare_length (const Tuple * a, const Tuple * b) -{ - return tuple_compare_int (a, b, FIELD_LENGTH); -} - -static const PlaylistStringCompareFunc filename_comparisons[] = { - [PLAYLIST_SORT_PATH] = str_compare_encoded, - [PLAYLIST_SORT_FILENAME] = filename_compare_basename, - [PLAYLIST_SORT_TITLE] = NULL, - [PLAYLIST_SORT_ALBUM] = NULL, - [PLAYLIST_SORT_ARTIST] = NULL, - [PLAYLIST_SORT_DATE] = NULL, - [PLAYLIST_SORT_TRACK] = NULL, - [PLAYLIST_SORT_FORMATTED_TITLE] = NULL, - [PLAYLIST_SORT_LENGTH] = NULL}; - -static const PlaylistTupleCompareFunc tuple_comparisons[] = { - [PLAYLIST_SORT_PATH] = NULL, - [PLAYLIST_SORT_FILENAME] = NULL, - [PLAYLIST_SORT_TITLE] = tuple_compare_title, - [PLAYLIST_SORT_ALBUM] = tuple_compare_album, - [PLAYLIST_SORT_ARTIST] = tuple_compare_artist, - [PLAYLIST_SORT_DATE] = tuple_compare_date, - [PLAYLIST_SORT_TRACK] = tuple_compare_track, - [PLAYLIST_SORT_FORMATTED_TITLE] = NULL, - [PLAYLIST_SORT_LENGTH] = tuple_compare_length}; - -static const PlaylistStringCompareFunc title_comparisons[] = { - [PLAYLIST_SORT_PATH] = NULL, - [PLAYLIST_SORT_FILENAME] = NULL, - [PLAYLIST_SORT_TITLE] = NULL, - [PLAYLIST_SORT_ALBUM] = NULL, - [PLAYLIST_SORT_ARTIST] = NULL, - [PLAYLIST_SORT_DATE] = NULL, - [PLAYLIST_SORT_TRACK] = NULL, - [PLAYLIST_SORT_FORMATTED_TITLE] = str_compare, - [PLAYLIST_SORT_LENGTH] = NULL}; - -void playlist_sort_by_scheme (int playlist, int scheme) -{ - if (filename_comparisons[scheme] != NULL) - playlist_sort_by_filename (playlist, filename_comparisons[scheme]); - else if (tuple_comparisons[scheme] != NULL) - playlist_sort_by_tuple (playlist, tuple_comparisons[scheme]); - else if (title_comparisons[scheme] != NULL) - playlist_sort_by_title (playlist, title_comparisons[scheme]); -} - -void playlist_sort_selected_by_scheme (int playlist, int scheme) -{ - if (filename_comparisons[scheme] != NULL) - playlist_sort_selected_by_filename (playlist, - filename_comparisons[scheme]); - else if (tuple_comparisons[scheme] != NULL) - playlist_sort_selected_by_tuple (playlist, tuple_comparisons[scheme]); - else if (title_comparisons[scheme] != NULL) - playlist_sort_selected_by_title (playlist, title_comparisons[scheme]); -} - -/* Fix me: This considers empty fields as duplicates. */ -void playlist_remove_duplicates_by_scheme (int playlist, int scheme) -{ - int entries = playlist_entry_count (playlist); - int count; - - if (entries < 1) - return; - - playlist_select_all (playlist, FALSE); - - if (filename_comparisons[scheme] != NULL) - { - int (* compare) (const char * a, const char * b) = - filename_comparisons[scheme]; - - playlist_sort_by_filename (playlist, compare); - char * last = playlist_entry_get_filename (playlist, 0); - - for (count = 1; count < entries; count ++) - { - char * current = playlist_entry_get_filename (playlist, count); - - if (compare (last, current) == 0) - playlist_entry_set_selected (playlist, count, TRUE); - - str_unref (last); - last = current; - } - - str_unref (last); - } - else if (tuple_comparisons[scheme] != NULL) - { - int (* compare) (const Tuple * a, const Tuple * b) = - tuple_comparisons[scheme]; - - playlist_sort_by_tuple (playlist, compare); - Tuple * last = playlist_entry_get_tuple (playlist, 0, FALSE); - - for (count = 1; count < entries; count ++) - { - Tuple * current = playlist_entry_get_tuple (playlist, count, FALSE); - - if (last != NULL && current != NULL && compare (last, current) == 0) - playlist_entry_set_selected (playlist, count, TRUE); - - if (last) - tuple_unref (last); - last = current; - } - - if (last) - tuple_unref (last); - } - - playlist_delete_selected (playlist); -} - -void playlist_remove_failed (int playlist) -{ - int entries = playlist_entry_count (playlist); - int count; - - playlist_select_all (playlist, FALSE); - - for (count = 0; count < entries; count ++) - { - char * filename = playlist_entry_get_filename (playlist, count); - - /* vfs_file_test() only works for file:// URIs currently */ - if (! strncmp (filename, "file://", 7) && ! vfs_file_test (filename, - G_FILE_TEST_EXISTS)) - playlist_entry_set_selected (playlist, count, TRUE); - - str_unref (filename); - } - - playlist_delete_selected (playlist); -} - -void playlist_select_by_patterns (int playlist, const Tuple * patterns) -{ - const int fields[] = {FIELD_TITLE, FIELD_ALBUM, FIELD_ARTIST, - FIELD_FILE_NAME}; - - int entries = playlist_entry_count (playlist); - int field, entry; - - playlist_select_all (playlist, TRUE); - - for (field = 0; field < ARRAY_LEN (fields); field ++) - { - char * pattern = tuple_get_str (patterns, fields[field]); - GRegex * regex; - - if (! pattern || ! pattern[0] || ! (regex = g_regex_new (pattern, - G_REGEX_CASELESS, 0, NULL))) - { - str_unref (pattern); - continue; - } - - for (entry = 0; entry < entries; entry ++) - { - if (! playlist_entry_get_selected (playlist, entry)) - continue; - - Tuple * tuple = playlist_entry_get_tuple (playlist, entry, FALSE); - char * string = tuple ? tuple_get_str (tuple, fields[field]) : NULL; - - if (! string || ! g_regex_match (regex, string, 0, NULL)) - playlist_entry_set_selected (playlist, entry, FALSE); - - str_unref (string); - if (tuple) - tuple_unref (tuple); - } - - g_regex_unref (regex); - str_unref (pattern); - } -} - -static char * make_playlist_path (int playlist) -{ - if (! playlist) - return filename_build (get_path (AUD_PATH_USER_DIR), "playlist.xspf"); - - SPRINTF (name, "playlist_%02d.xspf", 1 + playlist); - return filename_build (get_path (AUD_PATH_PLAYLISTS_DIR), name); -} - -static void load_playlists_real (void) -{ - const char * folder = get_path (AUD_PATH_PLAYLISTS_DIR); - - /* old (v3.1 and earlier) naming scheme */ - - int count; - for (count = 0; ; count ++) - { - char * path = make_playlist_path (count); - - if (! g_file_test (path, G_FILE_TEST_EXISTS)) - { - str_unref (path); - break; - } - - char * uri = filename_to_uri (path); - - playlist_insert (count); - playlist_insert_playlist_raw (count, 0, uri); - playlist_set_modified (count, TRUE); - - str_unref (path); - str_unref (uri); - } - - /* unique ID-based naming scheme */ - - char * order_path = filename_build (folder, "order"); - char * order_string; - g_file_get_contents (order_path, & order_string, NULL, NULL); - str_unref (order_path); - - if (! order_string) - goto DONE; - - Index * order = str_list_to_index (order_string, " "); - g_free (order_string); - - for (int i = 0; i < index_count (order); i ++) - { - char * number = index_get (order, i); - - SCONCAT2 (name, number, ".audpl"); - char * path = filename_build (folder, name); - - if (! g_file_test (path, G_FILE_TEST_EXISTS)) - { - str_unref (path); - - SCONCAT2 (name2, number, ".xspf"); - path = filename_build (folder, name2); - } - - char * uri = filename_to_uri (path); - - playlist_insert_with_id (count + i, atoi (number)); - playlist_insert_playlist_raw (count + i, 0, uri); - playlist_set_modified (count + i, FALSE); - - if (g_str_has_suffix (path, ".xspf")) - playlist_set_modified (count + i, TRUE); - - str_unref (path); - str_unref (uri); - } - - index_free_full (order, (IndexFreeFunc) str_unref); - -DONE: - if (! playlist_count ()) - playlist_insert (0); - - playlist_set_active (0); -} - -static void save_playlists_real (void) -{ - int lists = playlist_count (); - const char * folder = get_path (AUD_PATH_PLAYLISTS_DIR); - - /* save playlists */ - - Index * order = index_new (); - GHashTable * saved = g_hash_table_new_full (g_str_hash, g_str_equal, - (GDestroyNotify) str_unref, NULL); - - for (int i = 0; i < lists; i ++) - { - int id = playlist_get_unique_id (i); - char * number = int_to_str (id); - - SCONCAT2 (name, number, ".audpl"); - - if (playlist_get_modified (i)) - { - char * path = filename_build (folder, name); - char * uri = filename_to_uri (path); - - playlist_save (i, uri); - playlist_set_modified (i, FALSE); - - str_unref (path); - str_unref (uri); - } - - index_insert (order, -1, number); - g_hash_table_insert (saved, str_get (name), NULL); - } - - char * order_string = index_to_str_list (order, " "); - index_free_full (order, (IndexFreeFunc) str_unref); - - GError * error = NULL; - char * order_path = filename_build (folder, "order"); - - char * old_order_string; - g_file_get_contents (order_path, & old_order_string, NULL, NULL); - - if (! old_order_string || strcmp (old_order_string, order_string)) - { - if (! g_file_set_contents (order_path, order_string, -1, & error)) - { - fprintf (stderr, "Cannot write to %s: %s\n", order_path, error->message); - g_error_free (error); - } - } - - str_unref (order_string); - str_unref (order_path); - g_free (old_order_string); - - /* clean up deleted playlists and files from old naming scheme */ - - char * path = make_playlist_path (0); - g_unlink (path); - str_unref (path); - - GDir * dir = g_dir_open (folder, 0, NULL); - if (! dir) - goto DONE; - - const char * name; - while ((name = g_dir_read_name (dir))) - { - if (! g_str_has_suffix (name, ".audpl") && ! g_str_has_suffix (name, ".xspf")) - continue; - - if (! g_hash_table_contains (saved, name)) - { - char * path = filename_build (folder, name); - g_unlink (path); - str_unref (path); - } - } - - g_dir_close (dir); - -DONE: - g_hash_table_destroy (saved); -} - -static bool_t hooks_added, state_changed; - -static void update_cb (void * data, void * user) -{ - if (GPOINTER_TO_INT (data) < PLAYLIST_UPDATE_METADATA) - return; - - state_changed = TRUE; -} - -static void state_cb (void * data, void * user) -{ - state_changed = TRUE; -} - -void load_playlists (void) -{ - load_playlists_real (); - playlist_load_state (); - - state_changed = FALSE; - - if (! hooks_added) - { - hook_associate ("playlist update", update_cb, NULL); - hook_associate ("playlist activate", state_cb, NULL); - hook_associate ("playlist position", state_cb, NULL); - - hooks_added = TRUE; - } -} - -void save_playlists (bool_t exiting) -{ - save_playlists_real (); - - /* on exit, save resume states */ - if (state_changed || exiting) - { - playlist_save_state (); - state_changed = FALSE; - } - - if (exiting && hooks_added) - { - hook_dissociate ("playlist update", update_cb); - hook_dissociate ("playlist activate", state_cb); - hook_dissociate ("playlist position", state_cb); - - hooks_added = FALSE; - } -} diff --git a/src/audacious/playlist.h b/src/audacious/playlist.h deleted file mode 100644 index d457ea0..0000000 --- a/src/audacious/playlist.h +++ /dev/null @@ -1,147 +0,0 @@ -/* - * playlist.h - * Copyright 2010-2012 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_PLAYLIST_H -#define AUDACIOUS_PLAYLIST_H - -#include <stdint.h> - -#include <audacious/api.h> -#include <audacious/types.h> -#include <libaudcore/index.h> -#include <libaudcore/tuple.h> - -/* The values which can be passed (packed into a pointer) to the "playlist - * update" hook. PLAYLIST_UPDATE_SELECTION means that entries have been - * selected or unselected, or that entries have been added to or removed from - * the queue. PLAYLIST_UPDATE_METADATA means that new metadata has been read - * for some entries, or that the title or filename of a playlist has changed, - * and implies PLAYLIST_UPDATE_SELECTION. PLAYLIST_UPDATE_STRUCTURE covers any - * change not listed under the other types, and implies both - * PLAYLIST_UPDATE_SELECTION and PLAYLIST_UPDATE_METADATA. */ -enum { - PLAYLIST_UPDATE_SELECTION = 1, - PLAYLIST_UPDATE_METADATA, - PLAYLIST_UPDATE_STRUCTURE}; - -/* The values which can be passed to playlist_sort_by_scheme(), - * playlist_sort_selected_by_scheme(), and - * playlist_remove_duplicates_by_scheme(). PLAYLIST_SORT_PATH means the entire - * URI of a song file; PLAYLIST_SORT_FILENAME means the portion after the last - * "/" (forward slash). PLAYLIST_SORT_DATE means the song's release date (not - * the file's modification time). */ -enum { - PLAYLIST_SORT_PATH, - PLAYLIST_SORT_FILENAME, - PLAYLIST_SORT_TITLE, - PLAYLIST_SORT_ALBUM, - PLAYLIST_SORT_ARTIST, - PLAYLIST_SORT_DATE, - PLAYLIST_SORT_TRACK, - PLAYLIST_SORT_FORMATTED_TITLE, - PLAYLIST_SORT_LENGTH, - PLAYLIST_SORT_SCHEMES}; - -typedef bool_t (* PlaylistFilterFunc) (const char * filename, void * user); -typedef int (* PlaylistStringCompareFunc) (const char * a, const char * b); -typedef int (* PlaylistTupleCompareFunc) (const Tuple * a, const Tuple * b); - -#define AUD_API_NAME PlaylistAPI -#define AUD_API_SYMBOL playlist_api - -#ifdef _AUDACIOUS_CORE - -#include "api-local-begin.h" -#include "playlist-api.h" -#include "api-local-end.h" - -/* playlist-files.c */ -bool_t playlist_load (const char * filename, char * * title, - Index * * filenames, Index * * tuples); -bool_t playlist_insert_playlist_raw (int list, int at, - const char * filename); - -/* playlist-new.c */ -void playlist_init (void); -void playlist_end (void); - -void playlist_insert_with_id (int at, int id); -void playlist_set_modified (int playlist, bool_t modified); -bool_t playlist_get_modified (int playlist); - -void playlist_load_state (void); -void playlist_save_state (void); -void playlist_resume (void); - -void playlist_reformat_titles (void); -void playlist_trigger_scan (void); - -void playlist_entry_insert_batch_raw (int playlist, int at, - Index * filenames, Index * tuples, Index * decoders); - -bool_t playlist_prev_song (int playlist); -bool_t playlist_next_song (int playlist, bool_t repeat); - -int playback_entry_get_position (void); -char * playback_entry_get_filename (void); -PluginHandle * playback_entry_get_decoder (void); -Tuple * playback_entry_get_tuple (void); -char * playback_entry_get_title (void); -int playback_entry_get_length (void); - -void playback_entry_set_tuple (Tuple * tuple); - -/* playlist-utils.c */ -void load_playlists (void); -void save_playlists (bool_t exiting); - -#else - -#include <audacious/api-define-begin.h> -#include <audacious/playlist-api.h> -#include <audacious/api-define-end.h> - -#include <audacious/api-alias-begin.h> -#include <audacious/playlist-api.h> -#include <audacious/api-alias-end.h> - -#endif - -#undef AUD_API_NAME -#undef AUD_API_SYMBOL - -#endif - -#ifdef AUD_API_DECLARE - -#define AUD_API_NAME PlaylistAPI -#define AUD_API_SYMBOL playlist_api - -#include "api-define-begin.h" -#include "playlist-api.h" -#include "api-define-end.h" - -#include "api-declare-begin.h" -#include "playlist-api.h" -#include "api-declare-end.h" - -#undef AUD_API_NAME -#undef AUD_API_SYMBOL - -#endif diff --git a/src/audacious/plugin-init.c b/src/audacious/plugin-init.c deleted file mode 100644 index b7690e3..0000000 --- a/src/audacious/plugin-init.c +++ /dev/null @@ -1,333 +0,0 @@ -/* - * plugin-init.c - * Copyright 2010-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> - -#include <glib.h> - -#include "debug.h" -#include "effect.h" -#include "general.h" -#include "interface.h" -#include "misc.h" -#include "output.h" -#include "plugin.h" -#include "plugins.h" -#include "ui_preferences.h" -#include "visualization.h" - -static const struct { - const char * name; - bool_t is_single; - - union { - struct { - bool_t (* start) (PluginHandle * plugin); - void (* stop) (PluginHandle * plugin); - } m; - - struct { - PluginHandle * (* probe) (void); - PluginHandle * (* get_current) (void); - bool_t (* set_current) (PluginHandle * plugin); - } s; - } u; -} table[PLUGIN_TYPES] = { - [PLUGIN_TYPE_TRANSPORT] = {"transport", FALSE, .u.m = {NULL, NULL}}, - [PLUGIN_TYPE_PLAYLIST] = {"playlist", FALSE, .u.m = {NULL, NULL}}, - [PLUGIN_TYPE_INPUT] = {"input", FALSE, .u.m = {NULL, NULL}}, - [PLUGIN_TYPE_EFFECT] = {"effect", FALSE, .u.m = {effect_plugin_start, effect_plugin_stop}}, - [PLUGIN_TYPE_OUTPUT] = {"output", TRUE, .u.s = {output_plugin_probe, - output_plugin_get_current, output_plugin_set_current}}, - [PLUGIN_TYPE_VIS] = {"visualization", FALSE, .u.m = {vis_plugin_start, vis_plugin_stop}}, - [PLUGIN_TYPE_GENERAL] = {"general", FALSE, .u.m = {general_plugin_start, general_plugin_stop}}, - [PLUGIN_TYPE_IFACE] = {"interface", TRUE, .u.s = {iface_plugin_probe, - iface_plugin_get_current, iface_plugin_set_current}}}; - -static bool_t find_enabled_cb (PluginHandle * p, void * pp) -{ - * (PluginHandle * *) pp = p; - return FALSE; -} - -static PluginHandle * find_enabled (int type) -{ - PluginHandle * p = NULL; - plugin_for_enabled (type, find_enabled_cb, & p); - return p; -} - -static void start_single (int type) -{ - PluginHandle * p; - - if ((p = find_enabled (type)) != NULL) - { - AUDDBG ("Starting selected %s plugin %s.\n", table[type].name, - plugin_get_name (p)); - - if (table[type].u.s.set_current (p)) - return; - - AUDDBG ("%s failed to start.\n", plugin_get_name (p)); - plugin_set_enabled (p, FALSE); - } - - AUDDBG ("Probing for %s plugin.\n", table[type].name); - - if ((p = table[type].u.s.probe ()) == NULL) - { - fprintf (stderr, "FATAL: No %s plugin found.\n" - "(Did you forget to install audacious-plugins?)\n", table[type].name); - abort (); - } - - AUDDBG ("Starting %s.\n", plugin_get_name (p)); - plugin_set_enabled (p, TRUE); - - if (! table[type].u.s.set_current (p)) - { - fprintf (stderr, "FATAL: %s failed to start.\n", plugin_get_name (p)); - abort (); - } -} - -static bool_t start_multi_cb (PluginHandle * p, void * type) -{ - AUDDBG ("Starting %s.\n", plugin_get_name (p)); - - if (! table[GPOINTER_TO_INT (type)].u.m.start (p)) - { - AUDDBG ("%s failed to start; disabling.\n", plugin_get_name (p)); - plugin_set_enabled (p, FALSE); - } - - return TRUE; -} - -static void start_plugins (int type) -{ - if (type == PLUGIN_TYPE_IFACE && headless_mode ()) - return; - - if (table[type].is_single) - start_single (type); - else - { - if (table[type].u.m.start) - plugin_for_enabled (type, start_multi_cb, GINT_TO_POINTER (type)); - } -} - -static VFSConstructor * lookup_transport (const char * scheme) -{ - PluginHandle * plugin = transport_plugin_for_scheme (scheme); - if (! plugin) - return NULL; - - TransportPlugin * tp = plugin_get_header (plugin); - return tp ? tp->vtable : NULL; -} - -void start_plugins_one (void) -{ - plugin_system_init (); - vfs_set_lookup_func (lookup_transport); - - for (int i = 0; i < PLUGIN_TYPE_GENERAL; i ++) - start_plugins (i); -} - -void start_plugins_two (void) -{ - for (int i = PLUGIN_TYPE_GENERAL; i < PLUGIN_TYPES; i ++) - start_plugins (i); -} - -static bool_t misc_cleanup_cb (PluginHandle * p, void * unused) -{ - plugin_misc_cleanup (p); - return TRUE; -} - -static bool_t stop_multi_cb (PluginHandle * p, void * type) -{ - AUDDBG ("Shutting down %s.\n", plugin_get_name (p)); - table[GPOINTER_TO_INT (type)].u.m.stop (p); - return TRUE; -} - -static void stop_plugins (int type) -{ - if (type == PLUGIN_TYPE_IFACE && headless_mode ()) - return; - - plugin_for_enabled (type, misc_cleanup_cb, GINT_TO_POINTER (type)); - - if (table[type].is_single) - { - AUDDBG ("Shutting down %s.\n", plugin_get_name - (table[type].u.s.get_current ())); - table[type].u.s.set_current (NULL); - } - else - { - if (table[type].u.m.stop) - plugin_for_enabled (type, stop_multi_cb, GINT_TO_POINTER (type)); - } -} - -void stop_plugins_two (void) -{ - for (int i = PLUGIN_TYPES - 1; i >= PLUGIN_TYPE_GENERAL; i --) - stop_plugins (i); -} - -void stop_plugins_one (void) -{ - for (int i = PLUGIN_TYPE_GENERAL - 1; i >= 0; i --) - stop_plugins (i); - - vfs_set_lookup_func (NULL); - plugin_system_cleanup (); -} - -PluginHandle * plugin_get_current (int type) -{ - g_return_val_if_fail (table[type].is_single, NULL); - return table[type].u.s.get_current (); -} - -static bool_t enable_single (int type, PluginHandle * p) -{ - PluginHandle * old = table[type].u.s.get_current (); - - plugin_misc_cleanup (old); - - AUDDBG ("Switching from %s to %s.\n", plugin_get_name (old), - plugin_get_name (p)); - plugin_set_enabled (old, FALSE); - plugin_set_enabled (p, TRUE); - - if (table[type].u.s.set_current (p)) - return TRUE; - - fprintf (stderr, "%s failed to start; falling back to %s.\n", - plugin_get_name (p), plugin_get_name (old)); - plugin_set_enabled (p, FALSE); - plugin_set_enabled (old, TRUE); - - if (table[type].u.s.set_current (old)) - return FALSE; - - fprintf (stderr, "FATAL: %s failed to start.\n", plugin_get_name (old)); - abort (); -} - -static bool_t enable_multi (int type, PluginHandle * p, bool_t enable) -{ - if (! enable) - plugin_misc_cleanup (p); - - AUDDBG ("%sabling %s.\n", enable ? "En" : "Dis", plugin_get_name (p)); - plugin_set_enabled (p, enable); - - if (enable) - { - if (table[type].u.m.start && ! table[type].u.m.start (p)) - { - fprintf (stderr, "%s failed to start.\n", plugin_get_name (p)); - plugin_set_enabled (p, FALSE); - return FALSE; - } - } - else - { - if (table[type].u.m.stop) - table[type].u.m.stop (p); - } - - return TRUE; -} - -bool_t plugin_enable (PluginHandle * plugin, bool_t enable) -{ - if (! enable == ! plugin_get_enabled (plugin)) - return TRUE; - - int type = plugin_get_type (plugin); - - if (table[type].is_single) - { - g_return_val_if_fail (enable, FALSE); - return enable_single (type, plugin); - } - - return enable_multi (type, plugin, enable); -} - -/* Miscellaneous plugin-related functions ... */ - -PluginHandle * plugin_by_widget (/* GtkWidget * */ void * widget) -{ - PluginHandle * p; - if ((p = vis_plugin_by_widget (widget))) - return p; - if ((p = general_plugin_by_widget (widget))) - return p; - return NULL; -} - -int plugin_send_message (PluginHandle * plugin, const char * code, const void * data, int size) -{ - if (! plugin_get_enabled (plugin)) - return ENOSYS; - - Plugin * header = plugin_get_header (plugin); - if (! header || ! PLUGIN_HAS_FUNC (header, take_message)) - return ENOSYS; - - return header->take_message (code, data, size); -} - -void plugin_do_about (PluginHandle * plugin) -{ - g_return_if_fail (plugin_get_enabled (plugin)); - Plugin * header = plugin_get_header (plugin); - g_return_if_fail (header); - - if (PLUGIN_HAS_FUNC (header, about)) - header->about (); - else if (PLUGIN_HAS_FUNC (header, about_text)) - plugin_make_about_window (plugin); -} - -void plugin_do_configure (PluginHandle * plugin) -{ - g_return_if_fail (plugin_get_enabled (plugin)); - Plugin * header = plugin_get_header (plugin); - g_return_if_fail (header); - - if (PLUGIN_HAS_FUNC (header, configure)) - header->configure (); - else if (PLUGIN_HAS_FUNC (header, prefs)) - plugin_make_config_window (plugin); -} diff --git a/src/audacious/plugin-preferences.c b/src/audacious/plugin-preferences.c deleted file mode 100644 index 8a0bf81..0000000 --- a/src/audacious/plugin-preferences.c +++ /dev/null @@ -1,132 +0,0 @@ -/* - * plugin-preferences.c - * Copyright 2012-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <libaudcore/audstrings.h> -#include <libaudgui/libaudgui-gtk.h> - -#include "i18n.h" -#include "misc.h" -#include "plugin.h" -#include "plugins.h" -#include "preferences.h" -#include "ui_preferences.h" - -typedef struct { - GtkWidget * about_window; - GtkWidget * config_window; -} PluginMiscData; - -void plugin_make_about_window (PluginHandle * plugin) -{ - PluginMiscData * misc = plugin_get_misc_data (plugin, sizeof (PluginMiscData)); - Plugin * header = plugin_get_header (plugin); - - if (misc->about_window) - { - gtk_window_present ((GtkWindow *) misc->about_window); - return; - } - - const char * name = header->name; - const char * text = header->about_text; - - if (PLUGIN_HAS_FUNC (header, domain)) - { - name = dgettext (header->domain, name); - text = dgettext (header->domain, text); - } - - SCONCAT3 (title, _("About"), " ", name); - audgui_simple_message (& misc->about_window, GTK_MESSAGE_INFO, title, text); -} - -static void response_cb (GtkWidget * window, int response, const PluginPreferences * p) -{ - if (response == GTK_RESPONSE_OK && p->apply) - p->apply (); - - gtk_widget_destroy (window); -} - -static void destroy_cb (GtkWidget * window, const PluginPreferences * p) -{ - if (p->cleanup) - p->cleanup (); -} - -void plugin_make_config_window (PluginHandle * plugin) -{ - PluginMiscData * misc = plugin_get_misc_data (plugin, sizeof (PluginMiscData)); - Plugin * header = plugin_get_header (plugin); - const PluginPreferences * p = header->prefs; - - if (misc->config_window) - { - gtk_window_present ((GtkWindow *) misc->config_window); - return; - } - - if (p->init) - p->init (); - - const char * name = header->name; - if (PLUGIN_HAS_FUNC (header, domain)) - name = dgettext (header->domain, header->name); - - GtkWidget * window = gtk_dialog_new (); - - SCONCAT3 (title, name, " ", _("Settings")); - gtk_window_set_title ((GtkWindow *) window, title); - - if (p->apply) - { - GtkWidget * button1 = audgui_button_new (_("_Set"), "system-run", NULL, NULL); - GtkWidget * button2 = audgui_button_new (_("_Cancel"), "process-stop", NULL, NULL); - gtk_dialog_add_action_widget ((GtkDialog *) window, button2, GTK_RESPONSE_CANCEL); - gtk_dialog_add_action_widget ((GtkDialog *) window, button1, GTK_RESPONSE_OK); - } - else - { - GtkWidget * button = audgui_button_new (_("_Close"), "window-close", NULL, NULL); - gtk_dialog_add_action_widget ((GtkDialog *) window, button, GTK_RESPONSE_CLOSE); - } - - GtkWidget * content = gtk_dialog_get_content_area ((GtkDialog *) window); - GtkWidget * box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - create_widgets_with_domain (box, p->widgets, p->n_widgets, header->domain); - gtk_box_pack_start ((GtkBox *) content, box, TRUE, TRUE, 0); - - g_signal_connect (window, "response", (GCallback) response_cb, (void *) p); - g_signal_connect (window, "destroy", (GCallback) destroy_cb, (void *) p); - - misc->config_window = window; - g_signal_connect (window, "destroy", (GCallback) gtk_widget_destroyed, & misc->config_window); - - gtk_widget_show_all (window); -} - -void plugin_misc_cleanup (PluginHandle * plugin) -{ - PluginMiscData * misc = plugin_get_misc_data (plugin, sizeof (PluginMiscData)); - - if (misc->about_window) - gtk_widget_destroy (misc->about_window); - if (misc->config_window) - gtk_widget_destroy (misc->config_window); -} diff --git a/src/audacious/plugin-registry.c b/src/audacious/plugin-registry.c deleted file mode 100644 index bc0d441..0000000 --- a/src/audacious/plugin-registry.c +++ /dev/null @@ -1,871 +0,0 @@ -/* - * plugin-registry.c - * Copyright 2009-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -/* While the registry is being built (during early startup) or destroyed (during - * late shutdown), the registry_locked flag will be set. Once this flag is - * cleared, the registry will not be modified and can be read by concurrent - * threads. The one change that can happen during this time is that a plugin is - * loaded; hence the mutex must be locked before checking that a plugin is - * loaded and while loading it. */ - -#include <pthread.h> -#include <stdio.h> -#include <string.h> - -#include <glib.h> -#include <glib/gstdio.h> - -#include <libaudcore/audstrings.h> - -#include "debug.h" -#include "i18n.h" -#include "interface.h" -#include "misc.h" -#include "plugin.h" -#include "plugins.h" - -#define FILENAME "plugin-registry" -#define FORMAT 8 - -typedef struct { - GList * schemes; -} TransportPluginData; - -typedef struct { - GList * exts; -} PlaylistPluginData; - -typedef struct { - GList * keys[INPUT_KEYS]; - bool_t has_images, has_subtunes, can_write_tuple, has_infowin; -} InputPluginData; - -struct PluginHandle { - char * path; - bool_t confirmed, loaded; - int timestamp, type; - Plugin * header; - char * name, * domain; - int priority; - bool_t has_about, has_configure, enabled; - GList * watches; - void * misc; - - union { - TransportPluginData t; - PlaylistPluginData p; - InputPluginData i; - } u; -}; - -typedef struct { - PluginForEachFunc func; - void * data; -} PluginWatch; - -static const char * plugin_type_names[] = { - [PLUGIN_TYPE_TRANSPORT] = "transport", - [PLUGIN_TYPE_PLAYLIST] = "playlist", - [PLUGIN_TYPE_INPUT] = "input", - [PLUGIN_TYPE_EFFECT] = "effect", - [PLUGIN_TYPE_OUTPUT] = "output", - [PLUGIN_TYPE_VIS] = "vis", - [PLUGIN_TYPE_GENERAL] = "general", - [PLUGIN_TYPE_IFACE] = "iface"}; - -static const char * input_key_names[] = { - [INPUT_KEY_SCHEME] = "scheme", - [INPUT_KEY_EXTENSION] = "ext", - [INPUT_KEY_MIME] = "mime"}; - -static GList * plugin_list = NULL; -static bool_t registry_locked = TRUE; -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - -static PluginHandle * plugin_new (char * path, bool_t confirmed, bool_t - loaded, int timestamp, int type, Plugin * header) -{ - PluginHandle * plugin = g_slice_new (PluginHandle); - - plugin->path = path; - plugin->confirmed = confirmed; - plugin->loaded = loaded; - plugin->timestamp = timestamp; - plugin->type = type; - plugin->header = header; - plugin->name = NULL; - plugin->domain = NULL; - plugin->priority = 0; - plugin->has_about = FALSE; - plugin->has_configure = FALSE; - plugin->enabled = FALSE; - plugin->watches = NULL; - plugin->misc = NULL; - - if (type == PLUGIN_TYPE_TRANSPORT) - { - plugin->enabled = TRUE; - plugin->u.t.schemes = NULL; - } - else if (type == PLUGIN_TYPE_PLAYLIST) - { - plugin->enabled = TRUE; - plugin->u.p.exts = NULL; - } - else if (type == PLUGIN_TYPE_INPUT) - { - plugin->enabled = TRUE; - memset (plugin->u.i.keys, 0, sizeof plugin->u.i.keys); - plugin->u.i.has_images = FALSE; - plugin->u.i.has_subtunes = FALSE; - plugin->u.i.can_write_tuple = FALSE; - plugin->u.i.has_infowin = FALSE; - } - - plugin_list = g_list_prepend (plugin_list, plugin); - return plugin; -} - -static void plugin_free (PluginHandle * plugin) -{ - plugin_list = g_list_remove (plugin_list, plugin); - - for (GList * node = plugin->watches; node; node = node->next) - g_slice_free (PluginWatch, node->data); - - if (plugin->type == PLUGIN_TYPE_TRANSPORT) - g_list_free_full (plugin->u.t.schemes, (GDestroyNotify) str_unref); - else if (plugin->type == PLUGIN_TYPE_PLAYLIST) - g_list_free_full (plugin->u.p.exts, (GDestroyNotify) str_unref); - else if (plugin->type == PLUGIN_TYPE_INPUT) - { - for (int key = 0; key < INPUT_KEYS; key ++) - g_list_free_full (plugin->u.i.keys[key], (GDestroyNotify) str_unref); - } - - str_unref (plugin->path); - str_unref (plugin->name); - str_unref (plugin->domain); - g_free (plugin->misc); - g_slice_free (PluginHandle, plugin); -} - -static FILE * open_registry_file (const char * mode) -{ - const char * user_dir = get_path (AUD_PATH_USER_DIR); - SCONCAT2 (path, user_dir, "/" FILENAME); - return g_fopen (path, mode); -} - -static void transport_plugin_save (PluginHandle * plugin, FILE * handle) -{ - for (GList * node = plugin->u.t.schemes; node; node = node->next) - fprintf (handle, "scheme %s\n", (const char *) node->data); -} - -static void playlist_plugin_save (PluginHandle * plugin, FILE * handle) -{ - for (GList * node = plugin->u.p.exts; node; node = node->next) - fprintf (handle, "ext %s\n", (const char *) node->data); -} - -static void input_plugin_save (PluginHandle * plugin, FILE * handle) -{ - for (int key = 0; key < INPUT_KEYS; key ++) - { - for (GList * node = plugin->u.i.keys[key]; node; node = node->next) - fprintf (handle, "%s %s\n", input_key_names[key], (const char *) - node->data); - } - - fprintf (handle, "images %d\n", plugin->u.i.has_images); - fprintf (handle, "subtunes %d\n", plugin->u.i.has_subtunes); - fprintf (handle, "writes %d\n", plugin->u.i.can_write_tuple); - fprintf (handle, "infowin %d\n", plugin->u.i.has_infowin); -} - -static void plugin_save (PluginHandle * plugin, FILE * handle) -{ - fprintf (handle, "%s %s\n", plugin_type_names[plugin->type], plugin->path); - fprintf (handle, "stamp %d\n", plugin->timestamp); - fprintf (handle, "name %s\n", plugin->name); - - if (plugin->domain) - fprintf (handle, "domain %s\n", plugin->domain); - - fprintf (handle, "priority %d\n", plugin->priority); - fprintf (handle, "about %d\n", plugin->has_about); - fprintf (handle, "config %d\n", plugin->has_configure); - fprintf (handle, "enabled %d\n", plugin->enabled); - - if (plugin->type == PLUGIN_TYPE_TRANSPORT) - transport_plugin_save (plugin, handle); - else if (plugin->type == PLUGIN_TYPE_PLAYLIST) - playlist_plugin_save (plugin, handle); - else if (plugin->type == PLUGIN_TYPE_INPUT) - input_plugin_save (plugin, handle); -} - -void plugin_registry_save (void) -{ - FILE * handle = open_registry_file ("w"); - g_return_if_fail (handle); - - fprintf (handle, "format %d\n", FORMAT); - - g_list_foreach (plugin_list, (GFunc) plugin_save, handle); - fclose (handle); - - g_list_foreach (plugin_list, (GFunc) plugin_free, NULL); - registry_locked = TRUE; -} - -static char parse_key[512]; -static char * parse_value; - -static void parse_next (FILE * handle) -{ - parse_value = NULL; - - if (! fgets (parse_key, sizeof parse_key, handle)) - return; - - char * space = strchr (parse_key, ' '); - if (! space) - return; - - * space = 0; - parse_value = space + 1; - - char * newline = strchr (parse_value, '\n'); - if (newline) - * newline = 0; -} - -static bool_t parse_integer (const char * key, int * value) -{ - return (parse_value && ! strcmp (parse_key, key) && sscanf (parse_value, - "%d", value) == 1); -} - -static char * parse_string (const char * key) -{ - return (parse_value && ! strcmp (parse_key, key)) ? str_get (parse_value) : NULL; -} - -static void transport_plugin_parse (PluginHandle * plugin, FILE * handle) -{ - char * value; - while ((value = parse_string ("scheme"))) - { - plugin->u.t.schemes = g_list_prepend (plugin->u.t.schemes, value); - parse_next (handle); - } -} - -static void playlist_plugin_parse (PluginHandle * plugin, FILE * handle) -{ - char * value; - while ((value = parse_string ("ext"))) - { - plugin->u.p.exts = g_list_prepend (plugin->u.p.exts, value); - parse_next (handle); - } -} - -static void input_plugin_parse (PluginHandle * plugin, FILE * handle) -{ - for (int key = 0; key < INPUT_KEYS; key ++) - { - char * value; - while ((value = parse_string (input_key_names[key]))) - { - plugin->u.i.keys[key] = g_list_prepend (plugin->u.i.keys[key], - value); - parse_next (handle); - } - } - - if (parse_integer ("images", & plugin->u.i.has_images)) - parse_next (handle); - if (parse_integer ("subtunes", & plugin->u.i.has_subtunes)) - parse_next (handle); - if (parse_integer ("writes", & plugin->u.i.can_write_tuple)) - parse_next (handle); - if (parse_integer ("infowin", & plugin->u.i.has_infowin)) - parse_next (handle); -} - -static bool_t plugin_parse (FILE * handle) -{ - char * path = NULL; - - int type; - for (type = 0; type < PLUGIN_TYPES; type ++) - { - if ((path = parse_string (plugin_type_names[type]))) - goto FOUND; - } - - return FALSE; - -FOUND: - parse_next (handle); - - int timestamp; - if (! parse_integer ("stamp", & timestamp)) - { - str_unref (path); - return FALSE; - } - - PluginHandle * plugin = plugin_new (path, FALSE, FALSE, timestamp, type, - NULL); - parse_next (handle); - - if ((plugin->name = parse_string ("name"))) - parse_next (handle); - if ((plugin->domain = parse_string ("domain"))) - parse_next (handle); - if (parse_integer ("priority", & plugin->priority)) - parse_next (handle); - if (parse_integer ("about", & plugin->has_about)) - parse_next (handle); - if (parse_integer ("config", & plugin->has_configure)) - parse_next (handle); - if (parse_integer ("enabled", & plugin->enabled)) - parse_next (handle); - - if (type == PLUGIN_TYPE_TRANSPORT) - transport_plugin_parse (plugin, handle); - else if (type == PLUGIN_TYPE_PLAYLIST) - playlist_plugin_parse (plugin, handle); - else if (type == PLUGIN_TYPE_INPUT) - input_plugin_parse (plugin, handle); - - return TRUE; -} - -void plugin_registry_load (void) -{ - FILE * handle = open_registry_file ("r"); - if (! handle) - goto UNLOCK; - - parse_next (handle); - - int format; - if (! parse_integer ("format", & format) || format != FORMAT) - goto ERR; - - parse_next (handle); - - while (plugin_parse (handle)) - ; - -ERR: - fclose (handle); -UNLOCK: - registry_locked = FALSE; -} - -static void plugin_prune (PluginHandle * plugin) -{ - if (plugin->confirmed) - return; - - AUDDBG ("Plugin not found: %s\n", plugin->path); - plugin_free (plugin); -} - -int plugin_compare (PluginHandle * a, PluginHandle * b) -{ - if (a->type < b->type) - return -1; - if (a->type > b->type) - return 1; - if (a->priority < b->priority) - return -1; - if (a->priority > b->priority) - return 1; - - int diff; - if ((diff = str_compare (dgettext (a->domain, a->name), dgettext (b->domain, b->name)))) - return diff; - - return str_compare (a->path, b->path); -} - -void plugin_registry_prune (void) -{ - g_list_foreach (plugin_list, (GFunc) plugin_prune, NULL); - plugin_list = g_list_sort (plugin_list, (GCompareFunc) plugin_compare); - registry_locked = TRUE; -} - -static int plugin_lookup_cb (PluginHandle * plugin, const char * path) -{ - return strcmp (plugin->path, path); -} - -PluginHandle * plugin_lookup (const char * path) -{ - GList * node = g_list_find_custom (plugin_list, path, (GCompareFunc) - plugin_lookup_cb); - return node ? node->data : NULL; -} - -static int plugin_lookup_basename_cb (PluginHandle * plugin, const char * basename) -{ - const char * slash = strrchr (plugin->path, G_DIR_SEPARATOR); - if (! slash) - return TRUE; - - const char * dot = strrchr (slash + 1, '.'); - if (! dot) - return TRUE; - - return strncmp (slash + 1, basename, dot - (slash + 1)); -} - -/* Note: If there are multiple plugins with the same basename, this returns only - * one of them. So give different plugins different basenames. --jlindgren */ -PluginHandle * plugin_lookup_basename (const char * basename) -{ - GList * node = g_list_find_custom (plugin_list, basename, (GCompareFunc) - plugin_lookup_basename_cb); - return node ? node->data : NULL; -} - -static void plugin_get_info (PluginHandle * plugin, bool_t new) -{ - Plugin * header = plugin->header; - - str_unref (plugin->name); - str_unref (plugin->domain); - plugin->name = str_get (header->name); - plugin->domain = PLUGIN_HAS_FUNC (header, domain) ? str_get (header->domain) : NULL; - plugin->has_about = PLUGIN_HAS_FUNC (header, about) || PLUGIN_HAS_FUNC (header, about_text); - plugin->has_configure = PLUGIN_HAS_FUNC (header, configure) || PLUGIN_HAS_FUNC (header, prefs); - - if (header->type == PLUGIN_TYPE_TRANSPORT) - { - TransportPlugin * tp = (TransportPlugin *) header; - - g_list_free_full (plugin->u.t.schemes, (GDestroyNotify) str_unref); - plugin->u.t.schemes = NULL; - - for (int i = 0; tp->schemes[i]; i ++) - plugin->u.t.schemes = g_list_prepend (plugin->u.t.schemes, str_get (tp->schemes[i])); - } - else if (header->type == PLUGIN_TYPE_PLAYLIST) - { - PlaylistPlugin * pp = (PlaylistPlugin *) header; - - g_list_free_full (plugin->u.p.exts, (GDestroyNotify) str_unref); - plugin->u.p.exts = NULL; - - for (int i = 0; pp->extensions[i]; i ++) - plugin->u.p.exts = g_list_prepend (plugin->u.p.exts, str_get (pp->extensions[i])); - } - else if (header->type == PLUGIN_TYPE_INPUT) - { - InputPlugin * ip = (InputPlugin *) header; - plugin->priority = ip->priority; - - for (int key = 0; key < INPUT_KEYS; key ++) - { - g_list_free_full (plugin->u.i.keys[key], (GDestroyNotify) str_unref); - plugin->u.i.keys[key] = NULL; - } - - if (PLUGIN_HAS_FUNC (ip, extensions)) - { - for (int i = 0; ip->extensions[i]; i ++) - plugin->u.i.keys[INPUT_KEY_EXTENSION] = g_list_prepend - (plugin->u.i.keys[INPUT_KEY_EXTENSION], - str_get (ip->extensions[i])); - } - - if (PLUGIN_HAS_FUNC (ip, mimes)) - { - for (int i = 0; ip->mimes[i]; i ++) - plugin->u.i.keys[INPUT_KEY_MIME] = g_list_prepend - (plugin->u.i.keys[INPUT_KEY_MIME], str_get (ip->mimes[i])); - } - - if (PLUGIN_HAS_FUNC (ip, schemes)) - { - for (int i = 0; ip->schemes[i]; i ++) - plugin->u.i.keys[INPUT_KEY_SCHEME] = g_list_prepend - (plugin->u.i.keys[INPUT_KEY_SCHEME], str_get (ip->schemes[i])); - } - - plugin->u.i.has_images = PLUGIN_HAS_FUNC (ip, get_song_image); - plugin->u.i.has_subtunes = ip->have_subtune; - plugin->u.i.can_write_tuple = PLUGIN_HAS_FUNC (ip, update_song_tuple); - plugin->u.i.has_infowin = PLUGIN_HAS_FUNC (ip, file_info_box); - } - else if (header->type == PLUGIN_TYPE_OUTPUT) - { - OutputPlugin * op = (OutputPlugin *) header; - plugin->priority = 10 - op->probe_priority; - } - else if (header->type == PLUGIN_TYPE_EFFECT) - { - EffectPlugin * ep = (EffectPlugin *) header; - plugin->priority = ep->order; - } - else if (header->type == PLUGIN_TYPE_GENERAL) - { - GeneralPlugin * gp = (GeneralPlugin *) header; - if (new) - plugin->enabled = gp->enabled_by_default; - } -} - -void plugin_register (const char * path, int timestamp) -{ - PluginHandle * plugin = plugin_lookup (path); - - if (plugin) - { - AUDDBG ("Register plugin: %s\n", path); - plugin->confirmed = TRUE; - - if (plugin->timestamp != timestamp) - { - AUDDBG ("Rescan plugin: %s\n", path); - Plugin * header = plugin_load (path); - if (! header || header->type != plugin->type) - return; - - plugin->loaded = TRUE; - plugin->header = header; - plugin->timestamp = timestamp; - - plugin_get_info (plugin, FALSE); - } - } - else - { - AUDDBG ("New plugin: %s\n", path); - Plugin * header = plugin_load (path); - if (! header) - return; - - plugin = plugin_new (str_get (path), TRUE, TRUE, timestamp, - header->type, header); - - plugin_get_info (plugin, TRUE); - } -} - -int plugin_get_type (PluginHandle * plugin) -{ - return plugin->type; -} - -const char * plugin_get_filename (PluginHandle * plugin) -{ - return plugin->path; -} - -const void * plugin_get_header (PluginHandle * plugin) -{ - pthread_mutex_lock (& mutex); - - if (! plugin->loaded) - { - Plugin * header = plugin_load (plugin->path); - if (! header || header->type != plugin->type) - goto DONE; - - plugin->loaded = TRUE; - plugin->header = header; - } - -DONE: - pthread_mutex_unlock (& mutex); - return plugin->header; -} - -static int plugin_by_header_cb (PluginHandle * plugin, const void * header) -{ - return (plugin->header == header) ? 0 : -1; -} - -PluginHandle * plugin_by_header (const void * header) -{ - GList * node = g_list_find_custom (plugin_list, header, (GCompareFunc) - plugin_by_header_cb); - return node ? node->data : NULL; -} - -int plugin_count (int type) -{ - int count = 0; - - for (GList * node = plugin_list; node; node = node->next) - { - PluginHandle * plugin = node->data; - if (plugin->type == type) - count ++; - } - - return count; -} - -int plugin_get_index (PluginHandle * plugin) -{ - int index = 0; - - for (GList * node = plugin_list; node; node = node->next) - { - PluginHandle * plugin2 = node->data; - if (plugin2->type == plugin->type) - { - if (plugin2 == plugin) - return index; - index ++; - } - } - - return -1; -} - -PluginHandle * plugin_by_index (int type, int index) -{ - for (GList * node = plugin_list; node; node = node->next) - { - PluginHandle * plugin = node->data; - if (plugin->type == type) - { - if (! index) - return plugin; - index --; - } - } - - return NULL; -} - -void plugin_for_each (int type, PluginForEachFunc func, void * data) -{ - for (GList * node = plugin_list; node; node = node->next) - { - if (((PluginHandle *) node->data)->type != type) - continue; - if (! func (node->data, data)) - break; - } -} - -const char * plugin_get_name (PluginHandle * plugin) -{ - return dgettext (plugin->domain, plugin->name); -} - -bool_t plugin_has_about (PluginHandle * plugin) -{ - return plugin->has_about; -} - -bool_t plugin_has_configure (PluginHandle * plugin) -{ - return plugin->has_configure; -} - -bool_t plugin_get_enabled (PluginHandle * plugin) -{ - return plugin->enabled; -} - -static void plugin_call_watches (PluginHandle * plugin) -{ - for (GList * node = plugin->watches; node; ) - { - GList * next = node->next; - PluginWatch * watch = node->data; - - if (! watch->func (plugin, watch->data)) - { - g_slice_free (PluginWatch, watch); - plugin->watches = g_list_delete_link (plugin->watches, node); - } - - node = next; - } -} - -void plugin_set_enabled (PluginHandle * plugin, bool_t enabled) -{ - plugin->enabled = enabled; - plugin_call_watches (plugin); -} - -typedef struct { - PluginForEachFunc func; - void * data; -} PluginForEnabledState; - -static bool_t plugin_for_enabled_cb (PluginHandle * plugin, - PluginForEnabledState * state) -{ - if (! plugin->enabled) - return TRUE; - return state->func (plugin, state->data); -} - -void plugin_for_enabled (int type, PluginForEachFunc func, void * data) -{ - PluginForEnabledState state = {func, data}; - plugin_for_each (type, (PluginForEachFunc) plugin_for_enabled_cb, & state); -} - -void plugin_add_watch (PluginHandle * plugin, PluginForEachFunc func, void * - data) -{ - PluginWatch * watch = g_slice_new (PluginWatch); - watch->func = func; - watch->data = data; - plugin->watches = g_list_prepend (plugin->watches, watch); -} - -void plugin_remove_watch (PluginHandle * plugin, PluginForEachFunc func, void * - data) -{ - for (GList * node = plugin->watches; node; ) - { - GList * next = node->next; - PluginWatch * watch = node->data; - - if (watch->func == func && watch->data == data) - { - g_slice_free (PluginWatch, watch); - plugin->watches = g_list_delete_link (plugin->watches, node); - } - - node = next; - } -} - -void * plugin_get_misc_data (PluginHandle * plugin, int size) -{ - if (! plugin->misc) - plugin->misc = g_malloc0 (size); - - return plugin->misc; -} - -typedef struct { - const char * scheme; - PluginHandle * plugin; -} TransportPluginForSchemeState; - -static bool_t transport_plugin_for_scheme_cb (PluginHandle * plugin, - TransportPluginForSchemeState * state) -{ - if (! g_list_find_custom (plugin->u.t.schemes, state->scheme, - (GCompareFunc) g_ascii_strcasecmp)) - return TRUE; - - state->plugin = plugin; - return FALSE; -} - -PluginHandle * transport_plugin_for_scheme (const char * scheme) -{ - TransportPluginForSchemeState state = {scheme, NULL}; - plugin_for_enabled (PLUGIN_TYPE_TRANSPORT, (PluginForEachFunc) - transport_plugin_for_scheme_cb, & state); - return state.plugin; -} - -typedef struct { - const char * ext; - PluginForEachFunc func; - void * data; -} PlaylistPluginForExtState; - -static bool_t playlist_plugin_for_ext_cb (PluginHandle * plugin, - PlaylistPluginForExtState * state) -{ - if (! g_list_find_custom (plugin->u.p.exts, state->ext, - (GCompareFunc) g_ascii_strcasecmp)) - return TRUE; - - return state->func (plugin, state->data); -} - -void playlist_plugin_for_ext (const char * ext, PluginForEachFunc func, void * data) -{ - PlaylistPluginForExtState state = {ext, func, data}; - plugin_for_enabled (PLUGIN_TYPE_PLAYLIST, (PluginForEachFunc) - playlist_plugin_for_ext_cb, & state); -} - -typedef struct { - int key; - const char * value; - PluginForEachFunc func; - void * data; -} InputPluginForKeyState; - -static bool_t input_plugin_for_key_cb (PluginHandle * plugin, - InputPluginForKeyState * state) -{ - if (! g_list_find_custom (plugin->u.i.keys[state->key], state->value, - (GCompareFunc) g_ascii_strcasecmp)) - return TRUE; - - return state->func (plugin, state->data); -} - -void input_plugin_for_key (int key, const char * value, PluginForEachFunc - func, void * data) -{ - InputPluginForKeyState state = {key, value, func, data}; - plugin_for_enabled (PLUGIN_TYPE_INPUT, (PluginForEachFunc) - input_plugin_for_key_cb, & state); -} - -bool_t input_plugin_has_images (PluginHandle * plugin) -{ - g_return_val_if_fail (plugin->type == PLUGIN_TYPE_INPUT, FALSE); - return plugin->u.i.has_images; -} - -bool_t input_plugin_has_subtunes (PluginHandle * plugin) -{ - g_return_val_if_fail (plugin->type == PLUGIN_TYPE_INPUT, FALSE); - return plugin->u.i.has_subtunes; -} - -bool_t input_plugin_can_write_tuple (PluginHandle * plugin) -{ - g_return_val_if_fail (plugin->type == PLUGIN_TYPE_INPUT, FALSE); - return plugin->u.i.can_write_tuple; -} - -bool_t input_plugin_has_infowin (PluginHandle * plugin) -{ - g_return_val_if_fail (plugin->type == PLUGIN_TYPE_INPUT, FALSE); - return plugin->u.i.has_infowin; -} diff --git a/src/audacious/plugin.h b/src/audacious/plugin.h deleted file mode 100644 index 49f5f27..0000000 --- a/src/audacious/plugin.h +++ /dev/null @@ -1,373 +0,0 @@ -/* - * plugin.h - * Copyright 2005-2013 William Pitcock, Yoshiki Yazawa, Eugene Zagidullin, and - * John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_PLUGIN_H -#define AUDACIOUS_PLUGIN_H - -#include <audacious/api.h> -#include <audacious/types.h> -#include <libaudcore/audio.h> -#include <libaudcore/index.h> -#include <libaudcore/tuple.h> -#include <libaudcore/vfs.h> - -/* "Magic" bytes identifying an Audacious plugin header. */ -#define _AUD_PLUGIN_MAGIC 0x8EAC8DE2 - -/* A NOTE ON THREADS - * - * How thread-safe a plugin must be depends on the type of plugin. Note that - * some parts of the Audacious API are *not* thread-safe and therefore cannot be - * used in some parts of some plugins; for example, input plugins cannot use - * GUI-related calls or access the playlist except in about() and configure(). - * - * Thread-safe plugins: transport, playlist, input, effect, and output. These - * must be mostly thread-safe. init() and cleanup() may be called from - * secondary threads; however, no other functions provided by the plugin will be - * called at the same time. about() and configure() will be called only from - * the main thread. All other functions provided by the plugin may be called - * from any thread and from multiple threads simultaneously. - * - * Exceptions: - * - Because many existing input plugins are not coded to handle simultaneous - * calls to play(), play() will only be called from one thread at a time. New - * plugins should not rely on this exception, though. - * - Some combinations of calls, especially for output and effect plugins, make - * no sense; for example, flush() in an output plugin will only be called - * after open_audio() and before close_audio(). - * - * Single-thread plugins: visualization, general, and interface. Functions - * provided by these plugins will only be called from the main thread. */ - -/* CROSS-PLUGIN MESSAGES - * - * Since 3.2, Audacious implements a basic messaging system between plugins. - * Messages are sent using aud_plugin_send_message() and received through the - * take_message() method specified in the header of the receiving plugin. - * Plugins that do not need to receive messages can set take_message() to NULL. - * - * Each message includes a code indicating the type of message, a pointer to - * some data, and a value indicating the size of that data. What the message - * data contains is entirely up to the two plugins involved. For this reason, it - * is crucial that both plugins agree on the meaning of the message codes used. - * - * Once the message is sent, an integer error code is returned. If the receiving - * plugin does not provide the take_message() method, ENOSYS is returned. If - * take_message() does not recognize the message code, it should ignore the - * message and return EINVAL. An error code of zero represents success. Other - * error codes may be used with more specific meanings. - * - * For the time being, aud_plugin_send_message() should only be called from the - * program's main thread. */ - -#define PLUGIN_COMMON_FIELDS \ - int magic; /* checked against _AUD_PLUGIN_MAGIC */ \ - int version; /* checked against _AUD_PLUGIN_VERSION */ \ - int type; /* PLUGIN_TYPE_XXX */ \ - int size; /* size in bytes of the struct */ \ - const char * name; \ - const char * domain; /* for gettext */ \ - const char * about_text; \ - const PluginPreferences * prefs; \ - bool_t (* init) (void); \ - void (* cleanup) (void); \ - int (* take_message) (const char * code, const void * data, int size); \ - void (* about) (void); /* use about_text instead if possible */ \ - void (* configure) (void); /* use prefs instead if possible */ \ - void * reserved1; \ - void * reserved2; \ - void * reserved3; \ - void * reserved4; - -struct _Plugin -{ - PLUGIN_COMMON_FIELDS -}; - -struct _TransportPlugin -{ - PLUGIN_COMMON_FIELDS - - /* supported URI schemes (without "://") - * (array terminated with null pointer) */ - const char * const * schemes; - - /* file operation implementations - * (struct of function pointers, may contain null pointers) */ - const VFSConstructor * vtable; -}; - -struct _PlaylistPlugin -{ - PLUGIN_COMMON_FIELDS - - /* supported file extensions (without periods) - * (array terminated with null pointer) */ - const char * const * extensions; - - /* path: URI of playlist file (in) - * file: VFS handle of playlist file (in, read-only file, not seekable) - * title: title of playlist (out, string-pooled) - * filenames: container to fill with URIs read from playlist file - * (in-out, list of (char *), string-pooled) - * tuples: container to fill with metadata read from playlist - * (in-out, list of (Tuple *), may contain null pointers) */ - bool_t (* load) (const char * path, VFSFile * file, char * * title, - Index * filenames, Index * tuples); - - /* path: URI of playlist file (in) - * file: VFS handle of playlist file (in, write-only file, not seekable) - * title: title of playlist (in) - * filenames: container filled with URIs to be written to playlist - * (in, list of (char *)) - * tuples: container filled with metadata to be written to playlist - * (in, list of (Tuple *), may contain null pointers) */ - bool_t (* save) (const char * path, VFSFile * file, const char * title, - Index * filenames, Index * tuples); -}; - -struct _OutputPlugin -{ - PLUGIN_COMMON_FIELDS - - /* During probing, plugins with higher priority (10 to 0) are tried first. */ - int probe_priority; - - /* Returns current volume for left and right channels (0 to 100). */ - void (* get_volume) (int * l, int * r); - - /* Changes volume for left and right channels (0 to 100). */ - void (* set_volume) (int l, int r); - - /* Begins playback of a PCM stream. <format> is one of the FMT_* - * enumeration values defined in libaudcore/audio.h. Returns nonzero on - * success. */ - bool_t (* open_audio) (int format, int rate, int chans); - - /* Ends playback. Any buffered audio data is discarded. */ - void (* close_audio) (void); - - /* Returns how many bytes of data may be passed to a following write_audio() - * call. NULL if the plugin supports only blocking writes (not recommended). */ - int (* buffer_free) (void); - - /* Waits until buffer_free() will return a size greater than zero. - * output_time(), pause(), and flush() may be called meanwhile; if flush() - * is called, period_wait() should return immediately. NULL if the plugin - * supports only blocking writes (not recommended). */ - void (* period_wait) (void); - - /* Buffers <size> bytes of data, in the format given to open_audio(). */ - void (* write_audio) (void * data, int size); - - /* Waits until all buffered data has been heard by the user. */ - void (* drain) (void); - - /* Returns time count (in milliseconds) of how much data has been heard by - * the user. */ - int (* output_time) (void); - - /* Pauses the stream if <p> is nonzero; otherwise unpauses it. - * write_audio() will not be called while the stream is paused. */ - void (* pause) (bool_t p); - - /* Discards any buffered audio data and sets the time counter (in - * milliseconds) of data written. */ - void (* flush) (int time); - - /* Whether close_audio() and open_audio() must always be called between - * songs, even if the audio format is the same. Note that this defeats - * gapless playback. */ - bool_t force_reopen; -}; - -struct _EffectPlugin -{ - PLUGIN_COMMON_FIELDS - - /* All processing is done in floating point. If the effect plugin wants to - * change the channel count or sample rate, it can change the parameters - * passed to start(). They cannot be changed in the middle of a song. */ - void (* start) (int * channels, int * rate); - - /* process() has two options: modify the samples in place and leave the data - * pointer unchanged or copy them into a buffer of its own. If it sets the - * pointer to dynamically allocated memory, it is the plugin's job to free - * that memory. process() may return different lengths of audio than it is - * passed, even a zero length. */ - void (* process) (float * * data, int * samples); - - /* Optional. A seek is taking place; any buffers should be discarded. */ - void (* flush) (void); - - /* Exactly like process() except that any buffers should be drained (i.e. - * the data processed and returned). finish() will be called a second time - * at the end of the last song in the playlist. */ - void (* finish) (float * * data, int * samples); - - /* Required only for plugins that change the time domain (e.g. a time - * stretch) or use read-ahead buffering. translate_delay() must do two - * things: first, translate <delay> (which is in milliseconds) from the - * output time domain back to the input time domain; second, increase - * <delay> by the size of the read-ahead buffer. It should return the - * adjusted delay. */ - int (* adjust_delay) (int delay); - - /* Effects with lowest order (0 to 9) are applied first. */ - int order; - - /* If the effect does not change the number of channels or the sampling - * rate, it can be enabled and disabled more smoothly. */ - bool_t preserves_format; -}; - -struct _InputPlugin -{ - PLUGIN_COMMON_FIELDS - - /* Nonzero if the files handled by the plugin may contain more than one - * song. When reading the tuple for such a file, the plugin should set the - * FIELD_SUBSONG_NUM field to the number of songs in the file. For all - * other files, the field should be left unset. - * - * Example: - * 1. User adds a file named "somefile.xxx" to the playlist. Having - * determined that this plugin can handle the file, Audacious opens the file - * and calls probe_for_tuple(). probe_for_tuple() sees that there are 3 - * songs in the file and sets FIELD_SUBSONG_NUM to 3. - * 2. For each song in the file, Audacious opens the file and calls - * probe_for_tuple() -- this time, however, a question mark and song number - * are appended to the file name passed: "somefile.sid?2" refers to the - * second song in the file "somefile.sid". - * 3. When one of the songs is played, Audacious opens the file and calls - * play() with a file name modified in this way. - */ - bool_t have_subtune; - - /* Pointer to an array (terminated with NULL) of file extensions associated - * with file types the plugin can handle. */ - const char * const * extensions; - /* Pointer to an array (terminated with NULL) of MIME types the plugin can - * handle. */ - const char * const * mimes; - - /* Pointer to an array (terminated with NULL) of custom URI schemes the - * plugin supports. Plugins using custom URI schemes are expected to - * handle their own I/O. Hence, any VFSFile pointers passed to play(), - * probe_for_tuple(), etc. will be NULL. */ - const char * const * schemes; - - /* How quickly the plugin should be tried in searching for a plugin to - * handle a file which could not be identified from its extension. Plugins - * with priority 0 are tried first, 10 last. */ - int priority; - - /* Returns TRUE if the plugin can handle the file. */ - bool_t (* is_our_file_from_vfs) (const char * filename, VFSFile * file); - - /* Reads metadata from the file, returning a reference to the tuple produced. */ - Tuple * (* probe_for_tuple) (const char * filename, VFSFile * file); - - /* Plays the file. Returns FALSE on error. Also see input-api.h. */ - bool_t (* play) (const char * filename, VFSFile * file); - - /* Optional. Writes metadata to the file, returning FALSE on error. */ - bool_t (* update_song_tuple) (const char * filename, VFSFile * file, const Tuple * tuple); - - /* Optional. Reads an album art image (JPEG or PNG data) from the file. - * Returns a pointer to the data along with its size in bytes. The returned - * data will be freed when no longer needed. Returns FALSE on error. */ - bool_t (* get_song_image) (const char * filename, VFSFile * file, - void * * data, int64_t * size); - - /* Optional. Displays a window showing info about the file. In general, - * this function should be avoided since Audacious already provides a file - * info window. */ - void (* file_info_box) (const char * filename); -}; - -struct _GeneralPlugin -{ - PLUGIN_COMMON_FIELDS - - bool_t enabled_by_default; - - /* GtkWidget * (* get_widget) (void); */ - void * (* get_widget) (void); -}; - -struct _VisPlugin -{ - PLUGIN_COMMON_FIELDS - - /* reset internal state and clear display */ - void (* clear) (void); - - /* 512 frames of a single-channel PCM signal */ - void (* render_mono_pcm) (const float * pcm); - - /* 512 frames of an interleaved multi-channel PCM signal */ - void (* render_multi_pcm) (const float * pcm, int channels); - - /* intensity of frequencies 1/512, 2/512, ..., 256/512 of sample rate */ - void (* render_freq) (const float * freq); - - /* GtkWidget * (* get_widget) (void); */ - void * (* get_widget) (void); -}; - -struct _IfacePlugin -{ - PLUGIN_COMMON_FIELDS - - void (* show) (bool_t show); - - void (* run_gtk_plugin) (void /* GtkWidget */ * widget, const char * name); - void (* stop_gtk_plugin) (void /* GtkWidget */ * widget); -}; - -#undef PLUGIN_COMMON_FIELDS - -#define AUD_PLUGIN(stype, itype, ...) \ -AudAPITable * _aud_api_table = NULL; \ -stype _aud_plugin_self = { \ - .magic = _AUD_PLUGIN_MAGIC, \ - .version = _AUD_PLUGIN_VERSION, \ - .type = itype, \ - .size = sizeof (stype), \ - __VA_ARGS__}; \ -stype * get_plugin_info (AudAPITable * table) { \ - _aud_api_table = table; \ - return & _aud_plugin_self; \ -} - -#define AUD_TRANSPORT_PLUGIN(...) AUD_PLUGIN (TransportPlugin, PLUGIN_TYPE_TRANSPORT, __VA_ARGS__) -#define AUD_PLAYLIST_PLUGIN(...) AUD_PLUGIN (PlaylistPlugin, PLUGIN_TYPE_PLAYLIST, __VA_ARGS__) -#define AUD_INPUT_PLUGIN(...) AUD_PLUGIN (InputPlugin, PLUGIN_TYPE_INPUT, __VA_ARGS__) -#define AUD_EFFECT_PLUGIN(...) AUD_PLUGIN (EffectPlugin, PLUGIN_TYPE_EFFECT, __VA_ARGS__) -#define AUD_OUTPUT_PLUGIN(...) AUD_PLUGIN (OutputPlugin, PLUGIN_TYPE_OUTPUT, __VA_ARGS__) -#define AUD_VIS_PLUGIN(...) AUD_PLUGIN (VisPlugin, PLUGIN_TYPE_VIS, __VA_ARGS__) -#define AUD_GENERAL_PLUGIN(...) AUD_PLUGIN (GeneralPlugin, PLUGIN_TYPE_GENERAL, __VA_ARGS__) -#define AUD_IFACE_PLUGIN(...) AUD_PLUGIN (IfacePlugin, PLUGIN_TYPE_IFACE, __VA_ARGS__) - -#define PLUGIN_HAS_FUNC(p, func) \ - ((p)->size > (char *) & (p)->func - (char *) (p) && (p)->func) - -#endif /* AUDACIOUS_PLUGIN_H */ diff --git a/src/audacious/pluginenum.c b/src/audacious/pluginenum.c deleted file mode 100644 index 58216a5..0000000 --- a/src/audacious/pluginenum.c +++ /dev/null @@ -1,210 +0,0 @@ -/* - * pluginenum.c - * Copyright 2007-2013 William Pitcock and John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <assert.h> -#include <errno.h> -#include <pthread.h> -#include <string.h> -#include <sys/stat.h> - -#include <glib.h> -#include <glib/gstdio.h> -#include <gmodule.h> - -#include <libaudcore/audstrings.h> -#include <libaudgui/init.h> - -#include "debug.h" -#include "plugin.h" -#include "util.h" - -#define AUD_API_DECLARE -#include "drct.h" -#include "input.h" -#include "misc.h" -#include "playlist.h" -#include "plugins.h" -#undef AUD_API_DECLARE - -static const char * plugin_dir_list[] = { - "Transport", - "Container", - "Input", - "Output", - "Effect", - "General", - "Visualization" -}; - -char verbose = 0; - -AudAPITable api_table = { - .drct_api = & drct_api, - .input_api = & input_api, - .misc_api = & misc_api, - .playlist_api = & playlist_api, - .plugins_api = & plugins_api, - .verbose = & verbose}; - -typedef struct { - Plugin * header; - GModule * module; -} LoadedModule; - -static GList * loaded_modules = NULL; -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - -Plugin * plugin_load (const char * filename) -{ - AUDDBG ("Loading plugin: %s.\n", filename); - - GModule * module = g_module_open (filename, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); - if (! module) - { - fprintf (stderr, " *** ERROR: %s could not be loaded: %s\n", filename, - g_module_error ()); - return NULL; - } - - void * ptr; - if (! g_module_symbol (module, "get_plugin_info", & ptr)) - ptr = NULL; - - Plugin * (* func) (AudAPITable * table) = ptr; - Plugin * header; - - if (! func || ! (header = func (& api_table)) || header->magic != _AUD_PLUGIN_MAGIC) - { - fprintf (stderr, " *** ERROR: %s is not a valid Audacious plugin.\n", filename); - g_module_close (module); - return NULL; - } - - if (header->version < _AUD_PLUGIN_VERSION_MIN || - header->version > _AUD_PLUGIN_VERSION) - { - fprintf (stderr, " *** ERROR: %s is not compatible with this version " - "of Audacious.\n", filename); - g_module_close (module); - return NULL; - } - - if (header->type == PLUGIN_TYPE_TRANSPORT || - header->type == PLUGIN_TYPE_PLAYLIST || - header->type == PLUGIN_TYPE_INPUT || - header->type == PLUGIN_TYPE_EFFECT) - { - if (PLUGIN_HAS_FUNC (header, init) && ! header->init ()) - { - fprintf (stderr, " *** ERROR: %s failed to initialize.\n", filename); - g_module_close (module); - return NULL; - } - } - - pthread_mutex_lock (& mutex); - LoadedModule * loaded = g_slice_new (LoadedModule); - loaded->header = header; - loaded->module = module; - loaded_modules = g_list_prepend (loaded_modules, loaded); - pthread_mutex_unlock (& mutex); - - return header; -} - -static void plugin2_unload (LoadedModule * loaded) -{ - Plugin * header = loaded->header; - - switch (header->type) - { - case PLUGIN_TYPE_TRANSPORT: - case PLUGIN_TYPE_PLAYLIST: - case PLUGIN_TYPE_INPUT: - case PLUGIN_TYPE_EFFECT: - if (PLUGIN_HAS_FUNC (header, cleanup)) - header->cleanup (); - break; - } - - pthread_mutex_lock (& mutex); -#ifndef VALGRIND_FRIENDLY - g_module_close (loaded->module); -#endif - g_slice_free (LoadedModule, loaded); - pthread_mutex_unlock (& mutex); -} - -/******************************************************************/ - -static bool_t scan_plugin_func(const char * path, const char * basename, void * data) -{ - if (!str_has_suffix_nocase(basename, PLUGIN_SUFFIX)) - return FALSE; - - GStatBuf st; - if (g_stat (path, & st) < 0) - { - fprintf (stderr, "Unable to stat %s: %s\n", path, strerror (errno)); - return FALSE; - } - - if (S_ISREG (st.st_mode)) - plugin_register (path, st.st_mtime); - - return FALSE; -} - -static void scan_plugins(const char * path) -{ - dir_foreach (path, scan_plugin_func, NULL); -} - -void plugin_system_init(void) -{ - assert (g_module_supported ()); - - audgui_init (& api_table, _AUD_PLUGIN_VERSION); - - plugin_registry_load (); - - const char * path = get_path (AUD_PATH_PLUGIN_DIR); - - for (int i = 0; i < ARRAY_LEN (plugin_dir_list); i ++) - { - char * dir = filename_build (path, plugin_dir_list[i]); - scan_plugins (dir); - str_unref (dir); - } - - plugin_registry_prune (); -} - -void plugin_system_cleanup(void) -{ - plugin_registry_save (); - - for (GList * node = loaded_modules; node != NULL; node = node->next) - plugin2_unload (node->data); - - g_list_free (loaded_modules); - loaded_modules = NULL; - - audgui_cleanup (); -} diff --git a/src/audacious/plugins-api.h b/src/audacious/plugins-api.h deleted file mode 100644 index af4c1b2..0000000 --- a/src/audacious/plugins-api.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * plugins-api.h - * Copyright 2010-2012 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -/* Do not include this file directly; use misc.h instead. */ - -/* CAUTION: These functions are not thread safe. */ - -/* plugin-init.c */ -AUD_FUNC1 (PluginHandle *, plugin_get_current, int, type) -AUD_FUNC2 (bool_t, plugin_enable, PluginHandle *, plugin, bool_t, enable) -AUD_FUNC1 (PluginHandle *, plugin_by_widget, void /* GtkWidget */ *, widget) -AUD_FUNC4 (int, plugin_send_message, PluginHandle *, plugin, - const char *, code, const void *, data, int, size) - -/* plugin-registry.c */ -AUD_FUNC1 (int, plugin_get_type, PluginHandle *, plugin) -AUD_FUNC1 (const char *, plugin_get_filename, PluginHandle *, plugin) -AUD_FUNC1 (PluginHandle *, plugin_lookup, const char *, filename) -AUD_FUNC1 (PluginHandle *, plugin_lookup_basename, const char *, basename) - -AUD_FUNC1 (const void *, plugin_get_header, PluginHandle *, plugin) -AUD_FUNC1 (PluginHandle *, plugin_by_header, const void *, header) - -AUD_FUNC1 (int, plugin_count, int, type) -AUD_FUNC1 (int, plugin_get_index, PluginHandle *, plugin) -AUD_FUNC2 (PluginHandle *, plugin_by_index, int, type, int, index) - -AUD_FUNC2 (int, plugin_compare, PluginHandle *, a, PluginHandle *, b) -AUD_VFUNC3 (plugin_for_each, int, type, PluginForEachFunc, func, void *, data) - -AUD_FUNC1 (bool_t, plugin_get_enabled, PluginHandle *, plugin) -AUD_VFUNC3 (plugin_for_enabled, int, type, PluginForEachFunc, func, - void *, data) - -AUD_FUNC1 (const char *, plugin_get_name, PluginHandle *, plugin) -AUD_FUNC1 (bool_t, plugin_has_about, PluginHandle *, plugin) -AUD_FUNC1 (bool_t, plugin_has_configure, PluginHandle *, plugin) -AUD_VFUNC1 (plugin_do_about, PluginHandle *, plugin) -AUD_VFUNC1 (plugin_do_configure, PluginHandle *, plugin) - -AUD_VFUNC3 (plugin_add_watch, PluginHandle *, plugin, PluginForEachFunc, - func, void *, data) -AUD_VFUNC3 (plugin_remove_watch, PluginHandle *, plugin, PluginForEachFunc, - func, void *, data) diff --git a/src/audacious/plugins.h b/src/audacious/plugins.h deleted file mode 100644 index ea956ee..0000000 --- a/src/audacious/plugins.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * plugins.h - * Copyright 2010-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_PLUGINS_H -#define AUDACIOUS_PLUGINS_H - -#include <audacious/api.h> -#include <audacious/types.h> -#include <libaudcore/core.h> - -/* returns TRUE to call again for the next plugin, FALSE to stop */ -typedef bool_t (* PluginForEachFunc) (PluginHandle * plugin, void * data); - -#define AUD_API_NAME PluginsAPI -#define AUD_API_SYMBOL plugins_api - -#ifdef _AUDACIOUS_CORE - -#include "api-local-begin.h" -#include "plugins-api.h" -#include "api-local-end.h" - -enum { - INPUT_KEY_SCHEME, - INPUT_KEY_EXTENSION, - INPUT_KEY_MIME, - INPUT_KEYS}; - -/* plugin-init.c */ -void start_plugins_one (void); -void start_plugins_two (void); -void stop_plugins_two (void); -void stop_plugins_one (void); - -/* plugin-registry.c */ -void plugin_registry_load (void); -void plugin_registry_prune (void); -void plugin_registry_save (void); - -void plugin_register (const char * path, int timestamp); - -void plugin_set_enabled (PluginHandle * plugin, bool_t enabled); -void * plugin_get_misc_data (PluginHandle * plugin, int size); - -PluginHandle * transport_plugin_for_scheme (const char * scheme); -void playlist_plugin_for_ext (const char * ext, PluginForEachFunc func, void * data); -void input_plugin_for_key (int key, const char * value, PluginForEachFunc func, void * data); -bool_t input_plugin_has_images (PluginHandle * plugin); -bool_t input_plugin_has_subtunes (PluginHandle * plugin); -bool_t input_plugin_can_write_tuple (PluginHandle * plugin); -bool_t input_plugin_has_infowin (PluginHandle * plugin); - -/* pluginenum.c */ -void plugin_system_init (void); -void plugin_system_cleanup (void); -Plugin * plugin_load (const char * path); - -#else - -#include <audacious/api-define-begin.h> -#include <audacious/plugins-api.h> -#include <audacious/api-define-end.h> - -#include <audacious/api-alias-begin.h> -#include <audacious/plugins-api.h> -#include <audacious/api-alias-end.h> - -#endif - -#undef AUD_API_NAME -#undef AUD_API_SYMBOL - -#endif - -#ifdef AUD_API_DECLARE - -#define AUD_API_NAME PluginsAPI -#define AUD_API_SYMBOL plugins_api - -#include "api-define-begin.h" -#include "plugins-api.h" -#include "api-define-end.h" - -#include "api-declare-begin.h" -#include "plugins-api.h" -#include "api-declare-end.h" - -#undef AUD_API_NAME -#undef AUD_API_SYMBOL - -#endif diff --git a/src/audacious/preferences.c b/src/audacious/preferences.c deleted file mode 100644 index eb1b651..0000000 --- a/src/audacious/preferences.c +++ /dev/null @@ -1,641 +0,0 @@ -/* - * preferences.c - * Copyright 2007-2012 Tomasz MoĆ, William Pitcock, and John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include "preferences.h" - -#include <string.h> -#include <gtk/gtk.h> - -#include "i18n.h" -#include "misc.h" - -/* HELPERS */ - -static bool_t widget_get_bool (const PreferencesWidget * widget) -{ - g_return_val_if_fail (widget->cfg_type == VALUE_BOOLEAN, FALSE); - - if (widget->cfg) - return * (bool_t *) widget->cfg; - else if (widget->cname) - return get_bool (widget->csect, widget->cname); - else - return FALSE; -} - -static void widget_set_bool (const PreferencesWidget * widget, bool_t value) -{ - g_return_if_fail (widget->cfg_type == VALUE_BOOLEAN); - - if (widget->cfg) - * (bool_t *) widget->cfg = value; - else if (widget->cname) - set_bool (widget->csect, widget->cname, value); - - if (widget->callback) - widget->callback (); -} - -static int widget_get_int (const PreferencesWidget * widget) -{ - g_return_val_if_fail (widget->cfg_type == VALUE_INT, 0); - - if (widget->cfg) - return * (int *) widget->cfg; - else if (widget->cname) - return get_int (widget->csect, widget->cname); - else - return 0; -} - -static void widget_set_int (const PreferencesWidget * widget, int value) -{ - g_return_if_fail (widget->cfg_type == VALUE_INT); - - if (widget->cfg) - * (int *) widget->cfg = value; - else if (widget->cname) - set_int (widget->csect, widget->cname, value); - - if (widget->callback) - widget->callback (); -} - -static double widget_get_double (const PreferencesWidget * widget) -{ - g_return_val_if_fail (widget->cfg_type == VALUE_FLOAT, 0); - - if (widget->cfg) - return * (float *) widget->cfg; - else if (widget->cname) - return get_double (widget->csect, widget->cname); - else - return 0; -} - -static void widget_set_double (const PreferencesWidget * widget, double value) -{ - g_return_if_fail (widget->cfg_type == VALUE_FLOAT); - - if (widget->cfg) - * (float *) widget->cfg = value; - else if (widget->cname) - set_double (widget->csect, widget->cname, value); - - if (widget->callback) - widget->callback (); -} - -static char * widget_get_string (const PreferencesWidget * widget) -{ - g_return_val_if_fail (widget->cfg_type == VALUE_STRING, NULL); - - if (widget->cfg) - return str_get (* (char * *) widget->cfg); - else if (widget->cname) - return get_str (widget->csect, widget->cname); - else - return NULL; -} - -static void widget_set_string (const PreferencesWidget * widget, const char * value) -{ - g_return_if_fail (widget->cfg_type == VALUE_STRING); - - if (widget->cfg) - { - g_free (* (char * *) widget->cfg); - * (char * *) widget->cfg = g_strdup (value); - } - else if (widget->cname) - set_str (widget->csect, widget->cname, value); - - if (widget->callback) - widget->callback (); -} - -/* WIDGET_CHK_BTN */ - -static void on_toggle_button_toggled (GtkToggleButton * button, const PreferencesWidget * widget) -{ - bool_t active = gtk_toggle_button_get_active (button); - widget_set_bool (widget, active); - - GtkWidget * child = g_object_get_data ((GObject *) button, "child"); - if (child) - gtk_widget_set_sensitive (child, active); -} - -static void init_toggle_button (GtkWidget * button, const PreferencesWidget * widget) -{ - if (widget->cfg_type != VALUE_BOOLEAN) - return; - - gtk_toggle_button_set_active ((GtkToggleButton *) button, widget_get_bool (widget)); - g_signal_connect (button, "toggled", (GCallback) on_toggle_button_toggled, (void *) widget); -} - -/* WIDGET_LABEL */ - -static void create_label (const PreferencesWidget * widget, GtkWidget * * label, - GtkWidget * * icon, const char * domain) -{ - if (widget->data.label.stock_id) - * icon = gtk_image_new_from_icon_name (widget->data.label.stock_id, GTK_ICON_SIZE_BUTTON); - - * label = gtk_label_new_with_mnemonic (dgettext (domain, widget->label)); - gtk_label_set_use_markup ((GtkLabel *) * label, TRUE); - - if (widget->data.label.single_line == FALSE) - gtk_label_set_line_wrap ((GtkLabel *) * label, TRUE); - - gtk_misc_set_alignment ((GtkMisc *) * label, 0, 0.5); -} - -/* WIDGET_RADIO_BTN */ - -static void on_radio_button_toggled (GtkWidget * button, const PreferencesWidget * widget) -{ - if (gtk_toggle_button_get_active ((GtkToggleButton *) button)) - widget_set_int (widget, widget->data.radio_btn.value); -} - -static void init_radio_button (GtkWidget * button, const PreferencesWidget * widget) -{ - if (widget->cfg_type != VALUE_INT) - return; - - if (widget_get_int (widget) == widget->data.radio_btn.value) - gtk_toggle_button_set_active ((GtkToggleButton *) button, TRUE); - - g_signal_connect (button, "toggled", (GCallback) on_radio_button_toggled, (void *) widget); -} - -/* WIDGET_SPIN_BTN */ - -static void on_spin_btn_changed_int (GtkSpinButton * button, const PreferencesWidget * widget) -{ - widget_set_int (widget, gtk_spin_button_get_value_as_int (button)); -} - -static void on_spin_btn_changed_float (GtkSpinButton * button, const PreferencesWidget * widget) -{ - widget_set_double (widget, gtk_spin_button_get_value (button)); -} - -static void create_spin_button (const PreferencesWidget * widget, - GtkWidget * * label_pre, GtkWidget * * spin_btn, GtkWidget * * label_past, - const char * domain) -{ - * label_pre = gtk_label_new (dgettext (domain, widget->label)); - * spin_btn = gtk_spin_button_new_with_range (widget->data.spin_btn.min, - widget->data.spin_btn.max, widget->data.spin_btn.step); - - if (widget->tooltip) - gtk_widget_set_tooltip_text (* spin_btn, dgettext (domain, widget->tooltip)); - - if (widget->data.spin_btn.right_label) - * label_past = gtk_label_new (dgettext (domain, widget->data.spin_btn.right_label)); - - switch (widget->cfg_type) - { - case VALUE_INT: - gtk_spin_button_set_value ((GtkSpinButton *) * spin_btn, widget_get_int (widget)); - g_signal_connect (* spin_btn, "value_changed", (GCallback) - on_spin_btn_changed_int, (void *) widget); - break; - - case VALUE_FLOAT: - gtk_spin_button_set_value ((GtkSpinButton *) * spin_btn, widget_get_double (widget)); - g_signal_connect (* spin_btn, "value_changed", (GCallback) - on_spin_btn_changed_float, (void *) widget); - break; - - default: - break; - } -} - -/* WIDGET_FONT_BTN */ - -static void on_font_btn_font_set (GtkFontButton * button, const PreferencesWidget * widget) -{ - widget_set_string (widget, gtk_font_button_get_font_name (button)); -} - -void create_font_btn (const PreferencesWidget * widget, GtkWidget * * label, - GtkWidget * * font_btn, const char * domain) -{ - * font_btn = gtk_font_button_new (); - gtk_font_button_set_use_font ((GtkFontButton *) * font_btn, TRUE); - gtk_font_button_set_use_size ((GtkFontButton *) * font_btn, TRUE); - gtk_widget_set_hexpand (* font_btn, TRUE); - - if (widget->label) - { - * label = gtk_label_new_with_mnemonic (dgettext (domain, widget->label)); - gtk_label_set_use_markup ((GtkLabel *) * label, TRUE); - gtk_misc_set_alignment ((GtkMisc *) * label, 1, 0.5); - gtk_label_set_justify ((GtkLabel *) * label, GTK_JUSTIFY_RIGHT); - gtk_label_set_mnemonic_widget ((GtkLabel *) * label, * font_btn); - } - - if (widget->data.font_btn.title) - gtk_font_button_set_title ((GtkFontButton *) * font_btn, - dgettext (domain, widget->data.font_btn.title)); - - char * name = widget_get_string (widget); - if (name) - { - gtk_font_button_set_font_name ((GtkFontButton *) * font_btn, name); - str_unref (name); - } - - g_signal_connect (* font_btn, "font_set", (GCallback) on_font_btn_font_set, (void *) widget); -} - -/* WIDGET_ENTRY */ - -static void on_entry_changed (GtkEntry * entry, const PreferencesWidget * widget) -{ - widget_set_string (widget, gtk_entry_get_text (entry)); -} - -static void create_entry (const PreferencesWidget * widget, GtkWidget * * label, - GtkWidget * * entry, const char * domain) -{ - * entry = gtk_entry_new (); - gtk_entry_set_visibility ((GtkEntry *) * entry, ! widget->data.entry.password); - gtk_widget_set_hexpand (* entry, TRUE); - - if (widget->label) - * label = gtk_label_new (dgettext (domain, widget->label)); - - if (widget->tooltip) - gtk_widget_set_tooltip_text (* entry, dgettext (domain, widget->tooltip)); - - if (widget->cfg_type == VALUE_STRING) - { - char * value = widget_get_string (widget); - if (value) - { - gtk_entry_set_text ((GtkEntry *) * entry, value); - str_unref (value); - } - - g_signal_connect (* entry, "changed", (GCallback) on_entry_changed, (void *) widget); - } -} - -/* WIDGET_COMBO_BOX */ - -static void on_cbox_changed_int (GtkComboBox * combobox, const PreferencesWidget * widget) -{ - int position = gtk_combo_box_get_active (combobox); - const ComboBoxElements * elements = g_object_get_data ((GObject *) combobox, "comboboxelements"); - widget_set_int (widget, GPOINTER_TO_INT (elements[position].value)); -} - -static void on_cbox_changed_string (GtkComboBox * combobox, const PreferencesWidget * widget) -{ - int position = gtk_combo_box_get_active (combobox); - const ComboBoxElements * elements = g_object_get_data ((GObject *) combobox, "comboboxelements"); - widget_set_string (widget, elements[position].value); -} - -static void fill_cbox (GtkWidget * combobox, const PreferencesWidget * widget, const char * domain) -{ - const ComboBoxElements * elements = widget->data.combo.elements; - int n_elements = widget->data.combo.n_elements; - - if (widget->data.combo.fill) - elements = widget->data.combo.fill (& n_elements); - - g_object_set_data ((GObject *) combobox, "comboboxelements", (void *) elements); - - for (int i = 0; i < n_elements; i ++) - gtk_combo_box_text_append_text ((GtkComboBoxText *) combobox, - dgettext (domain, elements[i].label)); - - switch (widget->cfg_type) - { - case VALUE_INT:; - int ivalue = widget_get_int (widget); - - for (int i = 0; i < n_elements; i++) - { - if (GPOINTER_TO_INT (elements[i].value) == ivalue) - { - gtk_combo_box_set_active ((GtkComboBox *) combobox, i); - break; - } - } - - g_signal_connect (combobox, "changed", (GCallback) on_cbox_changed_int, (void *) widget); - break; - - case VALUE_STRING:; - char * value = widget_get_string (widget); - - for(int i = 0; i < n_elements; i++) - { - if (value && ! strcmp (elements[i].value, value)) - { - gtk_combo_box_set_active ((GtkComboBox *) combobox, i); - break; - } - } - - str_unref (value); - - g_signal_connect (combobox, "changed", (GCallback) on_cbox_changed_string, (void *) widget); - break; - - default: - break; - } -} - -static void create_cbox (const PreferencesWidget * widget, GtkWidget * * label, - GtkWidget * * combobox, const char * domain) -{ - * combobox = gtk_combo_box_text_new (); - - if (widget->label) - * label = gtk_label_new (dgettext (domain, widget->label)); - - fill_cbox (* combobox, widget, domain); -} - -/* WIDGET_TABLE */ - -static void fill_grid (GtkWidget * grid, const PreferencesWidget * elements, - int n_elements, const char * domain) -{ - for (int i = 0; i < n_elements; i ++) - { - GtkWidget * widget_left = NULL, * widget_middle = NULL, * widget_right = NULL; - - switch (elements[i].type) - { - case WIDGET_SPIN_BTN: - create_spin_button (& elements[i], & widget_left, - & widget_middle, & widget_right, domain); - break; - - case WIDGET_LABEL: - create_label (& elements[i], & widget_middle, & widget_left, domain); - break; - - case WIDGET_FONT_BTN: - create_font_btn (& elements[i], & widget_left, & widget_middle, domain); - break; - - case WIDGET_ENTRY: - create_entry (& elements[i], & widget_left, & widget_middle, domain); - break; - - case WIDGET_COMBO_BOX: - create_cbox (& elements[i], & widget_left, & widget_middle, domain); - break; - - default: - break; - } - - if (widget_left) - gtk_grid_attach ((GtkGrid *) grid, widget_left, 0, i, 1, 1); - - if (widget_middle) - gtk_grid_attach ((GtkGrid *) grid, widget_middle, 1, i, 1, 1); - - if (widget_right) - gtk_grid_attach ((GtkGrid *) grid, widget_right, 2, i, 1, 1); - } -} - -/* ALL WIDGETS */ - -/* box: a GtkBox */ -void create_widgets_with_domain (void * box, const PreferencesWidget * widgets, - int n_widgets, const char * domain) -{ - GtkWidget * widget = NULL, * child_box = NULL; - GSList * radio_btn_group = NULL; - - for (int i = 0; i < n_widgets; i ++) - { - GtkWidget * label = NULL; - - if (widget && widgets[i].child) - { - if (! child_box) - { - child_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - g_object_set_data ((GObject *) widget, "child", child_box); - - GtkWidget * alignment = gtk_alignment_new (0.5, 0.5, 1, 1); - gtk_box_pack_start (box, alignment, FALSE, FALSE, 0); - gtk_alignment_set_padding ((GtkAlignment *) alignment, 0, 0, 12, 0); - gtk_container_add ((GtkContainer *) alignment, child_box); - - if (GTK_IS_TOGGLE_BUTTON (widget)) - gtk_widget_set_sensitive (child_box, - gtk_toggle_button_get_active ((GtkToggleButton *) widget)); - } - } - else - child_box = NULL; - - GtkWidget * alignment = gtk_alignment_new (0.5, 0.5, 1, 1); - gtk_alignment_set_padding ((GtkAlignment *) alignment, 6, 0, 12, 0); - gtk_box_pack_start (child_box ? (GtkBox *) child_box : box, alignment, FALSE, FALSE, 0); - - widget = NULL; - - if (radio_btn_group && widgets[i].type != WIDGET_RADIO_BTN) - radio_btn_group = NULL; - - switch (widgets[i].type) - { - case WIDGET_CHK_BTN: - widget = gtk_check_button_new_with_mnemonic (dgettext (domain, widgets[i].label)); - init_toggle_button (widget, & widgets[i]); - break; - - case WIDGET_LABEL: - if (strstr (widgets[i].label, "<b>")) - gtk_alignment_set_padding ((GtkAlignment *) alignment, - (i == 0) ? 0 : 12, 0, 0, 0); - - GtkWidget * icon = NULL; - create_label (& widgets[i], & label, & icon, domain); - - if (icon) - { - widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); - gtk_box_pack_start ((GtkBox *) widget, icon, FALSE, FALSE, 0); - gtk_box_pack_start ((GtkBox *) widget, label, FALSE, FALSE, 0); - } - else - widget = label; - - break; - - case WIDGET_RADIO_BTN: - widget = gtk_radio_button_new_with_mnemonic (radio_btn_group, - dgettext (domain, widgets[i].label)); - radio_btn_group = gtk_radio_button_get_group ((GtkRadioButton *) widget); - init_radio_button (widget, & widgets[i]); - break; - - case WIDGET_SPIN_BTN: - widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); - - GtkWidget * label_pre = NULL, * spin_btn = NULL, * label_past = NULL; - create_spin_button (& widgets[i], & label_pre, & spin_btn, & label_past, domain); - - if (label_pre) - gtk_box_pack_start ((GtkBox *) widget, label_pre, FALSE, FALSE, 0); - if (spin_btn) - gtk_box_pack_start ((GtkBox *) widget, spin_btn, FALSE, FALSE, 0); - if (label_past) - gtk_box_pack_start ((GtkBox *) widget, label_past, FALSE, FALSE, 0); - - break; - - case WIDGET_CUSTOM: - if (widgets[i].data.populate) - widget = widgets[i].data.populate (); - - break; - - case WIDGET_FONT_BTN: - widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); - - GtkWidget * font_btn = NULL; - create_font_btn (& widgets[i], & label, & font_btn, domain); - - if (label) - gtk_box_pack_start ((GtkBox *) widget, label, FALSE, FALSE, 0); - if (font_btn) - gtk_box_pack_start ((GtkBox *) widget, font_btn, FALSE, FALSE, 0); - - break; - - case WIDGET_TABLE: - widget = gtk_grid_new (); - gtk_grid_set_column_spacing ((GtkGrid *) widget, 6); - gtk_grid_set_row_spacing ((GtkGrid *) widget, 6); - - fill_grid (widget, widgets[i].data.table.elem, widgets[i].data.table.rows, domain); - - break; - - case WIDGET_ENTRY: - widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); - - GtkWidget * entry = NULL; - create_entry (& widgets[i], & label, & entry, domain); - - if (label) - gtk_box_pack_start ((GtkBox *) widget, label, FALSE, FALSE, 0); - if (entry) - gtk_box_pack_start ((GtkBox *) widget, entry, TRUE, TRUE, 0); - - break; - - case WIDGET_COMBO_BOX: - widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); - - GtkWidget * combo = NULL; - create_cbox (& widgets[i], & label, & combo, domain); - - if (label) - gtk_box_pack_start ((GtkBox *) widget, label, FALSE, FALSE, 0); - if (combo) - gtk_box_pack_start ((GtkBox *) widget, combo, FALSE, FALSE, 0); - - break; - - case WIDGET_BOX: - if (widgets[i].data.box.horizontal) - widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); - else - widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - - create_widgets_with_domain ((GtkBox *) widget, - widgets[i].data.box.elem, widgets[i].data.box.n_elem, domain); - - if (widgets[i].data.box.frame) - { - GtkWidget * frame = gtk_frame_new (dgettext (domain, widgets[i].label)); - gtk_container_add ((GtkContainer *) frame, widget); - widget = frame; - } - - break; - - case WIDGET_NOTEBOOK: - gtk_alignment_set_padding ((GtkAlignment *) alignment, 0, 0, 0, 0); - - widget = gtk_notebook_new (); - - for (int j = 0; j < widgets[i].data.notebook.n_tabs; j ++) - { - GtkWidget * vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - gtk_container_set_border_width ((GtkContainer *) vbox, 6); - - create_widgets_with_domain ((GtkBox *) vbox, - widgets[i].data.notebook.tabs[j].widgets, - widgets[i].data.notebook.tabs[j].n_widgets, domain); - - gtk_notebook_append_page ((GtkNotebook *) widget, vbox, - gtk_label_new (dgettext (domain, - widgets[i].data.notebook.tabs[j].name))); - } - - break; - - case WIDGET_SEPARATOR: - gtk_alignment_set_padding ((GtkAlignment *) alignment, 6, 6, 0, 0); - - widget = gtk_separator_new (widgets[i].data.separator.horizontal - ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL); - break; - - default: - break; - } - - if (widget) - { - /* use uniform spacing for horizontal boxes */ - if (gtk_orientable_get_orientation ((GtkOrientable *) box) == - GTK_ORIENTATION_HORIZONTAL) - gtk_alignment_set_padding ((GtkAlignment *) alignment, 0, 0, 0, 0); - - gtk_container_add ((GtkContainer *) alignment, widget); - - if (widgets[i].tooltip && widgets[i].type != WIDGET_SPIN_BTN) - gtk_widget_set_tooltip_text (widget, dgettext (domain, - widgets[i].tooltip)); - } - } -} diff --git a/src/audacious/preferences.h b/src/audacious/preferences.h deleted file mode 100644 index af58567..0000000 --- a/src/audacious/preferences.h +++ /dev/null @@ -1,142 +0,0 @@ -/* - * preferences.h - * Copyright 2007-2012 Tomasz MoĆ, William Pitcock, and John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_PREFERENCES_H -#define AUDACIOUS_PREFERENCES_H - -#include <audacious/types.h> - -typedef enum { - WIDGET_NONE, - WIDGET_CHK_BTN, - WIDGET_LABEL, - WIDGET_RADIO_BTN, - WIDGET_SPIN_BTN, - WIDGET_CUSTOM, /* 'custom' widget, you hand back the widget you want to add --nenolod */ - WIDGET_FONT_BTN, - WIDGET_TABLE, - WIDGET_ENTRY, - WIDGET_COMBO_BOX, - WIDGET_BOX, - WIDGET_NOTEBOOK, - WIDGET_SEPARATOR, -} WidgetType; - -typedef enum { - VALUE_INT, - VALUE_FLOAT, - VALUE_BOOLEAN, - VALUE_STRING, - VALUE_NULL, -} ValueType; - -typedef struct { - void * value; - const char * label; -} ComboBoxElements; - -struct _NotebookTab; - -struct _PreferencesWidget { - WidgetType type; /* widget type */ - const char * label; /* widget title (for SPIN_BTN it's text left to widget) */ - void * cfg; /* connected config value */ - void (* callback) (void); /* this func will be called after value change, can be NULL */ - const char * tooltip; /* widget tooltip, can be NULL */ - bool_t child; - ValueType cfg_type; /* connected value type */ - const char * csect; /* config file section */ - const char * cname; /* config file key name */ - - union { - struct { - int value; - } radio_btn; - - struct { - double min, max, step; - const char * right_label; /* text right to widget */ - } spin_btn; - - struct { - struct _PreferencesWidget *elem; - int rows; - } table; - - struct { - const char * stock_id; - bool_t single_line; /* FALSE to enable line wrap */ - } label; - - struct { - const char * title; - } font_btn; - - struct { - bool_t password; - } entry; - - struct { - /* static init */ - const ComboBoxElements * elements; - int n_elements; - - /* runtime init */ - const ComboBoxElements * (* fill) (int * n_elements); - } combo; - - struct { - const struct _PreferencesWidget * elem; - int n_elem; - - bool_t horizontal; /* FALSE gives vertical, TRUE gives horizontal aligment of child widgets */ - bool_t frame; /* whether to draw frame around box */ - } box; - - struct { - const struct _NotebookTab * tabs; - int n_tabs; - } notebook; - - struct { - bool_t horizontal; /* FALSE gives vertical, TRUE gives horizontal separator */ - } separator; - - /* for WIDGET_CUSTOM --nenolod */ - /* GtkWidget * (* populate) (void); */ - void * (* populate) (void); - } data; -}; - -typedef struct _NotebookTab { - const char * name; - const PreferencesWidget * widgets; - int n_widgets; -} NotebookTab; - -struct _PluginPreferences { - const PreferencesWidget * widgets; - int n_widgets; - - void (*init)(void); - void (*apply)(void); - void (*cleanup)(void); -}; - -#endif /* AUDACIOUS_PREFERENCES_H */ diff --git a/src/audacious/probe-buffer.c b/src/audacious/probe-buffer.c deleted file mode 100644 index c27995b..0000000 --- a/src/audacious/probe-buffer.c +++ /dev/null @@ -1,161 +0,0 @@ -/* - * probe-buffer.c - * Copyright 2010-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <string.h> - -#include <glib.h> - -#include "debug.h" -#include "probe-buffer.h" - -#define BUFSIZE (256 * 1024) - -typedef struct -{ - VFSFile * file; - int filled, at; - unsigned char buffer[BUFSIZE]; -} -ProbeBuffer; - -static int probe_buffer_fclose (VFSFile * file) -{ - ProbeBuffer * p = vfs_get_handle (file); - - int ret = vfs_fclose (p->file); - g_free (p); - return ret; -} - -static void increase_buffer (ProbeBuffer * p, int64_t size) -{ - size = (size + 0xFF) & ~0xFF; - - if (size > sizeof p->buffer) - size = sizeof p->buffer; - - if (p->filled < size) - p->filled += vfs_fread (p->buffer + p->filled, 1, size - p->filled, - p->file); -} - -static int64_t probe_buffer_fread (void * buffer, int64_t size, int64_t count, - VFSFile * file) -{ - ProbeBuffer * p = vfs_get_handle (file); - - increase_buffer (p, p->at + size * count); - int readed = (size > 0) ? MIN (count, (p->filled - p->at) / size) : 0; - memcpy (buffer, p->buffer + p->at, size * readed); - - p->at += size * readed; - return readed; -} - -static int64_t probe_buffer_fwrite (const void * data, int64_t size, int64_t count, - VFSFile * file) -{ - return 0; /* not allowed */ -} - -static int probe_buffer_fseek (VFSFile * file, int64_t offset, int whence) -{ - ProbeBuffer * p = vfs_get_handle (file); - - if (whence == SEEK_END) - return -1; /* not allowed */ - - if (whence == SEEK_CUR) - offset += p->at; - - if (offset < 0 || offset > sizeof p->buffer) - return -1; - - increase_buffer (p, offset); - - if (offset > p->filled) - return -1; - - p->at = offset; - return 0; -} - -static int64_t probe_buffer_ftell (VFSFile * file) -{ - return ((ProbeBuffer *) vfs_get_handle (file))->at; -} - -static bool_t probe_buffer_feof (VFSFile * file) -{ - ProbeBuffer * p = vfs_get_handle (file); - - if (p->at < p->filled) - return FALSE; - if (p->at == sizeof p->buffer) - return TRUE; - - return vfs_feof (p->file); -} - -static int probe_buffer_ftruncate (VFSFile * file, int64_t size) -{ - return -1; /* not allowed */ -} - -static int64_t probe_buffer_fsize (VFSFile * file) -{ - ProbeBuffer * p = vfs_get_handle (file); - - int64_t size = vfs_fsize (p->file); - return MIN (size, sizeof p->buffer); -} - -static char * probe_buffer_get_metadata (VFSFile * file, const char * field) -{ - return vfs_get_metadata (((ProbeBuffer *) vfs_get_handle (file))->file, field); -} - -static VFSConstructor probe_buffer_table = -{ - .vfs_fopen_impl = NULL, - .vfs_fclose_impl = probe_buffer_fclose, - .vfs_fread_impl = probe_buffer_fread, - .vfs_fwrite_impl = probe_buffer_fwrite, - .vfs_fseek_impl = probe_buffer_fseek, - .vfs_ftell_impl = probe_buffer_ftell, - .vfs_feof_impl = probe_buffer_feof, - .vfs_ftruncate_impl = probe_buffer_ftruncate, - .vfs_fsize_impl = probe_buffer_fsize, - .vfs_get_metadata_impl = probe_buffer_get_metadata, -}; - -VFSFile * probe_buffer_new (const char * filename) -{ - VFSFile * file = vfs_fopen (filename, "r"); - - if (! file) - return NULL; - - ProbeBuffer * p = g_new (ProbeBuffer, 1); - p->file = file; - p->filled = 0; - p->at = 0; - - return vfs_new (filename, & probe_buffer_table, p); -} diff --git a/src/audacious/probe-buffer.h b/src/audacious/probe-buffer.h deleted file mode 100644 index d1abf96..0000000 --- a/src/audacious/probe-buffer.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * probe-buffer.h - * Copyright 2010 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_PROBE_BUFFER_H -#define AUDACIOUS_PROBE_BUFFER_H - -#include <libaudcore/vfs.h> - -VFSFile * probe_buffer_new (const char * filename); - -#endif diff --git a/src/audacious/probe.c b/src/audacious/probe.c deleted file mode 100644 index c627abe..0000000 --- a/src/audacious/probe.c +++ /dev/null @@ -1,287 +0,0 @@ -/* - * probe.c - * Copyright 2009-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <stdio.h> -#include <string.h> - -#include <libaudcore/audstrings.h> - -#include "debug.h" -#include "misc.h" -#include "playlist.h" -#include "plugin.h" -#include "plugins.h" -#include "probe-buffer.h" - -typedef struct -{ - const char * filename; - VFSFile * handle; - bool_t failed; - PluginHandle * plugin; -} -ProbeState; - -static bool_t check_opened (ProbeState * state) -{ - if (state->handle != NULL) - return TRUE; - if (state->failed) - return FALSE; - - AUDDBG ("Opening %s.\n", state->filename); - state->handle = probe_buffer_new (state->filename); - - if (state->handle != NULL) - return TRUE; - - AUDDBG ("FAILED.\n"); - state->failed = TRUE; - return FALSE; -} - -static bool_t probe_func (PluginHandle * plugin, ProbeState * state) -{ - AUDDBG ("Trying %s.\n", plugin_get_name (plugin)); - InputPlugin * decoder = plugin_get_header (plugin); - if (decoder == NULL) - return TRUE; - - if (decoder->is_our_file_from_vfs != NULL) - { - if (! check_opened (state)) - return FALSE; - - if (decoder->is_our_file_from_vfs (state->filename, state->handle)) - { - state->plugin = plugin; - return FALSE; - } - - if (vfs_fseek (state->handle, 0, SEEK_SET) < 0) - return FALSE; - } - - return TRUE; -} - -/* Optimization: If we have found plugins with a key match, assume that at least - * one of them will succeed. This means that we need not check the very last - * plugin. (If there is only one, we do not need to check it at all.) This is - * implemented as follows: - * - * 1. On the first call, assume until further notice the plugin passed is the - * last one and will therefore succeed. - * 2. On a subsequent call, think twice and probe the plugin we assumed would - * succeed. If it does in fact succeed, then we are done. If not, assume - * similarly that the plugin passed in this call is the last one. - */ - -static bool_t probe_func_fast (PluginHandle * plugin, ProbeState * state) -{ - if (state->plugin != NULL) - { - PluginHandle * prev = state->plugin; - state->plugin = NULL; - - if (! probe_func (prev, state)) - return FALSE; - } - - AUDDBG ("Guessing %s.\n", plugin_get_name (plugin)); - state->plugin = plugin; - return TRUE; -} - -static void probe_by_scheme (ProbeState * state) -{ - const char * s = strstr (state->filename, "://"); - if (s == NULL) - return; - - AUDDBG ("Probing by scheme.\n"); - SNCOPY (buf, state->filename, s - state->filename); - input_plugin_for_key (INPUT_KEY_SCHEME, buf, (PluginForEachFunc) probe_func_fast, state); -} - -static void probe_by_extension (ProbeState * state) -{ - char buf[32]; - if (! uri_get_extension (state->filename, buf, sizeof buf)) - return; - - AUDDBG ("Probing by extension.\n"); - input_plugin_for_key (INPUT_KEY_EXTENSION, buf, (PluginForEachFunc) probe_func_fast, state); -} - -static void probe_by_mime (ProbeState * state) -{ - char * mime; - - if (! check_opened (state)) - return; - - if ((mime = vfs_get_metadata (state->handle, "content-type")) == NULL) - return; - - AUDDBG ("Probing by MIME type.\n"); - input_plugin_for_key (INPUT_KEY_MIME, mime, (PluginForEachFunc) - probe_func_fast, state); - str_unref (mime); -} - -static void probe_by_content (ProbeState * state) -{ - AUDDBG ("Probing by content.\n"); - plugin_for_enabled (PLUGIN_TYPE_INPUT, (PluginForEachFunc) probe_func, state); -} - -PluginHandle * file_find_decoder (const char * filename, bool_t fast) -{ - ProbeState state; - - AUDDBG ("Probing %s.\n", filename); - state.plugin = NULL; - state.filename = filename; - state.handle = NULL; - state.failed = FALSE; - - probe_by_scheme (& state); - - if (state.plugin != NULL) - goto DONE; - - probe_by_extension (& state); - - if (state.plugin != NULL || fast) - goto DONE; - - probe_by_mime (& state); - - if (state.plugin != NULL) - goto DONE; - - probe_by_content (& state); - -DONE: - if (state.handle != NULL) - vfs_fclose (state.handle); - - if (state.plugin != NULL) - AUDDBG ("Probe succeeded: %s\n", plugin_get_name (state.plugin)); - else - AUDDBG ("Probe failed.\n"); - - return state.plugin; -} - -static bool_t open_file (const char * filename, InputPlugin * ip, - const char * mode, VFSFile * * handle) -{ - /* no need to open a handle for custom URI schemes */ - if (ip->schemes && ip->schemes[0]) - return TRUE; - - * handle = vfs_fopen (filename, mode); - return (* handle != NULL); -} - -Tuple * file_read_tuple (const char * filename, PluginHandle * decoder) -{ - InputPlugin * ip = plugin_get_header (decoder); - g_return_val_if_fail (ip, NULL); - g_return_val_if_fail (ip->probe_for_tuple, NULL); - - VFSFile * handle = NULL; - if (! open_file (filename, ip, "r", & handle)) - return FALSE; - - Tuple * tuple = ip->probe_for_tuple (filename, handle); - - if (handle) - vfs_fclose (handle); - - return tuple; -} - -bool_t file_read_image (const char * filename, PluginHandle * decoder, - void * * data, int64_t * size) -{ - * data = NULL; - * size = 0; - - if (! input_plugin_has_images (decoder)) - return FALSE; - - InputPlugin * ip = plugin_get_header (decoder); - g_return_val_if_fail (ip, FALSE); - g_return_val_if_fail (ip->get_song_image, FALSE); - - VFSFile * handle = NULL; - if (! open_file (filename, ip, "r", & handle)) - return FALSE; - - bool_t success = ip->get_song_image (filename, handle, data, size); - - if (handle) - vfs_fclose (handle); - - return success; -} - -bool_t file_can_write_tuple (const char * filename, PluginHandle * decoder) -{ - return input_plugin_can_write_tuple (decoder); -} - -bool_t file_write_tuple (const char * filename, PluginHandle * decoder, - const Tuple * tuple) -{ - InputPlugin * ip = plugin_get_header (decoder); - g_return_val_if_fail (ip, FALSE); - g_return_val_if_fail (ip->update_song_tuple, FALSE); - - VFSFile * handle = NULL; - if (! open_file (filename, ip, "r+", & handle)) - return FALSE; - - bool_t success = ip->update_song_tuple (filename, handle, tuple); - - if (handle) - vfs_fclose (handle); - - if (success) - playlist_rescan_file (filename); - - return success; -} - -bool_t custom_infowin (const char * filename, PluginHandle * decoder) -{ - if (! input_plugin_has_infowin (decoder)) - return FALSE; - - InputPlugin * ip = plugin_get_header (decoder); - g_return_val_if_fail (ip, FALSE); - g_return_val_if_fail (ip->file_info_box, FALSE); - - ip->file_info_box (filename); - return TRUE; -} diff --git a/src/audacious/scanner.c b/src/audacious/scanner.c deleted file mode 100644 index e3257da..0000000 --- a/src/audacious/scanner.c +++ /dev/null @@ -1,175 +0,0 @@ -/* - * scanner.c - * Copyright 2012 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <pthread.h> -#include <stdlib.h> -#include <string.h> - -#include <libaudcore/audstrings.h> - -#include "main.h" -#include "misc.h" -#include "scanner.h" - -struct _ScanRequest { - char * filename; /* pooled */ - int flags; - PluginHandle * decoder; - ScanCallback callback; - Tuple * tuple; - void * image_data; - int64_t image_len; - char * image_file; /* pooled */ -}; - -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; - -static GQueue requests = G_QUEUE_INIT; - -static pthread_t scan_threads[SCAN_THREADS]; - -static bool_t quit_flag = FALSE; - -ScanRequest * scan_request (const char * filename, int flags, - PluginHandle * decoder, ScanCallback callback) -{ - ScanRequest * request = g_slice_new0 (ScanRequest); - - request->filename = str_get (filename); - request->flags = flags; - request->decoder = decoder; - request->callback = callback; - - pthread_mutex_lock (& mutex); - - g_queue_push_tail (& requests, request); - pthread_cond_signal (& cond); - - pthread_mutex_unlock (& mutex); - return request; -} - -static void scan_request_free (ScanRequest * request) -{ - if (request->tuple) - tuple_unref (request->tuple); - - str_unref (request->filename); - g_free (request->image_data); - str_unref (request->image_file); - g_slice_free (ScanRequest, request); -} - -static void * scan_worker (void * unused) -{ - pthread_mutex_lock (& mutex); - - while (! quit_flag) - { - ScanRequest * request = g_queue_pop_head (& requests); - - if (! request) - { - pthread_cond_wait (& cond, & mutex); - continue; - } - - pthread_mutex_unlock (& mutex); - - if (! request->decoder) - request->decoder = file_find_decoder (request->filename, FALSE); - - if (request->decoder && (request->flags & SCAN_TUPLE)) - request->tuple = file_read_tuple (request->filename, request->decoder); - - if (request->decoder && (request->flags & SCAN_IMAGE)) - { - file_read_image (request->filename, request->decoder, - & request->image_data, & request->image_len); - - if (! request->image_data) - request->image_file = get_associated_image_file (request->filename); - } - - request->callback (request); - scan_request_free (request); - - pthread_mutex_lock (& mutex); - } - - pthread_mutex_unlock (& mutex); - return NULL; -} - -const char * scan_request_get_filename (ScanRequest * request) -{ - return request->filename; -} - -PluginHandle * scan_request_get_decoder (ScanRequest * request) -{ - return request->decoder; -} - -Tuple * scan_request_get_tuple (ScanRequest * request) -{ - Tuple * tuple = request->tuple; - request->tuple = NULL; - return tuple; -} - -void scan_request_get_image_data (ScanRequest * request, void * * data, int64_t * len) -{ - * data = request->image_data; - * len = request->image_len; - request->image_data = NULL; - request->image_len = 0; -} - -const char * scan_request_get_image_file (ScanRequest * request) -{ - return request->image_file; -} - -void scanner_init (void) -{ - for (int i = 0; i < SCAN_THREADS; i ++) - pthread_create (& scan_threads[i], 0, scan_worker, NULL); -} - -void scanner_cleanup (void) -{ - pthread_mutex_lock (& mutex); - - quit_flag = TRUE; - pthread_cond_broadcast (& cond); - - pthread_mutex_unlock (& mutex); - - for (int i = 0; i < SCAN_THREADS; i ++) - pthread_join (scan_threads[i], NULL); - - ScanRequest * request; - while ((request = g_queue_pop_head (& requests))) - scan_request_free (request); - - quit_flag = FALSE; -} diff --git a/src/audacious/signals.c b/src/audacious/signals.cc index 2b5515e..0a6f216 100644 --- a/src/audacious/signals.c +++ b/src/audacious/signals.cc @@ -22,7 +22,8 @@ #include <pthread.h> #include <signal.h> -#include "drct.h" +#include <libaudcore/hook.h> + #include "main.h" static sigset_t signal_set; @@ -32,9 +33,9 @@ static void * signal_thread (void * data) int signal; while (! sigwait (& signal_set, & signal)) - drct_quit (); + event_queue ("quit", nullptr); - return NULL; + return nullptr; } /* Must be called before any threads are created. */ @@ -46,13 +47,13 @@ void signals_init_one (void) sigaddset (& signal_set, SIGQUIT); sigaddset (& signal_set, SIGTERM); - sigprocmask (SIG_BLOCK, & signal_set, NULL); + sigprocmask (SIG_BLOCK, & signal_set, nullptr); } void signals_init_two (void) { pthread_t thread; - pthread_create (& thread, NULL, signal_thread, NULL); + pthread_create (& thread, nullptr, signal_thread, nullptr); } #endif /* HAVE_SIGWAIT */ diff --git a/src/audacious/types.h b/src/audacious/types.h deleted file mode 100644 index d160bab..0000000 --- a/src/audacious/types.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * types.h - * Copyright 2010-2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_TYPES_H -#define AUDACIOUS_TYPES_H - -#include <libaudcore/core.h> - -#define AUD_EQUALIZER_NBANDS 10 -#define EQUALIZER_MAX_GAIN 12 - -enum { - PLUGIN_TYPE_TRANSPORT, - PLUGIN_TYPE_PLAYLIST, - PLUGIN_TYPE_INPUT, - PLUGIN_TYPE_EFFECT, - PLUGIN_TYPE_OUTPUT, - PLUGIN_TYPE_VIS, - PLUGIN_TYPE_GENERAL, - PLUGIN_TYPE_IFACE, - PLUGIN_TYPES}; - -typedef struct PluginHandle PluginHandle; - -typedef const struct _Plugin Plugin; -typedef const struct _TransportPlugin TransportPlugin; -typedef const struct _PlaylistPlugin PlaylistPlugin; -typedef const struct _InputPlugin InputPlugin; -typedef const struct _EffectPlugin EffectPlugin; -typedef const struct _OutputPlugin OutputPlugin; -typedef const struct _VisPlugin VisPlugin; -typedef const struct _GeneralPlugin GeneralPlugin; -typedef const struct _IfacePlugin IfacePlugin; - -typedef struct _PluginPreferences PluginPreferences; -typedef struct _PreferencesWidget PreferencesWidget; - -typedef struct { - char * name; - float preamp; - float bands[AUD_EQUALIZER_NBANDS]; -} EqualizerPreset; - -typedef struct { - float track_gain; /* dB */ - float track_peak; /* 0-1 */ - float album_gain; /* dB */ - float album_peak; /* 0-1 */ -} ReplayGainInfo; - -#endif diff --git a/src/audacious/ui_albumart.c b/src/audacious/ui_albumart.c deleted file mode 100644 index a3f14a4..0000000 --- a/src/audacious/ui_albumart.c +++ /dev/null @@ -1,188 +0,0 @@ -/* - * ui_albumart.c - * Copyright 2006-2013 Michael Hanselmann, Yoshiki Yazawa, and John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <string.h> - -#include <libaudcore/audstrings.h> - -#include "i18n.h" -#include "main.h" -#include "misc.h" -#include "util.h" - -typedef struct { - const char * basename; - Index * include, * exclude; -} SearchParams; - -static bool_t has_front_cover_extension (const char * name) -{ - char * ext = strrchr (name, '.'); - if (! ext) - return FALSE; - - return ! g_ascii_strcasecmp (ext, ".jpg") || - ! g_ascii_strcasecmp (ext, ".jpeg") || ! g_ascii_strcasecmp (ext, ".png"); -} - -static bool_t cover_name_filter (const char * name, Index * keywords, bool_t ret_on_empty) -{ - int count = index_count (keywords); - if (! count) - return ret_on_empty; - - for (int i = 0; i < count; i ++) - { - if (strstr_nocase (name, index_get (keywords, i))) - return TRUE; - } - - return FALSE; -} - -static bool_t is_file_image (const char * imgfile, const char * file_name) -{ - char * imgfile_ext = strrchr (imgfile, '.'); - if (! imgfile_ext) - return FALSE; - - char * file_name_ext = strrchr (file_name, '.'); - if (! file_name_ext) - return FALSE; - - size_t imgfile_len = imgfile_ext - imgfile; - size_t file_name_len = file_name_ext - file_name; - - return imgfile_len == file_name_len && ! g_ascii_strncasecmp (imgfile, file_name, imgfile_len); -} - -static char * fileinfo_recursive_get_image (const char * path, - const SearchParams * params, int depth) -{ - GDir * d = g_dir_open (path, 0, NULL); - if (! d) - return NULL; - - const char * name; - - if (get_bool (NULL, "use_file_cover") && ! depth) - { - /* Look for images matching file name */ - while ((name = g_dir_read_name (d))) - { - char * newpath = filename_build (path, name); - - if (! g_file_test (newpath, G_FILE_TEST_IS_DIR) && - has_front_cover_extension (name) && - is_file_image (name, params->basename)) - { - g_dir_close (d); - return newpath; - } - - str_unref (newpath); - } - - g_dir_rewind (d); - } - - /* Search for files using filter */ - while ((name = g_dir_read_name (d))) - { - char * newpath = filename_build (path, name); - - if (! g_file_test (newpath, G_FILE_TEST_IS_DIR) && - has_front_cover_extension (name) && - cover_name_filter (name, params->include, TRUE) && - ! cover_name_filter (name, params->exclude, FALSE)) - { - g_dir_close (d); - return newpath; - } - - str_unref (newpath); - } - - g_dir_rewind (d); - - if (get_bool (NULL, "recurse_for_cover") && depth < get_int (NULL, "recurse_for_cover_depth")) - { - /* Descend into directories recursively. */ - while ((name = g_dir_read_name (d))) - { - char * newpath = filename_build (path, name); - - if (g_file_test (newpath, G_FILE_TEST_IS_DIR)) - { - char * tmp = fileinfo_recursive_get_image (newpath, params, depth + 1); - - if (tmp) - { - str_unref (newpath); - g_dir_close (d); - return tmp; - } - } - - str_unref (newpath); - } - } - - g_dir_close (d); - return NULL; -} - -char * get_associated_image_file (const char * filename) -{ - char * image_uri = NULL; - - char * local = uri_to_filename (filename); - char * base = local ? last_path_element (local) : NULL; - - if (local && base) - { - char * include = get_str (NULL, "cover_name_include"); - char * exclude = get_str (NULL, "cover_name_exclude"); - - SearchParams params = { - .basename = base, - .include = str_list_to_index (include, ", "), - .exclude = str_list_to_index (exclude, ", ") - }; - - str_unref (include); - str_unref (exclude); - - SNCOPY (path, local, base - 1 - local); - - char * image_local = fileinfo_recursive_get_image (path, & params, 0); - if (image_local) - image_uri = filename_to_uri (image_local); - - str_unref (image_local); - - index_free_full (params.include, (IndexFreeFunc) str_unref); - index_free_full (params.exclude, (IndexFreeFunc) str_unref); - } - - str_unref (local); - - return image_uri; -} diff --git a/src/audacious/ui_preferences.c b/src/audacious/ui_preferences.c deleted file mode 100644 index 2949fdc..0000000 --- a/src/audacious/ui_preferences.c +++ /dev/null @@ -1,814 +0,0 @@ -/* - * ui_preferences.c - * Copyright 2006-2012 William Pitcock, Tomasz MoĆ, Michael FĂ€rber, and - * John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <string.h> - -#include <gdk/gdkkeysyms.h> -#include <gtk/gtk.h> - -#include <libaudcore/audstrings.h> -#include <libaudcore/hook.h> -#include <libaudgui/libaudgui-gtk.h> - -#include "debug.h" -#include "i18n.h" -#include "misc.h" -#include "output.h" -#include "playlist.h" -#include "plugin.h" -#include "plugins.h" -#include "preferences.h" -#include "ui_preferences.h" - -#ifdef USE_CHARDET -#include <libguess.h> -#endif - -enum CategoryViewCols { - CATEGORY_VIEW_COL_ICON, - CATEGORY_VIEW_COL_NAME, - CATEGORY_VIEW_N_COLS -}; - -typedef struct { - const char * icon_path; - const char * name; -} Category; - -typedef struct { - int type; - const char * name; -} PluginCategory; - -typedef struct { - const char * name; - const char * tag; -} TitleFieldTag; - -static const char aud_version_string[] = - "<span size='small'>Audacious " VERSION " (" BUILDSTAMP ")</span>"; - -static GtkWidget * prefswin; -static GtkWidget * category_treeview, * category_notebook, * plugin_notebook; -static GtkWidget * titlestring_entry; - -enum { - CATEGORY_APPEARANCE = 0, - CATEGORY_AUDIO, - CATEGORY_NETWORK, - CATEGORY_PLAYLIST, - CATEGORY_SONG_INFO, - CATEGORY_PLUGINS -}; - -static const Category categories[] = { - { "appearance.png", N_("Appearance") }, - { "audio.png", N_("Audio") }, - { "connectivity.png", N_("Network") }, - { "playlist.png", N_("Playlist")} , - { "info.png", N_("Song Info") }, - { "plugins.png", N_("Plugins") } -}; - -static const PluginCategory plugin_categories[] = { - { PLUGIN_TYPE_GENERAL, N_("General") }, - { PLUGIN_TYPE_EFFECT, N_("Effect") }, - { PLUGIN_TYPE_VIS, N_("Visualization") }, - { PLUGIN_TYPE_INPUT, N_("Input") }, - { PLUGIN_TYPE_PLAYLIST, N_("Playlist") }, - { PLUGIN_TYPE_TRANSPORT, N_("Transport") } -}; - -static TitleFieldTag title_field_tags[] = { - { N_("Artist") , "${artist}" }, - { N_("Album") , "${album}" }, - { N_("Title") , "${title}" }, - { N_("Tracknumber"), "${track-number}" }, - { N_("Genre") , "${genre}" }, - { N_("Filename") , "${file-name}" }, - { N_("Filepath") , "${file-path}" }, - { N_("Date") , "${date}" }, - { N_("Year") , "${year}" }, - { N_("Comment") , "${comment}" }, - { N_("Codec") , "${codec}" }, - { N_("Quality") , "${quality}" } -}; - -#ifdef USE_CHARDET -static ComboBoxElements chardet_detector_presets[] = { - { "", N_("None")}, - { GUESS_REGION_AR, N_("Arabic") }, - { GUESS_REGION_BL, N_("Baltic") }, - { GUESS_REGION_CN, N_("Chinese") }, - { GUESS_REGION_GR, N_("Greek") }, - { GUESS_REGION_HW, N_("Hebrew") }, - { GUESS_REGION_JP, N_("Japanese") }, - { GUESS_REGION_KR, N_("Korean") }, - { GUESS_REGION_PL, N_("Polish") }, - { GUESS_REGION_RU, N_("Russian") }, - { GUESS_REGION_TW, N_("Taiwanese") }, - { GUESS_REGION_TR, N_("Turkish") } -}; -#endif - -static ComboBoxElements bitdepth_elements[] = { - { GINT_TO_POINTER (16), "16" }, - { GINT_TO_POINTER (24), "24" }, - { GINT_TO_POINTER (32), "32" }, - { GINT_TO_POINTER (0), N_("Floating point") } -}; - -static GArray * iface_combo_elements; -static int iface_combo_selected; -static GtkWidget * iface_prefs_box; - -static const ComboBoxElements * iface_combo_fill (int * n_elements); -static void iface_combo_changed (void); -static void * iface_create_prefs_box (void); - -static PreferencesWidget appearance_page_widgets[] = { - {WIDGET_LABEL, N_("<b>Interface Settings</b>")}, - {WIDGET_COMBO_BOX, N_("Interface plugin:"), - .cfg_type = VALUE_INT, .cfg = & iface_combo_selected, - .data.combo.fill = iface_combo_fill, .callback = iface_combo_changed}, - {WIDGET_CUSTOM, .data.populate = iface_create_prefs_box}}; - -static GArray * output_combo_elements; -static int output_combo_selected; -static GtkWidget * output_config_button; -static GtkWidget * output_about_button; - -static const ComboBoxElements * output_combo_fill (int * n_elements); -static void output_combo_changed (void); -static void * output_create_config_button (void); -static void * output_create_about_button (void); -static void output_bit_depth_changed (void); - -static PreferencesWidget output_combo_widgets[] = { - {WIDGET_COMBO_BOX, N_("Output plugin:"), - .cfg_type = VALUE_INT, .cfg = & output_combo_selected, - .data.combo.fill = output_combo_fill, .callback = output_combo_changed}, - {WIDGET_CUSTOM, .data.populate = output_create_config_button}, - {WIDGET_CUSTOM, .data.populate = output_create_about_button}}; - -static PreferencesWidget audio_page_widgets[] = { - {WIDGET_LABEL, N_("<b>Output Settings</b>")}, - {WIDGET_BOX, .data.box = {.elem = output_combo_widgets, - .n_elem = ARRAY_LEN (output_combo_widgets), .horizontal = TRUE}}, - {WIDGET_COMBO_BOX, N_("Bit depth:"), - .cfg_type = VALUE_INT, .cname = "output_bit_depth", .callback = output_bit_depth_changed, - .data.combo = {bitdepth_elements, ARRAY_LEN (bitdepth_elements)}}, - {WIDGET_SPIN_BTN, N_("Buffer size:"), - .cfg_type = VALUE_INT, .cname = "output_buffer_size", - .data.spin_btn = {100, 10000, 1000, N_("ms")}}, - {WIDGET_CHK_BTN, N_("Soft clipping"), - .cfg_type = VALUE_BOOLEAN, .cname = "soft_clipping"}, - {WIDGET_CHK_BTN, N_("Use software volume control (not recommended)"), - .cfg_type = VALUE_BOOLEAN, .cname = "software_volume_control"}, - {WIDGET_LABEL, N_("<b>Replay Gain</b>")}, - {WIDGET_CHK_BTN, N_("Enable Replay Gain"), - .cfg_type = VALUE_BOOLEAN, .cname = "enable_replay_gain"}, - {WIDGET_CHK_BTN, N_("Album mode"), .child = TRUE, - .cfg_type = VALUE_BOOLEAN, .cname = "replay_gain_album"}, - {WIDGET_CHK_BTN, N_("Prevent clipping (recommended)"), .child = TRUE, - .cfg_type = VALUE_BOOLEAN, .cname = "enable_clipping_prevention"}, - {WIDGET_LABEL, N_("<b>Adjust Levels</b>"), .child = TRUE}, - {WIDGET_SPIN_BTN, N_("Amplify all files:"), .child = TRUE, - .cfg_type = VALUE_FLOAT, .cname = "replay_gain_preamp", - .data.spin_btn = {-15, 15, 0.1, N_("dB")}}, - {WIDGET_SPIN_BTN, N_("Amplify untagged files:"), .child = TRUE, - .cfg_type = VALUE_FLOAT, .cname = "default_gain", - .data.spin_btn = {-15, 15, 0.1, N_("dB")}}}; - -static PreferencesWidget proxy_host_port_elements[] = { - {WIDGET_ENTRY, N_("Proxy hostname:"), .cfg_type = VALUE_STRING, .cname = "proxy_host"}, - {WIDGET_ENTRY, N_("Proxy port:"), .cfg_type = VALUE_STRING, .cname = "proxy_port"}}; - -static PreferencesWidget proxy_auth_elements[] = { - {WIDGET_ENTRY, N_("Proxy username:"), .cfg_type = VALUE_STRING, .cname = "proxy_user"}, - {WIDGET_ENTRY, N_("Proxy password:"), .cfg_type = VALUE_STRING, .cname = "proxy_pass", - .data.entry.password = TRUE}}; - -static PreferencesWidget connectivity_page_widgets[] = { - {WIDGET_LABEL, N_("<b>Proxy Configuration</b>"), NULL, NULL, NULL, FALSE}, - {WIDGET_CHK_BTN, N_("Enable proxy usage"), .cfg_type = VALUE_BOOLEAN, .cname = "use_proxy"}, - {WIDGET_TABLE, .child = TRUE, .data.table = {proxy_host_port_elements, - ARRAY_LEN (proxy_host_port_elements)}}, - {WIDGET_CHK_BTN, N_("Use authentication with proxy"), - .cfg_type = VALUE_BOOLEAN, .cname = "use_proxy_auth"}, - {WIDGET_TABLE, .child = TRUE, .data.table = {proxy_auth_elements, - ARRAY_LEN (proxy_auth_elements)}}}; - -static PreferencesWidget chardet_elements[] = { -#ifdef USE_CHARDET - {WIDGET_COMBO_BOX, N_("Auto character encoding detector for:"), - .cfg_type = VALUE_STRING, .cname = "chardet_detector", .child = TRUE, - .data.combo = {chardet_detector_presets, ARRAY_LEN (chardet_detector_presets)}}, -#endif - {WIDGET_ENTRY, N_("Fallback character encodings:"), .cfg_type = VALUE_STRING, - .cname = "chardet_fallback", .child = TRUE}}; - -static PreferencesWidget playlist_page_widgets[] = { - {WIDGET_LABEL, N_("<b>Behavior</b>"), NULL, NULL, NULL, FALSE}, - {WIDGET_CHK_BTN, N_("Continue playback on startup"), - .cfg_type = VALUE_BOOLEAN, .cname = "resume_playback_on_startup"}, - {WIDGET_CHK_BTN, N_("Advance when the current song is deleted"), - .cfg_type = VALUE_BOOLEAN, .cname = "advance_on_delete"}, - {WIDGET_CHK_BTN, N_("Clear the playlist when opening files"), - .cfg_type = VALUE_BOOLEAN, .cname = "clear_playlist"}, - {WIDGET_CHK_BTN, N_("Open files in a temporary playlist"), - .cfg_type = VALUE_BOOLEAN, .cname = "open_to_temporary"}, - {WIDGET_CHK_BTN, N_("Do not load metadata for songs until played"), - .cfg_type = VALUE_BOOLEAN, .cname = "metadata_on_play", - .callback = playlist_trigger_scan}, - {WIDGET_LABEL, N_("<b>Compatibility</b>"), NULL, NULL, NULL, FALSE}, - {WIDGET_CHK_BTN, N_("Interpret \\ (backward slash) as a folder delimiter"), - .cfg_type = VALUE_BOOLEAN, .cname = "convert_backslash"}, - {WIDGET_TABLE, .data.table = {chardet_elements, ARRAY_LEN (chardet_elements)}}}; - -static PreferencesWidget song_info_page_widgets[] = { - {WIDGET_LABEL, N_("<b>Album Art</b>")}, - {WIDGET_LABEL, N_("Search for images matching these words (comma-separated):")}, - {WIDGET_ENTRY, .cfg_type = VALUE_STRING, .cname = "cover_name_include"}, - {WIDGET_LABEL, N_("Exclude images matching these words (comma-separated):")}, - {WIDGET_ENTRY, .cfg_type = VALUE_STRING, .cname = "cover_name_exclude"}, - {WIDGET_CHK_BTN, N_("Search for images matching song file name"), - .cfg_type = VALUE_BOOLEAN, .cname = "use_file_cover"}, - {WIDGET_CHK_BTN, N_("Search recursively"), - .cfg_type = VALUE_BOOLEAN, .cname = "recurse_for_cover"}, - {WIDGET_SPIN_BTN, N_("Search depth:"), .child = TRUE, - .cfg_type = VALUE_INT, .cname = "recurse_for_cover_depth", - .data.spin_btn = {0, 100, 1}}, - {WIDGET_LABEL, N_("<b>Popup Information</b>")}, - {WIDGET_CHK_BTN, N_("Show popup information"), - .cfg_type = VALUE_BOOLEAN, .cname = "show_filepopup_for_tuple"}, - {WIDGET_SPIN_BTN, N_("Popup delay (tenths of a second):"), .child = TRUE, - .cfg_type = VALUE_INT, .cname = "filepopup_delay", - .data.spin_btn = {0, 100, 1}}, - {WIDGET_CHK_BTN, N_("Show time scale for current song"), .child = TRUE, - .cfg_type = VALUE_BOOLEAN, .cname = "filepopup_showprogressbar"}}; - -#define TITLESTRING_NPRESETS 6 - -static const char * const titlestring_presets[TITLESTRING_NPRESETS] = { - "${title}", - "${?artist:${artist} - }${title}", - "${?artist:${artist} - }${?album:${album} - }${title}", - "${?artist:${artist} - }${?album:${album} - }${?track-number:${track-number}. }${title}", - "${?artist:${artist} }${?album:[ ${album} ] }${?artist:- }${?track-number:${track-number}. }${title}", - "${?album:${album} - }${title}" -}; - -static const char * const titlestring_preset_names[TITLESTRING_NPRESETS] = { - N_("TITLE"), - N_("ARTIST - TITLE"), - N_("ARTIST - ALBUM - TITLE"), - N_("ARTIST - ALBUM - TRACK. TITLE"), - N_("ARTIST [ ALBUM ] - TRACK. TITLE"), - N_("ALBUM - TITLE") -}; - -static GArray * fill_plugin_combo (int type) -{ - GArray * array = g_array_new (FALSE, FALSE, sizeof (ComboBoxElements)); - g_array_set_size (array, plugin_count (type)); - - for (int i = 0; i < array->len; i ++) - { - ComboBoxElements * elem = & g_array_index (array, ComboBoxElements, i); - elem->label = plugin_get_name (plugin_by_index (type, i)); - elem->value = GINT_TO_POINTER (i); - } - - return array; -} - -static void change_category (int category) -{ - GtkTreeSelection * selection = gtk_tree_view_get_selection ((GtkTreeView *) category_treeview); - GtkTreePath * path = gtk_tree_path_new_from_indices (category, -1); - gtk_tree_selection_select_path (selection, path); - gtk_tree_path_free (path); -} - -static void category_changed (GtkTreeSelection * selection) -{ - GtkTreeModel * model; - GtkTreeIter iter; - - if (gtk_tree_selection_get_selected (selection, & model, & iter)) - { - GtkTreePath * path = gtk_tree_model_get_path (model, & iter); - int category = gtk_tree_path_get_indices (path)[0]; - gtk_notebook_set_current_page ((GtkNotebook *) category_notebook, category); - gtk_tree_path_free (path); - } -} - -static void titlestring_tag_menu_cb (GtkMenuItem * menuitem, void * data) -{ - const char * separator = " - "; - int item = GPOINTER_TO_INT (data); - int pos = gtk_editable_get_position ((GtkEditable *) titlestring_entry); - - /* insert separator as needed */ - if (gtk_entry_get_text ((GtkEntry *) titlestring_entry)[0]) - gtk_editable_insert_text ((GtkEditable *) titlestring_entry, separator, -1, & pos); - - gtk_editable_insert_text ((GtkEditable *) titlestring_entry, _(title_field_tags[item].tag), -1, & pos); - gtk_editable_set_position ((GtkEditable *) titlestring_entry, pos); -} - -static void on_titlestring_help_button_clicked (GtkButton * button, void * menu) -{ - gtk_menu_popup (menu, NULL, NULL, NULL, NULL, 0, GDK_CURRENT_TIME); -} - -static void update_titlestring_cbox (GtkComboBox * cbox, const char * format) -{ - int preset; - for (preset = 0; preset < TITLESTRING_NPRESETS; preset ++) - { - if (! strcmp (titlestring_presets[preset], format)) - break; - } - - if (gtk_combo_box_get_active (cbox) != preset) - gtk_combo_box_set_active (cbox, preset); -} - -static void on_titlestring_entry_changed (GtkEntry * entry, GtkComboBox * cbox) -{ - const char * format = gtk_entry_get_text (entry); - set_str (NULL, "generic_title_format", format); - update_titlestring_cbox (cbox, format); - playlist_reformat_titles (); -} - -static void on_titlestring_cbox_changed (GtkComboBox * cbox, GtkEntry * entry) -{ - int preset = gtk_combo_box_get_active (cbox); - if (preset < TITLESTRING_NPRESETS) - gtk_entry_set_text (entry, titlestring_presets[preset]); -} - -static void fill_category_list (GtkTreeView * treeview, GtkNotebook * notebook) -{ - GtkTreeViewColumn * column = gtk_tree_view_column_new (); - gtk_tree_view_column_set_title (column, _("Category")); - gtk_tree_view_append_column (treeview, column); - gtk_tree_view_column_set_spacing (column, 2); - - GtkCellRenderer * renderer = gtk_cell_renderer_pixbuf_new (); - gtk_tree_view_column_pack_start (column, renderer, FALSE); - gtk_tree_view_column_set_attributes (column, renderer, "pixbuf", 0, NULL); - - renderer = gtk_cell_renderer_text_new (); - gtk_tree_view_column_pack_start (column, renderer, FALSE); - gtk_tree_view_column_set_attributes (column, renderer, "text", 1, NULL); - - g_object_set ((GObject *) renderer, "wrap-width", 96, "wrap-mode", - PANGO_WRAP_WORD_CHAR, NULL); - - GtkListStore * store = gtk_list_store_new (CATEGORY_VIEW_N_COLS, - GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_INT); - gtk_tree_view_set_model (treeview, (GtkTreeModel *) store); - - const char * data_dir = get_path (AUD_PATH_DATA_DIR); - - for (int i = 0; i < ARRAY_LEN (categories); i ++) - { - SCONCAT3 (path, data_dir, "/images/", categories[i].icon_path); - - GtkTreeIter iter; - - gtk_list_store_append (store, & iter); - gtk_list_store_set (store, & iter, CATEGORY_VIEW_COL_NAME, - gettext (categories[i].name), -1); - - GdkPixbuf * img = gdk_pixbuf_new_from_file (path, NULL); - - if (img) - { - gtk_list_store_set (store, & iter, CATEGORY_VIEW_COL_ICON, img, -1); - g_object_unref (img); - } - } - - g_object_unref (store); - - GtkTreeSelection * selection = gtk_tree_view_get_selection (treeview); - g_signal_connect (selection, "changed", (GCallback) category_changed, NULL); -} - -static GtkWidget * create_titlestring_tag_menu (void) -{ - GtkWidget * titlestring_tag_menu = gtk_menu_new (); - - for (int i = 0; i < ARRAY_LEN (title_field_tags); i ++) - { - GtkWidget * menu_item = gtk_menu_item_new_with_label (_(title_field_tags[i].name)); - gtk_menu_shell_append ((GtkMenuShell *) titlestring_tag_menu, menu_item); - g_signal_connect(menu_item, "activate", - (GCallback) titlestring_tag_menu_cb, GINT_TO_POINTER (i)); - }; - - gtk_widget_show_all (titlestring_tag_menu); - - return titlestring_tag_menu; -} - -static void show_numbers_cb (GtkToggleButton * numbers, void * unused) -{ - set_bool (NULL, "show_numbers_in_pl", gtk_toggle_button_get_active (numbers)); - playlist_reformat_titles (); - hook_call ("title change", NULL); -} - -static void leading_zero_cb (GtkToggleButton * leading) -{ - set_bool (NULL, "leading_zero", gtk_toggle_button_get_active (leading)); - playlist_reformat_titles (); - hook_call ("title change", NULL); -} - -static void create_titlestring_widgets (GtkWidget * * cbox, GtkWidget * * entry) -{ - * cbox = gtk_combo_box_text_new (); - for (int i = 0; i < TITLESTRING_NPRESETS; i ++) - gtk_combo_box_text_append_text ((GtkComboBoxText *) * cbox, _(titlestring_preset_names[i])); - gtk_combo_box_text_append_text ((GtkComboBoxText *) * cbox, _("Custom")); - - * entry = gtk_entry_new (); - - char * format = get_str (NULL, "generic_title_format"); - update_titlestring_cbox ((GtkComboBox *) * cbox, format); - gtk_entry_set_text ((GtkEntry *) * entry, format); - str_unref (format); - - g_signal_connect (* cbox, "changed", (GCallback) on_titlestring_cbox_changed, * entry); - g_signal_connect (* entry, "changed", (GCallback) on_titlestring_entry_changed, * cbox); -} - -static void create_playlist_category (void) -{ - GtkWidget * vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - gtk_container_add ((GtkContainer *) category_notebook, vbox); - - create_widgets ((GtkBox *) vbox, playlist_page_widgets, ARRAY_LEN (playlist_page_widgets)); - - GtkWidget * alignment = gtk_alignment_new (0.5, 0.5, 1, 1); - gtk_box_pack_start ((GtkBox *) vbox, alignment, FALSE, FALSE, 0); - gtk_alignment_set_padding ((GtkAlignment *) alignment, 12, 3, 0, 0); - - GtkWidget * label = gtk_label_new (_("<b>Song Display</b>")); - gtk_container_add ((GtkContainer *) alignment, label); - gtk_label_set_use_markup ((GtkLabel *) label, TRUE); - gtk_misc_set_alignment ((GtkMisc *) label, 0, 0.5); - - GtkWidget * numbers_alignment = gtk_alignment_new (0, 0, 0, 0); - gtk_alignment_set_padding ((GtkAlignment *) numbers_alignment, 0, 0, 12, 0); - gtk_box_pack_start ((GtkBox *) vbox, numbers_alignment, 0, 0, 3); - - GtkWidget * numbers = gtk_check_button_new_with_label (_("Show song numbers")); - gtk_toggle_button_set_active ((GtkToggleButton *) numbers, - get_bool (NULL, "show_numbers_in_pl")); - g_signal_connect (numbers, "toggled", (GCallback) show_numbers_cb, 0); - gtk_container_add ((GtkContainer *) numbers_alignment, numbers); - - numbers_alignment = gtk_alignment_new (0, 0, 0, 0); - gtk_alignment_set_padding ((GtkAlignment *) numbers_alignment, 0, 0, 12, 0); - gtk_box_pack_start ((GtkBox *) vbox, numbers_alignment, 0, 0, 3); - - numbers = gtk_check_button_new_with_label ( - _("Show leading zeroes (02:00 instead of 2:00)")); - gtk_toggle_button_set_active ((GtkToggleButton *) numbers, get_bool (NULL, "leading_zero")); - g_signal_connect (numbers, "toggled", (GCallback) leading_zero_cb, 0); - gtk_container_add ((GtkContainer *) numbers_alignment, numbers); - - alignment = gtk_alignment_new (0.5, 0.5, 1, 1); - gtk_box_pack_start ((GtkBox *) vbox, alignment, FALSE, FALSE, 0); - gtk_alignment_set_padding ((GtkAlignment *) alignment, 0, 0, 12, 0); - - GtkWidget * grid = gtk_grid_new (); - gtk_container_add ((GtkContainer *) alignment, grid); - gtk_grid_set_row_spacing ((GtkGrid *) grid, 4); - gtk_grid_set_column_spacing ((GtkGrid *) grid, 12); - - label = gtk_label_new (_("Title format:")); - gtk_grid_attach ((GtkGrid *) grid, label, 0, 0, 1, 1); - gtk_label_set_justify ((GtkLabel *) label, GTK_JUSTIFY_RIGHT); - gtk_misc_set_alignment ((GtkMisc *) label, 1, 0.5); - - label = gtk_label_new (_("Custom string:")); - gtk_grid_attach ((GtkGrid *) grid, label, 0, 1, 1, 1); - gtk_label_set_justify ((GtkLabel *) label, GTK_JUSTIFY_RIGHT); - gtk_misc_set_alignment ((GtkMisc *) label, 1, 0.5); - - GtkWidget * titlestring_cbox; - create_titlestring_widgets (& titlestring_cbox, & titlestring_entry); - gtk_widget_set_hexpand (titlestring_cbox, TRUE); - gtk_widget_set_hexpand (titlestring_entry, TRUE); - gtk_grid_attach ((GtkGrid *) grid, titlestring_cbox, 1, 0, 1, 1); - gtk_grid_attach ((GtkGrid *) grid, titlestring_entry, 1, 1, 1, 1); - - GtkWidget * titlestring_help_button = gtk_button_new (); - gtk_widget_set_can_focus (titlestring_help_button, FALSE); - gtk_button_set_focus_on_click ((GtkButton *) titlestring_help_button, FALSE); - gtk_button_set_relief ((GtkButton *) titlestring_help_button, GTK_RELIEF_HALF); - gtk_grid_attach ((GtkGrid *) grid, titlestring_help_button, 2, 1, 1, 1); - - GtkWidget * titlestring_tag_menu = create_titlestring_tag_menu (); - - g_signal_connect (titlestring_help_button, "clicked", - (GCallback) on_titlestring_help_button_clicked, titlestring_tag_menu); - - GtkWidget * image = gtk_image_new_from_icon_name ("list-add", GTK_ICON_SIZE_BUTTON); - gtk_container_add ((GtkContainer *) titlestring_help_button, image); -} - -static void create_song_info_category (void) -{ - GtkWidget * vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - gtk_container_add ((GtkContainer *) category_notebook, vbox); - create_widgets ((GtkBox *) vbox, song_info_page_widgets, - ARRAY_LEN (song_info_page_widgets)); -} - -static void iface_fill_prefs_box (void) -{ - Plugin * header = plugin_get_header (plugin_get_current (PLUGIN_TYPE_IFACE)); - if (header && header->prefs) - create_widgets_with_domain (iface_prefs_box, header->prefs->widgets, - header->prefs->n_widgets, header->domain); -} - -static void iface_combo_changed (void) -{ - gtk_container_foreach ((GtkContainer *) iface_prefs_box, (GtkCallback) gtk_widget_destroy, NULL); - - plugin_enable (plugin_by_index (PLUGIN_TYPE_IFACE, iface_combo_selected), TRUE); - - iface_fill_prefs_box (); - gtk_widget_show_all (iface_prefs_box); -} - -static const ComboBoxElements * iface_combo_fill (int * n_elements) -{ - if (! iface_combo_elements) - { - iface_combo_elements = fill_plugin_combo (PLUGIN_TYPE_IFACE); - iface_combo_selected = plugin_get_index (plugin_get_current (PLUGIN_TYPE_IFACE)); - } - - * n_elements = iface_combo_elements->len; - return (const ComboBoxElements *) iface_combo_elements->data; -} - -static void * iface_create_prefs_box (void) -{ - iface_prefs_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - iface_fill_prefs_box (); - return iface_prefs_box; -} - -static void create_appearance_category (void) -{ - GtkWidget * vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - gtk_container_add ((GtkContainer *) category_notebook, vbox); - create_widgets ((GtkBox *) vbox, appearance_page_widgets, ARRAY_LEN (appearance_page_widgets)); -} - -static void output_combo_changed (void) -{ - PluginHandle * plugin = plugin_by_index (PLUGIN_TYPE_OUTPUT, output_combo_selected); - - if (plugin_enable (plugin, TRUE)) - { - gtk_widget_set_sensitive (output_config_button, plugin_has_configure (plugin)); - gtk_widget_set_sensitive (output_about_button, plugin_has_about (plugin)); - } -} - -static const ComboBoxElements * output_combo_fill (int * n_elements) -{ - if (! output_combo_elements) - { - output_combo_elements = fill_plugin_combo (PLUGIN_TYPE_OUTPUT); - output_combo_selected = plugin_get_index (output_plugin_get_current ()); - } - - * n_elements = output_combo_elements->len; - return (const ComboBoxElements *) output_combo_elements->data; -} - -static void output_bit_depth_changed (void) -{ - output_reset (OUTPUT_RESET_SOFT); -} - -static void output_do_config (void * unused) -{ - plugin_do_configure (output_plugin_get_current ()); -} - -static void output_do_about (void * unused) -{ - plugin_do_about (output_plugin_get_current ()); -} - -static void * output_create_config_button (void) -{ - bool_t enabled = plugin_has_configure (output_plugin_get_current ()); - - output_config_button = audgui_button_new (_("_Settings"), - "preferences-system", output_do_config, NULL); - gtk_widget_set_sensitive (output_config_button, enabled); - - return output_config_button; -} - -static void * output_create_about_button (void) -{ - bool_t enabled = plugin_has_about (output_plugin_get_current ()); - - output_about_button = audgui_button_new (_("_About"), "help-about", output_do_about, NULL); - gtk_widget_set_sensitive (output_about_button, enabled); - - return output_about_button; -} - -static void create_audio_category (void) -{ - GtkWidget * audio_page_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - create_widgets ((GtkBox *) audio_page_vbox, audio_page_widgets, ARRAY_LEN (audio_page_widgets)); - gtk_container_add ((GtkContainer *) category_notebook, audio_page_vbox); -} - -static void create_connectivity_category (void) -{ - GtkWidget * connectivity_page_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - gtk_container_add ((GtkContainer *) category_notebook, connectivity_page_vbox); - - GtkWidget * vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - gtk_box_pack_start ((GtkBox *) connectivity_page_vbox, vbox, TRUE, TRUE, 0); - - create_widgets ((GtkBox *) vbox, connectivity_page_widgets, ARRAY_LEN (connectivity_page_widgets)); -} - -static void create_plugin_category (void) -{ - plugin_notebook = gtk_notebook_new (); - gtk_container_add ((GtkContainer *) category_notebook, plugin_notebook); - - for (int i = 0; i < ARRAY_LEN (plugin_categories); i ++) - { - const PluginCategory * cat = & plugin_categories[i]; - gtk_notebook_append_page ((GtkNotebook *) plugin_notebook, - plugin_view_new (cat->type), gtk_label_new (_(cat->name))); - } -} - -static void destroy_cb (void) -{ - prefswin = NULL; - category_treeview = NULL; - category_notebook = NULL; - titlestring_entry = NULL; - - if (iface_combo_elements) - { - g_array_free (iface_combo_elements, TRUE); - iface_combo_elements = NULL; - } - - if (output_combo_elements) - { - g_array_free (output_combo_elements, TRUE); - output_combo_elements = NULL; - } -} - -static void create_prefs_window (void) -{ - prefswin = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_window_set_type_hint ((GtkWindow *) prefswin, GDK_WINDOW_TYPE_HINT_DIALOG); - gtk_container_set_border_width ((GtkContainer *) prefswin, 12); - gtk_window_set_title ((GtkWindow *) prefswin, _("Audacious Settings")); - gtk_window_set_default_size ((GtkWindow *) prefswin, 680, 400); - - GtkWidget * vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - gtk_container_add ((GtkContainer *) prefswin, vbox); - - GtkWidget * hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8); - gtk_box_pack_start ((GtkBox *) vbox, hbox, TRUE, TRUE, 0); - - GtkWidget * scrolledwindow = gtk_scrolled_window_new (NULL, NULL); - gtk_box_pack_start ((GtkBox *) hbox, scrolledwindow, FALSE, FALSE, 0); - gtk_scrolled_window_set_policy ((GtkScrolledWindow *) scrolledwindow, - GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_shadow_type ((GtkScrolledWindow *) scrolledwindow, GTK_SHADOW_IN); - - category_treeview = gtk_tree_view_new (); - gtk_container_add ((GtkContainer *) scrolledwindow, category_treeview); - gtk_widget_set_size_request (scrolledwindow, 168, -1); - gtk_tree_view_set_headers_visible ((GtkTreeView *) category_treeview, FALSE); - - category_notebook = gtk_notebook_new (); - gtk_box_pack_start ((GtkBox *) hbox, category_notebook, TRUE, TRUE, 0); - - gtk_widget_set_can_focus (category_notebook, FALSE); - gtk_notebook_set_show_tabs ((GtkNotebook *) category_notebook, FALSE); - gtk_notebook_set_show_border ((GtkNotebook *) category_notebook, FALSE); - - create_appearance_category (); - create_audio_category (); - create_connectivity_category (); - create_playlist_category (); - create_song_info_category (); - create_plugin_category (); - - GtkWidget * hseparator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); - gtk_box_pack_start ((GtkBox *) vbox, hseparator, FALSE, FALSE, 6); - - hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); - gtk_box_pack_start ((GtkBox *) vbox, hbox, FALSE, FALSE, 0); - - GtkWidget * audversionlabel = gtk_label_new (aud_version_string); - gtk_box_pack_start ((GtkBox *) hbox, audversionlabel, FALSE, FALSE, 0); - gtk_label_set_use_markup ((GtkLabel *) audversionlabel, TRUE); - - GtkWidget * prefswin_button_box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); - gtk_box_pack_start ((GtkBox *) hbox, prefswin_button_box, TRUE, TRUE, 0); - gtk_button_box_set_layout ((GtkButtonBox *) prefswin_button_box, GTK_BUTTONBOX_END); - gtk_box_set_spacing ((GtkBox *) prefswin_button_box, 6); - - GtkWidget * close = audgui_button_new (_("_Close"), "window-close", - (AudguiCallback) gtk_widget_destroy, prefswin); - gtk_container_add ((GtkContainer *) prefswin_button_box, close); - gtk_widget_set_can_default (close, TRUE); - - fill_category_list ((GtkTreeView *) category_treeview, (GtkNotebook *) category_notebook); - - gtk_widget_show_all (vbox); - - g_signal_connect (prefswin, "destroy", (GCallback) destroy_cb, NULL); - - audgui_destroy_on_escape (prefswin); -} - -void show_prefs_window (void) -{ - if (! prefswin) - create_prefs_window (); - - change_category (CATEGORY_APPEARANCE); - - gtk_window_present ((GtkWindow *) prefswin); -} - -void show_prefs_for_plugin_type (int type) -{ - if (! prefswin) - create_prefs_window (); - - if (type == PLUGIN_TYPE_IFACE) - change_category (CATEGORY_APPEARANCE); - else if (type == PLUGIN_TYPE_OUTPUT) - change_category (CATEGORY_AUDIO); - else - { - change_category (CATEGORY_PLUGINS); - - for (int i = 0; i < ARRAY_LEN (plugin_categories); i ++) - { - if (plugin_categories[i].type == type) - gtk_notebook_set_current_page ((GtkNotebook *) plugin_notebook, i); - } - } - - gtk_window_present ((GtkWindow *) prefswin); -} - -void hide_prefs_window (void) -{ - if (prefswin) - gtk_widget_destroy (prefswin); -} diff --git a/src/audacious/ui_preferences.h b/src/audacious/ui_preferences.h deleted file mode 100644 index b9b2707..0000000 --- a/src/audacious/ui_preferences.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * ui_preferences.h - * Copyright 2006-2012 William Pitcock, Tomasz MoĆ, and John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_UI_PREFERENCES_H -#define AUDACIOUS_UI_PREFERENCES_H - -#include <gtk/gtk.h> - -#include "types.h" - -void show_prefs_window(void); -void hide_prefs_window(void); - -/* plugin-preferences.c */ -void plugin_make_about_window (PluginHandle * plugin); -void plugin_make_config_window (PluginHandle * plugin); -void plugin_misc_cleanup (PluginHandle * plugin); - -/* plugin-view.c */ -GtkWidget * plugin_view_new (int type); - -#endif /* AUDACIOUS_UI_PREFERENCES_H */ diff --git a/src/audacious/util.c b/src/audacious/util.c deleted file mode 100644 index 3556e68..0000000 --- a/src/audacious/util.c +++ /dev/null @@ -1,474 +0,0 @@ -/* - * util.c - * Copyright 2009-2013 John Lindgren and MichaĆ Lipski - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <ctype.h> -#include <errno.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#ifdef _WIN32 -#include <windows.h> -#endif - -#ifdef __APPLE__ -#include <mach-o/dyld.h> -#endif - -#include <glib.h> - -#include <libaudcore/audstrings.h> - -#include "debug.h" -#include "i18n.h" -#include "misc.h" -#include "plugins.h" -#include "util.h" - -bool_t dir_foreach (const char * path, DirForeachFunc func, void * user) -{ - GDir * dir = g_dir_open (path, 0, NULL); - if (! dir) - return FALSE; - - const char * name; - while ((name = g_dir_read_name (dir))) - { - char * full = filename_build (path, name); - bool_t stop = func (full, name, user); - str_unref (full); - - if (stop) - break; - } - - g_dir_close (dir); - return TRUE; -} - -char * construct_uri (const char * path, const char * reference) -{ - /* URI */ - if (strstr (path, "://")) - return str_get (path); - - /* absolute filename */ -#ifdef _WIN32 - if (path[0] && path[1] == ':' && path[2] == '\\') -#else - if (path[0] == '/') -#endif - return filename_to_uri (path); - - /* relative path */ - const char * slash = strrchr (reference, '/'); - if (! slash) - return NULL; - - char * utf8 = str_to_utf8 (path, -1); - if (! utf8) - return NULL; - - int pathlen = slash + 1 - reference; - - char buf[pathlen + 3 * strlen (utf8) + 1]; - memcpy (buf, reference, pathlen); - - if (get_bool (NULL, "convert_backslash")) - { - SCOPY (tmp, utf8); - str_replace_char (tmp, '\\', '/'); - str_encode_percent (tmp, -1, buf + pathlen); - } - else - str_encode_percent (utf8, -1, buf + pathlen); - - str_unref (utf8); - return str_get (buf); -} - -void -make_directory(const char * path, mode_t mode) -{ - if (g_mkdir_with_parents(path, mode) == 0) - return; - - g_printerr(_("Could not create directory (%s): %s\n"), path, - g_strerror(errno)); -} - -char * write_temp_file (void * data, int64_t len) -{ - char * temp = filename_build (g_get_tmp_dir (), "audacious-temp-XXXXXX"); - SCOPY (name, temp); - str_unref (temp); - - int handle = g_mkstemp (name); - if (handle < 0) - { - fprintf (stderr, "Error creating temporary file: %s\n", strerror (errno)); - return NULL; - } - - while (len) - { - int64_t written = write (handle, data, len); - if (written < 0) - { - fprintf (stderr, "Error writing %s: %s\n", name, strerror (errno)); - close (handle); - return NULL; - } - - data = (char *) data + written; - len -= written; - } - - if (close (handle) < 0) - { - fprintf (stderr, "Error closing %s: %s\n", name, strerror (errno)); - return NULL; - } - - return str_get (name); -} - -char * get_path_to_self (void) -{ -#ifdef HAVE_PROC_SELF_EXE - int size = 256; - - while (1) - { - char buf[size]; - int len; - - if ((len = readlink ("/proc/self/exe", buf, size)) < 0) - { - fprintf (stderr, "Cannot access /proc/self/exe: %s.\n", strerror (errno)); - return NULL; - } - - if (len < size) - { - buf[len] = 0; - return str_get (buf); - } - - size += size; - } -#elif defined _WIN32 - int size = 256; - - while (1) - { - wchar_t buf[size]; - int len; - - if (! (len = GetModuleFileNameW (NULL, buf, size))) - { - fprintf (stderr, "GetModuleFileName failed.\n"); - return NULL; - } - - if (len < size) - { - char * temp = g_utf16_to_utf8 (buf, len, NULL, NULL, NULL); - char * path = str_get (temp); - g_free (temp); - return path; - } - - size += size; - } -#elif defined __APPLE__ - unsigned int size = 256; - - while (1) - { - char buf[size]; - int res; - - if (! (res = _NSGetExecutablePath (buf, &size))) - return str_get (buf); - - if (res != -1) - return NULL; - } -#else - return NULL; -#endif -} - -#ifdef _WIN32 -void get_argv_utf8 (int * argc, char * * * argv) -{ - wchar_t * combined = GetCommandLineW (); - wchar_t * * split = CommandLineToArgvW (combined, argc); - - * argv = g_new (char *, argc + 1); - - for (int i = 0; i < * argc; i ++) - (* argv)[i] = g_utf16_to_utf8 (split[i], -1, NULL, NULL, NULL); - - (* argv)[* argc] = 0; - - LocalFree (split); -} - -void free_argv_utf8 (int * argc, char * * * argv) -{ - g_strfreev (* argv); - * argc = 0; - * argv = NULL; -} -#endif - -/* Strips various common top-level folders from a filename. The string passed - * will not be modified, but the string returned will share the same memory. - * Examples: - * "/home/john/folder/file.mp3" -> "folder/file.mp3" - * "/folder/file.mp3" -> "folder/file.mp3" */ - -static char * skip_top_folders (char * name) -{ - static const char * home; - static int len; - - if (! home) - { - home = g_get_home_dir (); - len = strlen (home); - - if (len > 0 && home[len - 1] == G_DIR_SEPARATOR) - len --; - } - -#ifdef _WIN32 - if (! g_ascii_strncasecmp (name, home, len) && name[len] == '\\') -#else - if (! strncmp (name, home, len) && name[len] == '/') -#endif - return name + len + 1; - -#ifdef _WIN32 - if (g_ascii_isalpha (name[0]) && name[1] == ':' && name[2] == '\\') - return name + 3; -#else - if (name[0] == '/') - return name + 1; -#endif - - return name; -} - -/* Divides a filename into the base name, the lowest folder, and the - * second lowest folder. The string passed will be modified, and the strings - * returned will use the same memory. May return NULL for <first> and <second>. - * Examples: - * "a/b/c/d/e.mp3" -> "e", "d", "c" - * "d/e.mp3" -> "e", "d", NULL - * "e.mp3" -> "e", NULL, NULL */ - -static void split_filename (char * name, char * * base, char * * first, - char * * second) -{ - * first = * second = NULL; - - char * c; - - if ((c = strrchr (name, G_DIR_SEPARATOR))) - { - * base = c + 1; - * c = 0; - } - else - { - * base = name; - goto DONE; - } - - if ((c = strrchr (name, G_DIR_SEPARATOR))) - { - * first = c + 1; - * c = 0; - } - else - { - * first = name; - goto DONE; - } - - if ((c = strrchr (name, G_DIR_SEPARATOR))) - * second = c + 1; - else - * second = name; - -DONE: - if ((c = strrchr (* base, '.'))) - * c = 0; -} - -/* Separates the domain name from an internet URI. The string passed will be - * modified, and the string returned will share the same memory. May return - * NULL. Examples: - * "http://some.domain.org/folder/file.mp3" -> "some.domain.org" - * "http://some.stream.fm:8000" -> "some.stream.fm" */ - -static char * stream_name (char * name) -{ - if (! strncmp (name, "http://", 7)) - name += 7; - else if (! strncmp (name, "https://", 8)) - name += 8; - else if (! strncmp (name, "mms://", 6)) - name += 6; - else - return NULL; - - char * c; - - if ((c = strchr (name, '/'))) - * c = 0; - if ((c = strchr (name, ':'))) - * c = 0; - if ((c = strchr (name, '?'))) - * c = 0; - - return name; -} - -static char * get_nonblank_field (const Tuple * tuple, int field) -{ - char * str = tuple ? tuple_get_str (tuple, field) : NULL; - - if (str && ! str[0]) - { - str_unref (str); - str = NULL; - } - - return str; -} - -static char * str_get_decoded (char * str) -{ - if (! str) - return NULL; - - str_decode_percent (str, -1, str); - return str_get (str); -} - -/* Derives best guesses of title, artist, and album from a file name (URI) and - * tuple (which may be NULL). The returned strings are stringpooled or NULL. */ - -void describe_song (const char * name, const Tuple * tuple, char * * _title, - char * * _artist, char * * _album) -{ - /* Common folder names to skip */ - static const char * const skip[] = {"music"}; - - char * title = get_nonblank_field (tuple, FIELD_TITLE); - char * artist = get_nonblank_field (tuple, FIELD_ARTIST); - char * album = get_nonblank_field (tuple, FIELD_ALBUM); - - if (title && artist && album) - { -DONE: - * _title = title; - * _artist = artist; - * _album = album; - return; - } - - if (! strncmp (name, "file:///", 8)) - { - char * filename = uri_to_display (name); - if (! filename) - goto DONE; - - SCOPY (buf, filename); - - char * base, * first, * second; - split_filename (skip_top_folders (buf), & base, & first, & second); - - if (! title) - title = str_get (base); - - for (int i = 0; i < ARRAY_LEN (skip); i ++) - { - if (first && ! g_ascii_strcasecmp (first, skip[i])) - first = NULL; - if (second && ! g_ascii_strcasecmp (second, skip[i])) - second = NULL; - } - - if (first) - { - if (second && ! artist && ! album) - { - artist = str_get (second); - album = str_get (first); - } - else if (! artist) - artist = str_get (first); - else if (! album) - album = str_get (first); - } - - str_unref (filename); - } - else - { - SCOPY (buf, name); - - if (! title) - { - title = str_get_decoded (stream_name (buf)); - - if (! title) - title = str_get_decoded (buf); - } - else if (! artist) - artist = str_get_decoded (stream_name (buf)); - else if (! album) - album = str_get_decoded (stream_name (buf)); - } - - goto DONE; -} - -char * last_path_element (char * path) -{ - char * slash = strrchr (path, G_DIR_SEPARATOR); - return (slash && slash[1]) ? slash + 1 : NULL; -} - -void cut_path_element (char * path, char * elem) -{ -#ifdef _WIN32 - if (elem > path + 3) -#else - if (elem > path + 1) -#endif - elem[-1] = 0; /* overwrite slash */ - else - elem[0] = 0; /* leave [drive letter and] leading slash */ -} diff --git a/src/audacious/debug.h b/src/audacious/util.cc index 2b372ad..c4449de 100644 --- a/src/audacious/debug.h +++ b/src/audacious/util.cc @@ -1,6 +1,6 @@ /* - * debug.h - * Copyright 2010-2011 John Lindgren + * util.c + * Copyright 2009-2013 John Lindgren and MichaĆ Lipski * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -17,17 +17,32 @@ * the use of this software. */ -#ifndef AUDACIOUS_DEBUG_H -#define AUDACIOUS_DEBUG_H +#include "util.h" -#include <stdio.h> +#ifdef _WIN32 +#include <windows.h> -#include <audacious/api.h> - -#ifdef _AUDACIOUS_CORE -#define AUDDBG(...) do {if (verbose) {printf ("%s:%d [%s]: ", __FILE__, __LINE__, __FUNCTION__); printf (__VA_ARGS__);}} while (0) +#ifdef WORDS_BIGENDIAN +#define UTF16_NATIVE "UTF-16BE" #else -#define AUDDBG(...) do {if (* _aud_api_table->verbose) {printf ("%s:%d [%s]: ", __FILE__, __LINE__, __FUNCTION__); printf (__VA_ARGS__);}} while (0) +#define UTF16_NATIVE "UTF-16LE" #endif +Index<String> get_argv_utf8 () +{ + int argc; + wchar_t * combined = GetCommandLineW (); + wchar_t * * split = CommandLineToArgvW (combined, & argc); + + Index<String> argv; + argv.insert (0, argc); + + for (int i = 0; i < argc; i ++) + argv[i] = String (str_convert ((char *) split[i], + wcslen (split[i]) * sizeof (wchar_t), UTF16_NATIVE, "UTF-8")); + + LocalFree (split); + return argv; +} + #endif diff --git a/src/audacious/util.h b/src/audacious/util.h index 7f92cb3..55bfd6b 100644 --- a/src/audacious/util.h +++ b/src/audacious/util.h @@ -1,6 +1,6 @@ /* * util.h - * Copyright 2009-2013 John Lindgren + * Copyright 2014 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -20,29 +20,10 @@ #ifndef AUDACIOUS_UTIL_H #define AUDACIOUS_UTIL_H -#include <sys/types.h> -#include <libaudcore/core.h> - -typedef bool_t(*DirForeachFunc) (const char * path, - const char * basename, - void * user_data); - -bool_t dir_foreach (const char * path, DirForeachFunc func, void * user_data); - -void make_directory(const char * path, mode_t mode); -char * write_temp_file (void * data, int64_t len); /* pooled */ - -char * get_path_to_self (void); /* pooled */ +#include <libaudcore/audstrings.h> #ifdef _WIN32 -void get_argv_utf8 (int * argc, char * * * argv); -void free_argv_utf8 (int * argc, char * * * argv); +Index<String> get_argv_utf8 (); #endif -void describe_song (const char * filename, const Tuple * tuple, - char * * title, char * * artist, char * * album); - -char * last_path_element (char * path); -void cut_path_element (char * path, char * elem); - #endif /* AUDACIOUS_UTIL_H */ diff --git a/src/audacious/vis_runner.h b/src/audacious/vis_runner.h deleted file mode 100644 index 4b08c5b..0000000 --- a/src/audacious/vis_runner.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * vis_runner.h - * Copyright 2009-2010 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUD_VIS_RUNNER_H -#define AUD_VIS_RUNNER_H - -#include <libaudcore/core.h> - -void vis_runner_start_stop (bool_t playing, bool_t paused); -void vis_runner_pass_audio (int time, float * data, int samples, int channels, int rate); -void vis_runner_flush (void); -void vis_runner_enable (bool_t enable); - -#endif diff --git a/src/audacious/visualization.c b/src/audacious/visualization.c deleted file mode 100644 index 85ef430..0000000 --- a/src/audacious/visualization.c +++ /dev/null @@ -1,261 +0,0 @@ -/* - * visualization.c - * Copyright 2010-2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <gtk/gtk.h> -#include <string.h> - -#include "debug.h" -#include "fft.h" -#include "interface.h" -#include "misc.h" -#include "plugin.h" -#include "plugins.h" -#include "ui_preferences.h" -#include "visualization.h" -#include "vis_runner.h" - -static GList * vis_funcs[AUD_VIS_TYPES]; - -typedef struct { - PluginHandle * plugin; - VisPlugin * header; - GtkWidget * widget; -} LoadedVis; - -static int running = FALSE; -static GList * loaded_vis_plugins = NULL; - -void vis_func_add (int type, GCallback func) -{ - g_return_if_fail (type >= 0 && type < AUD_VIS_TYPES); - vis_funcs[type] = g_list_prepend (vis_funcs[type], (void *) func); - - vis_runner_enable (TRUE); -} - -void vis_func_remove (GCallback func) -{ - bool_t disable = TRUE; - - for (int i = 0; i < AUD_VIS_TYPES; i ++) - { - vis_funcs[i] = g_list_remove_all (vis_funcs[i], (void *) func); - if (vis_funcs[i]) - disable = FALSE; - } - - if (disable) - vis_runner_enable (FALSE); -} - -void vis_send_clear (void) -{ - for (GList * node = vis_funcs[AUD_VIS_TYPE_CLEAR]; node; node = node->next) - { - void (* func) (void) = (void (*) (void)) node->data; - func (); - } -} - -static void pcm_to_mono (const float * data, float * mono, int channels) -{ - if (channels == 1) - memcpy (mono, data, sizeof (float) * 512); - else - { - float * set = mono; - while (set < & mono[512]) - { - * set ++ = (data[0] + data[1]) / 2; - data += channels; - } - } -} - -void vis_send_audio (const float * data, int channels) -{ - float mono[512]; - float freq[256]; - - if (vis_funcs[AUD_VIS_TYPE_MONO_PCM] || vis_funcs[AUD_VIS_TYPE_FREQ]) - pcm_to_mono (data, mono, channels); - if (vis_funcs[AUD_VIS_TYPE_FREQ]) - calc_freq (mono, freq); - - for (GList * node = vis_funcs[AUD_VIS_TYPE_MONO_PCM]; node; node = node->next) - { - void (* func) (const float *) = (void (*) (const float *)) node->data; - func (mono); - } - - for (GList * node = vis_funcs[AUD_VIS_TYPE_MULTI_PCM]; node; node = node->next) - { - void (* func) (const float *, int) = (void (*) (const float *, int)) node->data; - func (data, channels); - } - - for (GList * node = vis_funcs[AUD_VIS_TYPE_FREQ]; node; node = node->next) - { - void (* func) (const float *) = (void (*) (const float *)) node->data; - func (freq); - } -} - -static int vis_find_cb (LoadedVis * vis, PluginHandle * plugin) -{ - return (vis->plugin == plugin) ? 0 : -1; -} - -static void vis_load (PluginHandle * plugin) -{ - GList * node = g_list_find_custom (loaded_vis_plugins, plugin, - (GCompareFunc) vis_find_cb); - if (node != NULL) - return; - - AUDDBG ("Loading %s.\n", plugin_get_name (plugin)); - VisPlugin * header = plugin_get_header (plugin); - g_return_if_fail (header != NULL); - - LoadedVis * vis = g_slice_new (LoadedVis); - vis->plugin = plugin; - vis->header = header; - vis->widget = NULL; - - if (header->get_widget != NULL) - vis->widget = header->get_widget (); - - if (vis->widget != NULL) - { - AUDDBG ("Adding %s to interface.\n", plugin_get_name (plugin)); - g_signal_connect (vis->widget, "destroy", (GCallback) - gtk_widget_destroyed, & vis->widget); - interface_add_plugin_widget (plugin, vis->widget); - } - - if (PLUGIN_HAS_FUNC (header, clear)) - vis_func_add (AUD_VIS_TYPE_CLEAR, (GCallback) header->clear); - if (PLUGIN_HAS_FUNC (header, render_mono_pcm)) - vis_func_add (AUD_VIS_TYPE_MONO_PCM, (GCallback) header->render_mono_pcm); - if (PLUGIN_HAS_FUNC (header, render_multi_pcm)) - vis_func_add (AUD_VIS_TYPE_MULTI_PCM, (GCallback) header->render_multi_pcm); - if (PLUGIN_HAS_FUNC (header, render_freq)) - vis_func_add (AUD_VIS_TYPE_FREQ, (GCallback) header->render_freq); - - loaded_vis_plugins = g_list_prepend (loaded_vis_plugins, vis); -} - -static void vis_unload (PluginHandle * plugin) -{ - GList * node = g_list_find_custom (loaded_vis_plugins, plugin, - (GCompareFunc) vis_find_cb); - if (node == NULL) - return; - - AUDDBG ("Unloading %s.\n", plugin_get_name (plugin)); - LoadedVis * vis = node->data; - loaded_vis_plugins = g_list_delete_link (loaded_vis_plugins, node); - - VisPlugin * header = vis->header; - if (PLUGIN_HAS_FUNC (header, clear)) - vis_func_remove ((GCallback) header->clear); - if (PLUGIN_HAS_FUNC (header, render_mono_pcm)) - vis_func_remove ((GCallback) header->render_mono_pcm); - if (PLUGIN_HAS_FUNC (header, render_multi_pcm)) - vis_func_remove ((GCallback) header->render_multi_pcm); - if (PLUGIN_HAS_FUNC (header, render_freq)) - vis_func_remove ((GCallback) header->render_freq); - - if (vis->widget != NULL) - { - AUDDBG ("Removing %s from interface.\n", plugin_get_name (plugin)); - interface_remove_plugin_widget (plugin, vis->widget); - g_return_if_fail (vis->widget == NULL); /* not destroyed? */ - } - - g_slice_free (LoadedVis, vis); -} - -static bool_t vis_init_cb (PluginHandle * plugin) -{ - vis_load (plugin); - return TRUE; -} - -void vis_init (void) -{ - g_return_if_fail (! running); - running = TRUE; - - plugin_for_enabled (PLUGIN_TYPE_VIS, (PluginForEachFunc) vis_init_cb, NULL); -} - -static void vis_cleanup_cb (LoadedVis * vis) -{ - vis_unload (vis->plugin); -} - -void vis_cleanup (void) -{ - g_return_if_fail (running); - running = FALSE; - - g_list_foreach (loaded_vis_plugins, (GFunc) vis_cleanup_cb, NULL); -} - -bool_t vis_plugin_start (PluginHandle * plugin) -{ - VisPlugin * vp = plugin_get_header (plugin); - g_return_val_if_fail (vp != NULL, FALSE); - - if (vp->init != NULL && ! vp->init ()) - return FALSE; - - if (running) - vis_load (plugin); - - return TRUE; -} - -void vis_plugin_stop (PluginHandle * plugin) -{ - VisPlugin * vp = plugin_get_header (plugin); - g_return_if_fail (vp != NULL); - - if (running) - vis_unload (plugin); - - if (vp->cleanup != NULL) - vp->cleanup (); -} - -PluginHandle * vis_plugin_by_widget (/* GtkWidget * */ void * widget) -{ - g_return_val_if_fail (widget, NULL); - - for (GList * node = loaded_vis_plugins; node; node = node->next) - { - LoadedVis * vis = node->data; - if (vis->widget == widget) - return vis->plugin; - } - - return NULL; -} diff --git a/src/audacious/visualization.h b/src/audacious/visualization.h deleted file mode 100644 index f57e86e..0000000 --- a/src/audacious/visualization.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * visualization.h - * Copyright 2010-2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDACIOUS_VISUALIZATION_H -#define AUDACIOUS_VISUALIZATION_H - -#include "plugins.h" - -void vis_send_clear (void); -void vis_send_audio (const float * data, int channels); - -void vis_init (void); -void vis_cleanup (void); - -bool_t vis_plugin_start (PluginHandle * plugin); -void vis_plugin_stop (PluginHandle * plugin); - -PluginHandle * vis_plugin_by_widget (/* GtkWidget * */ void * widget); - -#endif diff --git a/src/audtool/handlers_playback.c b/src/audtool/handlers_playback.c index 4e7e17f..1b3a90a 100644 --- a/src/audtool/handlers_playback.c +++ b/src/audtool/handlers_playback.c @@ -86,7 +86,7 @@ void playback_seek (int argc, char * * argv) exit (1); } - obj_audacious_call_seek_sync (dbus_proxy, atof (argv[1]) * 1000, NULL, NULL); + obj_audacious_call_seek_sync (dbus_proxy, MAX (0, atof (argv[1]) * 1000), NULL, NULL); } void playback_seek_relative (int argc, char * * argv) @@ -99,5 +99,5 @@ void playback_seek_relative (int argc, char * * argv) unsigned oldtime = 0; obj_audacious_call_time_sync (dbus_proxy, & oldtime, NULL, NULL); - obj_audacious_call_seek_sync (dbus_proxy, oldtime + atof (argv[1]) * 1000, NULL, NULL); + obj_audacious_call_seek_sync (dbus_proxy, MAX (0, oldtime + atof (argv[1]) * 1000), NULL, NULL); } diff --git a/src/libaudcore/Makefile b/src/libaudcore/Makefile index 8f380f7..3771eed 100644 --- a/src/libaudcore/Makefile +++ b/src/libaudcore/Makefile @@ -1,34 +1,79 @@ SHARED_LIB = ${LIB_PREFIX}audcore${LIB_SUFFIX} -LIB_MAJOR = 2 +LIB_MAJOR = 3 LIB_MINOR = 0 -SRCS = audio.c \ - audstrings.c \ - charset.c \ - eventqueue.c \ - hook.c \ - index.c \ - inifile.c \ - multihash.c \ - strpool.c \ - tinylock.c \ - tuple.c \ - tuple_compiler.c \ - tuple_formatter.c \ - vfs.c \ - vfs_async.c \ - vfs_common.c \ - vfs_local.c +SRCS = adder.cc \ + art.cc \ + art-search.cc \ + audio.cc \ + audstrings.cc \ + charset.cc \ + config.cc \ + drct.cc \ + effect.cc \ + equalizer.cc \ + equalizer-preset.cc \ + eventqueue.cc \ + fft.cc \ + history.cc \ + hook.cc \ + index.cc \ + inifile.cc \ + interface.cc \ + list.cc \ + logger.cc \ + mainloop.cc \ + multihash.cc \ + output.cc \ + playback.cc \ + playlist.cc \ + playlist-files.cc \ + playlist-utils.cc \ + plugin-init.cc \ + plugin-load.cc \ + plugin-registry.cc \ + preferences.cc \ + probe.cc \ + probe-buffer.cc \ + ringbuf.cc \ + runtime.cc \ + scanner.cc \ + stringbuf.cc \ + strpool.cc \ + tinylock.cc \ + tuple.cc \ + tuple-compiler.cc \ + util.cc \ + vfs.cc \ + vfs_async.cc \ + vfs_local.cc \ + vis-runner.cc \ + visualization.cc INCLUDES = audio.h \ audstrings.h \ - core.h \ + drct.h \ + equalizer.h \ hook.h \ + i18n.h \ index.h \ inifile.h \ + interface.h \ + list.h \ + mainloop.h \ multihash.h \ + objects.h \ + playlist.h \ + plugin.h \ + plugins.h \ + preferences.h \ + probe.h \ + ringbuf.h \ + runtime.h \ + templates.h \ tinylock.h \ tuple.h \ + visualizer.h \ vfs.h \ vfs_async.h @@ -37,13 +82,25 @@ include ../../extra.mk includesubdir = libaudcore +LD = ${CXX} + CPPFLAGS := -I.. -I../.. \ ${CPPFLAGS} \ ${GLIB_CFLAGS} \ - ${LIBGUESS_CFLAGS} + ${GMODULE_CFLAGS} \ + ${LIBGUESS_CFLAGS} \ + ${QT_CFLAGS} \ + -DHARDCODE_BINDIR=\"${bindir}\" \ + -DHARDCODE_DATADIR=\"${datadir}/audacious\" \ + -DHARDCODE_PLUGINDIR=\"${plugindir}\" \ + -DHARDCODE_LOCALEDIR=\"${localedir}\" \ + -DHARDCODE_DESKTOPFILE=\"${datarootdir}/applications/audacious.desktop\" \ + -DHARDCODE_ICONFILE=\"${datarootdir}/icons/hicolor/48x48/apps/audacious.png\" CFLAGS += ${LIB_CFLAGS} LIBS += -lm \ ${GLIB_LIBS} \ - ${LIBGUESS_LIBS} + ${GMODULE_LIBS} \ + ${LIBGUESS_LIBS} \ + ${QT_LIBS} diff --git a/src/libaudcore/adder.cc b/src/libaudcore/adder.cc new file mode 100644 index 0000000..f56143a --- /dev/null +++ b/src/libaudcore/adder.cc @@ -0,0 +1,475 @@ +/* + * adder.c + * Copyright 2011-2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "playlist-internal.h" +#include "internal.h" + +#include <pthread.h> +#include <string.h> +#include <sys/stat.h> + +#include <glib/gstdio.h> + +#include "audstrings.h" +#include "hook.h" +#include "i18n.h" +#include "list.h" +#include "mainloop.h" +#include "plugins-internal.h" +#include "probe.h" +#include "runtime.h" +#include "tuple.h" +#include "vfs.h" + +struct AddTask : public ListNode +{ + int playlist_id, at; + bool play; + Index<PlaylistAddItem> items; + PlaylistFilterFunc filter; + void * user; +}; + +struct AddResult : public ListNode +{ + int playlist_id, at; + bool play; + String title; + Index<PlaylistAddItem> items; +}; + +static void * add_worker (void * unused); + +static List<AddTask> add_tasks; +static List<AddResult> add_results; +static int current_playlist_id = -1; + +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static bool add_thread_started = false; +static bool add_thread_exited = false; +static pthread_t add_thread; +static QueuedFunc queued_add; +static QueuedFunc status_timer; +static char status_path[512]; +static int status_count; + +static void status_cb (void * unused) +{ + pthread_mutex_lock (& mutex); + + char scratch[128]; + snprintf (scratch, sizeof scratch, dngettext (PACKAGE, "%d file found", + "%d files found", status_count), status_count); + + if (aud_get_headless_mode ()) + { + printf ("Searching, %s ...\r", scratch); + fflush (stdout); + } + else + { + hook_call ("ui show progress", status_path); + hook_call ("ui show progress 2", scratch); + } + + pthread_mutex_unlock (& mutex); +} + +static void status_update (const char * filename, int found) +{ + pthread_mutex_lock (& mutex); + + snprintf (status_path, sizeof status_path, "%s", filename); + status_count = found; + + if (! status_timer.running ()) + status_timer.start (250, status_cb, nullptr); + + pthread_mutex_unlock (& mutex); +} + +static void status_done_locked (void) +{ + status_timer.stop (); + + if (aud_get_headless_mode ()) + printf ("\n"); + else + hook_call ("ui hide progress", nullptr); +} + +static void add_file (const char * filename, Tuple && tuple, + PluginHandle * decoder, PlaylistFilterFunc filter, void * user, + AddResult * result, bool validate) +{ + if (filter && ! filter (filename, user)) + return; + + AUDINFO ("Adding file: %s\n", filename); + status_update (filename, result->items.len ()); + + if (! tuple && ! decoder) + { + decoder = aud_file_find_decoder (filename, ! aud_get_bool (nullptr, "slow_probe")); + if (validate && ! decoder) + return; + } + + if (! tuple && decoder && input_plugin_has_subtunes (decoder) && ! strchr (filename, '?')) + tuple = aud_file_read_tuple (filename, decoder); + + int n_subtunes = tuple.get_n_subtunes (); + + if (n_subtunes) + { + for (int sub = 0; sub < n_subtunes; sub ++) + { + StringBuf subname = str_printf ("%s?%d", filename, tuple.get_nth_subtune (sub)); + add_file (subname, Tuple (), decoder, filter, user, result, false); + } + } + else + result->items.append (String (filename), std::move (tuple), decoder); +} + +static void add_playlist (const char * filename, PlaylistFilterFunc filter, + void * user, AddResult * result, bool is_single) +{ + if (filter && ! filter (filename, user)) + return; + + AUDINFO ("Adding playlist: %s\n", filename); + status_update (filename, result->items.len ()); + + String title; + Index<PlaylistAddItem> items; + + if (! playlist_load (filename, title, items)) + return; + + if (is_single) + result->title = title; + + for (auto & item : items) + add_file (item.filename, std::move (item.tuple), nullptr, filter, user, result, false); +} + +static void add_folder (const char * filename, PlaylistFilterFunc filter, + void * user, AddResult * result, bool is_single) +{ + Index<String> cuesheets, files; + GDir * folder; + + if (filter && ! filter (filename, user)) + return; + + AUDINFO ("Adding folder: %s\n", filename); + status_update (filename, result->items.len ()); + + StringBuf path = uri_to_filename (filename); + if (! path) + return; + + if (! (folder = g_dir_open (path, 0, nullptr))) + return; + + const char * name; + while ((name = g_dir_read_name (folder))) + { + if (str_has_suffix_nocase (name, ".cue")) + cuesheets.append (name); + else + files.append (name); + } + + g_dir_close (folder); + + for (const char * cuesheet : cuesheets) + { + AUDINFO ("Found cuesheet: %s\n", cuesheet); + + auto is_match = [=] (const char * name) + { return same_basename (name, cuesheet); }; + + files.remove_if (is_match); + } + + files.move_from (cuesheets, 0, -1, -1, true, true); + + if (! files.len ()) + return; + + if (is_single) + { + const char * last = last_path_element (path); + result->title = String (last ? last : path); + } + + auto compare_wrapper = [] (const String & a, const String & b, void *) + { return str_compare (a, b); }; + + files.sort (compare_wrapper, nullptr); + + for (const char * name : files) + { + StringBuf filepath = filename_build ({path, name}); + StringBuf uri = filename_to_uri (filepath); + if (! uri) + continue; + + GStatBuf info; + if (g_lstat (filepath, & info) < 0) + continue; + + if (S_ISREG (info.st_mode)) + { + if (str_has_suffix_nocase (name, ".cue")) + add_playlist (uri, filter, user, result, false); + else + add_file (uri, Tuple (), nullptr, filter, user, result, true); + } + else if (S_ISDIR (info.st_mode)) + add_folder (uri, filter, user, result, false); + } +} + +static void add_generic (const char * filename, Tuple && tuple, + PlaylistFilterFunc filter, void * user, AddResult * result, bool is_single) +{ + if (tuple) + add_file (filename, std::move (tuple), nullptr, filter, user, result, false); + else if (VFSFile::test_file (filename, VFS_IS_DIR)) + add_folder (filename, filter, user, result, is_single); + else if (aud_filename_is_playlist (filename)) + add_playlist (filename, filter, user, result, is_single); + else + add_file (filename, Tuple (), nullptr, filter, user, result, false); +} + +static void start_thread_locked (void) +{ + if (add_thread_exited) + { + pthread_mutex_unlock (& mutex); + pthread_join (add_thread, nullptr); + pthread_mutex_lock (& mutex); + } + + if (! add_thread_started || add_thread_exited) + { + pthread_create (& add_thread, nullptr, add_worker, nullptr); + add_thread_started = true; + add_thread_exited = false; + } +} + +static void stop_thread_locked (void) +{ + if (add_thread_started) + { + pthread_mutex_unlock (& mutex); + pthread_join (add_thread, nullptr); + pthread_mutex_lock (& mutex); + add_thread_started = false; + add_thread_exited = false; + } +} + +static void add_finish (void * unused) +{ + pthread_mutex_lock (& mutex); + + AddResult * result; + while ((result = add_results.head ())) + { + add_results.remove (result); + + int playlist, count; + + playlist = aud_playlist_by_unique_id (result->playlist_id); + if (playlist < 0) /* playlist deleted */ + goto FREE; + + count = aud_playlist_entry_count (playlist); + if (result->at < 0 || result->at > count) + result->at = count; + + if (result->title && ! count) + { + String old_title = aud_playlist_get_title (playlist); + + if (! strcmp (old_title, N_("New Playlist"))) + aud_playlist_set_title (playlist, result->title); + } + + playlist_entry_insert_batch_raw (playlist, result->at, std::move (result->items)); + + if (result->play && aud_playlist_entry_count (playlist) > count) + { + if (! aud_get_bool (0, "shuffle")) + aud_playlist_set_position (playlist, result->at); + + aud_playlist_play (playlist); + } + + FREE: + delete result; + } + + if (add_thread_exited) + { + stop_thread_locked (); + status_done_locked (); + } + + pthread_mutex_unlock (& mutex); + + hook_call ("playlist add complete", nullptr); +} + +static void * add_worker (void * unused) +{ + pthread_mutex_lock (& mutex); + + AddTask * task; + while ((task = add_tasks.head ())) + { + add_tasks.remove (task); + + current_playlist_id = task->playlist_id; + pthread_mutex_unlock (& mutex); + + AddResult * result = new AddResult (); + + result->playlist_id = task->playlist_id; + result->at = task->at; + result->play = task->play; + + bool is_single = (task->items.len () == 1); + + for (auto & item : task->items) + add_generic (item.filename, std::move (item.tuple), task->filter, + task->user, result, is_single); + + delete task; + + pthread_mutex_lock (& mutex); + current_playlist_id = -1; + + if (! add_results.head ()) + queued_add.queue (add_finish, nullptr); + + add_results.append (result); + } + + add_thread_exited = true; + pthread_mutex_unlock (& mutex); + return nullptr; +} + +void adder_cleanup (void) +{ + pthread_mutex_lock (& mutex); + + add_tasks.clear (); + + stop_thread_locked (); + status_done_locked (); + + add_results.clear (); + + queued_add.stop (); + + pthread_mutex_unlock (& mutex); +} + +EXPORT void aud_playlist_entry_insert (int playlist, int at, + const char * filename, Tuple && tuple, bool play) +{ + Index<PlaylistAddItem> items; + items.append (String (filename), std::move (tuple)); + + aud_playlist_entry_insert_batch (playlist, at, std::move (items), play); +} + +EXPORT void aud_playlist_entry_insert_batch (int playlist, int at, + Index<PlaylistAddItem> && items, bool play) +{ + aud_playlist_entry_insert_filtered (playlist, at, std::move (items), nullptr, nullptr, play); +} + +EXPORT void aud_playlist_entry_insert_filtered (int playlist, int at, + Index<PlaylistAddItem> && items, PlaylistFilterFunc filter, void * user, + bool play) +{ + int playlist_id = aud_playlist_get_unique_id (playlist); + + pthread_mutex_lock (& mutex); + + AddTask * task = new AddTask (); + + task->playlist_id = playlist_id; + task->at = at; + task->play = play; + task->items = std::move (items); + task->filter = filter; + task->user = user; + + add_tasks.append (task); + start_thread_locked (); + + pthread_mutex_unlock (& mutex); +} + +EXPORT bool aud_playlist_add_in_progress (int playlist) +{ + pthread_mutex_lock (& mutex); + + if (playlist >= 0) + { + int playlist_id = aud_playlist_get_unique_id (playlist); + + for (AddTask * task = add_tasks.head (); task; task = add_tasks.next (task)) + { + if (task->playlist_id == playlist_id) + goto YES; + } + + if (current_playlist_id == playlist_id) + goto YES; + + for (AddResult * result = add_results.head (); result; result = add_results.next (result)) + { + if (result->playlist_id == playlist_id) + goto YES; + } + } + else + { + if (add_tasks.head () || current_playlist_id >= 0 || add_results.head ()) + goto YES; + } + + pthread_mutex_unlock (& mutex); + return false; + +YES: + pthread_mutex_unlock (& mutex); + return true; +} diff --git a/src/libaudcore/art-search.cc b/src/libaudcore/art-search.cc new file mode 100644 index 0000000..5188d82 --- /dev/null +++ b/src/libaudcore/art-search.cc @@ -0,0 +1,155 @@ +/* + * art-search.c + * Copyright 2006-2013 Michael Hanselmann, Yoshiki Yazawa, and John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "internal.h" + +#include <string.h> + +#include <glib.h> /* for g_dir_open, g_file_test */ + +#include "audstrings.h" +#include "index.h" +#include "runtime.h" + +struct SearchParams { + String filename; + Index<String> include, exclude; +}; + +static bool has_front_cover_extension (const char * name) +{ + const char * ext = strrchr (name, '.'); + if (! ext) + return false; + + return ! strcmp_nocase (ext, ".jpg") || ! strcmp_nocase (ext, ".jpeg") || + ! strcmp_nocase (ext, ".png"); +} + +static bool cover_name_filter (const char * name, + const Index<String> & keywords, bool ret_on_empty) +{ + if (! keywords.len ()) + return ret_on_empty; + + for (const String & keyword : keywords) + { + if (strstr_nocase (name, keyword)) + return true; + } + + return false; +} + +static String fileinfo_recursive_get_image (const char * path, + const SearchParams * params, int depth) +{ + GDir * d = g_dir_open (path, 0, nullptr); + if (! d) + return String (); + + const char * name; + + if (aud_get_bool (nullptr, "use_file_cover") && ! depth) + { + /* Look for images matching file name */ + while ((name = g_dir_read_name (d))) + { + StringBuf newpath = filename_build ({path, name}); + + if (! g_file_test (newpath, G_FILE_TEST_IS_DIR) && + has_front_cover_extension (name) && + same_basename (name, params->filename)) + { + g_dir_close (d); + return String (newpath); + } + } + + g_dir_rewind (d); + } + + /* Search for files using filter */ + while ((name = g_dir_read_name (d))) + { + StringBuf newpath = filename_build ({path, name}); + + if (! g_file_test (newpath, G_FILE_TEST_IS_DIR) && + has_front_cover_extension (name) && + cover_name_filter (name, params->include, true) && + ! cover_name_filter (name, params->exclude, false)) + { + g_dir_close (d); + return String (newpath); + } + } + + g_dir_rewind (d); + + if (aud_get_bool (nullptr, "recurse_for_cover") && depth < aud_get_int (nullptr, "recurse_for_cover_depth")) + { + /* Descend into directories recursively. */ + while ((name = g_dir_read_name (d))) + { + StringBuf newpath = filename_build ({path, name}); + + if (g_file_test (newpath, G_FILE_TEST_IS_DIR)) + { + String tmp = fileinfo_recursive_get_image (newpath, params, depth + 1); + + if (tmp) + { + g_dir_close (d); + return tmp; + } + } + } + } + + g_dir_close (d); + return String (); +} + +String art_search (const char * filename) +{ + StringBuf local = uri_to_filename (filename); + if (! local) + return String (); + + const char * elem = last_path_element (local); + if (! elem) + return String (); + + String include = aud_get_str (nullptr, "cover_name_include"); + String exclude = aud_get_str (nullptr, "cover_name_exclude"); + + SearchParams params = { + String (elem), + str_list_to_index (include, ", "), + str_list_to_index (exclude, ", ") + }; + + cut_path_element (local, elem - local); + + String image_local = fileinfo_recursive_get_image (local, & params, 0); + if (! image_local) + return String (); + + return String (filename_to_uri (image_local)); +} diff --git a/src/libaudcore/art.cc b/src/libaudcore/art.cc new file mode 100644 index 0000000..e37b017 --- /dev/null +++ b/src/libaudcore/art.cc @@ -0,0 +1,271 @@ +/* + * art.c + * Copyright 2011-2012 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "probe.h" +#include "internal.h" + +#include <assert.h> +#include <errno.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <glib/gstdio.h> + +#include "audstrings.h" +#include "hook.h" +#include "mainloop.h" +#include "multihash.h" +#include "playlist.h" +#include "runtime.h" +#include "scanner.h" +#include "vfs.h" + +#define FLAG_DONE 1 +#define FLAG_SENT 2 + +struct ArtItem { + int refcount; + int flag; + + /* album art as JPEG or PNG data */ + Index<char> data; + + /* album art as (possibly a temporary) file */ + String art_file; + bool is_temp; +}; + +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +static SimpleHash<String, ArtItem> art_items; +static String current_ref; +static QueuedFunc queued_requests; + +static void get_queued_cb (const String & key, ArtItem & item, void * list) +{ + if (item.flag == FLAG_DONE) + { + ((Index<String> *) list)->append (key); + item.flag = FLAG_SENT; + } +} + +static Index<String> get_queued () +{ + Index<String> queued; + pthread_mutex_lock (& mutex); + + art_items.iterate (get_queued_cb, & queued); + + queued_requests.stop (); + + pthread_mutex_unlock (& mutex); + return queued; +} + +static void send_requests (void *) +{ + Index<String> queued = get_queued (); + + String current_wanted; + if (! current_ref) + { + int playlist = aud_playlist_get_playing (); + int entry = aud_playlist_get_position (playlist); + current_wanted = aud_playlist_entry_get_filename (playlist, entry); + } + + for (const String & file : queued) + { + hook_call ("art ready", (void *) (const char *) file); + + if (file == current_wanted) + { + hook_call ("current art ready", (void *) (const char *) file); + current_ref = file; + } + else + aud_art_unref (file); /* release temporary reference */ + } +} + +static void request_callback (ScanRequest * request) +{ + pthread_mutex_lock (& mutex); + + ArtItem * item = art_items.lookup (request->filename); + assert (item && ! item->flag); + + item->data = std::move (request->image_data); + item->art_file = std::move (request->image_file); + item->flag = FLAG_DONE; + + queued_requests.queue (send_requests, nullptr); + + pthread_mutex_unlock (& mutex); +} + +static ArtItem * art_item_get (const String & file) +{ + ArtItem * item = art_items.lookup (file); + + if (item && item->flag) + { + item->refcount ++; + return item; + } + + if (! item) + { + item = art_items.add (file, ArtItem ()); + item->refcount = 1; /* temporary reference */ + + scanner_request (new ScanRequest (file, SCAN_IMAGE, request_callback)); + } + + return nullptr; +} + +static void art_item_unref (const String & file, ArtItem * item) +{ + if (! -- item->refcount) + { + /* delete temporary file */ + if (item->art_file && item->is_temp) + { + StringBuf local = uri_to_filename (item->art_file); + if (local) + g_unlink (local); + } + + art_items.remove (file); + } +} + +static void release_current (void) +{ + if (current_ref) + { + aud_art_unref (current_ref); + current_ref = String (); + } +} + +void art_init (void) +{ + hook_associate ("playlist position", (HookFunction) release_current, nullptr); + hook_associate ("playlist set playing", (HookFunction) release_current, nullptr); +} + +void art_cleanup (void) +{ + hook_dissociate ("playlist position", (HookFunction) release_current); + hook_dissociate ("playlist set playing", (HookFunction) release_current); + + Index<String> queued = get_queued (); + for (const String & file : queued) + aud_art_unref (file); /* release temporary reference */ + + release_current (); + + if (art_items.n_items ()) + AUDWARN ("Album art reference count not zero at exit!\n"); +} + +EXPORT const Index<char> * aud_art_request_data (const char * file, bool * queued) +{ + const Index<char> * data = nullptr; + pthread_mutex_lock (& mutex); + + String key (file); + ArtItem * item = art_item_get (key); + + if (queued) + * queued = ! item; + + if (! item) + goto UNLOCK; + + /* load data from external image file */ + if (! item->data.len () && item->art_file) + { + VFSFile file (item->art_file, "r"); + if (file) + item->data = file.read_all (); + } + + if (item->data.len ()) + data = & item->data; + else + art_item_unref (key, item); + +UNLOCK: + pthread_mutex_unlock (& mutex); + return data; +} + +EXPORT const char * aud_art_request_file (const char * file, bool * queued) +{ + const char * art_file = nullptr; + pthread_mutex_lock (& mutex); + + String key (file); + ArtItem * item = art_item_get (key); + + if (queued) + * queued = ! item; + + if (! item) + goto UNLOCK; + + /* save data to temporary file */ + if (item->data.len () && ! item->art_file) + { + String local = write_temp_file (item->data.begin (), item->data.len ()); + if (local) + { + item->art_file = String (filename_to_uri (local)); + item->is_temp = true; + } + } + + if (item->art_file) + art_file = item->art_file; + else + art_item_unref (key, item); + +UNLOCK: + pthread_mutex_unlock (& mutex); + return art_file; +} + +EXPORT void aud_art_unref (const char * file) +{ + pthread_mutex_lock (& mutex); + + String key (file); + ArtItem * item = art_items.lookup (key); + assert (item); + + art_item_unref (key, item); + + pthread_mutex_unlock (& mutex); +} diff --git a/src/libaudcore/audio.c b/src/libaudcore/audio.c deleted file mode 100644 index 25246be..0000000 --- a/src/libaudcore/audio.c +++ /dev/null @@ -1,242 +0,0 @@ -/* - * audio.c - * Copyright 2009-2013 John Lindgren, MichaĆ Lipski, and Anders Johansson - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <stdint.h> -#include <math.h> - -#include "audio.h" -#include "core.h" - -#define INTERLACE_LOOP(TYPE) \ -for (int c = 0; c < channels; c ++) \ -{ \ - const TYPE * get = in[c]; \ - const TYPE * end = get + frames; \ - TYPE * set = (TYPE *) out + c; \ - while (get < end) \ - { \ - * set = * get ++; \ - set += channels; \ - } \ -} - -EXPORT void audio_interlace (const void * const * in, int format, int channels, - void * out, int frames) -{ - switch (format) - { - case FMT_FLOAT: - INTERLACE_LOOP (float); - break; - - case FMT_S8: - case FMT_U8: - INTERLACE_LOOP (int8_t); - break; - - case FMT_S16_LE: - case FMT_S16_BE: - case FMT_U16_LE: - case FMT_U16_BE: - INTERLACE_LOOP (int16_t); - break; - - case FMT_S24_LE: - case FMT_S24_BE: - case FMT_U24_LE: - case FMT_U24_BE: - case FMT_S32_LE: - case FMT_S32_BE: - case FMT_U32_LE: - case FMT_U32_BE: - INTERLACE_LOOP (int32_t); - break; - } -} - -#define FROM_INT_LOOP(NAME, TYPE, SWAP, OFFSET, RANGE) \ -static void NAME (const TYPE * in, float * out, int samples) \ -{ \ - const TYPE * end = in + samples; \ - while (in < end) \ - * out ++ = (TYPE) (SWAP (* in ++) - OFFSET) * (1.0 / (RANGE + 1.0)); \ -} - -#define TO_INT_LOOP(NAME, TYPE, SWAP, OFFSET, RANGE) \ -static void NAME (const float * in, TYPE * out, int samples) \ -{ \ - const float * end = in + samples; \ - while (in < end) \ - { \ - double f = (* in ++) * (RANGE + 1.0); \ - * out ++ = SWAP (OFFSET + (TYPE) round (CLAMP (f, -RANGE - 1, RANGE))); \ - } \ -} - -FROM_INT_LOOP (from_s8, int8_t, , 0x00, 0x7f) -FROM_INT_LOOP (from_u8, int8_t, , 0x80, 0x7f) -FROM_INT_LOOP (from_s16, int16_t, , 0x0000, 0x7fff) -FROM_INT_LOOP (from_u16, int16_t, , 0x8000, 0x7fff) -FROM_INT_LOOP (from_s24, int32_t, , 0x000000, 0x7fffff) -FROM_INT_LOOP (from_u24, int32_t, , 0x800000, 0x7fffff) -FROM_INT_LOOP (from_s32, int32_t, , 0x00000000, 0x7fffffff) -FROM_INT_LOOP (from_u32, int32_t, , 0x80000000, 0x7fffffff) - -TO_INT_LOOP (to_s8, int8_t, , 0x00, 0x7f) -TO_INT_LOOP (to_u8, int8_t, , 0x80, 0x7f) -TO_INT_LOOP (to_s16, int16_t, , 0x0000, 0x7fff) -TO_INT_LOOP (to_u16, int16_t, , 0x8000, 0x7fff) -TO_INT_LOOP (to_s24, int32_t, , 0x000000, 0x7fffff) -TO_INT_LOOP (to_u24, int32_t, , 0x800000, 0x7fffff) -TO_INT_LOOP (to_s32, int32_t, , 0x00000000, 0x7fffffff) -TO_INT_LOOP (to_u32, int32_t, , 0x80000000, 0x7fffffff) - -static inline int16_t SWAP16 (int16_t i) {return GUINT16_SWAP_LE_BE (i);} -static inline int32_t SWAP32 (int32_t i) {return GUINT32_SWAP_LE_BE (i);} - -FROM_INT_LOOP (from_s16_swap, int16_t, SWAP16, 0x0000, 0x7fff) -FROM_INT_LOOP (from_u16_swap, int16_t, SWAP16, 0x8000, 0x7fff) -FROM_INT_LOOP (from_s24_swap, int32_t, SWAP32, 0x000000, 0x7fffff) -FROM_INT_LOOP (from_u24_swap, int32_t, SWAP32, 0x800000, 0x7fffff) -FROM_INT_LOOP (from_s32_swap, int32_t, SWAP32, 0x00000000, 0x7fffffff) -FROM_INT_LOOP (from_u32_swap, int32_t, SWAP32, 0x80000000, 0x7fffffff) - -TO_INT_LOOP (to_s16_swap, int16_t, SWAP16, 0x0000, 0x7fff) -TO_INT_LOOP (to_u16_swap, int16_t, SWAP16, 0x8000, 0x7fff) -TO_INT_LOOP (to_s24_swap, int32_t, SWAP32, 0x000000, 0x7fffff) -TO_INT_LOOP (to_u24_swap, int32_t, SWAP32, 0x800000, 0x7fffff) -TO_INT_LOOP (to_s32_swap, int32_t, SWAP32, 0x00000000, 0x7fffffff) -TO_INT_LOOP (to_u32_swap, int32_t, SWAP32, 0x80000000, 0x7fffffff) - -typedef void (* FromFunc) (const void * in, float * out, int samples); -typedef void (* ToFunc) (const float * in, void * out, int samples); - -struct -{ - int format; - FromFunc from; - ToFunc to; -} -convert_table [] = -{ - {FMT_S8, (FromFunc) from_s8, (ToFunc) to_s8}, - {FMT_U8, (FromFunc) from_u8, (ToFunc) to_u8}, - -#if G_BYTE_ORDER == G_LITTLE_ENDIAN - {FMT_S16_LE, (FromFunc) from_s16, (ToFunc) to_s16}, - {FMT_U16_LE, (FromFunc) from_u16, (ToFunc) to_u16}, - {FMT_S24_LE, (FromFunc) from_s24, (ToFunc) to_s24}, - {FMT_U24_LE, (FromFunc) from_u24, (ToFunc) to_u24}, - {FMT_S32_LE, (FromFunc) from_s32, (ToFunc) to_s32}, - {FMT_U32_LE, (FromFunc) from_u32, (ToFunc) to_u32}, - - {FMT_S16_BE, (FromFunc) from_s16_swap, (ToFunc) to_s16_swap}, - {FMT_U16_BE, (FromFunc) from_u16_swap, (ToFunc) to_u16_swap}, - {FMT_S24_BE, (FromFunc) from_s24_swap, (ToFunc) to_s24_swap}, - {FMT_U24_BE, (FromFunc) from_u24_swap, (ToFunc) to_u24_swap}, - {FMT_S32_BE, (FromFunc) from_s32_swap, (ToFunc) to_s32_swap}, - {FMT_U32_BE, (FromFunc) from_u32_swap, (ToFunc) to_u32_swap}, -#else - {FMT_S16_BE, (FromFunc) from_s16, (ToFunc) to_s16}, - {FMT_U16_BE, (FromFunc) from_u16, (ToFunc) to_u16}, - {FMT_S24_BE, (FromFunc) from_s24, (ToFunc) to_s24}, - {FMT_U24_BE, (FromFunc) from_u24, (ToFunc) to_u24}, - {FMT_S32_BE, (FromFunc) from_s32, (ToFunc) to_s32}, - {FMT_U32_BE, (FromFunc) from_u32, (ToFunc) to_u32}, - - {FMT_S16_LE, (FromFunc) from_s16_swap, (ToFunc) to_s16_swap}, - {FMT_U16_LE, (FromFunc) from_u16_swap, (ToFunc) to_u16_swap}, - {FMT_S24_LE, (FromFunc) from_s24_swap, (ToFunc) to_s24_swap}, - {FMT_U24_LE, (FromFunc) from_u24_swap, (ToFunc) to_u24_swap}, - {FMT_S32_LE, (FromFunc) from_s32_swap, (ToFunc) to_s32_swap}, - {FMT_U32_LE, (FromFunc) from_u32_swap, (ToFunc) to_u32_swap}, -#endif -}; - -EXPORT void audio_from_int (const void * in, int format, float * out, int samples) -{ - int entry; - - for (entry = 0; entry < ARRAY_LEN (convert_table); entry ++) - { - if (convert_table[entry].format == format) - { - convert_table[entry].from (in, out, samples); - return; - } - } -} - -EXPORT void audio_to_int (const float * in, void * out, int format, int samples) -{ - int entry; - - for (entry = 0; entry < ARRAY_LEN (convert_table); entry ++) - { - if (convert_table[entry].format == format) - { - convert_table[entry].to (in, out, samples); - return; - } - } -} - -EXPORT void audio_amplify (float * data, int channels, int frames, float * factors) -{ - float * end = data + channels * frames; - int channel; - - while (data < end) - { - for (channel = 0; channel < channels; channel ++) - { - * data = * data * factors[channel]; - data ++; - } - } -} - -/* linear approximation of y = sin(x) */ -/* contributed by Anders Johansson */ -EXPORT void audio_soft_clip (float * data, int samples) -{ - float * end = data + samples; - - while (data < end) - { - float x = * data; - float y = fabsf (x); - - if (y <= 0.4) - ; /* (0, 0.4) -> (0, 0.4) */ - else if (y <= 0.7) - y = 0.8 * y + 0.08; /* (0.4, 0.7) -> (0.4, 0.64) */ - else if (y <= 1.0) - y = 0.7 * y + 0.15; /* (0.7, 1) -> (0.64, 0.85) */ - else if (y <= 1.3) - y = 0.4 * y + 0.45; /* (1, 1.3) -> (0.85, 0.97) */ - else if (y <= 1.5) - y = 0.15 * y + 0.775; /* (1.3, 1.5) -> (0.97, 1) */ - else - y = 1.0; /* (1.5, inf) -> 1 */ - - * data ++ = (x > 0) ? y : -y; - } -} diff --git a/src/libaudcore/audio.cc b/src/libaudcore/audio.cc new file mode 100644 index 0000000..9f92403 --- /dev/null +++ b/src/libaudcore/audio.cc @@ -0,0 +1,300 @@ +/* + * audio.c + * Copyright 2009-2013 John Lindgren, MichaĆ Lipski, and Anders Johansson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <math.h> +#include <stdint.h> + +#define WANT_AUD_BSWAP +#include "audio.h" +#include "objects.h" + +#define SW_VOLUME_RANGE 40 /* decibels */ + +#define INTERLACE_LOOP(TYPE) \ +for (int c = 0; c < channels; c ++) \ +{ \ + const TYPE * get = (const TYPE *) in[c]; \ + const TYPE * end = get + frames; \ + TYPE * set = (TYPE *) out + c; \ + while (get < end) \ + { \ + * set = * get ++; \ + set += channels; \ + } \ +} + +EXPORT void audio_interlace (const void * const * in, int format, int channels, + void * out, int frames) +{ + switch (format) + { + case FMT_FLOAT: + INTERLACE_LOOP (float); + break; + + case FMT_S8: + case FMT_U8: + INTERLACE_LOOP (int8_t); + break; + + case FMT_S16_LE: + case FMT_S16_BE: + case FMT_U16_LE: + case FMT_U16_BE: + INTERLACE_LOOP (int16_t); + break; + + case FMT_S24_LE: + case FMT_S24_BE: + case FMT_U24_LE: + case FMT_U24_BE: + case FMT_S32_LE: + case FMT_S32_BE: + case FMT_U32_LE: + case FMT_U32_BE: + INTERLACE_LOOP (int32_t); + break; + } +} + +#define DEINTERLACE_LOOP(TYPE) \ +for (int c = 0; c < channels; c ++) \ +{ \ + const TYPE * get = (const TYPE *) in + c; \ + TYPE * set = (TYPE *) out[c]; \ + TYPE * end = set + frames; \ + while (set < end) \ + { \ + * set ++ = * get; \ + get += channels; \ + } \ +} + +EXPORT void audio_deinterlace (const void * in, int format, int channels, + void * const * out, int frames) +{ + switch (format) + { + case FMT_FLOAT: + DEINTERLACE_LOOP (float); + break; + + case FMT_S8: + case FMT_U8: + DEINTERLACE_LOOP (int8_t); + break; + + case FMT_S16_LE: + case FMT_S16_BE: + case FMT_U16_LE: + case FMT_U16_BE: + DEINTERLACE_LOOP (int16_t); + break; + + case FMT_S24_LE: + case FMT_S24_BE: + case FMT_U24_LE: + case FMT_U24_BE: + case FMT_S32_LE: + case FMT_S32_BE: + case FMT_U32_LE: + case FMT_U32_BE: + DEINTERLACE_LOOP (int32_t); + break; + } +} + +#define FROM_INT_LOOP(NAME, TYPE, SWAP, OFFSET, RANGE) \ +static void NAME (const TYPE * in, float * out, int samples) \ +{ \ + const TYPE * end = in + samples; \ + while (in < end) \ + * out ++ = (TYPE) (SWAP (* in ++) - OFFSET) * (1.0 / (RANGE + 1.0)); \ +} + +#define TO_INT_LOOP(NAME, TYPE, SWAP, OFFSET, RANGE) \ +static void NAME (const float * in, TYPE * out, int samples) \ +{ \ + const float * end = in + samples; \ + while (in < end) \ + { \ + double f = (* in ++) * (RANGE + 1.0); \ + * out ++ = SWAP (OFFSET + (TYPE) round (aud::clamp<double> (f, -RANGE - 1, RANGE))); \ + } \ +} + +FROM_INT_LOOP (from_s8, int8_t, , 0x00, 0x7f) +FROM_INT_LOOP (from_u8, int8_t, , 0x80, 0x7f) + +TO_INT_LOOP (to_s8, int8_t, , 0x00, 0x7f) +TO_INT_LOOP (to_u8, int8_t, , 0x80, 0x7f) + +FROM_INT_LOOP (from_s16le, int16_t, FROM_LE16, 0x0000, 0x7fff) +FROM_INT_LOOP (from_u16le, int16_t, FROM_LE16, 0x8000, 0x7fff) +FROM_INT_LOOP (from_s24le, int32_t, FROM_LE32, 0x000000, 0x7fffff) +FROM_INT_LOOP (from_u24le, int32_t, FROM_LE32, 0x800000, 0x7fffff) +FROM_INT_LOOP (from_s32le, int32_t, FROM_LE32, 0x00000000, 0x7fffffff) +FROM_INT_LOOP (from_u32le, int32_t, FROM_LE32, 0x80000000, 0x7fffffff) + +TO_INT_LOOP (to_s16le, int16_t, TO_LE16, 0x0000, 0x7fff) +TO_INT_LOOP (to_u16le, int16_t, TO_LE16, 0x8000, 0x7fff) +TO_INT_LOOP (to_s24le, int32_t, TO_LE32, 0x000000, 0x7fffff) +TO_INT_LOOP (to_u24le, int32_t, TO_LE32, 0x800000, 0x7fffff) +TO_INT_LOOP (to_s32le, int32_t, TO_LE32, 0x00000000, 0x7fffffff) +TO_INT_LOOP (to_u32le, int32_t, TO_LE32, 0x80000000, 0x7fffffff) + +FROM_INT_LOOP (from_s16be, int16_t, FROM_BE16, 0x0000, 0x7fff) +FROM_INT_LOOP (from_u16be, int16_t, FROM_BE16, 0x8000, 0x7fff) +FROM_INT_LOOP (from_s24be, int32_t, FROM_BE32, 0x000000, 0x7fffff) +FROM_INT_LOOP (from_u24be, int32_t, FROM_BE32, 0x800000, 0x7fffff) +FROM_INT_LOOP (from_s32be, int32_t, FROM_BE32, 0x00000000, 0x7fffffff) +FROM_INT_LOOP (from_u32be, int32_t, FROM_BE32, 0x80000000, 0x7fffffff) + +TO_INT_LOOP (to_s16be, int16_t, TO_BE16, 0x0000, 0x7fff) +TO_INT_LOOP (to_u16be, int16_t, TO_BE16, 0x8000, 0x7fff) +TO_INT_LOOP (to_s24be, int32_t, TO_BE32, 0x000000, 0x7fffff) +TO_INT_LOOP (to_u24be, int32_t, TO_BE32, 0x800000, 0x7fffff) +TO_INT_LOOP (to_s32be, int32_t, TO_BE32, 0x00000000, 0x7fffffff) +TO_INT_LOOP (to_u32be, int32_t, TO_BE32, 0x80000000, 0x7fffffff) + +typedef void (* FromFunc) (const void * in, float * out, int samples); +typedef void (* ToFunc) (const float * in, void * out, int samples); + +static const struct +{ + int format; + FromFunc from; + ToFunc to; +} +convert_table [] = +{ + {FMT_S8, (FromFunc) from_s8, (ToFunc) to_s8}, + {FMT_U8, (FromFunc) from_u8, (ToFunc) to_u8}, + + {FMT_S16_LE, (FromFunc) from_s16le, (ToFunc) to_s16le}, + {FMT_U16_LE, (FromFunc) from_u16le, (ToFunc) to_u16le}, + {FMT_S24_LE, (FromFunc) from_s24le, (ToFunc) to_s24le}, + {FMT_U24_LE, (FromFunc) from_u24le, (ToFunc) to_u24le}, + {FMT_S32_LE, (FromFunc) from_s32le, (ToFunc) to_s32le}, + {FMT_U32_LE, (FromFunc) from_u32le, (ToFunc) to_u32le}, + + {FMT_S16_BE, (FromFunc) from_s16be, (ToFunc) to_s16be}, + {FMT_U16_BE, (FromFunc) from_u16be, (ToFunc) to_u16be}, + {FMT_S24_BE, (FromFunc) from_s24be, (ToFunc) to_s24be}, + {FMT_U24_BE, (FromFunc) from_u24be, (ToFunc) to_u24be}, + {FMT_S32_BE, (FromFunc) from_s32be, (ToFunc) to_s32be}, + {FMT_U32_BE, (FromFunc) from_u32be, (ToFunc) to_u32be}, +}; + +EXPORT void audio_from_int (const void * in, int format, float * out, int samples) +{ + for (auto & conv : convert_table) + { + if (conv.format == format) + { + conv.from (in, out, samples); + return; + } + } +} + +EXPORT void audio_to_int (const float * in, void * out, int format, int samples) +{ + for (auto & conv : convert_table) + { + if (conv.format == format) + { + conv.to (in, out, samples); + return; + } + } +} + +EXPORT void audio_amplify (float * data, int channels, int frames, const float * factors) +{ + float * end = data + channels * frames; + int channel; + + while (data < end) + { + for (channel = 0; channel < channels; channel ++) + { + * data = * data * factors[channel]; + data ++; + } + } +} + +EXPORT void audio_amplify (float * data, int channels, int frames, StereoVolume volume) +{ + if (channels < 1 || channels > AUD_MAX_CHANNELS) + return; + + if (volume.left == 100 && volume.right == 100) + return; + + float lfactor = 0, rfactor = 0; + float factors[AUD_MAX_CHANNELS]; + + if (volume.left > 0) + lfactor = powf (10, (float) SW_VOLUME_RANGE * (volume.left - 100) / 100 / 20); + if (volume.right > 0) + rfactor = powf (10, (float) SW_VOLUME_RANGE * (volume.right - 100) / 100 / 20); + + if (channels == 2) + { + factors[0] = lfactor; + factors[1] = rfactor; + } + else + { + for (int c = 0; c < channels; c ++) + factors[c] = aud::max (lfactor, rfactor); + } + + audio_amplify (data, channels, frames, factors); +} + +/* linear approximation of y = sin(x) */ +/* contributed by Anders Johansson */ +EXPORT void audio_soft_clip (float * data, int samples) +{ + float * end = data + samples; + + while (data < end) + { + float x = * data; + float y = fabsf (x); + + if (y <= 0.4) + ; /* (0, 0.4) -> (0, 0.4) */ + else if (y <= 0.7) + y = 0.8 * y + 0.08; /* (0.4, 0.7) -> (0.4, 0.64) */ + else if (y <= 1.0) + y = 0.7 * y + 0.15; /* (0.7, 1) -> (0.64, 0.85) */ + else if (y <= 1.3) + y = 0.4 * y + 0.45; /* (1, 1.3) -> (0.85, 0.97) */ + else if (y <= 1.5) + y = 0.15 * y + 0.775; /* (1.3, 1.5) -> (0.97, 1) */ + else + y = 1.0; /* (1.5, inf) -> 1 */ + + * data ++ = (x > 0) ? y : -y; + } +} diff --git a/src/libaudcore/audio.h.in b/src/libaudcore/audio.h.in index 2e9dd5a..e49302f 100644 --- a/src/libaudcore/audio.h.in +++ b/src/libaudcore/audio.h.in @@ -20,6 +20,8 @@ #ifndef LIBAUDCORE_AUDIO_H #define LIBAUDCORE_AUDIO_H +#define AUD_MAX_CHANNELS 10 + /* 24-bit integer samples are padded to 32-bit; high byte is always 0 */ enum { FMT_FLOAT, @@ -28,28 +30,103 @@ enum { FMT_S24_LE, FMT_S24_BE, FMT_U24_LE, FMT_U24_BE, FMT_S32_LE, FMT_S32_BE, FMT_U32_LE, FMT_U32_BE}; +struct ReplayGainInfo { + float track_gain; /* dB */ + float track_peak; /* 0-1 */ + float album_gain; /* dB */ + float album_peak; /* 0-1 */ +}; + +struct StereoVolume { + int left, right; +}; + +#ifdef WANT_AUD_BSWAP + +#include <stdint.h> + +#undef bswap16 +#undef bswap32 +#undef bswap64 + +/* GCC will optimize these to appropriate bswap instructions */ +constexpr uint16_t bswap16 (uint16_t x) + { return ((x & 0xff00) >> 8) | ((x & 0x00ff) << 8); } + +constexpr uint32_t bswap32 (uint32_t x) +{ + return ((x & 0xff000000) >> 24) | ((x & 0x00ff0000) >> 8) | + ((x & 0x0000ff00) << 8) | ((x & 0x000000ff) << 24); +} + +constexpr uint64_t bswap64 (uint64_t x) +{ + return ((x & 0xff00000000000000) >> 56) | ((x & 0x00ff000000000000) >> 40) | + ((x & 0x0000ff0000000000) >> 24) | ((x & 0x000000ff00000000) >> 8) | + ((x & 0x00000000ff000000) << 8) | ((x & 0x0000000000ff0000) << 24) | + ((x & 0x000000000000ff00) << 40) | ((x & 0x00000000000000ff) << 56); +} + +#endif // WANT_AUD_BSWAP + #if @BIGENDIAN@ + #define FMT_S16_NE FMT_S16_BE #define FMT_U16_NE FMT_U16_BE #define FMT_S24_NE FMT_S24_BE #define FMT_U24_NE FMT_U24_BE #define FMT_S32_NE FMT_S32_BE #define FMT_U32_NE FMT_U32_BE -#else + +#ifdef WANT_AUD_BSWAP +#define FROM_BE16(x) (x) +#define FROM_BE32(x) (x) +#define FROM_BE64(x) (x) +#define FROM_LE16(x) (bswap16 (x)) +#define FROM_LE32(x) (bswap32 (x)) +#define FROM_LE64(x) (bswap64 (x)) +#define TO_BE16(x) (x) +#define TO_BE32(x) (x) +#define TO_BE64(x) (x) +#define TO_LE16(x) (bswap16 (x)) +#define TO_LE32(x) (bswap32 (x)) +#define TO_LE64(x) (bswap64 (x)) +#endif + +#else // ! BIGENDIAN + #define FMT_S16_NE FMT_S16_LE #define FMT_U16_NE FMT_U16_LE #define FMT_S24_NE FMT_S24_LE #define FMT_U24_NE FMT_U24_LE #define FMT_S32_NE FMT_S32_LE #define FMT_U32_NE FMT_U32_LE + +#ifdef WANT_AUD_BSWAP +#define FROM_BE16(x) (bswap16 (x)) +#define FROM_BE32(x) (bswap32 (x)) +#define FROM_BE64(x) (bswap64 (x)) +#define FROM_LE16(x) (x) +#define FROM_LE32(x) (x) +#define FROM_LE64(x) (x) +#define TO_BE16(x) (bswap16 (x)) +#define TO_BE32(x) (bswap32 (x)) +#define TO_BE64(x) (bswap64 (x)) +#define TO_LE16(x) (x) +#define TO_LE32(x) (x) +#define TO_LE64(x) (x) +#endif + #endif #define FMT_SIZEOF(f) ((f) == FMT_FLOAT ? sizeof (float) : (f) <= FMT_U8 ? 1 : (f) <= FMT_U16_BE ? 2 : 4) void audio_interlace (const void * const * in, int format, int channels, void * out, int frames); +void audio_deinterlace (const void * in, int format, int channels, void * const * out, int frames); void audio_from_int (const void * in, int format, float * out, int samples); void audio_to_int (const float * in, void * out, int format, int samples); -void audio_amplify (float * data, int channels, int frames, float * factors); +void audio_amplify (float * data, int channels, int frames, const float * factors); +void audio_amplify (float * data, int channels, int frames, StereoVolume volume); void audio_soft_clip (float * data, int samples); #endif /* LIBAUDCORE_AUDIO_H */ diff --git a/src/libaudcore/audstrings.c b/src/libaudcore/audstrings.c deleted file mode 100644 index c1d878b..0000000 --- a/src/libaudcore/audstrings.c +++ /dev/null @@ -1,784 +0,0 @@ -/* - * audstrings.c - * Copyright 2009-2012 John Lindgren and William Pitcock - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <limits.h> -#include <math.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <glib.h> - -#include <audacious/i18n.h> - -#include "audstrings.h" -#include "index.h" - -static const char ascii_to_hex[256] = { - ['0'] = 0x0, ['1'] = 0x1, ['2'] = 0x2, ['3'] = 0x3, ['4'] = 0x4, - ['5'] = 0x5, ['6'] = 0x6, ['7'] = 0x7, ['8'] = 0x8, ['9'] = 0x9, - ['a'] = 0xa, ['b'] = 0xb, ['c'] = 0xc, ['d'] = 0xd, ['e'] = 0xe, ['f'] = 0xf, - ['A'] = 0xa, ['B'] = 0xb, ['C'] = 0xc, ['D'] = 0xd, ['E'] = 0xe, ['F'] = 0xf -}; - -static const char hex_to_ascii[16] = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' -}; - -static const char uri_legal_table[256] = { - ['0'] = 1, ['1'] = 1, ['2'] = 1, ['3'] = 1, ['4'] = 1, - ['5'] = 1, ['6'] = 1, ['7'] = 1, ['8'] = 1, ['9'] = 1, - ['a'] = 1, ['b'] = 1, ['c'] = 1, ['d'] = 1, ['e'] = 1, ['f'] = 1, ['g'] = 1, - ['h'] = 1, ['i'] = 1, ['j'] = 1, ['k'] = 1, ['l'] = 1, ['m'] = 1, ['n'] = 1, - ['o'] = 1, ['p'] = 1, ['q'] = 1, ['r'] = 1, ['s'] = 1, ['t'] = 1, ['u'] = 1, - ['v'] = 1, ['w'] = 1, ['x'] = 1, ['y'] = 1, ['z'] = 1, - ['A'] = 1, ['B'] = 1, ['C'] = 1, ['D'] = 1, ['E'] = 1, ['F'] = 1, ['G'] = 1, - ['H'] = 1, ['I'] = 1, ['J'] = 1, ['K'] = 1, ['L'] = 1, ['M'] = 1, ['N'] = 1, - ['O'] = 1, ['P'] = 1, ['Q'] = 1, ['R'] = 1, ['S'] = 1, ['T'] = 1, ['U'] = 1, - ['V'] = 1, ['W'] = 1, ['X'] = 1, ['Y'] = 1, ['Z'] = 1, - ['-'] = 1, ['_'] = 1, ['.'] = 1, ['~'] = 1, ['/'] = 1 -}; - -static const char swap_case[256] = - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0abcdefghijklmnopqrstuvwxyz\0\0\0\0\0" - "\0ABCDEFGHIJKLMNOPQRSTUVWXYZ\0\0\0\0\0"; - -#define FROM_HEX(c) (ascii_to_hex[(unsigned char) (c)]) -#define TO_HEX(i) (hex_to_ascii[(i) & 15]) -#define IS_LEGAL(c) (uri_legal_table[(unsigned char) (c)]) -#define SWAP_CASE(c) (swap_case[(unsigned char) (c)]) - -EXPORT char * str_printf (const char * format, ...) -{ - va_list args; - va_start (args, format); - - char * str = str_vprintf (format, args); - - va_end (args); - return str; -} - -EXPORT char * str_vprintf (const char * format, va_list args) -{ - VSPRINTF (buf, format, args); - return str_get (buf); -} - -EXPORT bool_t str_has_prefix_nocase (const char * str, const char * prefix) -{ - return ! g_ascii_strncasecmp (str, prefix, strlen (prefix)); -} - -EXPORT bool_t str_has_suffix_nocase (const char * str, const char * suffix) -{ - int len1 = strlen (str); - int len2 = strlen (suffix); - - if (len2 > len1) - return FALSE; - - return ! g_ascii_strcasecmp (str + len1 - len2, suffix); -} - -EXPORT char * strstr_nocase (const char * haystack, const char * needle) -{ - while (1) - { - const char * ap = haystack; - const char * bp = needle; - - while (1) - { - char a = * ap ++; - char b = * bp ++; - - if (! b) /* all of needle matched */ - return (char *) haystack; - if (! a) /* end of haystack reached */ - return NULL; - - if (a != b && a != SWAP_CASE (b)) - break; - } - - haystack ++; - } -} - -EXPORT char * strstr_nocase_utf8 (const char * haystack, const char * needle) -{ - while (1) - { - const char * ap = haystack; - const char * bp = needle; - - while (1) - { - gunichar a = g_utf8_get_char (ap); - gunichar b = g_utf8_get_char (bp); - - if (! b) /* all of needle matched */ - return (char *) haystack; - if (! a) /* end of haystack reached */ - return NULL; - - if (a != b && (a < 128 ? SWAP_CASE (a) != b : - g_unichar_tolower (a) != g_unichar_tolower (b))) - break; - - ap = g_utf8_next_char (ap); - bp = g_utf8_next_char (bp); - } - - haystack = g_utf8_next_char (haystack); - } -} - -EXPORT char * str_tolower_utf8 (const char * str) -{ - char buf[6 * strlen (str) + 1]; - const char * get = str; - char * set = buf; - gunichar c; - - while ((c = g_utf8_get_char (get))) - { - if (c < 128) - * set ++ = g_ascii_tolower (c); - else - set += g_unichar_to_utf8 (g_unichar_tolower (c), set); - - get = g_utf8_next_char (get); - } - - * set = 0; - - return str_get (buf); -} - -EXPORT void str_replace_char (char * string, char old_c, char new_c) -{ - while ((string = strchr (string, old_c))) - * string ++ = new_c; -} - -EXPORT void str_itoa (int x, char * buf, int bufsize) -{ - if (! bufsize) - return; - - if (x < 0) - { - if (bufsize > 1) - { - * buf ++ = '-'; - bufsize --; - } - - x = -x; - } - - char * rev = buf + bufsize - 1; - * rev = 0; - - while (rev > buf) - { - * (-- rev) = '0' + x % 10; - if (! (x /= 10)) - break; - } - - while ((* buf ++ = * rev ++)); -} - -/* Percent-decodes up to <len> bytes of <str> to <out>, which must be large - * enough to hold the decoded string (i.e., (len + 1) bytes). If <len> is - * negative, decodes all of <str>. */ - -EXPORT void str_decode_percent (const char * str, int len, char * out) -{ - const char * nul; - - if (len < 0) - len = strlen (str); - else if ((nul = memchr (str, 0, len))) - len = nul - str; - - while (1) - { - const char * p = memchr (str, '%', len); - if (! p) - break; - - int block = p - str; - memmove (out, str, block); - - str += block; - out += block; - len -= block; - - if (len < 3) - break; - - * out ++ = (FROM_HEX (str[1]) << 4) | FROM_HEX (str[2]); - - str += 3; - len -= 3; - } - - memmove (out, str, len); - out[len] = 0; -} - -/* Percent-encodes up to <len> bytes of <str> to <out>, which must be large - * enough to hold the encoded string (i.e., (3 * len + 1) bytes). If <len> is - * negative, decodes all of <str>. */ - -EXPORT void str_encode_percent (const char * str, int len, char * out) -{ - const char * nul; - - if (len < 0) - len = strlen (str); - else if ((nul = memchr (str, 0, len))) - len = nul - str; - - while (len --) - { - char c = * str ++; - - if (IS_LEGAL (c)) - * out ++ = c; - else - { - * out ++ = '%'; - * out ++ = TO_HEX ((unsigned char) c >> 4); - * out ++ = TO_HEX (c & 0xF); - } - } - - * out = 0; -} - -EXPORT void filename_normalize (char * filename) -{ -#ifdef _WIN32 - /* convert slash to backslash on Windows */ - str_replace_char (filename, '/', '\\'); -#endif - - /* remove trailing slash */ - int len = strlen (filename); -#ifdef _WIN32 - if (len > 3 && filename[len - 1] == '\\') /* leave "C:\" */ -#else - if (len > 1 && filename[len - 1] == '/') /* leave leading "/" */ -#endif - filename[len - 1] = 0; -} - -EXPORT char * filename_build (const char * path, const char * name) -{ - int len = strlen (path); - -#ifdef _WIN32 - if (! len || path[len - 1] == '/' || path[len - 1] == '\\') - { - SCONCAT2 (filename, path, name); - return str_get (filename); - } - - SCONCAT3 (filename, path, "\\", name); - return str_get (filename); -#else - if (! len || path[len - 1] == '/') - { - SCONCAT2 (filename, path, name); - return str_get (filename); - } - - SCONCAT3 (filename, path, "/", name); - return str_get (filename); -#endif -} - -#ifdef _WIN32 -#define URI_PREFIX "file:///" -#define URI_PREFIX_LEN 8 -#else -#define URI_PREFIX "file://" -#define URI_PREFIX_LEN 7 -#endif - -/* Like g_filename_to_uri, but converts the filename from the system locale to - * UTF-8 before percent-encoding (except on Windows, where filenames are assumed - * to be UTF-8). On Windows, replaces '\' with '/' and adds a leading '/'. */ - -EXPORT char * filename_to_uri (const char * name) -{ -#ifdef _WIN32 - SCOPY (utf8, name); - str_replace_char (utf8, '\\', '/'); -#else - char * utf8 = str_from_locale (name, -1); - if (! utf8) - return NULL; -#endif - - char enc[URI_PREFIX_LEN + 3 * strlen (utf8) + 1]; - strcpy (enc, URI_PREFIX); - str_encode_percent (utf8, -1, enc + URI_PREFIX_LEN); - -#ifndef _WIN32 - str_unref (utf8); -#endif - - return str_get (enc); -} - -/* Like g_filename_from_uri, but converts the filename from UTF-8 to the system - * locale after percent-decoding (except on Windows, where filenames are assumed - * to be UTF-8). On Windows, strips the leading '/' and replaces '/' with '\'. */ - -EXPORT char * uri_to_filename (const char * uri) -{ - if (strncmp (uri, URI_PREFIX, URI_PREFIX_LEN)) - return NULL; - - char buf[strlen (uri + URI_PREFIX_LEN) + 1]; - str_decode_percent (uri + URI_PREFIX_LEN, -1, buf); - - filename_normalize (buf); - -#ifdef _WIN32 - return str_get (buf); -#else - return str_to_locale (buf, -1); -#endif -} - -/* Formats a URI for human-readable display. Percent-decodes and, for file:// - * URI's, converts to filename format, but in UTF-8. */ - -EXPORT char * uri_to_display (const char * uri) -{ - if (! strncmp (uri, "cdda://?", 8)) - return str_printf (_("Audio CD, track %s"), uri + 8); - - char buf[strlen (uri) + 1]; - - if (! strncmp (uri, URI_PREFIX, URI_PREFIX_LEN)) - { - str_decode_percent (uri + URI_PREFIX_LEN, -1, buf); -#ifdef _WIN32 - str_replace_char (buf, '/', '\\'); -#endif - } - else - str_decode_percent (uri, -1, buf); - - return str_get (buf); -} - -#undef URI_PREFIX -#undef URI_PREFIX_LEN - -EXPORT void uri_parse (const char * uri, const char * * base_p, const char * * ext_p, - const char * * sub_p, int * isub_p) -{ - const char * end = uri + strlen (uri); - const char * base, * ext, * sub, * c; - int isub = 0; - char junk; - - if ((c = strrchr (uri, '/'))) - base = c + 1; - else - base = end; - - if ((c = strrchr (base, '?')) && sscanf (c + 1, "%d%c", & isub, & junk) == 1) - sub = c; - else - sub = end; - - SNCOPY (buf, base, sub - base); - - if ((c = strrchr (buf, '.'))) - ext = base + (c - buf); - else - ext = sub; - - if (base_p) - * base_p = base; - if (ext_p) - * ext_p = ext; - if (sub_p) - * sub_p = sub; - if (isub_p) - * isub_p = isub; -} - -EXPORT bool_t uri_get_extension (const char * uri, char * buf, int buflen) -{ - const char * ext; - uri_parse (uri, NULL, & ext, NULL, NULL); - - if (ext[0] != '.') - return FALSE; - - strncpy (buf, ext + 1, buflen - 1); - buf[buflen - 1] = 0; - - /* remove subtunes and HTTP query strings */ - char * qmark; - if ((qmark = strchr (buf, '?'))) - * qmark = 0; - - return (buf[0] != 0); -} - -/* Like strcasecmp, but orders numbers correctly (2 before 10). */ -/* Non-ASCII characters are treated exactly as is. */ -/* Handles NULL gracefully. */ - -EXPORT int str_compare (const char * ap, const char * bp) -{ - if (ap == NULL) - return (bp == NULL) ? 0 : -1; - if (bp == NULL) - return 1; - - unsigned char a = * ap ++, b = * bp ++; - for (; a || b; a = * ap ++, b = * bp ++) - { - if (a > '9' || b > '9' || a < '0' || b < '0') - { - if (a <= 'Z' && a >= 'A') - a += 'a' - 'A'; - if (b <= 'Z' && b >= 'A') - b += 'a' - 'A'; - - if (a > b) - return 1; - if (a < b) - return -1; - } - else - { - int x = a - '0'; - for (; (a = * ap) <= '9' && a >= '0'; ap ++) - x = 10 * x + (a - '0'); - - int y = b - '0'; - for (; (b = * bp) >= '0' && b <= '9'; bp ++) - y = 10 * y + (b - '0'); - - if (x > y) - return 1; - if (x < y) - return -1; - } - } - - return 0; -} - -/* Decodes percent-encoded strings, then compares then with str_compare. */ - -EXPORT int str_compare_encoded (const char * ap, const char * bp) -{ - if (ap == NULL) - return (bp == NULL) ? 0 : -1; - if (bp == NULL) - return 1; - - unsigned char a = * ap ++, b = * bp ++; - for (; a || b; a = * ap ++, b = * bp ++) - { - if (a == '%' && ap[0] && ap[1]) - { - a = (FROM_HEX (ap[0]) << 4) | FROM_HEX (ap[1]); - ap += 2; - } - if (b == '%' && bp[0] && bp[1]) - { - b = (FROM_HEX (bp[0]) << 4) | FROM_HEX (bp[1]); - bp += 2; - } - - if (a > '9' || b > '9' || a < '0' || b < '0') - { - if (a <= 'Z' && a >= 'A') - a += 'a' - 'A'; - if (b <= 'Z' && b >= 'A') - b += 'a' - 'A'; - - if (a > b) - return 1; - if (a < b) - return -1; - } - else - { - int x = a - '0'; - for (; (a = * ap) <= '9' && a >= '0'; ap ++) - x = 10 * x + (a - '0'); - - int y = b - '0'; - for (; (b = * bp) >= '0' && b <= '9'; bp ++) - y = 10 * y + (b - '0'); - - if (x > y) - return 1; - if (x < y) - return -1; - } - } - - return 0; -} - -EXPORT Index * str_list_to_index (const char * list, const char * delims) -{ - char dmap[256] = {0}; - - for (; * delims; delims ++) - dmap[(unsigned char) (* delims)] = 1; - - Index * index = index_new (); - const char * word = NULL; - - for (; * list; list ++) - { - if (dmap[(unsigned char) (* list)]) - { - if (word) - { - index_insert (index, -1, str_nget (word, list - word)); - word = NULL; - } - } - else - { - if (! word) - { - word = list; - } - } - } - - if (word) - index_insert (index, -1, str_get (word)); - - return index; -} - -EXPORT char * index_to_str_list (Index * index, const char * sep) -{ - int count = index_count (index); - int seplen = strlen (sep); - int total = count ? seplen * (count - 1) : 0; - int lengths[count]; - - for (int i = 0; i < count; i ++) - { - lengths[i] = strlen (index_get (index, i)); - total += lengths[i]; - } - - char buf[total + 1]; - int pos = 0; - - for (int i = 0; i < count; i ++) - { - if (i) - { - strcpy (buf + pos, sep); - pos += seplen; - } - - strcpy (buf + pos, index_get (index, i)); - pos += lengths[i]; - } - - buf[pos] = 0; - - return str_get (buf); -} - -/* - * Routines to convert numbers between string and binary representations. - * - * Goals: - * - * - Accuracy, meaning that we can convert back and forth between string and - * binary without the number changing slightly each time. - * - Consistency, meaning that we get the same results no matter what - * architecture or locale we have to deal with. - * - Readability, meaning that the number one is rendered "1", not "1.000". - * - * Values between -1,000,000,000 and 1,000,000,000 (inclusive) are guaranteed to - * have an accuracy of 6 decimal places. - */ - -EXPORT int str_to_int (const char * string) -{ - bool_t neg = (string[0] == '-'); - if (neg) - string ++; - - int val = 0; - char c; - - while ((c = * string ++) && c >= '0' && c <= '9') - val = val * 10 + (c - '0'); - - return neg ? -val : val; -} - -EXPORT double str_to_double (const char * string) -{ - bool_t neg = (string[0] == '-'); - if (neg) - string ++; - - double val = str_to_int (string); - const char * p = strchr (string, '.'); - - if (p) - { - char buf[7] = "000000"; - const char * nul = memchr (p + 1, 0, 6); - memcpy (buf, p + 1, nul ? nul - (p + 1) : 6); - val += (double) str_to_int (buf) / 1000000; - } - - return neg ? -val : val; -} - -EXPORT char * int_to_str (int val) -{ - char buf[16]; - str_itoa (val, buf, sizeof buf); - return str_get (buf); -} - -EXPORT char * double_to_str (double val) -{ - bool_t neg = (val < 0); - if (neg) - val = -val; - - int i = floor (val); - int f = round ((val - i) * 1000000); - - if (f == 1000000) - { - i ++; - f = 0; - } - - SPRINTF (buf, "%s%d.%06d", neg ? "-" : "", i, f); - - char * c = buf + strlen (buf); - while (* (c - 1) == '0') - c --; - if (* (c - 1) == '.') - c --; - * c = 0; - - return str_get (buf); -} - -EXPORT bool_t str_to_int_array (const char * string, int * array, int count) -{ - Index * index = str_list_to_index (string, ", "); - bool_t okay = (index_count (index) == count); - - if (okay) - { - for (int i = 0; i < count; i ++) - array[i] = str_to_int (index_get (index, i)); - } - - index_free_full (index, (IndexFreeFunc) str_unref); - return okay; -} - -EXPORT char * int_array_to_str (const int * array, int count) -{ - Index * index = index_new (); - - for (int i = 0; i < count; i ++) - { - char * value = int_to_str (array[i]); - if (! value) - goto ERR; - - index_insert (index, -1, value); - } - - char * string = index_to_str_list (index, ","); - index_free_full (index, (IndexFreeFunc) str_unref); - return string; - -ERR: - index_free_full (index, (IndexFreeFunc) str_unref); - return NULL; -} - -EXPORT bool_t str_to_double_array (const char * string, double * array, int count) -{ - Index * index = str_list_to_index (string, ", "); - bool_t okay = (index_count (index) == count); - - if (okay) - { - for (int i = 0; i < count; i ++) - array[i] = str_to_double (index_get (index, i)); - } - - index_free_full (index, (IndexFreeFunc) str_unref); - return okay; -} - -EXPORT char * double_array_to_str (const double * array, int count) -{ - Index * index = index_new (); - - for (int i = 0; i < count; i ++) - { - char * value = double_to_str (array[i]); - if (! value) - goto ERR; - - index_insert (index, -1, value); - } - - char * string = index_to_str_list (index, ","); - index_free_full (index, (IndexFreeFunc) str_unref); - return string; - -ERR: - index_free_full (index, (IndexFreeFunc) str_unref); - return NULL; -} diff --git a/src/libaudcore/audstrings.cc b/src/libaudcore/audstrings.cc new file mode 100644 index 0000000..04309d4 --- /dev/null +++ b/src/libaudcore/audstrings.cc @@ -0,0 +1,974 @@ +/* + * audstrings.c + * Copyright 2009-2012 John Lindgren and William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "audstrings.h" + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <new> + +#include <glib.h> + +#include "i18n.h" +#include "index.h" +#include "internal.h" +#include "runtime.h" + +static const char ascii_to_hex[256] = + "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" + "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" + "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" + "\x0\x1\x2\x3\x4\x5\x6\x7\x8\x9\x0\x0\x0\x0\x0\x0" + "\x0\xa\xb\xc\xd\xe\xf\x0\x0\x0\x0\x0\x0\x0\x0\x0" + "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" + "\x0\xa\xb\xc\xd\xe\xf\x0\x0\x0\x0\x0\x0\x0\x0\x0" + "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0"; + +static const char hex_to_ascii[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' +}; + +static const char uri_legal_table[256] = + "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" + "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" + "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x1\x1\x1" // '-' '.' '/' + "\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x0\x0\x0\x0\x0\x0" // 0-9 + "\x0\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1" // A-O + "\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x0\x0\x0\x0\x1" // P-Z '_' + "\x0\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1" // a-o + "\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x0\x0\x0\x1\x0"; // p-z '~' + +static const char swap_case[256] = + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0abcdefghijklmnopqrstuvwxyz\0\0\0\0\0" + "\0ABCDEFGHIJKLMNOPQRSTUVWXYZ\0\0\0\0\0"; + +#define FROM_HEX(c) (ascii_to_hex[(unsigned char) (c)]) +#define TO_HEX(i) (hex_to_ascii[(i) & 15]) +#define IS_LEGAL(c) (uri_legal_table[(unsigned char) (c)]) +#define SWAP_CASE(c) (swap_case[(unsigned char) (c)]) + +/* strcmp() that handles nullptr safely */ +EXPORT int strcmp_safe (const char * a, const char * b, int len) +{ + if (! a) + return b ? -1 : 0; + if (! b) + return 1; + + return len < 0 ? strcmp (a, b) : strncmp (a, b, len); +} + +/* ASCII version of strcasecmp, also handles nullptr safely */ +EXPORT int strcmp_nocase (const char * a, const char * b, int len) +{ + if (! a) + return b ? -1 : 0; + if (! b) + return 1; + + return len < 0 ? g_ascii_strcasecmp (a, b) : g_ascii_strncasecmp (a, b, len); +} + +/* strlen() if <len> is negative, otherwise strnlen() */ +EXPORT int strlen_bounded (const char * s, int len) +{ + if (len < 0) + return strlen (s); + + const char * nul = (const char *) memchr (s, 0, len); + if (nul) + return nul - s; + + return len; +} + +EXPORT StringBuf str_copy (const char * s, int len) +{ + if (len < 0) + len = strlen (s); + + StringBuf str (len); + memcpy (str, s, len); + return str; +} + +EXPORT StringBuf str_concat (const std::initializer_list<const char *> & strings) +{ + StringBuf str (-1); + char * set = str; + int left = str.len (); + + for (const char * s : strings) + { + int len = strlen (s); + if (len > left) + throw std::bad_alloc (); + + memcpy (set, s, len); + + set += len; + left -= len; + } + + str.resize (set - str); + return str; +} + +EXPORT StringBuf str_printf (const char * format, ...) +{ + va_list args; + va_start (args, format); + StringBuf str = str_vprintf (format, args); + va_end (args); + return str; +} + +EXPORT StringBuf str_vprintf (const char * format, va_list args) +{ + StringBuf str (-1); + int len = vsnprintf (str, str.len (), format, args); + str.resize (len); + return str; +} + +EXPORT bool str_has_prefix_nocase (const char * str, const char * prefix) +{ + return ! g_ascii_strncasecmp (str, prefix, strlen (prefix)); +} + +EXPORT bool str_has_suffix_nocase (const char * str, const char * suffix) +{ + int len1 = strlen (str); + int len2 = strlen (suffix); + + if (len2 > len1) + return false; + + return ! g_ascii_strcasecmp (str + len1 - len2, suffix); +} + +/* Bernstein's hash function (unrolled version): + * h(0) = 5381 + * h(n) = 33 * h(n-1) + c + * + * This function is more than twice as fast as g_str_hash (a simpler version of + * Bernstein's hash) and even slightly faster than Murmur 3. */ + +EXPORT unsigned str_calc_hash (const char * s) +{ + unsigned h = 5381; + + int len = strlen (s); + + while (len >= 8) + { + h = h * 1954312449 + + s[0] * 3963737313 + + s[1] * 1291467969 + + s[2] * 39135393 + + s[3] * 1185921 + + s[4] * 35937 + + s[5] * 1089 + + s[6] * 33 + + s[7]; + + s += 8; + len -= 8; + } + + if (len >= 4) + { + h = h * 1185921 + + s[0] * 35937 + + s[1] * 1089 + + s[2] * 33 + + s[3]; + + s += 4; + len -= 4; + } + + switch (len) + { + case 3: h = h * 33 + (* s ++); + case 2: h = h * 33 + (* s ++); + case 1: h = h * 33 + (* s ++); + } + + return h; +} + +EXPORT const char * strstr_nocase (const char * haystack, const char * needle) +{ + while (1) + { + const char * ap = haystack; + const char * bp = needle; + + while (1) + { + char a = * ap ++; + char b = * bp ++; + + if (! b) /* all of needle matched */ + return (char *) haystack; + if (! a) /* end of haystack reached */ + return nullptr; + + if (a != b && a != SWAP_CASE (b)) + break; + } + + haystack ++; + } +} + +EXPORT const char * strstr_nocase_utf8 (const char * haystack, const char * needle) +{ + while (1) + { + const char * ap = haystack; + const char * bp = needle; + + while (1) + { + gunichar a = g_utf8_get_char (ap); + gunichar b = g_utf8_get_char (bp); + + if (! b) /* all of needle matched */ + return (char *) haystack; + if (! a) /* end of haystack reached */ + return nullptr; + + if (a != b && (a < 128 ? (gunichar) SWAP_CASE (a) != b : + g_unichar_tolower (a) != g_unichar_tolower (b))) + break; + + ap = g_utf8_next_char (ap); + bp = g_utf8_next_char (bp); + } + + haystack = g_utf8_next_char (haystack); + } +} + +EXPORT StringBuf str_tolower (const char * str) +{ + StringBuf buf (strlen (str)); + char * set = buf; + + while (* str) + * set ++ = g_ascii_tolower (* str ++); + + return buf; +} + +EXPORT StringBuf str_tolower_utf8 (const char * str) +{ + StringBuf buf (6 * strlen (str)); + char * set = buf; + gunichar c; + + while ((c = g_utf8_get_char (str))) + { + if (c < 128) + * set ++ = g_ascii_tolower (c); + else + set += g_unichar_to_utf8 (g_unichar_tolower (c), set); + + str = g_utf8_next_char (str); + } + + buf.resize (set - buf); + return buf; +} + +EXPORT void str_replace_char (char * string, char old_c, char new_c) +{ + while ((string = strchr (string, old_c))) + * string ++ = new_c; +} + +/* Percent-decodes <len> bytes of <str>. If <len> is negative, decodes all of <str>. */ + +EXPORT StringBuf str_decode_percent (const char * str, int len) +{ + if (len < 0) + len = strlen (str); + + StringBuf buf (len); + char * out = buf; + + while (1) + { + const char * p = (const char *) memchr (str, '%', len); + if (! p) + break; + + int block = p - str; + memcpy (out, str, block); + + str += block; + out += block; + len -= block; + + if (len < 3) + break; + + * out ++ = (FROM_HEX (str[1]) << 4) | FROM_HEX (str[2]); + + str += 3; + len -= 3; + } + + memcpy (out, str, len); + buf.resize (out + len - buf); + return buf; +} + +/* Percent-encodes <len> bytes of <str>. If <len> is negative, decodes all of <str>. */ + +EXPORT StringBuf str_encode_percent (const char * str, int len) +{ + if (len < 0) + len = strlen (str); + + StringBuf buf (3 * len); + char * out = buf; + + while (len --) + { + char c = * str ++; + + if (IS_LEGAL (c)) + * out ++ = c; + else + { + * out ++ = '%'; + * out ++ = TO_HEX ((unsigned char) c >> 4); + * out ++ = TO_HEX (c & 0xF); + } + } + + buf.resize (out - buf); + return buf; +} + +EXPORT StringBuf filename_normalize (StringBuf && filename) +{ + int len; + char * s; + +#ifdef _WIN32 + /* convert slash to backslash on Windows */ + str_replace_char (filename, '/', '\\'); +#endif + + /* remove current directory (".") elements */ + while ((len = filename.len ()) >= 2 && + (! strcmp ((s = filename + len - 2), G_DIR_SEPARATOR_S ".") || + (s = strstr (filename, G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S)))) + filename.remove (s + 1 - filename, aud::min (s + 3, filename + len) - (s + 1)); + + /* remove parent directory ("..") elements */ + while ((len = filename.len ()) >= 3 && + (! strcmp ((s = filename + len - 3), G_DIR_SEPARATOR_S "..") || + (s = strstr (filename, G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S)))) + { + * s = 0; + char * s2 = strrchr (filename, G_DIR_SEPARATOR); + if (! s2) + * (s2 = s) = G_DIR_SEPARATOR; + + filename.remove (s2 + 1 - filename, aud::min (s + 4, filename + len) - (s2 + 1)); + } + + /* remove trailing slash */ +#ifdef _WIN32 + if ((len = filename.len ()) > 3 && filename[len - 1] == '\\') /* leave "C:\" */ +#else + if ((len = filename.len ()) > 1 && filename[len - 1] == '/') /* leave leading "/" */ +#endif + filename.resize (len - 1); + + return std::move (filename); +} + +EXPORT StringBuf filename_build (const std::initializer_list<const char *> & elems) +{ + StringBuf str (-1); + char * set = str; + int left = str.len (); + + for (const char * s : elems) + { +#ifdef _WIN32 + if (set > str && set[-1] != '/' && set[-1] != '\\') + { + if (! left) + throw std::bad_alloc (); + + * set ++ = '\\'; + left --; + } +#else + if (set > str && set[-1] != '/') + { + if (! left) + throw std::bad_alloc (); + + * set ++ = '/'; + left --; + } +#endif + + int len = strlen (s); + if (len > left) + throw std::bad_alloc (); + + memcpy (set, s, len); + + set += len; + left -= len; + } + + str.resize (set - str); + return str; +} + +#ifdef _WIN32 +#define URI_PREFIX "file:///" +#define URI_PREFIX_LEN 8 +#else +#define URI_PREFIX "file://" +#define URI_PREFIX_LEN 7 +#endif + +/* Like g_filename_to_uri, but converts the filename from the system locale to + * UTF-8 before percent-encoding (except on Windows, where filenames are assumed + * to be UTF-8). On Windows, replaces '\' with '/' and adds a leading '/'. */ + +EXPORT StringBuf filename_to_uri (const char * name) +{ +#ifdef _WIN32 + StringBuf buf = str_copy (name); + str_replace_char (buf, '\\', '/'); +#else + StringBuf buf; + + /* convert from locale if: + * 1) system locale is not UTF-8, and + * 2) filename is not already valid UTF-8 */ + if (! g_get_charset (nullptr) && ! g_utf8_validate (name, -1, nullptr)) + buf.steal (str_from_locale (name)); + + if (! buf) + buf.steal (str_copy (name)); +#endif + + buf.steal (str_encode_percent (buf)); + buf.insert (0, URI_PREFIX); + return buf; +} + +/* Like g_filename_from_uri, but converts the filename from UTF-8 to the system + * locale after percent-decoding (except on Windows, where filenames are assumed + * to be UTF-8). On Windows, strips the leading '/' and replaces '/' with '\'. */ + +EXPORT StringBuf uri_to_filename (const char * uri, bool use_locale) +{ + if (strncmp (uri, URI_PREFIX, URI_PREFIX_LEN)) + return StringBuf (); + + StringBuf buf = str_decode_percent (uri + URI_PREFIX_LEN); + +#ifndef _WIN32 + /* convert to locale if: + * 1) use_locale flag was not set to false, and + * 2) system locale is not UTF-8, and + * 3) decoded URI is valid UTF-8 */ + if (use_locale && ! g_get_charset (nullptr) && g_utf8_validate (buf, buf.len (), nullptr)) + { + StringBuf locale = str_to_locale (buf); + if (locale) + buf.steal (std::move (locale)); + } +#endif + + return filename_normalize (std::move (buf)); +} + +/* Formats a URI for human-readable display. Percent-decodes and, for file:// + * URI's, converts to filename format, but in UTF-8. */ + +EXPORT StringBuf uri_to_display (const char * uri) +{ + if (! strncmp (uri, "cdda://?", 8)) + return str_printf (_("Audio CD, track %s"), uri + 8); + + StringBuf buf = str_to_utf8 (str_decode_percent (uri)); + if (! buf) + return str_copy (_("(character encoding error)")); + + if (strncmp (buf, URI_PREFIX, URI_PREFIX_LEN)) + return buf; + + buf.remove (0, URI_PREFIX_LEN); + buf.steal (filename_normalize (std::move (buf))); + + const char * home = get_home_utf8 (); + int homelen = home ? strlen (home) : 0; + + if (homelen && ! strncmp (buf, home, homelen) && buf[homelen] == G_DIR_SEPARATOR) + { + buf[0] = '~'; + buf.remove (1, homelen - 1); + } + + return buf; +} + +#undef URI_PREFIX +#undef URI_PREFIX_LEN + +EXPORT void uri_parse (const char * uri, const char * * base_p, const char * * ext_p, + const char * * sub_p, int * isub_p) +{ + const char * end = uri + strlen (uri); + const char * base, * ext, * sub, * c; + int isub = 0; + char junk; + + if ((c = strrchr (uri, '/'))) + base = c + 1; + else + base = end; + + if ((c = strrchr (base, '?')) && sscanf (c + 1, "%d%c", & isub, & junk) == 1) + sub = c; + else + sub = end; + + if ((c = strrchr (base, '.')) && c < sub) + ext = c; + else + ext = sub; + + if (base_p) + * base_p = base; + if (ext_p) + * ext_p = ext; + if (sub_p) + * sub_p = sub; + if (isub_p) + * isub_p = isub; +} + +EXPORT StringBuf uri_get_scheme (const char * uri) +{ + const char * delim = strstr (uri, "://"); + return delim ? str_copy (uri, delim - uri) : StringBuf (); +} + +EXPORT StringBuf uri_get_extension (const char * uri) +{ + const char * ext; + uri_parse (uri, nullptr, & ext, nullptr, nullptr); + + if (ext[0] != '.') + return StringBuf (); + + ext ++; // skip period + + // remove subtunes and HTTP query strings + const char * qmark = strchr (ext, '?'); + return str_copy (ext, qmark ? qmark - ext : -1); +} + +/* Constructs a full URI given: + * 1. path: one of the following: + * a. a full URI (returned unchanged) + * b. an absolute filename (in the system locale) + * c. a relative path (character set detected according to user settings) + * 2. reference: the full URI of the playlist containing <path> */ + +EXPORT StringBuf uri_construct (const char * path, const char * reference) +{ + /* URI */ + if (strstr (path, "://")) + return str_copy (path); + + /* absolute filename */ +#ifdef _WIN32 + if (path[0] && path[1] == ':' && path[2] == '\\') +#else + if (path[0] == '/') +#endif + return filename_to_uri (path); + + /* relative path */ + const char * slash = strrchr (reference, '/'); + if (! slash) + return StringBuf (); + + StringBuf buf = str_to_utf8 (path, -1); + if (! buf) + return buf; + + if (aud_get_bool (nullptr, "convert_backslash")) + str_replace_char (buf, '\\', '/'); + + buf.steal (str_encode_percent (buf)); + buf.insert (0, reference, slash + 1 - reference); + return buf; +} + +/* Like strcasecmp, but orders numbers correctly (2 before 10). */ +/* Non-ASCII characters are treated exactly as is. */ +/* Handles nullptr gracefully. */ + +EXPORT int str_compare (const char * ap, const char * bp) +{ + if (! ap) + return bp ? -1 : 0; + if (! bp) + return 1; + + unsigned char a = * ap ++, b = * bp ++; + for (; a || b; a = * ap ++, b = * bp ++) + { + if (a > '9' || b > '9' || a < '0' || b < '0') + { + if (a <= 'Z' && a >= 'A') + a += 'a' - 'A'; + if (b <= 'Z' && b >= 'A') + b += 'a' - 'A'; + + if (a > b) + return 1; + if (a < b) + return -1; + } + else + { + int x = a - '0'; + for (; (a = * ap) <= '9' && a >= '0'; ap ++) + x = 10 * x + (a - '0'); + + int y = b - '0'; + for (; (b = * bp) >= '0' && b <= '9'; bp ++) + y = 10 * y + (b - '0'); + + if (x > y) + return 1; + if (x < y) + return -1; + } + } + + return 0; +} + +/* Decodes percent-encoded strings, then compares then with str_compare. */ + +EXPORT int str_compare_encoded (const char * ap, const char * bp) +{ + if (! ap) + return bp ? -1 : 0; + if (! bp) + return 1; + + unsigned char a = * ap ++, b = * bp ++; + for (; a || b; a = * ap ++, b = * bp ++) + { + if (a == '%' && ap[0] && ap[1]) + { + a = (FROM_HEX (ap[0]) << 4) | FROM_HEX (ap[1]); + ap += 2; + } + if (b == '%' && bp[0] && bp[1]) + { + b = (FROM_HEX (bp[0]) << 4) | FROM_HEX (bp[1]); + bp += 2; + } + + if (a > '9' || b > '9' || a < '0' || b < '0') + { + if (a <= 'Z' && a >= 'A') + a += 'a' - 'A'; + if (b <= 'Z' && b >= 'A') + b += 'a' - 'A'; + + if (a > b) + return 1; + if (a < b) + return -1; + } + else + { + int x = a - '0'; + for (; (a = * ap) <= '9' && a >= '0'; ap ++) + x = 10 * x + (a - '0'); + + int y = b - '0'; + for (; (b = * bp) >= '0' && b <= '9'; bp ++) + y = 10 * y + (b - '0'); + + if (x > y) + return 1; + if (x < y) + return -1; + } + } + + return 0; +} + +EXPORT Index<String> str_list_to_index (const char * list, const char * delims) +{ + char dmap[256] = {0}; + + for (; * delims; delims ++) + dmap[(unsigned char) (* delims)] = 1; + + Index<String> index; + const char * word = nullptr; + + for (; * list; list ++) + { + if (dmap[(unsigned char) (* list)]) + { + if (word) + { + index.append (String (str_copy (word, list - word))); + word = nullptr; + } + } + else + { + if (! word) + { + word = list; + } + } + } + + if (word) + index.append (String (word)); + + return index; +} + +EXPORT StringBuf index_to_str_list (const Index<String> & index, const char * sep) +{ + StringBuf str (-1); + char * set = str; + int left = str.len (); + int seplen = strlen (sep); + + for (const String & s : index) + { + int len = strlen (s); + if (len + seplen > left) + throw std::bad_alloc (); + + if (set > str) + { + memcpy (set, sep, seplen); + + set += seplen; + left -= seplen; + } + + memcpy (set, s, len); + + set += len; + left -= len; + } + + str.resize (set - str); + return str; +} + +/* + * Routines to convert numbers between string and binary representations. + * + * Goals: + * + * - Accuracy, meaning that we can convert back and forth between string and + * binary without the number changing slightly each time. + * - Consistency, meaning that we get the same results no matter what + * architecture or locale we have to deal with. + * - Readability, meaning that the number one is rendered "1", not "1.000". + * + * Values between -1,000,000,000 and 1,000,000,000 (inclusive) are guaranteed to + * have an accuracy of 6 decimal places. + */ + +EXPORT int str_to_int (const char * string) +{ + bool neg = (string[0] == '-'); + if (neg) + string ++; + + int val = 0; + char c; + + while ((c = * string ++) && c >= '0' && c <= '9') + val = val * 10 + (c - '0'); + + return neg ? -val : val; +} + +EXPORT double str_to_double (const char * string) +{ + bool neg = (string[0] == '-'); + if (neg) + string ++; + + double val = str_to_int (string); + const char * p = strchr (string, '.'); + + if (p) + { + char buf[7] = "000000"; + memcpy (buf, p + 1, strlen_bounded (p + 1, 6)); + val += (double) str_to_int (buf) / 1000000; + } + + return neg ? -val : val; +} + +EXPORT StringBuf int_to_str (int val) +{ + bool neg = (val < 0); + if (neg) + val = -val; + + char buf[16]; + char * rev = buf + sizeof buf; + + while (rev > buf) + { + * (-- rev) = '0' + val % 10; + if (! (val /= 10)) + break; + } + + if (neg && rev > buf) + * (-- rev) = '-'; + + int len = buf + sizeof buf - rev; + StringBuf buf2 (len); + memcpy (buf2, rev, len); + return buf2; +} + +EXPORT StringBuf double_to_str (double val) +{ + bool neg = (val < 0); + if (neg) + val = -val; + + int i = floor (val); + int f = round ((val - i) * 1000000); + + if (f == 1000000) + { + i ++; + f = 0; + } + + StringBuf buf = str_printf ("%s%d.%06d", neg ? "-" : "", i, f); + + char * c = buf + buf.len (); + while (c[-1] == '0') + c --; + if (c[-1] == '.') + c --; + + buf.resize (c - buf); + return buf; +} + +EXPORT bool str_to_int_array (const char * string, int * array, int count) +{ + Index<String> index = str_list_to_index (string, ", "); + + if (index.len () != count) + return false; + + for (int i = 0; i < count; i ++) + array[i] = str_to_int (index[i]); + + return true; +} + +EXPORT StringBuf int_array_to_str (const int * array, int count) +{ + Index<String> index; + + for (int i = 0; i < count; i ++) + index.append (String (int_to_str (array[i]))); + + return index_to_str_list (index, ","); +} + +EXPORT bool str_to_double_array (const char * string, double * array, int count) +{ + Index<String> index = str_list_to_index (string, ", "); + + if (index.len () != count) + return false; + + for (int i = 0; i < count; i ++) + array[i] = str_to_double (index[i]); + + return true; +} + +EXPORT StringBuf double_array_to_str (const double * array, int count) +{ + Index<String> index; + + for (int i = 0; i < count; i ++) + index.append (String (double_to_str (array[i]))); + + return index_to_str_list (index, ","); +} + +EXPORT StringBuf str_format_time (int64_t milliseconds) +{ + int hours = milliseconds / 3600000; + int minutes = (milliseconds / 60000) % 60; + int seconds = (milliseconds / 1000) % 60; + + if (hours) + return str_printf ("%d:%02d:%02d", hours, minutes, seconds); + else + { + bool zero = aud_get_bool (nullptr, "leading_zero"); + return str_printf (zero ? "%02d:%02d" : "%d:%02d", minutes, seconds); + } +} diff --git a/src/libaudcore/audstrings.h b/src/libaudcore/audstrings.h index 9f6cae7..5da5b40 100644 --- a/src/libaudcore/audstrings.h +++ b/src/libaudcore/audstrings.h @@ -21,107 +21,84 @@ #define LIBAUDCORE_STRINGS_H #include <stdarg.h> -#include <stdio.h> -#include <string.h> +#include <stdint.h> -#include <libaudcore/core.h> +#include <initializer_list> -#define SPRINTF(s, ...) \ - char s[snprintf (NULL, 0, __VA_ARGS__) + 1]; \ - snprintf (s, sizeof s, __VA_ARGS__) +#include <libaudcore/index.h> +#include <libaudcore/objects.h> -#define VSPRINTF(s, f, v) \ - va_list v##2; \ - va_copy (v##2, v); \ - char s[vsnprintf (NULL, 0, f, v##2) + 1]; \ - va_end (v##2); \ - vsnprintf (s, sizeof s, f, v) +int strcmp_safe (const char * a, const char * b, int len = -1); +int strcmp_nocase (const char * a, const char * b, int len = -1); +int strlen_bounded (const char * s, int len = -1); -#define SCOPY(s, a) \ - char s[strlen (a) + 1]; \ - strcpy (s, a) +StringBuf str_copy (const char * s, int len = -1); +StringBuf str_concat (const std::initializer_list<const char *> & strings); +StringBuf str_printf (const char * format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); +StringBuf str_vprintf (const char * format, va_list args); -#define SNCOPY(s, a, x) \ - char s[(x) + 1]; \ - strncpy (s, a, sizeof s - 1); \ - s[sizeof s - 1] = 0 +bool str_has_prefix_nocase (const char * str, const char * prefix); +bool str_has_suffix_nocase (const char * str, const char * suffix); -#define SCONCAT2(s, a, b) \ - int s##_1 = strlen (a), s##_2 = strlen (b); \ - char s[s##_1 + s##_2 + 1]; \ - memcpy (s, (a), s##_1); \ - strcpy (s + s##_1, (b)) +unsigned str_calc_hash (const char * str); -#define SCONCAT3(s, a, b, c) \ - int s##_1 = strlen (a), s##_2 = strlen (b), s##_3 = strlen (c); \ - char s[s##_1 + s##_2 + s##_3 + 1]; \ - memcpy (s, (a), s##_1); \ - memcpy (s + s##_1, (b), s##_2); \ - strcpy (s + s##_1 + s##_2, (c)) +const char * strstr_nocase (const char * haystack, const char * needle); +const char * strstr_nocase_utf8 (const char * haystack, const char * needle); -#define SCONCAT4(s, a, b, c, d) \ - int s##_1 = strlen (a), s##_2 = strlen (b), s##_3 = strlen (c), s##_4 = strlen (d); \ - char s[s##_1 + s##_2 + s##_3 + s##_4 + 1]; \ - memcpy (s, (a), s##_1); \ - memcpy (s + s##_1, (b), s##_2); \ - memcpy (s + s##_1 + s##_2, (c), s##_3); \ - strcpy (s + s##_1 + s##_2 + s##_3, (d)) +static inline char * strstr_nocase (char * haystack, const char * needle) + { return (char *) strstr_nocase ((const char *) haystack, needle); } +static inline char * strstr_nocase_utf8 (char * haystack, const char * needle) + { return (char *) strstr_nocase_utf8 ((const char *) haystack, needle); } -struct _Index; - -/* all (char *) return values must be freed with str_unref() */ - -char * str_printf (const char * format, ...) __attribute__ ((__format__ (__printf__, 1, 2))); -char * str_vprintf (const char * format, va_list args); - -bool_t str_has_prefix_nocase(const char * str, const char * prefix); -bool_t str_has_suffix_nocase(const char * str, const char * suffix); - -char * strstr_nocase (const char * haystack, const char * needle); -char * strstr_nocase_utf8 (const char * haystack, const char * needle); - -char * str_tolower_utf8 (const char * str); +StringBuf str_tolower (const char * str); +StringBuf str_tolower_utf8 (const char * str); void str_replace_char (char * string, char old_c, char new_c); -void str_itoa (int x, char * buf, int bufsize); +StringBuf str_decode_percent (const char * str, int len = -1); +StringBuf str_encode_percent (const char * str, int len = -1); -void str_decode_percent (const char * str, int len, char * out); -void str_encode_percent (const char * str, int len, char * out); +StringBuf str_convert (const char * str, int len, const char * from_charset, const char * to_charset); +StringBuf str_from_locale (const char * str, int len = -1); +StringBuf str_to_locale (const char * str, int len = -1); -char * str_convert (const char * str, int len, const char * from_charset, const char * to_charset); -char * str_from_locale (const char * str, int len); -char * str_to_locale (const char * str, int len); -char * str_to_utf8 (const char * str, int len); +/* Requires: aud_init() */ +StringBuf str_to_utf8 (const char * str, int len); // no "len = -1" to avoid ambiguity +StringBuf str_to_utf8 (StringBuf && str); -/* takes ownership of <fallbacks> and the pooled strings in it */ -void str_set_charsets (const char * region, struct _Index * fallbacks); +StringBuf filename_normalize (StringBuf && filename); -void filename_normalize (char * filename); - -char * filename_build (const char * path, const char * name); -char * filename_to_uri (const char * filename); -char * uri_to_filename (const char * uri); -char * uri_to_display (const char * uri); +StringBuf filename_build (const std::initializer_list<const char *> & elems); +StringBuf filename_to_uri (const char * filename); +StringBuf uri_to_filename (const char * uri, bool use_locale = true); +StringBuf uri_to_display (const char * uri); void uri_parse (const char * uri, const char * * base_p, const char * * ext_p, const char * * sub_p, int * isub_p); -bool_t uri_get_extension (const char * uri, char * buf, int buflen); + +StringBuf uri_get_scheme (const char * uri); +StringBuf uri_get_extension (const char * uri); + +/* Requires: aud_init() */ +StringBuf uri_construct (const char * path, const char * reference); int str_compare (const char * a, const char * b); int str_compare_encoded (const char * a, const char * b); -struct _Index * str_list_to_index (const char * list, const char * delims); -char * index_to_str_list (struct _Index * index, const char * sep); +Index<String> str_list_to_index (const char * list, const char * delims); +StringBuf index_to_str_list (const Index<String> & index, const char * sep); int str_to_int (const char * string); double str_to_double (const char * string); -char * int_to_str (int val); -char * double_to_str (double val); +StringBuf int_to_str (int val); +StringBuf double_to_str (double val); + +bool str_to_int_array (const char * string, int * array, int count); +StringBuf int_array_to_str (const int * array, int count); +bool str_to_double_array (const char * string, double * array, int count); +StringBuf double_array_to_str (const double * array, int count); -bool_t str_to_int_array (const char * string, int * array, int count); -char * int_array_to_str (const int * array, int count); -bool_t str_to_double_array (const char * string, double * array, int count); -char * double_array_to_str (const double * array, int count); +/* Requires: aud_init() */ +StringBuf str_format_time (int64_t milliseconds); #endif /* LIBAUDCORE_STRINGS_H */ diff --git a/src/libaudcore/charset.c b/src/libaudcore/charset.c deleted file mode 100644 index 231b756..0000000 --- a/src/libaudcore/charset.c +++ /dev/null @@ -1,180 +0,0 @@ -/* - * charset.c - * Copyright 2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <iconv.h> -#include <stdio.h> -#include <string.h> - -#include <glib.h> - -#ifdef USE_CHARDET -#include <libguess/libguess.h> -#endif - -#include "audstrings.h" -#include "index.h" -#include "tinylock.h" - -EXPORT char * str_convert (const char * str, int len, const char * from_charset, - const char * to_charset) -{ - iconv_t conv = iconv_open (to_charset, from_charset); - if (conv == (iconv_t) -1) - return NULL; - - if (len < 0) - len = strlen (str); - - // liberal estimate of how much space we will need - // are there obscure cases that require even more? - int maxlen = 4 * len; - - char buf[maxlen + 1]; - char * result = NULL; - - size_t inbytes = len; - size_t outbytes = maxlen; - ICONV_CONST char * in = (ICONV_CONST char *) str; - char * out = buf; - - if (iconv (conv, & in, & inbytes, & out, & outbytes) != (size_t) -1 && ! inbytes) - { - buf[maxlen - outbytes] = 0; - result = str_get (buf); - } - - iconv_close (conv); - return result; -} - -static void whine_locale (const char * str, int len, const char * dir, const char * charset) -{ - if (len < 0) - fprintf (stderr, "Cannot convert %s locale (%s): %s\n", dir, charset, str); - else - fprintf (stderr, "Cannot convert %s locale (%s): %.*s\n", dir, charset, len, str); -} - -EXPORT char * str_from_locale (const char * str, int len) -{ - const char * charset; - - if (g_get_charset (& charset)) - { - /* locale is UTF-8 */ - if (! g_utf8_validate (str, len, NULL)) - { - whine_locale (str, len, "from", "UTF-8"); - return NULL; - } - - return (len < 0) ? str_get (str) : str_nget (str, len); - } - else - { - char * utf8 = str_convert (str, len, charset, "UTF-8"); - if (! utf8) - whine_locale (str, len, "from", charset); - - return utf8; - } -} - -EXPORT char * str_to_locale (const char * str, int len) -{ - const char * charset; - - if (g_get_charset (& charset)) - { - /* locale is UTF-8 */ - return (len < 0) ? str_get (str) : str_nget (str, len); - } - else - { - char * local = str_convert (str, len, "UTF-8", charset); - if (! local) - whine_locale (str, len, "to", charset); - - return local; - } -} - -static TinyRWLock settings_lock; -static char * detect_region; -static Index * fallback_charsets; - -EXPORT void str_set_charsets (const char * region, Index * fallbacks) -{ - tiny_lock_write (& settings_lock); - - str_unref (detect_region); - detect_region = str_get (region); - -#ifdef USE_CHARDET - if (detect_region) - libguess_init (); -#endif - - if (fallback_charsets) - index_free_full (fallback_charsets, (IndexFreeFunc) str_unref); - - fallback_charsets = fallbacks; - - tiny_unlock_write (& settings_lock); -} - -EXPORT char * str_to_utf8 (const char * str, int len) -{ - /* check whether already UTF-8 */ - if (g_utf8_validate (str, len, NULL)) - return (len < 0) ? str_get (str) : str_nget (str, len); - - if (len < 0) - len = strlen (str); - - char * utf8 = NULL; - tiny_lock_read (& settings_lock); - -#ifdef USE_CHARDET - if (detect_region) - { - /* prefer libguess-detected charset */ - const char * detected = libguess_determine_encoding (str, len, detect_region); - if (detected && (utf8 = str_convert (str, len, detected, "UTF-8"))) - goto DONE; - } -#endif - - if (fallback_charsets) - { - /* try user-configured fallbacks */ - for (int i = 0; i < index_count (fallback_charsets); i ++) - { - if ((utf8 = str_convert (str, len, index_get (fallback_charsets, i), "UTF-8"))) - goto DONE; - } - } - - /* try system locale last (this one will print a warning if it fails) */ - utf8 = str_from_locale (str, len); - -DONE: - tiny_unlock_read (& settings_lock); - return utf8; -} diff --git a/src/libaudcore/charset.cc b/src/libaudcore/charset.cc new file mode 100644 index 0000000..af2671a --- /dev/null +++ b/src/libaudcore/charset.cc @@ -0,0 +1,226 @@ +/* + * charset.c + * Copyright 2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "audstrings.h" +#include "internal.h" + +#include <errno.h> +#include <iconv.h> +#include <string.h> + +#include <new> + +#include <glib.h> + +#ifdef USE_CHARDET +extern "C" { +#include <libguess/libguess.h> +} +#endif + +#include "hook.h" +#include "index.h" +#include "runtime.h" +#include "tinylock.h" + +EXPORT StringBuf str_convert (const char * str, int len, const char * from_charset, + const char * to_charset) +{ + iconv_t conv = iconv_open (to_charset, from_charset); + if (conv == (iconv_t) -1) + return StringBuf (); + + if (len < 0) + len = strlen (str); + + StringBuf buf (-1); + + size_t inbytesleft = len; + size_t outbytesleft = buf.len (); + ICONV_CONST char * in = (ICONV_CONST char *) str; + char * out = buf; + + errno = 0; + size_t ret = iconv (conv, & in, & inbytesleft, & out, & outbytesleft); + + if (ret == (size_t) -1 && errno == E2BIG) + throw std::bad_alloc (); + + iconv_close (conv); + + if (ret == (size_t) -1 || inbytesleft) + return StringBuf (); + + buf.resize (buf.len () - outbytesleft); + return buf; +} + +static void whine_locale (const char * str, int len, const char * dir, const char * charset) +{ + if (len < 0) + AUDWARN ("Cannot convert %s locale (%s): %s\n", dir, charset, str); + else + AUDWARN ("Cannot convert %s locale (%s): %.*s\n", dir, charset, len, str); +} + +EXPORT StringBuf str_from_locale (const char * str, int len) +{ + const char * charset; + + if (g_get_charset (& charset)) + { + /* locale is UTF-8 */ + if (! g_utf8_validate (str, len, nullptr)) + { + whine_locale (str, len, "from", "UTF-8"); + return StringBuf (); + } + + return str_copy (str, len); + } + else + { + StringBuf utf8 = str_convert (str, len, charset, "UTF-8"); + if (! utf8) + whine_locale (str, len, "from", charset); + + return utf8; + } +} + +EXPORT StringBuf str_to_locale (const char * str, int len) +{ + const char * charset; + + if (g_get_charset (& charset)) + { + /* locale is UTF-8 */ + return str_copy (str, len); + } + else + { + StringBuf local = str_convert (str, len, "UTF-8", charset); + if (! local) + whine_locale (str, len, "to", charset); + + return local; + } +} + +static TinyRWLock settings_lock; +static String detect_region; +static Index<String> fallback_charsets; + +static void set_charsets (const char * region, const char * fallbacks) +{ + tiny_lock_write (& settings_lock); + + detect_region = String (region); + +#ifdef USE_CHARDET + if (detect_region) + libguess_init (); +#endif + + if (fallbacks) + fallback_charsets = str_list_to_index (fallbacks, ", "); + else + fallback_charsets.clear (); + + tiny_unlock_write (& settings_lock); +} + +static StringBuf convert_to_utf8_locked (const char * str, int len) +{ + if (len < 0) + len = strlen (str); + +#ifdef USE_CHARDET + if (detect_region) + { + /* prefer libguess-detected charset */ + const char * detected = libguess_determine_encoding (str, len, detect_region); + if (detected) + { + StringBuf utf8 = str_convert (str, len, detected, "UTF-8"); + if (utf8) + return utf8; + } + } +#endif + + /* try user-configured fallbacks */ + for (const String & fallback : fallback_charsets) + { + StringBuf utf8 = str_convert (str, len, fallback, "UTF-8"); + if (utf8) + return utf8; + } + + /* try system locale last (this one will print a warning if it fails) */ + return str_from_locale (str, len); +} + +EXPORT StringBuf str_to_utf8 (const char * str, int len) +{ + /* check whether already UTF-8 */ + if (g_utf8_validate (str, len, nullptr)) + return str_copy (str, len); + + tiny_lock_read (& settings_lock); + StringBuf utf8 = convert_to_utf8_locked (str, len); + tiny_unlock_read (& settings_lock); + return utf8; +} + +EXPORT StringBuf str_to_utf8 (StringBuf && str) +{ + /* check whether already UTF-8 */ + if (g_utf8_validate (str, str.len (), nullptr)) + return std::move (str); + + tiny_lock_read (& settings_lock); + str.steal (convert_to_utf8_locked (str, str.len ())); + tiny_unlock_read (& settings_lock); + return std::move (str); +} + +static void chardet_update (void) +{ + String region = aud_get_str (nullptr, "chardet_detector"); + String fallbacks = aud_get_str (nullptr, "chardet_fallback"); + + set_charsets (region[0] ? (const char *) region : nullptr, fallbacks); +} + +void chardet_init (void) +{ + chardet_update (); + + hook_associate ("set chardet_detector", (HookFunction) chardet_update, nullptr); + hook_associate ("set chardet_fallback", (HookFunction) chardet_update, nullptr); +} + +void chardet_cleanup (void) +{ + hook_dissociate ("set chardet_detector", (HookFunction) chardet_update); + hook_dissociate ("set chardet_fallback", (HookFunction) chardet_update); + + set_charsets (nullptr, nullptr); +} diff --git a/src/libaudcore/config.cc b/src/libaudcore/config.cc new file mode 100644 index 0000000..de69e3c --- /dev/null +++ b/src/libaudcore/config.cc @@ -0,0 +1,397 @@ +/* + * config.c + * Copyright 2011-2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "runtime.h" +#include "internal.h" + +#include <assert.h> +#include <string.h> + +#include "audstrings.h" +#include "hook.h" +#include "inifile.h" +#include "multihash.h" +#include "runtime.h" +#include "vfs.h" + +#define DEFAULT_SECTION "audacious" + +static const char * const core_defaults[] = { + + /* general */ + "advance_on_delete", "FALSE", + "always_resume_paused", "TRUE", + "clear_playlist", "TRUE", + "open_to_temporary", "TRUE", + "resume_playback_on_startup", "TRUE", + "show_interface", "TRUE", + + /* equalizer */ + "eqpreset_default_file", "", + "eqpreset_extension", "", + "equalizer_active", "FALSE", + "equalizer_autoload", "FALSE", + "equalizer_bands", "0,0,0,0,0,0,0,0,0,0", + "equalizer_preamp", "0", + + /* info popup / info window */ + "cover_name_exclude", "back", + "cover_name_include", "album,cover,front,folder", + "filepopup_delay", "5", + "filepopup_showprogressbar", "FALSE", + "recurse_for_cover", "FALSE", + "recurse_for_cover_depth", "0", + "show_filepopup_for_tuple", "TRUE", + "use_file_cover", "FALSE", + + /* network */ + "net_buffer_kb", "128", + "use_proxy", "FALSE", + "use_proxy_auth", "FALSE", + + /* output */ + "default_gain", "0", + "enable_replay_gain", "TRUE", + "enable_clipping_prevention", "TRUE", + "output_bit_depth", "16", + "output_buffer_size", "500", + "replay_gain_album", "FALSE", + "replay_gain_preamp", "0", + "soft_clipping", "FALSE", + "software_volume_control", "FALSE", + "sw_volume_left", "100", + "sw_volume_right", "100", + + /* playback */ + "no_playlist_advance", "FALSE", + "repeat", "FALSE", + "shuffle", "FALSE", + "stop_after_current_song", "FALSE", + + /* playlist */ + "chardet_fallback", "ISO-8859-1", +#ifdef _WIN32 + "convert_backslash", "TRUE", +#else + "convert_backslash", "FALSE", +#endif + "generic_title_format", "${?artist:${artist} - }${?album:${album} - }${title}", + "leading_zero", "FALSE", + "metadata_on_play", "FALSE", + "show_numbers_in_pl", "FALSE", + "slow_probe", "FALSE", + + nullptr}; + +enum OpType { + OP_IS_DEFAULT, + OP_GET, + OP_SET, + OP_SET_NO_FLAG, + OP_CLEAR, + OP_CLEAR_NO_FLAG +}; + +struct ConfigItem { + String section; + String key; + String value; +}; + +struct ConfigNode { + MultiHash::Node node; + ConfigItem item; +}; + +struct ConfigOp { + OpType type; + const char * section; + const char * key; + String value; + unsigned hash; + bool result; +}; + +struct SaveState { + Index<ConfigItem> list; +}; + +static int item_compare (const ConfigItem & a, const ConfigItem & b, void *) +{ + if (a.section == b.section) + return strcmp (a.key, b.key); + else + return strcmp (a.section, b.section); +} + +static bool config_node_match (const MultiHash::Node * node0, const void * data) +{ + const ConfigNode * node = (const ConfigNode *) node0; + const ConfigOp * op = (const ConfigOp *) data; + + return ! strcmp (node->item.section, op->section) && ! strcmp (node->item.key, op->key); +} + +static MultiHash defaults (config_node_match); +static MultiHash config (config_node_match); + +static volatile bool modified; + +static MultiHash::Node * add_cb (const void * data, void * state) +{ + ConfigOp * op = (ConfigOp *) state; + + switch (op->type) + { + case OP_IS_DEFAULT: + op->result = ! op->value[0]; /* empty string is default */ + return nullptr; + + case OP_SET: + op->result = true; + modified = true; + + case OP_SET_NO_FLAG: + { + ConfigNode * node = new ConfigNode; + node->item.section = String (op->section); + node->item.key = String (op->key); + node->item.value = op->value; + return (MultiHash::Node *) node; + } + + default: + return nullptr; + } +} + +static bool action_cb (MultiHash::Node * node0, void * state) +{ + ConfigNode * node = (ConfigNode *) node0; + ConfigOp * op = (ConfigOp *) state; + + switch (op->type) + { + case OP_IS_DEFAULT: + op->result = ! strcmp (node->item.value, op->value); + return false; + + case OP_GET: + op->value = node->item.value; + return false; + + case OP_SET: + op->result = !! strcmp (node->item.value, op->value); + if (op->result) + modified = true; + + case OP_SET_NO_FLAG: + node->item.value = op->value; + return false; + + case OP_CLEAR: + op->result = true; + modified = true; + + case OP_CLEAR_NO_FLAG: + delete node; + return true; + + default: + return false; + } +} + +static bool config_op_run (ConfigOp * op, MultiHash * table) +{ + if (! op->hash) + op->hash = str_calc_hash (op->section) + str_calc_hash (op->key); + + op->result = false; + table->lookup (op, op->hash, add_cb, action_cb, op); + return op->result; +} + +class ConfigParser : public IniParser +{ +private: + String section; + + void handle_heading (const char * heading) + { section = String (heading); } + + void handle_entry (const char * key, const char * value) + { + if (! section) + return; + + ConfigOp op = {OP_SET_NO_FLAG, section, key, String (value)}; + config_op_run (& op, & config); + } +}; + +void config_load (void) +{ + StringBuf path = filename_to_uri (aud_get_path (AudPath::UserDir)); + path.insert (-1, "/config"); + + if (VFSFile::test_file (path, VFS_EXISTS)) + { + VFSFile file (path, "r"); + if (file) + ConfigParser ().parse (file); + } + + aud_config_set_defaults (nullptr, core_defaults); +} + +static bool add_to_save_list (MultiHash::Node * node0, void * state0) +{ + ConfigNode * node = (ConfigNode *) node0; + SaveState * state = (SaveState *) state0; + + state->list.append (node->item); + + modified = false; + + return false; +} + +void config_save (void) +{ + if (! modified) + return; + + SaveState state = SaveState (); + + config.iterate (add_to_save_list, & state); + state.list.sort (item_compare, nullptr); + + StringBuf path = filename_to_uri (aud_get_path (AudPath::UserDir)); + path.insert (-1, "/config"); + + String current_heading; + + VFSFile file (path, "w"); + if (! file) + goto FAILED; + + for (const ConfigItem & item : state.list) + { + if (item.section != current_heading) + { + if (! inifile_write_heading (file, item.section)) + goto FAILED; + + current_heading = item.section; + } + + if (! inifile_write_entry (file, item.key, item.value)) + goto FAILED; + } + + if (file.fflush () < 0) + goto FAILED; + + return; + +FAILED: + AUDWARN ("Error saving configuration.\n"); +} + +EXPORT void aud_config_set_defaults (const char * section, const char * const * entries) +{ + if (! section) + section = DEFAULT_SECTION; + + while (1) + { + const char * name = * entries ++; + const char * value = * entries ++; + if (! name || ! value) + break; + + ConfigOp op = {OP_SET_NO_FLAG, section, name, String (value)}; + config_op_run (& op, & defaults); + } +} + +void config_cleanup (void) +{ + ConfigOp op = {OP_CLEAR_NO_FLAG}; + config.iterate (action_cb, & op); + defaults.iterate (action_cb, & op); +} + +EXPORT void aud_set_str (const char * section, const char * name, const char * value) +{ + assert (name && value); + + ConfigOp op = {OP_IS_DEFAULT, section ? section : DEFAULT_SECTION, name, String (value)}; + bool is_default = config_op_run (& op, & defaults); + + op.type = is_default ? OP_CLEAR : OP_SET; + bool changed = config_op_run (& op, & config); + + if (changed && ! section) + event_queue (str_concat ({"set ", name}), nullptr); +} + +EXPORT String aud_get_str (const char * section, const char * name) +{ + assert (name); + + ConfigOp op = {OP_GET, section ? section : DEFAULT_SECTION, name}; + config_op_run (& op, & config); + + if (! op.value) + config_op_run (& op, & defaults); + + return op.value ? op.value : String (""); +} + +EXPORT void aud_set_bool (const char * section, const char * name, bool value) +{ + aud_set_str (section, name, value ? "TRUE" : "FALSE"); +} + +EXPORT bool aud_get_bool (const char * section, const char * name) +{ + return ! strcmp (aud_get_str (section, name), "TRUE"); +} + +EXPORT void aud_set_int (const char * section, const char * name, int value) +{ + aud_set_str (section, name, int_to_str (value)); +} + +EXPORT int aud_get_int (const char * section, const char * name) +{ + return str_to_int (aud_get_str (section, name)); +} + +EXPORT void aud_set_double (const char * section, const char * name, double value) +{ + aud_set_str (section, name, double_to_str (value)); +} + +EXPORT double aud_get_double (const char * section, const char * name) +{ + return str_to_double (aud_get_str (section, name)); +} diff --git a/src/libaudcore/core.h b/src/libaudcore/core.h deleted file mode 100644 index f3c2615..0000000 --- a/src/libaudcore/core.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * core.h - * Copyright 2011-2012 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef LIBAUDCORE_CORE_H -#define LIBAUDCORE_CORE_H - -#undef NULL -#ifdef __cplusplus /* *sigh* */ -#define NULL 0 -#else -#define NULL ((void *) 0) -#endif - -/* "bool_t" means "int" for compatibility with GLib */ -#undef bool_t -#define bool_t int - -#undef FALSE -#define FALSE ((bool_t) 0) -#undef TRUE -#define TRUE ((bool_t) 1) - -#undef MIN -#define MIN(a,b) ((a) < (b) ? (a) : (b)) -#undef MAX -#define MAX(a,b) ((a) > (b) ? (a) : (b)) -#undef CLAMP -#define CLAMP(a,min,max) ((a) < (min) ? (min) : (a) > (max) ? (max) : (a)) - -#define ARRAY_LEN(a) (sizeof (a) / sizeof (a)[0]) - -/* If the pool contains a copy of <str>, increments its reference count. - * Otherwise, adds a copy of <str> to the pool with a reference count of one. - * In either case, returns the copy. Because this copy may be shared by other - * parts of the code, it should not be modified. If <str> is NULL, simply - * returns NULL with no side effects. */ -char * str_get (const char * str); - -/* Increments the reference count of <str>, where <str> is the address of a - * string already in the pool. Faster than calling str_get() a second time. - * Returns <str> for convenience. If <str> is NULL, simply returns NULL with no - * side effects. */ -char * str_ref (const char * str); - -/* Decrements the reference count of <str>, where <str> is the address of a - * string in the pool. If the reference count drops to zero, releases the - * memory used by <str>. If <str> is NULL, simply returns NULL with no side - * effects. */ -void str_unref (char * str); - -/* Returns the cached hash value of a pooled string (or 0 for NULL). */ -unsigned str_hash (const char * str); - -/* Checks whether two pooled strings are equal. Since the pool never contains - * duplicate strings, this is a simple pointer comparison and thus much faster - * than strcmp(). NULL is considered equal to NULL but not equal to any string. */ -bool_t str_equal (const char * str1, const char * str2); - -/* Calls str_get() on the first <len> characters of <str>. If <str> has less - * than or equal to <len> characters, equivalent to str_get(). */ -char * str_nget (const char * str, int len); - -/* Releases all memory used by the string pool. If strings remain in the pool, - * a warning may be printed to stderr in order to reveal memory leaks. */ -void strpool_shutdown (void); - -#endif /* LIBAUDCORE_CORE_H */ diff --git a/src/libaudcore/drct.cc b/src/libaudcore/drct.cc new file mode 100644 index 0000000..7121192 --- /dev/null +++ b/src/libaudcore/drct.cc @@ -0,0 +1,195 @@ +/* + * drct.c + * Copyright 2009-2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "drct.h" + +#include "i18n.h" +#include "internal.h" +#include "playlist-internal.h" +#include "runtime.h" +#include "tuple.h" + +/* --- PLAYBACK CONTROL --- */ + +EXPORT void aud_drct_play () +{ + if (aud_drct_get_playing ()) + { + if (aud_drct_get_paused ()) + aud_drct_pause (); + else + { + int a, b; + aud_drct_get_ab_repeat (a, b); + aud_drct_seek (aud::max (a, 0)); + } + } + else + { + int playlist = aud_playlist_get_active (); + aud_playlist_set_position (playlist, aud_playlist_get_position (playlist)); + aud_playlist_play (playlist); + } +} + +EXPORT void aud_drct_play_pause () +{ + if (aud_drct_get_playing ()) + aud_drct_pause (); + else + aud_drct_play (); +} + +EXPORT void aud_drct_stop () +{ + aud_playlist_play (-1); +} + +EXPORT int aud_drct_get_position () +{ + int playlist = aud_playlist_get_playing (); + return aud_playlist_get_position (playlist); +} + +EXPORT String aud_drct_get_filename () +{ + int playlist = aud_playlist_get_playing (); + int position = aud_playlist_get_position (playlist); + return aud_playlist_entry_get_filename (playlist, position); +} + +/* --- VOLUME CONTROL --- */ + +EXPORT int aud_drct_get_volume_main () +{ + StereoVolume volume = aud_drct_get_volume (); + return aud::max (volume.left, volume.right); +} + +EXPORT void aud_drct_set_volume_main (int volume) +{ + StereoVolume old = aud_drct_get_volume (); + int main = aud::max (old.left, old.right); + + if (main > 0) + aud_drct_set_volume ({ + aud::rescale (old.left, main, volume), + aud::rescale (old.right, main, volume) + }); + else + aud_drct_set_volume ({volume, volume}); +} + +EXPORT int aud_drct_get_volume_balance () +{ + StereoVolume volume = aud_drct_get_volume (); + + if (volume.left == volume.right) + return 0; + else if (volume.left > volume.right) + return -100 + aud::rescale (volume.right, volume.left, 100); + else + return 100 - aud::rescale (volume.left, volume.right, 100); +} + +EXPORT void aud_drct_set_volume_balance (int balance) +{ + int main = aud_drct_get_volume_main (); + + if (balance < 0) + aud_drct_set_volume ({main, aud::rescale (main, 100, 100 + balance)}); + else + aud_drct_set_volume ({aud::rescale (main, 100, 100 - balance), main}); +} + +/* --- PLAYLIST CONTROL --- */ + +EXPORT void aud_drct_pl_next () +{ + int playlist = aud_playlist_get_playing (); + if (playlist < 0) + playlist = aud_playlist_get_active (); + + playlist_next_song (playlist, aud_get_bool (nullptr, "repeat")); +} + +EXPORT void aud_drct_pl_prev () +{ + int playlist = aud_playlist_get_playing (); + if (playlist < 0) + playlist = aud_playlist_get_active (); + + playlist_prev_song (playlist); +} + +static void add_list (Index<PlaylistAddItem> && items, int at, bool to_temp, bool play) +{ + if (to_temp) + aud_playlist_set_active (aud_playlist_get_temporary ()); + + int playlist = aud_playlist_get_active (); + + /* queue the new entries before deleting the old ones */ + /* this is to avoid triggering the --quit-after-play condition */ + aud_playlist_entry_insert_batch (playlist, at, std::move (items), play); + + if (play) + { + if (aud_get_bool (nullptr, "clear_playlist")) + aud_playlist_entry_delete (playlist, 0, aud_playlist_entry_count (playlist)); + else + aud_playlist_queue_delete (playlist, 0, aud_playlist_queue_count (playlist)); + } +} + +EXPORT void aud_drct_pl_add (const char * filename, int at) +{ + Index<PlaylistAddItem> items; + items.append (String (filename)); + add_list (std::move (items), at, false, false); +} + +EXPORT void aud_drct_pl_add_list (Index<PlaylistAddItem> && items, int at) +{ + add_list (std::move (items), at, false, false); +} + +EXPORT void aud_drct_pl_open (const char * filename) +{ + Index<PlaylistAddItem> items; + items.append (String (filename)); + add_list (std::move (items), -1, aud_get_bool (nullptr, "open_to_temporary"), true); +} + +EXPORT void aud_drct_pl_open_list (Index<PlaylistAddItem> && items) +{ + add_list (std::move (items), -1, aud_get_bool (nullptr, "open_to_temporary"), true); +} + +EXPORT void aud_drct_pl_open_temp (const char * filename) +{ + Index<PlaylistAddItem> items; + items.append (String (filename)); + add_list (std::move (items), -1, true, true); +} + +EXPORT void aud_drct_pl_open_temp_list (Index<PlaylistAddItem> && items) +{ + add_list (std::move (items), -1, true, true); +} diff --git a/src/libaudcore/drct.h b/src/libaudcore/drct.h new file mode 100644 index 0000000..45d2667 --- /dev/null +++ b/src/libaudcore/drct.h @@ -0,0 +1,89 @@ +/* + * drct.h + * Copyright 2010-2012 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_DRCT_H +#define LIBAUDCORE_DRCT_H + +#include <libaudcore/audio.h> +#include <libaudcore/index.h> +#include <libaudcore/tuple.h> + +/* CAUTION: These functions are not thread safe. */ + +/* --- PLAYBACK CONTROL --- */ + +void aud_drct_play (); +void aud_drct_play_pause (); +void aud_drct_pause (); +void aud_drct_stop (); +bool aud_drct_get_playing (); +bool aud_drct_get_ready (); +bool aud_drct_get_paused (); + +// returns entry number of playing song (zero-based) +int aud_drct_get_position (); + +// returns filename of playing song +String aud_drct_get_filename (); + +// returns formatted title of playing song +// connect to the "title change" hook to be notified of changes +String aud_drct_get_title (); + +// returns metadata of playing song +// connect to the "tuple change" hook to be notified of changes +Tuple aud_drct_get_tuple (); + +// returns some statistics of playing song +// connect to the "info change" hook to be notified of changes +void aud_drct_get_info (int & bitrate, int & samplerate, int & channels); + +int aud_drct_get_time (); +int aud_drct_get_length (); +void aud_drct_seek (int time); + +/* "A-B repeat": when playback reaches point B, it returns to point A (where A + * and B are in milliseconds). The value -1 is interpreted as the beginning of + * the song (for A) or the end of the song (for B). A-B repeat is disabled + * entirely by setting both A and B to -1. */ +void aud_drct_set_ab_repeat (int a, int b); +void aud_drct_get_ab_repeat (int & a, int & b); + +/* --- VOLUME CONTROL --- */ + +StereoVolume aud_drct_get_volume (); +void aud_drct_set_volume (StereoVolume volume); +int aud_drct_get_volume_main (); +void aud_drct_set_volume_main (int volume); +int aud_drct_get_volume_balance (); +void aud_drct_set_volume_balance (int balance); + +/* --- PLAYLIST CONTROL --- */ + +void aud_drct_pl_next (); +void aud_drct_pl_prev (); + +void aud_drct_pl_add (const char * filename, int at); +void aud_drct_pl_add_list (Index<PlaylistAddItem> && items, int at); +void aud_drct_pl_open (const char * filename); +void aud_drct_pl_open_list (Index<PlaylistAddItem> && items); +void aud_drct_pl_open_temp (const char * filename); +void aud_drct_pl_open_temp_list (Index<PlaylistAddItem> && items); + +#endif diff --git a/src/libaudcore/effect.cc b/src/libaudcore/effect.cc new file mode 100644 index 0000000..b0538d0 --- /dev/null +++ b/src/libaudcore/effect.cc @@ -0,0 +1,268 @@ +/* + * effect.c + * Copyright 2010-2012 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "internal.h" + +#include <pthread.h> + +#include "drct.h" +#include "list.h" +#include "plugin.h" +#include "plugins.h" +#include "runtime.h" + +struct Effect : public ListNode +{ + PluginHandle * plugin; + int position; + EffectPlugin * header; + int channels_returned, rate_returned; + bool remove_flag; +}; + +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static List<Effect> effects; +static int input_channels, input_rate; + +void effect_start (int & channels, int & rate) +{ + pthread_mutex_lock (& mutex); + + AUDDBG ("Starting effects.\n"); + + effects.clear (); + + input_channels = channels; + input_rate = rate; + + auto & list = aud_plugin_list (PluginType::Effect); + + for (int i = 0; i < list.len (); i ++) + { + PluginHandle * plugin = list[i]; + if (! aud_plugin_get_enabled (plugin)) + continue; + + AUDINFO ("Starting %s at %d channels, %d Hz.\n", + aud_plugin_get_name (plugin), channels, rate); + + EffectPlugin * header = (EffectPlugin *) aud_plugin_get_header (plugin); + if (! header) + continue; + + header->start (channels, rate); + + Effect * effect = new Effect (); + effect->plugin = plugin; + effect->position = i; + effect->header = header; + effect->channels_returned = channels; + effect->rate_returned = rate; + + effects.append (effect); + } + + pthread_mutex_unlock (& mutex); +} + +Index<float> & effect_process (Index<float> & data) +{ + Index<float> * cur = & data; + pthread_mutex_lock (& mutex); + + Effect * e = effects.head (); + while (e) + { + Effect * next = effects.next (e); + + if (e->remove_flag) + { + cur = & e->header->finish (* cur, false); + + // simulate end-of-playlist call + // first save the current data + Index<float> save = std::move (* cur); + cur = & e->header->finish (* cur, true); + + // combine the saved and new data + save.move_from (* cur, 0, -1, -1, true, true); + * cur = std::move (save); + + effects.remove (e); + delete e; + } + else + cur = & e->header->process (* cur); + + e = next; + } + + pthread_mutex_unlock (& mutex); + return * cur; +} + +bool effect_flush (bool force) +{ + bool flushed = true; + pthread_mutex_lock (& mutex); + + for (Effect * e = effects.head (); e; e = effects.next (e)) + { + if (! e->header->flush (force) && ! force) + { + flushed = false; + break; + } + } + + pthread_mutex_unlock (& mutex); + return flushed; +} + +Index<float> & effect_finish (Index<float> & data, bool end_of_playlist) +{ + Index<float> * cur = & data; + pthread_mutex_lock (& mutex); + + for (Effect * e = effects.head (); e; e = effects.next (e)) + cur = & e->header->finish (* cur, end_of_playlist); + + pthread_mutex_unlock (& mutex); + return * cur; +} + +int effect_adjust_delay (int delay) +{ + pthread_mutex_lock (& mutex); + + for (Effect * e = effects.tail (); e; e = effects.prev (e)) + delay = e->header->adjust_delay (delay); + + pthread_mutex_unlock (& mutex); + return delay; +} + +static void effect_insert (PluginHandle * plugin, EffectPlugin * header) +{ + int position = aud_plugin_list (PluginType::Effect).find (plugin); + + Effect * prev = nullptr; + + for (Effect * e = effects.head (); e; e = effects.next (e)) + { + if (e->plugin == plugin) + { + e->remove_flag = false; + return; + } + + if (e->position > position) + break; + + prev = e; + } + + AUDDBG ("Adding %s without reset.\n", aud_plugin_get_name (plugin)); + + int channels, rate; + if (prev) + { + AUDDBG ("Adding %s after %s.\n", aud_plugin_get_name (plugin), + aud_plugin_get_name (prev->plugin)); + channels = prev->channels_returned; + rate = prev->rate_returned; + } + else + { + AUDDBG ("Adding %s as first effect.\n", aud_plugin_get_name (plugin)); + channels = input_channels; + rate = input_rate; + } + + AUDINFO ("Starting %s at %d channels, %d Hz.\n", aud_plugin_get_name (plugin), channels, rate); + header->start (channels, rate); + + Effect * effect = new Effect (); + effect->plugin = plugin; + effect->position = position; + effect->header = header; + effect->channels_returned = channels; + effect->rate_returned = rate; + + effects.insert_after (prev, effect); +} + +static void effect_remove (PluginHandle * plugin) +{ + for (Effect * e = effects.head (); e; e = effects.next (e)) + { + if (e->plugin == plugin) + { + AUDDBG ("Removing %s without reset.\n", aud_plugin_get_name (plugin)); + e->remove_flag = true; + return; + } + } +} + +static void effect_enable (PluginHandle * plugin, EffectPlugin * ep, bool enable) +{ + if (ep->preserves_format) + { + pthread_mutex_lock (& mutex); + + if (enable) + effect_insert (plugin, ep); + else + effect_remove (plugin); + + pthread_mutex_unlock (& mutex); + } + else + { + AUDDBG ("Reset to add/remove %s.\n", aud_plugin_get_name (plugin)); + aud_output_reset (OutputReset::EffectsOnly); + } +} + +bool effect_plugin_start (PluginHandle * plugin) +{ + if (aud_drct_get_playing ()) + { + EffectPlugin * ep = (EffectPlugin *) aud_plugin_get_header (plugin); + if (! ep) + return false; + + effect_enable (plugin, ep, true); + } + + return true; +} + +void effect_plugin_stop (PluginHandle * plugin) +{ + if (aud_drct_get_playing ()) + { + EffectPlugin * ep = (EffectPlugin *) aud_plugin_get_header (plugin); + if (! ep) + return; + + effect_enable (plugin, ep, false); + } +} diff --git a/src/libaudcore/equalizer-preset.cc b/src/libaudcore/equalizer-preset.cc new file mode 100644 index 0000000..699d0fb --- /dev/null +++ b/src/libaudcore/equalizer-preset.cc @@ -0,0 +1,202 @@ +/* + * equalizer_preset.c + * Copyright 2003-2013 Eugene Zagidullin, William Pitcock, John Lindgren, and + * Thomas Lange + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "equalizer.h" + +#include <math.h> +#include <string.h> + +#include <glib.h> /* for GKeyFile */ + +#include "audstrings.h" +#include "runtime.h" +#include "vfs.h" + +EXPORT Index<EqualizerPreset> aud_eq_read_presets (const char * basename) +{ + Index<EqualizerPreset> list; + + GKeyFile * rcfile = g_key_file_new (); + StringBuf filename = filename_build ({aud_get_path (AudPath::UserDir), basename}); + + if (! g_key_file_load_from_file (rcfile, filename, G_KEY_FILE_NONE, nullptr)) + { + StringBuf filename2 = filename_build ({aud_get_path (AudPath::DataDir), basename}); + + if (! g_key_file_load_from_file (rcfile, filename2, G_KEY_FILE_NONE, nullptr)) + { + g_key_file_free (rcfile); + return list; + } + } + + for (int p = 0;; p ++) + { + char * name = g_key_file_get_string (rcfile, "Presets", str_printf ("Preset%d", p), nullptr); + if (! name) + break; + + EqualizerPreset & preset = list.append (String (name)); + preset.preamp = g_key_file_get_double (rcfile, name, "Preamp", nullptr); + + for (int i = 0; i < AUD_EQ_NBANDS; i++) + preset.bands[i] = g_key_file_get_double (rcfile, name, str_printf ("Band%d", i), nullptr); + + g_free (name); + } + + g_key_file_free (rcfile); + + return list; +} + +EXPORT bool aud_eq_write_presets (const Index<EqualizerPreset> & list, const char * basename) +{ + GKeyFile * rcfile = g_key_file_new (); + + for (int p = 0; p < list.len (); p ++) + { + const EqualizerPreset & preset = list[p]; + + g_key_file_set_string (rcfile, "Presets", str_printf ("Preset%d", p), preset.name); + g_key_file_set_double (rcfile, preset.name, "Preamp", preset.preamp); + + for (int i = 0; i < AUD_EQ_NBANDS; i ++) + g_key_file_set_double (rcfile, preset.name, str_printf ("Band%d", i), preset.bands[i]); + } + + size_t len; + char * data = g_key_file_to_data (rcfile, & len, nullptr); + + StringBuf filename = filename_build ({aud_get_path (AudPath::UserDir), basename}); + bool success = g_file_set_contents (filename, data, len, nullptr); + + g_key_file_free (rcfile); + g_free (data); + + return success; +} + +/* Note: Winamp 2.x had a +/- 20 dB range. + * Winamp 5.x had a +/- 12 dB range, which we use here. */ +#define FROM_WINAMP_VAL(x) ((31.5 - (x)) * (12.0 / 31.5)) +#define TO_WINAMP_VAL(x) (round (31.5 - (x) * (31.5 / 12.0))) + +EXPORT Index<EqualizerPreset> aud_import_winamp_presets (VFSFile & file) +{ + char header[31]; + char bands[11]; + char preset_name[181]; + + Index<EqualizerPreset> list; + + if (file.fread (header, 1, sizeof header) != sizeof header || + strncmp (header, "Winamp EQ library file v1.1", 27)) + return list; + + while (file.fread (preset_name, 1, 180) == 180) + { + preset_name[180] = 0; /* protect against buffer overflow */ + + if (file.fseek (77, VFS_SEEK_CUR)) /* unknown crap --asphyx */ + break; + + if (file.fread (bands, 1, 11) != 11) + break; + + EqualizerPreset & preset = list.append (String (preset_name)); + preset.preamp = FROM_WINAMP_VAL (bands[10]); + + for (int i = 0; i < AUD_EQ_NBANDS; i ++) + preset.bands[i] = FROM_WINAMP_VAL (bands[i]); + } + + return list; +} + +EXPORT bool aud_export_winamp_preset (const EqualizerPreset & preset, VFSFile & file) +{ + char name[257]; + char bands[11]; + + if (file.fwrite ("Winamp EQ library file v1.1\x1a!--", 1, 31) != 31) + return false; + + strncpy (name, preset.name, 257); + + if (file.fwrite (name, 1, 257) != 257) + return false; + + for (int i = 0; i < AUD_EQ_NBANDS; i ++) + bands[i] = TO_WINAMP_VAL (preset.bands[i]); + + bands[10] = TO_WINAMP_VAL (preset.preamp); + + if (file.fwrite (bands, 1, 11) != 11) + return false; + + return true; +} + +EXPORT bool aud_save_preset_file (const EqualizerPreset & preset, VFSFile & file) +{ + GKeyFile * rcfile = g_key_file_new (); + + g_key_file_set_double (rcfile, "Equalizer preset", "Preamp", preset.preamp); + + for (int i = 0; i < AUD_EQ_NBANDS; i ++) + g_key_file_set_double (rcfile, "Equalizer preset", + str_printf ("Band%d", i), preset.bands[i]); + + size_t len; + char * data = g_key_file_to_data (rcfile, & len, nullptr); + + bool success = (file.fwrite (data, 1, len) == (int64_t) len); + + g_key_file_free (rcfile); + g_free (data); + + return success; +} + +EXPORT bool aud_load_preset_file (EqualizerPreset & preset, VFSFile & file) +{ + GKeyFile * rcfile = g_key_file_new (); + + Index<char> data = file.read_all (); + + if (! data.len () || ! g_key_file_load_from_data (rcfile, data.begin (), + data.len (), G_KEY_FILE_NONE, nullptr)) + { + g_key_file_free (rcfile); + return false; + } + + preset.name = String (""); + preset.preamp = g_key_file_get_double (rcfile, "Equalizer preset", "Preamp", nullptr); + + for (int i = 0; i < AUD_EQ_NBANDS; i ++) + preset.bands[i] = g_key_file_get_double (rcfile, "Equalizer preset", + str_printf ("Band%d", i), nullptr); + + g_key_file_free (rcfile); + + return true; +} diff --git a/src/audacious/equalizer.c b/src/libaudcore/equalizer.cc index 1b74f31..ebaa438 100644 --- a/src/audacious/equalizer.c +++ b/src/libaudcore/equalizer.cc @@ -23,20 +23,18 @@ * - tallica */ -#include <glib.h> +#include "equalizer.h" +#include "internal.h" + +#include <assert.h> #include <math.h> #include <pthread.h> #include <string.h> -#include <libaudcore/audstrings.h> -#include <libaudcore/hook.h> - -#include "equalizer.h" -#include "misc.h" -#include "types.h" - -#define EQ_BANDS AUD_EQUALIZER_NBANDS -#define MAX_CHANNELS 10 +#include "audio.h" +#include "audstrings.h" +#include "hook.h" +#include "runtime.h" /* Q value for band-pass filters 1.2247 = (3/2)^(1/2) * Gives 4 dB suppression at Fc*2 and Fc/2 */ @@ -46,23 +44,23 @@ /* These are not the historical WinAmp frequencies, because the IIR filters used * here are designed for each frequency to be twice the previous. Using WinAmp * frequencies leads to too much gain in some bands and too little in others. */ -static const float CF[EQ_BANDS] = {31.25, 62.5, 125, 250, 500, 1000, 2000, +static const float CF[AUD_EQ_NBANDS] = {31.25, 62.5, 125, 250, 500, 1000, 2000, 4000, 8000, 16000}; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -static bool_t active; +static bool active; static int channels, rate; -static float a[EQ_BANDS][2]; /* A weights */ -static float b[EQ_BANDS][2]; /* B weights */ -static float wqv[MAX_CHANNELS][EQ_BANDS][2]; /* Circular buffer for W data */ -static float gv[MAX_CHANNELS][EQ_BANDS]; /* Gain factor for each channel and band */ -static int K; /* Number of used eq bands */ +static float a[AUD_EQ_NBANDS][2]; /* A weights */ +static float b[AUD_EQ_NBANDS][2]; /* B weights */ +static float wqv[AUD_MAX_CHANNELS][AUD_EQ_NBANDS][2]; /* Circular buffer for W data */ +static float gv[AUD_MAX_CHANNELS][AUD_EQ_NBANDS]; /* Gain factor for each channel and band */ +static int K; /* Number of used EQ bands */ /* 2nd order band-pass filter design */ -static void bp2 (float *a, float *b, float fc, float q) +static void bp2 (float *a, float *b, float fc) { float th = 2 * M_PI * fc; - float C = (1 - tanf (th * q / 2)) / (1 + tanf (th * q / 2)); + float C = (1 - tanf (th * Q / 2)) / (1 + tanf (th * Q / 2)); a[0] = (1 + C) * cosf (th); a[1] = -C; @@ -72,22 +70,21 @@ static void bp2 (float *a, float *b, float fc, float q) void eq_set_format (int new_channels, int new_rate) { - int k; - pthread_mutex_lock (& mutex); channels = new_channels; rate = new_rate; - /* Calculate number of active filters */ - K = EQ_BANDS; + /* Calculate number of active filters: the center frequency must be less + * than rate/2Q to avoid singularities in the tangent used in bp2() */ + K = AUD_EQ_NBANDS; - while (CF[K - 1] > (float) rate / 2.2) + while (K > 0 && CF[K - 1] > (float) rate / (2.005 * Q)) K --; /* Generate filter taps */ - for (k = 0; k < K; k ++) - bp2 (a[k], b[k], CF[k] / (float) rate, Q); + for (int k = 0; k < K; k ++) + bp2 (a[k], b[k], CF[k] / (float) rate); /* Reset state */ memset (wqv[0][0], 0, sizeof wqv); @@ -97,13 +94,16 @@ void eq_set_format (int new_channels, int new_rate) static void eq_set_bands_real (double preamp, double *values) { - float adj[EQ_BANDS]; - for (int i = 0; i < EQ_BANDS; i ++) + float adj[AUD_EQ_NBANDS]; + + for (int i = 0; i < AUD_EQ_NBANDS; i ++) adj[i] = preamp + values[i]; - for (int c = 0; c < MAX_CHANNELS; c ++) - for (int i = 0; i < EQ_BANDS; i ++) - gv[c][i] = pow (10, adj[i] / 20) - 1; + for (int c = 0; c < AUD_MAX_CHANNELS; c ++) + { + for (int i = 0; i < AUD_EQ_NBANDS; i ++) + gv[c][i] = pow (10, adj[i] / 20) - 1; + } } void eq_filter (float *data, int samples) @@ -156,21 +156,21 @@ static void eq_update (void *data, void *user) { pthread_mutex_lock (& mutex); - active = get_bool (NULL, "equalizer_active"); + active = aud_get_bool (nullptr, "equalizer_active"); - double values[EQ_BANDS]; - eq_get_bands (values); - eq_set_bands_real (get_double (NULL, "equalizer_preamp"), values); + double values[AUD_EQ_NBANDS]; + aud_eq_get_bands (values); + eq_set_bands_real (aud_get_double (nullptr, "equalizer_preamp"), values); pthread_mutex_unlock (& mutex); } void eq_init (void) { - eq_update (NULL, NULL); - hook_associate ("set equalizer_active", eq_update, NULL); - hook_associate ("set equalizer_preamp", eq_update, NULL); - hook_associate ("set equalizer_bands", eq_update, NULL); + eq_update (nullptr, nullptr); + hook_associate ("set equalizer_active", eq_update, nullptr); + hook_associate ("set equalizer_preamp", eq_update, nullptr); + hook_associate ("set equalizer_bands", eq_update, nullptr); } void eq_cleanup (void) @@ -180,35 +180,34 @@ void eq_cleanup (void) hook_dissociate ("set equalizer_bands", eq_update); } -void eq_set_bands (const double *values) +EXPORT void aud_eq_set_bands (const double values[AUD_EQ_NBANDS]) { - char *string = double_array_to_str (values, EQ_BANDS); - g_return_if_fail (string); - set_str (NULL, "equalizer_bands", string); - str_unref (string); + StringBuf string = double_array_to_str (values, AUD_EQ_NBANDS); + aud_set_str (nullptr, "equalizer_bands", string); } -void eq_get_bands (double *values) +EXPORT void aud_eq_get_bands (double values[AUD_EQ_NBANDS]) { - memset (values, 0, sizeof (double) * EQ_BANDS); - char *string = get_str (NULL, "equalizer_bands"); - str_to_double_array (string, values, EQ_BANDS); - str_unref (string); + memset (values, 0, sizeof (double) * AUD_EQ_NBANDS); + String string = aud_get_str (nullptr, "equalizer_bands"); + str_to_double_array (string, values, AUD_EQ_NBANDS); } -void eq_set_band (int band, double value) +EXPORT void aud_eq_set_band (int band, double value) { - g_return_if_fail (band >= 0 && band < EQ_BANDS); - double values[EQ_BANDS]; - eq_get_bands (values); + assert (band >= 0 && band < AUD_EQ_NBANDS); + + double values[AUD_EQ_NBANDS]; + aud_eq_get_bands (values); values[band] = value; - eq_set_bands (values); + aud_eq_set_bands (values); } -double eq_get_band (int band) +EXPORT double aud_eq_get_band (int band) { - g_return_val_if_fail (band >= 0 && band < EQ_BANDS, 0); - double values[EQ_BANDS]; - eq_get_bands (values); + assert (band >= 0 && band < AUD_EQ_NBANDS); + + double values[AUD_EQ_NBANDS]; + aud_eq_get_bands (values); return values[band]; } diff --git a/src/libaudcore/equalizer.h b/src/libaudcore/equalizer.h new file mode 100644 index 0000000..30b28c6 --- /dev/null +++ b/src/libaudcore/equalizer.h @@ -0,0 +1,51 @@ +/* + * equalizer.h + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_EQUALIZER_H +#define LIBAUDCORE_EQUALIZER_H + +#include <libaudcore/index.h> +#include <libaudcore/objects.h> + +class VFSFile; + +#define AUD_EQ_NBANDS 10 +#define AUD_EQ_MAX_GAIN 12 + +struct EqualizerPreset { + String name; + float preamp; + float bands[AUD_EQ_NBANDS]; +}; + +void aud_eq_set_bands (const double values[AUD_EQ_NBANDS]); +void aud_eq_get_bands (double values[AUD_EQ_NBANDS]); +void aud_eq_set_band (int band, double value); +double aud_eq_get_band (int band); + +Index<EqualizerPreset> aud_eq_read_presets (const char * basename); +bool aud_eq_write_presets (const Index<EqualizerPreset> & list, const char * basename); + +bool aud_load_preset_file (EqualizerPreset & preset, VFSFile & file); +bool aud_save_preset_file (const EqualizerPreset & preset, VFSFile & file); + +Index<EqualizerPreset> aud_import_winamp_presets (VFSFile & file); +bool aud_export_winamp_preset (const EqualizerPreset & preset, VFSFile & file); + +#endif /* LIBAUDCORE_EQUALIZER_H */ diff --git a/src/libaudcore/eventqueue.c b/src/libaudcore/eventqueue.c deleted file mode 100644 index ee92c03..0000000 --- a/src/libaudcore/eventqueue.c +++ /dev/null @@ -1,122 +0,0 @@ -/* - * eventqueue.c - * Copyright 2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <pthread.h> -#include <string.h> - -#include "core.h" -#include "hook.h" - -typedef struct { - char * name; - void * data; - void (* destroy) (void *); - int source; -} Event; - -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -static GList * events; - -static bool_t event_execute (Event * event) -{ - pthread_mutex_lock (& mutex); - - g_source_remove (event->source); - events = g_list_remove (events, event); - - pthread_mutex_unlock (& mutex); - - hook_call (event->name, event->data); - - str_unref (event->name); - if (event->destroy) - event->destroy (event->data); - - g_slice_free (Event, event); - return FALSE; -} - -EXPORT void event_queue_full (int time, const char * name, void * data, void (* destroy) (void *)) -{ - Event * event = g_slice_new (Event); - event->name = str_get (name); - event->data = data; - event->destroy = destroy; - - pthread_mutex_lock (& mutex); - - event->source = g_timeout_add (time, (GSourceFunc) event_execute, event); - events = g_list_prepend (events, event); - - pthread_mutex_unlock (& mutex); -} - -EXPORT void event_queue_cancel (const char * name, void * data) -{ - pthread_mutex_lock (& mutex); - - GList * node = events; - while (node) - { - Event * event = node->data; - GList * next = node->next; - - if (! strcmp (event->name, name) && (! data || event->data == data)) - { - g_source_remove (event->source); - events = g_list_delete_link (events, node); - - str_unref (event->name); - if (event->destroy) - event->destroy (event->data); - - g_slice_free (Event, event); - } - - node = next; - } - - pthread_mutex_unlock (& mutex); -} - -EXPORT void event_queue_cancel_all (void) -{ - pthread_mutex_lock (& mutex); - - GList * node = events; - while (node) - { - Event * event = node->data; - GList * next = node->next; - - g_source_remove (event->source); - events = g_list_delete_link (events, node); - - str_unref (event->name); - if (event->destroy) - event->destroy (event->data); - - g_slice_free (Event, event); - - node = next; - } - - pthread_mutex_unlock (& mutex); -} diff --git a/src/libaudcore/eventqueue.cc b/src/libaudcore/eventqueue.cc new file mode 100644 index 0000000..c65a439 --- /dev/null +++ b/src/libaudcore/eventqueue.cc @@ -0,0 +1,110 @@ +/* + * eventqueue.cc + * Copyright 2011-2014 John Lindgren, MichaĆ Lipski + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "hook.h" + +#include <pthread.h> +#include <string.h> + +#include "internal.h" +#include "list.h" +#include "mainloop.h" +#include "objects.h" + +struct Event : public ListNode +{ + String name; + void * data; + void (* destroy) (void *); + + Event (const char * name, void * data, EventDestroyFunc destroy) : + name (name), + data (data), + destroy (destroy) {} + + ~Event () + { + if (destroy) + destroy (data); + } +}; + +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static List<Event> events; +static QueuedFunc queued_events; + +static void events_execute (void *) +{ + pthread_mutex_lock (& mutex); + + Event * event; + while ((event = events.head ())) + { + events.remove (event); + + pthread_mutex_unlock (& mutex); + + hook_call (event->name, event->data); + delete event; + + pthread_mutex_lock (& mutex); + } + + pthread_mutex_unlock (& mutex); +} + +EXPORT void event_queue (const char * name, void * data, EventDestroyFunc destroy) +{ + pthread_mutex_lock (& mutex); + + if (! events.head ()) + queued_events.queue (events_execute, nullptr); + + events.append (new Event (name, data, destroy)); + + pthread_mutex_unlock (& mutex); +} + +EXPORT void event_queue_cancel (const char * name, void * data) +{ + pthread_mutex_lock (& mutex); + + Event * event = events.head (); + while (event) + { + Event * next = events.next (event); + + if (! strcmp (event->name, name) && (! data || event->data == data)) + { + events.remove (event); + delete event; + } + + event = next; + } + + pthread_mutex_unlock (& mutex); +} + +void event_queue_cancel_all () +{ + pthread_mutex_lock (& mutex); + events.clear (); + pthread_mutex_unlock (& mutex); +} diff --git a/src/audacious/fft.c b/src/libaudcore/fft.cc index 09a6602..071e23d 100644 --- a/src/audacious/fft.c +++ b/src/libaudcore/fft.cc @@ -17,17 +17,19 @@ * the use of this software. */ -#include <complex.h> -#include <math.h> +#include "internal.h" -#include "fft.h" +#include <complex> +#include <math.h> #define N 512 /* size of the DFT */ #define LOGN 9 /* log N (base 2) */ +typedef std::complex<float> Complex; + static float hamming[N]; /* hamming window, scaled to sum to 1 */ static int reversed[N]; /* bit-reversal table */ -static float complex roots[N / 2]; /* N-th roots of unity */ +static Complex roots[N / 2]; /* N-th roots of unity */ static char generated = 0; /* set if tables have been generated */ /* Reverse the order of the lowest LOGN bits in an integer. */ @@ -57,7 +59,7 @@ static void generate_tables (void) for (int n = 0; n < N; n ++) reversed[n] = bit_reverse (n); for (int n = 0; n < N / 2; n ++) - roots[n] = cexpf (2 * M_PI * I * n / N); + roots[n] = exp (Complex (0, 2 * M_PI * n / N)); generated = 1; } @@ -67,7 +69,7 @@ static void generate_tables (void) * operations. Each group contains (2^s)/2 butterflies, and each butterfly has * a span of (2^s)/2. The twiddle factors are nth roots of unity where n = 2^s. */ -static void do_fft (float complex a[N]) +static void do_fft (Complex a[N]) { int half = 1; /* (2^s)/2 */ int inv = N / 2; /* N/(2^s) */ @@ -81,8 +83,8 @@ static void do_fft (float complex a[N]) /* loop through butterflies */ for (int b = 0, r = 0; b < half; b ++, r += inv) { - float complex even = a[g + b]; - float complex odd = roots[r] * a[g + half + b]; + Complex even = a[g + b]; + Complex odd = roots[r] * a[g + half + b]; a[g + b] = even + odd; a[g + half + b] = even - odd; } @@ -102,7 +104,7 @@ void calc_freq (const float data[N], float freq[N / 2]) /* input is filtered by a Hamming window */ /* input values are in bit-reversed order */ - float complex a[N]; + Complex a[N]; for (int n = 0; n < N; n ++) a[reversed[n]] = data[n] * hamming[n]; @@ -111,8 +113,8 @@ void calc_freq (const float data[N], float freq[N / 2]) /* output values are divided by N */ /* frequencies from 1 to N/2-1 are doubled */ for (int n = 0; n < N / 2 - 1; n ++) - freq[n] = 2 * cabsf (a[1 + n]) / N; + freq[n] = 2 * abs (a[1 + n]) / N; /* frequency N/2 is not doubled */ - freq[N / 2 - 1] = cabsf (a[N / 2]) / N; + freq[N / 2 - 1] = abs (a[N / 2]) / N; } diff --git a/src/audacious/general.h b/src/libaudcore/history.cc index c9a7c9e..4dab916 100644 --- a/src/audacious/general.h +++ b/src/libaudcore/history.cc @@ -1,5 +1,5 @@ /* - * general.h + * history.c * Copyright 2011 John Lindgren * * Redistribution and use in source and binary forms, with or without @@ -17,17 +17,33 @@ * the use of this software. */ -#ifndef AUDACIOUS_GENERAL_H -#define AUDACIOUS_GENERAL_H +#include "audstrings.h" +#include "runtime.h" -#include "plugins.h" +#include <string.h> -void general_init (void); -void general_cleanup (void); +#define MAX_ENTRIES 30 -bool_t general_plugin_start (PluginHandle * plugin); -void general_plugin_stop (PluginHandle * plugin); +EXPORT String aud_history_get (int entry) +{ + StringBuf name = str_printf ("entry%d", entry); + String path = aud_get_str ("history", name); + return (path[0] ? path : String ()); +} -PluginHandle * general_plugin_by_widget (/* GtkWidget * */ void * widget); +EXPORT void aud_history_add (const char * path) +{ + String add = String (path); -#endif + for (int i = 0; i < MAX_ENTRIES; i ++) + { + StringBuf name = str_printf ("entry%d", i); + String old = aud_get_str ("history", name); + aud_set_str ("history", name, add); + + if (! strcmp (old, path)) + break; + + add = old; + } +} diff --git a/src/libaudcore/hook.c b/src/libaudcore/hook.c deleted file mode 100644 index e9f4e39..0000000 --- a/src/libaudcore/hook.c +++ /dev/null @@ -1,141 +0,0 @@ -/* - * hook.c - * Copyright 2011-2012 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <pthread.h> - -#include "core.h" -#include "hook.h" - -typedef struct { - HookFunction func; - void * user; - int lock_count; - bool_t remove_flag; -} HookItem; - -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -static GHashTable * hooks; /* of (GQueue of (HookItem *) *) */ - -EXPORT void hook_associate (const char * name, HookFunction func, void * user) -{ - pthread_mutex_lock (& mutex); - - if (! hooks) - hooks = g_hash_table_new_full (g_str_hash, g_str_equal, - (GDestroyNotify) str_unref, (GDestroyNotify) g_queue_free); - - GQueue * list = g_hash_table_lookup (hooks, name); - - if (! list) - g_hash_table_insert (hooks, str_get (name), list = g_queue_new ()); - - HookItem * item = g_slice_new (HookItem); - item->func = func; - item->user = user; - item->lock_count = 0; - item->remove_flag = FALSE; - - g_queue_push_tail (list, item); - - pthread_mutex_unlock (& mutex); -} - -EXPORT void hook_dissociate_full (const char * name, HookFunction func, void * user) -{ - pthread_mutex_lock (& mutex); - - if (! hooks) - goto DONE; - - GQueue * list = g_hash_table_lookup (hooks, name); - - if (! list) - goto DONE; - - for (GList * node = list->head; node;) - { - HookItem * item = node->data; - GList * next = node->next; - - if (item->func == func && (! user || item->user == user)) - { - if (item->lock_count) - item->remove_flag = TRUE; - else - { - g_queue_delete_link (list, node); - g_slice_free (HookItem, item); - } - } - - node = next; - } - - if (! list->head) - g_hash_table_remove (hooks, name); - -DONE: - pthread_mutex_unlock (& mutex); -} - -EXPORT void hook_call (const char * name, void * data) -{ - pthread_mutex_lock (& mutex); - - if (! hooks) - goto DONE; - - GQueue * list = g_hash_table_lookup (hooks, name); - - if (! list) - goto DONE; - - for (GList * node = list->head; node;) - { - HookItem * item = node->data; - - if (! item->remove_flag) - { - item->lock_count ++; - pthread_mutex_unlock (& mutex); - - item->func (data, item->user); - - pthread_mutex_lock (& mutex); - item->lock_count --; - } - - GList * next = node->next; - - if (item->remove_flag && ! item->lock_count) - { - g_queue_delete_link (list, node); - g_slice_free (HookItem, item); - } - - node = next; - } - - if (! list->head) - g_hash_table_remove (hooks, name); - -DONE: - pthread_mutex_unlock (& mutex); -} diff --git a/src/libaudcore/hook.cc b/src/libaudcore/hook.cc new file mode 100644 index 0000000..3b8b80e --- /dev/null +++ b/src/libaudcore/hook.cc @@ -0,0 +1,141 @@ +/* + * hook.c + * Copyright 2011-2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "hook.h" + +#include <pthread.h> + +#include "index.h" +#include "internal.h" +#include "multihash.h" +#include "objects.h" +#include "runtime.h" + +struct HookItem { + HookFunction func; + void * user; +}; + +struct HookList +{ + Index<HookItem> items; + int use_count; + + void compact () + { + auto is_empty = [] (const HookItem & item) + { return ! item.func; }; + + items.remove_if (is_empty); + } +}; + +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static SimpleHash<String, HookList> hooks; + +EXPORT void hook_associate (const char * name, HookFunction func, void * user) +{ + pthread_mutex_lock (& mutex); + + String key (name); + HookList * list = hooks.lookup (key); + if (! list) + list = hooks.add (key, HookList ()); + + list->items.append (func, user); + + pthread_mutex_unlock (& mutex); +} + +EXPORT void hook_dissociate (const char * name, HookFunction func, void * user) +{ + pthread_mutex_lock (& mutex); + + String key (name); + HookList * list = hooks.lookup (key); + if (! list) + goto DONE; + + for (HookItem & item : list->items) + { + if (item.func == func && (! user || item.user == user)) + item.func = nullptr; + } + + if (! list->use_count) + { + list->compact (); + if (! list->items.len ()) + hooks.remove (key); + } + +DONE: + pthread_mutex_unlock (& mutex); +} + +EXPORT void hook_call (const char * name, void * data) +{ + pthread_mutex_lock (& mutex); + + String key (name); + HookList * list = hooks.lookup (key); + if (! list) + goto DONE; + + list->use_count ++; + + /* note: the list may grow (but not shrink) during the hook call */ + for (int i = 0; i < list->items.len (); i ++) + { + /* copy locally to prevent race condition */ + HookItem item = list->items[i]; + + if (item.func) + { + pthread_mutex_unlock (& mutex); + item.func (data, item.user); + pthread_mutex_lock (& mutex); + } + } + + list->use_count --; + + if (! list->use_count) + { + list->compact (); + if (! list->items.len ()) + hooks.remove (key); + } + +DONE: + pthread_mutex_unlock (& mutex); +} + +void leak_cb (const String & name, HookList & list, void *) +{ + AUDWARN ("Hook not disconnected: %s (%d)\n", (const char *) name, list.items.len ()); +} + +void hook_cleanup () +{ + pthread_mutex_lock (& mutex); + hooks.iterate (leak_cb, nullptr); + hooks.clear (); + pthread_mutex_unlock (& mutex); +} diff --git a/src/libaudcore/hook.h b/src/libaudcore/hook.h index 646359b..bdca358 100644 --- a/src/libaudcore/hook.h +++ b/src/libaudcore/hook.h @@ -1,6 +1,6 @@ /* * hook.h - * Copyright 2011 John Lindgren + * Copyright 2011-2014 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -20,6 +20,8 @@ #ifndef LIBAUDCORE_HOOK_H #define LIBAUDCORE_HOOK_H +#include <libaudcore/templates.h> + typedef void (* HookFunction) (void * data, void * user); /* Adds <func> to the list of functions to be called when the hook <name> is @@ -27,27 +29,87 @@ typedef void (* HookFunction) (void * data, void * user); void hook_associate (const char * name, HookFunction func, void * user); /* Removes all instances matching <func> and <user> from the list of functions - * to be called when the hook <name> is triggered. If <user> is NULL, all + * to be called when the hook <name> is triggered. If <user> is nullptr, all * instances matching <func> are removed. */ -void hook_dissociate_full (const char * name, HookFunction func, void * user); - -#define hook_dissociate(n, f) hook_dissociate_full (n, f, NULL) +void hook_dissociate (const char * name, HookFunction func, void * user = nullptr); /* Triggers the hook <name>. */ void hook_call (const char * name, void * data); +typedef void (* EventDestroyFunc) (void * data); + /* Schedules a call of the hook <name> from the program's main loop, to be - * executed in <time> milliseconds. If <destroy> is not NULL, it will be called + * executed in <time> milliseconds. If <destroy> is not nullptr, it will be called * on <data> after the hook is called. */ -void event_queue_full (int time, const char * name, void * data, void (* destroy) (void *)); - -#define event_queue(n, d) event_queue_full (0, n, d, NULL) +void event_queue (const char * name, void * data, EventDestroyFunc destroy = nullptr); -/* Cancels pending hook calls matching <name> and <data>. If <data> is NULL, +/* Cancels pending hook calls matching <name> and <data>. If <data> is nullptr, * all hook calls matching <name> are canceled. */ void event_queue_cancel (const char * name, void * data); -/* Cancels all pending hook calls. */ -void event_queue_cancel_all (void); +/* Convenience wrapper for C++ classes. Allows non-static member functions to + * be used as hook callbacks. The HookReceiver should be made a member of the + * class in question so that hook_dissociate() is called automatically from the + * destructor. */ +template<class T, class D = void> +class HookReceiver +{ +public: + HookReceiver (const char * hook, T * target, void (T::* func) (D)) : + hook (hook), + target (target), + func (func) + { + hook_associate (hook, run, this); + } + + ~HookReceiver () + { hook_dissociate (hook, run, this); } + + HookReceiver (const HookReceiver &) = delete; + void operator= (const HookReceiver &) = delete; + +private: + const char * const hook; + T * const target; + void (T::* const func) (D); + + static void run (void * d, void * recv_) + { + auto recv = (HookReceiver *) recv_; + (recv->target->* recv->func) (aud::from_ptr<D> (d)); + } +}; + +/* Partial specialization for data-less hooks. */ +template<class T> +class HookReceiver<T, void> +{ +public: + HookReceiver (const char * hook, T * target, void (T::* func) ()) : + hook (hook), + target (target), + func (func) + { + hook_associate (hook, run, this); + } + + ~HookReceiver () + { hook_dissociate (hook, run, this); } + + HookReceiver (const HookReceiver &) = delete; + void operator= (const HookReceiver &) = delete; + +private: + const char * const hook; + T * const target; + void (T::* const func) (); + + static void run (void *, void * recv_) + { + auto recv = (HookReceiver *) recv_; + (recv->target->* recv->func) (); + } +}; #endif /* LIBAUDCORE_HOOK_H */ diff --git a/src/audacious/i18n.h b/src/libaudcore/i18n.h index 5b5205c..5b5205c 100644 --- a/src/audacious/i18n.h +++ b/src/libaudcore/i18n.h diff --git a/src/libaudcore/index.c b/src/libaudcore/index.c deleted file mode 100644 index c2f7f39..0000000 --- a/src/libaudcore/index.c +++ /dev/null @@ -1,213 +0,0 @@ -/* - * index.c - * Copyright 2009-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <assert.h> -#include <stdlib.h> -#include <string.h> - -#include <glib.h> - -#include "core.h" -#include "index.h" - -struct _Index { - void * * data; - int count, size; - void * sdata[16]; -}; - -typedef struct { - int (* compare) (const void * a, const void * b); -} CompareWrapper; - -typedef struct { - int (* compare) (const void * a, const void * b, void * data); - void * data; -} CompareWrapper2; - -EXPORT Index * index_new (void) -{ - Index * index = g_new0 (Index, 1); - index->data = index->sdata; - index->size = ARRAY_LEN (index->sdata); - return index; -} - -EXPORT void index_free (Index * index) -{ - if (index->data != index->sdata) - g_free (index->data); - - g_free (index); -} - -EXPORT void index_free_full (Index * index, IndexFreeFunc func) -{ - for (int i = 0; i < index->count; i ++) - func (index->data[i]); - - index_free (index); -} - -EXPORT int index_count (Index * index) -{ - return index->count; -} - -EXPORT void index_allocate (Index * index, int size) -{ - assert (size >= 0); - - if (index->size >= size) - return; - - if (index->size < 64) - index->size = 64; - - while (index->size < size) - index->size <<= 1; - - if (index->data == index->sdata) - { - index->data = g_new (void *, index->size); - memcpy (index->data, index->sdata, sizeof index->sdata); - } - else - index->data = g_renew (void *, index->data, index->size); -} - -EXPORT void * index_get (Index * index, int at) -{ - assert (at >= 0 && at < index->count); - - return index->data[at]; -} - -EXPORT void index_set (Index * index, int at, void * value) -{ - assert (at >= 0 && at < index->count); - - index->data[at] = value; -} - -static void make_room (Index * index, int at, int count) -{ - index_allocate (index, index->count + count); - - if (at < index->count) - memmove (index->data + at + count, index->data + at, sizeof (void *) * (index->count - at)); - - index->count += count; -} - -EXPORT void index_insert (Index * index, int at, void * value) -{ - if (at == -1) - at = index->count; - - assert (at >= 0 && at <= index->count); - - make_room (index, at, 1); - index->data[at] = value; -} - -EXPORT void index_copy_set (Index * source, int from, Index * target, int to, int count) -{ - assert (count >= 0); - assert (from >= 0 && from + count <= source->count); - assert (to >= 0 && to + count <= target->count); - - memmove (target->data + to, source->data + from, sizeof (void *) * count); -} - -EXPORT void index_copy_insert (Index * source, int from, Index * target, int to, int count) -{ - if (to == -1) - to = target->count; - if (count == -1) - count = source->count - from; - - assert (count >= 0); - assert (from >= 0 && from + count <= source->count); - assert (to >= 0 && to <= target->count); - - make_room (target, to, count); - - if (source == target && to <= from) - index_copy_set (source, from + count, target, to, count); - else if (source == target && to <= from + count) - { - index_copy_set (source, from, target, to, to - from); - index_copy_set (source, to + count, target, to + (to - from), count - (to - from)); - } - else - index_copy_set (source, from, target, to, count); -} - -EXPORT void index_delete (Index * index, int at, int count) -{ - if (count == -1) - count = index->count - at; - - assert (count >= 0); - assert (at >= 0 && at + count <= index->count); - - index_copy_set (index, at + count, index, at, index->count - (at + count)); - - index->count -= count; -} - -EXPORT void index_delete_full (Index * index, int at, int count, IndexFreeFunc func) -{ - if (count == -1) - count = index->count - at; - - assert (count >= 0); - assert (at >= 0 && at + count <= index->count); - - for (int i = at; i < at + count; i ++) - func (index->data[i]); - - index_delete (index, at, count); -} - -static int index_compare (const void * ap, const void * bp, void * _wrapper) -{ - CompareWrapper * wrapper = _wrapper; - return wrapper->compare (* (const void * *) ap, * (const void * *) bp); -} - -EXPORT void index_sort (Index * index, int (* compare) (const void *, const void *)) -{ - CompareWrapper wrapper = {compare}; - g_qsort_with_data (index->data, index->count, sizeof (void *), index_compare, & wrapper); -} - -static int index_compare2 (const void * ap, const void * bp, void * _wrapper) -{ - CompareWrapper2 * wrapper = _wrapper; - return wrapper->compare (* (const void * *) ap, * (const void * *) bp, wrapper->data); -} - -EXPORT void index_sort_with_data (Index * index, int (* compare) - (const void * a, const void * b, void * data), void * data) -{ - CompareWrapper2 wrapper = {compare, data}; - g_qsort_with_data (index->data, index->count, sizeof (void *), index_compare2, & wrapper); -} diff --git a/src/libaudcore/index.cc b/src/libaudcore/index.cc new file mode 100644 index 0000000..75aa559 --- /dev/null +++ b/src/libaudcore/index.cc @@ -0,0 +1,196 @@ +/* + * index.cc + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "index.h" +#include "internal.h" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include <glib.h> /* for g_qsort_with_data */ + +static void do_fill (void * data, int len, aud::FillFunc fill_func) +{ + if (fill_func) + fill_func (data, len); + else + memset (data, 0, len); +} + +static void do_erase (void * data, int len, aud::EraseFunc erase_func) +{ + if (erase_func) + erase_func (data, len); +} + +EXPORT void IndexBase::clear (aud::EraseFunc erase_func) +{ + __sync_sub_and_fetch (& misc_bytes_allocated, m_size); + + do_erase (m_data, m_len, erase_func); + free (m_data); + + m_data = nullptr; + m_len = 0; + m_size = 0; +} + +EXPORT void * IndexBase::insert (int pos, int len) +{ + assert (pos <= m_len); + assert (len >= 0); + + if (pos < 0) + pos = m_len; /* insert at end */ + + if (m_size < m_len + len) + { + /* never allocate less than 16 bytes */ + int new_size = aud::max (m_size, 16); + + /* next try 4/3 current size, biased toward multiples of 4 */ + if (new_size < m_len + len) + new_size = (new_size + 2) / 3 * 4; + + /* use requested size if still too small */ + if (new_size < m_len + len) + new_size = m_len + len; + + void * new_data = realloc (m_data, new_size); + if (! new_data) + throw std::bad_alloc (); /* nothing changed yet */ + + __sync_add_and_fetch (& misc_bytes_allocated, new_size - m_size); + + m_data = new_data; + m_size = new_size; + } + + memmove ((char *) m_data + pos + len, (char *) m_data + pos, m_len - pos); + m_len += len; + + return (char *) m_data + pos; +} + +EXPORT void IndexBase::insert (int pos, int len, aud::FillFunc fill_func) +{ + void * to = insert (pos, len); + + if (fill_func) + fill_func (to, len); + else + memset (to, 0, len); +} + +EXPORT void IndexBase::insert (const void * from, int pos, int len, aud::CopyFunc copy_func) +{ + void * to = insert (pos, len); + + if (copy_func) + copy_func (from, to, len); + else + memcpy (to, from, len); +} + +EXPORT void IndexBase::remove (int pos, int len, aud::EraseFunc erase_func) +{ + assert (pos >= 0 && pos <= m_len); + assert (len <= m_len - pos); + + if (len < 0) + len = m_len - pos; /* remove all following */ + + do_erase ((char *) m_data + pos, len, erase_func); + memmove ((char *) m_data + pos, (char *) m_data + pos + len, m_len - pos - len); + m_len -= len; +} + +EXPORT void IndexBase::erase (int pos, int len, aud::FillFunc fill_func, aud::EraseFunc erase_func) +{ + assert (pos >= 0 && pos <= m_len); + assert (len <= m_len - pos); + + if (len < 0) + len = m_len - pos; /* erase all following */ + + do_erase ((char *) m_data + pos, len, erase_func); + do_fill ((char *) m_data + pos, len, fill_func); +} + +EXPORT void IndexBase::shift (int from, int to, int len, aud::FillFunc fill_func, aud::EraseFunc erase_func) +{ + assert (len >= 0 && len <= m_len); + assert (from >= 0 && from + len <= m_len); + assert (to >= 0 && to + len <= m_len); + + int erase_len = aud::min (len, abs (to - from)); + + if (to < from) + do_erase ((char *) m_data + to, erase_len, erase_func); + else + do_erase ((char *) m_data + to + len - erase_len, erase_len, erase_func); + + memmove ((char *) m_data + to, (char *) m_data + from, len); + + if (to < from) + do_fill ((char *) m_data + from + len - erase_len, erase_len, fill_func); + else + do_fill ((char *) m_data + from, erase_len, fill_func); +} + +EXPORT void IndexBase::move_from (IndexBase & b, int from, int to, int len, + bool expand, bool collapse, aud::FillFunc fill_func, aud::EraseFunc erase_func) +{ + assert (this != & b); + assert (from >= 0 && from <= b.m_len); + assert (len <= b.m_len - from); + + if (len < 0) + len = b.m_len - from; /* copy all following */ + + if (expand) + { + assert (to <= m_len); + if (to < 0) + to = m_len; /* insert at end */ + + insert (to, len); + } + else + { + assert (to >= 0 && to <= m_len - len); + do_erase ((char *) m_data + to, len, erase_func); + } + + memcpy ((char *) m_data + to, (char *) b.m_data + from, len); + + if (collapse) + { + memmove ((char *) b.m_data + from, (char *) b.m_data + from + len, b.m_len - from - len); + b.m_len -= len; + } + else + do_fill ((char *) b.m_data + from, len, fill_func); +} + +EXPORT void IndexBase::sort (CompareFunc compare, int elemsize, void * userdata) +{ + g_qsort_with_data (m_data, m_len / elemsize, elemsize, compare, userdata); +} diff --git a/src/libaudcore/index.h b/src/libaudcore/index.h index 3990ff8..56dbdc5 100644 --- a/src/libaudcore/index.h +++ b/src/libaudcore/index.h @@ -1,6 +1,6 @@ /* * index.h - * Copyright 2009-2013 John Lindgren + * Copyright 2014 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -20,78 +20,200 @@ #ifndef LIBAUDCORE_INDEX_H #define LIBAUDCORE_INDEX_H -/* An "index" is an opaque structure representing a list of pointers. It is - * used primarily to store Audacious playlists, but can be useful for other - * purposes as well. */ - -struct _Index; -typedef struct _Index Index; - -typedef void (* IndexFreeFunc) (void * value); - -/* Returns a new, empty index. */ -Index * index_new (void); - -/* Destroys <index>. */ -void index_free (Index * index); - -/* Destroys <index>, first calling <func> on each pointer stored in it. */ -void index_free_full (Index * index, IndexFreeFunc func); - -/* Returns the number of pointers stored in <index>. */ -int index_count (Index * index); - -/* Preallocates space to store <size> pointers in <index>. This can be used to - * avoid repeated memory allocations when adding pointers to <index> one by one. - * The value returned by index_count() does not changed until the pointers are - * actually added. */ -void index_allocate (Index * index, int size); - -/* Returns the value stored in <index> at position <at>. */ -void * index_get (Index * index, int at); - -/* Stores <value> in <index> at position <at>, overwriting the previous value. */ -void index_set (Index * index, int at, void * value); - -/* Stores <value> in <index> at position <at>, shifting the previous value (if - * any) and those following it forward to make room. If <at> is -1, <value> is - * added to the end of <index>. */ -void index_insert (Index * index, int at, void * value); - -/* Copies <count> pointers from <source>, starting at position <from>, to - * <target>, starting at position <to>. Existing pointers in <target> are - * overwritten. Overlapping regions are handled correctly if <source> and - * <target> are the same index. */ -void index_copy_set (Index * source, int from, Index * target, int to, int count); - -/* Copies <count> pointers from <source>, starting at position <from>, to - * <target>, starting at position <to>. Existing pointers in <target> are - * shifted forward to make room. All cases are handled correctly if <source> - * and <target> are the same index, including the case where <to> is between - * <from> and <from + count>. If <to> is -1, the pointers are added to the end - * of <target>. If <count> is -1, copying continues until the end of <source> - * is reached. */ -void index_copy_insert (Index * source, int from, Index * target, int to, int count); - -/* Removes <count> pointers from <index>, start at position <at>. Any following - * pointers are shifted backward to close the gap. If <count> is -1, all - * pointers from <at> to the end of <index> are removed. */ -void index_delete (Index * index, int at, int count); - -/* Like index_delete(), but first calls <func> on any pointer that is being - * removed. */ -void index_delete_full (Index * index, int at, int count, IndexFreeFunc func); - -/* Sort the entries in <index> using the quick-sort algorithm with the given - * comparison function. The return value of <compare> should follow the - * convention used by strcmp(): negative if (a < b), zero if (a = b), positive - * if (a > b). */ -void index_sort (Index * index, int (* compare) (const void * a, const void * b)); - -/* Exactly like index_sort() except that <data> is forwarded to the comparison - * function. This allows comparison functions whose behavior changes depending - * on context. */ -void index_sort_with_data (Index * index, int (* compare) (const void * a, - const void * b, void * data), void * data); - -#endif /* LIBAUDCORE_INDEX_H */ +#include <libaudcore/templates.h> + +/* + * Index is a lightweight list class similar to std::vector, but with the + * following differences: + * - The base implementation is type-agnostic, so it only needs to be compiled + * once. Type-safety is provided by a thin template subclass. + * - Objects are moved in memory without calling any assignment operator. + * Be careful to use only objects that can handle this. + */ + +class IndexBase +{ +public: + typedef int (* CompareFunc) (const void * a, const void * b, void * userdata); + + constexpr IndexBase () : + m_data (nullptr), + m_len (0), + m_size (0) {} + + void clear (aud::EraseFunc erase_func); // use as destructor + + IndexBase (IndexBase && b) : + m_data (b.m_data), + m_len (b.m_len), + m_size (b.m_size) + { + b.m_data = nullptr; + b.m_len = 0; + b.m_size = 0; + } + + void steal (IndexBase && b, aud::EraseFunc erase_func) + { + if (this != & b) + { + clear (erase_func); + new (this) IndexBase (std::move (b)); + } + } + + void * begin () + { return m_data; } + const void * begin () const + { return m_data; } + void * end () + { return (char *) m_data + m_len; } + const void * end () const + { return (char *) m_data + m_len; } + + int len () const + { return m_len; } + + void * insert (int pos, int len); // no fill + void insert (int pos, int len, aud::FillFunc fill_func); + void insert (const void * from, int pos, int len, aud::CopyFunc copy_func); + void remove (int pos, int len, aud::EraseFunc erase_func); + void erase (int pos, int len, aud::FillFunc fill_func, aud::EraseFunc erase_func); + void shift (int from, int to, int len, aud::FillFunc fill_func, aud::EraseFunc erase_func); + + void move_from (IndexBase & b, int from, int to, int len, bool expand, + bool collapse, aud::FillFunc fill_func, aud::EraseFunc erase_func); + + void sort (CompareFunc compare, int elemsize, void * userdata); + +private: + void * m_data; + int m_len, m_size; +}; + +template<class T> +class Index : private IndexBase +{ +public: + typedef int (* CompareFunc) (const T & a, const T & b, void * userdata); + + constexpr Index () : + IndexBase () {} + + // use with care! + IndexBase & base () + { return * this; } + + void clear () + { IndexBase::clear (aud::erase_func<T> ()); } + ~Index () + { clear (); } + + Index (Index && b) : + IndexBase (std::move (b)) {} + void operator= (Index && b) + { steal (std::move (b), aud::erase_func<T> ()); } + + T * begin () + { return (T *) IndexBase::begin (); } + const T * begin () const + { return (const T *) IndexBase::begin (); } + T * end () + { return (T *) IndexBase::end (); } + const T * end () const + { return (const T *) IndexBase::end (); } + + int len () const + { return cooked (IndexBase::len ()); } + + T & operator[] (int i) + { return begin ()[i]; } + const T & operator[] (int i) const + { return begin ()[i]; } + + void insert (int pos, int len) + { IndexBase::insert (raw (pos), raw (len), aud::fill_func<T> ()); } + void insert (const T * from, int pos, int len) + { IndexBase::insert (from, raw (pos), raw (len), aud::copy_func<T> ()); } + void remove (int pos, int len) + { IndexBase::remove (raw (pos), raw (len), aud::erase_func<T> ()); } + void erase (int pos, int len) + { IndexBase::erase (raw (pos), raw (len), aud::fill_func<T> (), aud::erase_func<T> ()); } + void shift (int from, int to, int len) + { IndexBase::shift (raw (from), raw (to), raw (len), aud::fill_func<T> (), aud::erase_func<T> ()); } + + void move_from (Index<T> & b, int from, int to, int len, bool expand, bool collapse) + { IndexBase::move_from (b, raw (from), raw (to), raw (len), expand, + collapse, aud::fill_func<T> (), aud::erase_func<T> ()); } + + template<class ... Args> + T & append (Args && ... args) + { + return * aud::construct<T>::make (IndexBase::insert (-1, sizeof (T)), + std::forward<Args> (args) ...); + } + + int find (const T & val) const + { + for (const T * iter = begin (); iter != end (); iter ++) + { + if (* iter == val) + return iter - begin (); + } + + return -1; + } + + template<class F> + void remove_if (F func, bool clear_if_empty = false) + { + T * iter = begin (); + while (iter != end ()) + { + if (func (* iter)) + remove (iter - begin (), 1); + else + iter ++; + } + + if (clear_if_empty && ! len ()) + clear (); + } + + void sort (CompareFunc compare, void * userdata) + { + struct state_t { + CompareFunc compare; + void * userdata; + }; + + auto wrapper = [] (const void * a, const void * b, void * userdata) -> int + { + auto state = (const state_t *) userdata; + return state->compare (* (const T *) a, * (const T *) b, state->userdata); + }; + + const state_t state = {compare, userdata}; + IndexBase::sort (wrapper, sizeof (T), (void *) & state); + } + + // for use of Index as a raw data buffer + // unlike insert(), does not zero-fill any added space + void resize (int size) + { + static_assert (std::is_trivial<T>::value, "for basic types only"); + int diff = size - len (); + if (diff > 0) + IndexBase::insert (-1, raw (diff)); + else if (diff < 0) + IndexBase::remove (raw (size), -1, nullptr); + } + +private: + static constexpr int raw (int len) + { return len * sizeof (T); } + static constexpr int cooked (int len) + { return len / sizeof (T); } +}; + +#endif // LIBAUDCORE_INDEX_H diff --git a/src/libaudcore/inifile.c b/src/libaudcore/inifile.c deleted file mode 100644 index 6e13a48..0000000 --- a/src/libaudcore/inifile.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * inifile.c - * Copyright 2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include "audstrings.h" -#include "inifile.h" - -#include <glib.h> - -#include <string.h> - -static char * strskip (char * str) -{ - while (g_ascii_isspace (* str)) - str ++; - - return str; -} - -static char * strtrim (char * str) -{ - int len = strlen (str); - - while (len && g_ascii_isspace(str[len - 1])) - str[-- len] = 0; - - return str; -} - -EXPORT void inifile_parse (VFSFile * file, - void (* handle_heading) (const char * heading, void * data), - void (* handle_entry) (const char * key, const char * value, void * data), - void * data) -{ - int size = 512; - char * buf = g_new (char, size); - - char * pos = buf; - int len = 0; - bool_t eof = FALSE; - - while (1) - { - char * newline = memchr (pos, '\n', len); - - while (! newline && ! eof) - { - memmove (buf, pos, len); - pos = buf; - - if (len >= size - 1) - { - size <<= 1; - buf = g_renew (char, buf, size); - pos = buf; - } - - len += vfs_fread (buf + len, 1, size - 1 - len, file); - - if (len < size - 1) - eof = TRUE; - - newline = memchr (pos, '\n', len); - } - - if (newline) - * newline = 0; - else - pos[len] = 0; - - char * start = strskip (pos); - - switch (* start) - { - case 0: - case '#': - case ';': - break; - - case '[':; - char * end = strchr (start + 1, ']'); - if (! end) - break; - - * end = 0; - handle_heading (strtrim (strskip (start + 1)), data); - break; - - default:; - char * sep = strchr (start, '='); - if (! sep) - break; - - * sep = 0; - handle_entry (strtrim (start), strtrim (strskip (sep + 1)), data); - break; - } - - if (! newline) - break; - - len -= newline + 1 - pos; - pos = newline + 1; - } - - g_free (buf); -} - -EXPORT bool_t inifile_write_heading (VFSFile * file, const char * heading) -{ - SCONCAT3 (buf, "\n[", heading, "]\n"); - return (vfs_fwrite (buf, 1, sizeof buf - 1, file) == sizeof buf - 1); -} - -EXPORT bool_t inifile_write_entry (VFSFile * file, const char * key, const char * value) -{ - SCONCAT4 (buf, key, "=", value, "\n"); - return (vfs_fwrite (buf, 1, sizeof buf - 1, file) == sizeof buf - 1); -} diff --git a/src/libaudcore/inifile.cc b/src/libaudcore/inifile.cc new file mode 100644 index 0000000..046c14a --- /dev/null +++ b/src/libaudcore/inifile.cc @@ -0,0 +1,123 @@ +/* + * inifile.c + * Copyright 2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "inifile.h" + +#include <string.h> + +#include <glib.h> /* for g_ascii_isspace */ + +#include "audstrings.h" +#include "vfs.h" + +static char * strskip (char * str, char * end) +{ + while (str < end && g_ascii_isspace (* str)) + str ++; + + return str; +} + +static char * strtrim (char * str, char * end) +{ + while (end > str && g_ascii_isspace (end[-1])) + end --; + + * end = 0; + return str; +} + +EXPORT void IniParser::parse (VFSFile & file) +{ + int size = 512; + StringBuf buf (size); + + char * pos = buf; + int len = 0; + bool eof = false; + + while (1) + { + char * newline = (char *) memchr (pos, '\n', len); + + while (! newline && ! eof) + { + memmove (buf, pos, len); + pos = buf; + + if (len >= size - 1) + { + size <<= 1; + buf.resize (size); + pos = buf; + } + + len += file.fread (buf + len, 1, size - 1 - len); + + if (len < size - 1) + eof = true; + + newline = (char *) memchr (pos, '\n', len); + } + + char * end = newline ? newline : pos + len; + char * start = strskip (pos, end); + char * sep; + + if (start < end) + { + switch (* start) + { + case '#': + case ';': + break; + + case '[': + if ((end = (char *) memchr (start, ']', end - start))) + handle_heading (strtrim (strskip (start + 1, end), end)); + + break; + + default: + if ((sep = (char *) memchr (start, '=', end - start))) + handle_entry (strtrim (start, sep), strtrim (strskip (sep + 1, end), end)); + + break; + } + } + + if (! newline) + break; + + len -= newline + 1 - pos; + pos = newline + 1; + } +} + +EXPORT bool inifile_write_heading (VFSFile & file, const char * heading) +{ + StringBuf line = str_concat ({"\n[", heading, "]\n"}); + return (file.fwrite (line, 1, line.len ()) == line.len ()); +} + +EXPORT bool inifile_write_entry (VFSFile & file, const char * key, const char * value) +{ + StringBuf line = str_concat ({key, "=", value, "\n"}); + return (file.fwrite (line, 1, line.len ()) == line.len ()); +} diff --git a/src/libaudcore/inifile.h b/src/libaudcore/inifile.h index 87189c2..f9ee049 100644 --- a/src/libaudcore/inifile.h +++ b/src/libaudcore/inifile.h @@ -20,14 +20,23 @@ #ifndef LIBAUDCORE_INIFILE_H #define LIBAUDCORE_INIFILE_H -#include "vfs.h" +class VFSFile; -void inifile_parse (VFSFile * file, - void (* handle_heading) (const char * heading, void * data), - void (* handle_entry) (const char * key, const char * value, void * data), - void * data); +class IniParser +{ +public: + virtual ~IniParser () {} -bool_t inifile_write_heading (VFSFile * file, const char * heading); -bool_t inifile_write_entry (VFSFile * file, const char * key, const char * value); + void parse (VFSFile & file); + +protected: + virtual void handle_heading (const char * heading) = 0; + virtual void handle_entry (const char * key, const char * value) = 0; +}; + +bool inifile_write_heading (VFSFile & file, const char * heading) + __attribute__ ((warn_unused_result)); +bool inifile_write_entry (VFSFile & file, const char * key, const char * value) + __attribute__ ((warn_unused_result)); #endif /* LIBAUDCORE_INIFILE_H */ diff --git a/src/libaudcore/interface.cc b/src/libaudcore/interface.cc new file mode 100644 index 0000000..403f64d --- /dev/null +++ b/src/libaudcore/interface.cc @@ -0,0 +1,256 @@ +/* + * interface.c + * Copyright 2010-2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "interface.h" +#include "internal.h" + +#include <assert.h> + +#include "drct.h" +#include "hook.h" +#include "mainloop.h" +#include "plugin.h" +#include "plugins.h" +#include "runtime.h" + +struct MenuItem { + const char * name; + const char * icon; + void (* func) (); +}; + +static PluginHandle * current_plugin; +static PluginHandle * next_plugin; + +static IfacePlugin * current_interface; + +static aud::array<AudMenuID, Index<MenuItem>> menu_items; + +static void add_menu_items () +{ + for (AudMenuID id : aud::range<AudMenuID> ()) + { + for (MenuItem & item : menu_items[id]) + current_interface->plugin_menu_add (id, item.func, item.name, item.icon); + } +} + +static void remove_menu_items () +{ + for (AudMenuID id : aud::range<AudMenuID> ()) + { + for (MenuItem & item : menu_items[id]) + current_interface->plugin_menu_remove (id, item.func); + } +} + +static bool interface_load (PluginHandle * plugin) +{ + auto i = (IfacePlugin *) aud_plugin_get_header (plugin); + if (! i) + return false; + + AUDINFO ("Loading %s.\n", aud_plugin_get_name (plugin)); + + if (! i->init ()) + return false; + + current_interface = i; + + add_menu_items (); + + if (aud_get_bool (0, "show_interface")) + current_interface->show (true); + + return true; +} + +static void interface_unload () +{ + AUDINFO ("Unloading %s.\n", aud_plugin_get_name (current_plugin)); + + if (aud_get_bool (0, "show_interface")) + current_interface->show (false); + + remove_menu_items (); + + current_interface->cleanup (); + current_interface = nullptr; +} + +EXPORT void aud_ui_show (bool show) +{ + if (! current_interface) + return; + + aud_set_bool (0, "show_interface", show); + + current_interface->show (show); + + vis_activate (show); +} + +EXPORT bool aud_ui_is_shown () +{ + if (! current_interface) + return false; + + return aud_get_bool (0, "show_interface"); +} + +EXPORT void aud_ui_show_error (const char * message) +{ + if (aud_get_headless_mode ()) + AUDERR ("%s\n", message); + else + event_queue ("ui show error", String::raw_get (message), + (EventDestroyFunc) String::raw_unref); +} + +PluginHandle * iface_plugin_get_current () +{ + return current_plugin; +} + +bool iface_plugin_set_current (PluginHandle * plugin) +{ + if (current_interface) + { + // queue up interface switch + next_plugin = plugin; + + // restart main loop + aud_quit (); + + return true; + } + + if (! interface_load (plugin)) + return false; + + current_plugin = plugin; + return true; +} + +void interface_run () +{ + if (aud_get_headless_mode ()) + { + mainloop_run (); + + // call before shutting down + hook_call ("config save", nullptr); + } + else + { + vis_activate (aud_get_bool (0, "show_interface")); + + while (current_interface) + { + current_interface->run (); + + // call before unloading interface + hook_call ("config save", nullptr); + + interface_unload (); + + if (next_plugin) + { + // handle queued interface switch + aud_plugin_enable (next_plugin, true); + next_plugin = nullptr; + } + } + } +} + +EXPORT void aud_quit () +{ + if (current_interface) + current_interface->quit (); + else + mainloop_quit (); +} + +EXPORT void aud_plugin_menu_add (AudMenuID id, void (* func) (), const char * name, const char * icon) +{ + menu_items[id].append (name, icon, func); + + if (current_interface) + current_interface->plugin_menu_add (id, func, name, icon); +} + +EXPORT void aud_plugin_menu_remove (AudMenuID id, void (* func) ()) +{ + if (current_interface) + current_interface->plugin_menu_remove (id, func); + + auto is_match = [=] (const MenuItem & item) + { return item.func == func; }; + + menu_items[id].remove_if (is_match, true); +} + +EXPORT void aud_ui_show_about_window () +{ + if (current_interface) + current_interface->show_about_window (); +} + +EXPORT void aud_ui_hide_about_window () +{ + if (current_interface) + current_interface->hide_about_window (); +} + +EXPORT void aud_ui_show_filebrowser (bool open) +{ + if (current_interface) + current_interface->show_filebrowser (open); +} + +EXPORT void aud_ui_hide_filebrowser () +{ + if (current_interface) + current_interface->hide_filebrowser (); +} + +EXPORT void aud_ui_show_jump_to_song () +{ + if (current_interface) + current_interface->show_jump_to_song (); +} + +EXPORT void aud_ui_hide_jump_to_song () +{ + if (current_interface) + current_interface->hide_jump_to_song (); +} + +EXPORT void aud_ui_show_prefs_window () +{ + if (current_interface) + current_interface->show_prefs_window (); +} + +EXPORT void aud_ui_hide_prefs_window () +{ + if (current_interface) + current_interface->hide_prefs_window (); +} diff --git a/src/libaudcore/interface.h b/src/libaudcore/interface.h new file mode 100644 index 0000000..43ccb38 --- /dev/null +++ b/src/libaudcore/interface.h @@ -0,0 +1,53 @@ +/* + * interface.h + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_INTERFACE_H +#define LIBAUDCORE_INTERFACE_H + +#include <libaudcore/visualizer.h> + +enum class AudMenuID { + Main, + Playlist, + PlaylistAdd, + PlaylistRemove, + count +}; + +void aud_ui_show (bool show); +bool aud_ui_is_shown (); + +void aud_ui_show_error (const char * message); /* thread-safe */ + +void aud_ui_show_about_window (); +void aud_ui_hide_about_window (); +void aud_ui_show_filebrowser (bool open); +void aud_ui_hide_filebrowser (); +void aud_ui_show_jump_to_song (); +void aud_ui_hide_jump_to_song (); +void aud_ui_show_prefs_window (); +void aud_ui_hide_prefs_window (); + +void aud_plugin_menu_add (AudMenuID id, void (* func) (), const char * name, const char * icon); +void aud_plugin_menu_remove (AudMenuID id, void (* func) ()); + +void aud_visualizer_add (Visualizer * vis); +void aud_visualizer_remove (Visualizer * vis); + +#endif diff --git a/src/libaudcore/internal.h b/src/libaudcore/internal.h new file mode 100644 index 0000000..aa93cff --- /dev/null +++ b/src/libaudcore/internal.h @@ -0,0 +1,130 @@ +/* + * internal.h + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_INTERNAL_H +#define LIBAUDCORE_INTERNAL_H + +#include <stdint.h> +#include <sys/types.h> + +#include "index.h" +#include "objects.h" + +class Plugin; +class PluginHandle; +class VFSFile; +class Tuple; + +typedef bool (* DirForeachFunc) (const char * path, const char * basename, void * user); + +/* adder.cc */ +void adder_cleanup (); + +/* art.cc */ +void art_init (); +void art_cleanup (); + +/* art-search.cc */ +String art_search (const char * filename); + +/* charset.cc */ +void chardet_init (); +void chardet_cleanup (); + +/* config.cc */ +void config_load (); +void config_save (); +void config_cleanup (); + +/* effect.cc */ +void effect_start (int & channels, int & rate); +Index<float> & effect_process (Index<float> & data); +bool effect_flush (bool force); +Index<float> & effect_finish (Index<float> & data, bool end_of_playlist); +int effect_adjust_delay (int delay); + +bool effect_plugin_start (PluginHandle * plugin); +void effect_plugin_stop (PluginHandle * plugin); + +/* equalizer.cc */ +void eq_init (); +void eq_cleanup (); +void eq_set_format (int new_channels, int new_rate); +void eq_filter (float * data, int samples); + +/* eventqueue.cc */ +void event_queue_cancel_all (); + +/* fft.cc */ +void calc_freq (const float data[512], float freq[256]); + +/* hook.cc */ +void hook_cleanup (); + +/* interface.cc */ +PluginHandle * iface_plugin_probe (); +PluginHandle * iface_plugin_get_current (); +bool iface_plugin_set_current (PluginHandle * plugin); + +void interface_run (); + +/* playback.cc */ +/* do not call these; use aud_drct_play/stop() instead */ +void playback_play (int seek_time, bool pause); +void playback_stop (bool exiting = false); + +bool playback_check_serial (int serial); +bool playback_set_info (int entry, const String & filename, + PluginHandle * decoder, Tuple && tuple); + +/* probe-buffer.cc */ +VFSFile probe_buffer_new (const char * filename); + +/* runtime.cc */ +extern size_t misc_bytes_allocated; + +/* strpool.cc */ +void string_leak_check (); + +/* util.cc */ +const char * get_home_utf8 (); +bool dir_foreach (const char * path, DirForeachFunc func, void * user_data); +String write_temp_file (const void * data, int64_t len); + +bool same_basename (const char * a, const char * b); +const char * last_path_element (const char * path); +void cut_path_element (char * path, int pos); + +unsigned int32_hash (unsigned val); + +/* vis-runner.cc */ +void vis_runner_start_stop (bool playing, bool paused); +void vis_runner_pass_audio (int time, const Index<float> & data, int channels, int rate); +void vis_runner_flush (); +void vis_runner_enable (bool enable); + +/* visualization.cc */ +void vis_activate (bool activate); +void vis_send_clear (); +void vis_send_audio (const float * data, int channels); + +bool vis_plugin_start (PluginHandle * plugin); +void vis_plugin_stop (PluginHandle * plugin); + +#endif diff --git a/src/libaudcore/list.cc b/src/libaudcore/list.cc new file mode 100644 index 0000000..f2b7a5e --- /dev/null +++ b/src/libaudcore/list.cc @@ -0,0 +1,77 @@ +/* + * list.cpp + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "list.h" + +EXPORT void ListBase::insert_after (ListNode * prev, ListNode * node) +{ + ListNode * next; + + if (prev) + { + next = prev->next; + prev->next = node; + } + else + { + next = head; + head = node; + } + + node->prev = prev; + node->next = next; + + if (next) + next->prev = node; + else + tail = node; +} + +EXPORT void ListBase::remove (ListNode * node) +{ + ListNode * prev = node->prev; + ListNode * next = node->next; + + node->prev = nullptr; + node->next = nullptr; + + if (prev) + prev->next = next; + else + head = next; + + if (next) + next->prev = prev; + else + tail = prev; +} + +EXPORT void ListBase::clear (DestroyFunc destroy) +{ + ListNode * node = head; + while (node) + { + ListNode * next = node->next; + destroy (node); + node = next; + } + + head = nullptr; + tail = nullptr; +} diff --git a/src/libaudcore/list.h b/src/libaudcore/list.h new file mode 100644 index 0000000..623603d --- /dev/null +++ b/src/libaudcore/list.h @@ -0,0 +1,82 @@ +/* + * list.h + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_LIST_H +#define LIBAUDCORE_LIST_H + +struct ListNode +{ + friend class ListBase; + +private: + ListNode * prev = nullptr; + ListNode * next = nullptr; +}; + +class ListBase +{ +protected: + typedef void (* DestroyFunc) (ListNode *); + + ListNode * head = nullptr; + ListNode * tail = nullptr; + + void insert_after (ListNode * prev, ListNode * node); + void remove (ListNode * node); + void clear (DestroyFunc destroy); + + static ListNode * prev (ListNode * node) + { return node->prev; } + static ListNode * next (ListNode * node) + { return node->next; } +}; + +template<class C> +class List : private ListBase +{ +public: + C * head () const + { return (C *) ListBase::head; } + C * tail () const + { return (C *) ListBase::tail; } + + static C * prev (C * node) + { return (C *) ListBase::prev (node); } + static C * next (C * node) + { return (C *) ListBase::next (node); } + + void insert_after (C * prev, C * node) + { ListBase::insert_after (prev, node); } + void remove (C * node) + { ListBase::remove (node); } + + void prepend (C * node) + { insert_after (nullptr, node); } + void append (C * node) + { insert_after (tail (), node); } + + void clear () + { ListBase::clear (destroy); } + +private: + static void destroy (ListNode * node) + { delete (C *) node; } +}; + +#endif // LIBAUDCORE_LIST_H diff --git a/src/libaudcore/logger.cc b/src/libaudcore/logger.cc new file mode 100644 index 0000000..5fa300b --- /dev/null +++ b/src/libaudcore/logger.cc @@ -0,0 +1,125 @@ +/* + * logger.cc + * Copyright 2014 John Lindgren and William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "audstrings.h" +#include "index.h" +#include "runtime.h" +#include "tinylock.h" + +#include <stdio.h> + +namespace audlog { + +struct HandlerData { + Handler handler; + Level level; +}; + +static TinyRWLock lock; +static Index<HandlerData> handlers; +static Level stderr_level = Warning; +static Level min_level = Warning; + +EXPORT void set_stderr_level (Level level) +{ + tiny_lock_write (& lock); + + min_level = stderr_level = level; + + for (const HandlerData & h : handlers) + { + if (h.level < min_level) + min_level = h.level; + } + + tiny_unlock_write (& lock); +} + +EXPORT void subscribe (Handler handler, Level level) +{ + tiny_lock_write (& lock); + + handlers.append (handler, level); + + if (level < min_level) + min_level = level; + + tiny_unlock_write (& lock); +} + +EXPORT void unsubscribe (Handler handler) +{ + tiny_lock_write (& lock); + + auto is_match = [=] (const HandlerData & data) + { return data.handler == handler; }; + + handlers.remove_if (is_match, true); + + min_level = stderr_level; + + for (const HandlerData & h : handlers) + { + if (h.level < min_level) + min_level = h.level; + } + + tiny_unlock_write (& lock); +} + +EXPORT const char * get_level_name (Level level) +{ + switch (level) + { + case Debug: return "DEBUG"; + case Info: return "INFO"; + case Warning: return "WARNING"; + case Error: return "ERROR"; + }; + + return nullptr; +} + +EXPORT void log (Level level, const char * file, int line, const char * func, + const char * format, ...) +{ + tiny_lock_read (& lock); + + if (level >= min_level) + { + va_list args; + va_start (args, format); + StringBuf message = str_vprintf (format, args); + va_end (args); + + if (level >= stderr_level) + fprintf (stderr, "%s %s:%d [%s]: %s", get_level_name (level), file, + line, func, (const char *) message); + + for (const HandlerData & h : handlers) + { + if (level >= h.level) + h.handler (level, file, line, func, message); + } + } + + tiny_unlock_read (& lock); +} + +} // namespace audlog diff --git a/src/libaudcore/mainloop.cc b/src/libaudcore/mainloop.cc new file mode 100644 index 0000000..e979770 --- /dev/null +++ b/src/libaudcore/mainloop.cc @@ -0,0 +1,218 @@ +/* + * mainloop.cc + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "mainloop.h" + +#include <pthread.h> +#include <glib.h> + +#include "runtime.h" + +struct QueuedFuncRunner +{ + QueuedFunc * queued; + QueuedFunc::Func func; + void * data; + int serial; + + bool run () + { + if (__sync_add_and_fetch (& queued->serial, 0) != serial) + return false; + + func (data); + return true; + } +}; + +#ifdef USE_QT + +#include <QCoreApplication> + +class QueuedFuncEvent : public QEvent +{ +public: + QueuedFuncEvent (const QueuedFuncRunner & runner) : + QEvent (User), + runner (runner) {} + + void run () + { runner.run (); } + +private: + QueuedFuncRunner runner; +}; + +class QueuedFuncRouter : public QObject +{ +protected: + void customEvent (QEvent * event) + { dynamic_cast<QueuedFuncEvent *> (event)->run (); } +}; + +static QueuedFuncRouter router; + +class QueuedFuncTimer : public QObject +{ +public: + QueuedFuncTimer (int interval_ms, const QueuedFuncRunner & runner) : + interval_ms (interval_ms), + runner (runner) + { + moveToThread (router.thread ()); // main thread + + // The timer cannot be started until QCoreApplication is instantiated. + // Send ourselves an event and wait till it comes back, then start the timer. + QCoreApplication::postEvent (this, new QEvent (QEvent::User), Qt::HighEventPriority); + } + +protected: + void customEvent (QEvent * event) + { startTimer (interval_ms); } + + void timerEvent (QTimerEvent * event) + { + if (! runner.run ()) + deleteLater (); + } + +private: + int interval_ms; + QueuedFuncRunner runner; +}; + +static QCoreApplication * qt_mainloop; + +#endif // USE_QT + +static int queued_wrapper (void * ptr) +{ + auto runner = (QueuedFuncRunner *) ptr; + runner->run (); + delete runner; + return false; +} + +static int timed_wrapper (void * ptr) +{ + auto runner = (QueuedFuncRunner *) ptr; + if (runner->run ()) + return true; + + delete runner; + return false; +} + +static GMainLoop * glib_mainloop; + +EXPORT void QueuedFunc::queue (Func func, void * data) +{ + int new_serial = __sync_add_and_fetch (& serial, 1); + +#ifdef USE_QT + if (aud_get_mainloop_type () == MainloopType::Qt) + QCoreApplication::postEvent (& router, + new QueuedFuncEvent ({this, func, data, new_serial}), + Qt::HighEventPriority); + else +#endif + g_idle_add_full (G_PRIORITY_HIGH, queued_wrapper, + new QueuedFuncRunner ({this, func, data, new_serial}), nullptr); +} + +EXPORT void QueuedFunc::start (int interval_ms, Func func, void * data) +{ + _running = true; + int new_serial = __sync_add_and_fetch (& serial, 1); + +#ifdef USE_QT + if (aud_get_mainloop_type () == MainloopType::Qt) + new QueuedFuncTimer (interval_ms, {this, func, data, new_serial}); + else +#endif + g_timeout_add (interval_ms, timed_wrapper, + new QueuedFuncRunner ({this, func, data, new_serial})); +} + +EXPORT void QueuedFunc::stop () +{ + __sync_add_and_fetch (& serial, 1); + _running = false; +} + +static pthread_mutex_t mainloop_mutex = PTHREAD_MUTEX_INITIALIZER; + +EXPORT void mainloop_run () +{ + pthread_mutex_lock (& mainloop_mutex); + +#ifdef USE_QT + if (aud_get_mainloop_type () == MainloopType::Qt) + { + if (! qt_mainloop) + { + int dummy_argc = 0; + qt_mainloop = new QCoreApplication (dummy_argc, nullptr); + pthread_mutex_unlock (& mainloop_mutex); + + qt_mainloop->exec (); + + pthread_mutex_lock (& mainloop_mutex); + delete qt_mainloop; + qt_mainloop = nullptr; + } + } + else +#endif + { + if (! glib_mainloop) + { + glib_mainloop = g_main_loop_new (nullptr, true); + pthread_mutex_unlock (& mainloop_mutex); + + g_main_loop_run (glib_mainloop); + + pthread_mutex_lock (& mainloop_mutex); + g_main_loop_unref (glib_mainloop); + glib_mainloop = nullptr; + } + } + + pthread_mutex_unlock (& mainloop_mutex); +} + +EXPORT void mainloop_quit () +{ + pthread_mutex_lock (& mainloop_mutex); + +#ifdef USE_QT + if (aud_get_mainloop_type () == MainloopType::Qt) + { + if (qt_mainloop) + qt_mainloop->quit (); + } + else +#endif + { + if (glib_mainloop) + g_main_loop_quit (glib_mainloop); + } + + pthread_mutex_unlock (& mainloop_mutex); +} diff --git a/src/libaudcore/mainloop.h b/src/libaudcore/mainloop.h new file mode 100644 index 0000000..7c7d615 --- /dev/null +++ b/src/libaudcore/mainloop.h @@ -0,0 +1,50 @@ +/* + * mainloop.h + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +/* Main loop abstraction layer which can use either GLib or Qt as a backend. + * The API is completely thread-safe and can thus be used as a means to call + * back into the main thread from a worker thread. */ + +#ifndef LIBAUDCORE_MAINLOOP_H +#define LIBAUDCORE_MAINLOOP_H + +// all instances must be declared static +class QueuedFunc +{ + friend struct QueuedFuncRunner; + +public: + typedef void (* Func) (void * data); + + void queue (Func func, void * data); + void start (int interval_ms, Func func, void * data); + void stop (); + + bool running () + { return _running; } + +private: + int serial; + bool _running; +}; + +void mainloop_run (); +void mainloop_quit (); + +#endif // LIBAUDCORE_MAINLOOP_H diff --git a/src/libaudcore/multihash.c b/src/libaudcore/multihash.c deleted file mode 100644 index 2be8839..0000000 --- a/src/libaudcore/multihash.c +++ /dev/null @@ -1,153 +0,0 @@ -/* - * multihash.c - * Copyright 2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include "multihash.h" - -#include <glib.h> - -#define INITIAL_SIZE 256 /* must be a power of two */ - -static void resize_channel (MultihashTable * table, MultihashChannel * channel, unsigned size) -{ - MultihashNode * * buckets = g_new0 (MultihashNode *, size); - - for (int b1 = 0; b1 < channel->size; b1 ++) - { - MultihashNode * node = channel->buckets[b1]; - - while (node) - { - MultihashNode * next = node->next; - - unsigned hash = table->hash_func (node); - unsigned b2 = (hash >> MULTIHASH_SHIFT) & (size - 1); - MultihashNode * * node_ptr = & buckets[b2]; - - node->next = * node_ptr; - * node_ptr = node; - - node = next; - } - } - - g_free (channel->buckets); - channel->buckets = buckets; - channel->size = size; -} - -EXPORT int multihash_lookup (MultihashTable * table, const void * data, - unsigned hash, MultihashAddFunc add, MultihashActionFunc action, void * state) -{ - unsigned c = hash & (MULTIHASH_CHANNELS - 1); - MultihashChannel * channel = & table->channels[c]; - - int status = 0; - tiny_lock (& channel->lock); - - if (! channel->buckets) - { - if (! add) - goto DONE; - - channel->buckets = g_new0 (MultihashNode *, INITIAL_SIZE); - channel->size = INITIAL_SIZE; - channel->used = 0; - } - - unsigned b = (hash >> MULTIHASH_SHIFT) & (channel->size - 1); - MultihashNode * * node_ptr = & channel->buckets[b]; - MultihashNode * node = * node_ptr; - - while (node && ! table->match_func (node, data, hash)) - { - node_ptr = & node->next; - node = * node_ptr; - } - - if (node) - { - status |= MULTIHASH_FOUND; - - MultihashNode * next = node->next; - - if (action && action (node, state)) - { - status |= MULTIHASH_REMOVED; - - * node_ptr = next; - - channel->used --; - if (channel->used < channel->size >> 2 && channel->size > INITIAL_SIZE) - resize_channel (table, channel, channel->size >> 1); - } - } - else if (add && (node = add (data, hash, state))) - { - status |= MULTIHASH_ADDED; - - * node_ptr = node; - node->next = NULL; - - channel->used ++; - if (channel->used > channel->size) - resize_channel (table, channel, channel->size << 1); - } - -DONE: - tiny_unlock (& channel->lock); - return status; -} - -EXPORT void multihash_iterate (MultihashTable * table, MultihashActionFunc action, void * state) -{ - for (int c = 0; c < MULTIHASH_CHANNELS; c ++) - tiny_lock (& table->channels[c].lock); - - for (int c = 0; c < MULTIHASH_CHANNELS; c ++) - { - MultihashChannel * channel = & table->channels[c]; - - for (int b = 0; b < channel->size; b ++) - { - MultihashNode * * node_ptr = & channel->buckets[b]; - MultihashNode * node = * node_ptr; - - while (node) - { - MultihashNode * next = node->next; - - if (action (node, state)) - { - * node_ptr = next; - channel->used --; - } - else - node_ptr = & node->next; - - node = next; - } - } - - if (channel->used < channel->size >> 2 && channel->size > INITIAL_SIZE) - resize_channel (table, channel, channel->size >> 1); - } - - for (int c = 0; c < MULTIHASH_CHANNELS; c ++) - tiny_unlock (& table->channels[c].lock); -} diff --git a/src/libaudcore/multihash.cc b/src/libaudcore/multihash.cc new file mode 100644 index 0000000..160239c --- /dev/null +++ b/src/libaudcore/multihash.cc @@ -0,0 +1,173 @@ +/* + * multihash.c + * Copyright 2013-2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "multihash.h" + +EXPORT void HashBase::add (Node * node, unsigned hash) +{ + if (! buckets) + { + buckets = new Node *[InitialSize](); + size = InitialSize; + } + + unsigned b = hash & (size - 1); + node->next = buckets[b]; + node->hash = hash; + buckets[b] = node; + + used ++; + if (used > size) + resize (size << 1); +} + +EXPORT HashBase::Node * HashBase::lookup (MatchFunc match, const void * data, + unsigned hash, NodeLoc * loc) const +{ + if (! buckets) + return nullptr; + + unsigned b = hash & (size - 1); + Node * * node_ptr = & buckets[b]; + Node * node = * node_ptr; + + while (1) + { + if (! node) + return nullptr; + + if (node->hash == hash && match (node, data)) + break; + + node_ptr = & node->next; + node = * node_ptr; + } + + if (loc) + { + loc->ptr = node_ptr; + loc->next = node->next; + } + + return node; +} + +EXPORT void HashBase::remove (const NodeLoc & loc) +{ + * loc.ptr = loc.next; + + used --; + if (used < size >> 2 && size > InitialSize) + resize (size >> 1); +} + +EXPORT void HashBase::iterate (FoundFunc func, void * state) +{ + for (unsigned b = 0; b < size; b ++) + { + Node * * ptr = & buckets[b]; + Node * node = * ptr; + + while (node) + { + Node * next = node->next; + + if (func (node, state)) + { + * ptr = next; + used --; + } + else + ptr = & node->next; + + node = next; + } + } + + if (used < size >> 2 && size > InitialSize) + resize (size >> 1); +} + +void HashBase::resize (unsigned new_size) +{ + Node * * new_buckets = new Node *[new_size](); + + for (unsigned b1 = 0; b1 < size; b1 ++) + { + Node * node = buckets[b1]; + + while (node) + { + Node * next = node->next; + + unsigned b2 = node->hash & (new_size - 1); + node->next = new_buckets[b2]; + new_buckets[b2] = node; + + node = next; + } + } + + delete[] buckets; + buckets = new_buckets; + size = new_size; +} + +EXPORT int MultiHash::lookup (const void * data, unsigned hash, AddFunc add, + FoundFunc found, void * state) +{ + const unsigned c = (hash >> Shift) & (Channels - 1); + HashBase & channel = channels[c]; + + int status = 0; + tiny_lock (& locks[c]); + + HashBase::NodeLoc loc; + Node * node = channel.lookup (match, data, hash, & loc); + + if (node) + { + status |= Found; + if (found && found (node, state)) + { + status |= Removed; + channel.remove (loc); + } + } + else if (add && (node = add (data, state))) + { + status |= Added; + channel.add (node, hash); + } + + tiny_unlock (& locks[c]); + return status; +} + +EXPORT void MultiHash::iterate (FoundFunc func, void * state) +{ + for (TinyLock & lock : locks) + tiny_lock (& lock); + + for (HashBase & channel : channels) + channel.iterate (func, state); + + for (TinyLock & lock : locks) + tiny_unlock (& lock); +} diff --git a/src/libaudcore/multihash.h b/src/libaudcore/multihash.h index 1e66b21..186e195 100644 --- a/src/libaudcore/multihash.h +++ b/src/libaudcore/multihash.h @@ -1,6 +1,6 @@ /* * multihash.h - * Copyright 2013 John Lindgren + * Copyright 2013-2014 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -20,76 +20,207 @@ #ifndef LIBAUDCORE_MULTIHASH_H #define LIBAUDCORE_MULTIHASH_H -#include <libaudcore/core.h> +#include <utility> #include <libaudcore/tinylock.h> -/* Multihash is a generic, thread-safe hash table. It scales well to multiple +/* HashBase is a low-level hash table implementation. It is used as a backend + * for SimpleHash as well as for a single channel of MultiHash. */ + +class HashBase +{ +public: + /* Skeleton structure containing internal members of a hash node. Actual + * node structures should be defined with Node as the first member. */ + struct Node { + Node * next; + unsigned hash; + }; + + /* Represents the location of a node within the table. */ + struct NodeLoc { + Node * * ptr; + Node * next; + }; + + /* Callback. Returns true if <node> matches <data>, otherwise false. */ + typedef bool (* MatchFunc) (const Node * node, const void * data); + + /* Callback. Called when a node is found. Returns true if <node> is to be + * removed, otherwise false. */ + typedef bool (* FoundFunc) (Node * node, void * state); + + constexpr HashBase () : + buckets (nullptr), + size (0), + used (0) {} + + ~HashBase () + { delete[] buckets; } + + int n_items () const + { return used; } + + /* Adds a node. Does not check for duplicates. */ + void add (Node * node, unsigned hash); + + /* Locates the node matching <data>. Returns null if no node is found. If + * <loc> is not null, also returns the location of the node, which can be + * used to remove it from the table. */ + Node * lookup (MatchFunc match, const void * data, unsigned hash, + NodeLoc * loc = nullptr) const; + + /* Removes a node, given a location returned by lookup_full(). */ + void remove (const NodeLoc & loc); + + /* Iterates over all nodes in the table, removing them as desired. */ + void iterate (FoundFunc func, void * state); + +private: + static constexpr unsigned InitialSize = 16; + + void resize (unsigned new_size); + + Node * * buckets; + unsigned size, used; +}; + +/* MultiHash is a generic, thread-safe hash table. It scales well to multiple * processors by the use of multiple channels, each with a separate lock. The * hash value of a given node decides what channel it is stored in. Hence, * different processors will tend to hit different channels, keeping lock - * contention to a minimum. The data structures are public in order to allow - * static initialization (setting all bytes to zero gives the initial state). - * The all-purpose lookup function enables a variety of atomic operations, such - * as allocating and adding a node only if not already present. */ - -#define MULTIHASH_CHANNELS 16 /* must be a power of two */ -#define MULTIHASH_SHIFT 4 /* log (base 2) of MULTIHASH_CHANNELS */ - -#define MULTIHASH_FOUND (1 << 0) -#define MULTIHASH_ADDED (1 << 1) -#define MULTIHASH_REMOVED (1 << 2) - -/* Skeleton structure containing internal member(s) of a multihash node. Actual - * node structures should be defined with MultihashNode as the first member. */ -typedef struct _MultihashNode { - struct _MultihashNode * next; -} MultihashNode; - -/* Single channel of a multihash table. For internal use only. */ -typedef struct { - TinyLock lock; - MultihashNode * * buckets; - unsigned size, used; -} MultihashChannel; - -/* Callback. Calculates (or retrieves) the hash value of <node>. */ -typedef unsigned (* MultihashFunc) (const MultihashNode * node); - -/* Callback. Returns TRUE if <node> matches <data>, otherwise FALSE. */ -typedef bool_t (* MultihashMatchFunc) (const MultihashNode * node, - const void * data, unsigned hash); - -/* Multihash table. <hash_func> and <match_func> should be initialized to - * functions appropriate for the type of data to be stored in the table. */ -typedef struct { - MultihashFunc hash_func; - MultihashMatchFunc match_func; - MultihashChannel channels[MULTIHASH_CHANNELS]; -} MultihashTable; - -/* Callback. May create a new node representing <data> to be added to the - * table. Returns the new node or NULL. */ -typedef MultihashNode * (* MultihashAddFunc) (const void * data, unsigned hash, void * state); - -/* Callback. Performs a user-defined action when a matching node is found. - * Doubles as a node removal function. Returns TRUE if <node> was freed and is - * to be removed, otherwise FALSE. */ -typedef bool_t (* MultihashActionFunc) (MultihashNode * node, void * state); - -/* All-purpose lookup function. The caller passes in the data to be looked up - * along with its hash value. The two callbacks are optional. <add> (if not - * NULL) is called if no matching node is found, and may return a new node to - * add to the table. <action> (if not NULL) is called if a matching node is - * found, and may return TRUE to remove the node from the table. <state> is - * forwarded to either callback. Returns the status of the lookup as a bitmask - * of MULTIHASH_FOUND, MULTIHASH_ADDED, and MULTIHASH_REMOVED. */ -int multihash_lookup (MultihashTable * table, const void * data, unsigned hash, - MultihashAddFunc add, MultihashActionFunc action, void * state); - -/* All-purpose iteration function. All channels of the table are locked - * simultaneously during the iteration to freeze the table in a consistent - * state. <action> is called on each node in order, and may return TRUE to - * remove the node from the table. <state> is forwarded to <action>. */ -void multihash_iterate (MultihashTable * table, MultihashActionFunc action, void * state); + * contention to a minimum. The all-purpose lookup function enables a variety + * of atomic operations, such as allocating and adding a node only if not + * already present. */ + +class MultiHash +{ +public: + static constexpr int Found = 1 << 0; + static constexpr int Added = 1 << 1; + static constexpr int Removed = 1 << 2; + + typedef HashBase::Node Node; + typedef HashBase::MatchFunc MatchFunc; + typedef HashBase::FoundFunc FoundFunc; + + /* Callback. May create a new node representing <data> to be added to the + * table. Returns the new node or null. */ + typedef Node * (* AddFunc) (const void * data, void * state); + + MultiHash (MatchFunc match) : + match (match), + locks (), + channels () {} + + /* All-purpose lookup function. The caller passes in the data to be looked + * up along with its hash value. The two callbacks are optional. <add> is + * called if no matching node is found, and may return a new node to add to + * the table. <found> is called if a matching node is found, and may return + * true to remove the node from the table. Returns the status of the lookup + * as a bitmask of Found, Added, and Removed. */ + int lookup (const void * data, unsigned hash, AddFunc add, FoundFunc found, void * state); + + /* All-purpose iteration function. All channels of the table are locked + * simultaneously during the iteration to freeze the table in a consistent + * state. <func> is called on each node in order, and may return true to + * remove the node from the table. */ + void iterate (FoundFunc func, void * state); + +private: + static constexpr int Channels = 16; /* must be a power of two */ + static constexpr int Shift = 24; /* bit shift for channel selection */ + + const MatchFunc match; + TinyLock locks[Channels]; + HashBase channels[Channels]; +}; + +template<class Key, class Value> +class SimpleHash : private HashBase +{ +public: + typedef void (* IterFunc) (const Key & key, Value & value, void * state); + + ~SimpleHash () + { clear (); } + + using HashBase::n_items; + + Value * lookup (const Key & key) + { + Node * node = (Node *) HashBase::lookup (match_cb, & key, key.hash ()); + return node ? & node->value : nullptr; + } + + Value * add (const Key & key, Value && value) + { + unsigned hash = key.hash (); + Node * node = (Node *) HashBase::lookup (match_cb, & key, hash); + + if (node) + node->value = std::move (value); + else + { + node = new Node (key, std::move (value)); + HashBase::add (node, hash); + } + + return & node->value; + } + + void remove (const Key & key) + { + NodeLoc loc; + Node * node = (Node *) HashBase::lookup (match_cb, & key, key.hash (), & loc); + + if (node) + { + delete node; + HashBase::remove (loc); + } + } + + void iterate (IterFunc func, void * state) + { + IterData data = {func, state}; + HashBase::iterate (iterate_cb, & data); + } + + void clear () + { HashBase::iterate (remove_cb, nullptr); } + +private: + struct Node : public HashBase::Node + { + Node (const Key & key, Value && value) : + key (key), + value (std::move (value)) {} + + Key key; + Value value; + }; + + struct IterData { + IterFunc func; + void * state; + }; + + static bool match_cb (const HashBase::Node * node, const void * data) + { return ((const Node *) node)->key == * (const Key *) data; } + + static bool remove_cb (HashBase::Node * node, void *) + { + delete (Node *) node; + return true; + } + + static bool iterate_cb (HashBase::Node * node0, void * data0) + { + Node * node = (Node *) node0; + IterData * data = (IterData *) data0; + data->func (node->key, node->value, data->state); + return false; + } +}; #endif /* LIBAUDCORE_MULTIHASH_H */ diff --git a/src/libaudcore/objects.h b/src/libaudcore/objects.h new file mode 100644 index 0000000..d6bce0f --- /dev/null +++ b/src/libaudcore/objects.h @@ -0,0 +1,272 @@ +/* + * objects.h + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_OBJECTS_H +#define LIBAUDCORE_OBJECTS_H + +#include <libaudcore/templates.h> + +// Stores array pointer together with deduced array length. + +template<class T> +struct ArrayRef +{ + const T * data; + int len; + + constexpr ArrayRef (decltype (nullptr) = nullptr) : + data (nullptr), + len (0) {} + + template<int N> + constexpr ArrayRef (const T (& array) [N]) : + data (array), + len (N) {} + + constexpr ArrayRef (const T * data, int len) : + data (data), + len (len) {} + + const T * begin () const + { return data; } + const T * end () const + { return data + len; } +}; + +// Smart pointer. Deletes object pointed to when the pointer goes out of scope. + +template<class T> +class SmartPtr +{ +public: + explicit constexpr SmartPtr (T * ptr = nullptr) : + ptr (ptr) {} + + ~SmartPtr () + { delete ptr; } + + void capture (T * ptr2) + { + delete ptr; + ptr = ptr2; + } + + void clear () + { capture (nullptr); } + + SmartPtr (SmartPtr && b) : + ptr (b.ptr) + { + b.ptr = nullptr; + } + + SmartPtr & operator= (SmartPtr && b) + { + if (this != & b) + { + capture (b.ptr); + b.ptr = nullptr; + } + return * this; + } + + explicit operator bool () const + { return (bool) ptr; } + + T * get () + { return ptr; } + const T * get () const + { return ptr; } + T & operator* () + { return (* ptr); } + const T & operator* () const + { return (* ptr); } + T * operator-> () + { return ptr; } + const T * operator-> () const + { return ptr; } + +private: + T * ptr; +}; + +template<class T, class ... Args> +SmartPtr<T> SmartNew (Args && ... args) +{ + return SmartPtr<T> (aud::construct<T>::make (operator new (sizeof (T)), + std::forward<Args> (args) ...)); +} + +// Wrapper class for a string stored in the string pool. + +class String +{ +public: + constexpr String () : + raw (nullptr) {} + + ~String () + { raw_unref (raw); } + + String (const String & b) : + raw (raw_ref (b.raw)) {} + + String & operator= (const String & b) + { + if (this != & b) + { + raw_unref (raw); + raw = raw_ref (b.raw); + } + return * this; + } + + String (String && b) : + raw (b.raw) + { + b.raw = nullptr; + } + + String & operator= (String && b) + { + if (this != & b) + { + raw_unref (raw); + raw = b.raw; + b.raw = nullptr; + } + return * this; + } + + bool operator== (const String & b) const + { return raw_equal (raw, b.raw); } + + explicit String (const char * str) : + raw (raw_get (str)) {} + + String (decltype (nullptr)) = delete; + + operator const char * () const + { return raw; } + + unsigned hash () const + { return raw_hash (raw); } + + // raw interface + // avoid using where possible + + static String from_raw (char * str) + { + String s; + s.raw = str; + return s; + } + + char * to_raw () + { + char * str = raw; + raw = nullptr; + return str; + } + + static char * raw_get (const char * str); + static char * raw_ref (const char * str); + static void raw_unref (char * str); + static unsigned raw_hash (const char * str); + static bool raw_equal (const char * str1, const char * str2); + +private: + char * raw; +}; + +struct StringStack; + +// Mutable string buffer, allocated on a stack to allow fast allocation. The +// price for this speed is that only the top string in the stack (i.e. the one +// most recently allocated) can be resized or deleted. The string is always +// null-terminated (i.e. str[str.len ()] == 0). Rules for the correct use of +// StringBuf can be summarized as follows: +// +// 1. Always declare StringBufs within function or block scope, never at file +// or class scope. Do not attempt to create a StringBuf with new or +// malloc(). +// 2. Only the first StringBuf declared in a function can be used as the +// return value. It is possible to create a second StringBuf and then +// transfer its contents to the first with steal(), but doing so carries +// a performance penalty. +// 3. Do not truncate the StringBuf by inserting null characters manually; +// instead, use resize(). + +class StringBuf +{ +public: + constexpr StringBuf () : + stack (nullptr), + m_data (nullptr), + m_len (0) {} + + // A length of -1 means to use all available space. This can be useful when + // the final length of the string is not known in advance, but keep in mind + // that you will not be able to create any further StringBufs until you call + // resize(). Also, the string will not be null-terminated in this case. + explicit StringBuf (int len) : + stack (nullptr), + m_data (nullptr), + m_len (0) + { + resize (len); + } + + StringBuf (StringBuf && other) : + stack (other.stack), + m_data (other.m_data), + m_len (other.m_len) + { + other.stack = nullptr; + other.m_data = nullptr; + other.m_len = 0; + } + + // only allowed for top (or null) string + ~StringBuf (); + + // only allowed for top (or null) string + void resize (int size); + void insert (int pos, const char * s, int len = -1); + void remove (int pos, int len); + + // only allowed for top two strings (or when one string is null) + void steal (StringBuf && other); + + // only allowed for top two strings + void combine (StringBuf && other); + + int len () const + { return m_len; } + + operator char * () + { return m_data; } + +private: + StringStack * stack; + char * m_data; + int m_len; +}; + +#endif // LIBAUDCORE_OBJECTS_H diff --git a/src/libaudcore/output.cc b/src/libaudcore/output.cc new file mode 100644 index 0000000..972352c --- /dev/null +++ b/src/libaudcore/output.cc @@ -0,0 +1,582 @@ +/* + * output.c + * Copyright 2009-2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "drct.h" +#include "output.h" +#include "runtime.h" + +#include <math.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> + +#include "equalizer.h" +#include "internal.h" +#include "plugin.h" +#include "plugins.h" + +static pthread_mutex_t mutex_major = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t mutex_minor = PTHREAD_MUTEX_INITIALIZER; + +#define LOCK_MAJOR pthread_mutex_lock (& mutex_major) +#define UNLOCK_MAJOR pthread_mutex_unlock (& mutex_major) +#define LOCK_MINOR pthread_mutex_lock (& mutex_minor) +#define UNLOCK_MINOR pthread_mutex_unlock (& mutex_minor) +#define LOCK_ALL do { LOCK_MAJOR; LOCK_MINOR; } while (0) +#define UNLOCK_ALL do { UNLOCK_MINOR; UNLOCK_MAJOR; } while (0) + +/* State variables. State changes that are allowed between LOCK_MINOR and + * UNLOCK_MINOR (all others must take place between LOCK_ALL and UNLOCK_ALL): + * s_paused -> true or false, s_flushed -> true, s_resetting -> true */ + +static bool s_input; /* input plugin connected */ +static bool s_output; /* output plugin connected */ +static bool s_gain; /* replay gain info set */ +static bool s_paused; /* paused */ +static bool s_flushed; /* flushed, writes ignored until resume */ +static bool s_resetting; /* resetting output system */ + +/* Condition variable linked to LOCK_MINOR. + * The input thread will wait if the following is true: + * ((! s_output || s_resetting) && ! s_flushed) + * Hence you must signal if you cause the inverse to be true: + * ((s_output && ! s_resetting) || s_flushed) */ + +static pthread_cond_t cond_minor = PTHREAD_COND_INITIALIZER; + +#define SIGNAL_MINOR pthread_cond_broadcast (& cond_minor) +#define WAIT_MINOR pthread_cond_wait (& cond_minor, & mutex_minor) + +static OutputPlugin * cop; +static int seek_time; +static String in_filename; +static Tuple in_tuple; +static int in_format, in_channels, in_rate; +static int out_format, out_channels, out_rate; +static int out_bytes_per_sec, out_bytes_held; +static int64_t in_frames, out_bytes_written; +static ReplayGainInfo gain_info; + +static bool change_op; +static OutputPlugin * new_op; + +static Index<float> buffer1; +static Index<char> buffer2; + +static inline int get_format () +{ + switch (aud_get_int (0, "output_bit_depth")) + { + case 16: return FMT_S16_NE; + case 24: return FMT_S24_NE; + case 32: return FMT_S32_NE; + default: return FMT_FLOAT; + } +} + +/* assumes LOCK_ALL, s_output */ +static void cleanup_output () +{ + if (! s_paused && ! s_flushed && ! s_resetting) + { + UNLOCK_MINOR; + cop->drain (); + LOCK_MINOR; + } + + s_output = false; + + buffer1.clear (); + buffer2.clear (); + + effect_flush (true); + cop->close_audio (); + vis_runner_start_stop (false, false); +} + +/* assumes LOCK_ALL, s_output */ +static void apply_pause () +{ + cop->pause (s_paused); + vis_runner_start_stop (true, s_paused); +} + +/* assumes LOCK_ALL, s_input */ +static void setup_output () +{ + int format = get_format (); + int channels = in_channels; + int rate = in_rate; + + effect_start (channels, rate); + eq_set_format (channels, rate); + + if (s_output && format == out_format && channels == out_channels && rate == + out_rate && ! cop->force_reopen) + { + AUDINFO ("Reusing output stream, format %d, %d channels, %d Hz.\n", format, channels, rate); + return; + } + + if (s_output) + cleanup_output (); + + if (! cop) + return; + + cop->set_info (in_filename, in_tuple); + if (! cop->open_audio (format, rate, channels)) + return; + + AUDINFO ("Opened output stream, format %d, %d channels, %d Hz.\n", format, channels, rate); + + s_output = true; + + out_format = format; + out_channels = channels; + out_rate = rate; + out_bytes_per_sec = FMT_SIZEOF (format) * channels * rate; + out_bytes_held = 0; + out_bytes_written = 0; + + apply_pause (); + + if (! s_flushed && ! s_resetting) + SIGNAL_MINOR; +} + +/* assumes LOCK_MINOR, s_output */ +static bool flush_output (bool force) +{ + if (! effect_flush (force)) + return false; + + out_bytes_held = 0; + out_bytes_written = 0; + + cop->flush (); + vis_runner_flush (); + return true; +} + +static void apply_replay_gain (Index<float> & data) +{ + if (! aud_get_bool (0, "enable_replay_gain")) + return; + + float factor = powf (10, aud_get_double (0, "replay_gain_preamp") / 20); + + if (s_gain) + { + float peak; + + if (aud_get_bool (0, "replay_gain_album")) + { + factor *= powf (10, gain_info.album_gain / 20); + peak = gain_info.album_peak; + } + else + { + factor *= powf (10, gain_info.track_gain / 20); + peak = gain_info.track_peak; + } + + if (aud_get_bool (0, "enable_clipping_prevention") && peak * factor > 1) + factor = 1 / peak; + } + else + factor *= powf (10, aud_get_double (0, "default_gain") / 20); + + if (factor < 0.99 || factor > 1.01) + audio_amplify (data.begin (), 1, data.len (), & factor); +} + +/* assumes LOCK_ALL, s_output */ +static void write_output_raw (Index<float> & data) +{ + if (! data.len ()) + return; + + int out_time = aud::rescale<int64_t> (out_bytes_written, out_bytes_per_sec, 1000); + vis_runner_pass_audio (out_time, data, out_channels, out_rate); + + eq_filter (data.begin (), data.len ()); + + if (aud_get_bool (0, "software_volume_control")) + { + StereoVolume v = {aud_get_int (0, "sw_volume_left"), aud_get_int (0, "sw_volume_right")}; + audio_amplify (data.begin (), out_channels, data.len () / out_channels, v); + } + + if (aud_get_bool (0, "soft_clipping")) + audio_soft_clip (data.begin (), data.len ()); + + const void * out_data = data.begin (); + + if (out_format != FMT_FLOAT) + { + buffer2.resize (FMT_SIZEOF (out_format) * data.len ()); + audio_to_int (data.begin (), buffer2.begin (), out_format, data.len ()); + out_data = buffer2.begin (); + } + + out_bytes_held = FMT_SIZEOF (out_format) * data.len (); + + while (! s_flushed && ! s_resetting) + { + int written = cop->write_audio (out_data, out_bytes_held); + + out_data = (char *) out_data + written; + out_bytes_held -= written; + out_bytes_written += written; + + if (! out_bytes_held) + break; + + UNLOCK_MINOR; + cop->period_wait (); + LOCK_MINOR; + } +} + +/* assumes LOCK_ALL, s_input, s_output */ +static bool write_output (const void * data, int size, int stop_time) +{ + int samples = size / FMT_SIZEOF (in_format); + bool stopped = false; + + if (stop_time != -1) + { + int64_t frames_left = aud::rescale<int64_t> (stop_time - seek_time, 1000, in_rate) - in_frames; + int64_t samples_left = in_channels * aud::max ((int64_t) 0, frames_left); + + if (samples >= samples_left) + { + samples = samples_left; + stopped = true; + } + } + + in_frames += samples / in_channels; + + buffer1.resize (samples); + + if (in_format == FMT_FLOAT) + memcpy (buffer1.begin (), data, sizeof (float) * samples); + else + audio_from_int (data, in_format, buffer1.begin (), samples); + + apply_replay_gain (buffer1); + write_output_raw (effect_process (buffer1)); + + return ! stopped; +} + +/* assumes LOCK_ALL, s_output */ +static void finish_effects (bool end_of_playlist) +{ + buffer1.resize (0); + write_output_raw (effect_finish (buffer1, end_of_playlist)); +} + +bool output_open_audio (const String & filename, const Tuple & tuple, + int format, int rate, int channels, int start_time) +{ + /* prevent division by zero */ + if (rate < 1 || channels < 1 || channels > AUD_MAX_CHANNELS) + return false; + + LOCK_ALL; + + if (s_output && s_paused) + { + if (! s_flushed && ! s_resetting) + flush_output (true); + + s_paused = false; + apply_pause (); + } + + s_input = true; + s_gain = s_paused = s_flushed = false; + seek_time = start_time; + + in_filename = filename; + in_tuple = tuple.ref (); + in_format = format; + in_channels = channels; + in_rate = rate; + in_frames = 0; + + setup_output (); + + UNLOCK_ALL; + return true; +} + +void output_set_replay_gain (const ReplayGainInfo & info) +{ + LOCK_ALL; + + if (s_input) + { + gain_info = info; + s_gain = true; + + AUDINFO ("Replay Gain info:\n"); + AUDINFO (" album gain: %f dB\n", info.album_gain); + AUDINFO (" album peak: %f\n", info.album_peak); + AUDINFO (" track gain: %f dB\n", info.track_gain); + AUDINFO (" track peak: %f\n", info.track_peak); + } + + UNLOCK_ALL; +} + +/* returns false if stop_time is reached */ +bool output_write_audio (const void * data, int size, int stop_time) +{ +RETRY: + LOCK_ALL; + bool good = false; + + if (s_input && ! s_flushed) + { + if (! s_output || s_resetting) + { + UNLOCK_MAJOR; + WAIT_MINOR; + UNLOCK_MINOR; + goto RETRY; + } + + good = write_output (data, size, stop_time); + } + + UNLOCK_ALL; + return good; +} + +void output_flush (int time, bool force) +{ + LOCK_MINOR; + + if (s_input && ! s_flushed) + { + if (s_output && ! s_resetting) + { + // allow effect plugins to prevent the flush, but + // always flush if paused to prevent locking up + s_flushed = flush_output (s_paused || force); + } + else + { + s_flushed = true; + SIGNAL_MINOR; + } + } + + if (s_input) + { + seek_time = time; + in_frames = 0; + } + + UNLOCK_MINOR; +} + +void output_resume () +{ + LOCK_ALL; + + if (s_input) + s_flushed = false; + + UNLOCK_ALL; +} + +void output_pause (bool pause) +{ + LOCK_MINOR; + + if (s_input) + { + s_paused = pause; + + if (s_output) + apply_pause (); + } + + UNLOCK_MINOR; +} + +int output_get_time () +{ + LOCK_MINOR; + int time = 0, delay = 0; + + if (s_input) + { + if (s_output) + { + delay = cop->get_delay (); + delay += aud::rescale<int64_t> (out_bytes_held, out_bytes_per_sec, 1000); + } + + delay = effect_adjust_delay (delay); + time = aud::rescale<int64_t> (in_frames, in_rate, 1000); + time = seek_time + aud::max (time - delay, 0); + } + + UNLOCK_MINOR; + return time; +} + +int output_get_raw_time () +{ + LOCK_MINOR; + int time = 0; + + if (s_output) + { + time = aud::rescale<int64_t> (out_bytes_written, out_bytes_per_sec, 1000); + time = aud::max (time - cop->get_delay (), 0); + } + + UNLOCK_MINOR; + return time; +} + +void output_close_audio () +{ + LOCK_ALL; + + if (s_input) + { + s_input = false; + in_filename = String (); + in_tuple = Tuple (); + + if (s_output && ! (s_paused || s_flushed || s_resetting)) + finish_effects (false); /* first time for end of song */ + } + + UNLOCK_ALL; +} + +void output_drain () +{ + LOCK_ALL; + + if (! s_input && s_output) + { + finish_effects (true); /* second time for end of playlist */ + cleanup_output (); + } + + UNLOCK_ALL; +} + +EXPORT void aud_output_reset (OutputReset type) +{ + LOCK_MINOR; + + s_resetting = true; + + if (s_output && ! s_flushed) + flush_output (true); + + UNLOCK_MINOR; + LOCK_ALL; + + if (s_output && type != OutputReset::EffectsOnly) + cleanup_output (); + + if (type == OutputReset::ResetPlugin) + { + if (cop) + cop->cleanup (); + + if (change_op) + cop = new_op; + + if (cop && ! cop->init ()) + cop = nullptr; + } + + if (s_input) + setup_output (); + + s_resetting = false; + + if (s_output && ! s_flushed) + SIGNAL_MINOR; + + UNLOCK_ALL; +} + +EXPORT StereoVolume aud_drct_get_volume () +{ + StereoVolume volume = {0, 0}; + LOCK_MINOR; + + if (aud_get_bool (0, "software_volume_control")) + volume = {aud_get_int (0, "sw_volume_left"), aud_get_int (0, "sw_volume_right")}; + else if (cop) + volume = cop->get_volume (); + + UNLOCK_MINOR; + return volume; +} + +EXPORT void aud_drct_set_volume (StereoVolume volume) +{ + LOCK_MINOR; + + volume.left = aud::clamp (volume.left, 0, 100); + volume.right = aud::clamp (volume.right, 0, 100); + + if (aud_get_bool (0, "software_volume_control")) + { + aud_set_int (0, "sw_volume_left", volume.left); + aud_set_int (0, "sw_volume_right", volume.right); + } + else if (cop) + cop->set_volume (volume); + + UNLOCK_MINOR; +} + +PluginHandle * output_plugin_get_current () +{ + return cop ? aud_plugin_by_header (cop) : nullptr; +} + +bool output_plugin_set_current (PluginHandle * plugin) +{ + change_op = true; + new_op = plugin ? (OutputPlugin *) aud_plugin_get_header (plugin) : nullptr; + aud_output_reset (OutputReset::ResetPlugin); + + bool success = (! plugin || (new_op && cop == new_op)); + change_op = false; + new_op = nullptr; + + return success; +} diff --git a/src/libaudcore/output.h b/src/libaudcore/output.h new file mode 100644 index 0000000..a8e4eea --- /dev/null +++ b/src/libaudcore/output.h @@ -0,0 +1,44 @@ +/* + * output.h + * Copyright 2010-2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_OUTPUT_H +#define LIBAUDCORE_OUTPUT_H + +#include <libaudcore/audio.h> +#include <libaudcore/objects.h> + +class Tuple; + +bool output_open_audio (const String & filename, const Tuple & tuple, + int format, int rate, int channels, int start_time); +void output_set_replay_gain (const ReplayGainInfo & info); +bool output_write_audio (const void * data, int size, int stop_time); +void output_flush (int time, bool force = false); +void output_resume (); +void output_pause (bool pause); + +int output_get_time (); +int output_get_raw_time (); +void output_close_audio (); +void output_drain (); + +PluginHandle * output_plugin_get_current (); +bool output_plugin_set_current (PluginHandle * plugin); + +#endif diff --git a/src/libaudcore/playback.cc b/src/libaudcore/playback.cc new file mode 100644 index 0000000..2fdd5b3 --- /dev/null +++ b/src/libaudcore/playback.cc @@ -0,0 +1,749 @@ +/* + * playback.cc + * Copyright 2009-2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +/* + * Since Audacious 3.6, the playback thread is completely asynchronous; that is, + * the main thread never blocks waiting for the playback thread to process a + * play(), seek(), or stop() command. As a result, the playback thread can lag + * behind the main/playlist thread, and the "current" song from the perspective + * of the playback thread may not be the same as the "current" song from the + * perspective of the main/playlist thread. Therefore, some care is necessary + * to ensure that information generated in the playback thread is not applied to + * the wrong song. To this end, separate serial numbers are maintained by each + * thread and compared when information crosses thread boundaries; if the serial + * numbers do not match, the information is generally discarded. + * + * Note that this file and playlist.cc each have their own mutex. The one in + * playlist.cc is conceptually the "outer" mutex and must be locked first (in + * situations where both need to be locked) in order to avoid deadlock. + */ + +#include "drct.h" +#include "internal.h" + +#include <assert.h> +#include <pthread.h> + +#include "audstrings.h" +#include "hook.h" +#include "i18n.h" +#include "interface.h" +#include "mainloop.h" +#include "output.h" +#include "playlist-internal.h" +#include "plugin.h" +#include "plugins.h" +#include "plugins-internal.h" +#include "runtime.h" + +struct PlaybackState { + bool playing = false; + bool thread_running = false; + int control_serial = 0; + int playback_serial = 0; +}; + +struct PlaybackControl { + bool paused = false; + int seek = -1; + int repeat_a = -1; + int repeat_b = -1; +}; + +struct PlaybackInfo { + // set by playback_set_info + String filename; + PluginHandle * decoder = nullptr; + Tuple tuple; + + int entry = -1; + String title; + + // set by playback thread + int length = -1; + int time_offset = 0; + int stop_time = -1; + + ReplayGainInfo gain {}; + + int bitrate = 0; + int samplerate = 0; + int channels = 0; + + bool ready = false; + bool ended = false; + bool error = false; + String error_s; +}; + +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; + +static PlaybackState pb_state; +static PlaybackControl pb_control; +static PlaybackInfo pb_info; + +static QueuedFunc end_queue; +static bool song_finished = false; +static int failed_entries = 0; + +static void lock () + { pthread_mutex_lock (& mutex); } +static void unlock () + { pthread_mutex_unlock (& mutex); } + +static bool lock_if (bool (* test) ()) +{ + lock (); + if (test ()) + return true; + + unlock (); + return false; +} + +// check that the playback thread is not lagging +static bool in_sync () + { return pb_state.playing && pb_state.control_serial == pb_state.playback_serial; } + +// check that the playback thread is not lagging and playback is "ready" +static bool is_ready () + { return in_sync () && pb_info.ready; } + +// called by playback_entry_set_tuple() to ensure that the tuple still applies +// to the current song from the perspective of the main/playlist thread; the +// check is necessary because playback_entry_set_tuple() is itself called from +// the playback thread +bool playback_check_serial (int serial) +{ + lock (); + bool okay = (pb_state.playing && pb_state.control_serial == serial); + unlock (); + return okay; +} + +// called from the playlist to update the tuple for the current song +bool playback_set_info (int entry, const String & filename, PluginHandle * decoder, Tuple && tuple) +{ + // do nothing if the playback thread is lagging behind; + // in that case, playback_set_info() will get called again anyway + if (! lock_if (in_sync)) + return false; + + if (! pb_info.filename) + pb_info.filename = filename; + if (! pb_info.decoder) + pb_info.decoder = decoder; + + if (tuple && tuple != pb_info.tuple) + { + pb_info.tuple = std::move (tuple); + + // don't call "tuple change" before "playback ready" + if (is_ready ()) + event_queue ("tuple change", nullptr); + } + + String title = pb_info.tuple.get_str (Tuple::FormattedTitle); + if (entry != pb_info.entry || title != pb_info.title) + { + pb_info.entry = entry; + pb_info.title = title; + + // don't call "title change" before "playback ready" + if (is_ready ()) + event_queue ("title change", nullptr); + } + + unlock (); + return true; +} + +// cleanup common to both playback_play() and playback_stop() +static void playback_cleanup_locked () +{ + pb_state.playing = false; + pb_control = PlaybackControl (); + + // discard audio buffer if the song did not end on its own + if (! song_finished) + output_flush (0); + + // miscellaneous cleanup + end_queue.stop (); + song_finished = false; + + event_queue_cancel ("playback ready", nullptr); + event_queue_cancel ("playback pause", nullptr); + event_queue_cancel ("playback unpause", nullptr); + event_queue_cancel ("playback seek", nullptr); + event_queue_cancel ("info change", nullptr); + event_queue_cancel ("title change", nullptr); + event_queue_cancel ("tuple change", nullptr); + + aud_set_bool (nullptr, "stop_after_current_song", false); +} + +// main thread: stops playback when no more songs are to be played +void playback_stop (bool exiting) +{ + if (! pb_state.playing && ! exiting) + return; + + lock (); + + if (pb_state.playing) + playback_cleanup_locked (); + + if (pb_state.thread_running) + { + // discard audio buffer if exiting + if (exiting) + output_flush (0, true); + + // signal playback thread to drain audio buffer + pb_state.control_serial ++; + pthread_cond_broadcast (& cond); + + // wait for playback thread to finish if exiting + while (exiting && pb_state.thread_running) + pthread_cond_wait (& cond, & mutex); + } + + unlock (); + + // miscellaneous cleanup + failed_entries = 0; +} + +// called from top-level event loop after playback finishes +static void end_cb (void *) +{ + song_finished = true; + hook_call ("playback end", nullptr); + + int playlist = aud_playlist_get_playing (); + + auto do_stop = [playlist] () + { + aud_playlist_play (-1); + aud_playlist_set_position (playlist, aud_playlist_get_position (playlist)); + }; + + auto do_next = [playlist] () + { + if (! playlist_next_song (playlist, aud_get_bool (nullptr, "repeat"))) + { + aud_playlist_set_position (playlist, -1); + hook_call ("playlist end reached", nullptr); + } + }; + + if (aud_get_bool (nullptr, "no_playlist_advance")) + { + // we assume here that repeat is not enabled; + // single-song repeats are handled in run_playback() + do_stop (); + } + else if (aud_get_bool (nullptr, "stop_after_current_song")) + { + do_stop (); + do_next (); + } + else + { + if (failed_entries < 10) + do_next (); + else + do_stop (); + } +} + +// helper, can be called from either main or playback thread +static void request_seek_locked (int time) +{ + // set up "seek" command whether ready or not; + // if not ready, it will take effect upon open_audio() + pb_control.seek = aud::max (0, time); + + // trigger seek immediately if ready + if (is_ready () && pb_info.length > 0) + { + output_flush (aud::clamp (time, 0, pb_info.length)); + event_queue ("playback seek", nullptr); + } +} + +// playback thread helper +static void run_playback () +{ + // due to mutex ordering, we cannot call into the playlist while locked; + // instead, playback_entry_read() calls back into playback_set_info() + if (! playback_entry_read (pb_state.playback_serial, pb_info.error_s)) + { + pb_info.error = true; + return; + } + + lock (); + + // playback_set_info() should always set this info + assert (pb_info.filename && pb_info.decoder && pb_info.tuple); + + // get various other bits of info from the tuple + pb_info.length = pb_info.tuple.get_int (Tuple::Length); + pb_info.time_offset = aud::max (0, pb_info.tuple.get_int (Tuple::StartTime)); + pb_info.stop_time = aud::max (-1, pb_info.tuple.get_int (Tuple::EndTime) - pb_info.time_offset); + pb_info.gain = pb_info.tuple.get_replay_gain (); + + // force initial seek if we are playing a segmented track + if (pb_info.time_offset > 0 && pb_control.seek < 0) + pb_control.seek = 0; + + unlock (); + + InputPlugin * ip; + VFSFile file; + + // load input plugin + if (! (ip = (InputPlugin *) aud_plugin_get_header (pb_info.decoder))) + { + pb_info.error = true; + pb_info.error_s = String (_("Error loading plugin")); + return; + } + + // open file (not necessary for custom URI schemes) + if (! ip->input_info.keys[InputKey::Scheme] && ! (file = VFSFile (pb_info.filename, "r"))) + { + pb_info.error = true; + pb_info.error_s = String (file.error ()); + return; + } + + while (1) + { + // hand off control to input plugin + if (! ip->play (pb_info.filename, file)) + pb_info.error = true; + + // close audio (no-op if it wasn't opened) + output_close_audio (); + + if (pb_info.error || pb_info.length <= 0) + break; + + if (! lock_if (in_sync)) + break; + + // check whether we need to repeat + pb_info.ended = (pb_control.repeat_a < 0 && ! (aud_get_bool (nullptr, + "repeat") && aud_get_bool (nullptr, "no_playlist_advance"))); + + if (! pb_info.ended) + request_seek_locked (pb_control.repeat_a); + + unlock (); + + if (pb_info.ended) + break; + + // rewind file pointer before repeating + if (file && file.fseek (0, VFS_SEEK_SET) != 0) + { + pb_info.error = true; + break; + } + } +} + +// playback thread helper +static void finish_playback_locked () +{ + // record any playback error that occurred + if (pb_info.error) + { + failed_entries ++; + aud_ui_show_error (str_printf (_("Error opening %s:\n%s"), + (const char *) pb_info.filename, pb_info.error_s ? + (const char *) pb_info.error_s : _("Unknown playback error"))); + } + else + failed_entries = 0; + + // queue up function to start next song (or perform cleanup) + end_queue.queue (end_cb, nullptr); +} + +// playback thread +static void * playback_thread (void *) +{ + lock (); + + while (1) + { + // wait for a command + while (pb_state.control_serial == pb_state.playback_serial) + pthread_cond_wait (& cond, & mutex); + + // fetch the command (either "play" or "drain") + bool play = pb_state.playing; + + // update playback thread serial number + pb_state.playback_serial = pb_state.control_serial; + + unlock (); + + if (play) + run_playback (); + else + output_drain (); + + lock (); + + if (play) + { + // don't report errors or queue next song if another command is pending + if (in_sync ()) + finish_playback_locked (); + + pb_info = PlaybackInfo (); + } + else + { + // quit if we did not receive a new command after draining + if (pb_state.control_serial == pb_state.playback_serial) + break; + } + } + + // signal the main thread that we are quitting + pb_state.thread_running = false; + pthread_cond_broadcast (& cond); + unlock (); + return nullptr; +} + +// main thread: starts playback of a new song +void playback_play (int seek_time, bool pause) +{ + lock (); + + if (pb_state.playing) + playback_cleanup_locked (); + + // set up "play" command + pb_state.playing = true; + pb_state.control_serial ++; + pb_control.paused = pause; + pb_control.seek = (seek_time > 0) ? seek_time : -1; + + // start playback thread (or signal it if it's already running) + if (pb_state.thread_running) + pthread_cond_broadcast (& cond); + else + { + pthread_t thread; + pthread_create (& thread, nullptr, playback_thread, nullptr); + pthread_detach (thread); + pb_state.thread_running = true; + } + + unlock (); +} + +// main thread +EXPORT void aud_drct_pause () +{ + if (! pb_state.playing) + return; + + lock (); + + // set up "pause" command whether ready or not; + // if not ready, it will take effect upon open_audio() + bool pause = ! pb_control.paused; + pb_control.paused = pause; + + // apply pause immediately if ready + if (is_ready ()) + output_pause (pause); + + event_queue (pause ? "playback pause" : "playback unpause", nullptr); + + unlock (); +} + +// main thread +EXPORT void aud_drct_seek (int time) +{ + if (! pb_state.playing) + return; + + lock (); + request_seek_locked (time); + unlock (); +} + +EXPORT void InputPlugin::open_audio (int format, int rate, int channels) +{ + // don't open audio if playback thread is lagging + if (! lock_if (in_sync)) + return; + + if (! output_open_audio (pb_info.filename, pb_info.tuple, format, rate, + channels, aud::max (0, pb_control.seek))) + { + pb_info.error = true; + pb_info.error_s = String (_("Invalid audio format")); + unlock (); + return; + } + + output_set_replay_gain (pb_info.gain); + if (pb_control.paused) + output_pause (true); + + pb_info.samplerate = rate; + pb_info.channels = channels; + + if (pb_info.ready) + event_queue ("info change", nullptr); + else + event_queue ("playback ready", nullptr); + + pb_info.ready = true; + + unlock (); +} + +EXPORT void InputPlugin::set_replay_gain (const ReplayGainInfo & gain) +{ + lock (); + pb_info.gain = gain; + + if (is_ready ()) + output_set_replay_gain (gain); + + unlock (); +} + +EXPORT void InputPlugin::write_audio (const void * data, int length) +{ + if (! lock_if (in_sync)) + return; + + // fetch A-B repeat settings + int a = pb_control.repeat_a; + int b = pb_control.repeat_b; + + unlock (); + + // it's okay to call output_write_audio() even if we are no longer in sync, + // since it will return immediately if output_flush() has been called + int stop_time = (b >= 0) ? b : pb_info.stop_time; + if (output_write_audio (data, length, stop_time)) + return; + + if (! lock_if (in_sync)) + return; + + // if we are still in sync, then one of the following happened: + // 1. output_flush() was called due to a seek request + // 2. we've reached repeat point B + // 3. we've reached the end of a segmented track + if (pb_control.seek < 0) + { + if (b >= 0) + request_seek_locked (a); + else + pb_info.ended = true; + } + + unlock (); +} + +EXPORT Tuple InputPlugin::get_playback_tuple () +{ + lock (); + Tuple tuple = pb_info.tuple.ref (); + unlock (); + + // tuples passed to us from input plugins do not have fallback fields + // generated; for consistency, tuples passed back should not either + tuple.delete_fallbacks (); + return tuple; +} + +EXPORT void InputPlugin::set_playback_tuple (Tuple && tuple) +{ + // due to mutex ordering, we cannot call into the playlist while locked; + // instead, playback_entry_set_tuple() calls back into first + // playback_check_serial() and then eventually playlist_set_info() + playback_entry_set_tuple (pb_state.playback_serial, std::move (tuple)); +} + +EXPORT void InputPlugin::set_stream_bitrate (int bitrate) +{ + lock (); + pb_info.bitrate = bitrate; + + if (is_ready ()) + event_queue ("info change", nullptr); + + unlock (); +} + +EXPORT bool InputPlugin::check_stop () +{ + lock (); + bool stop = ! is_ready () || pb_info.ended || pb_info.error; + unlock (); + return stop; +} + +EXPORT int InputPlugin::check_seek () +{ + lock (); + int seek = -1; + + if (is_ready () && pb_control.seek >= 0 && pb_info.length > 0) + { + seek = pb_info.time_offset + aud::min (pb_control.seek, pb_info.length); + pb_control.seek = -1; + output_resume (); + } + + unlock (); + return seek; +} + +// thread-safe +EXPORT bool aud_drct_get_playing () +{ + lock (); + bool playing = pb_state.playing; + unlock (); + return playing; +} + +// thread-safe +EXPORT bool aud_drct_get_ready () +{ + lock (); + bool ready = is_ready (); + unlock (); + return ready; +} + +// thread-safe +EXPORT bool aud_drct_get_paused () +{ + lock (); + bool paused = pb_control.paused; + unlock (); + return paused; +} + +// thread-safe +EXPORT String aud_drct_get_title () +{ + if (! lock_if (is_ready)) + return String (); + + String title = pb_info.title; + int entry = pb_info.entry; + int length = pb_info.length; + + unlock (); + + StringBuf prefix = aud_get_bool (nullptr, "show_numbers_in_pl") ? + str_printf ("%d. ", 1 + entry) : StringBuf (0); + + StringBuf time = (length > 0) ? str_format_time (length) : StringBuf (); + StringBuf suffix = time ? str_concat ({" (", time, ")"}) : StringBuf (0); + + return String (str_concat ({prefix, title, suffix})); +} + +// thread-safe +EXPORT Tuple aud_drct_get_tuple () +{ + lock (); + Tuple tuple = is_ready () ? pb_info.tuple.ref () : Tuple (); + unlock (); + return tuple; +} + +// thread-safe +EXPORT void aud_drct_get_info (int & bitrate, int & samplerate, int & channels) +{ + lock (); + + bool ready = is_ready (); + bitrate = ready ? pb_info.bitrate : 0; + samplerate = ready ? pb_info.samplerate : 0; + channels = ready ? pb_info.channels : 0; + + unlock (); +} + +// thread-safe +EXPORT int aud_drct_get_time () +{ + lock (); + int time = is_ready () ? output_get_time () : 0; + unlock (); + return time; +} + +// thread-safe +EXPORT int aud_drct_get_length () +{ + lock (); + int length = is_ready () ? pb_info.length : -1; + unlock (); + return length; +} + +// main thread +EXPORT void aud_drct_set_ab_repeat (int a, int b) +{ + if (! pb_state.playing) + return; + + lock (); + + pb_control.repeat_a = a; + pb_control.repeat_b = b; + + if (b >= 0 && is_ready () && output_get_time () >= b) + request_seek_locked (a); + + unlock (); +} + +// thread-safe +EXPORT void aud_drct_get_ab_repeat (int & a, int & b) +{ + lock (); + a = pb_control.repeat_a; + b = pb_control.repeat_b; + unlock (); +} diff --git a/src/libaudcore/playlist-files.cc b/src/libaudcore/playlist-files.cc new file mode 100644 index 0000000..a3dbccd --- /dev/null +++ b/src/libaudcore/playlist-files.cc @@ -0,0 +1,139 @@ +/* + * playlist-files.c + * Copyright 2010-2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "playlist-internal.h" + +#include "audstrings.h" +#include "i18n.h" +#include "interface.h" +#include "plugin.h" +#include "plugins-internal.h" +#include "runtime.h" + +EXPORT bool aud_filename_is_playlist (const char * filename) +{ + StringBuf ext = uri_get_extension (filename); + + if (ext) + { + for (PluginHandle * plugin : aud_plugin_list (PluginType::Playlist)) + { + if (aud_plugin_get_enabled (plugin) && playlist_plugin_has_ext (plugin, ext)) + return true; + } + } + + return false; +} + +bool playlist_load (const char * filename, String & title, Index<PlaylistAddItem> & items) +{ + AUDINFO ("Loading playlist %s.\n", filename); + + StringBuf ext = uri_get_extension (filename); + + if (ext) + { + for (PluginHandle * plugin : aud_plugin_list (PluginType::Playlist)) + { + if (! aud_plugin_get_enabled (plugin) || ! playlist_plugin_has_ext (plugin, ext)) + continue; + + AUDINFO ("Trying playlist plugin %s.\n", aud_plugin_get_name (plugin)); + + PlaylistPlugin * pp = (PlaylistPlugin *) aud_plugin_get_header (plugin); + if (! pp) + continue; + + VFSFile file (filename, "r"); + if (! file) + return false; + + if (pp->load (filename, file, title, items)) + return true; + + title = String (); + items.clear (); + } + } + + aud_ui_show_error (str_printf (_("Cannot load %s: unsupported file name extension."), filename)); + + return false; +} + +bool playlist_insert_playlist_raw (int list, int at, const char * filename) +{ + String title; + Index<PlaylistAddItem> items; + + if (! playlist_load (filename, title, items)) + return false; + + if (title && ! aud_playlist_entry_count (list)) + aud_playlist_set_title (list, title); + + playlist_entry_insert_batch_raw (list, at, std::move (items)); + + return true; +} + +EXPORT bool aud_playlist_save (int list, const char * filename, Playlist::GetMode mode) +{ + String title = aud_playlist_get_title (list); + + Index<PlaylistAddItem> items; + items.insert (0, aud_playlist_entry_count (list)); + + int i = 0; + for (PlaylistAddItem & item : items) + { + item.filename = aud_playlist_entry_get_filename (list, i); + item.tuple = aud_playlist_entry_get_tuple (list, i, mode); + item.tuple.delete_fallbacks (); + i ++; + } + + AUDINFO ("Saving playlist %s.\n", filename); + + StringBuf ext = uri_get_extension (filename); + + if (ext) + { + for (PluginHandle * plugin : aud_plugin_list (PluginType::Playlist)) + { + if (! aud_plugin_get_enabled (plugin) || ! playlist_plugin_has_ext (plugin, ext)) + continue; + + PlaylistPlugin * pp = (PlaylistPlugin *) aud_plugin_get_header (plugin); + if (! pp || ! pp->can_save) + continue; + + VFSFile file (filename, "w"); + if (! file) + return false; + + return pp->save (filename, file, title, items) && file.fflush () == 0; + } + } + + aud_ui_show_error (str_printf (_("Cannot save %s: unsupported file name extension."), filename)); + + return false; +} diff --git a/src/libaudcore/playlist-internal.h b/src/libaudcore/playlist-internal.h new file mode 100644 index 0000000..ffcb17c --- /dev/null +++ b/src/libaudcore/playlist-internal.h @@ -0,0 +1,53 @@ +/* + * playlist-internal.h + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_PLAYLIST_INTERNAL_H +#define LIBAUDCORE_PLAYLIST_INTERNAL_H + +#include "playlist.h" + +/* playlist.c */ +void playlist_init (); +void playlist_enable_scan (bool enable); +void playlist_end (); + +void playlist_insert_with_id (int at, int id); +void playlist_set_modified (int playlist, bool modified); +bool playlist_get_modified (int playlist); + +void playlist_load_state (); +void playlist_save_state (); + +void playlist_entry_insert_batch_raw (int playlist, int at, Index<PlaylistAddItem> && items); + +bool playlist_prev_song (int playlist); +bool playlist_next_song (int playlist, bool repeat); + +bool playback_entry_read (int serial, String & error); +void playback_entry_set_tuple (int serial, Tuple && tuple); + +/* playlist-files.c */ +bool playlist_load (const char * filename, String & title, Index<PlaylistAddItem> & items); +bool playlist_insert_playlist_raw (int list, int at, const char * filename); + +/* playlist-utils.c */ +void load_playlists (); +void save_playlists (bool exiting); + +#endif diff --git a/src/libaudcore/playlist-utils.cc b/src/libaudcore/playlist-utils.cc new file mode 100644 index 0000000..46e3136 --- /dev/null +++ b/src/libaudcore/playlist-utils.cc @@ -0,0 +1,442 @@ +/* + * playlist-utils.c + * Copyright 2009-2011 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "playlist-internal.h" + +#include <stdlib.h> +#include <string.h> + +#include <glib/gstdio.h> + +#include "audstrings.h" +#include "hook.h" +#include "multihash.h" +#include "runtime.h" +#include "tuple.h" +#include "vfs.h" + +static const char * get_basename (const char * filename) +{ + const char * slash = strrchr (filename, '/'); + return slash ? slash + 1 : filename; +} + +static int filename_compare_basename (const char * a, const char * b) +{ + return str_compare_encoded (get_basename (a), get_basename (b)); +} + +static int tuple_compare_string (const Tuple & a, const Tuple & b, Tuple::Field field) +{ + String string_a = a.get_str (field); + String string_b = b.get_str (field); + + if (! string_a) + return (! string_b) ? 0 : -1; + + return (! string_b) ? 1 : str_compare (string_a, string_b); +} + +static int tuple_compare_int (const Tuple & a, const Tuple & b, Tuple::Field field) +{ + if (a.get_value_type (field) != Tuple::Int) + return (b.get_value_type (field) != Tuple::Int) ? 0 : -1; + if (b.get_value_type (field) != Tuple::Int) + return 1; + + int int_a = a.get_int (field); + int int_b = b.get_int (field); + + return (int_a < int_b) ? -1 : (int_a > int_b); +} + +static int tuple_compare_title (const Tuple & a, const Tuple & b) +{ + return tuple_compare_string (a, b, Tuple::Title); +} + +static int tuple_compare_album (const Tuple & a, const Tuple & b) +{ + return tuple_compare_string (a, b, Tuple::Album); +} + +static int tuple_compare_artist (const Tuple & a, const Tuple & b) +{ + return tuple_compare_string (a, b, Tuple::Artist); +} + +static int tuple_compare_album_artist (const Tuple & a, const Tuple & b) +{ + return tuple_compare_string (a, b, Tuple::AlbumArtist); +} + +static int tuple_compare_date (const Tuple & a, const Tuple & b) +{ + return tuple_compare_int (a, b, Tuple::Year); +} + +static int tuple_compare_genre (const Tuple & a, const Tuple & b) +{ + return tuple_compare_string (a, b, Tuple::Genre); +} + +static int tuple_compare_track (const Tuple & a, const Tuple & b) +{ + return tuple_compare_int (a, b, Tuple::Track); +} + +static int tuple_compare_formatted_title (const Tuple & a, const Tuple & b) +{ + return tuple_compare_string (a, b, Tuple::FormattedTitle); +} + +static int tuple_compare_length (const Tuple & a, const Tuple & b) +{ + return tuple_compare_int (a, b, Tuple::Length); +} + +static const PlaylistStringCompareFunc filename_comparisons[] = { + str_compare_encoded, // path + filename_compare_basename, // filename + nullptr, // title + nullptr, // album + nullptr, // artist + nullptr, // album artist + nullptr, // date + nullptr, // genre + nullptr, // track + nullptr, // formatted title + nullptr // length +}; + +static const PlaylistTupleCompareFunc tuple_comparisons[] = { + nullptr, // path + nullptr, // filename + tuple_compare_title, + tuple_compare_album, + tuple_compare_artist, + tuple_compare_album_artist, + tuple_compare_date, + tuple_compare_genre, + tuple_compare_track, + tuple_compare_formatted_title, + tuple_compare_length +}; + +static_assert (aud::n_elems (filename_comparisons) == Playlist::n_sort_types && + aud::n_elems (tuple_comparisons) == Playlist::n_sort_types, + "Update playlist comparison functions"); + +EXPORT void aud_playlist_sort_by_scheme (int playlist, Playlist::SortType scheme) +{ + if (filename_comparisons[scheme]) + aud_playlist_sort_by_filename (playlist, filename_comparisons[scheme]); + else if (tuple_comparisons[scheme]) + aud_playlist_sort_by_tuple (playlist, tuple_comparisons[scheme]); +} + +EXPORT void aud_playlist_sort_selected_by_scheme (int playlist, Playlist::SortType scheme) +{ + if (filename_comparisons[scheme]) + aud_playlist_sort_selected_by_filename (playlist, filename_comparisons[scheme]); + else if (tuple_comparisons[scheme]) + aud_playlist_sort_selected_by_tuple (playlist, tuple_comparisons[scheme]); +} + +/* FIXME: this considers empty fields as duplicates */ +EXPORT void aud_playlist_remove_duplicates_by_scheme (int playlist, Playlist::SortType scheme) +{ + int entries = aud_playlist_entry_count (playlist); + if (entries < 1) + return; + + aud_playlist_select_all (playlist, false); + + if (filename_comparisons[scheme]) + { + PlaylistStringCompareFunc compare = filename_comparisons[scheme]; + + aud_playlist_sort_by_filename (playlist, compare); + String last = aud_playlist_entry_get_filename (playlist, 0); + + for (int count = 1; count < entries; count ++) + { + String current = aud_playlist_entry_get_filename (playlist, count); + + if (compare (last, current) == 0) + aud_playlist_entry_set_selected (playlist, count, true); + + last = current; + } + } + else if (tuple_comparisons[scheme]) + { + PlaylistTupleCompareFunc compare = tuple_comparisons[scheme]; + + aud_playlist_sort_by_tuple (playlist, compare); + Tuple last = aud_playlist_entry_get_tuple (playlist, 0); + + for (int count = 1; count < entries; count ++) + { + Tuple current = aud_playlist_entry_get_tuple (playlist, count); + + if (last && current && compare (last, current) == 0) + aud_playlist_entry_set_selected (playlist, count, true); + + last = std::move (current); + } + } + + aud_playlist_delete_selected (playlist); +} + +EXPORT void aud_playlist_remove_failed (int playlist) +{ + int entries = aud_playlist_entry_count (playlist); + int count; + + aud_playlist_select_all (playlist, false); + + for (count = 0; count < entries; count ++) + { + String filename = aud_playlist_entry_get_filename (playlist, count); + + /* vfs_file_test() only works for file:// URIs currently */ + if (! strncmp (filename, "file://", 7) && ! VFSFile::test_file (filename, VFS_EXISTS)) + aud_playlist_entry_set_selected (playlist, count, true); + } + + aud_playlist_delete_selected (playlist); +} + +EXPORT void aud_playlist_select_by_patterns (int playlist, const Tuple & patterns) +{ + int entries = aud_playlist_entry_count (playlist); + + aud_playlist_select_all (playlist, true); + + for (Tuple::Field field : {Tuple::Title, Tuple::Album, Tuple::Artist, Tuple::Basename}) + { + String pattern = patterns.get_str (field); + GRegex * regex; + + if (! pattern || ! pattern[0] || ! (regex = g_regex_new (pattern, + G_REGEX_CASELESS, (GRegexMatchFlags) 0, nullptr))) + continue; + + for (int entry = 0; entry < entries; entry ++) + { + if (! aud_playlist_entry_get_selected (playlist, entry)) + continue; + + Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry); + String string = tuple.get_str (field); + + if (! string || ! g_regex_match (regex, string, (GRegexMatchFlags) 0, nullptr)) + aud_playlist_entry_set_selected (playlist, entry, false); + } + + g_regex_unref (regex); + } +} + +static StringBuf make_playlist_path (int playlist) +{ + if (! playlist) + return filename_build ({aud_get_path (AudPath::UserDir), "playlist.xspf"}); + + StringBuf name = str_printf ("playlist_%02d.xspf", 1 + playlist); + name.steal (filename_build ({aud_get_path (AudPath::UserDir), name})); + return name; +} + +static void load_playlists_real (void) +{ + const char * folder = aud_get_path (AudPath::PlaylistDir); + + /* old (v3.1 and earlier) naming scheme */ + + int count; + for (count = 0; ; count ++) + { + StringBuf path = make_playlist_path (count); + if (! g_file_test (path, G_FILE_TEST_EXISTS)) + break; + + aud_playlist_insert (count); + playlist_insert_playlist_raw (count, 0, filename_to_uri (path)); + playlist_set_modified (count, true); + } + + /* unique ID-based naming scheme */ + + StringBuf order_path = filename_build ({folder, "order"}); + char * order_string; + Index<String> order; + + g_file_get_contents (order_path, & order_string, nullptr, nullptr); + if (! order_string) + goto DONE; + + order = str_list_to_index (order_string, " "); + g_free (order_string); + + for (int i = 0; i < order.len (); i ++) + { + const String & number = order[i]; + + StringBuf name1 = str_concat ({number, ".audpl"}); + StringBuf name2 = str_concat ({number, ".xspf"}); + + StringBuf path = filename_build ({folder, name1}); + if (! g_file_test (path, G_FILE_TEST_EXISTS)) + path.steal (filename_build ({folder, name2})); + + playlist_insert_with_id (count + i, atoi (number)); + playlist_insert_playlist_raw (count + i, 0, filename_to_uri (path)); + playlist_set_modified (count + i, false); + + if (g_str_has_suffix (path, ".xspf")) + playlist_set_modified (count + i, true); + } + +DONE: + if (! aud_playlist_count ()) + aud_playlist_insert (0); + + aud_playlist_set_active (0); +} + +static void save_playlists_real (void) +{ + int lists = aud_playlist_count (); + const char * folder = aud_get_path (AudPath::PlaylistDir); + + /* save playlists */ + + Index<String> order; + SimpleHash<String, bool> saved; + + for (int i = 0; i < lists; i ++) + { + int id = aud_playlist_get_unique_id (i); + StringBuf number = int_to_str (id); + StringBuf name = str_concat ({number, ".audpl"}); + + if (playlist_get_modified (i)) + { + StringBuf path = filename_build ({folder, name}); + aud_playlist_save (i, filename_to_uri (path), Playlist::Nothing); + playlist_set_modified (i, false); + } + + order.append (String (number)); + saved.add (String (name), true); + } + + StringBuf order_string = index_to_str_list (order, " "); + StringBuf order_path = filename_build ({folder, "order"}); + + char * old_order_string; + g_file_get_contents (order_path, & old_order_string, nullptr, nullptr); + + if (! old_order_string || strcmp (old_order_string, order_string)) + { + GError * error = nullptr; + if (! g_file_set_contents (order_path, order_string, -1, & error)) + { + AUDERR ("Cannot write to %s: %s\n", (const char *) order_path, error->message); + g_error_free (error); + } + } + + g_free (old_order_string); + + /* clean up deleted playlists and files from old naming scheme */ + + g_unlink (make_playlist_path (0)); + + GDir * dir = g_dir_open (folder, 0, nullptr); + if (! dir) + return; + + const char * name; + while ((name = g_dir_read_name (dir))) + { + if (! g_str_has_suffix (name, ".audpl") && ! g_str_has_suffix (name, ".xspf")) + continue; + + if (! saved.lookup (String (name))) + g_unlink (filename_build ({folder, name})); + } + + g_dir_close (dir); +} + +static bool hooks_added, state_changed; + +static void update_cb (void * data, void *) +{ + auto level = aud::from_ptr<Playlist::UpdateLevel> (data); + if (level >= Playlist::Metadata) + state_changed = true; +} + +static void state_cb (void * data, void * user) +{ + state_changed = true; +} + +void load_playlists (void) +{ + load_playlists_real (); + playlist_load_state (); + + state_changed = false; + + if (! hooks_added) + { + hook_associate ("playlist update", update_cb, nullptr); + hook_associate ("playlist activate", state_cb, nullptr); + hook_associate ("playlist position", state_cb, nullptr); + + hooks_added = true; + } +} + +void save_playlists (bool exiting) +{ + save_playlists_real (); + + /* on exit, save resume states */ + if (state_changed || exiting) + { + playlist_save_state (); + state_changed = false; + } + + if (exiting && hooks_added) + { + hook_dissociate ("playlist update", update_cb); + hook_dissociate ("playlist activate", state_cb); + hook_dissociate ("playlist position", state_cb); + + hooks_added = false; + } +} diff --git a/src/libaudcore/playlist.cc b/src/libaudcore/playlist.cc new file mode 100644 index 0000000..d3046e5 --- /dev/null +++ b/src/libaudcore/playlist.cc @@ -0,0 +1,2261 @@ +/* + * playlist.cc + * Copyright 2009-2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +// uncomment to print a backtrace when scanning blocks the main thread +// #define WARN_BLOCKED + +#include "playlist-internal.h" +#include "runtime.h" + +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <glib/gstdio.h> + +#include "audstrings.h" +#include "drct.h" +#include "hook.h" +#include "i18n.h" +#include "interface.h" +#include "internal.h" +#include "list.h" +#include "mainloop.h" +#include "multihash.h" +#include "objects.h" +#include "plugins.h" +#include "scanner.h" +#include "tuple.h" +#include "tuple-compiler.h" + +#ifdef WARN_BLOCKED +#include <execinfo.h> +#include <stdio.h> +#include <stdlib.h> +#endif + +using namespace Playlist; + +enum { + ResumeStop, + ResumePlay, + ResumePause +}; + +enum PlaybackChange { + NoChange, + NextSong, + PlaybackStopped +}; + +#define STATE_FILE "playlist-state" + +#define ENTER pthread_mutex_lock (& mutex) +#define LEAVE pthread_mutex_unlock (& mutex) + +#define RETURN(...) do { \ + pthread_mutex_unlock (& mutex); \ + return __VA_ARGS__; \ +} while (0) + +#define ENTER_GET_PLAYLIST(...) ENTER; \ + PlaylistData * playlist = lookup_playlist (playlist_num); \ + if (! playlist) \ + RETURN (__VA_ARGS__); + +#define ENTER_GET_ENTRY(...) ENTER_GET_PLAYLIST (__VA_ARGS__); \ + Entry * entry = lookup_entry (playlist, entry_num); \ + if (! entry) \ + RETURN (__VA_ARGS__); + +struct UniqueID +{ + constexpr UniqueID (int val) : + val (val) {} + + operator int () const + { return val; } + + unsigned hash () const + { return int32_hash (val); } + +private: + int val; +}; + +struct Entry { + Entry (PlaylistAddItem && item); + ~Entry (); + + void set_tuple (Tuple && new_tuple); + void set_failed (String && new_error); + + String filename; + PluginHandle * decoder; + Tuple tuple; + String error; + int number; + int length; + int shuffle_num; + bool scanned, failed; + bool selected, queued; +}; + +struct PlaylistData { + PlaylistData (int id); + ~PlaylistData (); + + void set_entry_tuple (Entry * entry, Tuple && tuple); + + int number, unique_id; + String filename, title; + bool modified; + Index<SmartPtr<Entry>> entries; + Entry * position, * focus; + int selected_count; + int last_shuffle_num; + Index<Entry *> queued; + int64_t total_length, selected_length; + bool scanning, scan_ending; + Update next_update, last_update; + int resume_time; +}; + +static const char * const default_title = N_("New Playlist"); +static const char * const temp_title = N_("Now Playing"); + +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; + +#ifdef WARN_BLOCKED +static pthread_t main_thread; +#endif + +/* The unique ID table contains pointers to PlaylistData for ID's in use and nullptr + * for "dead" (previously used and therefore unavailable) ID's. */ +static SimpleHash<UniqueID, PlaylistData *> unique_id_table; +static int next_unique_id = 1000; + +static Index<SmartPtr<PlaylistData>> playlists; +static PlaylistData * active_playlist = nullptr; +static PlaylistData * playing_playlist = nullptr; +static int resume_playlist = -1; +static bool resume_paused = false; + +static QueuedFunc queued_update; +static UpdateLevel update_level; + +struct ScanItem : public ListNode +{ + ScanItem (PlaylistData * playlist, Entry * entry, ScanRequest * request) : + playlist (playlist), + entry (entry), + request (request) {} + + PlaylistData * playlist; + Entry * entry; + ScanRequest * request; +}; + +static bool scan_enabled; +static int scan_playlist, scan_row; +static List<ScanItem> scan_list; + +static void scan_finish (ScanRequest * request); +static void scan_cancel (Entry * entry); +static void scan_queue_playlist (PlaylistData * playlist); +static void scan_restart (); + +static bool next_song_locked (PlaylistData * playlist, bool repeat, int hint); + +static void playlist_reformat_titles (); +static void playlist_trigger_scan (); + +static SmartPtr<TupleCompiler> title_formatter; + +void Entry::set_tuple (Tuple && new_tuple) +{ + /* Hack: We cannot refresh segmented entries (since their info is read from + * the cue sheet when it is first loaded), so leave them alone. -jlindgren */ + if (tuple.get_value_type (Tuple::StartTime) == Tuple::Int) + return; + + scanned = (bool) new_tuple; + failed = false; + error = String (); + + if (! new_tuple) + new_tuple.set_filename (filename); + + new_tuple.generate_fallbacks (); + title_formatter->format (new_tuple); + + length = aud::max (0, new_tuple.get_int (Tuple::Length)); + tuple = std::move (new_tuple); +} + +void PlaylistData::set_entry_tuple (Entry * entry, Tuple && tuple) +{ + scan_cancel (entry); + + total_length -= entry->length; + if (entry->selected) + selected_length -= entry->length; + + entry->set_tuple (std::move (tuple)); + + total_length += entry->length; + if (entry->selected) + selected_length += entry->length; +} + +void Entry::set_failed (String && new_error) +{ + scanned = true; + failed = true; + error = std::move (new_error); +} + +Entry::Entry (PlaylistAddItem && item) : + filename (item.filename), + decoder (item.decoder), + number (-1), + length (0), + shuffle_num (0), + scanned (false), + failed (false), + selected (false), + queued (false) +{ + set_tuple (std::move (item.tuple)); +} + +Entry::~Entry () +{ + scan_cancel (this); +} + +static int new_unique_id (int preferred) +{ + if (preferred >= 0 && ! unique_id_table.lookup (preferred)) + return preferred; + + while (unique_id_table.lookup (next_unique_id)) + next_unique_id ++; + + return next_unique_id ++; +} + +PlaylistData::PlaylistData (int id) : + number (-1), + unique_id (new_unique_id (id)), + title (_(default_title)), + modified (true), + position (nullptr), + focus (nullptr), + selected_count (0), + last_shuffle_num (0), + total_length (0), + selected_length (0), + scanning (false), + scan_ending (false), + next_update (), + last_update (), + resume_time (0) +{ + unique_id_table.add (unique_id, (PlaylistData *) this); +} + +PlaylistData::~PlaylistData () +{ + unique_id_table.add (unique_id, nullptr); +} + +static void number_playlists (int at, int length) +{ + for (int i = at; i < at + length; i ++) + playlists[i]->number = i; +} + +static PlaylistData * lookup_playlist (int i) +{ + return (i >= 0 && i < playlists.len ()) ? playlists[i].get () : nullptr; +} + +static void number_entries (PlaylistData * p, int at, int length) +{ + for (int i = at; i < at + length; i ++) + p->entries[i]->number = i; +} + +static Entry * lookup_entry (PlaylistData * p, int i) +{ + return (i >= 0 && i < p->entries.len ()) ? p->entries[i].get () : nullptr; +} + +static void update (void *) +{ + ENTER; + + for (auto & p : playlists) + { + p->last_update = p->next_update; + p->next_update = Update (); + } + + UpdateLevel level = update_level; + update_level = NoUpdate; + + LEAVE; + + hook_call ("playlist update", aud::to_ptr (level)); +} + +static bool send_playback_info (Entry * entry) +{ + // if the entry was not scanned or failed to scan, we must still call + // playback_set_info() in order to update the entry number + Tuple tuple = (entry->scanned && ! entry->failed) ? entry->tuple.ref () : Tuple (); + return playback_set_info (entry->number, entry->filename, entry->decoder, std::move (tuple)); +} + +static void queue_update (UpdateLevel level, PlaylistData * p, int at, + int count, bool queue_changed = false) +{ + if (p) + { + if (level == Structure) + scan_queue_playlist (p); + + if (level >= Metadata) + { + if (p == playing_playlist && p->position) + send_playback_info (p->position); + + p->modified = true; + } + + if (p->next_update.level) + { + p->next_update.level = aud::max (p->next_update.level, level); + p->next_update.before = aud::min (p->next_update.before, at); + p->next_update.after = aud::min (p->next_update.after, p->entries.len () - at - count); + } + else + { + p->next_update.level = level; + p->next_update.before = at; + p->next_update.after = p->entries.len () - at - count; + } + + if (queue_changed) + p->next_update.queue_changed = true; + } + + if (level == Structure) + scan_restart (); + + if (! update_level) + queued_update.queue (update, nullptr); + + update_level = aud::max (update_level, level); +} + +EXPORT bool aud_playlist_update_pending (int playlist_num) +{ + if (playlist_num >= 0) + { + ENTER_GET_PLAYLIST (false); + bool pending = playlist->next_update.level ? true : false; + RETURN (pending); + } + else + { + ENTER; + bool pending = update_level ? true : false; + RETURN (pending); + } +} + +EXPORT Update aud_playlist_update_detail (int playlist_num) +{ + ENTER_GET_PLAYLIST (Update ()); + Update update = playlist->last_update; + RETURN (update); +} + +EXPORT bool aud_playlist_scan_in_progress (int playlist_num) +{ + if (playlist_num >= 0) + { + ENTER_GET_PLAYLIST (false); + bool scanning = playlist->scanning || playlist->scan_ending; + RETURN (scanning); + } + else + { + ENTER; + + bool scanning = false; + for (auto & p : playlists) + { + if (p->scanning || p->scan_ending) + scanning = true; + } + + RETURN (scanning); + } +} + +static ScanItem * scan_list_find_playlist (PlaylistData * playlist) +{ + for (ScanItem * item = scan_list.head (); item; item = scan_list.next (item)) + { + if (item->playlist == playlist) + return item; + } + + return nullptr; +} + +static ScanItem * scan_list_find_entry (Entry * entry) +{ + for (ScanItem * item = scan_list.head (); item; item = scan_list.next (item)) + { + if (item->entry == entry) + return item; + } + + return nullptr; +} + +static ScanItem * scan_list_find_request (ScanRequest * request) +{ + for (ScanItem * item = scan_list.head (); item; item = scan_list.next (item)) + { + if (item->request == request) + return item; + } + + return nullptr; +} + +static void scan_queue_entry (PlaylistData * playlist, Entry * entry) +{ + int flags = entry->scanned ? 0 : SCAN_TUPLE; + auto request = new ScanRequest (entry->filename, flags, scan_finish, entry->decoder); + scanner_request (request); + + scan_list.append (new ScanItem (playlist, entry, request)); +} + +static void scan_check_complete (PlaylistData * playlist) +{ + if (! playlist->scan_ending || scan_list_find_playlist (playlist)) + return; + + playlist->scan_ending = false; + event_queue_cancel ("playlist scan complete", nullptr); + event_queue ("playlist scan complete", nullptr); +} + +static bool scan_queue_next_entry () +{ + if (! scan_enabled || aud_get_bool (nullptr, "metadata_on_play")) + return false; + + while (scan_playlist < playlists.len ()) + { + PlaylistData * playlist = playlists[scan_playlist].get (); + + if (playlist->scanning) + { + while (scan_row < playlist->entries.len ()) + { + Entry * entry = playlist->entries[scan_row ++].get (); + + if (! entry->scanned && ! scan_list_find_entry (entry)) + { + scan_queue_entry (playlist, entry); + return true; + } + } + + playlist->scanning = false; + playlist->scan_ending = true; + scan_check_complete (playlist); + } + + scan_playlist ++; + scan_row = 0; + } + + return false; +} + +static void scan_schedule () +{ + int scheduled = 0; + + for (ScanItem * item = scan_list.head (); item; item = scan_list.next (item)) + { + if (++ scheduled >= SCAN_THREADS) + return; + } + + while (scan_queue_next_entry ()) + { + if (++ scheduled >= SCAN_THREADS) + return; + } +} + +static void scan_finish (ScanRequest * request) +{ + ENTER; + + ScanItem * item = scan_list_find_request (request); + if (! item) + RETURN (); + + PlaylistData * playlist = item->playlist; + Entry * entry = item->entry; + + scan_list.remove (item); + delete item; + + if (! entry->decoder) + entry->decoder = request->decoder; + + if (! entry->scanned && request->tuple) + { + playlist->set_entry_tuple (entry, std::move (request->tuple)); + queue_update (Metadata, playlist, entry->number, 1); + } + + if (! entry->decoder || ! entry->scanned) + entry->set_failed (std::move (request->error)); + + scan_check_complete (playlist); + scan_schedule (); + + pthread_cond_broadcast (& cond); + + LEAVE; +} + +static void scan_cancel (Entry * entry) +{ + ScanItem * item = scan_list_find_entry (entry); + if (! item) + return; + + scan_list.remove (item); + delete (item); +} + +static void scan_queue_playlist (PlaylistData * playlist) +{ + playlist->scanning = true; + playlist->scan_ending = false; +} + +static void scan_restart () +{ + scan_playlist = 0; + scan_row = 0; + scan_schedule (); +} + +#ifdef WARN_BLOCKED +static void warn_main_thread_blocked () +{ + printf ("\nMain thread blocked, backtrace:\n"); + + void * syms[100]; + int n_syms = backtrace (syms, aud::n_elems (syms)); + char * * names = backtrace_symbols (syms, n_syms); + + for (int i = 0; i < n_syms; i ++) + printf ("%d. %s\n", i, names[i]); + + free (names); +} +#endif + +/* mutex may be unlocked during the call */ +static Entry * get_entry (int playlist_num, int entry_num, + bool need_decoder, bool need_tuple) +{ +#ifdef WARN_BLOCKED + if ((need_decoder || need_tuple) && pthread_self () == main_thread) + warn_main_thread_blocked (); +#endif + + while (1) + { + PlaylistData * playlist = lookup_playlist (playlist_num); + Entry * entry = playlist ? lookup_entry (playlist, entry_num) : nullptr; + + if (! entry || entry->failed) + return entry; + + if ((need_decoder && ! entry->decoder) || (need_tuple && ! entry->scanned)) + { + if (! scan_list_find_entry (entry)) + scan_queue_entry (playlist, entry); + + pthread_cond_wait (& cond, & mutex); + continue; + } + + return entry; + } +} + +/* mutex may be unlocked during the call */ +static Entry * get_playback_entry (int serial) +{ + while (1) + { + if (! playback_check_serial (serial)) + return nullptr; + + Entry * entry = playing_playlist ? playing_playlist->position : nullptr; + + if (! entry || entry->failed) + return entry; + + if (! entry->decoder || ! entry->scanned) + { + if (! scan_list_find_entry (entry)) + scan_queue_entry (playing_playlist, entry); + + pthread_cond_wait (& cond, & mutex); + continue; + } + + return entry; + } +} + +void playlist_init () +{ + srand (time (nullptr)); + +#ifdef WARN_BLOCKED + main_thread = pthread_self (); +#endif + + ENTER; + + update_level = NoUpdate; + scan_enabled = false; + scan_playlist = scan_row = 0; + + title_formatter.capture (new TupleCompiler); + + LEAVE; + + /* initialize title formatter */ + playlist_reformat_titles (); + + hook_associate ("set metadata_on_play", (HookFunction) playlist_trigger_scan, nullptr); + hook_associate ("set generic_title_format", (HookFunction) playlist_reformat_titles, nullptr); + hook_associate ("set show_numbers_in_pl", (HookFunction) playlist_reformat_titles, nullptr); + hook_associate ("set leading_zero", (HookFunction) playlist_reformat_titles, nullptr); +} + +void playlist_enable_scan (bool enable) +{ + ENTER; + + if (! enable) + scan_list.clear (); + + scan_enabled = enable; + + LEAVE; + + if (enable) + playlist_trigger_scan (); +} + +void playlist_end () +{ + hook_dissociate ("set metadata_on_play", (HookFunction) playlist_trigger_scan); + hook_dissociate ("set generic_title_format", (HookFunction) playlist_reformat_titles); + hook_dissociate ("set show_numbers_in_pl", (HookFunction) playlist_reformat_titles); + hook_dissociate ("set leading_zero", (HookFunction) playlist_reformat_titles); + + ENTER; + + queued_update.stop (); + + active_playlist = playing_playlist = nullptr; + resume_playlist = -1; + + playlists.clear (); + unique_id_table.clear (); + + title_formatter.clear (); + + LEAVE; +} + +EXPORT int aud_playlist_count () +{ + ENTER; + int count = playlists.len (); + RETURN (count); +} + +void playlist_insert_with_id (int at, int id) +{ + ENTER; + + if (at < 0 || at > playlists.len ()) + at = playlists.len (); + + auto playlist = new PlaylistData (id); + playlists.insert (at, 1); + playlists[at].capture (playlist); + + number_playlists (at, playlists.len () - at); + + queue_update (Structure, playlist, 0, 0); + LEAVE; +} + +EXPORT void aud_playlist_insert (int at) +{ + playlist_insert_with_id (at, -1); +} + +EXPORT void aud_playlist_reorder (int from, int to, int count) +{ + ENTER; + + if (from < 0 || from + count > playlists.len () || to < 0 || to + + count > playlists.len () || count < 0) + RETURN (); + + Index<SmartPtr<PlaylistData>> displaced; + + if (to < from) + displaced.move_from (playlists, to, -1, from - to, true, false); + else + displaced.move_from (playlists, from + count, -1, to - from, true, false); + + playlists.shift (from, to, count); + + if (to < from) + { + playlists.move_from (displaced, 0, to + count, from - to, false, true); + number_playlists (to, from + count - to); + } + else + { + playlists.move_from (displaced, 0, from, to - from, false, true); + number_playlists (from, to + count - from); + } + + queue_update (Structure, nullptr, 0, 0); + LEAVE; +} + +EXPORT void aud_playlist_delete (int playlist_num) +{ + ENTER_GET_PLAYLIST (); + + bool was_active = false; + bool was_playing = false; + + playlists.remove (playlist_num, 1); + + if (! playlists.len ()) + playlists.append (SmartNew<PlaylistData> (-1)); + + number_playlists (playlist_num, playlists.len () - playlist_num); + + if (playlist == active_playlist) + { + int active_num = aud::min (playlist_num, playlists.len () - 1); + active_playlist = playlists[active_num].get (); + was_active = true; + } + + if (playlist == playing_playlist) + { + playing_playlist = nullptr; + playback_stop (); + was_playing = true; + } + + queue_update (Structure, nullptr, 0, 0); + LEAVE; + + if (was_active) + hook_call ("playlist activate", nullptr); + + if (was_playing) + { + hook_call ("playlist set playing", nullptr); + hook_call ("playback stop", nullptr); + } +} + +EXPORT int aud_playlist_get_unique_id (int playlist_num) +{ + ENTER_GET_PLAYLIST (-1); + int unique_id = playlist->unique_id; + RETURN (unique_id); +} + +EXPORT int aud_playlist_by_unique_id (int id) +{ + ENTER; + + PlaylistData * * ptr = unique_id_table.lookup (id); + int num = (ptr && * ptr) ? (* ptr)->number : -1; + + RETURN (num); +} + +EXPORT void aud_playlist_set_filename (int playlist_num, const char * filename) +{ + ENTER_GET_PLAYLIST (); + + playlist->filename = String (filename); + playlist->modified = true; + + queue_update (Metadata, nullptr, 0, 0); + LEAVE; +} + +EXPORT String aud_playlist_get_filename (int playlist_num) +{ + ENTER_GET_PLAYLIST (String ()); + String filename = playlist->filename; + RETURN (filename); +} + +EXPORT void aud_playlist_set_title (int playlist_num, const char * title) +{ + ENTER_GET_PLAYLIST (); + + playlist->title = String (title); + playlist->modified = true; + + queue_update (Metadata, nullptr, 0, 0); + LEAVE; +} + +EXPORT String aud_playlist_get_title (int playlist_num) +{ + ENTER_GET_PLAYLIST (String ()); + String title = playlist->title; + RETURN (title); +} + +void playlist_set_modified (int playlist_num, bool modified) +{ + ENTER_GET_PLAYLIST (); + playlist->modified = modified; + LEAVE; +} + +bool playlist_get_modified (int playlist_num) +{ + ENTER_GET_PLAYLIST (false); + bool modified = playlist->modified; + RETURN (modified); +} + +EXPORT void aud_playlist_set_active (int playlist_num) +{ + ENTER_GET_PLAYLIST (); + + bool changed = false; + + if (playlist != active_playlist) + { + changed = true; + active_playlist = playlist; + } + + LEAVE; + + if (changed) + hook_call ("playlist activate", nullptr); +} + +EXPORT int aud_playlist_get_active () +{ + ENTER; + int list = active_playlist ? active_playlist->number : -1; + RETURN (list); +} + +EXPORT void aud_playlist_play (int playlist_num, bool paused) +{ + ENTER; + + PlaylistData * playlist = lookup_playlist (playlist_num); + bool position_changed = false; + + if (playlist == playing_playlist) + { + /* already playing, just need to pause/unpause */ + if (aud_drct_get_paused () != paused) + aud_drct_pause (); + + RETURN (); + } + + if (playing_playlist) + playing_playlist->resume_time = aud_drct_get_time (); + + /* is there anything to play? */ + if (playlist && ! playlist->position) + { + if (next_song_locked (playlist, true, 0)) + position_changed = true; + else + playlist = nullptr; + } + + playing_playlist = playlist; + + if (playlist) + playback_play (playlist->resume_time, paused); + else + playback_stop (); + + LEAVE; + + if (position_changed) + hook_call ("playlist position", aud::to_ptr (playlist_num)); + + hook_call ("playlist set playing", nullptr); + + if (playlist) + hook_call ("playback begin", nullptr); + else + hook_call ("playback stop", nullptr); +} + +EXPORT int aud_playlist_get_playing () +{ + ENTER; + int list = playing_playlist ? playing_playlist->number: -1; + RETURN (list); +} + +EXPORT int aud_playlist_get_blank () +{ + int list = aud_playlist_get_active (); + String title = aud_playlist_get_title (list); + + if (strcmp (title, _(default_title)) || aud_playlist_entry_count (list) > 0) + { + list = aud_playlist_count (); + aud_playlist_insert (list); + } + + return list; +} + +EXPORT int aud_playlist_get_temporary () +{ + int count = aud_playlist_count (); + + for (int list = 0; list < count; list ++) + { + String title = aud_playlist_get_title (list); + if (! strcmp (title, _(temp_title))) + return list; + } + + int list = aud_playlist_get_blank (); + aud_playlist_set_title (list, _(temp_title)); + return list; +} + +static void set_position (PlaylistData * playlist, Entry * entry, bool update_shuffle) +{ + playlist->position = entry; + playlist->resume_time = 0; + + /* move entry to top of shuffle list */ + if (entry && update_shuffle) + entry->shuffle_num = ++ playlist->last_shuffle_num; +} + +// updates playback state (while locked) if playlist position was changed +static PlaybackChange change_playback (PlaylistData * playlist) +{ + if (playlist != playing_playlist) + return NoChange; + + if (playlist->position) + { + playback_play (0, aud_drct_get_paused ()); + return NextSong; + } + else + { + playing_playlist = nullptr; + playback_stop (); + return PlaybackStopped; + } +} + +// call hooks (while unlocked) if playback state was changed +static void call_playback_change_hooks (PlaybackChange change) +{ + if (change == NextSong) + hook_call ("playback begin", nullptr); + + if (change == PlaybackStopped) + { + hook_call ("playlist set playing", nullptr); + hook_call ("playback stop", nullptr); + } +} + +EXPORT int aud_playlist_entry_count (int playlist_num) +{ + ENTER_GET_PLAYLIST (0); + int count = playlist->entries.len (); + RETURN (count); +} + +void playlist_entry_insert_batch_raw (int playlist_num, int at, Index<PlaylistAddItem> && items) +{ + ENTER_GET_PLAYLIST (); + + int entries = playlist->entries.len (); + + if (at < 0 || at > entries) + at = entries; + + int number = items.len (); + + playlist->entries.insert (at, number); + + int i = at; + for (auto & item : items) + { + Entry * entry = new Entry (std::move (item)); + playlist->entries[i ++].capture (entry); + playlist->total_length += entry->length; + } + + items.clear (); + + number_entries (playlist, at, entries + number - at); + + queue_update (Structure, playlist, at, number); + LEAVE; +} + +EXPORT void aud_playlist_entry_delete (int playlist_num, int at, int number) +{ + ENTER_GET_PLAYLIST (); + + int entries = playlist->entries.len (); + bool position_changed = false, queue_changed = false; + PlaybackChange change = NoChange; + + if (at < 0 || at > entries) + at = entries; + if (number < 0 || number > entries - at) + number = entries - at; + + if (playlist->position && playlist->position->number >= at && + playlist->position->number < at + number) + { + set_position (playlist, nullptr, false); + position_changed = true; + } + + if (playlist->focus && playlist->focus->number >= at && + playlist->focus->number < at + number) + { + if (at + number < entries) + playlist->focus = playlist->entries[at + number].get (); + else if (at > 0) + playlist->focus = playlist->entries[at - 1].get (); + else + playlist->focus = nullptr; + } + + for (int count = 0; count < number; count ++) + { + Entry * entry = playlist->entries [at + count].get (); + + if (entry->queued) + { + playlist->queued.remove (playlist->queued.find (entry), 1); + queue_changed = true; + } + + if (entry->selected) + { + playlist->selected_count --; + playlist->selected_length -= entry->length; + } + + playlist->total_length -= entry->length; + } + + playlist->entries.remove (at, number); + number_entries (playlist, at, entries - at - number); + + if (position_changed) + { + if (aud_get_bool (nullptr, "advance_on_delete")) + next_song_locked (playlist, aud_get_bool (nullptr, "repeat"), at); + + change = change_playback (playlist); + } + + queue_update (Structure, playlist, at, 0, queue_changed); + LEAVE; + + if (position_changed) + hook_call ("playlist position", aud::to_ptr (playlist_num)); + + call_playback_change_hooks (change); +} + +EXPORT String aud_playlist_entry_get_filename (int playlist_num, int entry_num) +{ + ENTER_GET_ENTRY (String ()); + String filename = entry->filename; + RETURN (filename); +} + +EXPORT PluginHandle * aud_playlist_entry_get_decoder (int playlist_num, + int entry_num, GetMode mode, String * error) +{ + ENTER; + + const bool wait = (mode == Wait || mode == WaitGuess); + + Entry * entry = get_entry (playlist_num, entry_num, wait, false); + PluginHandle * decoder = entry ? entry->decoder : nullptr; + + if (error) + * error = entry ? entry->error : String (); + + RETURN (decoder); +} + +EXPORT Tuple aud_playlist_entry_get_tuple (int playlist_num, int entry_num, + GetMode mode, String * error) +{ + ENTER; + + const bool wait = (mode == Wait || mode == WaitGuess); + const bool guess = (mode == Guess || mode == WaitGuess); + + Entry * entry = get_entry (playlist_num, entry_num, false, wait); + + Tuple tuple; + if (entry && ((entry->scanned && ! entry->failed) || guess)) + tuple = entry->tuple.ref (); + + if (error) + * error = entry ? entry->error : String (); + + RETURN (tuple); +} + +EXPORT void aud_playlist_set_position (int playlist_num, int entry_num) +{ + ENTER_GET_PLAYLIST (); + + Entry * entry = lookup_entry (playlist, entry_num); + set_position (playlist, entry, true); + + PlaybackChange change = change_playback (playlist); + + LEAVE; + + hook_call ("playlist position", aud::to_ptr (playlist_num)); + call_playback_change_hooks (change); +} + +EXPORT int aud_playlist_get_position (int playlist_num) +{ + ENTER_GET_PLAYLIST (-1); + int position = playlist->position ? playlist->position->number : -1; + RETURN (position); +} + +EXPORT void aud_playlist_set_focus (int playlist_num, int entry_num) +{ + ENTER_GET_PLAYLIST (); + + int first = INT_MAX; + int last = -1; + + if (playlist->focus) + { + first = aud::min (first, playlist->focus->number); + last = aud::max (last, playlist->focus->number); + } + + playlist->focus = lookup_entry (playlist, entry_num); + + if (playlist->focus) + { + first = aud::min (first, playlist->focus->number); + last = aud::max (last, playlist->focus->number); + } + + if (first <= last) + queue_update (Selection, playlist, first, last + 1 - first); + + LEAVE; +} + +EXPORT int aud_playlist_get_focus (int playlist_num) +{ + ENTER_GET_PLAYLIST (-1); + int focus = playlist->focus ? playlist->focus->number : -1; + RETURN (focus); +} + +EXPORT void aud_playlist_entry_set_selected (int playlist_num, int entry_num, + bool selected) +{ + ENTER_GET_ENTRY (); + + if (entry->selected == selected) + RETURN (); + + entry->selected = selected; + + if (selected) + { + playlist->selected_count++; + playlist->selected_length += entry->length; + } + else + { + playlist->selected_count--; + playlist->selected_length -= entry->length; + } + + queue_update (Selection, playlist, entry_num, 1); + LEAVE; +} + +EXPORT bool aud_playlist_entry_get_selected (int playlist_num, int entry_num) +{ + ENTER_GET_ENTRY (false); + bool selected = entry->selected; + RETURN (selected); +} + +EXPORT int aud_playlist_selected_count (int playlist_num) +{ + ENTER_GET_PLAYLIST (0); + int selected_count = playlist->selected_count; + RETURN (selected_count); +} + +EXPORT void aud_playlist_select_all (int playlist_num, bool selected) +{ + ENTER_GET_PLAYLIST (); + + int entries = playlist->entries.len (); + int first = entries, last = 0; + + for (auto & entry : playlist->entries) + { + if ((selected && ! entry->selected) || (entry->selected && ! selected)) + { + entry->selected = selected; + first = aud::min (first, entry->number); + last = entry->number; + } + } + + if (selected) + { + playlist->selected_count = entries; + playlist->selected_length = playlist->total_length; + } + else + { + playlist->selected_count = 0; + playlist->selected_length = 0; + } + + if (first < entries) + queue_update (Selection, playlist, first, last + 1 - first); + + LEAVE; +} + +EXPORT int aud_playlist_shift (int playlist_num, int entry_num, int distance) +{ + ENTER_GET_ENTRY (0); + + if (! entry->selected || ! distance) + RETURN (0); + + int entries = playlist->entries.len (); + int shift = 0, center, top, bottom; + + if (distance < 0) + { + for (center = entry_num; center > 0 && shift > distance; ) + { + if (! playlist->entries[-- center]->selected) + shift --; + } + } + else + { + for (center = entry_num + 1; center < entries && shift < distance; ) + { + if (! playlist->entries[center ++]->selected) + shift ++; + } + } + + top = bottom = center; + + for (int i = 0; i < top; i ++) + { + if (playlist->entries[i]->selected) + top = i; + } + + for (int i = entries; i > bottom; i --) + { + if (playlist->entries[i - 1]->selected) + bottom = i; + } + + Index<SmartPtr<Entry>> temp; + + for (int i = top; i < center; i ++) + { + if (! playlist->entries[i]->selected) + temp.append (std::move (playlist->entries[i])); + } + + for (int i = top; i < bottom; i ++) + { + if (playlist->entries[i] && playlist->entries[i]->selected) + temp.append (std::move (playlist->entries[i])); + } + + for (int i = center; i < bottom; i ++) + { + if (playlist->entries[i] && ! playlist->entries[i]->selected) + temp.append (std::move (playlist->entries[i])); + } + + playlist->entries.move_from (temp, 0, top, bottom - top, false, true); + + number_entries (playlist, top, bottom - top); + queue_update (Structure, playlist, top, bottom - top); + + RETURN (shift); +} + +static Entry * find_unselected_focus (PlaylistData * playlist) +{ + if (! playlist->focus || ! playlist->focus->selected) + return playlist->focus; + + int entries = playlist->entries.len (); + + for (int search = playlist->focus->number + 1; search < entries; search ++) + { + Entry * entry = playlist->entries[search].get (); + if (! entry->selected) + return entry; + } + + for (int search = playlist->focus->number; search --;) + { + Entry * entry = playlist->entries[search].get (); + if (! entry->selected) + return entry; + } + + return nullptr; +} + +EXPORT void aud_playlist_delete_selected (int playlist_num) +{ + ENTER_GET_PLAYLIST (); + + if (! playlist->selected_count) + RETURN (); + + int entries = playlist->entries.len (); + bool position_changed = false, queue_changed = false; + PlaybackChange change = NoChange; + + if (playlist->position && playlist->position->selected) + { + set_position (playlist, nullptr, false); + position_changed = true; + } + + playlist->focus = find_unselected_focus (playlist); + + int before = 0; // number of entries before first selected + int after = 0; // number of entries after last selected + + while (before < entries && ! playlist->entries[before]->selected) + before ++; + + int to = before; + + for (int from = before; from < entries; from ++) + { + Entry * entry = playlist->entries[from].get (); + + if (entry->selected) + { + if (entry->queued) + { + playlist->queued.remove (playlist->queued.find (entry), 1); + queue_changed = true; + } + + playlist->total_length -= entry->length; + after = 0; + } + else + { + playlist->entries[to ++] = std::move (playlist->entries[from]); + after ++; + } + } + + entries = to; + playlist->entries.remove (entries, -1); + number_entries (playlist, before, entries - before); + + playlist->selected_count = 0; + playlist->selected_length = 0; + + if (position_changed) + { + if (aud_get_bool (nullptr, "advance_on_delete")) + next_song_locked (playlist, aud_get_bool (nullptr, "repeat"), entries - after); + + change = change_playback (playlist); + } + + queue_update (Structure, playlist, before, entries - after - before, queue_changed); + LEAVE; + + if (position_changed) + hook_call ("playlist position", aud::to_ptr (playlist_num)); + + call_playback_change_hooks (change); +} + +EXPORT void aud_playlist_reverse (int playlist_num) +{ + ENTER_GET_PLAYLIST (); + + int entries = playlist->entries.len (); + + for (int i = 0; i < entries / 2; i ++) + std::swap (playlist->entries[i], playlist->entries[entries - 1 - i]); + + number_entries (playlist, 0, entries); + queue_update (Structure, playlist, 0, entries); + LEAVE; +} + +EXPORT void aud_playlist_reverse_selected (int playlist_num) +{ + ENTER_GET_PLAYLIST (); + + int entries = playlist->entries.len (); + + int top = 0; + int bottom = entries - 1; + + while (1) + { + while (top < bottom && ! playlist->entries[top]->selected) + top ++; + while (top < bottom && ! playlist->entries[bottom]->selected) + bottom --; + + if (top >= bottom) + break; + + std::swap (playlist->entries[top ++], playlist->entries[bottom --]); + } + + number_entries (playlist, 0, entries); + queue_update (Structure, playlist, 0, entries); + LEAVE; +} + +EXPORT void aud_playlist_randomize (int playlist_num) +{ + ENTER_GET_PLAYLIST (); + + int entries = playlist->entries.len (); + + for (int i = 0; i < entries; i ++) + std::swap (playlist->entries[i], playlist->entries[rand () % entries]); + + number_entries (playlist, 0, entries); + queue_update (Structure, playlist, 0, entries); + LEAVE; +} + +EXPORT void aud_playlist_randomize_selected (int playlist_num) +{ + ENTER_GET_PLAYLIST (); + + int entries = playlist->entries.len (); + + Index<Entry *> selected; + + for (auto & entry : playlist->entries) + { + if (entry->selected) + selected.append (entry.get ()); + } + + int n_selected = selected.len (); + + for (int i = 0; i < n_selected; i ++) + { + int a = selected[i]->number; + int b = selected[rand () % n_selected]->number; + std::swap (playlist->entries[a], playlist->entries[b]); + } + + number_entries (playlist, 0, entries); + queue_update (Structure, playlist, 0, entries); + LEAVE; +} + +enum {COMPARE_TYPE_FILENAME, COMPARE_TYPE_TUPLE, COMPARE_TYPE_TITLE}; + +struct CompareData { + PlaylistStringCompareFunc filename_compare; + PlaylistTupleCompareFunc tuple_compare; +}; + +static int compare_cb (const SmartPtr<Entry> & a, const SmartPtr<Entry> & b, void * _data) +{ + CompareData * data = (CompareData *) _data; + + int diff = 0; + + if (data->filename_compare) + diff = data->filename_compare (a->filename, b->filename); + else if (data->tuple_compare) + diff = data->tuple_compare (a->tuple, b->tuple); + + if (diff) + return diff; + + /* preserve order of "equal" entries */ + return a->number - b->number; +} + +static void sort (PlaylistData * playlist, CompareData * data) +{ + playlist->entries.sort (compare_cb, data); + number_entries (playlist, 0, playlist->entries.len ()); + + queue_update (Structure, playlist, 0, playlist->entries.len ()); +} + +static void sort_selected (PlaylistData * playlist, CompareData * data) +{ + int entries = playlist->entries.len (); + + Index<SmartPtr<Entry>> selected; + + for (auto & entry : playlist->entries) + { + if (entry->selected) + selected.append (std::move (entry)); + } + + selected.sort (compare_cb, data); + + int i = 0; + for (auto & entry : playlist->entries) + { + if (! entry) + entry = std::move (selected[i ++]); + } + + number_entries (playlist, 0, entries); + queue_update (Structure, playlist, 0, entries); +} + +static bool entries_are_scanned (PlaylistData * playlist, bool selected) +{ + for (auto & entry : playlist->entries) + { + if (selected && ! entry->selected) + continue; + + if (! entry->scanned) + { + aud_ui_show_error (_("The playlist cannot be sorted because " + "metadata scanning is still in progress (or has been disabled).")); + return false; + } + } + + return true; +} + +EXPORT void aud_playlist_sort_by_filename (int playlist_num, PlaylistStringCompareFunc compare) +{ + ENTER_GET_PLAYLIST (); + + CompareData data = {compare}; + sort (playlist, & data); + + LEAVE; +} + +EXPORT void aud_playlist_sort_by_tuple (int playlist_num, PlaylistTupleCompareFunc compare) +{ + ENTER_GET_PLAYLIST (); + + CompareData data = {nullptr, compare}; + if (entries_are_scanned (playlist, false)) + sort (playlist, & data); + + LEAVE; +} + +EXPORT void aud_playlist_sort_selected_by_filename (int playlist_num, + PlaylistStringCompareFunc compare) +{ + ENTER_GET_PLAYLIST (); + + CompareData data = {compare}; + sort_selected (playlist, & data); + + LEAVE; +} + +EXPORT void aud_playlist_sort_selected_by_tuple (int playlist_num, + PlaylistTupleCompareFunc compare) +{ + ENTER_GET_PLAYLIST (); + + CompareData data = {nullptr, compare}; + if (entries_are_scanned (playlist, true)) + sort_selected (playlist, & data); + + LEAVE; +} + +static void playlist_reformat_titles () +{ + ENTER; + + String format = aud_get_str (nullptr, "generic_title_format"); + title_formatter->compile (format); + + for (auto & playlist : playlists) + { + for (auto & entry : playlist->entries) + title_formatter->format (entry->tuple); + + queue_update (Metadata, playlist.get (), 0, playlist->entries.len ()); + } + + LEAVE; +} + +static void playlist_trigger_scan () +{ + ENTER; + + for (auto & playlist : playlists) + scan_queue_playlist (playlist.get ()); + + scan_restart (); + + LEAVE; +} + +static void playlist_rescan_real (int playlist_num, bool selected) +{ + ENTER_GET_PLAYLIST (); + + for (auto & entry : playlist->entries) + { + if (! selected || entry->selected) + playlist->set_entry_tuple (entry.get (), Tuple ()); + } + + queue_update (Metadata, playlist, 0, playlist->entries.len ()); + scan_queue_playlist (playlist); + scan_restart (); + LEAVE; +} + +EXPORT void aud_playlist_rescan (int playlist_num) +{ + playlist_rescan_real (playlist_num, false); +} + +EXPORT void aud_playlist_rescan_selected (int playlist_num) +{ + playlist_rescan_real (playlist_num, true); +} + +EXPORT void aud_playlist_rescan_file (const char * filename) +{ + ENTER; + + bool restart = false; + + for (auto & playlist : playlists) + { + bool queue = false; + + for (auto & entry : playlist->entries) + { + if (! strcmp (entry->filename, filename)) + { + playlist->set_entry_tuple (entry.get (), Tuple ()); + queue_update (Metadata, playlist.get (), entry->number, 1); + queue = true; + } + } + + if (queue) + { + scan_queue_playlist (playlist.get ()); + restart = true; + } + } + + if (restart) + scan_restart (); + + LEAVE; +} + +EXPORT int64_t aud_playlist_get_total_length (int playlist_num) +{ + ENTER_GET_PLAYLIST (0); + int64_t length = playlist->total_length; + RETURN (length); +} + +EXPORT int64_t aud_playlist_get_selected_length (int playlist_num) +{ + ENTER_GET_PLAYLIST (0); + int64_t length = playlist->selected_length; + RETURN (length); +} + +EXPORT int aud_playlist_queue_count (int playlist_num) +{ + ENTER_GET_PLAYLIST (0); + int count = playlist->queued.len (); + RETURN (count); +} + +EXPORT void aud_playlist_queue_insert (int playlist_num, int at, int entry_num) +{ + ENTER_GET_ENTRY (); + + if (entry->queued || at > playlist->queued.len ()) + RETURN (); + + if (at < 0) + playlist->queued.append (entry); + else + { + playlist->queued.insert (at, 1); + playlist->queued[at] = entry; + } + + entry->queued = true; + + queue_update (Selection, playlist, entry_num, 1, true); + LEAVE; +} + +EXPORT void aud_playlist_queue_insert_selected (int playlist_num, int at) +{ + ENTER_GET_PLAYLIST (); + + if (at > playlist->queued.len ()) + RETURN (); + + Index<Entry *> add; + int first = playlist->entries.len (); + int last = 0; + + for (auto & entry : playlist->entries) + { + if (! entry->selected || entry->queued) + continue; + + add.append (entry.get ()); + entry->queued = true; + first = aud::min (first, entry->number); + last = entry->number; + } + + playlist->queued.move_from (add, 0, at, -1, true, true); + + if (first < playlist->entries.len ()) + queue_update (Selection, playlist, first, last + 1 - first, true); + + LEAVE; +} + +EXPORT int aud_playlist_queue_get_entry (int playlist_num, int at) +{ + ENTER_GET_PLAYLIST (-1); + + int entry_num = -1; + if (at >= 0 && at < playlist->queued.len ()) + entry_num = playlist->queued[at]->number; + + RETURN (entry_num); +} + +EXPORT int aud_playlist_queue_find_entry (int playlist_num, int entry_num) +{ + ENTER_GET_ENTRY (-1); + int pos = entry->queued ? playlist->queued.find (entry) : -1; + RETURN (pos); +} + +EXPORT void aud_playlist_queue_delete (int playlist_num, int at, int number) +{ + ENTER_GET_PLAYLIST (); + + if (at < 0 || number < 0 || at + number > playlist->queued.len ()) + RETURN (); + + int entries = playlist->entries.len (); + int first = entries, last = 0; + + for (int i = at; i < at + number; i ++) + { + Entry * entry = playlist->queued[i]; + entry->queued = false; + first = aud::min (first, entry->number); + last = entry->number; + } + + playlist->queued.remove (at, number); + + if (first < entries) + queue_update (Selection, playlist, first, last + 1 - first, true); + + LEAVE; +} + +EXPORT void aud_playlist_queue_delete_selected (int playlist_num) +{ + ENTER_GET_PLAYLIST (); + + int entries = playlist->entries.len (); + int first = entries, last = 0; + + for (int i = 0; i < playlist->queued.len ();) + { + Entry * entry = playlist->queued[i]; + + if (entry->selected) + { + playlist->queued.remove (i, 1); + entry->queued = false; + first = aud::min (first, entry->number); + last = entry->number; + } + else + i ++; + } + + if (first < entries) + queue_update (Selection, playlist, first, last + 1 - first, true); + + LEAVE; +} + +static bool shuffle_prev (PlaylistData * playlist) +{ + Entry * found = nullptr; + + for (auto & entry : playlist->entries) + { + if (entry->shuffle_num && (! playlist->position || + entry->shuffle_num < playlist->position->shuffle_num) && (! found + || entry->shuffle_num > found->shuffle_num)) + found = entry.get (); + } + + if (! found) + return false; + + set_position (playlist, found, false); + return true; +} + +bool playlist_prev_song (int playlist_num) +{ + ENTER_GET_PLAYLIST (false); + + if (aud_get_bool (nullptr, "shuffle")) + { + if (! shuffle_prev (playlist)) + RETURN (false); + } + else + { + if (! playlist->position || playlist->position->number == 0) + RETURN (false); + + set_position (playlist, playlist->entries[playlist->position->number - 1].get (), true); + } + + PlaybackChange change = change_playback (playlist); + + LEAVE; + + hook_call ("playlist position", aud::to_ptr (playlist_num)); + call_playback_change_hooks (change); + return true; +} + +static bool shuffle_next (PlaylistData * playlist) +{ + int choice = 0; + Entry * found = nullptr; + + for (auto & entry : playlist->entries) + { + if (! entry->shuffle_num) + choice ++; + else if (playlist->position && + entry->shuffle_num > playlist->position->shuffle_num && + (! found || entry->shuffle_num < found->shuffle_num)) + found = entry.get (); + } + + if (found) + { + set_position (playlist, found, false); + return true; + } + + if (! choice) + return false; + + choice = rand () % choice; + + for (auto & entry : playlist->entries) + { + if (! entry->shuffle_num) + { + if (! choice) + { + set_position (playlist, entry.get (), true); + break; + } + + choice --; + } + } + + return true; +} + +static void shuffle_reset (PlaylistData * playlist) +{ + playlist->last_shuffle_num = 0; + + for (auto & entry : playlist->entries) + entry->shuffle_num = 0; +} + +static bool next_song_locked (PlaylistData * playlist, bool repeat, int hint) +{ + int entries = playlist->entries.len (); + if (! entries) + return false; + + if (playlist->queued.len ()) + { + set_position (playlist, playlist->queued[0], true); + playlist->queued.remove (0, 1); + playlist->position->queued = false; + + queue_update (Selection, playlist, playlist->position->number, 1, true); + } + else if (aud_get_bool (nullptr, "shuffle")) + { + if (! shuffle_next (playlist)) + { + if (! repeat) + return false; + + shuffle_reset (playlist); + + if (! shuffle_next (playlist)) + return false; + } + } + else + { + if (hint >= entries) + { + if (! repeat) + return false; + + hint = 0; + } + + set_position (playlist, playlist->entries[hint].get (), true); + } + + return true; +} + +bool playlist_next_song (int playlist_num, bool repeat) +{ + ENTER_GET_PLAYLIST (false); + + int hint = playlist->position ? playlist->position->number + 1 : 0; + + if (! next_song_locked (playlist, repeat, hint)) + RETURN (false); + + PlaybackChange change = change_playback (playlist); + + LEAVE; + + hook_call ("playlist position", aud::to_ptr (playlist_num)); + call_playback_change_hooks (change); + return true; +} + +bool playback_entry_read (int serial, String & error) +{ + ENTER; + bool success = false; + Entry * entry = get_playback_entry (serial); + + if (entry && send_playback_info (entry)) + { + success = ! entry->failed; + error = entry->error; + } + + RETURN (success); +} + +void playback_entry_set_tuple (int serial, Tuple && tuple) +{ + ENTER; + PlaylistData * playlist = playing_playlist; + if (! playlist || ! playlist->position || ! playback_check_serial (serial)) + RETURN (); + + Entry * entry = playlist->position; + playlist->set_entry_tuple (entry, std::move (tuple)); + + queue_update (Metadata, playlist, entry->number, 1); + LEAVE; +} + +void playlist_save_state () +{ + /* get playback state before locking playlists */ + bool paused = aud_drct_get_paused (); + int time = aud_drct_get_time (); + + ENTER; + + const char * user_dir = aud_get_path (AudPath::UserDir); + StringBuf path = filename_build ({user_dir, STATE_FILE}); + + FILE * handle = g_fopen (path, "w"); + if (! handle) + RETURN (); + + fprintf (handle, "active %d\n", active_playlist ? active_playlist->number : -1); + fprintf (handle, "playing %d\n", playing_playlist ? playing_playlist->number : -1); + + for (auto & playlist : playlists) + { + fprintf (handle, "playlist %d\n", playlist->number); + + if (playlist->filename) + fprintf (handle, "filename %s\n", (const char *) playlist->filename); + + fprintf (handle, "position %d\n", playlist->position ? playlist->position->number : -1); + + /* resume state is stored per-playlist for historical reasons */ + bool is_playing = (playlist.get () == playing_playlist); + fprintf (handle, "resume-state %d\n", (is_playing && paused) ? ResumePause : ResumePlay); + fprintf (handle, "resume-time %d\n", is_playing ? time : playlist->resume_time); + } + + fclose (handle); + LEAVE; +} + +static char parse_key[512]; +static char * parse_value; + +static void parse_next (FILE * handle) +{ + parse_value = nullptr; + + if (! fgets (parse_key, sizeof parse_key, handle)) + return; + + char * space = strchr (parse_key, ' '); + if (! space) + return; + + * space = 0; + parse_value = space + 1; + + char * newline = strchr (parse_value, '\n'); + if (newline) + * newline = 0; +} + +static bool parse_integer (const char * key, int * value) +{ + return (parse_value && ! strcmp (parse_key, key) && sscanf (parse_value, "%d", value) == 1); +} + +static String parse_string (const char * key) +{ + return (parse_value && ! strcmp (parse_key, key)) ? String (parse_value) : String (); +} + +void playlist_load_state () +{ + ENTER; + int playlist_num; + + const char * user_dir = aud_get_path (AudPath::UserDir); + StringBuf path = filename_build ({user_dir, STATE_FILE}); + + FILE * handle = g_fopen (path, "r"); + if (! handle) + RETURN (); + + parse_next (handle); + + if (parse_integer ("active", & playlist_num)) + { + if (! (active_playlist = lookup_playlist (playlist_num))) + active_playlist = playlists[0].get (); + parse_next (handle); + } + + if (parse_integer ("playing", & resume_playlist)) + parse_next (handle); + + while (parse_integer ("playlist", & playlist_num) && playlist_num >= 0 && + playlist_num < playlists.len ()) + { + PlaylistData * playlist = playlists[playlist_num].get (); + int entries = playlist->entries.len (); + + parse_next (handle); + + playlist->filename = parse_string ("filename"); + if (playlist->filename) + parse_next (handle); + + int position = -1; + if (parse_integer ("position", & position)) + parse_next (handle); + + if (position >= 0 && position < entries) + set_position (playlist, playlist->entries [position].get (), true); + + /* resume state is stored per-playlist for historical reasons */ + int resume_state = ResumePlay; + if (parse_integer ("resume-state", & resume_state)) + parse_next (handle); + + if (playlist_num == resume_playlist) + { + if (resume_state == ResumeStop) + resume_playlist = -1; + if (resume_state == ResumePause) + resume_paused = true; + } + + if (parse_integer ("resume-time", & playlist->resume_time)) + parse_next (handle); + } + + fclose (handle); + + /* clear updates queued during init sequence */ + + for (auto & playlist : playlists) + { + playlist->next_update = Update (); + playlist->last_update = Update (); + } + + queued_update.stop (); + update_level = NoUpdate; + + LEAVE; +} + +EXPORT void aud_resume () +{ + if (aud_get_bool (nullptr, "always_resume_paused")) + resume_paused = true; + + aud_playlist_play (resume_playlist, resume_paused); +} diff --git a/src/audacious/playlist-api.h b/src/libaudcore/playlist.h index 634fd05..6649a65 100644 --- a/src/audacious/playlist-api.h +++ b/src/libaudcore/playlist.h @@ -1,5 +1,5 @@ /* - * playlist-api.h + * playlist.h * Copyright 2010-2013 John Lindgren * * Redistribution and use in source and binary forms, with or without @@ -17,346 +17,365 @@ * the use of this software. */ -/* Do not include this file directly; use playlist.h instead. */ - -/* Any functions in this API with a return type of (char *) return pooled - * strings that must not be modified and must be released with str_unref() when - * no longer needed. */ +#ifndef LIBAUDCORE_PLAYLIST_H +#define LIBAUDCORE_PLAYLIST_H + +#include <stdint.h> + +#include <libaudcore/index.h> +#include <libaudcore/tuple.h> + +namespace Playlist { + +/* The values which can be passed to the "playlist update" hook. Selection + * means that entries have been selected or unselected, or that entries have + * been added to or removed from the queue. Metadata means that new metadata + * has been read for some entries, or that the title or filename of a playlist + * has changed, and implies Selection. Structure covers any other change, and + * implies both Selection and Metadata. */ +enum UpdateLevel { + NoUpdate = 0, + Selection, + Metadata, + Structure +}; + +struct Update { + UpdateLevel level; // type of update + int before; // number of unaffected entries at playlist start + int after; // number of unaffected entries at playlist end + bool queue_changed; // true if entries have been added to/removed from queue +}; + +/* The values which can be passed to playlist_sort_by_scheme(), + * playlist_sort_selected_by_scheme(), and + * playlist_remove_duplicates_by_scheme(). PlaylistSort::Path means the entire + * URI of a song file; PlaylistSort::Filename means the portion after the last + * "/" (forward slash). PlaylistSort::Date means the song's release date (not + * the file's modification time). */ +enum SortType { + Path, + Filename, + Title, + Album, + Artist, + AlbumArtist, + Date, + Genre, + Track, + FormattedTitle, + Length, + n_sort_types +}; + +/* Possible behaviors for playlist_entry_get_{decoder, tuple}. */ +enum GetMode { + Nothing, // immediately return nullptr or Tuple() if not yet scanned + Guess, // immediately return a best guess if not yet scanned + Wait, // wait for the entry to be scanned; return nullptr or Tuple() on failure + WaitGuess // wait for the entry to be scanned; return a best guess on failure +}; + +} // namespace Playlist + +typedef bool (* PlaylistFilterFunc) (const char * filename, void * user); +typedef int (* PlaylistStringCompareFunc) (const char * a, const char * b); +typedef int (* PlaylistTupleCompareFunc) (const Tuple & a, const Tuple & b); /* --- PLAYLIST CORE API --- */ /* Returns the number of playlists currently open. There will always be at * least one playlist open. The playlists are numbered starting from zero. */ -AUD_FUNC0 (int, playlist_count) +int aud_playlist_count (); /* Adds a new playlist before the one numbered <at>. If <at> is -1 or equal to * the number of playlists, adds a new playlist after the last one. */ -AUD_VFUNC1 (playlist_insert, int, at) +void aud_playlist_insert (int at); /* Moves a contiguous block of <count> playlists starting with the one numbered * <from> such that that playlist ends up at the position <to>. */ -AUD_VFUNC3 (playlist_reorder, int, from, int, to, int, count) +void aud_playlist_reorder (int from, int to, int count); /* Closes a playlist. CAUTION: The playlist is not saved, and no confirmation * is presented to the user. If <playlist> is the only playlist, a new playlist * is added. If <playlist> is the active playlist, another playlist is marked * active. If <playlist> is the currently playing playlist, playback is * stopped. */ -AUD_VFUNC1 (playlist_delete, int, playlist) +void aud_playlist_delete (int playlist); /* Returns a unique non-negative integer which can be used to identify a given * playlist even if its numbering changes (as when playlists are reordered). * On error, returns -1. */ -AUD_FUNC1 (int, playlist_get_unique_id, int, playlist) +int aud_playlist_get_unique_id (int playlist); /* Returns the number of the playlist identified by a given integer ID as * returned by playlist_get_unique_id(). If the playlist no longer exists, * returns -1. */ -AUD_FUNC1 (int, playlist_by_unique_id, int, id) +int aud_playlist_by_unique_id (int id); /* Sets the filename associated with a playlist. (Audacious currently makes no * use of the filename.) */ -AUD_VFUNC2 (playlist_set_filename, int, playlist, const char *, filename) +void aud_playlist_set_filename (int playlist, const char * filename); /* Returns the filename associated with a playlist. */ -AUD_FUNC1 (char *, playlist_get_filename, int, playlist) +String aud_playlist_get_filename (int playlist); /* Sets the title associated with a playlist. */ -AUD_VFUNC2 (playlist_set_title, int, playlist, const char *, title) +void aud_playlist_set_title (int playlist, const char * title); /* Returns the title associated with a playlist. */ -AUD_FUNC1 (char *, playlist_get_title, int, playlist) +String aud_playlist_get_title (int playlist); /* Sets the active playlist. This is the playlist that user interfaces will * show to the user. */ -AUD_VFUNC1 (playlist_set_active, int, playlist) +void aud_playlist_set_active (int playlist); /* Returns the number of the active playlist. */ -AUD_FUNC0 (int, playlist_get_active) +int aud_playlist_get_active (); -/* Sets the currently playing playlist. Starts playback, resuming from the - * position last played if possible. If <playlist> is -1 or if the requested - * playlist is empty, stops playback. */ -AUD_VFUNC1 (playlist_set_playing, int, playlist) +/* Starts playback of a playlist, resuming from the position last played if + * possible. If <playlist> is -1 or if the requested playlist is empty, stops + * playback. If <paused> is true, starts playback in a paused state. */ +void aud_playlist_play (int playlist, bool paused = false); /* Returns the number of the currently playing playlist. If no playlist is * playing, returns -1. */ -AUD_FUNC0 (int, playlist_get_playing) +int aud_playlist_get_playing (); /* Returns the number of a "blank" playlist. The active playlist is returned if * it has the default title and has no entries; otherwise, a new playlist is * added and returned. */ -AUD_FUNC0 (int, playlist_get_blank) +int aud_playlist_get_blank (); /* Returns the number of the "temporary" playlist (which is no different from * any other playlist except in name). If the playlist does not exist, a * "blank" playlist is obtained from playlist_get_blank() and is renamed to * become the temporary playlist. */ -AUD_FUNC0 (int, playlist_get_temporary) +int aud_playlist_get_temporary (); /* Returns the number of entries in a playlist. The entries are numbered * starting from zero. */ -AUD_FUNC1 (int, playlist_entry_count, int, playlist) +int aud_playlist_entry_count (int playlist); /* Adds a song file, playlist file, or folder to a playlist before the entry * numbered <at>. If <at> is negative or equal to the number of entries, the - * item is added after the last entry. <tuple> may be NULL, in which case - * Audacious will attempt to read metadata from the song file. The caller gives - * up one reference count to <tuple>. If <play> is nonzero, Audacious will - * begin playback of the items once they have been added. + * item is added after the last entry. <tuple> may be nullptr, in which case + * Audacious will attempt to read metadata from the song file. If <play> is + * true, Audacious will begin playback of the items once they have been + * added. * * Because adding items to the playlist can be a slow process, this function may * return before the process is complete. Hence, the caller must not assume * that there will be new entries in the playlist immediately. */ -AUD_VFUNC5 (playlist_entry_insert, int, playlist, int, at, const char *, - filename, Tuple *, tuple, bool_t, play) +void aud_playlist_entry_insert (int playlist, int at, const char * filename, + Tuple && tuple, bool play); /* Similar to playlist_entry_insert, adds multiple song files, playlist files, - * or folders to a playlist. The filenames, stored as (char *) in an index - * (see libaudcore/index.h), must be pooled with str_get(); the caller gives up - * one reference count to each filename. Tuples are likewise stored as - * (Tuple *) in an index of the same length; the caller gives up one reference - * count to each tuple. <tuples> may be NULL, or individual pointers within it - * may be NULL. Finally, the caller also gives up ownership of the indexes - * themselves and should not access them after the call. */ -AUD_VFUNC5 (playlist_entry_insert_batch, int, playlist, int, at, - Index *, filenames, Index *, tuples, bool_t, play) + * or folders to a playlist. */ +void aud_playlist_entry_insert_batch (int playlist, int at, + Index<PlaylistAddItem> && items, bool play); /* Similar to playlist_entry_insert_batch, but allows the caller to prevent some * items from being added by returning false from the <filter> callback. Useful - * for searching a folder and adding only new files to the playlist. Filenames - * passed to the callback can be used with str_ref(), str_equal(), etc. <user> - * is an additional, untyped pointer passed to the callback. */ -AUD_VFUNC7 (playlist_entry_insert_filtered, int, playlist, int, at, - Index *, filenames, Index *, tuples, PlaylistFilterFunc, filter, - void *, user, bool_t, play) + * for searching a folder and adding only new files to the playlist. <user> is + * an additional, untyped pointer passed to the callback. */ +void aud_playlist_entry_insert_filtered (int playlist, int at, + Index<PlaylistAddItem> && items, PlaylistFilterFunc filter, void * user, + bool play); /* Removes a contiguous block of <number> entries starting from the one numbered * <at> from a playlist. If necessary, the playback position is moved elsewhere * in the playlist and playback is restarted (or stopped). */ -AUD_VFUNC3 (playlist_entry_delete, int, playlist, int, at, int, number) +void aud_playlist_entry_delete (int playlist, int at, int number); /* Returns the filename of an entry. */ -AUD_FUNC2 (char *, playlist_entry_get_filename, int, playlist, int, entry) - -/* Returns a handle to the decoder plugin associated with an entry, or NULL if - * none can be found. If <fast> is nonzero, returns NULL if no decoder plugin - * has yet been found. */ -AUD_FUNC3 (PluginHandle *, playlist_entry_get_decoder, int, playlist, int, - entry, bool_t, fast) - -/* Returns the tuple associated with an entry, or NULL if one is not available. - * The reference count of the tuple is incremented. If <fast> is nonzero, - * returns NULL if metadata for the entry has not yet been read from the song - * file. */ -AUD_FUNC3 (Tuple *, playlist_entry_get_tuple, int, playlist, int, entry, - bool_t, fast) - -/* Returns a formatted title string for an entry. This may include information - * such as the filename, song title, and/or artist. If <fast> is nonzero, - * returns a "best guess" based on the entry's filename if metadata for the - * entry has not yet been read. */ -AUD_FUNC3 (char *, playlist_entry_get_title, int, playlist, int, entry, - bool_t, fast) - -/* Returns three strings (title, artist, and album) describing an entry. The - * strings are pooled, and the usual cautions apply. If <fast> is nonzero, - * returns a "best guess" based on the entry's filename if metadata for the - * entry has not yet been read. The caller may pass NULL for any values that - * are not needed; NULL may also be returned for any values that are not - * available. */ -AUD_VFUNC6 (playlist_entry_describe, int, playlist, int, entry, - char * *, title, char * *, artist, char * *, album, bool_t, fast) - -/* Returns the length in milliseconds of an entry, or -1 if the length is not - * known. <fast> is as in playlist_entry_get_tuple(). */ -AUD_FUNC3 (int, playlist_entry_get_length, int, playlist, int, entry, - bool_t, fast) +String aud_playlist_entry_get_filename (int playlist, int entry); + +/* Returns a handle to the decoder plugin associated with an entry. On error, + * or if the entry has not yet been scanned, returns nullptr according to + * <mode>. On error, an error message is optionally returned. */ +PluginHandle * aud_playlist_entry_get_decoder (int playlist, int entry, + Playlist::GetMode mode = Playlist::WaitGuess, String * error = nullptr); + +/* Returns the tuple associated with an entry. On error, or if the entry has + * not yet been scanned, returns either a blank tuple or a tuple filled with + * "best guess" values, according to <mode>. On error, an error message is + * optionally returned. */ +Tuple aud_playlist_entry_get_tuple (int playlist, int entry, + Playlist::GetMode mode = Playlist::WaitGuess, String * error = nullptr); /* Moves the playback position to the beginning of the entry at <position>. If * <position> is -1, unsets the playback position. If <playlist> is the * currently playing playlist, playback is restarted (or stopped). */ -AUD_VFUNC2 (playlist_set_position, int, playlist, int, position) +void aud_playlist_set_position (int playlist, int position); /* Returns the playback position, or -1 if it is not set. Note that the * position may be set even if <playlist> is not currently playing. */ -AUD_FUNC1 (int, playlist_get_position, int, playlist) +int aud_playlist_get_position (int playlist); + +/* Sets the entry which has keyboard focus (-1 means no entry). */ +void aud_playlist_set_focus (int playlist, int entry); + +/* Gets the entry which has keyboard focus (-1 means no entry). */ +int aud_playlist_get_focus (int playlist); /* Sets whether an entry is selected. */ -AUD_VFUNC3 (playlist_entry_set_selected, int, playlist, int, entry, - bool_t, selected) +void aud_playlist_entry_set_selected (int playlist, int entry, bool selected); /* Returns whether an entry is selected. */ -AUD_FUNC2 (bool_t, playlist_entry_get_selected, int, playlist, int, entry) +bool aud_playlist_entry_get_selected (int playlist, int entry); /* Returns the number of selected entries in a playlist. */ -AUD_FUNC1 (int, playlist_selected_count, int, playlist) +int aud_playlist_selected_count (int playlist); /* Selects all (or none) of the entries in a playlist. */ -AUD_VFUNC2 (playlist_select_all, int, playlist, bool_t, selected) +void aud_playlist_select_all (int playlist, bool selected); /* Moves a selected entry within a playlist by an offset of <distance> entries. * Other selected entries are gathered around it. Returns the offset by which * the entry was actually moved, which may be less in absolute value than the * requested offset. */ -AUD_FUNC3 (int, playlist_shift, int, playlist, int, position, int, distance) +int aud_playlist_shift (int playlist, int position, int distance); /* Removes the selected entries from a playlist. If necessary, the playback * position is moved elsewhere in the playlist and playback is restarted (or * stopped). */ -AUD_VFUNC1 (playlist_delete_selected, int, playlist) +void aud_playlist_delete_selected (int playlist); /* Sorts the entries in a playlist based on filename. The callback function * should return negative if the first filename comes before the second, * positive if it comes after, or zero if the two are indistinguishable. */ -AUD_VFUNC2 (playlist_sort_by_filename, int, playlist, - PlaylistStringCompareFunc, compare) +void aud_playlist_sort_by_filename (int playlist, PlaylistStringCompareFunc compare); /* Sorts the entries in a playlist based on tuple. May fail if metadata * scanning is still in progress (or has been disabled). */ -AUD_VFUNC2 (playlist_sort_by_tuple, int, playlist, - PlaylistTupleCompareFunc, compare) +void aud_playlist_sort_by_tuple (int playlist, PlaylistTupleCompareFunc compare); /* Sorts the entries in a playlist based on formatted title string. May fail if * metadata scanning is still in progress (or has been disabled). */ -AUD_VFUNC2 (playlist_sort_by_title, int, playlist, - PlaylistStringCompareFunc, compare) +void aud_playlist_sort_by_title (int playlist, PlaylistStringCompareFunc compare); /* Sorts only the selected entries in a playlist based on filename. */ -AUD_VFUNC2 (playlist_sort_selected_by_filename, int, playlist, - PlaylistStringCompareFunc, compare) +void aud_playlist_sort_selected_by_filename (int playlist, PlaylistStringCompareFunc compare); /* Sorts only the selected entries in a playlist based on tuple. May fail if * metadata scanning is still in progress (or has been disabled). */ -AUD_VFUNC2 (playlist_sort_selected_by_tuple, int, playlist, - PlaylistTupleCompareFunc, compare) +void aud_playlist_sort_selected_by_tuple (int playlist, PlaylistTupleCompareFunc compare); /* Sorts only the selected entries in a playlist based on formatted title * string. May fail if metadata scanning is still in progress (or has been * disabled). */ -AUD_VFUNC2 (playlist_sort_selected_by_title, int, playlist, - PlaylistStringCompareFunc, compare) +void aud_playlist_sort_selected_by_title (int playlist, PlaylistStringCompareFunc compare); /* Reverses the order of the entries in a playlist. */ -AUD_VFUNC1 (playlist_reverse, int, playlist) +void aud_playlist_reverse (int playlist); /* Reorders the entries in a playlist randomly. */ -AUD_VFUNC1 (playlist_randomize, int, playlist) +void aud_playlist_randomize (int playlist); + +/* Reverses the order of the selected entries in a playlist. */ +void aud_playlist_reverse_selected (int playlist); + +/* Reorders the selected entries in a playlist randomly. */ +void aud_playlist_randomize_selected (int playlist); /* Discards the metadata stored for all the entries in a playlist and starts * reading it afresh from the song files in the background. */ -AUD_VFUNC1 (playlist_rescan, int, playlist) +void aud_playlist_rescan (int playlist); /* Like playlist_rescan, but applies only to the selected entries in a playlist. */ -AUD_VFUNC1 (playlist_rescan_selected, int, playlist) +void aud_playlist_rescan_selected (int playlist); /* Discards the metadata stored for all the entries that refer to a particular * song file, in whatever playlist they appear, and starts reading it afresh * from that file in the background. */ -AUD_VFUNC1 (playlist_rescan_file, const char *, filename) +void aud_playlist_rescan_file (const char * filename); /* Calculates the total length in milliseconds of all the entries in a playlist. * Only takes into account entries for which metadata has already been read. */ -AUD_FUNC1 (int64_t, playlist_get_total_length, int, playlist) +int64_t aud_playlist_get_total_length (int playlist); /* Calculates the total length in milliseconds of only the selected entries in a * playlist. Only takes into account entries for which metadata has already * been read. */ -AUD_FUNC1 (int64_t, playlist_get_selected_length, int, playlist) +int64_t aud_playlist_get_selected_length (int playlist); /* Returns the number of entries in a playlist queue. The entries are numbered * starting from zero, lower numbers being played first. */ -AUD_FUNC1 (int, playlist_queue_count, int, playlist) +int aud_playlist_queue_count (int playlist); /* Adds an entry to a playlist's queue before the entry numbered <at> in the * queue. If <at> is negative or equal to the number of entries in the queue, * adds the entry after the last one in the queue. The same entry cannot be * added to the queue more than once. */ -AUD_VFUNC3 (playlist_queue_insert, int, playlist, int, at, int, entry) +void aud_playlist_queue_insert (int playlist, int at, int entry); /* Adds the selected entries in a playlist to the queue, if they are not already * in it. */ -AUD_VFUNC2 (playlist_queue_insert_selected, int, playlist, int, at) +void aud_playlist_queue_insert_selected (int playlist, int at); /* Returns the position in the playlist of the entry at a given position in the * queue. */ -AUD_FUNC2 (int, playlist_queue_get_entry, int, playlist, int, at) +int aud_playlist_queue_get_entry (int playlist, int at); /* Returns the position in the queue of the entry at a given position in the * playlist. If it is not in the queue, returns -1. */ -AUD_FUNC2 (int, playlist_queue_find_entry, int, playlist, int, entry) +int aud_playlist_queue_find_entry (int playlist, int entry); /* Removes a contiguous block of <number> entries starting with the one numbered * <at> from the queue. */ -AUD_VFUNC3 (playlist_queue_delete, int, playlist, int, at, int, number) +void aud_playlist_queue_delete (int playlist, int at, int number); /* Removes the selected entries in a playlist from the queue, if they are in it. */ -AUD_VFUNC1 (playlist_queue_delete_selected, int, playlist) +void aud_playlist_queue_delete_selected (int playlist); -/* Returns nonzero if a "playlist update" hook call is pending. If called from - * within the hook, the current hook call is not considered pending. */ -AUD_FUNC0 (bool_t, playlist_update_pending) +/* Returns true if a "playlist update" hook call is pending for the given + * playlist (or for any playlist, if <playlist> is -1). If called from within + * the hook, the current hook call is not considered pending. */ +bool aud_playlist_update_pending (int playlist = -1); /* May be called within the "playlist update" hook to determine the update level - * and number of entries changed in a playlist. Returns the update level for - * the playlist, storing the number of the first entry changed in <at> and the - * number of contiguous entries to be updated in <count>. Note that entries may - * have been added or removed within this range. If no entries in the playlist - * have changed, returns zero. */ -AUD_FUNC3 (int, playlist_updated_range, int, playlist, int *, at, int *, count) - -/* Returns nonzero if entries are being added to a playlist in the background. + * and number of entries changed in a playlist. */ +Playlist::Update aud_playlist_update_detail (int playlist); + +/* Returns true if entries are being added to a playlist in the background. * If <playlist> is -1, checks all playlists. */ -AUD_FUNC1 (bool_t, playlist_add_in_progress, int, playlist) +bool aud_playlist_add_in_progress (int playlist); -/* Returns nonzero if entries in a playlist are being scanned for metadata in +/* Returns true if entries in a playlist are being scanned for metadata in * the background. If <playlist> is -1, checks all playlists. */ -AUD_FUNC1 (bool_t, playlist_scan_in_progress, int, playlist) +bool aud_playlist_scan_in_progress (int playlist); /* --- PLAYLIST UTILITY API --- */ /* Sorts the entries in a playlist according to one of the schemes listed in * playlist.h. */ -AUD_VFUNC2 (playlist_sort_by_scheme, int, playlist, int, scheme) +void aud_playlist_sort_by_scheme (int playlist, Playlist::SortType scheme); /* Sorts only the selected entries in a playlist according to one of those * schemes. */ -AUD_VFUNC2 (playlist_sort_selected_by_scheme, int, playlist, int, scheme) +void aud_playlist_sort_selected_by_scheme (int playlist, Playlist::SortType scheme); /* Removes duplicate entries in a playlist according to one of those schemes. * As currently implemented, first sorts the playlist. */ -AUD_VFUNC2 (playlist_remove_duplicates_by_scheme, int, playlist, int, - scheme) +void aud_playlist_remove_duplicates_by_scheme (int playlist, Playlist::SortType scheme); /* Removes all entries referring to unavailable files in a playlist. ("Remove * failed" is something of a misnomer for the current behavior.) As currently * implemented, only works for file:// URIs. */ -AUD_VFUNC1 (playlist_remove_failed, int, playlist) +void aud_playlist_remove_failed (int playlist); /* Selects all the entries in a playlist that match regular expressions stored * in the fields of a tuple. Does not free the memory used by the tuple. * Example: To select all the songs whose title starts with the letter "A", * create a blank tuple and set its title field to "^A". */ -AUD_VFUNC2 (playlist_select_by_patterns, int, playlist, const Tuple *, - patterns) +void aud_playlist_select_by_patterns (int playlist, const Tuple & patterns); -/* Returns nonzero if <filename> refers to a playlist file. */ -AUD_FUNC1 (bool_t, filename_is_playlist, const char *, filename) +/* Returns true if <filename> refers to a playlist file. */ +bool aud_filename_is_playlist (const char * filename); /* Saves the entries in a playlist to a playlist file. The format of the file - * is determined from the file extension. Returns nonzero on success. */ -AUD_FUNC2 (bool_t, playlist_save, int, playlist, const char *, filename) - -/* added in Audacious 3.4 */ - -/* Reverses the order of the selected entries in a playlist. */ -AUD_VFUNC1 (playlist_reverse_selected, int, playlist) + * is determined from the file extension. Returns true on success. */ +bool aud_playlist_save (int playlist, const char * filename, Playlist::GetMode mode); -/* Reorders the selected entries in a playlist randomly. */ -AUD_VFUNC1 (playlist_randomize_selected, int, playlist) - -/* Sets the entry which has keyboard focus (-1 means no entry). */ -AUD_VFUNC2 (playlist_set_focus, int, playlist_num, int, entry_num) - -/* Gets the entry which has keyboard focus (-1 means no entry). */ -AUD_FUNC1 (int, playlist_get_focus, int, playlist_num) +#endif diff --git a/src/libaudcore/plugin-init.cc b/src/libaudcore/plugin-init.cc new file mode 100644 index 0000000..4e85f25 --- /dev/null +++ b/src/libaudcore/plugin-init.cc @@ -0,0 +1,351 @@ +/* + * plugin-init.c + * Copyright 2010-2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "plugins-internal.h" + +#include <assert.h> +#include <stdlib.h> + +#include "hook.h" +#include "interface.h" +#include "internal.h" +#include "output.h" +#include "plugin.h" +#include "runtime.h" + +static bool general_plugin_start (PluginHandle * plugin) +{ + auto gp = (GeneralPlugin *) aud_plugin_get_header (plugin); + return gp && gp->init (); +} + +void general_plugin_stop (PluginHandle * plugin) +{ + GeneralPlugin * gp = (GeneralPlugin *) aud_plugin_get_header (plugin); + if (gp) + gp->cleanup (); +} + +struct MultiFuncs +{ + bool (* start) (PluginHandle * plugin); + void (* stop) (PluginHandle * plugin); +}; + +struct SingleFuncs +{ + PluginHandle * (* get_current) (); + bool (* set_current) (PluginHandle * plugin); +}; + +union PluginFuncs +{ + MultiFuncs m; + SingleFuncs s; + + constexpr PluginFuncs (MultiFuncs multi) : m (multi) {} + constexpr PluginFuncs (SingleFuncs single) : s (single) {} +}; + +struct PluginParams +{ + const char * name; + bool is_single; + PluginFuncs f; + + constexpr PluginParams (const char * name, MultiFuncs multi) : + name (name), is_single (false), f (multi) {} + constexpr PluginParams (const char * name, SingleFuncs single) : + name (name), is_single (true), f (single) {} +}; + +static constexpr aud::array<PluginType, PluginParams> table = { + PluginParams ("transport", MultiFuncs ({nullptr, nullptr})), + PluginParams ("playlist", MultiFuncs ({nullptr, nullptr})), + PluginParams ("input", MultiFuncs ({nullptr, nullptr})), + PluginParams ("effect", MultiFuncs ({effect_plugin_start, effect_plugin_stop})), + PluginParams ("output", SingleFuncs ({output_plugin_get_current, output_plugin_set_current})), + PluginParams ("visualization", MultiFuncs ({vis_plugin_start, vis_plugin_stop})), + PluginParams ("general", MultiFuncs ({general_plugin_start, general_plugin_stop})), + PluginParams ("interface", SingleFuncs ({iface_plugin_get_current, iface_plugin_set_current})) +}; + +static void start_single (PluginType type) +{ + PluginHandle * skip = nullptr; + + for (PluginHandle * p : aud_plugin_list (type)) + { + if (! aud_plugin_get_enabled (p)) + continue; + + AUDINFO ("Starting selected %s plugin %s.\n", table[type].name, + aud_plugin_get_name (p)); + + if (table[type].f.s.set_current (p)) + return; + + AUDWARN ("%s failed to start.\n", aud_plugin_get_name (p)); + plugin_set_failed (p); + plugin_set_enabled (p, false); + skip = p; + break; + } + + AUDINFO ("Probing for %s plugin.\n", table[type].name); + + for (PluginHandle * p : aud_plugin_list (type)) + { + if (p == skip) + continue; + + AUDINFO ("Trying to start %s.\n", aud_plugin_get_name (p)); + + if (! table[type].f.s.set_current (p)) + { + AUDWARN ("%s failed to start.\n", aud_plugin_get_name (p)); + plugin_set_failed (p); + continue; + } + + plugin_set_enabled (p, true); + return; + } + + AUDERR ("No %s plugin found.\n" + "(Did you forget to install audacious-plugins?)\n", table[type].name); + abort (); +} + +static void start_multi (PluginType type) +{ + for (PluginHandle * p : aud_plugin_list (type)) + { + if (! aud_plugin_get_enabled (p)) + continue; + + AUDINFO ("Starting %s.\n", aud_plugin_get_name (p)); + + if (! table[type].f.m.start (p)) + { + AUDWARN ("%s failed to start.\n", aud_plugin_get_name (p)); + plugin_set_failed (p); + } + } +} + +static void start_plugins (PluginType type) +{ + /* no interface plugin in headless mode */ + if (type == PluginType::Iface && aud_get_headless_mode ()) + return; + + if (table[type].is_single) + start_single (type); + else if (table[type].f.m.start) + start_multi (type); +} + +void start_plugins_one () +{ + plugin_system_init (); + + start_plugins (PluginType::Transport); + start_plugins (PluginType::Playlist); + start_plugins (PluginType::Input); + start_plugins (PluginType::Effect); + start_plugins (PluginType::Output); +} + +void start_plugins_two () +{ + start_plugins (PluginType::Vis); + start_plugins (PluginType::General); + start_plugins (PluginType::Iface); +} + +static void stop_plugins (PluginType type) +{ + if (table[type].is_single) + { + AUDINFO ("Shutting down %s.\n", aud_plugin_get_name + (table[type].f.s.get_current ())); + table[type].f.s.set_current (nullptr); + } + else if (table[type].f.m.stop) + { + for (PluginHandle * p : aud_plugin_list (type)) + { + if (aud_plugin_get_enabled (p)) + { + AUDINFO ("Shutting down %s.\n", aud_plugin_get_name (p)); + table[type].f.m.stop (p); + } + } + } +} + +void stop_plugins_two () +{ + /* interface plugin is already shut down */ + stop_plugins (PluginType::General); + stop_plugins (PluginType::Vis); +} + +void stop_plugins_one () +{ + stop_plugins (PluginType::Output); + stop_plugins (PluginType::Effect); + stop_plugins (PluginType::Input); + stop_plugins (PluginType::Playlist); + stop_plugins (PluginType::Transport); + + plugin_system_cleanup (); +} + +EXPORT PluginHandle * aud_plugin_get_current (PluginType type) +{ + assert (table[type].is_single); + return table[type].f.s.get_current (); +} + +static bool enable_single (PluginType type, PluginHandle * p) +{ + PluginHandle * old = table[type].f.s.get_current (); + + AUDINFO ("Switching from %s to %s.\n", aud_plugin_get_name (old), + aud_plugin_get_name (p)); + + plugin_set_enabled (old, false); + plugin_set_enabled (p, true); + + if (table[type].f.s.set_current (p)) + { + // check that the switch was not queued for later + if (table[type].f.s.get_current () == old) + { + plugin_set_enabled (p, false); + plugin_set_enabled (old, true); + } + + return true; + } + + AUDERR ("%s failed to start; falling back to %s.\n", + aud_plugin_get_name (p), aud_plugin_get_name (old)); + + plugin_set_failed (p); + plugin_set_enabled (p, false); + plugin_set_enabled (old, true); + + if (table[type].f.s.set_current (old)) + return false; + + AUDERR ("%s failed to start.\n", aud_plugin_get_name (old)); + abort (); +} + +static bool enable_multi (PluginType type, PluginHandle * p, bool enable) +{ + AUDINFO ("%sabling %s.\n", enable ? "En" : "Dis", aud_plugin_get_name (p)); + + if (enable) + { + plugin_set_enabled (p, true); + + if (table[type].f.m.start && ! table[type].f.m.start (p)) + { + AUDERR ("%s failed to start.\n", aud_plugin_get_name (p)); + plugin_set_failed (p); + plugin_set_enabled (p, false); + return false; + } + + if (type == PluginType::Vis || type == PluginType::General) + hook_call ("dock plugin enabled", p); + } + else + { + plugin_set_enabled (p, false); + + if (type == PluginType::Vis || type == PluginType::General) + hook_call ("dock plugin disabled", p); + + if (table[type].f.m.stop) + table[type].f.m.stop (p); + } + + return true; +} + +EXPORT bool aud_plugin_enable (PluginHandle * plugin, bool enable) +{ + if (! enable == ! aud_plugin_get_enabled (plugin)) + return true; + + PluginType type = aud_plugin_get_type (plugin); + + if (table[type].is_single) + { + assert (enable); + return enable_single (type, plugin); + } + + return enable_multi (type, plugin, enable); +} + +/* Miscellaneous plugin-related functions ... */ + +EXPORT int aud_plugin_send_message (PluginHandle * plugin, const char * code, const void * data, int size) +{ + if (! aud_plugin_get_enabled (plugin)) + return -1; + + Plugin * header = (Plugin *) aud_plugin_get_header (plugin); + if (! header) + return -1; + + return header->take_message (code, data, size); +} + +EXPORT void * aud_plugin_get_gtk_widget (PluginHandle * plugin) +{ + if (! aud_plugin_get_enabled (plugin)) + return nullptr; + + PluginType type = aud_plugin_get_type (plugin); + if (type != PluginType::General && type != PluginType::Vis) + return nullptr; + + auto dp = (DockablePlugin *) aud_plugin_get_header (plugin); + return dp ? dp->get_gtk_widget () : nullptr; +} + +EXPORT void * aud_plugin_get_qt_widget (PluginHandle * plugin) +{ + if (! aud_plugin_get_enabled (plugin)) + return nullptr; + + PluginType type = aud_plugin_get_type (plugin); + if (type != PluginType::General && type != PluginType::Vis) + return nullptr; + + auto dp = (DockablePlugin *) aud_plugin_get_header (plugin); + return dp ? dp->get_qt_widget () : nullptr; +} diff --git a/src/libaudcore/plugin-load.cc b/src/libaudcore/plugin-load.cc new file mode 100644 index 0000000..513cc6d --- /dev/null +++ b/src/libaudcore/plugin-load.cc @@ -0,0 +1,163 @@ +/* + * plugin-load.cc + * Copyright 2007-2013 William Pitcock and John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "plugins-internal.h" + +#include <assert.h> +#include <errno.h> +#include <pthread.h> +#include <string.h> +#include <sys/stat.h> + +#include <glib/gstdio.h> +#include <gmodule.h> + +#include "audstrings.h" +#include "internal.h" +#include "plugin.h" +#include "runtime.h" + +static const char * plugin_dir_list[] = { + "Transport", + "Container", + "Input", + "Output", + "Effect", + "General", + "Visualization" +}; + +struct LoadedModule { + Plugin * header; + GModule * module; +}; + +static Index<LoadedModule> loaded_modules; + +Plugin * plugin_load (const char * filename) +{ + AUDINFO ("Loading plugin: %s.\n", filename); + + GModule * module = g_module_open (filename, G_MODULE_BIND_LOCAL); + + if (! module) + { + AUDERR ("%s could not be loaded: %s\n", filename, g_module_error ()); + return nullptr; + } + + Plugin * header; + if (! g_module_symbol (module, "aud_plugin_instance", (void * *) & header)) + header = nullptr; + + if (! header || header->magic != _AUD_PLUGIN_MAGIC) + { + AUDERR ("%s is not a valid Audacious plugin.\n", filename); + g_module_close (module); + return nullptr; + } + + if (header->version < _AUD_PLUGIN_VERSION_MIN || + header->version > _AUD_PLUGIN_VERSION) + { + AUDERR ("%s is not compatible with this version of Audacious.\n", filename); + g_module_close (module); + return nullptr; + } + + if (header->type == PluginType::Transport || + header->type == PluginType::Playlist || + header->type == PluginType::Input || + header->type == PluginType::Effect) + { + if (! header->init ()) + { + AUDERR ("%s failed to initialize.\n", filename); + g_module_close (module); + return nullptr; + } + } + + loaded_modules.append (header, module); + + return header; +} + +static void plugin_unload (LoadedModule & loaded) +{ + if (loaded.header->type == PluginType::Transport || + loaded.header->type == PluginType::Playlist || + loaded.header->type == PluginType::Input || + loaded.header->type == PluginType::Effect) + { + loaded.header->cleanup (); + } + +#ifndef VALGRIND_FRIENDLY + g_module_close (loaded.module); +#endif +} + +/******************************************************************/ + +static bool scan_plugin_func (const char * path, const char * basename, void * data) +{ + if (! str_has_suffix_nocase (basename, PLUGIN_SUFFIX)) + return false; + + GStatBuf st; + if (g_stat (path, & st) < 0) + { + AUDERR ("Unable to stat %s: %s\n", path, strerror (errno)); + return false; + } + + if (S_ISREG (st.st_mode)) + plugin_register (path, st.st_mtime); + + return false; +} + +static void scan_plugins (const char * path) +{ + dir_foreach (path, scan_plugin_func, nullptr); +} + +void plugin_system_init () +{ + assert (g_module_supported ()); + + plugin_registry_load (); + + const char * path = aud_get_path (AudPath::PluginDir); + for (const char * dir : plugin_dir_list) + scan_plugins (filename_build ({path, dir})); + + plugin_registry_prune (); +} + +void plugin_system_cleanup () +{ + plugin_registry_save (); + + for (LoadedModule & loaded : loaded_modules) + plugin_unload (loaded); + + loaded_modules.clear (); +} diff --git a/src/libaudcore/plugin-registry.cc b/src/libaudcore/plugin-registry.cc new file mode 100644 index 0000000..eb1b1c4 --- /dev/null +++ b/src/libaudcore/plugin-registry.cc @@ -0,0 +1,705 @@ +/* + * plugin-registry.c + * Copyright 2009-2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "plugins-internal.h" + +#include <errno.h> +#include <pthread.h> +#include <string.h> + +#include <glib/gstdio.h> + +#include "audstrings.h" +#include "i18n.h" +#include "interface.h" +#include "plugin.h" +#include "runtime.h" + +#define FILENAME "plugin-registry" + +/* Increment this when the format of the plugin-registry file changes. + * Add 10 if the format changes in a way that will break parse_plugins_fallback(). */ +#define FORMAT 9 + +/* Oldest file format supported by parse_plugins_fallback() */ +#define MIN_FORMAT 2 // "enabled" flag was added in Audacious 2.4 + +struct PluginWatch { + PluginWatchFunc func; + void * data; +}; + +class PluginHandle +{ +public: + String basename, path; + bool loaded; + int timestamp; + PluginType type; + Plugin * header; + String name, domain; + int priority; + int has_about, has_configure, enabled; + Index<PluginWatch> watches; + + /* for transport plugins */ + Index<String> schemes; + + /* for playlist plugins */ + Index<String> exts; + + /* for input plugins */ + aud::array<InputKey, Index<String>> keys; + int has_subtunes, writes_tag; + + PluginHandle (const char * basename, const char * path, bool loaded, + int timestamp, PluginType type, Plugin * header) : + basename (basename), + path (path), + loaded (loaded), + timestamp (timestamp), + type (type), + header (header), + priority (0), + has_about (false), + has_configure (false), + enabled (type == PluginType::Transport || + type == PluginType::Playlist || type == PluginType::Input), + has_subtunes (false), + writes_tag (false) {} + + ~PluginHandle () + { + if (watches.len ()) + AUDWARN ("Plugin watch count not zero at exit!\n"); + } +}; + +static constexpr aud::array<PluginType, const char *> plugin_type_names = { + "transport", + "playlist", + "input", + "effect", + "output", + "vis", + "general", + "iface" +}; + +static constexpr aud::array<InputKey, const char *> input_key_names = { + "scheme", + "ext", + "mime" +}; + +static aud::array<PluginType, Index<PluginHandle *>> plugins; +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +static StringBuf get_basename (const char * path) +{ + const char * slash = strrchr (path, G_DIR_SEPARATOR); + const char * dot = slash ? strrchr (slash + 1, '.') : nullptr; + + return dot ? str_copy (slash + 1, dot - (slash + 1)) : StringBuf (); +} + +static FILE * open_registry_file (const char * mode) +{ + StringBuf path = filename_build ({aud_get_path (AudPath::UserDir), FILENAME}); + FILE * handle = g_fopen (path, mode); + + if (! handle && errno != ENOENT) + AUDWARN ("%s: %s\n", (const char *) path, strerror (errno)); + + return handle; +} + +static void transport_plugin_save (PluginHandle * plugin, FILE * handle) +{ + for (const String & scheme : plugin->schemes) + fprintf (handle, "scheme %s\n", (const char *) scheme); +} + +static void playlist_plugin_save (PluginHandle * plugin, FILE * handle) +{ + for (const String & ext : plugin->exts) + fprintf (handle, "ext %s\n", (const char *) ext); +} + +static void input_plugin_save (PluginHandle * plugin, FILE * handle) +{ + for (auto k : aud::range<InputKey> ()) + { + for (const String & key : plugin->keys[k]) + fprintf (handle, "%s %s\n", input_key_names[k], (const char *) key); + } + + fprintf (handle, "subtunes %d\n", plugin->has_subtunes); + fprintf (handle, "writes %d\n", plugin->writes_tag); +} + +static void plugin_save (PluginHandle * plugin, FILE * handle) +{ + fprintf (handle, "%s %s\n", plugin_type_names[plugin->type], (const char *) plugin->path); + fprintf (handle, "stamp %d\n", plugin->timestamp); + fprintf (handle, "name %s\n", (const char *) plugin->name); + + if (plugin->domain) + fprintf (handle, "domain %s\n", (const char *) plugin->domain); + + fprintf (handle, "priority %d\n", plugin->priority); + fprintf (handle, "about %d\n", plugin->has_about); + fprintf (handle, "config %d\n", plugin->has_configure); + fprintf (handle, "enabled %d\n", plugin->enabled); + + if (plugin->type == PluginType::Transport) + transport_plugin_save (plugin, handle); + else if (plugin->type == PluginType::Playlist) + playlist_plugin_save (plugin, handle); + else if (plugin->type == PluginType::Input) + input_plugin_save (plugin, handle); +} + +void plugin_registry_save () +{ + FILE * handle = open_registry_file ("w"); + if (! handle) + return; + + fprintf (handle, "format %d\n", FORMAT); + + for (auto & list : plugins) + { + for (PluginHandle * plugin : list) + { + plugin_save (plugin, handle); + delete plugin; + } + + list.clear (); + } + + fclose (handle); +} + +static char parse_key[512]; +static char * parse_value; + +static void parse_next (FILE * handle) +{ + parse_value = nullptr; + + if (! fgets (parse_key, sizeof parse_key, handle)) + return; + + char * space = strchr (parse_key, ' '); + if (! space) + return; + + * space = 0; + parse_value = space + 1; + + char * newline = strchr (parse_value, '\n'); + if (newline) + * newline = 0; +} + +static bool parse_integer (const char * key, int * value) +{ + return (parse_value && ! strcmp (parse_key, key) && sscanf (parse_value, "%d", value) == 1); +} + +static String parse_string (const char * key) +{ + return (parse_value && ! strcmp (parse_key, key)) ? String (parse_value) : String (); +} + +static void transport_plugin_parse (PluginHandle * plugin, FILE * handle) +{ + while (1) + { + String value = parse_string ("scheme"); + if (! value) + break; + + plugin->schemes.append (std::move (value)); + parse_next (handle); + } +} + +static void playlist_plugin_parse (PluginHandle * plugin, FILE * handle) +{ + while (1) + { + String value = parse_string ("ext"); + if (! value) + break; + + plugin->exts.append (std::move (value)); + parse_next (handle); + } +} + +static void input_plugin_parse (PluginHandle * plugin, FILE * handle) +{ + for (auto key : aud::range<InputKey> ()) + { + while (1) + { + String value = parse_string (input_key_names[key]); + if (! value) + break; + + plugin->keys[key].append (std::move (value)); + parse_next (handle); + } + } + + if (parse_integer ("subtunes", & plugin->has_subtunes)) + parse_next (handle); + if (parse_integer ("writes", & plugin->writes_tag)) + parse_next (handle); +} + +static bool plugin_parse (FILE * handle) +{ + PluginType type; + String path; + + for (auto type2 : aud::range<PluginType> ()) + { + type = type2; + if ((path = parse_string (plugin_type_names[type2]))) + break; + } + + if (! path) + return false; + + StringBuf basename = get_basename (path); + if (! basename) + return false; + + parse_next (handle); + + int timestamp; + if (! parse_integer ("stamp", & timestamp)) + return false; + + auto plugin = new PluginHandle (basename, String (), false, timestamp, type, nullptr); + plugins[type].append (plugin); + + parse_next (handle); + + plugin->name = parse_string ("name"); + if (plugin->name) + parse_next (handle); + + plugin->domain = parse_string ("domain"); + if (plugin->domain) + parse_next (handle); + + if (parse_integer ("priority", & plugin->priority)) + parse_next (handle); + if (parse_integer ("about", & plugin->has_about)) + parse_next (handle); + if (parse_integer ("config", & plugin->has_configure)) + parse_next (handle); + if (parse_integer ("enabled", & plugin->enabled)) + parse_next (handle); + + if (type == PluginType::Transport) + transport_plugin_parse (plugin, handle); + else if (type == PluginType::Playlist) + playlist_plugin_parse (plugin, handle); + else if (type == PluginType::Input) + input_plugin_parse (plugin, handle); + + return true; +} + +/* try to migrate enabled status from another version */ +static void parse_plugins_fallback (FILE * handle) +{ + for (; parse_value; parse_next (handle)) + { + PluginType type; + String path; + int enabled; + + for (auto type2 : aud::range<PluginType> ()) + { + type = type2; + if ((path = parse_string (plugin_type_names[type2]))) + break; + } + + if (! path) + continue; + + StringBuf basename = get_basename (path); + if (! basename) + continue; + + parse_next (handle); + + for (; parse_value; parse_next (handle)) + { + if (parse_integer ("enabled", & enabled)) + break; + } + + if (! parse_value) + return; + + // setting timestamp to zero forces a rescan + auto plugin = new PluginHandle (basename, String (), false, 0, type, nullptr); + plugins[type].append (plugin); + plugin->enabled = enabled; + } +} + +void plugin_registry_load () +{ + FILE * handle = open_registry_file ("r"); + if (! handle) + return; + + parse_next (handle); + + int format; + if (! parse_integer ("format", & format)) + goto ERR; + + parse_next (handle); + + if (format == FORMAT) + { + while (plugin_parse (handle)) + continue; + } + else if (format >= MIN_FORMAT && format < FORMAT + 10) + parse_plugins_fallback (handle); + +ERR: + fclose (handle); +} + +static int plugin_compare (PluginHandle * const & a, PluginHandle * const & b, void *) +{ + if (a->type < b->type) + return -1; + if (a->type > b->type) + return 1; + if (a->priority < b->priority) + return -1; + if (a->priority > b->priority) + return 1; + + int diff; + if ((diff = str_compare (dgettext (a->domain, a->name), dgettext (b->domain, b->name)))) + return diff; + + return str_compare (a->path, b->path); +} + +void plugin_registry_prune () +{ + auto check_not_found = [] (PluginHandle * plugin) + { + if (plugin->path) + return false; + + AUDINFO ("Plugin not found: %s\n", (const char *) plugin->basename); + delete plugin; + return true; + }; + + for (auto & list : plugins) + { + list.remove_if (check_not_found); + list.sort (plugin_compare, nullptr); + } +} + +/* Note: If there are multiple plugins with the same basename, this returns only + * one of them. Different plugins should be given different basenames. */ +EXPORT PluginHandle * aud_plugin_lookup_basename (const char * basename) +{ + for (auto & list : plugins) + { + for (PluginHandle * plugin : list) + { + if (! strcmp (plugin->basename, basename)) + return plugin; + } + } + + return nullptr; +} + +static void plugin_get_info (PluginHandle * plugin, bool is_new) +{ + Plugin * header = plugin->header; + + plugin->name = String (header->info.name); + plugin->domain = String (header->info.domain); + plugin->has_about = (bool) header->info.about; + plugin->has_configure = (bool) header->info.prefs; + + if (header->type == PluginType::Transport) + { + TransportPlugin * tp = (TransportPlugin *) header; + + plugin->schemes.clear (); + for (const char * scheme : tp->schemes) + plugin->schemes.append (String (scheme)); + } + else if (header->type == PluginType::Playlist) + { + PlaylistPlugin * pp = (PlaylistPlugin *) header; + + plugin->exts.clear (); + for (const char * ext : pp->extensions) + plugin->exts.append (String (ext)); + } + else if (header->type == PluginType::Input) + { + InputPlugin * ip = (InputPlugin *) header; + plugin->priority = ip->input_info.priority; + + for (auto k : aud::range<InputKey> ()) + { + plugin->keys[k].clear (); + for (auto key = ip->input_info.keys[k]; key && * key; key ++) + plugin->keys[k].append (String (* key)); + } + + plugin->has_subtunes = (ip->input_info.flags & InputPlugin::FlagSubtunes); + plugin->writes_tag = (ip->input_info.flags & InputPlugin::FlagWritesTag); + } + else if (header->type == PluginType::Output) + { + OutputPlugin * op = (OutputPlugin *) header; + plugin->priority = 10 - op->priority; + } + else if (header->type == PluginType::Effect) + { + EffectPlugin * ep = (EffectPlugin *) header; + plugin->priority = ep->order; + } + else if (header->type == PluginType::General) + { + GeneralPlugin * gp = (GeneralPlugin *) header; + if (is_new) + plugin->enabled = gp->enabled_by_default; + } +} + +void plugin_register (const char * path, int timestamp) +{ + StringBuf basename = get_basename (path); + if (! basename) + return; + + PluginHandle * plugin = aud_plugin_lookup_basename (basename); + + if (plugin) + { + AUDINFO ("Register plugin: %s\n", path); + plugin->path = String (path); + + if (plugin->timestamp != timestamp) + { + AUDINFO ("Rescan plugin: %s\n", path); + Plugin * header = plugin_load (path); + if (! header || header->type != plugin->type) + return; + + plugin->loaded = true; + plugin->header = header; + plugin->timestamp = timestamp; + + plugin_get_info (plugin, false); + } + } + else + { + AUDINFO ("New plugin: %s\n", path); + Plugin * header = plugin_load (path); + if (! header) + return; + + plugin = new PluginHandle (basename, path, true, timestamp, header->type, header); + plugins[plugin->type].append (plugin); + + plugin_get_info (plugin, true); + } +} + +EXPORT PluginType aud_plugin_get_type (PluginHandle * plugin) +{ + return plugin->type; +} + +EXPORT const char * aud_plugin_get_basename (PluginHandle * plugin) +{ + return plugin->basename; +} + +EXPORT const void * aud_plugin_get_header (PluginHandle * plugin) +{ + pthread_mutex_lock (& mutex); + + if (! plugin->loaded) + { + Plugin * header = plugin_load (plugin->path); + if (! header || header->type != plugin->type) + goto DONE; + + plugin->loaded = true; + plugin->header = header; + } + +DONE: + pthread_mutex_unlock (& mutex); + return plugin->header; +} + +EXPORT PluginHandle * aud_plugin_by_header (const void * header) +{ + for (auto & list : plugins) + { + for (PluginHandle * plugin : list) + { + if (plugin->header == header) + return plugin; + } + } + + return nullptr; +} + +EXPORT const Index<PluginHandle *> & aud_plugin_list (PluginType type) +{ + return plugins[type]; +} + +EXPORT const char * aud_plugin_get_name (PluginHandle * plugin) +{ + return dgettext (plugin->domain, plugin->name); +} + +EXPORT bool aud_plugin_has_about (PluginHandle * plugin) +{ + return plugin->has_about; +} + +EXPORT bool aud_plugin_has_configure (PluginHandle * plugin) +{ + return plugin->has_configure; +} + +EXPORT bool aud_plugin_get_enabled (PluginHandle * plugin) +{ + return plugin->enabled; +} + +static void plugin_call_watches (PluginHandle * plugin) +{ + auto call_and_check_remove = [=] (const PluginWatch & watch) + { return ! watch.func (plugin, watch.data); }; + + plugin->watches.remove_if (call_and_check_remove); +} + +void plugin_set_enabled (PluginHandle * plugin, bool enabled) +{ + plugin->enabled = enabled; + plugin_call_watches (plugin); +} + +void plugin_set_failed (PluginHandle * plugin) +{ + plugin->header = nullptr; +} + +EXPORT void aud_plugin_add_watch (PluginHandle * plugin, PluginWatchFunc func, void * data) +{ + plugin->watches.append (func, data); +} + +EXPORT void aud_plugin_remove_watch (PluginHandle * plugin, PluginWatchFunc func, void * data) +{ + auto is_match = [=] (const PluginWatch & watch) + { return watch.func == func && watch.data == data; }; + + plugin->watches.remove_if (is_match); +} + +bool transport_plugin_has_scheme (PluginHandle * plugin, const char * scheme) +{ + g_return_val_if_fail (plugin->type == PluginType::Transport, false); + + for (String & s : plugin->schemes) + { + if (! strcmp (s, scheme)) + return true; + } + + return false; +} + +bool playlist_plugin_has_ext (PluginHandle * plugin, const char * ext) +{ + g_return_val_if_fail (plugin->type == PluginType::Playlist, false); + + for (String & e : plugin->exts) + { + if (! strcmp_nocase (e, ext)) + return true; + } + + return false; +} + +bool input_plugin_has_key (PluginHandle * plugin, InputKey key, const char * value) +{ + g_return_val_if_fail (plugin->type == PluginType::Input, false); + + for (String & s : plugin->keys[key]) + { + if (! strcmp_nocase (s, value)) + return true; + } + + return false; +} + +bool input_plugin_has_subtunes (PluginHandle * plugin) +{ + g_return_val_if_fail (plugin->type == PluginType::Input, false); + return plugin->has_subtunes; +} + +bool input_plugin_can_write_tuple (PluginHandle * plugin) +{ + g_return_val_if_fail (plugin->type == PluginType::Input, false); + return plugin->writes_tag; +} diff --git a/src/libaudcore/plugin.h b/src/libaudcore/plugin.h new file mode 100644 index 0000000..8cd207b --- /dev/null +++ b/src/libaudcore/plugin.h @@ -0,0 +1,473 @@ +/* + * plugin.h + * Copyright 2005-2013 William Pitcock, Yoshiki Yazawa, Eugene Zagidullin, and + * John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_PLUGIN_H +#define LIBAUDCORE_PLUGIN_H + +#include <libaudcore/audio.h> +#include <libaudcore/plugins.h> +#include <libaudcore/tuple.h> +#include <libaudcore/visualizer.h> +#include <libaudcore/vfs.h> + +enum class AudMenuID; +struct PluginPreferences; + +/* "Magic" bytes identifying an Audacious plugin header. */ +#define _AUD_PLUGIN_MAGIC ((int) 0x8EAC8DE2) + +/* API version. Plugins are marked with this number at compile time. + * + * _AUD_PLUGIN_VERSION is the current version; _AUD_PLUGIN_VERSION_MIN is + * the oldest one we are backward compatible with. Plugins marked older than + * _AUD_PLUGIN_VERSION_MIN or newer than _AUD_PLUGIN_VERSION are not loaded. + * + * Before releases that add new pointers to the end of the API tables, increment + * _AUD_PLUGIN_VERSION but leave _AUD_PLUGIN_VERSION_MIN the same. + * + * Before releases that break backward compatibility (e.g. remove pointers from + * the API tables), increment _AUD_PLUGIN_VERSION *and* set + * _AUD_PLUGIN_VERSION_MIN to the same value. */ + +#define _AUD_PLUGIN_VERSION_MIN 46 /* 3.6-devel */ +#define _AUD_PLUGIN_VERSION 46 /* 3.6-devel */ + +/* A NOTE ON THREADS + * + * How thread-safe a plugin must be depends on the type of plugin. Note that + * some parts of the Audacious API are *not* thread-safe and therefore cannot be + * used in some parts of some plugins; for example, input plugins cannot use + * GUI-related calls or access the playlist except in about() and configure(). + * + * Thread-safe plugins: transport, playlist, input, effect, and output. These + * must be mostly thread-safe. init() and cleanup() may be called from + * secondary threads; however, no other functions provided by the plugin will be + * called at the same time. about() and configure() will be called only from + * the main thread. All other functions provided by the plugin may be called + * from any thread and from multiple threads simultaneously. + * + * Exceptions: + * - Because many existing input plugins are not coded to handle simultaneous + * calls to play(), play() will only be called from one thread at a time. New + * plugins should not rely on this exception, though. + * - Some combinations of calls, especially for output and effect plugins, make + * no sense; for example, flush() in an output plugin will only be called + * after open_audio() and before close_audio(). + * + * Single-thread plugins: visualization, general, and interface. Functions + * provided by these plugins will only be called from the main thread. */ + +/* CROSS-PLUGIN MESSAGES + * + * Since 3.2, Audacious implements a basic messaging system between plugins. + * Messages are sent using aud_plugin_send_message() and received through the + * take_message() method specified in the header of the receiving plugin. + * Plugins that do not need to receive messages can set take_message() to nullptr. + * + * Each message includes a code indicating the type of message, a pointer to + * some data, and a value indicating the size of that data. What the message + * data contains is entirely up to the two plugins involved. For this reason, it + * is crucial that both plugins agree on the meaning of the message codes used. + * + * Once the message is sent, an integer error code is returned. If the receiving + * plugin does not provide the take_message() method, -1 is returned. If + * take_message() does not recognize the message code, it should ignore the + * message and return -1. An error code of zero represents success. Other error + * codes may be used with more specific meanings. + * + * For the time being, aud_plugin_send_message() should only be called from the + * program's main thread. */ + +struct PluginInfo { + const char * name; + const char * domain; // for gettext + const char * about; + const PluginPreferences * prefs; +}; + +class Plugin +{ +public: + constexpr Plugin (PluginType type, PluginInfo info) : + type (type), + info (info) {} + + const int magic = _AUD_PLUGIN_MAGIC; + const int version = _AUD_PLUGIN_VERSION; + + const PluginType type; + const PluginInfo info; + + virtual bool init () { return true; } + virtual void cleanup () {} + + virtual int take_message (const char * code, const void * data, int size) { return -1; } +}; + +class TransportPlugin : public Plugin +{ +public: + constexpr TransportPlugin (const PluginInfo info, + const ArrayRef<const char *> schemes) : + Plugin (PluginType::Transport, info), + schemes (schemes) {} + + /* supported URI schemes (without "://") */ + const ArrayRef<const char *> schemes; + + /* fopen() implementation */ + virtual VFSImpl * fopen (const char * filename, const char * mode, String & error) = 0; +}; + +class PlaylistPlugin : public Plugin +{ +public: + constexpr PlaylistPlugin (const PluginInfo info, + const ArrayRef<const char *> extensions, bool can_save) : + Plugin (PluginType::Playlist, info), + extensions (extensions), + can_save (can_save) {} + + /* supported file extensions (without periods) */ + const ArrayRef<const char *> extensions; + + /* true if the plugin can save playlists */ + const bool can_save; + + /* path: URI of playlist file (in) + * file: VFS handle of playlist file (in, read-only file, not seekable) + * title: title of playlist (out) + * items: playlist entries (out) */ + virtual bool load (const char * path, VFSFile & file, String & title, + Index<PlaylistAddItem> & items) = 0; + + /* path: URI of playlist file (in) + * file: VFS handle of playlist file (in, write-only file, not seekable) + * title: title of playlist (in) + * items: playlist entries (in) */ + virtual bool save (const char * path, VFSFile & file, const char * title, + const Index<PlaylistAddItem> & items) { return false; } +}; + +class OutputPlugin : public Plugin +{ +public: + constexpr OutputPlugin (const PluginInfo info, int priority, bool force_reopen = false) : + Plugin (PluginType::Output, info), + priority (priority), + force_reopen (force_reopen) {} + + /* During probing, plugins with higher priority (10 to 0) are tried first. */ + const int priority; + + /* Whether close_audio() and open_audio() must always be called between + * songs, even if the audio format is the same. Note that this defeats + * gapless playback. */ + const bool force_reopen; + + /* Returns current volume for left and right channels (0 to 100). */ + virtual StereoVolume get_volume () = 0; + + /* Changes volume for left and right channels (0 to 100). */ + virtual void set_volume (StereoVolume volume) = 0; + + /* Sets information about the song being played. This function will be + * called before open_audio(). */ + virtual void set_info (const char * filename, const Tuple & tuple) {} + + /* Begins playback of a PCM stream. <format> is one of the FMT_* + * enumeration values defined in libaudcore/audio.h. Returns true on + * success. */ + virtual bool open_audio (int format, int rate, int chans) = 0; + + /* Ends playback. Any buffered audio data is discarded. */ + virtual void close_audio () = 0; + + /* Waits until write_audio() will return a size greater than zero. + * output_time(), pause(), and flush() may be called meanwhile; if flush() + * is called, period_wait() should return immediately. */ + virtual void period_wait () = 0; + + /* Writes up to <size> bytes of data, in the format given to open_audio(). + * If there is not enough buffer space for all <size> bytes, writes only as + * many bytes as can be written immediately without blocking. Returns the + * number of bytes actually written. */ + virtual int write_audio (const void * data, int size) = 0; + + /* Waits until all buffered data has been heard by the user. */ + virtual void drain () = 0; + + /* Returns an estimate of how many milliseconds will pass before all the + * data passed to write_audio() has been heard by the user. */ + virtual int get_delay () = 0; + + /* Pauses the stream if <p> is nonzero; otherwise unpauses it. + * write_audio() will not be called while the stream is paused. */ + virtual void pause (bool pause) = 0; + + /* Discards any buffered audio data. */ + virtual void flush () = 0; +}; + +class EffectPlugin : public Plugin +{ +public: + constexpr EffectPlugin (const PluginInfo info, int order, bool preserves_format) : + Plugin (PluginType::Effect, info), + order (order), + preserves_format (preserves_format) {} + + /* Effects with lowest order (0 to 9) are applied first. */ + const int order; + + /* If the effect does not change the number of channels or the sampling + * rate, it can be enabled and disabled more smoothly. */ + const bool preserves_format; + + /* All processing is done in floating point. If the effect plugin wants to + * change the channel count or sample rate, it can change the parameters + * passed to start(). They cannot be changed in the middle of a song. */ + virtual void start (int & channels, int & rate) = 0; + + /* Performs effect processing. process() may modify the audio samples in + * place and return a reference to the same buffer, or it may return a + * reference to an internal working buffer. The number of output samples + * need not be the same as the number of input samples. */ + virtual Index<float> & process (Index<float> & data) = 0; + + /* Optional. A seek is taking place; any buffers should be discarded. + * Unless the "force" flag is set, the plugin may choose to override the + * normal flush behavior and handle the flush itself (for example, to + * perform crossfading). The flush() function should return false in this + * case to prevent flush() from being called in downstream effect plugins. */ + virtual bool flush (bool force) + { return true; } + + /* Exactly like process() except that any buffers should be drained (i.e. + * the data processed and returned). finish() will be called a second time + * at the end of the last song in the playlist. */ + virtual Index<float> & finish (Index<float> & data, bool end_of_playlist) + { return process (data); } + + /* Required only for plugins that change the time domain (e.g. a time + * stretch) or use read-ahead buffering. translate_delay() must do two + * things: first, translate <delay> (which is in milliseconds) from the + * output time domain back to the input time domain; second, increase + * <delay> by the size of the read-ahead buffer. It should return the + * adjusted delay. */ + virtual int adjust_delay (int delay) + { return delay; } +}; + +enum class InputKey { + Ext, + MIME, + Scheme, + count +}; + +class InputPlugin : public Plugin +{ +public: + enum { + /* Indicates that the plugin can write file tags */ + FlagWritesTag = (1 << 0), + + /* Indicates that files handled by the plugin may contain more than one + * song. When reading the tuple for such a file, the plugin should set + * the FIELD_SUBSONG_NUM field to the number of songs in the file. For + * all other files, the field should be left unset. + * + * Example: + * 1. User adds a file named "somefile.xxx" to the playlist. Having + * determined that this plugin can handle the file, Audacious opens the + * file and calls probe_for_tuple(). probe_for_tuple() sees that there + * are 3 songs in the file and sets FIELD_SUBSONG_NUM to 3. + * 2. For each song in the file, Audacious opens the file and calls + * probe_for_tuple(); this time, however, a question mark and song + * number are appended to the file name passed: "somefile.sid?2" refers + * to the second song in the file "somefile.sid". + * 3. When one of the songs is played, Audacious opens the file and + * calls play() with a file name modified in this way. */ + FlagSubtunes = (1 << 1) + }; + + struct InputInfo + { + typedef const char * const * List; + + int flags, priority; + aud::array<InputKey, List> keys; + + constexpr InputInfo (int flags = 0) : + flags (flags), priority (0), keys {} {} + + /* Associates file extensions with the plugin. */ + constexpr InputInfo with_exts (List exts) const + { return InputInfo (flags, priority, + exts, keys[InputKey::MIME], keys[InputKey::Scheme]); } + + /* Associates MIME types with the plugin. */ + constexpr InputInfo with_mimes (List mimes) const + { return InputInfo (flags, priority, + keys[InputKey::Ext], mimes, keys[InputKey::Scheme]); } + + /* Associates custom URI schemes with the plugin. Plugins using custom + * URI schemes are expected to handle their own I/O. Hence, any VFSFile + * passed to play(), read_tuple(), etc. will be null. */ + constexpr InputInfo with_schemes (List schemes) const + { return InputInfo (flags, priority, + keys[InputKey::Ext], keys[InputKey::MIME], schemes); } + + /* Sets how quickly the plugin should be tried in searching for a plugin + * to handle a file which could not be identified from its extension. + * Plugins with priority 0 are tried first, 10 last. */ + constexpr InputInfo with_priority (int priority) const + { return InputInfo (flags, priority, + keys[InputKey::Ext], keys[InputKey::MIME], keys[InputKey::Scheme]); } + + private: + constexpr InputInfo (int flags, int priority, List exts, List mimes, List schemes) : + flags (flags), priority (priority), keys {exts, mimes, schemes} {} + }; + + constexpr InputPlugin (PluginInfo info, InputInfo input_info) : + Plugin (PluginType::Input, info), + input_info (input_info) {} + + const InputInfo input_info; + + /* Returns true if the plugin can handle the file. */ + virtual bool is_our_file (const char * filename, VFSFile & file) = 0; + + /* Reads metadata from the file. */ + virtual Tuple read_tuple (const char * filename, VFSFile & file) = 0; + + /* Plays the file. Returns false on error. Also see input-api.h. */ + virtual bool play (const char * filename, VFSFile & file) = 0; + + /* Optional. Writes metadata to the file, returning false on error. */ + virtual bool write_tuple (const char * filename, VFSFile & file, const Tuple & tuple) + { return false; } + + /* Optional. Reads an album art image (JPEG or PNG data) from the file. + * Returns an empty buffer on error. */ + virtual Index<char> read_image (const char * filename, VFSFile & file) + { return Index<char> (); } + + /* Optional. Displays a window showing info about the file. In general, + * this function should be avoided since Audacious already provides a file + * info window. */ + virtual bool file_info_box (const char * filename, VFSFile & file) + { return false; } + +protected: + /* Prepares the output system for playback in the specified format. Also + * triggers the "playback ready" hook. Hence, if you call set_replay_gain, + * set_playback_tuple, or set_stream_bitrate, consider doing so before + * calling open_audio. There is no return value. If the requested audio + * format is not supported, write_audio() will do nothing and check_stop() + * will immediately return true. */ + static void open_audio (int format, int rate, int channels); + + /* Informs the output system of replay gain values for the current song so + * that volume levels can be adjusted accordingly, if the user so desires. + * This may be called at any time during playback should the values change. */ + static void set_replay_gain (const ReplayGainInfo & gain); + + /* Passes audio data to the output system for playback. The data must be in + * the format passed to open_audio(), and the length (in bytes) must be an + * integral number of frames. This function blocks until all the data has + * been written (though it may not yet be heard by the user). */ + static void write_audio (const void * data, int length); + + /* Returns the current tuple for the stream. */ + static Tuple get_playback_tuple (); + + /* Updates the tuple for the stream. */ + static void set_playback_tuple (Tuple && tuple); + + /* Updates the displayed bitrate, in bits per second. */ + static void set_stream_bitrate (int bitrate); + + /* Checks whether playback is to be stopped. The play() function should + * poll check_stop() periodically and return as soon as check_stop() returns + * true. */ + static bool check_stop (); + + /* Checks whether a seek has been requested. If so, returns the position to + * seek to, in milliseconds. Otherwise, returns -1. */ + static int check_seek (); +}; + +class DockablePlugin : public Plugin +{ +public: + constexpr DockablePlugin (PluginType type, PluginInfo info) : + Plugin (type, info) {} + + /* GtkWidget * get_gtk_widget () */ + virtual void * get_gtk_widget () { return nullptr; } + + /* QWidget * get_qt_widget () */ + virtual void * get_qt_widget () { return nullptr; } +}; + +class GeneralPlugin : public DockablePlugin +{ +public: + constexpr GeneralPlugin (PluginInfo info, bool enabled_by_default) : + DockablePlugin (PluginType::General, info), + enabled_by_default (enabled_by_default) {} + + const bool enabled_by_default; +}; + +class VisPlugin : public DockablePlugin, public Visualizer +{ +public: + constexpr VisPlugin (PluginInfo info, int type_mask) : + DockablePlugin (PluginType::Vis, info), + Visualizer (type_mask) {} +}; + +class IfacePlugin : public Plugin +{ +public: + constexpr IfacePlugin (PluginInfo info) : + Plugin (PluginType::Iface, info) {} + + virtual void show (bool show) = 0; + virtual void run () = 0; + virtual void quit () = 0; + + virtual void show_about_window () = 0; + virtual void hide_about_window () = 0; + virtual void show_filebrowser (bool open) = 0; + virtual void hide_filebrowser () = 0; + virtual void show_jump_to_song () = 0; + virtual void hide_jump_to_song () = 0; + virtual void show_prefs_window () = 0; + virtual void hide_prefs_window () = 0; + virtual void plugin_menu_add (AudMenuID id, void func (), const char * name, const char * icon) = 0; + virtual void plugin_menu_remove (AudMenuID id, void func ()) = 0; +}; + +#endif diff --git a/src/libaudcore/plugins-internal.h b/src/libaudcore/plugins-internal.h new file mode 100644 index 0000000..06bbc1e --- /dev/null +++ b/src/libaudcore/plugins-internal.h @@ -0,0 +1,54 @@ +/* + * internal.h + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_PLUGINS_INTERNAL_H +#define LIBAUDCORE_PLUGINS_INTERNAL_H + +#include "plugins.h" + +enum class InputKey; +class Plugin; + +/* plugin-init.c */ +void start_plugins_one (); +void start_plugins_two (); +void stop_plugins_two (); +void stop_plugins_one (); + +/* plugin-load.c */ +void plugin_system_init (); +void plugin_system_cleanup (); +Plugin * plugin_load (const char * path); + +/* plugin-registry.c */ +void plugin_registry_load (); +void plugin_registry_prune (); +void plugin_registry_save (); + +void plugin_register (const char * path, int timestamp); +void plugin_set_enabled (PluginHandle * plugin, bool enabled); +void plugin_set_failed (PluginHandle * plugin); + +bool transport_plugin_has_scheme (PluginHandle * plugin, const char * scheme); +bool playlist_plugin_has_ext (PluginHandle * plugin, const char * ext); +bool input_plugin_has_key (PluginHandle * plugin, InputKey key, const char * value); +bool input_plugin_has_subtunes (PluginHandle * plugin); +bool input_plugin_can_write_tuple (PluginHandle * plugin); + +#endif diff --git a/src/libaudcore/plugins.h b/src/libaudcore/plugins.h new file mode 100644 index 0000000..7223560 --- /dev/null +++ b/src/libaudcore/plugins.h @@ -0,0 +1,68 @@ +/* + * plugins.h + * Copyright 2010-2012 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_PLUGINS_H +#define LIBAUDCORE_PLUGINS_H + +#include <libaudcore/index.h> + +enum class PluginType { + Transport, + Playlist, + Input, + Effect, + Output, + Vis, + General, + Iface, + count +}; + +class PluginHandle; + +/* CAUTION: These functions are not thread safe. */ + +PluginHandle * aud_plugin_get_current (PluginType type); +bool aud_plugin_get_enabled (PluginHandle * plugin); +bool aud_plugin_enable (PluginHandle * plugin, bool enable); + +int aud_plugin_send_message (PluginHandle * plugin, const char * code, const void * data, int size); +void * aud_plugin_get_gtk_widget (PluginHandle * plugin); // returns (GtkWidget *) +void * aud_plugin_get_qt_widget (PluginHandle * plugin); // return (QWidget *) + +PluginType aud_plugin_get_type (PluginHandle * plugin); +const char * aud_plugin_get_basename (PluginHandle * plugin); +PluginHandle * aud_plugin_lookup_basename (const char * basename); + +const void * aud_plugin_get_header (PluginHandle * plugin); +PluginHandle * aud_plugin_by_header (const void * header); + +const Index<PluginHandle *> & aud_plugin_list (PluginType type); + +const char * aud_plugin_get_name (PluginHandle * plugin); +bool aud_plugin_has_about (PluginHandle * plugin); +bool aud_plugin_has_configure (PluginHandle * plugin); + +/* returns true to continue watching, false to stop */ +typedef bool (* PluginWatchFunc) (PluginHandle * plugin, void * data); + +void aud_plugin_add_watch (PluginHandle * plugin, PluginWatchFunc func, void * data); +void aud_plugin_remove_watch (PluginHandle * plugin, PluginWatchFunc func, void * data); + +#endif diff --git a/src/libaudcore/preferences.cc b/src/libaudcore/preferences.cc new file mode 100644 index 0000000..4f14e39 --- /dev/null +++ b/src/libaudcore/preferences.cc @@ -0,0 +1,123 @@ +/* + * preferences.cc + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "preferences.h" +#include "runtime.h" + +#include <assert.h> + +EXPORT bool WidgetConfig::get_bool () const +{ + assert (type == Bool); + + if (value) + return * (bool *) value; + else if (name) + return aud_get_bool (section, name); + else + return false; +} + +EXPORT void WidgetConfig::set_bool (bool val) const +{ + assert (type == Bool); + + if (value) + * (bool *) value = val; + else if (name) + aud_set_bool (section, name, val); + + if (callback) + callback (); +} + +EXPORT int WidgetConfig::get_int () const +{ + assert (type == Int); + + if (value) + return * (int *) value; + else if (name) + return aud_get_int (section, name); + else + return 0; +} + +EXPORT void WidgetConfig::set_int (int val) const +{ + assert (type == Int); + + if (value) + * (int *) value = val; + else if (name) + aud_set_int (section, name, val); + + if (callback) + callback (); +} + +EXPORT double WidgetConfig::get_float () const +{ + assert (type == Float); + + if (value) + return * (double *) value; + else if (name) + return aud_get_double (section, name); + else + return 0; +} + +EXPORT void WidgetConfig::set_float (double val) const +{ + assert (type == Float); + + if (value) + * (double *) value = val; + else if (name) + aud_set_double (section, name, val); + + if (callback) + callback (); +} + +EXPORT String WidgetConfig::get_string () const +{ + assert (type == String); + + if (value) + return * (::String *) value; + else if (name) + return aud_get_str (section, name); + else + return ::String (); +} + +EXPORT void WidgetConfig::set_string (const char * val) const +{ + assert (type == String); + + if (value) + * (::String *) value = ::String (val); + else if (name) + aud_set_str (section, name, val); + + if (callback) + callback (); +} diff --git a/src/libaudcore/preferences.h b/src/libaudcore/preferences.h new file mode 100644 index 0000000..6f34ce5 --- /dev/null +++ b/src/libaudcore/preferences.h @@ -0,0 +1,301 @@ +/* + * preferences.h + * Copyright 2007-2014 Tomasz MoĆ, William Pitcock, and John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_PREFERENCES_H +#define LIBAUDCORE_PREFERENCES_H + +#include <libaudcore/objects.h> + +struct PreferencesWidget; + +struct ComboItem { + const char * label; + const char * str; + int num; + + constexpr ComboItem (const char * label, const char * str) : + label (label), + str (str), + num (-1) {} + + constexpr ComboItem (const char * label, int num) : + label (label), + str (nullptr), + num (num) {} +}; + +struct WidgetVButton { + void (* callback) (void); + const char * icon; +}; + +struct WidgetVRadio { + int value; +}; + +struct WidgetVSpin { + double min, max, step; + const char * right_label; /* text right to widget */ +}; + +struct WidgetVTable { + ArrayRef<PreferencesWidget> widgets; +}; + +struct WidgetVFonts { + const char * title; +}; + +struct WidgetVEntry { + bool password; +}; + +struct WidgetVCombo { + /* static init */ + ArrayRef<ComboItem> elems; + + /* runtime init */ + ArrayRef<ComboItem> (* fill) (); +}; + +struct WidgetVBox { + ArrayRef<PreferencesWidget> widgets; + + bool horizontal; /* false gives vertical, true gives horizontal alignment of child widgets */ + bool frame; /* whether to draw frame around box */ +}; + +struct NotebookTab { + const char * name; + ArrayRef<PreferencesWidget> widgets; +}; + +struct WidgetVNotebook { + ArrayRef<NotebookTab> tabs; +}; + +struct WidgetVSeparator { + bool horizontal; /* false gives vertical, true gives horizontal separator */ +}; + +union WidgetVariant { + WidgetVButton button; + WidgetVRadio radio_btn; + WidgetVSpin spin_btn; + WidgetVTable table; + WidgetVFonts font_btn; + WidgetVEntry entry; + WidgetVCombo combo; + WidgetVBox box; + WidgetVNotebook notebook; + WidgetVSeparator separator; + + /* for custom GTK or Qt widgets */ + /* GtkWidget * (* populate) (void); */ + /* QWidget * (* populate) (void); */ + void * (* populate) (void); + + constexpr WidgetVariant (WidgetVButton button) : button (button) {} + constexpr WidgetVariant (WidgetVRadio radio) : radio_btn (radio) {} + constexpr WidgetVariant (WidgetVSpin spin) : spin_btn (spin) {} + constexpr WidgetVariant (WidgetVTable table) : table (table) {} + constexpr WidgetVariant (WidgetVFonts fonts) : font_btn (fonts) {} + constexpr WidgetVariant (WidgetVEntry entry) : entry (entry) {} + constexpr WidgetVariant (WidgetVCombo combo) : combo (combo) {} + constexpr WidgetVariant (WidgetVBox box) : box (box) {} + constexpr WidgetVariant (WidgetVNotebook notebook) : notebook (notebook) {} + constexpr WidgetVariant (WidgetVSeparator separator) : separator (separator) {} + + /* also serves as default constructor */ + constexpr WidgetVariant (void * (* populate) (void) = 0) : populate (populate) {} +}; + +struct WidgetConfig +{ + enum Type { + None, + Bool, + Int, + Float, + String + }; + + Type type; + + /* pointer to immediate value */ + void * value; + /* identifier for configuration value */ + const char * section, * name; + /* called when value is changed */ + void (* callback) (void); + /* widget updates when this hook is called */ + const char * hook; + + constexpr WidgetConfig () : + type (None), + value (nullptr), + section (nullptr), + name (nullptr), + callback (nullptr), + hook (nullptr) {} + + constexpr WidgetConfig (Type type, void * value, const char * section, + const char * name, void (* callback) (void), const char * hook) : + type (type), + value (value), + section (section), + name (name), + callback (callback), + hook (hook) {} + + bool get_bool () const; + void set_bool (bool val) const; + int get_int () const; + void set_int (int val) const; + double get_float () const; + void set_float (double val) const; + ::String get_string () const; + void set_string (const char * val) const; +}; + +constexpr WidgetConfig WidgetBool (bool & value, + void (* callback) (void) = nullptr, const char * hook = nullptr) + { return WidgetConfig (WidgetConfig::Bool, (void *) & value, 0, 0, callback, hook); } +constexpr WidgetConfig WidgetInt (int & value, + void (* callback) (void) = nullptr, const char * hook = nullptr) + { return WidgetConfig (WidgetConfig::Int, (void *) & value, 0, 0, callback, hook); } +constexpr WidgetConfig WidgetFloat (double & value, + void (* callback) (void) = nullptr, const char * hook = nullptr) + { return WidgetConfig (WidgetConfig::Float, (void *) & value, 0, 0, callback, hook); } +constexpr WidgetConfig WidgetString (String & value, + void (* callback) (void) = nullptr, const char * hook = nullptr) + { return WidgetConfig (WidgetConfig::String, (void *) & value, 0, 0, callback, hook); } + +constexpr WidgetConfig WidgetBool (const char * section, const char * name, + void (* callback) (void) = nullptr, const char * hook = nullptr) + { return WidgetConfig (WidgetConfig::Bool, 0, section, name, callback, hook); } +constexpr WidgetConfig WidgetInt (const char * section, const char * name, + void (* callback) (void) = nullptr, const char * hook = nullptr) + { return WidgetConfig (WidgetConfig::Int, 0, section, name, callback, hook); } +constexpr WidgetConfig WidgetFloat (const char * section, const char * name, + void (* callback) (void) = nullptr, const char * hook = nullptr) + { return WidgetConfig (WidgetConfig::Float, 0, section, name, callback, hook); } +constexpr WidgetConfig WidgetString (const char * section, const char * name, + void (* callback) (void) = nullptr, const char * hook = nullptr) + { return WidgetConfig (WidgetConfig::String, 0, section, name, callback, hook); } + +struct PreferencesWidget +{ + enum Type { + Label, + Button, + CheckButton, + RadioButton, + SpinButton, + Entry, + ComboBox, + FontButton, + Box, + Table, + Notebook, + Separator, + CustomGTK, + CustomQt + }; + + Type type; + const char * label; /* widget title (for SPIN_BTN it's text left to widget) */ + bool child; + + WidgetConfig cfg; + WidgetVariant data; +}; + +enum WidgetIsChild { + WIDGET_NOT_CHILD, + WIDGET_CHILD +}; + +constexpr PreferencesWidget WidgetLabel (const char * label, + WidgetIsChild child = WIDGET_NOT_CHILD) + { return {PreferencesWidget::Label, label, (child == WIDGET_CHILD)}; } + +constexpr PreferencesWidget WidgetButton (const char * label, + WidgetVButton button, WidgetIsChild child = WIDGET_NOT_CHILD) + { return {PreferencesWidget::Button, label, (child == WIDGET_CHILD), {}, button}; } + +constexpr PreferencesWidget WidgetCheck (const char * label, + WidgetConfig cfg, WidgetIsChild child = WIDGET_NOT_CHILD) + { return {PreferencesWidget::CheckButton, label, + (child == WIDGET_CHILD), cfg}; } + +constexpr PreferencesWidget WidgetRadio (const char * label, + WidgetConfig cfg, WidgetVRadio radio, WidgetIsChild child = WIDGET_NOT_CHILD) + { return {PreferencesWidget::RadioButton, label, + (child == WIDGET_CHILD), cfg, radio}; } + +constexpr PreferencesWidget WidgetSpin (const char * label, + WidgetConfig cfg, WidgetVSpin spin, WidgetIsChild child = WIDGET_NOT_CHILD) + { return {PreferencesWidget::SpinButton, label, + (child == WIDGET_CHILD), cfg, spin}; } + +constexpr PreferencesWidget WidgetEntry (const char * label, + WidgetConfig cfg, WidgetVEntry entry = WidgetVEntry(), + WidgetIsChild child = WIDGET_NOT_CHILD) + { return {PreferencesWidget::Entry, label, + (child == WIDGET_CHILD), cfg, entry}; } + +constexpr PreferencesWidget WidgetCombo (const char * label, + WidgetConfig cfg, WidgetVCombo combo, WidgetIsChild child = WIDGET_NOT_CHILD) + { return {PreferencesWidget::ComboBox, label, + (child == WIDGET_CHILD), cfg, combo}; } + +constexpr PreferencesWidget WidgetFonts (const char * label, + WidgetConfig cfg, WidgetVFonts fonts, WidgetIsChild child = WIDGET_NOT_CHILD) + { return {PreferencesWidget::FontButton, label, + (child == WIDGET_CHILD), cfg, fonts}; } + +constexpr PreferencesWidget WidgetBox (WidgetVBox box, WidgetIsChild child = WIDGET_NOT_CHILD) + { return {PreferencesWidget::Box, 0, (child == WIDGET_CHILD), {}, box}; } + +constexpr PreferencesWidget WidgetTable (WidgetVTable table, + WidgetIsChild child = WIDGET_NOT_CHILD) + { return {PreferencesWidget::Table, 0, (child == WIDGET_CHILD), {}, table}; } + +constexpr PreferencesWidget WidgetNotebook (WidgetVNotebook notebook) + { return {PreferencesWidget::Notebook, 0, 0, {}, notebook}; } + +constexpr PreferencesWidget WidgetSeparator (WidgetVSeparator separator = WidgetVSeparator ()) + { return {PreferencesWidget::Separator, 0, 0, {}, separator}; } + +constexpr PreferencesWidget WidgetCustomGTK (void * (* populate) (void)) + { return {PreferencesWidget::CustomGTK, 0, 0, {}, populate}; } + +constexpr PreferencesWidget WidgetCustomQt (void * (* populate) (void)) + { return {PreferencesWidget::CustomQt, 0, 0, {}, populate}; } + +struct PluginPreferences { + ArrayRef<PreferencesWidget> widgets; + + void (* init) (void); + void (* apply) (void); + void (* cleanup) (void); +}; + +#endif /* LIBAUDCORE_PREFERENCES_H */ diff --git a/src/libaudcore/probe-buffer.cc b/src/libaudcore/probe-buffer.cc new file mode 100644 index 0000000..599f587 --- /dev/null +++ b/src/libaudcore/probe-buffer.cc @@ -0,0 +1,146 @@ +/* + * probe-buffer.c + * Copyright 2010-2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "internal.h" + +#include <string.h> + +#include "vfs.h" + +#define BUFSIZE (256 * 1024) + +class ProbeBuffer : public VFSImpl +{ +public: + ProbeBuffer (VFSFile && file) : + m_file (std::move (file)), + m_filled (0), + m_at (0) {} + + int64_t fread (void * ptr, int64_t size, int64_t nmemb); + int fseek (int64_t offset, VFSSeekType whence); + + int64_t ftell (); + int64_t fsize (); + bool feof (); + + int64_t fwrite (const void * ptr, int64_t size, int64_t nmemb); + int ftruncate (int64_t length); + int fflush (); + + String get_metadata (const char * field); + +private: + void increase_buffer (int64_t size); + + VFSFile m_file; + int m_filled, m_at; + unsigned char m_buffer[BUFSIZE]; +}; + +void ProbeBuffer::increase_buffer (int64_t size) +{ + size = (size + 0xFF) & ~0xFF; + + if (size > (int64_t) sizeof m_buffer) + size = (int64_t) sizeof m_buffer; + + if (m_filled < size) + m_filled += m_file.fread (m_buffer + m_filled, 1, size - m_filled); +} + +int64_t ProbeBuffer::fread (void * buffer, int64_t size, int64_t count) +{ + increase_buffer (m_at + size * count); + int readed = (size > 0) ? aud::min (count, (m_filled - m_at) / size) : 0; + memcpy (buffer, m_buffer + m_at, size * readed); + + m_at += size * readed; + return readed; +} + +int64_t ProbeBuffer::fwrite (const void * data, int64_t size, int64_t count) +{ + return 0; /* not allowed */ +} + +int ProbeBuffer::fseek (int64_t offset, VFSSeekType whence) +{ + if (whence == VFS_SEEK_END) + return -1; /* not allowed */ + + if (whence == VFS_SEEK_CUR) + offset += m_at; + + if (offset < 0 || offset > (int64_t) sizeof m_buffer) + return -1; + + increase_buffer (offset); + + if (offset > m_filled) + return -1; + + m_at = offset; + return 0; +} + +int64_t ProbeBuffer::ftell () +{ + return m_at; +} + +bool ProbeBuffer::feof () +{ + if (m_at < m_filled) + return false; + if (m_at == sizeof m_buffer) + return true; + + return m_file.feof (); +} + +int ProbeBuffer::ftruncate (int64_t size) +{ + return -1; /* not allowed */ +} + +int ProbeBuffer::fflush () +{ + return 0; /* no-op */ +} + +int64_t ProbeBuffer::fsize () +{ + int64_t size = m_file.fsize (); + return aud::min (size, (int64_t) sizeof m_buffer); +} + +String ProbeBuffer::get_metadata (const char * field) +{ + return m_file.get_metadata (field); +} + +VFSFile probe_buffer_new (const char * filename) +{ + VFSFile file (filename, "r"); + if (! file) + return file; // preserve error message + + return VFSFile (filename, new ProbeBuffer (std::move (file))); +} diff --git a/src/libaudcore/probe.cc b/src/libaudcore/probe.cc new file mode 100644 index 0000000..579f41c --- /dev/null +++ b/src/libaudcore/probe.cc @@ -0,0 +1,215 @@ +/* + * probe.c + * Copyright 2009-2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "probe.h" + +#include <string.h> + +#include "audstrings.h" +#include "i18n.h" +#include "internal.h" +#include "playlist.h" +#include "plugin.h" +#include "plugins-internal.h" +#include "runtime.h" + +EXPORT PluginHandle * aud_file_find_decoder (const char * filename, bool fast, String * error) +{ + AUDINFO ("Probing %s.\n", filename); + + auto & list = aud_plugin_list (PluginType::Input); + + StringBuf scheme = uri_get_scheme (filename); + StringBuf ext = uri_get_extension (filename); + Index<PluginHandle *> ext_matches; + + for (PluginHandle * plugin : list) + { + if (! aud_plugin_get_enabled (plugin)) + continue; + + if (scheme && input_plugin_has_key (plugin, InputKey::Scheme, scheme)) + { + AUDINFO ("Matched %s by URI scheme.\n", aud_plugin_get_name (plugin)); + return plugin; + } + + if (ext && input_plugin_has_key (plugin, InputKey::Ext, ext)) + ext_matches.append (plugin); + } + + if (ext_matches.len () == 1) + { + AUDINFO ("Matched %s by extension.\n", aud_plugin_get_name (ext_matches[0])); + return ext_matches[0]; + } + + AUDDBG ("Matched %d plugins by extension.\n", ext_matches.len ()); + + if (fast && ! ext_matches.len ()) + return nullptr; + + AUDDBG ("Opening %s.\n", filename); + + VFSFile file (probe_buffer_new (filename)); + + if (! file) + { + if (error) + * error = String (file.error ()); + + AUDINFO ("Open failed: %s.\n", file.error ()); + return nullptr; + } + + String mime = file.get_metadata ("content-type"); + + if (mime) + { + for (PluginHandle * plugin : (ext_matches.len () ? ext_matches : list)) + { + if (! aud_plugin_get_enabled (plugin)) + continue; + + if (input_plugin_has_key (plugin, InputKey::MIME, mime)) + { + AUDINFO ("Matched %s by MIME type %s.\n", + aud_plugin_get_name (plugin), (const char *) mime); + return plugin; + } + } + } + + for (PluginHandle * plugin : (ext_matches.len () ? ext_matches : list)) + { + if (! aud_plugin_get_enabled (plugin)) + continue; + + AUDINFO ("Trying %s.\n", aud_plugin_get_name (plugin)); + + InputPlugin * ip = (InputPlugin *) aud_plugin_get_header (plugin); + if (! ip) + continue; + + if (ip->is_our_file (filename, file)) + { + AUDINFO ("Matched %s by content.\n", aud_plugin_get_name (plugin)); + return plugin; + } + + if (file.fseek (0, VFS_SEEK_SET) != 0) + { + if (error) + * error = String (_("Seek error")); + + AUDINFO ("Seek failed.\n"); + return nullptr; + } + } + + if (error) + * error = String (_("File format not recognized")); + + AUDINFO ("No plugins matched.\n"); + return nullptr; +} + +static bool open_file (const char * filename, InputPlugin * ip, + const char * mode, VFSFile & handle, String * error = nullptr) +{ + /* no need to open a handle for custom URI schemes */ + if (ip->input_info.keys[InputKey::Scheme]) + return true; + + handle = VFSFile (filename, mode); + if (! handle && error) + * error = String (handle.error ()); + + return (bool) handle; +} + +EXPORT Tuple aud_file_read_tuple (const char * filename, PluginHandle * decoder, String * error) +{ + InputPlugin * ip = (InputPlugin *) aud_plugin_get_header (decoder); + if (! ip && error) + * error = String (_("Error loading plugin")); + if (! ip) + return Tuple (); + + VFSFile handle; + if (! open_file (filename, ip, "r", handle, error)) + return Tuple (); + + Tuple tuple = ip->read_tuple (filename, handle); + if (! tuple && error) + * error = String (_("Error reading metadata")); + + return tuple; +} + +EXPORT Index<char> aud_file_read_image (const char * filename, PluginHandle * decoder) +{ + InputPlugin * ip = (InputPlugin *) aud_plugin_get_header (decoder); + if (! ip) + return Index<char> (); + + VFSFile handle; + if (! open_file (filename, ip, "r", handle)) + return Index<char> (); + + return ip->read_image (filename, handle); +} + +EXPORT bool aud_file_can_write_tuple (const char * filename, PluginHandle * decoder) +{ + return input_plugin_can_write_tuple (decoder); +} + +EXPORT bool aud_file_write_tuple (const char * filename, + PluginHandle * decoder, const Tuple & tuple) +{ + InputPlugin * ip = (InputPlugin *) aud_plugin_get_header (decoder); + if (! ip) + return false; + + VFSFile handle; + if (! open_file (filename, ip, "r+", handle)) + return false; + + bool success = ip->write_tuple (filename, handle, tuple) && + (! handle || handle.fflush () == 0); + + if (success) + aud_playlist_rescan_file (filename); + + return success; +} + +EXPORT bool aud_custom_infowin (const char * filename, PluginHandle * decoder) +{ + InputPlugin * ip = (InputPlugin *) aud_plugin_get_header (decoder); + if (! ip) + return false; + + VFSFile handle; + if (! open_file (filename, ip, "r", handle)) + return false; + + return ip->file_info_box (filename, handle); +} diff --git a/src/libaudcore/probe.h b/src/libaudcore/probe.h new file mode 100644 index 0000000..4010f3f --- /dev/null +++ b/src/libaudcore/probe.h @@ -0,0 +1,51 @@ +/* + * probe.h + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_PROBE_H +#define LIBAUDCORE_PROBE_H + +#include <libaudcore/index.h> +#include <libaudcore/objects.h> + +class PluginHandle; +class Tuple; + +/* Gets album art for <file> (the URI of a song file) as JPEG or PNG data. If + * the album art is not yet loaded, sets *queued to true, returns nullptr, and + * begins to load the album art in the background. On completion, the "art + * ready" hook is called, with <file> as a parameter. The "current art ready" + * hook is also called if <file> is the currently playing song. If no album art + * could be loaded, sets *queued to false and returns nullptr. */ +const Index<char> * aud_art_request_data (const char * file, bool * queued = nullptr); + +/* Similar to art_request_data() but returns the URI of an image file. + * (A temporary file will be created if necessary.) */ +const char * aud_art_request_file (const char * file, bool * queued = nullptr); + +/* Releases album art returned by art_request_data() or art_request_file(). */ +void aud_art_unref (const char * file); + +PluginHandle * aud_file_find_decoder (const char * filename, bool fast, String * error = nullptr); +Tuple aud_file_read_tuple (const char * filename, PluginHandle * decoder, String * error = nullptr); +Index<char> aud_file_read_image (const char * filename, PluginHandle * decoder); +bool aud_file_can_write_tuple (const char * filename, PluginHandle * decoder); +bool aud_file_write_tuple (const char * filename, PluginHandle * decoder, const Tuple & tuple); +bool aud_custom_infowin (const char * filename, PluginHandle * decoder); + +#endif diff --git a/src/libaudcore/ringbuf.cc b/src/libaudcore/ringbuf.cc new file mode 100644 index 0000000..07fbc59 --- /dev/null +++ b/src/libaudcore/ringbuf.cc @@ -0,0 +1,197 @@ +/* + * ringbuf.cc + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "ringbuf.h" +#include "internal.h" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +void RingBufBase::get_areas (int pos, int len, Areas & areas) +{ + assert (pos >= 0 && len >= 0 && pos + len <= m_len); + + int start = (m_offset + pos) % m_size; + int part = aud::min (len, m_size - start); + + areas.area1 = (char *) m_data + start; + areas.area2 = m_data; + areas.len1 = part; + areas.len2 = len - part; +} + +void RingBufBase::do_realloc (int size) +{ + void * mem = realloc (m_data, size); + if (size && ! mem) + throw std::bad_alloc (); /* nothing changed yet */ + + m_data = mem; +} + +EXPORT void RingBufBase::alloc (int size) +{ + assert (size >= m_len); + + if (size == m_size) + return; + + /* reallocate first when growing */ + if (size > m_size) + do_realloc (size); + + __sync_add_and_fetch (& misc_bytes_allocated, size - m_size); + + int old_size = m_size; + int to_end = m_size - m_offset; + + m_size = size; + + if (to_end < m_len) + { + int new_offset = size - to_end; + memmove ((char *) m_data + new_offset, (char *) m_data + m_offset, to_end); + m_offset = new_offset; + } + + /* reallocate last when shrinking */ + if (size < old_size) + do_realloc (size); +} + +EXPORT void RingBufBase::destroy (aud::EraseFunc erase_func) +{ + if (! m_data) + return; + + __sync_sub_and_fetch (& misc_bytes_allocated, m_size); + + discard (-1, erase_func); + + free (m_data); + m_data = nullptr; + m_size = 0; +} + +EXPORT void RingBufBase::add (int len) +{ + assert (len >= 0 && m_len + len <= m_size); + m_len += len; +} + +EXPORT void RingBufBase::remove (int len) +{ + assert (len >= 0 && len <= m_len); + + if (len == m_len) + m_offset = m_len = 0; + else + { + m_offset = (m_offset + len) % m_size; + m_len -= len; + } +} + +EXPORT void RingBufBase::copy_in (const void * from, int len, aud::CopyFunc copy_func) +{ + Areas areas; + add (len); + get_areas (m_len - len, len, areas); + + if (copy_func) + { + copy_func (from, areas.area1, areas.len1); + copy_func ((const char *) from + areas.len1, areas.area2, areas.len2); + } + else + { + memcpy (areas.area1, from, areas.len1); + memcpy (areas.area2, (const char *) from + areas.len1, areas.len2); + } +} + +EXPORT void RingBufBase::move_in (void * from, int len, aud::FillFunc fill_func) +{ + Areas areas; + add (len); + get_areas (m_len - len, len, areas); + + memcpy (areas.area1, from, areas.len1); + memcpy (areas.area2, (const char *) from + areas.len1, areas.len2); + + if (fill_func) + fill_func (from, len); +} + +EXPORT void RingBufBase::move_out (void * to, int len, aud::EraseFunc erase_func) +{ + Areas areas; + get_areas (0, len, areas); + + if (erase_func) + erase_func (to, len); + + memcpy (to, areas.area1, areas.len1); + memcpy ((char *) to + areas.len1, areas.area2, areas.len2); + + remove (len); +} + +EXPORT void RingBufBase::discard (int len, aud::EraseFunc erase_func) +{ + if (! m_data) + return; + + if (len < 0) + len = m_len; + + if (erase_func) + { + Areas areas; + get_areas (0, len, areas); + erase_func (areas.area1, areas.len1); + erase_func (areas.area2, areas.len2); + } + + remove (len); +} + +EXPORT void RingBufBase::move_in (IndexBase & index, int from, int len) +{ + assert (from >= 0 && from <= index.len ()); + assert (len <= index.len () - from); + + if (len < 0) + len = index.len () - from; + + move_in ((char *) index.begin () + from, len, nullptr); + index.remove (from, len, nullptr); +} + +EXPORT void RingBufBase::move_out (IndexBase & index, int to, int len) +{ + assert (len <= m_len); + + if (len < 0) + len = m_len; + + void * ptr = index.insert (to, len); + move_out (ptr, len, nullptr); +} diff --git a/src/libaudcore/ringbuf.h b/src/libaudcore/ringbuf.h new file mode 100644 index 0000000..5fd6aef --- /dev/null +++ b/src/libaudcore/ringbuf.h @@ -0,0 +1,181 @@ +/* + * ringbuf.h + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_RINGBUF_H +#define LIBAUDCORE_RINGBUF_H + +#include <libaudcore/index.h> + +/* + * RingBuf is a lightweight ring buffer class, with the following properties: + * - The base implementation is type-agnostic, so it only needs to be compiled + * once. Type-safety is provided by a thin template subclass. + * - Objects are moved in memory without calling any assignment operator. + * Be careful to use only objects that can handle this. + * - The head and tail pointers within the ring buffer stay aligned as one + * would expect: that is, if data is only written and read in multiples of n + * bytes, and the size of the ring buffer is also a multiple of n bytes, then + * no n-byte block will ever wrap around the end of the ring buffer. The + * alignment of the buffer is reset whenever the buffer becomes empty. + */ + +class RingBufBase +{ +public: + constexpr RingBufBase () : + m_data (nullptr), + m_size (0), + m_offset (0), + m_len (0) {} + + RingBufBase (RingBufBase && b) : + m_data (b.m_data), + m_size (b.m_size), + m_offset (b.m_offset), + m_len (b.m_len) + { + b.m_data = nullptr; + b.m_size = 0; + b.m_offset = 0; + b.m_len = 0; + } + + void steal (RingBufBase && b, aud::EraseFunc erase_func) + { + if (this != & b) + { + destroy (erase_func); + new (this) RingBufBase (std::move (b)); + } + } + + // allocated size of the buffer + int size () const + { return m_size; } + + // number of bytes currently used + int len () const + { return m_len; } + + // number of bytes that can be read linearly + int linear () const + { return aud::min (m_len, m_size - m_offset); } + + void * at (int pos) const + { return (char *) m_data + (m_offset + pos) % m_size; } + + void alloc (int size); + void destroy (aud::EraseFunc erase_func); + + void add (int len); // no fill + void remove (int len); // no erase + + void copy_in (const void * from, int len, aud::CopyFunc copy_func); + void move_in (void * from, int len, aud::FillFunc fill_func); + void move_out (void * to, int len, aud::EraseFunc erase_func); + void discard (int len, aud::EraseFunc erase_func); + + void move_in (IndexBase & index, int from, int len); + void move_out (IndexBase & index, int to, int len); + +private: + struct Areas { + void * area1, * area2; + int len1, len2; + }; + + void get_areas (int pos, int len, Areas & areas); + void do_realloc (int size); + + void * m_data; + int m_size, m_offset, m_len; +}; + +template<class T> +class RingBuf : private RingBufBase +{ +public: + constexpr RingBuf () : + RingBufBase () {} + + ~RingBuf () + { destroy (); } + + RingBuf (RingBuf && b) : + RingBufBase (std::move (b)) {} + void operator= (RingBuf && b) + { steal (std::move (b), aud::erase_func<T> ()); } + + int size () const + { return cooked (RingBufBase::size ()); } + int len () const + { return cooked (RingBufBase::len ()); } + int linear () const + { return cooked (RingBufBase::linear ()); } + int space () const + { return size () - len (); } + + T & operator[] (int i) + { return * (T *) RingBufBase::at (raw (i)); } + const T & operator[] (int i) const + { return * (const T *) RingBufBase::at (raw (i)); } + + void alloc (int size) + { RingBufBase::alloc (raw (size)); } + void destroy () + { RingBufBase::destroy (aud::erase_func<T> ()); } + + void copy_in (const T * from, int len) + { RingBufBase::copy_in (from, raw (len), aud::copy_func<T> ()); } + void move_in (T * from, int len) + { RingBufBase::move_in (from, raw (len), aud::fill_func<T> ()); } + void move_out (T * to, int len) + { RingBufBase::move_out (to, raw (len), aud::erase_func<T> ()); } + void discard (int len = -1) + { RingBufBase::discard (raw (len), aud::erase_func<T> ()); } + + void move_in (Index<T> & index, int from, int len) + { RingBufBase::move_in (index.base (), raw (from), raw (len)); } + void move_out (Index<T> & index, int to, int len) + { RingBufBase::move_out (index.base (), raw (to), raw (len)); } + + template<class ... Args> + T & push (Args && ... args) + { + add (raw (1)); + return * aud::construct<T>::make (at (raw (len () - 1)), std::forward<Args> (args) ...); + } + + T & head () + { return * (T *) at (raw (0)); } + + void pop () + { + head ().~T (); + remove (raw (1)); + } + +private: + static constexpr int raw (int len) + { return len * sizeof (T); } + static constexpr int cooked (int len) + { return len / sizeof (T); } +}; + +#endif // LIBAUDCORE_RINGBUF_H diff --git a/src/libaudcore/runtime.cc b/src/libaudcore/runtime.cc new file mode 100644 index 0000000..9479426 --- /dev/null +++ b/src/libaudcore/runtime.cc @@ -0,0 +1,358 @@ +/* + * runtime.c + * Copyright 2010-2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "runtime.h" + +#include <errno.h> +#include <locale.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <new> + +#ifdef _WIN32 +#include <windows.h> +#endif +#ifdef __APPLE__ +#include <mach-o/dyld.h> +#endif + +#include <glib.h> +#include <libintl.h> + +#include "audstrings.h" +#include "drct.h" +#include "hook.h" +#include "internal.h" +#include "mainloop.h" +#include "playlist-internal.h" +#include "plugins-internal.h" +#include "scanner.h" + +#define AUTOSAVE_INTERVAL 300000 /* milliseconds, autosave every 5 minutes */ + +#ifdef WORDS_BIGENDIAN +#define UTF16_NATIVE "UTF-16BE" +#else +#define UTF16_NATIVE "UTF-16LE" +#endif + +#ifdef S_IRGRP +#define DIRMODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) +#else +#define DIRMODE (S_IRWXU) +#endif + +size_t misc_bytes_allocated; + +static bool headless_mode; + +#if defined(USE_QT) && ! defined(USE_GTK) +static MainloopType mainloop_type = MainloopType::Qt; +#else +static MainloopType mainloop_type = MainloopType::GLib; +#endif + +static aud::array<AudPath, String> aud_paths; + +EXPORT void aud_set_headless_mode (bool headless) +{ + headless_mode = headless; +} + +EXPORT bool aud_get_headless_mode () +{ + return headless_mode; +} + +EXPORT void aud_set_mainloop_type (MainloopType type) +{ + mainloop_type = type; +} + +EXPORT MainloopType aud_get_mainloop_type () +{ + return mainloop_type; +} + +static StringBuf get_path_to_self () +{ +#ifdef HAVE_PROC_SELF_EXE + + StringBuf buf (-1); + int len = readlink ("/proc/self/exe", buf, buf.len ()); + + if (len < 0) + { + AUDERR ("Failed to read /proc/self/exe: %s\n", strerror (errno)); + return StringBuf (); + } + + if (len == buf.len ()) + throw std::bad_alloc (); + + buf.resize (len); + return buf; + +#elif defined _WIN32 + + StringBuf buf (-1); + wchar_t * bufw = (wchar_t *) (char *) buf; + int sizew = buf.len () / sizeof (wchar_t); + int lenw = GetModuleFileNameW (nullptr, bufw, sizew); + + if (! lenw) + { + AUDERR ("GetModuleFileName failed.\n"); + return StringBuf (); + } + + if (lenw == sizew) + throw std::bad_alloc (); + + buf.resize (lenw * sizeof (wchar_t)); + buf.steal (str_convert (buf, buf.len (), UTF16_NATIVE, "UTF-8")); + return buf; + +#elif defined __APPLE__ + + StringBuf buf (-1); + uint32_t size = buf.len (); + + if (_NSGetExecutablePath (buf, & size) < 0) + throw std::bad_alloc (); + + buf.resize (strlen (buf)); + return buf; + +#else + + return StringBuf (); + +#endif +} + +static String relocate_path (const char * path, const char * from, const char * to) +{ + int oldlen = strlen (from); + int newlen = strlen (to); + + if (oldlen && from[oldlen - 1] == G_DIR_SEPARATOR) + oldlen --; + if (newlen && to[newlen - 1] == G_DIR_SEPARATOR) + newlen --; + +#ifdef _WIN32 + if (strcmp_nocase (path, from, oldlen) || (path[oldlen] && path[oldlen] != G_DIR_SEPARATOR)) +#else + if (strncmp (path, from, oldlen) || (path[oldlen] && path[oldlen] != G_DIR_SEPARATOR)) +#endif + return String (path); + + return String (str_printf ("%.*s%s", newlen, to, path + oldlen)); +} + +static void set_default_paths () +{ + aud_paths[AudPath::BinDir] = String (HARDCODE_BINDIR); + aud_paths[AudPath::DataDir] = String (HARDCODE_DATADIR); + aud_paths[AudPath::PluginDir] = String (HARDCODE_PLUGINDIR); + aud_paths[AudPath::LocaleDir] = String (HARDCODE_LOCALEDIR); + aud_paths[AudPath::DesktopFile] = String (HARDCODE_DESKTOPFILE); + aud_paths[AudPath::IconFile] = String (HARDCODE_ICONFILE); +} + +static void relocate_all_paths () +{ + StringBuf bindir = filename_normalize (str_copy (HARDCODE_BINDIR)); + StringBuf datadir = filename_normalize (str_copy (HARDCODE_DATADIR)); + StringBuf plugindir = filename_normalize (str_copy (HARDCODE_PLUGINDIR)); + StringBuf localedir = filename_normalize (str_copy (HARDCODE_LOCALEDIR)); + StringBuf desktopfile = filename_normalize (str_copy (HARDCODE_DESKTOPFILE)); + StringBuf iconfile = filename_normalize (str_copy (HARDCODE_ICONFILE)); + + StringBuf from = str_copy (bindir); + + /* get path to current executable */ + StringBuf to = get_path_to_self (); + + if (! to) + { + set_default_paths (); + return; + } + + to.steal (filename_normalize (std::move (to))); + + const char * base = last_path_element (to); + + if (! base) + { + set_default_paths (); + return; + } + + cut_path_element (to, base - to); + + /* trim trailing path elements common to old and new paths */ + /* at the end, the old and new installation prefixes are left */ + const char * a, * b; + while ((a = last_path_element (from)) && + (b = last_path_element (to)) && +#ifdef _WIN32 + ! strcmp_nocase (a, b)) +#else + ! strcmp (a, b)) +#endif + { + cut_path_element (from, a - from); + cut_path_element (to, b - to); + } + + /* replace old prefix with new one in each path */ + aud_paths[AudPath::BinDir] = relocate_path (bindir, from, to); + aud_paths[AudPath::DataDir] = relocate_path (datadir, from, to); + aud_paths[AudPath::PluginDir] = relocate_path (plugindir, from, to); + aud_paths[AudPath::LocaleDir] = relocate_path (localedir, from, to); + aud_paths[AudPath::DesktopFile] = relocate_path (desktopfile, from, to); + aud_paths[AudPath::IconFile] = relocate_path (iconfile, from, to); +} + +EXPORT void aud_init_paths () +{ + relocate_all_paths (); + + const char * xdg_config_home = g_get_user_config_dir (); + + aud_paths[AudPath::UserDir] = String (filename_build ({xdg_config_home, "audacious"})); + aud_paths[AudPath::PlaylistDir] = String (filename_build + ({aud_paths[AudPath::UserDir], "playlists"})); + + /* create ~/.config/audacious/playlists */ + if (g_mkdir_with_parents (aud_paths[AudPath::PlaylistDir], DIRMODE) < 0) + AUDERR ("Failed to create %s: %s\n", + (const char *) aud_paths[AudPath::PlaylistDir], strerror (errno)); + +#ifdef _WIN32 + /* set some UNIX-style environment variables */ + g_setenv ("HOME", g_get_home_dir (), true); + g_setenv ("XDG_CONFIG_HOME", xdg_config_home, true); + g_setenv ("XDG_DATA_HOME", g_get_user_data_dir (), true); + g_setenv ("XDG_CACHE_HOME", g_get_user_cache_dir (), true); +#endif +} + +EXPORT void aud_cleanup_paths () +{ + for (String & path : aud_paths) + path = String (); +} + +EXPORT const char * aud_get_path (AudPath id) +{ + return aud_paths[id]; +} + +EXPORT void aud_init_i18n () +{ + const char * localedir = aud_get_path (AudPath::LocaleDir); + + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, localedir); + bind_textdomain_codeset (PACKAGE, "UTF-8"); + bindtextdomain (PACKAGE "-plugins", localedir); + bind_textdomain_codeset (PACKAGE "-plugins", "UTF-8"); + textdomain (PACKAGE); +} + +EXPORT void aud_init () +{ + g_thread_pool_set_max_idle_time (100); + + config_load (); + + art_init (); + chardet_init (); + eq_init (); + playlist_init (); + + start_plugins_one (); + + scanner_init (); + playlist_enable_scan (true); + + load_playlists (); +} + +static void do_autosave (void *) +{ + hook_call ("config save", nullptr); + save_playlists (false); + config_save (); +} + +EXPORT void aud_run () +{ + start_plugins_two (); + + static QueuedFunc autosave; + autosave.start (AUTOSAVE_INTERVAL, do_autosave, nullptr); + + /* calls "config save" before returning */ + interface_run (); + + autosave.stop (); + + stop_plugins_two (); +} + +EXPORT void aud_cleanup () +{ + save_playlists (true); + + aud_playlist_play (-1); + playback_stop (true); + + adder_cleanup (); + playlist_enable_scan (false); + scanner_cleanup (); + + stop_plugins_one (); + + art_cleanup (); + chardet_cleanup (); + eq_cleanup (); + playlist_end (); + + event_queue_cancel_all (); + hook_cleanup (); + + config_save (); + config_cleanup (); +} + +EXPORT void aud_leak_check () +{ + string_leak_check (); + + if (misc_bytes_allocated) + AUDWARN ("Bytes allocated at exit: %zd\n", misc_bytes_allocated); +} diff --git a/src/libaudcore/runtime.h b/src/libaudcore/runtime.h new file mode 100644 index 0000000..c599e04 --- /dev/null +++ b/src/libaudcore/runtime.h @@ -0,0 +1,113 @@ +/* + * runtime.h + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_RUNTIME_H +#define LIBAUDCORE_RUNTIME_H + +#include <libaudcore/objects.h> + +enum class AudPath { + BinDir, + DataDir, + PluginDir, + LocaleDir, + DesktopFile, + IconFile, + UserDir, + PlaylistDir, + count +}; + +enum class MainloopType { + GLib, + Qt +}; + +enum class OutputReset { + EffectsOnly, + ReopenStream, + ResetPlugin +}; + +namespace audlog +{ + enum Level { + Debug, + Info, + Warning, + Error + }; + + typedef void (* Handler) (Level level, const char * file, int line, + const char * func, const char * message); + + void set_stderr_level (Level level); + void subscribe (Handler handler, Level level); + void unsubscribe (Handler handler); + + void log (Level level, const char * file, int line, const char * func, + const char * format, ...); + + const char * get_level_name (Level level); +} + +#define AUDERR(...) do { audlog::log (audlog::Error, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__); } while (0) +#define AUDWARN(...) do { audlog::log (audlog::Warning, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__); } while (0) +#define AUDINFO(...) do { audlog::log (audlog::Info, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__); } while (0) +#define AUDDBG(...) do { audlog::log (audlog::Debug, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__); } while (0) + +void aud_init_paths (); +void aud_cleanup_paths (); + +const char * aud_get_path (AudPath id); + +void aud_set_headless_mode (bool headless); +bool aud_get_headless_mode (); + +void aud_set_mainloop_type (MainloopType type); +MainloopType aud_get_mainloop_type (); + +/* Requires: aud_init_paths() */ +void aud_init_i18n (); + +void aud_config_set_defaults (const char * section, const char * const * entries); + +void aud_set_str (const char * section, const char * name, const char * value); +String aud_get_str (const char * section, const char * name); +void aud_set_bool (const char * section, const char * name, bool value); +bool aud_get_bool (const char * section, const char * name); +void aud_set_int (const char * section, const char * name, int value); +int aud_get_int (const char * section, const char * name); +void aud_set_double (const char * section, const char * name, double value); +double aud_get_double (const char * section, const char * name); + +void aud_init (); +void aud_resume (); +void aud_run (); +void aud_quit (); +void aud_cleanup (); + +void aud_leak_check (); + +String aud_history_get (int entry); +void aud_history_add (const char * path); + +void aud_output_reset (OutputReset type); + +#endif diff --git a/src/libaudcore/scanner.cc b/src/libaudcore/scanner.cc new file mode 100644 index 0000000..54c845d --- /dev/null +++ b/src/libaudcore/scanner.cc @@ -0,0 +1,66 @@ +/* + * scanner.c + * Copyright 2012 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "scanner.h" + +#include <glib.h> /* for GThreadPool */ + +#include "internal.h" +#include "probe.h" +#include "tuple.h" + +static GThreadPool * pool; + +static void scan_worker (void * data, void *) +{ + ScanRequest * request = (ScanRequest *) data; + + if (! request->decoder) + request->decoder = aud_file_find_decoder (request->filename, false, & request->error); + + if (request->decoder && (request->flags & SCAN_TUPLE)) + request->tuple = aud_file_read_tuple (request->filename, request->decoder, & request->error); + + if (request->decoder && (request->flags & SCAN_IMAGE)) + { + request->image_data = aud_file_read_image (request->filename, request->decoder); + + if (! request->image_data.len ()) + request->image_file = art_search (request->filename); + } + + request->callback (request); + + delete request; +} + +void scanner_init () +{ + pool = g_thread_pool_new (scan_worker, nullptr, SCAN_THREADS, false, nullptr); +} + +void scanner_request (ScanRequest * request) +{ + g_thread_pool_push (pool, request, nullptr); +} + +void scanner_cleanup () +{ + g_thread_pool_free (pool, false, true); +} diff --git a/src/audacious/scanner.h b/src/libaudcore/scanner.h index 3eee728..8b2ff6c 100644 --- a/src/audacious/scanner.h +++ b/src/libaudcore/scanner.h @@ -17,32 +17,42 @@ * the use of this software. */ -#ifndef AUDACIOUS_SCANNER_H -#define AUDACIOUS_SCANNER_H +#ifndef LIBAUDCORE_SCANNER_H +#define LIBAUDCORE_SCANNER_H -#include <audacious/types.h> -#include <libaudcore/tuple.h> +#include "index.h" +#include "tuple.h" #define SCAN_TUPLE (1 << 0) #define SCAN_IMAGE (1 << 1) #define SCAN_THREADS 2 -struct _ScanRequest; -typedef struct _ScanRequest ScanRequest; +struct ScanRequest +{ + typedef void (* Callback) (ScanRequest * request); -typedef void (* ScanCallback) (ScanRequest * request); + ScanRequest (const char * filename, int flags, Callback callback, + PluginHandle * decoder = nullptr) : + filename (filename), + flags (flags), + callback (callback), + decoder (decoder) {} -ScanRequest * scan_request (const char * filename, int flags, - PluginHandle * decoder, ScanCallback callback); + const String filename; + const int flags; + const Callback callback; -const char * scan_request_get_filename (ScanRequest * request); -PluginHandle * scan_request_get_decoder (ScanRequest * request); -Tuple * scan_request_get_tuple (ScanRequest * request); -void scan_request_get_image_data (ScanRequest * request, void * * data, int64_t * len); -const char * scan_request_get_image_file (ScanRequest * request); + PluginHandle * decoder; -void scanner_init (void); -void scanner_cleanup (void); + Tuple tuple; + Index<char> image_data; + String image_file; + String error; +}; + +void scanner_init (); +void scanner_request (ScanRequest * request); +void scanner_cleanup (); #endif diff --git a/src/libaudcore/stringbuf.cc b/src/libaudcore/stringbuf.cc new file mode 100644 index 0000000..95cc068 --- /dev/null +++ b/src/libaudcore/stringbuf.cc @@ -0,0 +1,223 @@ +/* + * stringbuf.cc + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <pthread.h> +#include <stdlib.h> +#include <string.h> + +#include <new> + +#include "objects.h" + +#ifdef _WIN32 +#include <windows.h> +#else +#include <sys/mman.h> +#ifndef MAP_ANONYMOUS +#define MAP_ANONYMOUS MAP_ANON +#endif +#endif + +struct StringStack +{ + static constexpr int Size = 1048576; // 1 MB + + char * top; + char buf[Size - sizeof top]; +}; + +// adds one byte for null character and rounds up to word boundary +static constexpr int align (int len) +{ + return (len + sizeof (void *)) & ~(sizeof (void *) - 1); +} + +static pthread_key_t key; +static pthread_once_t once = PTHREAD_ONCE_INIT; + +#ifdef _WIN32 +static HANDLE mapping; +#endif + +static void free_stack (void * stack) +{ + if (stack) +#ifdef _WIN32 + UnmapViewOfFile (stack); +#else + munmap (stack, sizeof (StringStack)); +#endif +} + +static void make_key () +{ + pthread_key_create (& key, free_stack); + +#ifdef _WIN32 + mapping = CreateFileMappingW (INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, + 0, sizeof (StringStack), nullptr); + + if (! mapping) + throw std::bad_alloc (); +#endif +} + +static StringStack * get_stack () +{ + pthread_once (& once, make_key); + + StringStack * stack = (StringStack *) pthread_getspecific (key); + + if (! stack) + { +#ifdef _WIN32 + stack = (StringStack *) MapViewOfFile (mapping, FILE_MAP_COPY, 0, 0, sizeof (StringStack)); + + if (! stack) + throw std::bad_alloc (); +#else + stack = (StringStack *) mmap (nullptr, sizeof (StringStack), + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (stack == MAP_FAILED) + throw std::bad_alloc (); +#endif + + stack->top = stack->buf; + pthread_setspecific (key, stack); + } + + return stack; +} + +EXPORT void StringBuf::resize (int len) +{ + if (! stack) + { + stack = get_stack (); + m_data = stack->top; + } + else + { + if (m_data + align (m_len) != stack->top) + throw std::bad_alloc (); + } + + if (len < 0) + { + stack->top = stack->buf + sizeof stack->buf; + m_len = stack->top - m_data - 1; + + if (m_len < 0) + throw std::bad_alloc (); + } + else + { + stack->top = m_data + align (len); + + if (stack->top - stack->buf > (int) sizeof stack->buf) + throw std::bad_alloc (); + + m_data[len] = 0; + m_len = len; + } +} + +EXPORT StringBuf::~StringBuf () +{ + if (m_data) + { + if (m_data + align (m_len) != stack->top) + throw std::bad_alloc (); + + stack->top = m_data; + } +} + +EXPORT void StringBuf::steal (StringBuf && other) +{ + if (other.m_data) + { + if (m_data) + { + if (m_data + align (m_len) != other.m_data || + other.m_data + align (other.m_len) != stack->top) + throw std::bad_alloc (); + + m_len = other.m_len; + memmove (m_data, other.m_data, m_len + 1); + stack->top = m_data + align (m_len); + } + else + { + stack = other.stack; + m_data = other.m_data; + m_len = other.m_len; + } + + other.stack = nullptr; + other.m_data = nullptr; + other.m_len = 0; + } + else + { + if (m_data) + { + this->~StringBuf (); + stack = nullptr; + m_data = nullptr; + m_len = 0; + } + } +} + +EXPORT void StringBuf::combine (StringBuf && other) +{ + if (m_data + align (m_len) != other.m_data || other.m_data + align (other.m_len) != stack->top) + throw std::bad_alloc (); + + memmove (m_data + m_len, other.m_data, other.m_len + 1); + m_len += other.m_len; + stack->top = m_data + align (m_len); + + other.stack = nullptr; + other.m_data = nullptr; + other.m_len = 0; +} + +EXPORT void StringBuf::insert (int pos, const char * s, int len) +{ + int len0 = m_len; + + if (pos < 0) + pos = len0; + if (len < 0) + len = strlen (s); + + resize (len0 + len); + memmove (m_data + pos + len, m_data + pos, len0 - pos); + memcpy (m_data + pos, s, len); +} + +EXPORT void StringBuf::remove (int pos, int len) +{ + int after = m_len - pos - len; + memmove (m_data + pos, m_data + pos + len, after); + resize (pos + after); +} diff --git a/src/libaudcore/strpool.c b/src/libaudcore/strpool.c deleted file mode 100644 index 4dceeb0..0000000 --- a/src/libaudcore/strpool.c +++ /dev/null @@ -1,256 +0,0 @@ -/* - * strpool.c - * Copyright 2011-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <assert.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <glib.h> - -#include "audstrings.h" -#include "multihash.h" - -#ifdef VALGRIND_FRIENDLY - -typedef struct { - unsigned hash; - char magic; - char str[]; -} StrNode; - -#define NODE_SIZE_FOR(s) (offsetof (StrNode, str) + strlen (s) + 1) -#define NODE_OF(s) ((StrNode *) ((s) - offsetof (StrNode, str))) - -EXPORT char * str_get (const char * str) -{ - if (! str) - return NULL; - - StrNode * node = g_malloc (NODE_SIZE_FOR (str)); - node->magic = '@'; - node->hash = g_str_hash (str); - - strcpy (node->str, str); - return node->str; -} - -EXPORT char * str_ref (const char * str) -{ - if (! str) - return NULL; - - StrNode * node = NODE_OF (str); - assert (node->magic == '@'); - assert (g_str_hash (str) == node->hash); - - return str_get (str); -} - -EXPORT void str_unref (char * str) -{ - if (! str) - return; - - StrNode * node = NODE_OF (str); - assert (node->magic == '@'); - assert (g_str_hash (str) == node->hash); - - node->magic = 0; - g_free (node); -} - -EXPORT unsigned str_hash (const char * str) -{ - if (! str) - return 0; - - StrNode * node = NODE_OF (str); - assert (node->magic == '@'); - - return g_str_hash (str); -} - -EXPORT bool_t str_equal (const char * str1, const char * str2) -{ - assert (! str1 || NODE_OF (str1)->magic == '@'); - assert (! str2 || NODE_OF (str2)->magic == '@'); - - return ! g_strcmp0 (str1, str2); -} - -EXPORT void strpool_shutdown (void) -{ -} - -#else /* ! VALGRIND_FRIENDLY */ - -typedef struct { - MultihashNode node; - unsigned hash, refs; - char magic; - char str[]; -} StrNode; - -#define NODE_SIZE_FOR(s) (offsetof (StrNode, str) + strlen (s) + 1) -#define NODE_OF(s) ((StrNode *) ((s) - offsetof (StrNode, str))) - -static unsigned hash_cb (const MultihashNode * node) -{ - return ((const StrNode *) node)->hash; -} - -static bool_t match_cb (const MultihashNode * node_, const void * data, unsigned hash) -{ - const StrNode * node = (const StrNode *) node_; - return data == node->str || (hash == node->hash && ! strcmp (data, node->str)); -} - -static MultihashTable strpool_table = { - .hash_func = hash_cb, - .match_func = match_cb -}; - -static MultihashNode * add_cb (const void * data, unsigned hash, void * state) -{ - StrNode * node = g_malloc (NODE_SIZE_FOR (data)); - node->hash = hash; - node->refs = 1; - node->magic = '@'; - strcpy (node->str, data); - - * ((char * *) state) = node->str; - return (MultihashNode *) node; -} - -static bool_t ref_cb (MultihashNode * node_, void * state) -{ - StrNode * node = (StrNode *) node_; - - __sync_fetch_and_add (& node->refs, 1); - - * ((char * *) state) = node->str; - return FALSE; -} - -EXPORT char * str_get (const char * str) -{ - if (! str) - return NULL; - - char * ret = NULL; - multihash_lookup (& strpool_table, str, g_str_hash (str), add_cb, ref_cb, & ret); - return ret; -} - -EXPORT char * str_ref (const char * str) -{ - if (! str) - return NULL; - - StrNode * node = NODE_OF (str); - assert (node->magic == '@'); - - __sync_fetch_and_add (& node->refs, 1); - - return (char *) str; -} - -static bool_t remove_cb (MultihashNode * node_, void * state) -{ - StrNode * node = (StrNode *) node_; - - if (! __sync_bool_compare_and_swap (& node->refs, 1, 0)) - return FALSE; - - node->magic = 0; - g_free (node); - return TRUE; -} - -EXPORT void str_unref (char * str) -{ - if (! str) - return; - - StrNode * node = NODE_OF (str); - assert (node->magic == '@'); - - while (1) - { - int refs = __sync_fetch_and_add (& node->refs, 0); - - if (refs > 1) - { - if (__sync_bool_compare_and_swap (& node->refs, refs, refs - 1)) - break; - } - else - { - int status = multihash_lookup (& strpool_table, node->str, - node->hash, NULL, remove_cb, NULL); - - assert (status & MULTIHASH_FOUND); - if (status & MULTIHASH_REMOVED) - break; - } - } -} - -static bool_t leak_cb (MultihashNode * node, void * state) -{ - fprintf (stderr, "String leaked: %s\n", ((StrNode *) node)->str); - return FALSE; -} - -EXPORT void strpool_shutdown (void) -{ - multihash_iterate (& strpool_table, leak_cb, NULL); -} - -EXPORT unsigned str_hash (const char * str) -{ - if (! str) - return 0; - - StrNode * node = NODE_OF (str); - assert (node->magic == '@'); - - return node->hash; -} - - -EXPORT bool_t str_equal (const char * str1, const char * str2) -{ - assert (! str1 || NODE_OF (str1)->magic == '@'); - assert (! str2 || NODE_OF (str2)->magic == '@'); - - return str1 == str2; -} - -#endif /* ! VALGRIND_FRIENDLY */ - -EXPORT char * str_nget (const char * str, int len) -{ - if (memchr (str, 0, len)) - return str_get (str); - - SNCOPY (buf, str, len); - return str_get (buf); -} diff --git a/src/libaudcore/strpool.cc b/src/libaudcore/strpool.cc new file mode 100644 index 0000000..8f3679d --- /dev/null +++ b/src/libaudcore/strpool.cc @@ -0,0 +1,266 @@ +/* + * strpool.c + * Copyright 2011-2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <assert.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#include "audstrings.h" +#include "internal.h" +#include "multihash.h" +#include "objects.h" +#include "runtime.h" + +#ifdef VALGRIND_FRIENDLY + +struct StrNode { + unsigned hash; + char magic; + char str[]; +}; + +#define NODE_SIZE_FOR(s) (offsetof (StrNode, str) + strlen (s) + 1) +#define NODE_OF(s) ((StrNode *) ((s) - offsetof (StrNode, str))) + +EXPORT char * String::raw_get (const char * str) +{ + if (! str) + return nullptr; + + StrNode * node = (StrNode *) malloc (NODE_SIZE_FOR (str)); + if (! node) + throw std::bad_alloc (); + + node->magic = '@'; + node->hash = str_calc_hash (str); + + strcpy (node->str, str); + return node->str; +} + +EXPORT char * String::raw_ref (const char * str) +{ + if (! str) + return nullptr; + + StrNode * node = NODE_OF (str); + assert (node->magic == '@'); + assert (str_calc_hash (str) == node->hash); + + return raw_get (str); +} + +EXPORT void String::raw_unref (char * str) +{ + if (! str) + return; + + StrNode * node = NODE_OF (str); + assert (node->magic == '@'); + assert (str_calc_hash (str) == node->hash); + + node->magic = 0; + free (node); +} + +EXPORT unsigned String::raw_hash (const char * str) +{ + if (! str) + return 0; + + StrNode * node = NODE_OF (str); + assert (node->magic == '@'); + + return str_calc_hash (str); +} + +EXPORT bool String::raw_equal (const char * str1, const char * str2) +{ + assert (! str1 || NODE_OF (str1)->magic == '@'); + assert (! str2 || NODE_OF (str2)->magic == '@'); + + return ! strcmp_safe (str1, str2); +} + +EXPORT void string_leak_check () +{ +} + +#else /* ! VALGRIND_FRIENDLY */ + +struct StrNode { + MultiHash::Node base; + unsigned refs; + char magic; + char str[1]; // variable size +}; + +#define NODE_SIZE_FOR(s) (offsetof (StrNode, str) + strlen (s) + 1) +#define NODE_OF(s) ((StrNode *) ((s) - offsetof (StrNode, str))) + +static bool match_cb (const MultiHash::Node * node_, const void * data_) +{ + const StrNode * node = (const StrNode *) node_; + const char * data = (const char *) data_; + + return data == node->str || ! strcmp (data, node->str); +} + +static MultiHash strpool_table (match_cb); + +static MultiHash::Node * add_cb (const void * data_, void * state) +{ + const char * data = (const char *) data_; + + StrNode * node = (StrNode *) malloc (NODE_SIZE_FOR (data)); + if (! node) + throw std::bad_alloc (); + + node->refs = 1; + node->magic = '@'; + strcpy (node->str, data); + + * ((char * *) state) = node->str; + return (MultiHash::Node *) node; +} + +static bool ref_cb (MultiHash::Node * node_, void * state) +{ + StrNode * node = (StrNode *) node_; + + __sync_fetch_and_add (& node->refs, 1); + + * ((char * *) state) = node->str; + return false; +} + +/* If the pool contains a copy of <str>, increments its reference count. + * Otherwise, adds a copy of <str> to the pool with a reference count of one. + * In either case, returns the copy. Because this copy may be shared by other + * parts of the code, it should not be modified. If <str> is null, simply + * returns null with no side effects. */ +EXPORT char * String::raw_get (const char * str) +{ + if (! str) + return nullptr; + + char * ret = nullptr; + strpool_table.lookup (str, str_calc_hash (str), add_cb, ref_cb, & ret); + return ret; +} + +/* Increments the reference count of <str>, where <str> is the address of a + * string already in the pool. Faster than calling raw_get() a second time. + * Returns <str> for convenience. If <str> is null, simply returns null with no + * side effects. */ +EXPORT char * String::raw_ref (const char * str) +{ + if (! str) + return nullptr; + + StrNode * node = NODE_OF (str); + assert (node->magic == '@'); + + __sync_fetch_and_add (& node->refs, 1); + + return (char *) str; +} + +static bool remove_cb (MultiHash::Node * node_, void * state) +{ + StrNode * node = (StrNode *) node_; + + if (! __sync_bool_compare_and_swap (& node->refs, 1, 0)) + return false; + + node->magic = 0; + free (node); + return true; +} + +/* Decrements the reference count of <str>, where <str> is the address of a + * string in the pool. If the reference count drops to zero, releases the + * memory used by <str>. If <str> is null, simply returns null with no side + * effects. */ +EXPORT void String::raw_unref (char * str) +{ + if (! str) + return; + + StrNode * node = NODE_OF (str); + assert (node->magic == '@'); + + while (1) + { + int refs = __sync_fetch_and_add (& node->refs, 0); + + if (refs > 1) + { + if (__sync_bool_compare_and_swap (& node->refs, refs, refs - 1)) + break; + } + else + { + int status = strpool_table.lookup (node->str, node->base.hash, nullptr, + remove_cb, nullptr); + + assert (status & MultiHash::Found); + if (status & MultiHash::Removed) + break; + } + } +} + +static bool leak_cb (MultiHash::Node * node, void * state) +{ + AUDWARN ("String leaked: %s\n", ((StrNode *) node)->str); + return false; +} + +void string_leak_check () +{ + strpool_table.iterate (leak_cb, nullptr); +} + +/* Returns the cached hash value of a pooled string (or 0 for null). */ +EXPORT unsigned String::raw_hash (const char * str) +{ + if (! str) + return 0; + + StrNode * node = NODE_OF (str); + assert (node->magic == '@'); + + return node->base.hash; +} + + +/* Checks whether two pooled strings are equal. Since the pool never contains + * duplicate strings, this is a simple pointer comparison and thus much faster + * than strcmp(). null is considered equal to null but not equal to any string. */ +EXPORT bool String::raw_equal (const char * str1, const char * str2) +{ + assert (! str1 || NODE_OF (str1)->magic == '@'); + assert (! str2 || NODE_OF (str2)->magic == '@'); + + return str1 == str2; +} + +#endif /* ! VALGRIND_FRIENDLY */ diff --git a/src/libaudcore/templates.h b/src/libaudcore/templates.h new file mode 100644 index 0000000..1a78251 --- /dev/null +++ b/src/libaudcore/templates.h @@ -0,0 +1,279 @@ +/* + * templates.h + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_TEMPLATES_H +#define LIBAUDCORE_TEMPLATES_H + +#include <new> +#include <type_traits> +#include <utility> + +#ifdef _WIN32 +#undef min +#undef max +#endif + +namespace aud { + +// Utility functions +// ================= + +// minimum of two numbers +template<class T> +constexpr T min (T a, T b) + { return a < b ? a : b; } + +// maximum of two numbers +template<class T> +constexpr T max (T a, T b) + { return a > b ? a : b; } + +// make sure a number is within the given range +template<class T> +constexpr T clamp (T x, T low, T high) + { return min (max (x, low), high); } + +// absolute value +template<class T> +constexpr T abs (T x) + { return x < 0 ? -x : x; } + +// change the sign of x to the sign of s +template<class T> +constexpr T chsign (T x, T s) + { return (x < 0) ^ (s < 0) ? -x : x; } + +// integer division with rounding +template<class T> +constexpr T rdiv (T x, T y) + { return (x + chsign (y / 2, x)) / y; } + +// convert integer from one scale to another, with rounding +template<class T> +constexpr T rescale (T x, T old_scale, T new_scale) + { return rdiv (x * new_scale, old_scale); } + +// number of characters needed to represent an integer (including minus sign) +template<class T> +constexpr T n_digits (T x) + { return x < 0 ? 1 + n_digits (-x) : x < 10 ? 1 : 1 + n_digits (x / 10); } + +// number of elements in an array +template<class T, int N> +constexpr int n_elems (const T (&) [N]) + { return N; } + +// Casts for storing various data in a void pointer +// ================================================ + +template<class T> +inline void * to_ptr (T t) +{ + union { void * v; T t; } u = {nullptr}; + static_assert (sizeof u == sizeof (void *), "Type cannot be stored in a pointer"); + u.t = t; return u.v; +} + +template<class T> +inline T from_ptr (void * v) +{ + union { void * v; T t; } u = {v}; + static_assert (sizeof u == sizeof (void *), "Type cannot be stored in a pointer"); + return u.t; +} + +// Wrapper class allowing enumerations to be used as array indexes; +// the enumeration must begin with zero and have a "count" constant +// ================================================================ + +template<class T, class V> +struct array +{ + // cannot use std::forward here; it is not constexpr until C++14 + template<class ... Args> + constexpr array (Args && ... args) : + vals { static_cast<Args &&> (args) ...} {} + + constexpr const V & operator[] (T t) const + { return vals[(int) t]; } + constexpr const V * begin () const + { return vals; } + constexpr const V * end () const + { return vals + (int) T::count; } + V & operator[] (T t) + { return vals[(int) t]; } + V * begin () + { return vals; } + V * end () + { return vals + (int) T::count; } + +private: + V vals[(int) T::count]; +}; + +// Wrapper class allowing enumerations to be used in range-based for loops +// ======================================================================= + +template<class T, T first = (T) 0, T last = (T) ((int) T::count - 1)> +struct range +{ + struct iter { + T v; + constexpr T operator* () const + { return v; } + constexpr bool operator!= (iter other) const + { return v != other.v; } + void operator++ () + { v = (T) ((int) v + 1); } + }; + + static constexpr iter begin () + { return {first}; } + static constexpr iter end () + { return {(T) ((int) last + 1)}; } +}; + +// Replacement for std::allocator::construct, which also supports aggregate +// initialization. For background, see: +// http://cplusplus.github.io/LWG/lwg-active.html#2089 +// ======================================================================== + +// class constructor proxy +template<class T, bool aggregate> +struct construct_base { + template<class ... Args> + static T * make (void * loc, Args && ... args) + { return new (loc) T (std::forward<Args> (args) ...); } +}; + +// aggregate constructor proxy +template<class T> +struct construct_base<T, true> { + template<class ... Args> + static T * make (void * loc, Args && ... args) + { return new (loc) T {std::forward<Args> (args) ...}; } +}; + +// generic constructor proxy +template<class T> +struct construct { + template<class ... Args> + static T * make (void * loc, Args && ... args) + { + constexpr bool aggregate = ! std::is_constructible<T, Args && ...>::value; + return construct_base<T, aggregate>::make (loc, std::forward<Args> (args) ...); + } +}; + +// Convert an integer constant to a string at compile-time; can be used for +// #defines, enums, constexpr calculations, etc. +// ======================================================================== + +// "metaprogramming" string type: each different string is a unique type +template<char... args> +struct metastring { + char data[sizeof... (args) + 1] = {args..., '\0'}; +}; + +// recursive number-printing template, general case (three or more digits) +template<int size, int x, char... args> +struct numeric_builder { + typedef typename numeric_builder<size - 1, x / 10, '0' + abs (x) % 10, args...>::type type; +}; + +// special case for two digits; minus sign is handled here +template<int x, char... args> +struct numeric_builder<2, x, args...> { + typedef metastring<x < 0 ? '-' : '0' + x / 10, '0' + abs (x) % 10, args...> type; +}; + +// special case for one digit (positive numbers only) +template<int x, char... args> +struct numeric_builder<1, x, args...> { + typedef metastring<'0' + x, args...> type; +}; + +// convenience wrapper for numeric_builder +template<int x> +class numeric_string +{ +private: + // generate a unique string type representing this number + typedef typename numeric_builder<n_digits (x), x>::type type; + + // declare a static string of that type (instantiated later at file scope) + static constexpr type value {}; + +public: + // pointer to the instantiated string + static constexpr const char * str = value.data; +}; + +// instantiate numeric_string::value as needed for different numbers +template<int x> +constexpr typename numeric_string<x>::type numeric_string<x>::value; + +// Functions for creating/copying/destroying objects en masse; +// these will be nullptr for basic types (use memset/memcpy instead) +// ================================================================= + +typedef void (* FillFunc) (void * data, int len); +typedef void (* CopyFunc) (const void * from, void * to, int len); +typedef void (* EraseFunc) (void * data, int len); + +template<class T> +static constexpr FillFunc fill_func () +{ + return std::is_trivial<T>::value ? (FillFunc) nullptr : + [] (void * data, int len) { + T * iter = (T *) data; + T * end = (T *) ((char *) data + len); + while (iter < end) + new (iter ++) T (); + }; +} + +template<class T> +static constexpr CopyFunc copy_func () +{ + return std::is_trivial<T>::value ? (CopyFunc) nullptr : + [] (const void * from, void * to, int len) { + const T * src = (const T *) from; + T * dest = (T *) to; + T * end = (T *) ((char *) to + len); + while (dest < end) + new (dest ++) T (* src ++); + }; +} + +template<class T> +static constexpr EraseFunc erase_func () +{ + return std::is_trivial<T>::value ? (EraseFunc) nullptr : + [] (void * data, int len) { + T * iter = (T *) data; + T * end = (T *) ((char *) data + len); + while (iter < end) + (* iter ++).~T (); + }; +} + +} // namespace aud + +#endif // LIBAUDCORE_TEMPLATES_H diff --git a/src/libaudcore/tests/Makefile b/src/libaudcore/tests/Makefile new file mode 100644 index 0000000..cb56cb1 --- /dev/null +++ b/src/libaudcore/tests/Makefile @@ -0,0 +1,40 @@ +all: test test-mainloop + +SRCS = ../audstrings.cc \ + ../charset.cc \ + ../hook.cc \ + ../index.cc \ + ../logger.cc \ + ../multihash.cc \ + ../ringbuf.cc \ + ../stringbuf.cc \ + ../strpool.cc \ + ../tinylock.cc \ + ../tuple.cc \ + ../tuple-compiler.cc \ + test.cc + +FLAGS = -I.. -I../.. -DEXPORT= -DPACKAGE=\"audacious\" -DICONV_CONST= \ + $(shell pkg-config --cflags --libs glib-2.0) \ + -std=c++11 -Wall -g -O0 -fno-elide-constructors \ + -fprofile-arcs -ftest-coverage -pthread + +MAINLOOP_SRCS = ../mainloop.cc test-mainloop.cc + +test: ${SRCS} + g++ ${SRCS} ${FLAGS} -o test + +test-mainloop: ${MAINLOOP_SRCS} + g++ ${MAINLOOP_SRCS} ${FLAGS} -DUSE_QT -fPIC \ + $(shell pkg-config --cflags --libs Qt5Core) \ + -o test-mainloop + +cov: all + rm -f *.gcda + ./test + ./test-mainloop + ./test-mainloop --qt + gcov --object-directory . ${SRCS} ${MAINLOOP_SRCS} + +clean: + rm -f test test-mainloop test-mainloop *.gcno *.gcda *.gcov diff --git a/src/libaudcore/tests/test-mainloop.cc b/src/libaudcore/tests/test-mainloop.cc new file mode 100644 index 0000000..577fbad --- /dev/null +++ b/src/libaudcore/tests/test-mainloop.cc @@ -0,0 +1,118 @@ +/* + * test-mainloop.cc - Main loop test for libaudcore + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "mainloop.h" +#include "runtime.h" + +#include <assert.h> +#include <pthread.h> +#include <stdio.h> +#include <string.h> + +static bool use_qt = false; + +MainloopType aud_get_mainloop_type () +{ + return use_qt ? MainloopType::Qt : MainloopType::GLib; +} + +static QueuedFunc counters[70]; +static QueuedFunc fast, slow; + +static int count; +static pthread_t main_thread; + +static void count_up (void * data) +{ + assert (pthread_self () == main_thread); + assert (count == (int) (size_t) data); + + if (! (count % 10)) + printf ("UP: "); + + count ++; + + printf ("%d%c", count, (count % 10) ? ' ' : '\n'); +} + +static void count_down (void * data) +{ + assert (pthread_self () == main_thread); + assert (data == & count); + + count -= 10; + + printf ("DOWN: %d\n", count); + + if (! count) + { + fast.stop (); + slow.stop (); + mainloop_quit (); + } +} + +static void check_count (void * data) +{ + assert (pthread_self () == main_thread); + assert (count == (int) (size_t) data); + + printf ("CHECK: %d\n", count); +} + +static void * worker (void * data) +{ + for (int i = 50; i < 70; i ++) + counters[i].queue (count_up, (void *) (size_t) (i - 10)); + + slow.start (350, check_count, (void *) (size_t) 30); + + return nullptr; +} + +int main (int argc, const char * * argv) +{ + if (argc >= 2 && ! strcmp (argv[1], "--qt")) + use_qt = true; + + main_thread = pthread_self (); + + for (int j = 0; j < 2; j ++) + { + for (int i = 0; i < 50; i ++) + counters[i].queue (count_up, (void *) (size_t) (i - 30)); + + for (int i = 10; i < 30; i ++) + counters[i].stop (); + + for (int i = 0; i < 20; i ++) + counters[i].queue (count_up, (void *) (size_t) (20 + i)); + + fast.start (100, count_down, & count); + + pthread_t thread; + pthread_create (& thread, nullptr, worker, nullptr); + + mainloop_run (); + + pthread_join (thread, nullptr); + } + + return 0; +} diff --git a/src/libaudcore/tests/test.cc b/src/libaudcore/tests/test.cc new file mode 100644 index 0000000..7e902ca --- /dev/null +++ b/src/libaudcore/tests/test.cc @@ -0,0 +1,305 @@ +/* + * test.cc - Various tests for libaudcore + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "audstrings.h" +#include "internal.h" +#include "ringbuf.h" +#include "tuple.h" +#include "tuple-compiler.h" +#include "vfs.h" + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* stubs */ +bool aud_get_bool (const char *, const char *) + { return false; } +String aud_get_str (const char *, const char *) + { return String (""); } +String VFSFile::get_metadata (const char *) + { return String (); } +const char * get_home_utf8 () + { return "/home/user"; } + +size_t misc_bytes_allocated; + +static void test_tuple_format (const char * format, Tuple & tuple, const char * expected) +{ + TupleCompiler compiler; + compiler.compile (format); + compiler.format (tuple); + + String result = tuple.get_str (Tuple::FormattedTitle); + if (strcmp (result, expected)) + { + printf ("For format [%s]\n", format); + printf ("Expected [%s]\n", expected); + printf ("Got [%s]\n", (const char *) result); + exit (1); + } +} + +static void test_tuple_formats () +{ + Tuple tuple; + + /* fallback tests */ + test_tuple_format ("", tuple, ""); + tuple.set_filename ("http://Path%20To/File%20Name"); + test_tuple_format ("", tuple, "File Name"); + tuple.set_str (Tuple::Title, "Song Title"); + test_tuple_format ("", tuple, "Song Title"); + + /* basic variable tests */ + test_tuple_format ("$", tuple, "Song Title"); + test_tuple_format ("${", tuple, "Song Title"); + test_tuple_format ("${file-name", tuple, "Song Title"); + test_tuple_format ("${file-name}", tuple, "File Name"); + test_tuple_format ("${file-name}}", tuple, "Song Title"); + test_tuple_format ("${invalid}", tuple, "Song Title"); + test_tuple_format ("${}", tuple, "Song Title"); + test_tuple_format ("\\$\\{\\}", tuple, "${}"); + test_tuple_format ("\\\0" "a", tuple, "Song Title"); + test_tuple_format ("{}", tuple, "Song Title"); + + /* integer variable tests */ + test_tuple_format ("${year}", tuple, "Song Title"); + tuple.set_int (Tuple::Year, -1); + test_tuple_format ("${year}", tuple, "-1"); + tuple.set_int (Tuple::Year, 0); + test_tuple_format ("${year}", tuple, "0"); + tuple.set_int (Tuple::Year, 1990); + test_tuple_format ("${year}", tuple, "1990"); + + /* filename variable tests */ + test_tuple_format ("${file-path}", tuple, "http://Path To/"); + test_tuple_format ("${file-ext}", tuple, "Song Title"); + tuple.set_filename ("http://Path%20To/File%20Name.Ext?3"); + test_tuple_format ("${file-name}", tuple, "File Name"); + test_tuple_format ("${file-ext}", tuple, "Ext"); + test_tuple_format ("${subsong-id}", tuple, "3"); + + /* existence tests */ + test_tuple_format ("x${?invalid:Field Exists}", tuple, "Song Title"); + test_tuple_format ("x${?subsong-id:Field Exists", tuple, "Song Title"); + test_tuple_format ("x${?subsong-id:Field Exists}", tuple, "xField Exists"); + test_tuple_format ("x${?subsong-id:${invalid}}", tuple, "Song Title"); + test_tuple_format ("x${?subsong-id:(${subsong-id})}", tuple, "x(3)"); + test_tuple_format ("x${?track-number:Field Exists}", tuple, "x"); + test_tuple_format ("x${?title:Field Exists}", tuple, "xField Exists"); + test_tuple_format ("x${?artist:Field Exists}", tuple, "x"); + test_tuple_format ("x${?artist}", tuple, "Song Title"); + + /* equality tests */ + test_tuple_format ("x${=}", tuple, "Song Title"); + test_tuple_format ("x${==}", tuple, "Song Title"); + test_tuple_format ("x${==a,}", tuple, "Song Title"); + test_tuple_format ("x${==a,a:}", tuple, "Song Title"); + test_tuple_format ("x${==\"a\",a:}", tuple, "Song Title"); + test_tuple_format ("x${==\"a\",\"a:Equal}", tuple, "Song Title"); + test_tuple_format ("x${==\"a\",\"a\":Equal}", tuple, "xEqual"); + test_tuple_format ("x${==\"a\",\"a\"\":Equal}", tuple, "Song Title"); + test_tuple_format ("x${==\"a\",\"b\":Equal}", tuple, "x"); + test_tuple_format ("x${==year,\"a\":Equal}", tuple, "x"); + test_tuple_format ("x${==\"a\",year:Equal}", tuple, "x"); + test_tuple_format ("x${==year,1990:Equal}", tuple, "xEqual"); + test_tuple_format ("x${==1990,year:Equal}", tuple, "xEqual"); + test_tuple_format ("x${==title,\"a\":Equal}", tuple, "x"); + test_tuple_format ("x${==\"a\",title:Equal}", tuple, "x"); + test_tuple_format ("x${==title,\"Song Title\":Equal}", tuple, "xEqual"); + test_tuple_format ("x${==\"Song Title\",title:Equal}", tuple, "xEqual"); + tuple.set_str (Tuple::Artist, "{}"); + test_tuple_format ("x${==artist,\"\\{\\}\":Equal}", tuple, "xEqual"); + + /* inequality tests */ + test_tuple_format ("x${!}", tuple, "Song Title"); + test_tuple_format ("x${!=}", tuple, "Song Title"); + test_tuple_format ("x${!=\"a\",\"a\":Unequal}", tuple, "x"); + test_tuple_format ("x${!=\"a\",\"b\":Unequal}", tuple, "xUnequal"); + test_tuple_format ("x${!=year,\"a\":Unequal}", tuple, "xUnequal"); + test_tuple_format ("x${!=\"a\",year:Unequal}", tuple, "xUnequal"); + test_tuple_format ("x${!=year,1990:Unequal}", tuple, "x"); + test_tuple_format ("x${!=1990,year:Unequal}", tuple, "x"); + test_tuple_format ("x${>}", tuple, "Song Title"); + test_tuple_format ("x${>year,1989:Greater}", tuple, "xGreater"); + test_tuple_format ("x${>year,1990:Greater}", tuple, "x"); + test_tuple_format ("x${>=year,1990:NotLess}", tuple, "xNotLess"); + test_tuple_format ("x${>=year,1991:NotLess}", tuple, "x"); + test_tuple_format ("x${<}", tuple, "Song Title"); + test_tuple_format ("x${<year,1991:Less}", tuple, "xLess"); + test_tuple_format ("x${<year,1990:Less}", tuple, "x"); + test_tuple_format ("x${<=year,1990:NotGreater}", tuple, "xNotGreater"); + test_tuple_format ("x${<=year,1989:NotGreater}", tuple, "x"); + + /* emptiness tests */ + tuple.set_int (Tuple::Year, 0); + tuple.set_str (Tuple::Artist, ""); + test_tuple_format ("x${(invalid)}", tuple, "Song Title"); + test_tuple_format ("x${(empty)?invalid:Empty}", tuple, "Song Title"); + test_tuple_format ("x${(empty)?subsong-id:Empty}", tuple, "x"); + test_tuple_format ("x${(empty)?subsong-id:${invalid}}", tuple, "Song Title"); + test_tuple_format ("x${(empty)?year:Empty}", tuple, "x"); + test_tuple_format ("x${(empty)?track-number:Empty}", tuple, "xEmpty"); + test_tuple_format ("x${(empty)?title:Empty}", tuple, "x"); + test_tuple_format ("x${(empty)?artist:Empty}", tuple, "x"); + test_tuple_format ("x${(empty)?album:Empty}", tuple, "xEmpty"); + test_tuple_format ("x${(empty)?\"Literal\":Empty}", tuple, "Song Title"); +} + +static void test_ringbuf () +{ + String nums[10]; + for (int i = 0; i < 10; i ++) + nums[i] = String (int_to_str (i)); + + RingBuf<String> ring; + + ring.alloc (7); + + for (int i = 0; i < 7; i ++) + assert (ring.push (nums[i]) == nums[i]); + + for (int i = 0; i < 5; i ++) + { + assert (ring.head () == nums[i]); + ring.pop (); + } + + for (int i = 7; i < 10; i ++) + assert (ring.push (nums[i]) == nums[i]); + + assert (ring.size () == 7); + assert (ring.len () == 5); + assert (ring.linear () == 2); + assert (ring.space () == 2); + + ring.alloc (5); + + for (int i = 0; i < 5; i ++) + assert (ring[i] == nums[5 + i]); + + assert (ring.size () == 5); + assert (ring.len () == 5); + assert (ring.linear () == 2); + assert (ring.space () == 0); + + ring.alloc (10); + + for (int i = 0; i < 5; i ++) + assert (ring[i] == nums[5 + i]); + + assert (ring.size () == 10); + assert (ring.len () == 5); + assert (ring.linear () == 2); + assert (ring.space () == 5); + + for (int i = 0; i < 5; i ++) + assert (ring[i] == nums[5 + i]); + + for (int i = 5; i --; ) + assert (ring.push (nums[i]) == nums[i]); + + for (int i = 0; i < 5; i ++) + { + assert (ring.head () == nums[5 + i]); + ring.pop (); + } + + for (int i = 0; i < 5; i ++) + { + assert (ring.head () == nums[4 - i]); + ring.pop (); + } + + ring.copy_in (& nums[5], 5); + ring.copy_in (& nums[0], 5); + + for (int i = 0; i < 5; i ++) + { + assert (ring.head () == nums[5 + i]); + ring.pop (); + } + + for (int i = 0; i < 5; i ++) + { + assert (ring.head () == nums[i]); + ring.pop (); + } + + ring.move_in (nums, 10); + + for (int i = 0; i < 10; i ++) + { + assert (! nums[i]); + assert (ring[i] == String (int_to_str (i))); + } + + ring.move_out (& nums[5], 5); + ring.move_out (& nums[0], 5); + + for (int i = 0; i < 10; i ++) + assert (nums[i] == String (int_to_str ((5 + i) % 10))); + + ring.move_in (nums, 10); + + Index<String> index; + ring.move_out (index, -1, 5); + + assert (ring.len () == 5); + assert (index.len () == 5); + + ring.move_out (index, 0, -1); + + assert (ring.len () == 0); + assert (index.len () == 10); + + for (int i = 0; i < 10; i ++) + assert (index[i] == String (int_to_str (i))); + + ring.move_in (index, 5, 5); + + assert (ring.len () == 5); + assert (index.len () == 5); + + ring.move_in (index, 0, -1); + + assert (ring.len () == 10); + assert (index.len () == 0); + + for (int i = 0; i < 10; i ++) + assert (ring[i] == String (int_to_str ((5 + i) % 10))); + + ring.discard (5); + assert (ring.len () == 5); + + ring.discard (); + assert (ring.len () == 0); + + string_leak_check (); +} + +int main () +{ + test_tuple_formats (); + test_ringbuf (); + + return 0; +} diff --git a/src/libaudcore/tinylock.c b/src/libaudcore/tinylock.cc index 873aff1..873aff1 100644 --- a/src/libaudcore/tinylock.c +++ b/src/libaudcore/tinylock.cc diff --git a/src/libaudcore/tuple-compiler.cc b/src/libaudcore/tuple-compiler.cc new file mode 100644 index 0000000..dc68920 --- /dev/null +++ b/src/libaudcore/tuple-compiler.cc @@ -0,0 +1,549 @@ +/* + * tuple_compiler.c + * Copyright (c) 2007 Matti 'ccr' HĂ€mĂ€lĂ€inen + * Copyright (c) 2011-2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <stdlib.h> +#include <string.h> + +#include <new> +#include <glib.h> + +#include "audstrings.h" +#include "runtime.h" +#include "tuple-compiler.h" + +struct Variable +{ + enum { + Invalid = 0, + Text, + Integer, + Field + } type; + + String text; + int integer; + Tuple::Field field; + + bool set (const char * name, bool literal); + bool exists (const Tuple & tuple) const; + Tuple::ValueType get (const Tuple & tuple, String & tmps, int & tmpi) const; +}; + +enum class Op { + Invalid = 0, + Var, + Exists, + Equal, + Unequal, + Greater, + GreaterEqual, + Less, + LessEqual, + Empty +}; + +struct TupleCompiler::Node { + Op op; + Variable var1, var2; + Index<Node> children; +}; + +typedef TupleCompiler::Node Node; + +bool Variable::set (const char * name, bool literal) +{ + if (g_ascii_isdigit (name[0])) + { + type = Integer; + integer = atoi (name); + } + else if (literal) + { + type = Text; + text = String (name); + } + else + { + type = Field; + field = Tuple::field_by_name (name); + + if (field < 0) + { + AUDWARN ("Invalid variable '%s'.\n", name); + return false; + } + } + + return true; +} + +bool Variable::exists (const Tuple & tuple) const +{ + g_return_val_if_fail (type == Field, false); + return tuple.get_value_type (field) != Tuple::Empty; +} + +Tuple::ValueType Variable::get (const Tuple & tuple, String & tmps, int & tmpi) const +{ + switch (type) + { + case Text: + tmps = text; + return Tuple::String; + + case Integer: + tmpi = integer; + return Tuple::Int; + + case Field: + switch (tuple.get_value_type (field)) + { + case Tuple::String: + tmps = tuple.get_str (field); + return Tuple::String; + + case Tuple::Int: + tmpi = tuple.get_int (field); + return Tuple::Int; + + default: + return Tuple::Empty; + } + + default: + g_return_val_if_reached (Tuple::Empty); + } +} + +TupleCompiler::TupleCompiler () {} +TupleCompiler::~TupleCompiler () {} + +static StringBuf get_item (const char * & str, char endch, bool & literal) +{ + const char * s = str; + + StringBuf buf (-1); + char * set = buf; + char * stop = buf + buf.len (); + + if (* s == '"') + { + if (! literal) + { + buf.steal (StringBuf ()); + AUDWARN ("Unexpected string literal at '%s'.\n", s); + return StringBuf (); + } + + s ++; + } + else + literal = false; + + if (literal) + { + while (* s != '"') + { + if (* s == '\\') + s ++; + + if (! * s) + { + buf.steal (StringBuf ()); + AUDWARN ("Unterminated string literal.\n"); + return StringBuf (); + } + + if (set == stop) + throw std::bad_alloc (); + + * set ++ = * s ++; + } + + s ++; + } + else + { + while (g_ascii_isalnum (* s) || * s == '-') + { + if (set == stop) + throw std::bad_alloc (); + + * set ++ = * s ++; + } + } + + if (* s != endch) + { + buf.steal (StringBuf ()); + AUDWARN ("Expected '%c' at '%s'.\n", endch, s); + return StringBuf (); + } + + str = s + 1; + + buf.resize (set - buf); + return buf; +} + +static bool compile_expression (Index<Node> & nodes, const char * & expression); + +static bool parse_construct (Node & node, const char * & c) +{ + bool literal1 = true, literal2 = true; + + StringBuf tmps1 = get_item (c, ',', literal1); + if (! tmps1) + return false; + + StringBuf tmps2 = get_item (c, ':', literal2); + if (! tmps2) + return false; + + if (! node.var1.set (tmps1, literal1)) + return false; + + if (! node.var2.set (tmps2, literal2)) + return false; + + return compile_expression (node.children, c); +} + +/* Compile format expression into Node tree. */ +static bool compile_expression (Index<Node> & nodes, const char * & expression) +{ + const char * c = expression; + + while (* c && * c != '}') + { + Node & node = nodes.append (); + + if (* c == '$') + { + /* Expression? */ + if (c[1] != '{') + { + AUDWARN ("Expected '${' at '%s'.\n", c); + return false; + } + + c += 2; + + switch (* c) + { + case '?': + case '(': + { + if (* c == '?') + { + node.op = Op::Exists; + c ++; + } + else + { + if (strncmp (c, "(empty)?", 8)) + { + AUDWARN ("Expected '(empty)?' at '%s'.\n", c); + return false; + } + + node.op = Op::Empty; + c += 8; + } + + bool literal = false; + StringBuf tmps = get_item (c, ':', literal); + if (! tmps) + return false; + + if (! node.var1.set (tmps, false)) + return false; + + if (! compile_expression (node.children, c)) + return false; + + break; + } + + case '=': + case '!': + node.op = (* c == '=') ? Op::Equal : Op::Unequal; + + if (c[1] != '=') + { + AUDWARN ("Expected '%c=' at '%s'.\n", c[0], c); + return false; + } + + c += 2; + + if (! parse_construct (node, c)) + return false; + + break; + + case '<': + case '>': + if (c[1] == '=') + { + node.op = (* c == '<') ? Op::LessEqual : Op::GreaterEqual; + c += 2; + } + else + { + node.op = (* c == '<') ? Op::Less : Op::Greater; + c ++; + } + + if (! parse_construct (node, c)) + return false; + + break; + + default: + { + bool literal = false; + StringBuf tmps = get_item (c, '}', literal); + if (! tmps) + return false; + + c --; + + node.op = Op::Var; + + if (! node.var1.set (tmps, false)) + return false; + } + } + + if (* c != '}') + { + AUDWARN ("Expected '}' at '%s'.\n", c); + return false; + } + + c ++; + } + else if (* c == '{') + { + AUDWARN ("Unexpected '%c' at '%s'.\n", * c, c); + return false; + } + else + { + /* Parse raw/literal text */ + StringBuf buf (-1); + char * set = buf; + char * stop = buf + buf.len (); + + while (* c && * c != '$' && * c != '{' && * c != '}') + { + if (* c == '\\') + { + c ++; + + if (! * c) + { + buf.steal (StringBuf ()); + AUDWARN ("Incomplete escaped character.\n"); + return false; + } + } + + if (set == stop) + throw std::bad_alloc (); + + * set ++ = * c ++; + } + + buf.resize (set - buf); + + node.op = Op::Var; + node.var1.type = Variable::Text; + node.var1.text = String (buf); + } + } + + expression = c; + return true; +} + +bool TupleCompiler::compile (const char * expr) +{ + const char * c = expr; + Index<Node> nodes; + + if (! compile_expression (nodes, c)) + return false; + + if (* c) + { + AUDWARN ("Unexpected '%c' at '%s'.\n", * c, c); + return false; + } + + root_nodes = std::move (nodes); + return true; +} + +/* Evaluate tuple in given TupleEval expression in given + * context and return resulting string. */ +static void eval_expression (const Index<Node> & nodes, const Tuple & tuple, StringBuf & out) +{ + for (const Node & node : nodes) + { + switch (node.op) + { + case Op::Var: + { + String tmps; + int tmpi; + + switch (node.var1.get (tuple, tmps, tmpi)) + { + case Tuple::String: + out.insert (-1, tmps); + break; + + case Tuple::Int: + out.combine (int_to_str (tmpi)); + break; + + default: + break; + } + + break; + } + + case Op::Equal: + case Op::Unequal: + case Op::Less: + case Op::LessEqual: + case Op::Greater: + case Op::GreaterEqual: + { + bool result = false; + String tmps1, tmps2; + int tmpi1 = 0, tmpi2 = 0; + + Tuple::ValueType type1 = node.var1.get (tuple, tmps1, tmpi1); + Tuple::ValueType type2 = node.var2.get (tuple, tmps2, tmpi2); + + if (type1 != Tuple::Empty && type2 != Tuple::Empty) + { + int resulti; + + if (type1 == type2) + { + if (type1 == Tuple::String) + resulti = strcmp (tmps1, tmps2); + else + resulti = tmpi1 - tmpi2; + } + else + { + if (type1 == Tuple::Int) + resulti = tmpi1 - atoi (tmps2); + else + resulti = atoi (tmps1) - tmpi2; + } + + switch (node.op) + { + case Op::Equal: + result = (resulti == 0); + break; + + case Op::Unequal: + result = (resulti != 0); + break; + + case Op::Less: + result = (resulti < 0); + break; + + case Op::LessEqual: + result = (resulti <= 0); + break; + + case Op::Greater: + result = (resulti > 0); + break; + + case Op::GreaterEqual: + result = (resulti >= 0); + break; + + default: + g_warn_if_reached (); + } + } + + if (result) + eval_expression (node.children, tuple, out); + + break; + } + + case Op::Exists: + if (node.var1.exists (tuple)) + eval_expression (node.children, tuple, out); + + break; + + case Op::Empty: + if (! node.var1.exists (tuple)) + eval_expression (node.children, tuple, out); + + break; + + default: + g_warn_if_reached (); + } + } +} + +void TupleCompiler::format (Tuple & tuple) const +{ + tuple.unset (Tuple::FormattedTitle); // prevent recursion + + StringBuf buf (0); + eval_expression (root_nodes, tuple, buf); + + if (buf[0]) + { + tuple.set_str (Tuple::FormattedTitle, buf); + return; + } + + /* formatting failed, try fallbacks */ + for (Tuple::Field fallback : {Tuple::Title, Tuple::Basename}) + { + String title = tuple.get_str (fallback); + if (title) + { + tuple.set_str (Tuple::FormattedTitle, title); + return; + } + } + + tuple.set_str (Tuple::FormattedTitle, ""); +} diff --git a/src/libaudcore/tuple_compiler.h b/src/libaudcore/tuple-compiler.h index 1a951c1..d5297c3 100644 --- a/src/libaudcore/tuple_compiler.h +++ b/src/libaudcore/tuple-compiler.h @@ -1,6 +1,7 @@ /* * tuple_compiler.h * Copyright (c) 2007 Matti 'ccr' HĂ€mĂ€lĂ€inen + * Copyright (c) 2014 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -17,23 +18,44 @@ * the use of this software. */ +/* + * the tuple formatter: + * + * this is a data-driven meta-language. + * + * language constructs follow the following basic rules: + * - begin with ${ + * - end with } + * + * language constructs: + * - ${field}: prints a field + * - ${?field:expr}: evaluates expr if field exists + * - ${==field,field:expr}: evaluates expr if both fields are the same + * - ${!=field,field:expr}: evaluates expr if both fields are not the same + * - ${(empty)?field:expr}: evaluates expr if field does not exist + * + * everything else is treated as raw text. + */ + #ifndef LIBAUDCORE_TUPLE_COMPILER_H #define LIBAUDCORE_TUPLE_COMPILER_H -#include <glib.h> +#include <libaudcore/index.h> #include <libaudcore/tuple.h> -typedef GArray TupleEvalContext; -typedef struct _TupleEvalNode TupleEvalNode; +class TupleCompiler +{ +public: + struct Node; -TupleEvalContext * tuple_evalctx_new(void); -void tuple_evalctx_reset(TupleEvalContext *ctx); -void tuple_evalctx_free(TupleEvalContext *ctx); + TupleCompiler (); + ~TupleCompiler (); -void tuple_evalnode_free(TupleEvalNode *expr); + bool compile (const char * expr); + void format (Tuple & tuple) const; -TupleEvalNode *tuple_formatter_compile(TupleEvalContext *ctx, const char *expr); -void tuple_formatter_eval (TupleEvalContext * ctx, TupleEvalNode * expr, - const Tuple * tuple, GString * out); +private: + Index<Node> root_nodes; +}; #endif /* LIBAUDCORE_TUPLE_COMPILER_H */ diff --git a/src/libaudcore/tuple.c b/src/libaudcore/tuple.c deleted file mode 100644 index 7974c8b..0000000 --- a/src/libaudcore/tuple.c +++ /dev/null @@ -1,496 +0,0 @@ -/* - * tuple.c - * Copyright 2007-2013 William Pitcock, Christian Birchinger, Matti HĂ€mĂ€lĂ€inen, - * Giacomo Lozito, Eugene Zagidullin, and John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <stdio.h> -#include <stdint.h> -#include <stdlib.h> -#include <string.h> - -#include <audacious/i18n.h> - -#include "audstrings.h" -#include "tinylock.h" -#include "tuple.h" - -#if TUPLE_FIELDS > 64 -#error The current tuple implementation is limited to 64 fields -#endif - -#define BLOCK_VALS 4 - -typedef struct { - char *name; - TupleValueType type; -} TupleBasicType; - -typedef union { - char * str; - int x; -} TupleVal; - -typedef struct _TupleBlock TupleBlock; - -struct _TupleBlock { - TupleBlock * next; - char fields[BLOCK_VALS]; - TupleVal vals[BLOCK_VALS]; -}; - -/** - * Structure for holding and passing around miscellaneous track - * metadata. This is not the same as a playlist entry, though. - */ -struct _Tuple { - int64_t setmask; - TupleBlock * blocks; - - int *subtunes; /**< Array of int containing subtune index numbers. - Can be NULL if indexing is linear or if - there are no subtunes. */ - int nsubtunes; /**< Number of subtunes, if any. Values greater than 0 - mean that there are subtunes and #subtunes array - may be set. */ - - int refcount; - TinyLock lock; -}; - -#define BIT(i) ((int64_t) 1 << (i)) - -#define LOCK(t) tiny_lock ((TinyLock *) & t->lock) -#define UNLOCK(t) tiny_unlock ((TinyLock *) & t->lock) - -/** Ordered table of basic #Tuple field names and their #TupleValueType. - */ -static const TupleBasicType tuple_fields[TUPLE_FIELDS] = { - { "artist", TUPLE_STRING }, - { "title", TUPLE_STRING }, - { "album", TUPLE_STRING }, - { "comment", TUPLE_STRING }, - { "genre", TUPLE_STRING }, - - { "track-number", TUPLE_INT }, - { "length", TUPLE_INT }, - { "year", TUPLE_INT }, - { "quality", TUPLE_STRING }, - - { "codec", TUPLE_STRING }, - { "file-name", TUPLE_STRING }, - { "file-path", TUPLE_STRING }, - { "file-ext", TUPLE_STRING }, - - { "song-artist", TUPLE_STRING }, - { "composer", TUPLE_STRING }, - { "performer", TUPLE_STRING }, - { "copyright", TUPLE_STRING }, - { "date", TUPLE_STRING }, - - { "subsong-id", TUPLE_INT }, - { "subsong-num", TUPLE_INT }, - { "mime-type", TUPLE_STRING }, - { "bitrate", TUPLE_INT }, - - { "segment-start", TUPLE_INT }, - { "segment-end", TUPLE_INT }, - - { "gain-album-gain", TUPLE_INT }, - { "gain-album-peak", TUPLE_INT }, - { "gain-track-gain", TUPLE_INT }, - { "gain-track-peak", TUPLE_INT }, - { "gain-gain-unit", TUPLE_INT }, - { "gain-peak-unit", TUPLE_INT }, -}; - -typedef struct { - const char * name; - int field; -} FieldDictEntry; - -/* used for binary search, MUST be in alphabetical order */ -static const FieldDictEntry field_dict[TUPLE_FIELDS] = { - {"album", FIELD_ALBUM}, - {"artist", FIELD_ARTIST}, - {"bitrate", FIELD_BITRATE}, - {"codec", FIELD_CODEC}, - {"comment", FIELD_COMMENT}, - {"composer", FIELD_COMPOSER}, - {"copyright", FIELD_COPYRIGHT}, - {"date", FIELD_DATE}, - {"file-ext", FIELD_FILE_EXT}, - {"file-name", FIELD_FILE_NAME}, - {"file-path", FIELD_FILE_PATH}, - {"gain-album-gain", FIELD_GAIN_ALBUM_GAIN}, - {"gain-album-peak", FIELD_GAIN_ALBUM_PEAK}, - {"gain-gain-unit", FIELD_GAIN_GAIN_UNIT}, - {"gain-peak-unit", FIELD_GAIN_PEAK_UNIT}, - {"gain-track-gain", FIELD_GAIN_TRACK_GAIN}, - {"gain-track-peak", FIELD_GAIN_TRACK_PEAK}, - {"genre", FIELD_GENRE}, - {"length", FIELD_LENGTH}, - {"mime-type", FIELD_MIMETYPE}, - {"performer", FIELD_PERFORMER}, - {"quality", FIELD_QUALITY}, - {"segment-end", FIELD_SEGMENT_END}, - {"segment-start", FIELD_SEGMENT_START}, - {"song-artist", FIELD_SONG_ARTIST}, - {"subsong-id", FIELD_SUBSONG_ID}, - {"subsong-num", FIELD_SUBSONG_NUM}, - {"title", FIELD_TITLE}, - {"track-number", FIELD_TRACK_NUMBER}, - {"year", FIELD_YEAR}}; - -#define VALID_FIELD(f) ((f) >= 0 && (f) < TUPLE_FIELDS) -#define FIELD_TYPE(f) (tuple_fields[f].type) - -static int field_dict_compare (const void * a, const void * b) -{ - return strcmp (((FieldDictEntry *) a)->name, ((FieldDictEntry *) b)->name); -} - -EXPORT int tuple_field_by_name (const char * name) -{ - FieldDictEntry find = {name, -1}; - FieldDictEntry * found = bsearch (& find, field_dict, TUPLE_FIELDS, - sizeof (FieldDictEntry), field_dict_compare); - - return found ? found->field : -1; -} - -EXPORT const char * tuple_field_get_name (int field) -{ - g_return_val_if_fail (VALID_FIELD (field), NULL); - return tuple_fields[field].name; -} - -EXPORT TupleValueType tuple_field_get_type (int field) -{ - g_return_val_if_fail (VALID_FIELD (field), TUPLE_UNKNOWN); - return tuple_fields[field].type; -} - -static TupleVal * lookup_val (Tuple * tuple, int field, bool_t add, bool_t remove) -{ - if ((tuple->setmask & BIT (field))) - { - for (TupleBlock * block = tuple->blocks; block; block = block->next) - { - for (int i = 0; i < BLOCK_VALS; i ++) - { - if (block->fields[i] == field) - { - if (remove) - { - tuple->setmask &= ~BIT (field); - block->fields[i] = -1; - } - - return & block->vals[i]; - } - } - } - } - - if (! add) - return NULL; - - tuple->setmask |= BIT (field); - - for (TupleBlock * block = tuple->blocks; block; block = block->next) - { - for (int i = 0; i < BLOCK_VALS; i ++) - { - if (block->fields[i] < 0) - { - block->fields[i] = field; - return & block->vals[i]; - } - } - } - - TupleBlock * block = g_slice_new0 (TupleBlock); - memset (block->fields, -1, BLOCK_VALS); - - block->next = tuple->blocks; - tuple->blocks = block; - - block->fields[0] = field; - return & block->vals[0]; -} - -static void tuple_destroy (Tuple * tuple) -{ - TupleBlock * next; - for (TupleBlock * block = tuple->blocks; block; block = next) - { - next = block->next; - - for (int i = 0; i < BLOCK_VALS; i ++) - { - int field = block->fields[i]; - if (field >= 0 && tuple_fields[field].type == TUPLE_STRING) - str_unref (block->vals[i].str); - } - - g_slice_free (TupleBlock, block); - } - - g_free (tuple->subtunes); - g_slice_free (Tuple, tuple); -} - -EXPORT Tuple * tuple_new (void) -{ - Tuple * tuple = g_slice_new0 (Tuple); - tuple->refcount = 1; - return tuple; -} - -EXPORT Tuple * tuple_ref (Tuple * tuple) -{ - __sync_fetch_and_add (& tuple->refcount, 1); - - return tuple; -} - -EXPORT void tuple_unref (Tuple * tuple) -{ - if (! tuple) - return; - - if (! __sync_sub_and_fetch (& tuple->refcount, 1)) - tuple_destroy (tuple); -} - -EXPORT void tuple_set_filename (Tuple * tuple, const char * filename) -{ - const char * base, * ext, * sub; - int isub; - - uri_parse (filename, & base, & ext, & sub, & isub); - - char path[base - filename + 1]; - str_decode_percent (filename, base - filename, path); - tuple_set_str (tuple, FIELD_FILE_PATH, path); - - char name[ext - base + 1]; - str_decode_percent (base, ext - base, name); - tuple_set_str (tuple, FIELD_FILE_NAME, name); - - if (ext < sub) - { - char extbuf[sub - ext]; - str_decode_percent (ext + 1, sub - ext - 1, extbuf); - tuple_set_str (tuple, FIELD_FILE_EXT, extbuf); - } - - if (sub[0]) - tuple_set_int (tuple, FIELD_SUBSONG_ID, isub); -} - -EXPORT Tuple * tuple_copy (const Tuple * old) -{ - Tuple * new = tuple_new (); - LOCK (old); - - for (int f = 0; f < TUPLE_FIELDS; f ++) - { - TupleVal * oldval = lookup_val ((Tuple *) old, f, FALSE, FALSE); - if (oldval) - { - TupleVal * newval = lookup_val (new, f, TRUE, FALSE); - if (tuple_fields[f].type == TUPLE_STRING) - newval->str = str_ref (oldval->str); - else - newval->x = oldval->x; - } - } - - new->nsubtunes = old->nsubtunes; - - if (old->subtunes) - new->subtunes = g_memdup (old->subtunes, sizeof (int) * old->nsubtunes); - - UNLOCK (old); - return new; -} - -EXPORT Tuple * tuple_new_from_filename (const char * filename) -{ - Tuple * tuple = tuple_new (); - tuple_set_filename (tuple, filename); - return tuple; -} - -EXPORT void tuple_set_int (Tuple * tuple, int field, int x) -{ - g_return_if_fail (VALID_FIELD (field) && FIELD_TYPE (field) == TUPLE_INT); - LOCK (tuple); - - TupleVal * val = lookup_val (tuple, field, TRUE, FALSE); - val->x = x; - - UNLOCK (tuple); -} - -EXPORT void tuple_set_str (Tuple * tuple, int field, const char * str) -{ - g_return_if_fail (VALID_FIELD (field) && FIELD_TYPE (field) == TUPLE_STRING); - - if (! str) - { - tuple_unset (tuple, field); - return; - } - - LOCK (tuple); - - TupleVal * val = lookup_val (tuple, field, TRUE, FALSE); - str_unref (val->str); - val->str = str_to_utf8 (str, -1); - - UNLOCK (tuple); -} - -EXPORT void tuple_unset (Tuple * tuple, int field) -{ - g_return_if_fail (VALID_FIELD (field)); - LOCK (tuple); - - TupleVal * val = lookup_val (tuple, field, FALSE, TRUE); - if (val) - { - if (tuple_fields[field].type == TUPLE_STRING) - { - str_unref (val->str); - val->str = NULL; - } - else - val->x = 0; - } - - UNLOCK (tuple); -} - -EXPORT TupleValueType tuple_get_value_type (const Tuple * tuple, int field) -{ - g_return_val_if_fail (VALID_FIELD (field), TUPLE_UNKNOWN); - LOCK (tuple); - - TupleVal * val = lookup_val ((Tuple *) tuple, field, FALSE, FALSE); - TupleValueType type = val ? FIELD_TYPE (field) : TUPLE_UNKNOWN; - - UNLOCK (tuple); - return type; -} - -EXPORT char * tuple_get_str (const Tuple * tuple, int field) -{ - g_return_val_if_fail (VALID_FIELD (field) && FIELD_TYPE (field) == TUPLE_STRING, NULL); - LOCK (tuple); - - TupleVal * val = lookup_val ((Tuple *) tuple, field, FALSE, FALSE); - char * str = val ? str_ref (val->str) : NULL; - - UNLOCK (tuple); - return str; -} - -EXPORT int tuple_get_int (const Tuple * tuple, int field) -{ - g_return_val_if_fail (VALID_FIELD (field) && FIELD_TYPE (field) == TUPLE_INT, -1); - LOCK (tuple); - - TupleVal * val = lookup_val ((Tuple *) tuple, field, FALSE, FALSE); - int x = val ? val->x : -1; - - UNLOCK (tuple); - return x; -} - -#define APPEND(b, ...) snprintf (b + strlen (b), sizeof b - strlen (b), __VA_ARGS__) - -EXPORT void tuple_set_format (Tuple * t, const char * format, int chans, int rate, - int brate) -{ - if (format) - tuple_set_str (t, FIELD_CODEC, format); - - char buf[32]; - buf[0] = 0; - - if (chans > 0) - { - if (chans == 1) - APPEND (buf, _("Mono")); - else if (chans == 2) - APPEND (buf, _("Stereo")); - else - APPEND (buf, dngettext (PACKAGE, "%d channel", "%d channels", chans), chans); - - if (rate > 0) - APPEND (buf, ", "); - } - - if (rate > 0) - APPEND (buf, "%d kHz", rate / 1000); - - if (buf[0]) - tuple_set_str (t, FIELD_QUALITY, buf); - - if (brate > 0) - tuple_set_int (t, FIELD_BITRATE, brate); -} - -EXPORT void tuple_set_subtunes (Tuple * tuple, int n_subtunes, const int * subtunes) -{ - LOCK (tuple); - - g_free (tuple->subtunes); - tuple->subtunes = NULL; - - tuple->nsubtunes = n_subtunes; - if (subtunes) - tuple->subtunes = g_memdup (subtunes, sizeof (int) * n_subtunes); - - UNLOCK (tuple); -} - -EXPORT int tuple_get_n_subtunes (Tuple * tuple) -{ - LOCK (tuple); - - int n_subtunes = tuple->nsubtunes; - - UNLOCK (tuple); - return n_subtunes; -} - -EXPORT int tuple_get_nth_subtune (Tuple * tuple, int n) -{ - LOCK (tuple); - - int subtune = -1; - if (n >= 0 && n < tuple->nsubtunes) - subtune = tuple->subtunes ? tuple->subtunes[n] : 1 + n; - - UNLOCK (tuple); - return subtune; -} diff --git a/src/libaudcore/tuple.cc b/src/libaudcore/tuple.cc new file mode 100644 index 0000000..4fe97b5 --- /dev/null +++ b/src/libaudcore/tuple.cc @@ -0,0 +1,780 @@ +/* + * tuple.c + * Copyright 2007-2013 William Pitcock, Christian Birchinger, Matti HĂ€mĂ€lĂ€inen, + * Giacomo Lozito, Eugene Zagidullin, and John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include <glib.h> /* for g_utf8_validate */ + +#include "audio.h" +#include "audstrings.h" +#include "i18n.h" +#include "tuple.h" +#include "vfs.h" + +enum { + FallbackTitle = Tuple::n_fields, + FallbackArtist, + FallbackAlbum, + + n_private_fields +}; + +static_assert (n_private_fields <= 64, + "The current tuple implementation is limited to 64 fields"); + +union TupleVal +{ + String str; + int x; + + // dummy constructor and destructor + TupleVal () {} + ~TupleVal () {} +}; + +/** + * Structure for holding and passing around miscellaneous track + * metadata. This is not the same as a playlist entry, though. + */ +struct TupleData +{ + uint64_t setmask; // which fields are present + Index<TupleVal> vals; // ordered list of field values + + int *subtunes; /**< Array of int containing subtune index numbers. + Can be nullptr if indexing is linear or if + there are no subtunes. */ + int nsubtunes; /**< Number of subtunes, if any. Values greater than 0 + mean that there are subtunes and #subtunes array + may be set. */ + + int refcount; + + TupleData (); + ~TupleData (); + + TupleData (const TupleData & other); + void operator= (const TupleData & other) = delete; + + bool is_set (int field) const + { return (setmask & bitmask (field)); } + + bool is_same (const TupleData & other); + + TupleVal * lookup (int field, bool add, bool remove); + void set_int (int field, int x); + void set_str (int field, const char * str); + void set_subtunes (int nsubs, const int * subs); + + static TupleData * ref (TupleData * tuple); + static void unref (TupleData * tuple); + + static TupleData * copy_on_write (TupleData * tuple); + +private: + static constexpr uint64_t bitmask (int n) + { return (uint64_t) 1 << n; } +}; + +/** Ordered table of basic #Tuple field names and their #ValueType. + */ +static const struct { + const char * name; + Tuple::ValueType type; + int fallback; +} field_info[] = { + {"title", Tuple::String, FallbackTitle}, + {"artist", Tuple::String, FallbackArtist}, + {"album", Tuple::String, FallbackAlbum}, + {"comment", Tuple::String, -1}, + {"genre", Tuple::String, -1}, + + {"track-number", Tuple::Int, -1}, + {"length", Tuple::Int, -1}, + {"year", Tuple::Int, -1}, + {"quality", Tuple::String, -1}, + {"codec", Tuple::String, -1}, + + {"file-name", Tuple::String, -1}, + {"file-path", Tuple::String, -1}, + {"file-ext", Tuple::String, -1}, + + {"album-artist", Tuple::String, -1}, + {"composer", Tuple::String, -1}, + {"performer", Tuple::String, -1}, + {"copyright", Tuple::String, -1}, + {"date", Tuple::String, -1}, + {"mbid", Tuple::String, -1}, + {"mime-type", Tuple::String, -1}, + {"bitrate", Tuple::Int, -1}, + + {"subsong-id", Tuple::Int, -1}, + {"subsong-num", Tuple::Int, -1}, + + {"segment-start", Tuple::Int, -1}, + {"segment-end", Tuple::Int, -1}, + + {"gain-album-gain", Tuple::Int, -1}, + {"gain-album-peak", Tuple::Int, -1}, + {"gain-track-gain", Tuple::Int, -1}, + {"gain-track-peak", Tuple::Int, -1}, + {"gain-gain-unit", Tuple::Int, -1}, + {"gain-peak-unit", Tuple::Int, -1}, + + {"formatted-title", Tuple::String, -1}, + + /* fallbacks */ + {nullptr, Tuple::String, -1}, + {nullptr, Tuple::String, -1}, + {nullptr, Tuple::String, -1}, +}; + +static_assert (aud::n_elems (field_info) == n_private_fields, "Update field_data"); + +struct FieldDictEntry { + const char * name; + Tuple::Field field; +}; + +/* used for binary search, MUST be in alphabetical order */ +static const FieldDictEntry field_dict[] = { + {"album", Tuple::Album}, + {"album-artist", Tuple::AlbumArtist}, + {"artist", Tuple::Artist}, + {"bitrate", Tuple::Bitrate}, + {"codec", Tuple::Codec}, + {"comment", Tuple::Comment}, + {"composer", Tuple::Composer}, + {"copyright", Tuple::Copyright}, + {"date", Tuple::Date}, + {"file-ext", Tuple::Suffix}, + {"file-name", Tuple::Basename}, + {"file-path", Tuple::Path}, + {"formatted-title", Tuple::FormattedTitle}, + {"gain-album-gain", Tuple::AlbumGain}, + {"gain-album-peak", Tuple::AlbumPeak}, + {"gain-gain-unit", Tuple::GainDivisor}, + {"gain-peak-unit", Tuple::PeakDivisor}, + {"gain-track-gain", Tuple::TrackGain}, + {"gain-track-peak", Tuple::TrackPeak}, + {"genre", Tuple::Genre}, + {"length", Tuple::Length}, + {"mbid", Tuple::MusicBrainz}, + {"mime-type", Tuple::MIMEType}, + {"performer", Tuple::Performer}, + {"quality", Tuple::Quality}, + {"segment-end", Tuple::EndTime}, + {"segment-start", Tuple::StartTime}, + {"subsong-id", Tuple::Subtune}, + {"subsong-num", Tuple::NumSubtunes}, + {"title", Tuple::Title}, + {"track-number", Tuple::Track}, + {"year", Tuple::Year} +}; + +static_assert (aud::n_elems (field_dict) == Tuple::n_fields, "Update field_dict"); + +static constexpr bool is_valid_field (int field) + { return field > Tuple::Invalid && field < Tuple::n_fields; } + +static int bitcount (uint64_t x) +{ + /* algorithm from http://en.wikipedia.org/wiki/Hamming_weight */ + x -= (x >> 1) & 0x5555555555555555; + x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333); + x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0f; + return (x * 0x0101010101010101) >> 56; +} + +static int field_dict_compare (const void * a, const void * b) +{ + return strcmp (((FieldDictEntry *) a)->name, ((FieldDictEntry *) b)->name); +} + +EXPORT Tuple::Field Tuple::field_by_name (const char * name) +{ + FieldDictEntry find = {name, Invalid}; + FieldDictEntry * found = (FieldDictEntry *) bsearch (& find, field_dict, + n_fields, sizeof (FieldDictEntry), field_dict_compare); + + return found ? found->field : Invalid; +} + +EXPORT const char * Tuple::field_get_name (Field field) +{ + assert (is_valid_field (field)); + return field_info[field].name; +} + +EXPORT Tuple::ValueType Tuple::field_get_type (Field field) +{ + assert (is_valid_field (field)); + return field_info[field].type; +} + +TupleVal * TupleData::lookup (int field, bool add, bool remove) +{ + /* calculate number of preceding fields */ + const uint64_t mask = bitmask (field); + const int pos = bitcount (setmask & (mask - 1)); + + if ((setmask & mask)) + { + if ((add || remove) && field_info[field].type == Tuple::String) + vals[pos].str.~String (); + + if (remove) + { + setmask &= ~mask; + vals.remove (pos, 1); + return nullptr; + } + + return & vals[pos]; + } + + if (! (add || remove) && field_info[field].fallback >= 0) + return lookup (field_info[field].fallback, false, false); + + if (! add) + return nullptr; + + setmask |= mask; + vals.insert (pos, 1); + return & vals[pos]; +} + +void TupleData::set_int (int field, int x) +{ + TupleVal * val = lookup (field, true, false); + val->x = x; +} + +void TupleData::set_str (int field, const char * str) +{ + TupleVal * val = lookup (field, true, false); + new (& val->str) String (str); +} + +void TupleData::set_subtunes (int nsubs, const int * subs) +{ + nsubtunes = nsubs; + + delete subtunes; + subtunes = nullptr; + + if (subs) + { + subtunes = new int[nsubs]; + memcpy (subtunes, subs, sizeof (int) * nsubs); + } +} + +TupleData::TupleData () : + setmask (0), + subtunes (nullptr), + nsubtunes (0), + refcount (1) {} + +TupleData::TupleData (const TupleData & other) : + setmask (other.setmask), + subtunes (nullptr), + nsubtunes (0), + refcount (1) +{ + vals.insert (0, other.vals.len ()); + + auto get = other.vals.begin (); + auto set = vals.begin (); + + for (int f = 0; f < n_private_fields; f ++) + { + if (other.setmask & bitmask (f)) + { + if (field_info[f].type == Tuple::String) + new (& set->str) String (get->str); + else + set->x = get->x; + + get ++; + set ++; + } + } + + set_subtunes (other.nsubtunes, other.subtunes); +} + +TupleData::~TupleData () +{ + auto iter = vals.begin (); + + for (int f = 0; f < n_private_fields; f ++) + { + if (setmask & bitmask (f)) + { + if (field_info[f].type == Tuple::String) + iter->str.~String (); + + iter ++; + } + } + + delete[] subtunes; +} + +bool TupleData::is_same (const TupleData & other) +{ + if (setmask != other.setmask || nsubtunes != other.nsubtunes || + (! subtunes) != (! other.subtunes)) + return false; + + auto a = vals.begin (); + auto b = other.vals.begin (); + + for (int f = 0; f < n_private_fields; f ++) + { + if (setmask & bitmask (f)) + { + bool same; + + if (field_info[f].type == Tuple::String) + same = (a->str == b->str); + else + same = (a->x = b->x); + + if (! same) + return false; + + a ++; + b ++; + } + } + + if (subtunes && memcmp (subtunes, other.subtunes, sizeof (int) * nsubtunes)) + return false; + + return true; +} + +TupleData * TupleData::ref (TupleData * tuple) +{ + if (tuple) + __sync_fetch_and_add (& tuple->refcount, 1); + + return tuple; +} + +void TupleData::unref (TupleData * tuple) +{ + if (tuple && ! __sync_sub_and_fetch (& tuple->refcount, 1)) + delete tuple; +} + +TupleData * TupleData::copy_on_write (TupleData * tuple) +{ + if (! tuple) + return new TupleData; + + if (__sync_fetch_and_add (& tuple->refcount, 0) == 1) + return tuple; + + TupleData * copy = new TupleData (* tuple); + unref (tuple); + return copy; +} + +EXPORT Tuple::~Tuple () +{ + TupleData::unref (data); +} + +EXPORT bool Tuple::operator== (const Tuple & b) const +{ + if (data == b.data) + return true; + + if (! data || ! b.data) + return false; + + return data->is_same (* b.data); +} + +EXPORT Tuple Tuple::ref () const +{ + Tuple tuple; + tuple.data = TupleData::ref (data); + return tuple; +} + +EXPORT Tuple::ValueType Tuple::get_value_type (Field field) const +{ + assert (is_valid_field (field)); + + const auto & info = field_info[field]; + if (data && (data->is_set (field) || (info.fallback >= 0 && data->is_set (info.fallback)))) + return info.type; + + return Empty; +} + +EXPORT int Tuple::get_int (Field field) const +{ + assert (is_valid_field (field) && field_info[field].type == Int); + + TupleVal * val = data ? data->lookup (field, false, false) : nullptr; + return val ? val->x : -1; +} + +EXPORT String Tuple::get_str (Field field) const +{ + assert (is_valid_field (field) && field_info[field].type == String); + + TupleVal * val = data ? data->lookup (field, false, false) : nullptr; + return val ? val->str : ::String (); +} + +EXPORT void Tuple::set_int (Field field, int x) +{ + assert (is_valid_field (field) && field_info[field].type == Int); + + data = TupleData::copy_on_write (data); + data->set_int (field, x); +} + +EXPORT void Tuple::set_str (Field field, const char * str) +{ + assert (is_valid_field (field) && field_info[field].type == String); + + if (! str) + { + unset (field); + return; + } + + data = TupleData::copy_on_write (data); + + if (g_utf8_validate (str, -1, nullptr)) + data->set_str (field, str); + else + { + StringBuf utf8 = str_to_utf8 (str, -1); + data->set_str (field, utf8 ? (const char *) utf8 : _("(character encoding error)")); + } +} + +EXPORT void Tuple::unset (Field field) +{ + assert (is_valid_field (field)); + + if (! data) + return; + + data = TupleData::copy_on_write (data); + data->lookup (field, false, true); +} + +EXPORT void Tuple::set_filename (const char * filename) +{ + assert (filename); + + const char * base, * ext, * sub; + int isub; + + uri_parse (filename, & base, & ext, & sub, & isub); + + data = TupleData::copy_on_write (data); + + if (base > filename) + data->set_str (Path, uri_to_display (str_copy (filename, base - filename))); + if (ext > base) + data->set_str (Basename, str_to_utf8 (str_decode_percent (base, ext - base))); + if (sub > ext + 1) + data->set_str (Suffix, str_to_utf8 (str_decode_percent (ext + 1, sub - ext - 1))); + + if (sub[0]) + data->set_int (Subtune, isub); +} + +EXPORT void Tuple::set_format (const char * format, int chans, int rate, int brate) +{ + if (format) + set_str (Codec, format); + + StringBuf buf; + + if (chans > 0) + { + if (chans == 1) + buf.insert (-1, _("Mono")); + else if (chans == 2) + buf.insert (-1, _("Stereo")); + else + buf.combine (str_printf (dngettext (PACKAGE, "%d channel", "%d channels", chans), chans)); + + if (rate > 0) + buf.insert (-1, ", "); + } + + if (rate > 0) + buf.combine (str_printf ("%d kHz", rate / 1000)); + + if (buf[0]) + set_str (Quality, buf); + + if (brate > 0) + set_int (Bitrate, brate); +} + +EXPORT void Tuple::set_subtunes (int n_subtunes, const int * subtunes) +{ + data = TupleData::copy_on_write (data); + data->set_subtunes (n_subtunes, subtunes); +} + +EXPORT int Tuple::get_n_subtunes () const +{ + return data ? data->nsubtunes : 0; +} + +EXPORT int Tuple::get_nth_subtune (int n) const +{ + if (! data || n < 0 || n >= data->nsubtunes) + return -1; + + return data->subtunes ? data->subtunes[n] : 1 + n; +} + +EXPORT ReplayGainInfo Tuple::get_replay_gain () const +{ + ReplayGainInfo gain {}; + + if (! data) + return gain; + + int gain_unit = get_int (GainDivisor); + int peak_unit = get_int (PeakDivisor); + + if (gain_unit > 0) + { + if (data->is_set (AlbumGain)) + gain.album_gain = get_int (AlbumGain) / (float) gain_unit; + if (data->is_set (TrackGain)) + gain.track_gain = get_int (TrackGain) / (float) gain_unit; + } + + if (peak_unit > 0) + { + if (data->is_set (AlbumPeak)) + gain.album_peak = get_int (AlbumPeak) / (float) peak_unit; + if (data->is_set (TrackPeak)) + gain.track_peak = get_int (TrackPeak) / (float) peak_unit; + } + + return gain; +} + +EXPORT bool Tuple::fetch_stream_info (VFSFile & stream) +{ + bool updated = false; + int value; + + ::String val = stream.get_metadata ("track-name"); + + if (val && val != get_str (Title)) + { + set_str (Title, val); + updated = true; + } + + val = stream.get_metadata ("stream-name"); + + if (val && val != get_str (Artist)) + { + set_str (Artist, val); + updated = true; + } + + val = stream.get_metadata ("content-bitrate"); + value = val ? atoi (val) / 1000 : 0; + + if (value && value != get_int (Bitrate)) + { + set_int (Bitrate, value); + updated = true; + } + + return updated; +} + +/* Separates the lowest-level folder from a file path. The string passed will + * be modified, and the string returned will use the same memory. May return + * nullptr. */ + +static char * split_folder (char * path) +{ + char * c; + while ((c = strrchr (path, G_DIR_SEPARATOR))) + { + * c = 0; + if (c[1]) + return c + 1; + } + + return path[0] ? path : nullptr; +} + +/* Separates the domain name from an internet URI. The string passed will be + * modified, and the string returned will share the same memory. May return + * nullptr. Examples: + * "http://some.domain.org/folder/file.mp3" -> "some.domain.org" + * "http://some.stream.fm:8000" -> "some.stream.fm" */ + +static char * domain_name (char * name) +{ + if (! strncmp (name, "http://", 7)) + name += 7; + else if (! strncmp (name, "https://", 8)) + name += 8; + else if (! strncmp (name, "mms://", 6)) + name += 6; + else + return nullptr; + + char * c; + + if ((c = strchr (name, '/'))) + * c = 0; + if ((c = strchr (name, ':'))) + * c = 0; + if ((c = strchr (name, '?'))) + * c = 0; + + return name; +} + +EXPORT void Tuple::generate_fallbacks () +{ + if (! data) + return; + + ::String title = get_str (Title); + ::String artist = get_str (Artist); + ::String album = get_str (Album); + + if (title && artist && album) + return; + + ::String filename = get_str (Basename); + ::String filepath = get_str (Path); + int subtune = get_int (Subtune); + + data = TupleData::copy_on_write (data); + + if (filepath && ! strcmp (filepath, "cdda://")) + { + // audio CD: + // use "Track N" as the title and "Audio CD" as the album + + if (! title && subtune >= 0) + data->set_str (FallbackTitle, str_printf (_("Track %d"), subtune)); + if (! album) + data->set_str (FallbackAlbum, _("Audio CD")); + + return; + } + + if (! title) + data->set_str (FallbackTitle, filename ? (const char *) filename : _("(unknown title)")); + + if (! filepath) + return; + + if (strstr (filepath, "://")) + { + // URL: + // use the domain name as the album + + if (album) + return; + + StringBuf buf = str_copy (filepath); + const char * domain = domain_name (buf); + + if (domain) + data->set_str (FallbackAlbum, domain); + } + else + { + // local file: + // use the top two path elements as the artist and album + + if (artist && album) + return; + + StringBuf buf; +#ifdef _WIN32 + if (filepath[0] && filepath[1] == ':' && filepath[2] == '\\') + buf.steal (str_copy (filepath + 3)); + else +#endif + buf.steal (str_copy (filepath)); + + char * first = split_folder (buf); + char * second = (first && first > buf) ? split_folder (buf) : nullptr; + + // skip common strings and avoid duplicates + for (auto skip : (const char *[]) {"~", "music", artist, album}) + { + if (first && skip && ! strcmp_nocase (first, skip)) + { + first = second; + second = nullptr; + } + + if (second && skip && ! strcmp_nocase (second, skip)) + second = nullptr; + } + + if (first) + { + if (second && ! artist && ! album) + { + data->set_str (FallbackArtist, second); + data->set_str (FallbackAlbum, first); + } + else + data->set_str (artist ? FallbackAlbum : FallbackArtist, first); + } + } +} + +EXPORT void Tuple::delete_fallbacks () +{ + if (! data) + return; + + data = TupleData::copy_on_write (data); + data->lookup (FallbackTitle, false, true); + data->lookup (FallbackArtist, false, true); + data->lookup (FallbackAlbum, false, true); +} diff --git a/src/libaudcore/tuple.h b/src/libaudcore/tuple.h index 873f699..b4e2efd 100644 --- a/src/libaudcore/tuple.h +++ b/src/libaudcore/tuple.h @@ -26,155 +26,186 @@ #ifndef LIBAUDCORE_TUPLE_H #define LIBAUDCORE_TUPLE_H -#include <libaudcore/core.h> +#include <libaudcore/objects.h> + +struct ReplayGainInfo; +struct TupleData; +class VFSFile; + +class Tuple +{ +public: + /* Smart pointer to the actual TupleData struct. + * Uses create-on-write and copy-on-write. */ + + enum Field { + Invalid = -1, + + Title = 0, /* Song title */ + Artist, /* Song artist */ + Album, /* Album name */ + Comment, /* Freeform comment */ + Genre, /* Song's genre */ + + Track, /* Track number */ + Length, /* Track length in milliseconds */ + Year, /* Year of production, performance, etc. */ + Quality, /* String representing quality, such as "Stereo, 44 kHz" */ + Codec, /* Codec name, such as "Ogg Vorbis" */ + + Basename, /* Base filename, not including the folder path */ + Path, /* Folder path, including the trailing "/" */ + Suffix, /* Filename extension, not including the "." */ + + AlbumArtist, /* Artist for entire album, if different than song artist */ + Composer, /* Composer of song, if different than artist */ + Performer, + Copyright, + Date, + MusicBrainz, /* MusicBrainz identifer for the song */ + MIMEType, + Bitrate, /* Bitrate in kbits/sec */ + + Subtune, /* Index number of subtune */ + NumSubtunes, /* Total number of subtunes in the file */ + + StartTime, + EndTime, + + /* Preserving replay gain information accurately is a challenge since there + * are several differents formats around. We use an integer fraction, with + * the denominator stored in the *Divisor fields. For example, if AlbumGain + * is 512 and GainDivisor is 256, then the album gain is +2 dB. If TrackPeak + * is 787 and PeakDivisor is 1000, then the peak volume is 0.787 in a -1.0 to + * 1.0 range. */ + AlbumGain, + AlbumPeak, + TrackGain, + TrackPeak, + GainDivisor, + PeakDivisor, + + /* Title formatted for display; input plugins do not need to set this field */ + FormattedTitle, + + n_fields + }; + + typedef aud::range<Field, Title, FormattedTitle> all_fields; + + enum ValueType { + String, + Int, + Empty + }; + + static Field field_by_name (const char * name); + static const char * field_get_name (Field field); + static ValueType field_get_type (Field field); + + constexpr Tuple () : + data (nullptr) {} + + ~Tuple (); + + Tuple (Tuple && b) : + data (b.data) + { + b.data = nullptr; + } + + Tuple & operator= (Tuple && b) + { + if (this != & b) + { + this->~Tuple (); + data = b.data; + b.data = nullptr; + } + return * this; + } + + explicit operator bool () const + { return (bool) data; } + + bool operator== (const Tuple & b) const; + bool operator!= (const Tuple & b) const + { return ! operator== (b); } + + Tuple ref () const; + + /* Returns the value type of a field if set, otherwise Empty. */ + ValueType get_value_type (Field field) const; + + /* Returns the integer value of a field if set, otherwise -1. If you need + * to distinguish between a value of -1 and an unset value, use + * get_value_type(). */ + int get_int (Field field) const; + + /* Returns the string value of a field if set, otherwise null. */ + ::String get_str (Field field) const; + + /* Sets a field to the integer value <x>. */ + void set_int (Field field, int x); + + /* Sets a field to the string value <str>. If <str> is not valid UTF-8, it + * will be converted according to the user's character set detection rules. + * Equivalent to unset() if <str> is null. */ + void set_str (Field field, const char * str); + + /* Clears any value that a field is currently set to. */ + void unset (Field field); + + /* Parses the URI <filename> and sets Basename, Path, Suffix, and Subtune accordingly. */ + void set_filename (const char * filename); + + /* Fills in format-related fields (specifically Codec, Quality, + * and Bitrate). Plugins should use this function instead of setting + * these fields individually to allow a consistent style across file + * formats. <format> should be a brief description such as "Ogg Vorbis", + * "MPEG-1 layer 3", "Audio CD", and so on. <samplerate> is in Hertz. + * <bitrate> is in (decimal) kbps. */ + void set_format (const char * format, int channels, int samplerate, int bitrate); + + /* In addition to the normal fields, tuples contain an integer array of + * subtune ID numbers. This function sets that array. It also sets + * NumSubtunes to the value <n_subtunes>. */ + void set_subtunes (int n_subtunes, const int * subtunes); + + /* Returns the length of the subtune array. If the array has not been set, + * returns zero. Note that if NumSubtunes is changed after + * set_subtunes() is called, this function returns the value <n_subtunes> + * passed to set_subtunes(), not the value of NumSubtunes. */ + int get_n_subtunes () const; + + /* Returns the <n>th member of the subtune array. */ + int get_nth_subtune (int n) const; + + /* Fills ReplayGainInfo struct from various fields. */ + ReplayGainInfo get_replay_gain () const; -/** Ordered enum for basic #Tuple fields. - * @sa TupleBasicType - */ -enum { - FIELD_ARTIST = 0, - FIELD_TITLE, /**< Song title */ - FIELD_ALBUM, /**< Album name */ - FIELD_COMMENT, /**< Freeform comment */ - FIELD_GENRE, /**< Song's genre */ - - FIELD_TRACK_NUMBER, - FIELD_LENGTH, /**< Track length in milliseconds */ - FIELD_YEAR, /**< Year of production/performance/etc */ - FIELD_QUALITY, /**< String representing quality, such as - "lossy", "lossless", "sequenced" */ - - FIELD_CODEC, /**< Codec name or similar */ - FIELD_FILE_NAME, /**< File name part of the location URI */ - FIELD_FILE_PATH, /**< Path part of the location URI */ - FIELD_FILE_EXT, /**< Filename extension part of the location URI */ - - FIELD_SONG_ARTIST, - FIELD_COMPOSER, /**< Composer of song, if different than artist. */ - FIELD_PERFORMER, - FIELD_COPYRIGHT, - FIELD_DATE, - - FIELD_SUBSONG_ID, /**< Index number of subsong/tune */ - FIELD_SUBSONG_NUM, /**< Total number of subsongs in the file */ - FIELD_MIMETYPE, - FIELD_BITRATE, /**< Bitrate in kbps */ - - FIELD_SEGMENT_START, - FIELD_SEGMENT_END, - - /* Preserving replay gain information accurately is a challenge since there - * are several differents formats around. We use an integer fraction, with - * the denominator stored in the *_UNIT fields. For example, if ALBUM_GAIN - * is 512 and GAIN_UNIT is 256, then the album gain is +2 dB. If TRACK_PEAK - * is 787 and PEAK_UNIT is 1000, then the peak volume is 0.787 in a -1.0 to - * 1.0 range. */ - FIELD_GAIN_ALBUM_GAIN, - FIELD_GAIN_ALBUM_PEAK, - FIELD_GAIN_TRACK_GAIN, - FIELD_GAIN_TRACK_PEAK, - FIELD_GAIN_GAIN_UNIT, - FIELD_GAIN_PEAK_UNIT, - - TUPLE_FIELDS + /* Set various fields based on the ICY metadata of <stream>. Returns true + * if any fields were changed. */ + bool fetch_stream_info (VFSFile & stream); + + /* Guesses the song title, artist, and album, if not already set, from the + * filename. */ + void generate_fallbacks (); + + /* Removes guesses made by generate_fallbacks(). This function should be + * called, for example, before writing a song tag from the tuple. */ + void delete_fallbacks (); + +private: + TupleData * data; }; -typedef enum { - TUPLE_STRING, - TUPLE_INT, - TUPLE_UNKNOWN -} TupleValueType; - -int tuple_field_by_name (const char * name); -const char * tuple_field_get_name (int field); -TupleValueType tuple_field_get_type (int field); - -typedef struct _Tuple Tuple; -typedef struct _TupleFormatter TupleFormatter; - -/* Creates a new, blank tuple with a reference count of one. */ -Tuple * tuple_new (void); - -/* Increments the reference count of <tuple> by one. */ -Tuple * tuple_ref (Tuple * tuple); - -/* Decrements the reference count of <tuple> by one. If the reference count - * drops to zero, releases all memory used by <tuple>. If <tuple> is NULL, does - * nothing. */ -void tuple_unref (Tuple * tuple); - -/* Makes a copy of <tuple>. Only use tuple_copy() if you need to modify one - * copy of the tuple while not modifying the other. In most cases, tuple_ref() - * is more appropriate. */ -Tuple * tuple_copy (const Tuple * tuple); - -/* Parses the URI <filename> and sets FIELD_FILE_NAME, FIELD_FILE_PATH, - * FIELD_FILE_EXT, and FIELD_SUBSONG_ID accordingly. */ -void tuple_set_filename (Tuple * tuple, const char * filename); - -/* Convenience function, equivalent to calling tuple_new() and then - * tuple_set_filename(). */ -Tuple * tuple_new_from_filename (const char * filename); - -/* Sets a field to the integer value <x>. */ -void tuple_set_int (Tuple * tuple, int field, int x); - -/* Sets a field to the string value <str>. If <str> is not valid UTF-8, it will - * be converted according to the user's character set detection rules. As a - * special case, if <str> is NULL, the result is equivalent to calling - * tuple_unset(). */ -void tuple_set_str (Tuple * tuple, int field, const char * str); - -/* Clears any value that a field is currently set to. */ -void tuple_unset (Tuple * tuple, int field); - -/* Returns the value type of a field, or TUPLE_UNKNOWN if the field has not been - * set to any value. */ -TupleValueType tuple_get_value_type (const Tuple * tuple, int field); - -/* Returns the string value of a field. The returned string is pooled and must - * be released with str_unref() when no longer needed. If the field has not - * been set to any value, returns NULL. */ -char * tuple_get_str (const Tuple * tuple, int field); - -/* Returns the integer value of a field. If the field has not been set to any - * value, returns -1. If you need to distinguish between a value of -1 and a - * field not set to any value, use tuple_get_value_type(). */ -int tuple_get_int (const Tuple * tuple, int field); - -/* Fills in format-related fields (specifically FIELD_CODEC, FIELD_QUALITY, and - * FIELD_BITRATE). Plugins should use this function instead of setting these - * fields individually so that the style is consistent across file formats. - * <format> should be a brief description such as "Microsoft WAV", "MPEG-1 layer - * 3", "Audio CD", and so on. <samplerate> is in Hertz. <bitrate> is in 1000 - * bits per second. */ -void tuple_set_format (Tuple * tuple, const char * format, int channels, int - samplerate, int bitrate); - -/* In addition to the normal fields, tuples contain an integer array of subtune - * ID numbers. This function sets that array. It also sets FIELD_SUBSONG_NUM - * to the value <n_subtunes>. */ -void tuple_set_subtunes (Tuple * tuple, int n_subtunes, const int * subtunes); - -/* Returns the length of the subtune array. If the array has not been set, - * returns zero. Note that if FIELD_SUBSONG_NUM is changed after - * tuple_set_subtunes() is called, this function returns the value <n_subtunes> - * passed to tuple_set_subtunes(), not the value of FIELD_SUBSONG_NUM. */ -int tuple_get_n_subtunes (Tuple * tuple); - -/* Returns the <n>th member of the subtune array. */ -int tuple_get_nth_subtune (Tuple * tuple, int n); - -/* Creates a tuple formatter object for the given format. The syntax of - * <format> is documented in tuple_formatter.c. */ -TupleFormatter * tuple_formatter_new (const char * format); - -/* Destroys a tuple formatter object. */ -void tuple_formatter_free (TupleFormatter * formatter); - -/* Generates a title string for <tuple> using the given formatter object. The - * returned string is pooled and must be released with str_unref() when no - * longer needed. Never returns NULL, but may return an empty string. */ -char * tuple_format_title (TupleFormatter * formatter, const Tuple * tuple); +/* somewhat out of place here */ +class PluginHandle; +struct PlaylistAddItem { + String filename; + Tuple tuple; + PluginHandle * decoder; +}; #endif /* LIBAUDCORE_TUPLE_H */ diff --git a/src/libaudcore/tuple_compiler.c b/src/libaudcore/tuple_compiler.c deleted file mode 100644 index 99d53b4..0000000 --- a/src/libaudcore/tuple_compiler.c +++ /dev/null @@ -1,708 +0,0 @@ -/* - * tuple_compiler.c - * Copyright (c) 2007 Matti 'ccr' HĂ€mĂ€lĂ€inen - * Copyright (c) 2011-2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -/* - * TODO: - * - Unicode/UTF-8 support in format strings. using any non-ASCII - * characters in Tuplez format strings WILL cause things go boom - * at the moment! - * - * - implement definitions (${=foo,"baz"} ${=foo,1234}) - * - implement functions - * - implement handling of external expressions - * - evaluation context: how local variables should REALLY work? - * currently there is just a single context, is a "global" context needed? - */ - -#include <stdarg.h> -#include <stdlib.h> -#include <stdio.h> -#include <string.h> - -#include <glib.h> - -#include "audstrings.h" -#include "tuple_compiler.h" - -#define MAX_STR (256) -#define TUPLEZ_MAX_VARS (4) - -#define GET_VAR(c, i) (& g_array_index ((c), TupleEvalVar, (i))) - -#define tuple_error(ctx, ...) fprintf (stderr, "Tuple compiler: " __VA_ARGS__) - -enum { - OP_RAW = 0, /* plain text */ - OP_FIELD, /* a field/variable */ - OP_EXISTS, - OP_EQUALS, - OP_NOT_EQUALS, - OP_GT, - OP_GTEQ, - OP_LT, - OP_LTEQ, - OP_IS_EMPTY -}; - -enum { - TUPLE_VAR_FIELD = 0, - TUPLE_VAR_CONST -}; - -struct _TupleEvalNode { - int opcode; /* operator, see OP_ enums */ - int var[TUPLEZ_MAX_VARS]; /* tuple variable references */ - char *text; /* raw text, if any (OP_RAW) */ - struct _TupleEvalNode *children, *next, *prev; /* children of this struct, and pointer to next node. */ -}; - -typedef struct { - char *name; - int type; /* Type of variable, see VAR_* */ - int defvali; - TupleValueType ctype; /* Type of constant/def value */ - - int fieldidx; /* if >= 0: Index # of "pre-defined" Tuple fields */ - bool_t fieldread, fieldvalid; - char * fieldstr; -} TupleEvalVar; - -/* Initialize an evaluation context - */ -TupleEvalContext * tuple_evalctx_new(void) -{ - return g_array_new (FALSE, TRUE, sizeof (TupleEvalVar)); -} - - -/* "Reset" the evaluation context - */ -void tuple_evalctx_reset(TupleEvalContext *ctx) -{ - for (int i = 0; i < ctx->len; i ++) - { - TupleEvalVar * var = GET_VAR (ctx, i); - - var->fieldread = FALSE; - var->fieldvalid = FALSE; - str_unref (var->fieldstr); - var->fieldstr = NULL; - } -} - - -/* Free an evaluation context and associated data - */ -void tuple_evalctx_free(TupleEvalContext *ctx) -{ - for (int i = 0; i < ctx->len; i ++) - { - TupleEvalVar * var = GET_VAR (ctx, i); - - str_unref (var->name); - str_unref (var->fieldstr); - } - - g_array_free (ctx, TRUE); -} - - -/* note: may invalidate TupleEvalVar pointers due to reallocation */ -static int tuple_evalctx_add_var (TupleEvalContext * ctx, const char * name, - const int type, const TupleValueType ctype) -{ - int field = -1; - - if (type == TUPLE_VAR_FIELD) - { - field = tuple_field_by_name (name); - if (field < 0) - return -1; - } - - int i = ctx->len; - g_array_set_size (ctx, i + 1); - - TupleEvalVar * var = GET_VAR (ctx, i); - - var->name = str_get (name); - var->type = type; - var->fieldidx = field; - var->ctype = ctype; - - switch (type) { - case TUPLE_VAR_FIELD: - var->ctype = tuple_field_get_type (field); - break; - - case TUPLE_VAR_CONST: - if (ctype == TUPLE_INT) - var->defvali = atoi (name); - break; - } - - return i; -} - - -static void tuple_evalnode_insert(TupleEvalNode **nodes, TupleEvalNode *node) -{ - if (*nodes) { - node->prev = (*nodes)->prev; - (*nodes)->prev->next = node; - (*nodes)->prev = node; - node->next = NULL; - } else { - *nodes = node; - node->prev = node; - node->next = NULL; - } -} - - -void tuple_evalnode_free(TupleEvalNode *expr) -{ - TupleEvalNode *curr = expr, *next; - - while (curr) { - next = curr->next; - - str_unref (curr->text); - - if (curr->children) - tuple_evalnode_free(curr->children); - - g_slice_free (TupleEvalNode, curr); - - curr = next; - } -} - - -static TupleEvalNode *tuple_compiler_pass1(int *level, TupleEvalContext *ctx, const char **expression); - - -static bool_t tc_get_item(TupleEvalContext *ctx, - const char **str, char *buf, gssize max, - char endch, bool_t *literal, char *errstr, const char *item) -{ - gssize i = 0; - const char *s = *str; - char tmpendch; - - if (*s == '"') { - if (*literal == FALSE) { - tuple_error(ctx, "Literal string value not allowed in '%s'.\n", item); - return FALSE; - } - s++; - *literal = TRUE; - tmpendch = '"'; - } else { - *literal = FALSE; - tmpendch = endch; - } - - if (*literal == FALSE) { - while (*s != '\0' && *s != tmpendch && (g_ascii_isalnum(*s) || *s == '-') && i < (max - 1)) { - buf[i++] = *(s++); - } - - if (*s != tmpendch && *s != '}' && !g_ascii_isalnum(*s) && *s != '-') { - tuple_error(ctx, "Invalid field '%s' in '%s'.\n", *str, item); - return FALSE; - } else if (*s != tmpendch) { - tuple_error(ctx, "Expected '%c' in '%s'.\n", tmpendch, item); - return FALSE; - } - } else { - while (*s != '\0' && *s != tmpendch && i < (max - 1)) { - if (*s == '\\') { - s++; - if (*s == '\0') - break; - } - buf[i++] = *(s++); - } - } - buf[i] = '\0'; - - if (*literal) { - if (*s == tmpendch) - s++; - else { - tuple_error(ctx, "Expected literal string end ('%c') in '%s'.\n", tmpendch, item); - return FALSE; - } - } - - if (*s != endch) { - tuple_error(ctx, "Expected '%c' after %s in '%s'\n", endch, errstr, item); - return FALSE; - } else { - *str = s; - return TRUE; - } -} - - -static int tc_get_variable(TupleEvalContext *ctx, char *name, int type) -{ - TupleValueType ctype = TUPLE_UNKNOWN; - - if (g_ascii_isdigit(name[0])) { - ctype = TUPLE_INT; - type = TUPLE_VAR_CONST; - } else - ctype = TUPLE_STRING; - - if (type != TUPLE_VAR_CONST) { - for (int i = 0; i < ctx->len; i ++) - { - TupleEvalVar * var = GET_VAR (ctx, i); - if (var->type == type && ! strcmp (var->name, name)) - return i; - } - } - - return tuple_evalctx_add_var(ctx, name, type, ctype); -} - - -static bool_t tc_parse_construct(TupleEvalContext *ctx, TupleEvalNode **res, - const char *item, const char **c, int *level, int opcode) -{ - char tmps1[MAX_STR], tmps2[MAX_STR]; - bool_t literal1 = TRUE, literal2 = TRUE; - - if (tc_get_item(ctx, c, tmps1, MAX_STR, ',', &literal1, "tag1", item)) { - (*c)++; - if (tc_get_item(ctx, c, tmps2, MAX_STR, ':', &literal2, "tag2", item)) { - TupleEvalNode *tmp = g_slice_new0 (TupleEvalNode); - (*c)++; - - tmp->opcode = opcode; - if ((tmp->var[0] = tc_get_variable(ctx, tmps1, literal1 ? TUPLE_VAR_CONST : TUPLE_VAR_FIELD)) < 0) { - tuple_evalnode_free(tmp); - tuple_error(ctx, "Invalid variable '%s' in '%s'.\n", tmps1, item); - return FALSE; - } - if ((tmp->var[1] = tc_get_variable(ctx, tmps2, literal2 ? TUPLE_VAR_CONST : TUPLE_VAR_FIELD)) < 0) { - tuple_evalnode_free(tmp); - tuple_error(ctx, "Invalid variable '%s' in '%s'.\n", tmps2, item); - return FALSE; - } - tmp->children = tuple_compiler_pass1(level, ctx, c); - tuple_evalnode_insert(res, tmp); - } else - return FALSE; - } else - return FALSE; - - return TRUE; -} - - -/* Compile format expression into TupleEvalNode tree. - * A "simple" straight compilation is sufficient in first pass, later - * passes can perform subexpression removal and other optimizations. - */ -static TupleEvalNode *tuple_compiler_pass1(int *level, TupleEvalContext *ctx, const char **expression) -{ - TupleEvalNode *res = NULL, *tmp = NULL; - const char *c = *expression, *item; - char tmps1[MAX_STR]; - bool_t literal, end = FALSE; - - (*level)++; - - while (*c != '\0' && !end) { - tmp = NULL; - if (*c == '}') { - c++; - (*level)--; - end = TRUE; - } else if (*c == '$') { - /* Expression? */ - item = c++; - if (*c == '{') { - int opcode; - const char *expr = ++c; - - switch (*c) { - case '?': c++; - /* Exists? */ - literal = FALSE; - if (tc_get_item(ctx, &c, tmps1, MAX_STR, ':', &literal, "tag", item)) { - c++; - tmp = g_slice_new0 (TupleEvalNode); - tmp->opcode = OP_EXISTS; - if ((tmp->var[0] = tc_get_variable(ctx, tmps1, TUPLE_VAR_FIELD)) < 0) { - tuple_error(ctx, "Invalid variable '%s' in '%s'.\n", tmps1, expr); - goto ret_error; - } - tmp->children = tuple_compiler_pass1(level, ctx, &c); - tuple_evalnode_insert(&res, tmp); - } else - goto ret_error; - break; - - case '=': c++; - if (*c != '=') goto ret_error; - c++; - /* Equals? */ - if (!tc_parse_construct(ctx, &res, item, &c, level, OP_EQUALS)) - goto ret_error; - break; - - case '!': c++; - if (*c != '=') goto ret_error; - c++; - if (!tc_parse_construct(ctx, &res, item, &c, level, OP_NOT_EQUALS)) - goto ret_error; - break; - - case '<': c++; - if (*c == '=') { - opcode = OP_LTEQ; - c++; - } else - opcode = OP_LT; - - if (!tc_parse_construct(ctx, &res, item, &c, level, opcode)) - goto ret_error; - break; - - case '>': c++; - if (*c == '=') { - opcode = OP_GTEQ; - c++; - } else - opcode = OP_GT; - - if (!tc_parse_construct(ctx, &res, item, &c, level, opcode)) - goto ret_error; - break; - - case '(': c++; - if (!strncmp(c, "empty)?", 7)) { - c += 7; - literal = FALSE; - if (tc_get_item(ctx, &c, tmps1, MAX_STR, ':', &literal, "tag", item)) { - c++; - tmp = g_slice_new0 (TupleEvalNode); - tmp->opcode = OP_IS_EMPTY; - if ((tmp->var[0] = tc_get_variable(ctx, tmps1, TUPLE_VAR_FIELD)) < 0) { - tuple_error(ctx, "Invalid variable '%s' in '%s'.\n", tmps1, expr); - goto ret_error; - } - tmp->children = tuple_compiler_pass1(level, ctx, &c); - tuple_evalnode_insert(&res, tmp); - } else - goto ret_error; - } else - goto ret_error; - break; - - default: - /* Get expression content */ - c = expr; - literal = FALSE; - if (tc_get_item(ctx, &c, tmps1, MAX_STR, '}', &literal, "field", item)) { - /* FIXME!! FIX ME! Check for external expressions */ - - /* I HAS A FIELD - A field. You has it. */ - tmp = g_slice_new0 (TupleEvalNode); - tmp->opcode = OP_FIELD; - if ((tmp->var[0] = tc_get_variable(ctx, tmps1, TUPLE_VAR_FIELD)) < 0) { - tuple_error(ctx, "Invalid variable '%s' in '%s'.\n", tmps1, expr); - goto ret_error; - } - tuple_evalnode_insert(&res, tmp); - c++; - - } else - goto ret_error; - } - } else { - tuple_error(ctx, "Expected '{', got '%c' in '%s'.\n", *c, c); - goto ret_error; - } - } else { - /* Parse raw/literal text */ - gssize i = 0; - while (*c != '\0' && *c != '$' && *c != '}' && i < (MAX_STR - 1)) { - if (*c == '\\') { - c++; - if (*c == '\0') - break; - } - tmps1[i++] = *(c++); - } - tmps1[i] = '\0'; - - tmp = g_slice_new0 (TupleEvalNode); - tmp->opcode = OP_RAW; - tmp->text = str_get (tmps1); - tuple_evalnode_insert(&res, tmp); - } - } - - if (*level <= 0) { - tuple_error(ctx, "Syntax error! Uneven/unmatched nesting of elements in '%s'!\n", c); - goto ret_error; - } - - *expression = c; - return res; - -ret_error: - tuple_evalnode_free(tmp); - tuple_evalnode_free(res); - return NULL; -} - - -TupleEvalNode *tuple_formatter_compile(TupleEvalContext *ctx, const char *expr) -{ - int level = 0; - const char *tmpexpr = expr; - TupleEvalNode *res1; - - res1 = tuple_compiler_pass1(&level, ctx, &tmpexpr); - - if (level != 1) { - tuple_error(ctx, "Syntax error! Uneven/unmatched nesting of elements! (%d)\n", level); - tuple_evalnode_free(res1); - return NULL; - } - - return res1; -} - - -/* Fetch a tuple field value. Return TRUE if found. */ -static bool_t tf_get_fieldval (TupleEvalVar * var, const Tuple * tuple) -{ - if (var->type != TUPLE_VAR_FIELD || var->fieldidx < 0) - return FALSE; - - if (var->fieldread) - return var->fieldvalid; - - if (tuple_get_value_type (tuple, var->fieldidx) != var->ctype) { - var->fieldread = TRUE; - var->fieldvalid = FALSE; - return FALSE; - } - - if (var->ctype == TUPLE_INT) - var->defvali = tuple_get_int (tuple, var->fieldidx); - else if (var->ctype == TUPLE_STRING) - var->fieldstr = tuple_get_str (tuple, var->fieldidx); - - var->fieldread = TRUE; - var->fieldvalid = TRUE; - return TRUE; -} - - -/* Fetch string or int value of given variable, whatever type it might be. - * Return VAR_* type for the variable. - */ -static TupleValueType tf_get_var (char * * tmps, int * tmpi, TupleEvalVar * - var, const Tuple * tuple) -{ - TupleValueType type = TUPLE_UNKNOWN; - *tmps = NULL; - *tmpi = 0; - - switch (var->type) { - case TUPLE_VAR_CONST: - switch (var->ctype) { - case TUPLE_STRING: *tmps = var->name; break; - case TUPLE_INT: *tmpi = var->defvali; break; - default: /* Cannot happen */ break; - } - type = var->ctype; - break; - - case TUPLE_VAR_FIELD: - if (tf_get_fieldval (var, tuple)) { - type = var->ctype; - if (type == TUPLE_INT) - * tmpi = var->defvali; - else if (type == TUPLE_STRING) - * tmps = var->fieldstr; - } - break; - } - - return type; -} - - -/* Evaluate tuple in given TupleEval expression in given - * context and return resulting string. - */ -static bool_t tuple_formatter_eval_do (TupleEvalContext * ctx, TupleEvalNode * - expr, const Tuple * tuple, GString * out) -{ - TupleEvalNode *curr = expr; - TupleEvalVar *var0, *var1; - TupleValueType type0, type1; - int tmpi0, tmpi1; - char tmps[MAX_STR], *tmps0, *tmps1, *tmps2; - bool_t result; - int resulti; - - if (!expr) return FALSE; - - while (curr) { - const char *str = NULL; - - switch (curr->opcode) { - case OP_RAW: - str = curr->text; - break; - - case OP_FIELD: - var0 = GET_VAR (ctx, curr->var[0]); - - switch (var0->type) { - case TUPLE_VAR_FIELD: - if (tf_get_fieldval (var0, tuple)) { - switch (var0->ctype) { - case TUPLE_STRING: - str = var0->fieldstr; - break; - - case TUPLE_INT: - str_itoa (var0->defvali, tmps, sizeof (tmps)); - str = tmps; - break; - - default: - str = NULL; - } - } - break; - } - break; - - case OP_EQUALS: - case OP_NOT_EQUALS: - case OP_LT: case OP_LTEQ: - case OP_GT: case OP_GTEQ: - var0 = GET_VAR (ctx, curr->var[0]); - var1 = GET_VAR (ctx, curr->var[1]); - - type0 = tf_get_var(&tmps0, &tmpi0, var0, tuple); - type1 = tf_get_var(&tmps1, &tmpi1, var1, tuple); - result = FALSE; - - if (type0 != TUPLE_UNKNOWN && type1 != TUPLE_UNKNOWN) { - if (type0 == type1) { - if (type0 == TUPLE_STRING) - resulti = strcmp(tmps0, tmps1); - else - resulti = tmpi0 - tmpi1; - } else { - if (type0 == TUPLE_INT) - resulti = tmpi0 - atoi(tmps1); - else - resulti = atoi(tmps0) - tmpi1; - } - - switch (curr->opcode) { - case OP_EQUALS: result = (resulti == 0); break; - case OP_NOT_EQUALS: result = (resulti != 0); break; - case OP_LT: result = (resulti < 0); break; - case OP_LTEQ: result = (resulti <= 0); break; - case OP_GT: result = (resulti > 0); break; - case OP_GTEQ: result = (resulti >= 0); break; - default: result = FALSE; - } - } - - if (result && ! tuple_formatter_eval_do (ctx, curr->children, tuple, out)) - return FALSE; - break; - - case OP_EXISTS: - if (tf_get_fieldval (GET_VAR (ctx, curr->var[0]), tuple)) - { - if (! tuple_formatter_eval_do (ctx, curr->children, tuple, out)) - return FALSE; - } - break; - - case OP_IS_EMPTY: - var0 = GET_VAR (ctx, curr->var[0]); - - if (tf_get_fieldval (var0, tuple)) { - switch (var0->ctype) { - case TUPLE_INT: - result = (var0->defvali == 0); - break; - - case TUPLE_STRING: - result = TRUE; - tmps2 = var0->fieldstr; - - while (result && tmps2 && *tmps2 != '\0') { - if (g_ascii_isspace (* tmps2)) - tmps2 ++; - else - result = FALSE; - } - break; - - default: - result = TRUE; - } - } else - result = TRUE; - - if (result && ! tuple_formatter_eval_do (ctx, curr->children, tuple, out)) - return FALSE; - break; - - default: - tuple_error(ctx, "Unimplemented opcode %d!\n", curr->opcode); - return FALSE; - break; - } - - if (str) - g_string_append (out, str); - - curr = curr->next; - } - - return TRUE; -} - -void tuple_formatter_eval (TupleEvalContext * ctx, TupleEvalNode * expr, - const Tuple * tuple, GString * out) -{ - g_string_truncate (out, 0); - tuple_formatter_eval_do (ctx, expr, tuple, out); -} diff --git a/src/libaudcore/tuple_formatter.c b/src/libaudcore/tuple_formatter.c deleted file mode 100644 index 44c6e44..0000000 --- a/src/libaudcore/tuple_formatter.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * tuple_formatter.c - * Copyright (c) 2007-2013 William Pitcock and John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> - -#include "tuple.h" -#include "tuple_compiler.h" - -/* - * the tuple formatter: - * - * this is a data-driven meta-language which eventually hopes to be - * turing complete. - * - * language constructs follow the following basic rules: - * - begin with ${ - * - end with } - * - * language constructs: - * - ${field}: prints a field - * - ${?field:expr}: evaluates expr if field exists - * - ${=field,"value"}: defines field in the currently iterated - * tuple as string value of "value" - * - ${=field,value}: defines field in the currently iterated - * tuple as integer value of "value" - * - ${==field,field:expr}: evaluates expr if both fields are the same - * - ${!=field,field:expr}: evaluates expr if both fields are not the same - * - ${(empty)?field:expr}: evaluates expr if field is empty or does not exist - * - %{function:args,arg2,...}: runs function and inserts the result. - * - * everything else is treated as raw text. - */ - -struct _TupleFormatter -{ - TupleEvalContext * context; - TupleEvalNode * node; - GString * buf; -}; - -EXPORT TupleFormatter * tuple_formatter_new (const char * format) -{ - TupleFormatter * formatter = g_slice_new (TupleFormatter); - - formatter->context = tuple_evalctx_new (); - formatter->node = tuple_formatter_compile (formatter->context, format); - formatter->buf = g_string_sized_new (255); - - return formatter; -} - -EXPORT void tuple_formatter_free (TupleFormatter * formatter) -{ - tuple_evalctx_free (formatter->context); - tuple_evalnode_free (formatter->node); - g_string_free (formatter->buf, TRUE); - - g_slice_free (TupleFormatter, formatter); -} - -EXPORT char * tuple_format_title (TupleFormatter * formatter, const Tuple * tuple) -{ - tuple_formatter_eval (formatter->context, formatter->node, tuple, formatter->buf); - tuple_evalctx_reset (formatter->context); - - if (formatter->buf->len) - return str_get (formatter->buf->str); - - /* formatting failed, try fallbacks */ - static const int fallbacks[] = {FIELD_TITLE, FIELD_FILE_NAME}; - - for (int i = 0; i < ARRAY_LEN (fallbacks); i ++) - { - char * title = tuple_get_str (tuple, fallbacks[i]); - if (title) - return title; - } - - return str_get (""); -} diff --git a/src/libaudcore/util.cc b/src/libaudcore/util.cc new file mode 100644 index 0000000..f968982 --- /dev/null +++ b/src/libaudcore/util.cc @@ -0,0 +1,135 @@ +/* + * util.c + * Copyright 2009-2013 John Lindgren and MichaĆ Lipski + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "internal.h" + +#include <errno.h> +#include <pthread.h> +#include <string.h> +#include <unistd.h> + +#include <glib/gstdio.h> + +#include "audstrings.h" +#include "runtime.h" + +const char * get_home_utf8 () +{ + static pthread_once_t once = PTHREAD_ONCE_INIT; + static char * home_utf8; + + auto init = [] () + { home_utf8 = g_filename_to_utf8 (g_get_home_dir (), -1, nullptr, nullptr, nullptr); }; + + pthread_once (& once, init); + return home_utf8; +} + +bool dir_foreach (const char * path, DirForeachFunc func, void * user) +{ + GDir * dir = g_dir_open (path, 0, nullptr); + if (! dir) + return false; + + const char * name; + while ((name = g_dir_read_name (dir))) + { + if (func (filename_build ({path, name}), name, user)) + break; + } + + g_dir_close (dir); + return true; +} + +String write_temp_file (const void * data, int64_t len) +{ + StringBuf name = filename_build ({g_get_tmp_dir (), "audacious-temp-XXXXXX"}); + + int handle = g_mkstemp (name); + if (handle < 0) + { + AUDERR ("Error creating temporary file: %s\n", strerror (errno)); + return String (); + } + + while (len) + { + int64_t written = write (handle, data, len); + if (written < 0) + { + AUDERR ("Error writing %s: %s\n", (const char *) name, strerror (errno)); + close (handle); + return String (); + } + + data = (char *) data + written; + len -= written; + } + + if (close (handle) < 0) + { + AUDERR ("Error closing %s: %s\n", (const char *) name, strerror (errno)); + return String (); + } + + return String (name); +} + +bool same_basename (const char * a, const char * b) +{ + const char * dot_a = strrchr (a, '.'); + const char * dot_b = strrchr (b, '.'); + int len_a = dot_a ? dot_a - a : strlen (a); + int len_b = dot_b ? dot_b - b : strlen (b); + + return len_a == len_b && ! strcmp_nocase (a, b, len_a); +} + +const char * last_path_element (const char * path) +{ + const char * slash = strrchr (path, G_DIR_SEPARATOR); + return (slash && slash[1]) ? slash + 1 : nullptr; +} + +void cut_path_element (char * path, int pos) +{ +#ifdef _WIN32 + if (pos > 3) +#else + if (pos > 1) +#endif + path[pos - 1] = 0; /* overwrite slash */ + else + path[pos] = 0; /* leave [drive letter and] leading slash */ +} + +/* Thomas Wang's 32-bit mix function. See: + * http://web.archive.org/web/20070307172248/http://www.concentric.net/~Ttwang/tech/inthash.htm */ + +unsigned int32_hash (unsigned val) +{ + val = ~val + (val << 15); + val = val ^ (val >> 12); + val = val + (val << 2); + val = val ^ (val >> 4); + val = val * 2057; + val = val ^ (val >> 16); + return val; +} diff --git a/src/libaudcore/vfs.c b/src/libaudcore/vfs.c deleted file mode 100644 index 70f4608..0000000 --- a/src/libaudcore/vfs.c +++ /dev/null @@ -1,483 +0,0 @@ -/* - * vfs.c - * Copyright 2006-2013 William Pitcock, Daniel Barkalow, Ralf Ertzinger, - * Yoshiki Yazawa, Matti HĂ€mĂ€lĂ€inen, and John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include "vfs.h" - -#include <inttypes.h> -#include <stdio.h> -#include <string.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include <glib.h> -#include <glib/gstdio.h> - -#include "audstrings.h" -#include "vfs_local.h" - -#define VFS_SIG ('V' | ('F' << 8) | ('S' << 16)) - -/** - * @struct _VFSFile - * #VFSFile objects describe an opened VFS stream, basically being - * similar in purpose as stdio FILE - */ -struct _VFSFile { - char * uri; /**< The URI of the stream */ - VFSConstructor * base; /**< The base vtable used for VFS functions */ - void * handle; /**< Opaque data used by the transport plugins */ - int sig; /**< Used to detect invalid or twice-closed objects */ -}; - -/* Audacious core provides us with a function that looks up a VFS transport for - * a given URI scheme. Since this function will load plugins as needed, it can - * only be called from the main thread. When VFS is used from parallel threads, - * vfs_prepare must be called from the main thread to look up any needed - * transports beforehand. */ - -static VFSConstructor * (* lookup_func) (const char * scheme) = NULL; - -EXPORT void vfs_set_lookup_func (VFSConstructor * (* func) (const char * scheme)) -{ - lookup_func = func; -} - -static bool_t verbose = FALSE; - -EXPORT void vfs_set_verbose (bool_t set) -{ - verbose = set; -} - -static void logger (const char * format, ...) -{ - static char last[256] = ""; - static int repeated = 0; - - char buf[256]; - - va_list args; - va_start (args, format); - vsnprintf (buf, sizeof buf, format, args); - va_end (args); - - if (! strcmp (buf, last)) - repeated ++; - else - { - if (repeated) - { - printf ("VFS: (last message repeated %d times)\n", repeated); - repeated = 0; - } - - fputs (buf, stdout); - strcpy (last, buf); - } -} - -EXPORT VFSFile * vfs_new (const char * path, VFSConstructor * vtable, void * handle) -{ - VFSFile * file = g_slice_new (VFSFile); - file->uri = str_get (path); - file->base = vtable; - file->handle = handle; - file->sig = VFS_SIG; - return file; -} - -EXPORT const char * vfs_get_filename (VFSFile * file) -{ - return file->uri; -} - -EXPORT void * vfs_get_handle (VFSFile * file) -{ - return file->handle; -} - -/** - * Opens a stream from a VFS transport using one of the registered - * #VFSConstructor handlers. - * - * @param path The path or URI to open. - * @param mode The preferred access privileges (not guaranteed). - * @return On success, a #VFSFile object representing the stream. - */ -EXPORT VFSFile * -vfs_fopen(const char * path, - const char * mode) -{ - g_return_val_if_fail (path && mode, NULL); - - VFSConstructor * vtable = NULL; - - if (! strncmp (path, "file://", 7)) - vtable = & vfs_local_vtable; - else - { - const char * s = strstr (path, "://"); - - if (! s) - { - fprintf (stderr, "Invalid URI: %s\n", path); - return NULL; - } - - SNCOPY (scheme, path, s - path); - - if (! (vtable = lookup_func (scheme))) - return NULL; - } - - const gchar * sub; - uri_parse (path, NULL, NULL, & sub, NULL); - - SNCOPY (buf, path, sub - path); - - void * handle = vtable->vfs_fopen_impl (buf, mode); - if (! handle) - return NULL; - - VFSFile * file = vfs_new (path, vtable, handle); - - if (verbose) - logger ("VFS: <%p> open (mode %s) %s\n", file, mode, path); - - return file; -} - -/** - * Closes a VFS stream and destroys a #VFSFile object. - * - * @param file A #VFSFile object to destroy. - * @return -1 on failure, 0 on success. - */ -EXPORT int -vfs_fclose(VFSFile * file) -{ - g_return_val_if_fail (file && file->sig == VFS_SIG, -1); - - if (verbose) - logger ("VFS: <%p> close\n", file); - - int ret = 0; - - if (file->base->vfs_fclose_impl(file) != 0) - ret = -1; - - str_unref (file->uri); - g_slice_free (VFSFile, file); - - return ret; -} - -/** - * Reads from a VFS stream. - * - * @param ptr A pointer to the destination buffer. - * @param size The size of each element to read. - * @param nmemb The number of elements to read. - * @param file #VFSFile object that represents the VFS stream. - * @return The number of elements succesfully read. - */ -EXPORT int64_t vfs_fread (void * ptr, int64_t size, int64_t nmemb, VFSFile * file) -{ - g_return_val_if_fail (file && file->sig == VFS_SIG, 0); - - int64_t readed = file->base->vfs_fread_impl (ptr, size, nmemb, file); - -/* if (verbose) - logger ("VFS: <%p> read %"PRId64" elements of size %"PRId64" = " - "%"PRId64"\n", file, nmemb, size, readed); */ - - return readed; -} - -/** - * Writes to a VFS stream. - * - * @param ptr A const pointer to the source buffer. - * @param size The size of each element to write. - * @param nmemb The number of elements to write. - * @param file #VFSFile object that represents the VFS stream. - * @return The number of elements succesfully written. - */ -EXPORT int64_t vfs_fwrite (const void * ptr, int64_t size, int64_t nmemb, VFSFile * file) -{ - g_return_val_if_fail (file && file->sig == VFS_SIG, 0); - - int64_t written = file->base->vfs_fwrite_impl (ptr, size, nmemb, file); - - if (verbose) - logger ("VFS: <%p> write %"PRId64" elements of size %"PRId64" = " - "%"PRId64"\n", file, nmemb, size, written); - - return written; -} - -/** - * Reads a character from a VFS stream. - * - * @param file #VFSFile object that represents the VFS stream. - * @return On success, a character. Otherwise, EOF. - */ -EXPORT int -vfs_getc(VFSFile *file) -{ - unsigned char c; - - if (vfs_fread (& c, 1, 1, file) != 1) - return EOF; - - return c; -} - -/** - * Pushes a character back to the VFS stream. - * - * @param c The character to push back. - * @param file #VFSFile object that represents the VFS stream. - * @return On success, 0. Otherwise, EOF. - */ -EXPORT int -vfs_ungetc(int c, VFSFile *file) -{ - if (vfs_fseek (file, -1, SEEK_CUR) < 0) - return EOF; - - return c; -} - -/** - * Performs a seek in given VFS stream. Standard C-style values - * of whence can be used to indicate desired action. - * - * - SEEK_CUR seeks relative to current stream position. - * - SEEK_SET seeks to given absolute position (relative to stream beginning). - * - SEEK_END sets stream position to current file end. - * - * @param file #VFSFile object that represents the VFS stream. - * @param offset The offset to seek to. - * @param whence Type of the seek: SEEK_CUR, SEEK_SET or SEEK_END. - * @return On success, 0. Otherwise, -1. - */ -EXPORT int -vfs_fseek(VFSFile * file, - int64_t offset, - int whence) -{ - g_return_val_if_fail (file && file->sig == VFS_SIG, -1); - - if (verbose) - logger ("VFS: <%p> seek to %"PRId64" from %s\n", file, offset, whence == - SEEK_CUR ? "current" : whence == SEEK_SET ? "beginning" : whence == - SEEK_END ? "end" : "invalid"); - - if (! file->base->vfs_fseek_impl (file, offset, whence)) - return 0; - - if (verbose) - logger ("VFS: <%p> seek failed!\n", file); - - return -1; -} - -/** - * Returns the current position in the VFS stream's buffer. - * - * @param file #VFSFile object that represents the VFS stream. - * @return On success, the current position. Otherwise, -1. - */ -EXPORT int64_t -vfs_ftell(VFSFile * file) -{ - g_return_val_if_fail (file && file->sig == VFS_SIG, -1); - - int64_t told = file->base->vfs_ftell_impl (file); - - if (verbose) - logger ("VFS: <%p> tell = %"PRId64"\n", file, told); - - return told; -} - -/** - * Returns whether or not the VFS stream has reached EOF. - * - * @param file #VFSFile object that represents the VFS stream. - * @return On success, whether or not the VFS stream is at EOF. Otherwise, FALSE. - */ -EXPORT bool_t -vfs_feof(VFSFile * file) -{ - g_return_val_if_fail (file && file->sig == VFS_SIG, TRUE); - - bool_t eof = file->base->vfs_feof_impl (file); - - if (verbose) - logger ("VFS: <%p> eof = %s\n", file, eof ? "yes" : "no"); - - return eof; -} - -/** - * Truncates a VFS stream to a certain size. - * - * @param file #VFSFile object that represents the VFS stream. - * @param length The length to truncate at. - * @return On success, 0. Otherwise, -1. - */ -EXPORT int vfs_ftruncate (VFSFile * file, int64_t length) -{ - g_return_val_if_fail (file && file->sig == VFS_SIG, -1); - - if (verbose) - logger ("VFS: <%p> truncate to %"PRId64"\n", file, length); - - return file->base->vfs_ftruncate_impl(file, length); -} - -/** - * Returns size of the file. - * - * @param file #VFSFile object that represents the VFS stream. - * @return On success, the size of the file in bytes. Otherwise, -1. - */ -EXPORT int64_t vfs_fsize (VFSFile * file) -{ - g_return_val_if_fail (file && file->sig == VFS_SIG, -1); - - int64_t size = file->base->vfs_fsize_impl (file); - - if (verbose) - logger ("VFS: <%p> size = %"PRId64"\n", file, size); - - return size; -} - -/** - * Returns metadata about the stream. - * - * @param file #VFSFile object that represents the VFS stream. - * @param field The string constant field name to get. - * @return On success, a copy of the value of the field. Otherwise, NULL. - */ -EXPORT char * -vfs_get_metadata(VFSFile * file, const char * field) -{ - g_return_val_if_fail (file && file->sig == VFS_SIG, NULL); - - if (file->base->vfs_get_metadata_impl) - return file->base->vfs_get_metadata_impl(file, field); - return NULL; -} - -/** - * Wrapper for g_file_test(). - * - * @param path A path to test. - * @param test A GFileTest to run. - * @return The result of g_file_test(). - */ -EXPORT bool_t -vfs_file_test(const char * path, int test) -{ - if (strncmp (path, "file://", 7)) - return FALSE; /* only local files are handled */ - - char * path2 = uri_to_filename (path); - if (! path2) - return FALSE; - -#ifdef S_ISLNK - if (test & VFS_IS_SYMLINK) - { - GStatBuf st; - if (g_lstat (path2, & st) < 0) - goto DONE; - - if (S_ISLNK (st.st_mode)) - test &= ~VFS_IS_SYMLINK; - } -#endif - - if (test & (VFS_IS_REGULAR | VFS_IS_DIR | VFS_IS_EXECUTABLE | VFS_EXISTS)) - { - GStatBuf st; - if (g_stat (path2, & st) < 0) - goto DONE; - - if (S_ISREG (st.st_mode)) - test &= ~VFS_IS_REGULAR; - if (S_ISDIR (st.st_mode)) - test &= ~VFS_IS_DIR; - if (st.st_mode & S_IXUSR) - test &= ~VFS_IS_EXECUTABLE; - - test &= ~VFS_EXISTS; - } - -DONE: - str_unref (path2); - return ! test; -} - -/** - * Tests if a file is writeable. - * - * @param path A path to test. - * @return TRUE if the file is writeable, otherwise FALSE. - */ -EXPORT bool_t -vfs_is_writeable(const char * path) -{ - GStatBuf info; - char * realfn = uri_to_filename (path); - - if (! realfn || g_stat (realfn, & info) < 0) - return FALSE; - - str_unref (realfn); - return (info.st_mode & S_IWUSR); -} - -/** - * Tests if a path is remote uri. - * - * @param path A path to test. - * @return TRUE if the file is remote, otherwise FALSE. - */ -EXPORT bool_t vfs_is_remote (const char * path) -{ - return strncmp (path, "file://", 7) ? TRUE : FALSE; -} - -/** - * Tests if a file is associated to streaming. - * - * @param file A #VFSFile object to test. - * @return TRUE if the file is streaming, otherwise FALSE. - */ -EXPORT bool_t vfs_is_streaming (VFSFile * file) -{ - return (vfs_fsize (file) < 0); -} diff --git a/src/libaudcore/vfs.cc b/src/libaudcore/vfs.cc new file mode 100644 index 0000000..900077b --- /dev/null +++ b/src/libaudcore/vfs.cc @@ -0,0 +1,340 @@ +/* + * vfs.c + * Copyright 2006-2013 William Pitcock, Daniel Barkalow, Ralf Ertzinger, + * Yoshiki Yazawa, Matti HĂ€mĂ€lĂ€inen, and John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "vfs.h" + +#define __STDC_FORMAT_MACROS +#include <inttypes.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <glib/gstdio.h> + +#include "audstrings.h" +#include "i18n.h" +#include "plugin.h" +#include "plugins-internal.h" +#include "runtime.h" +#include "vfs_local.h" + +static TransportPlugin * lookup_transport (const char * scheme) +{ + for (PluginHandle * plugin : aud_plugin_list (PluginType::Transport)) + { + if (! aud_plugin_get_enabled (plugin)) + continue; + + if (transport_plugin_has_scheme (plugin, scheme)) + return (TransportPlugin *) aud_plugin_get_header (plugin); + } + + return nullptr; +} + +/** + * Opens a stream from a VFS transport using one of the registered + * #VFSConstructor handlers. + * + * @param path The path or URI to open. + * @param mode The preferred access privileges (not guaranteed). + * @return On success, a #VFSFile object representing the stream. + */ +EXPORT VFSFile::VFSFile (const char * filename, const char * mode) +{ + StringBuf scheme = uri_get_scheme (filename); + if (! scheme) + { + AUDERR ("Invalid URI: %s\n", filename); + m_error = String (_("Invalid URI")); + return; + } + + const char * sub; + uri_parse (filename, nullptr, nullptr, & sub, nullptr); + StringBuf nosub = str_copy (filename, sub - filename); + + if (! strcmp (scheme, "file")) + m_impl.capture (vfs_local_fopen (nosub, mode, m_error)); + else + { + TransportPlugin * tp = lookup_transport (scheme); + if (! tp) + { + AUDERR ("Unknown URI scheme: %s://", (const char *) scheme); + m_error = String (_("Unknown URI scheme")); + return; + } + + m_impl.capture (tp->fopen (nosub, mode, m_error)); + } + + if (! m_impl) + return; + + AUDINFO ("<%p> open (mode %s) %s\n", m_impl.get (), mode, filename); + m_filename = String (filename); +} + +/** + * Reads from a VFS stream. + * + * @param ptr A pointer to the destination buffer. + * @param size The size of each element to read. + * @param nmemb The number of elements to read. + * @param file #VFSFile object that represents the VFS stream. + * @return The number of elements succesfully read. + */ +EXPORT int64_t VFSFile::fread (void * ptr, int64_t size, int64_t nmemb) +{ + int64_t readed = m_impl->fread (ptr, size, nmemb); + + AUDDBG ("<%p> read %" PRId64 " elements of size %" PRId64 " = %" PRId64 "\n", + this, nmemb, size, readed); + + return readed; +} + +/** + * Writes to a VFS stream. + * + * @param ptr A const pointer to the source buffer. + * @param size The size of each element to write. + * @param nmemb The number of elements to write. + * @param file #VFSFile object that represents the VFS stream. + * @return The number of elements succesfully written. + */ +EXPORT int64_t VFSFile::fwrite (const void * ptr, int64_t size, int64_t nmemb) +{ + int64_t written = m_impl->fwrite (ptr, size, nmemb); + + AUDDBG ("<%p> write %" PRId64 " elements of size %" PRId64 " = %" PRId64 "\n", + m_impl.get (), nmemb, size, written); + + return written; +} + +/** + * Performs a seek in given VFS stream. Standard C-style values + * of whence can be used to indicate desired action. + * + * - SEEK_CUR seeks relative to current stream position. + * - SEEK_SET seeks to given absolute position (relative to stream beginning). + * - SEEK_END sets stream position to current file end. + * + * @param file #VFSFile object that represents the VFS stream. + * @param offset The offset to seek to. + * @param whence Type of the seek: SEEK_CUR, SEEK_SET or SEEK_END. + * @return On success, 0. Otherwise, -1. + */ +EXPORT int VFSFile::fseek (int64_t offset, VFSSeekType whence) +{ + AUDDBG ("<%p> seek to %" PRId64 " from %s\n", m_impl.get (), offset, + whence == SEEK_CUR ? "current" : whence == SEEK_SET ? "beginning" : + whence == SEEK_END ? "end" : "invalid"); + + if (! m_impl->fseek (offset, whence)) + return 0; + + AUDDBG ("<%p> seek failed!\n", m_impl.get ()); + + return -1; +} + +/** + * Returns the current position in the VFS stream's buffer. + * + * @param file #VFSFile object that represents the VFS stream. + * @return On success, the current position. Otherwise, -1. + */ +EXPORT int64_t VFSFile::ftell () +{ + int64_t told = m_impl->ftell (); + + AUDDBG ("<%p> tell = %" PRId64 "\n", m_impl.get (), told); + + return told; +} + +/** + * Returns whether or not the VFS stream has reached EOF. + * + * @param file #VFSFile object that represents the VFS stream. + * @return On success, whether or not the VFS stream is at EOF. Otherwise, false. + */ +EXPORT bool VFSFile::feof () +{ + bool eof = m_impl->feof (); + + AUDDBG ("<%p> eof = %s\n", m_impl.get (), eof ? "yes" : "no"); + + return eof; +} + +/** + * Truncates a VFS stream to a certain size. + * + * @param file #VFSFile object that represents the VFS stream. + * @param length The length to truncate at. + * @return On success, 0. Otherwise, -1. + */ +EXPORT int VFSFile::ftruncate (int64_t length) +{ + AUDDBG ("<%p> truncate to %" PRId64 "\n", m_impl.get (), length); + + if (! m_impl->ftruncate (length)) + return 0; + + AUDDBG ("<%p> truncate failed!\n", m_impl.get ()); + + return -1; +} + +EXPORT int VFSFile::fflush () +{ + AUDDBG ("<%p> flush\n", m_impl.get ()); + + if (! m_impl->fflush ()) + return 0; + + AUDDBG ("<%p> flush failed!\n", m_impl.get ()); + + return -1; +} + +/** + * Returns size of the file. + * + * @param file #VFSFile object that represents the VFS stream. + * @return On success, the size of the file in bytes. Otherwise, -1. + */ +EXPORT int64_t VFSFile::fsize () +{ + int64_t size = m_impl->fsize (); + + AUDDBG ("<%p> size = %" PRId64 "\n", m_impl.get (), size); + + return size; +} + +/** + * Returns metadata about the stream. + * + * @param file #VFSFile object that represents the VFS stream. + * @param field The string constant field name to get. + * @return On success, a copy of the value of the field. Otherwise, nullptr. + */ +EXPORT String VFSFile::get_metadata (const char * field) +{ + return m_impl->get_metadata (field); +} + +EXPORT Index<char> VFSFile::read_all () +{ + constexpr int maxbuf = 16777216; + constexpr int pagesize = 4096; + + Index<char> buf; + int64_t size = fsize (); + int64_t pos = ftell (); + + if (size >= 0 && pos >= 0 && pos <= size) + { + buf.insert (0, aud::min (size - pos, (int64_t) maxbuf)); + size = fread (buf.begin (), 1, buf.len ()); + } + else + { + size = 0; + + buf.insert (0, pagesize); + + int64_t readsize; + while ((readsize = fread (& buf[size], 1, buf.len () - size))) + { + size += readsize; + + if (size == buf.len ()) + { + if (buf.len () > maxbuf - pagesize) + break; + + buf.insert (-1, pagesize); + } + } + } + + buf.remove (size, -1); + + return buf; +} + +/** + * Wrapper for g_file_test(). + * + * @param path A path to test. + * @param test A GFileTest to run. + * @return The result of g_file_test(). + */ +EXPORT bool VFSFile::test_file (const char * path, VFSFileTest test) +{ + if (strncmp (path, "file://", 7)) + return false; /* only local files are handled */ + + const char * sub; + uri_parse (path, nullptr, nullptr, & sub, nullptr); + + StringBuf no_sub = str_copy (path, sub - path); + + StringBuf path2 = uri_to_filename (no_sub); + if (! path2) + return false; + +#ifdef S_ISLNK + if (test & VFS_IS_SYMLINK) + { + GStatBuf st; + if (g_lstat (path2, & st) < 0) + return false; + + if (S_ISLNK (st.st_mode)) + test = (VFSFileTest) (test & ~VFS_IS_SYMLINK); + } +#endif + + if (test & (VFS_IS_REGULAR | VFS_IS_DIR | VFS_IS_EXECUTABLE | VFS_EXISTS)) + { + GStatBuf st; + if (g_stat (path2, & st) < 0) + return false; + + if (S_ISREG (st.st_mode)) + test = (VFSFileTest) (test & ~VFS_IS_REGULAR); + if (S_ISDIR (st.st_mode)) + test = (VFSFileTest) (test & ~VFS_IS_DIR); + if (st.st_mode & S_IXUSR) + test = (VFSFileTest) (test & ~VFS_IS_EXECUTABLE); + + test = (VFSFileTest) (test & ~VFS_EXISTS); + } + + return ! test; +} diff --git a/src/libaudcore/vfs.h b/src/libaudcore/vfs.h index 8dcbc32..1a3c909 100644 --- a/src/libaudcore/vfs.h +++ b/src/libaudcore/vfs.h @@ -29,106 +29,108 @@ #include <stdint.h> -#include <libaudcore/core.h> +#include <libaudcore/index.h> +#include <libaudcore/objects.h> + +enum VFSFileTest { + VFS_IS_REGULAR = (1 << 0), + VFS_IS_SYMLINK = (1 << 1), + VFS_IS_DIR = (1 << 2), + VFS_IS_EXECUTABLE = (1 << 3), + VFS_EXISTS = (1 << 4) +}; -/* equivalent to G_FILE_TEST_XXX */ -#define VFS_IS_REGULAR (1 << 0) -#define VFS_IS_SYMLINK (1 << 1) -#define VFS_IS_DIR (1 << 2) -#define VFS_IS_EXECUTABLE (1 << 3) -#define VFS_EXISTS (1 << 4) +enum VFSSeekType { + VFS_SEEK_SET = 0, + VFS_SEEK_CUR = 1, + VFS_SEEK_END = 2 +}; -/** @struct VFSFile */ -typedef struct _VFSFile VFSFile; -/** @struct VFSConstructor */ -typedef const struct _VFSConstructor VFSConstructor; +#ifdef WANT_VFS_STDIO_COMPAT -/** - * @struct _VFSConstructor - * #VFSConstructor objects contain the base vtables used for extrapolating - * a VFS stream. #VFSConstructor objects should be considered %virtual in - * nature. VFS base vtables are registered via vfs_register_transport(). - */ -struct _VFSConstructor { - /** A function pointer which points to a fopen implementation. */ - void * (* vfs_fopen_impl) (const char * filename, const char * mode); - /** A function pointer which points to a fclose implementation. */ - int (* vfs_fclose_impl) (VFSFile * file); - - /** A function pointer which points to a fread implementation. */ - int64_t (* vfs_fread_impl) (void * ptr, int64_t size, int64_t nmemb, VFSFile * - file); - /** A function pointer which points to a fwrite implementation. */ - int64_t (* vfs_fwrite_impl) (const void * ptr, int64_t size, int64_t nmemb, - VFSFile * file); - - void (* obs_getc) (void); // obsolete - void (* obs_ungetc) (void); // obsolete - - /** A function pointer which points to a fseek implementation. */ - int (* vfs_fseek_impl) (VFSFile * file, int64_t offset, int whence); - - void (* obs_rewind) (void); // obsolete - - /** A function pointer which points to a ftell implementation. */ - int64_t (* vfs_ftell_impl) (VFSFile * file); - /** A function pointer which points to a feof implementation. */ - bool_t (* vfs_feof_impl) (VFSFile * file); - /** A function pointer which points to a ftruncate implementation. */ - int (* vfs_ftruncate_impl) (VFSFile * file, int64_t length); - /** A function pointer which points to a fsize implementation. */ - int64_t (* vfs_fsize_impl) (VFSFile * file); - - /** A function pointer which points to a (stream) metadata fetching implementation. */ - char * (* vfs_get_metadata_impl) (VFSFile * file, const char * field); +#include <stdio.h> + +constexpr int from_vfs_seek_type (VFSSeekType whence) +{ + return (whence == VFS_SEEK_SET) ? SEEK_SET : + (whence == VFS_SEEK_CUR) ? SEEK_CUR : + (whence == VFS_SEEK_END) ? SEEK_END : -1; +} + +constexpr VFSSeekType to_vfs_seek_type (int whence) +{ + return (whence == SEEK_SET) ? VFS_SEEK_SET : + (whence == SEEK_CUR) ? VFS_SEEK_CUR : + (whence == SEEK_END) ? VFS_SEEK_END : (VFSSeekType) -1; +} + +#endif // WANT_VFS_STDIO_COMPAT + +class VFSImpl +{ +public: + VFSImpl () {} + virtual ~VFSImpl () {} + + VFSImpl (const VFSImpl &) = delete; + VFSImpl & operator= (const VFSImpl &) = delete; + + virtual int64_t fread (void * ptr, int64_t size, int64_t nmemb) = 0; + virtual int fseek (int64_t offset, VFSSeekType whence) = 0; + + virtual int64_t ftell () = 0; + virtual int64_t fsize () = 0; + virtual bool feof () = 0; + + virtual int64_t fwrite (const void * ptr, int64_t size, int64_t nmemb) = 0; + virtual int ftruncate (int64_t length) = 0; + virtual int fflush () = 0; + + virtual String get_metadata (const char * field) { return String (); } }; -#ifdef __GNUC__ -#define WARN_RETURN __attribute__ ((warn_unused_result)) -#else -#define WARN_RETURN -#endif +class VFSFile +{ +public: + VFSFile () {} + + VFSFile (const char * filename, VFSImpl * impl) : + m_filename (filename), + m_impl (impl) {} -VFSFile * vfs_new (const char * path, VFSConstructor * vtable, void * handle) WARN_RETURN; -const char * vfs_get_filename (VFSFile * file) WARN_RETURN; -void * vfs_get_handle (VFSFile * file) WARN_RETURN; + VFSFile (const char * filename, const char * mode); -VFSFile * vfs_fopen (const char * path, const char * mode) WARN_RETURN; -int vfs_fclose (VFSFile * file); + explicit operator bool () const + { return (bool) m_impl; } + const char * filename () const + { return m_filename; } + const char * error () const + { return m_error; } -int64_t vfs_fread (void * ptr, int64_t size, int64_t nmemb, VFSFile * file) - WARN_RETURN; -int64_t vfs_fwrite (const void * ptr, int64_t size, int64_t nmemb, VFSFile * file) - WARN_RETURN; + /* basic operations */ -int vfs_getc (VFSFile * stream) WARN_RETURN; -int vfs_ungetc (int c, VFSFile * stream) WARN_RETURN; -char * vfs_fgets (char * s, int n, VFSFile * stream) WARN_RETURN; -int vfs_fputs (const char * s, VFSFile * stream) WARN_RETURN; -bool_t vfs_feof (VFSFile * file) WARN_RETURN; -int vfs_fprintf (VFSFile * stream, char const * format, ...) __attribute__ - ((__format__ (__printf__, 2, 3))); + int64_t fread (void * ptr, int64_t size, int64_t nmemb) __attribute__ ((warn_unused_result)); + int fseek (int64_t offset, VFSSeekType whence) __attribute__ ((warn_unused_result)); -int vfs_fseek (VFSFile * file, int64_t offset, int whence) WARN_RETURN; -int64_t vfs_ftell (VFSFile * file) WARN_RETURN; -int64_t vfs_fsize (VFSFile * file) WARN_RETURN; -int vfs_ftruncate (VFSFile * file, int64_t length) WARN_RETURN; + int64_t ftell (); + int64_t fsize (); + bool feof (); -bool_t vfs_is_streaming (VFSFile * file) WARN_RETURN; + int64_t fwrite (const void * ptr, int64_t size, int64_t nmemb) __attribute__ ((warn_unused_result)); + int ftruncate (int64_t length) __attribute__ ((warn_unused_result)); + int fflush () __attribute__ ((warn_unused_result)); -/* free returned string with str_unref() */ -char * vfs_get_metadata (VFSFile * file, const char * field) WARN_RETURN; + String get_metadata (const char * field); -bool_t vfs_file_test (const char * path, int test) WARN_RETURN; -bool_t vfs_is_writeable (const char * path) WARN_RETURN; -bool_t vfs_is_remote (const char * path) WARN_RETURN; + /* utility functions */ -void vfs_file_read_all (VFSFile * file, void * * buf, int64_t * size); -void vfs_file_get_contents (const char * filename, void * * buf, int64_t * size); + Index<char> read_all (); -void vfs_set_lookup_func (VFSConstructor * (* func) (const char * scheme)); -void vfs_set_verbose (bool_t verbose); + static bool test_file (const char * path, VFSFileTest test); -#undef WARN_RETURN +private: + String m_filename, m_error; + SmartPtr<VFSImpl> m_impl; +}; #endif /* LIBAUDCORE_VFS_H */ diff --git a/src/libaudcore/vfs_async.c b/src/libaudcore/vfs_async.c deleted file mode 100644 index d8864c6..0000000 --- a/src/libaudcore/vfs_async.c +++ /dev/null @@ -1,73 +0,0 @@ -/* - * vfs_async.c - * Copyright 2010-2012 William Pitcock and John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <pthread.h> - -#include "vfs_async.h" - -typedef struct { - char * filename; /* pooled */ - void *buf; - int64_t size; - pthread_t thread; - void * userdata; - - VFSConsumer cons_f; -} VFSAsyncTrampoline; - -bool_t -vfs_async_file_get_contents_trampoline(void * data) -{ - VFSAsyncTrampoline *tr = data; - - pthread_join (tr->thread, NULL); - - tr->cons_f(tr->buf, tr->size, tr->userdata); - - str_unref (tr->filename); - g_slice_free(VFSAsyncTrampoline, tr); - - return FALSE; -} - -void * -vfs_async_file_get_contents_worker(void * data) -{ - VFSAsyncTrampoline *tr = data; - - vfs_file_get_contents(tr->filename, &tr->buf, &tr->size); - - g_idle_add_full(G_PRIORITY_HIGH_IDLE, vfs_async_file_get_contents_trampoline, tr, NULL); - - return NULL; -} - -EXPORT void -vfs_async_file_get_contents(const char *filename, VFSConsumer cons_f, void * userdata) -{ - VFSAsyncTrampoline *tr; - - tr = g_slice_new0(VFSAsyncTrampoline); - tr->filename = str_get (filename); - tr->cons_f = cons_f; - tr->userdata = userdata; - - pthread_create (& tr->thread, NULL, vfs_async_file_get_contents_worker, tr); -} diff --git a/src/libaudcore/vfs_async.cc b/src/libaudcore/vfs_async.cc new file mode 100644 index 0000000..8939236 --- /dev/null +++ b/src/libaudcore/vfs_async.cc @@ -0,0 +1,91 @@ +/* + * vfs_async.cc + * Copyright 2010-2014 William Pitcock and John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <pthread.h> + +#include "list.h" +#include "mainloop.h" +#include "vfs.h" +#include "vfs_async.h" + +struct QueuedData : public ListNode +{ + const String filename; + const VFSConsumer cons_f; + void * const user; + + pthread_t thread; + + Index<char> buf; + + QueuedData (const char * filename, VFSConsumer cons_f, void * user) : + filename (filename), + cons_f (cons_f), + user (user) {} +}; + +static QueuedFunc queued_func; +static List<QueuedData> queue; +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +static void send_data (void *) +{ + pthread_mutex_lock (& mutex); + + QueuedData * data; + while ((data = queue.head ())) + { + queue.remove (data); + + pthread_mutex_unlock (& mutex); + + pthread_join (data->thread, nullptr); + data->cons_f (data->filename, data->buf, data->user); + delete data; + + pthread_mutex_lock (& mutex); + } + + pthread_mutex_unlock (& mutex); +} + +static void * read_worker (void * data0) +{ + auto data = (QueuedData *) data0; + + VFSFile file (data->filename, "r"); + if (file) + data->buf = file.read_all (); + + pthread_mutex_lock (& mutex); + + if (! queue.head ()) + queued_func.queue (send_data, nullptr); + + queue.append (data); + + pthread_mutex_unlock (& mutex); + return nullptr; +} + +EXPORT void vfs_async_file_get_contents (const char * filename, VFSConsumer cons_f, void * user) +{ + auto data = new QueuedData (filename, cons_f, user); + pthread_create (& data->thread, nullptr, read_worker, data); +} diff --git a/src/libaudcore/vfs_async.h b/src/libaudcore/vfs_async.h index 9503013..b77f88b 100644 --- a/src/libaudcore/vfs_async.h +++ b/src/libaudcore/vfs_async.h @@ -20,10 +20,10 @@ #ifndef LIBAUDCORE_VFS_ASYNC_H #define LIBAUDCORE_VFS_ASYNC_H -#include <libaudcore/vfs.h> +#include <libaudcore/index.h> -typedef bool_t (*VFSConsumer)(void * buf, int64_t size, void * userdata); +typedef void (* VFSConsumer) (const char * filename, const Index<char> & buf, void * user); -void vfs_async_file_get_contents(const char *filename, VFSConsumer cons_f, void * userdata); +void vfs_async_file_get_contents (const char * filename, VFSConsumer cons_f, void * user); #endif diff --git a/src/libaudcore/vfs_common.c b/src/libaudcore/vfs_common.c deleted file mode 100644 index 18b05c7..0000000 --- a/src/libaudcore/vfs_common.c +++ /dev/null @@ -1,198 +0,0 @@ -/* - * vfs_common.c - * Copyright 2006-2013 Tony Vroon, William Pitcock, Matti HĂ€mĂ€lĂ€inen, and - * John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <stdio.h> -#include <string.h> - -#include <glib.h> - -#include "audstrings.h" -#include "vfs.h" - -/** - * @file vfs_common.c - * Common code for various VFS-stream related operations. - * Routines for string reading and writing and functions for - * reading and writing endianess-dependant integer values. - */ - - -/** - * Writes a character to a stream. - * - * @param c A character to write to the stream. - * @param stream A #VFSFile object representing the stream. - * @return The character on success, or EOF. - */ -EXPORT int vfs_fputc(int c, VFSFile *stream) -{ - unsigned char uc = (unsigned char) c; - - if (!vfs_fwrite(&uc, 1, 1, stream)) { - return EOF; - } - - return uc; -} - -/** - * Reads a string of characters from a stream, ending in newline or EOF. - * - * @param s A buffer to put the string in. - * @param n The amount of characters to read. - * @param stream A #VFSFile object representing the stream. - * @return The string on success, or NULL. - */ -EXPORT char *vfs_fgets(char *s, int n, VFSFile *stream) -{ - int c; - register char *p; - - if (n <= 0) return NULL; - - p = s; - - while (--n) { - if ((c = vfs_getc(stream))== EOF) { - break; - } - if ((*p++ = c) == '\n') { - break; - } - } - if (p > s) { - *p = 0; - return s; - } - - return NULL; -} - -/** - * Writes a string to a VFS stream. - * - * @param s A string to write to the stream. - * @param stream A #VFSFile object representing the stream. - * @return The amount of bytes written. - */ -EXPORT int vfs_fputs(const char *s, VFSFile *stream) -{ - int64_t n = strlen(s); - - return ((vfs_fwrite(s, 1, n, stream) == n) ? n : EOF); -} - -/** - * Writes a formatted string to a VFS stream via a va_list of args. - * - * @param stream A #VFSFile object representing the stream. - * @param format A printf-style format string. - * @param args A va_list of args to use. - * @return value The amount of bytes written. - */ -EXPORT int vfs_vfprintf(VFSFile *stream, char const *format, va_list args) -{ - VSPRINTF (buf, format, args); - return vfs_fputs (buf, stream); -} - -/** - * Writes a formatted string to a VFS stream. - * - * @param stream A #VFSFile object representing the stream. - * @param format A printf-style format string. - * @param ... Optional list of arguments. - * @return The amount of bytes written. - */ -EXPORT int vfs_fprintf(VFSFile *stream, char const *format, ...) -{ - va_list arg; - int rv; - - va_start(arg, format); - rv = vfs_vfprintf(stream, format, arg); - va_end(arg); - - return rv; -} - -EXPORT void vfs_file_read_all (VFSFile * file, void * * bufp, int64_t * sizep) -{ - char * buf = NULL; - int64_t size = vfs_fsize (file); - - if (size >= 0) - { - size = MIN (size, SIZE_MAX - 1); - buf = g_malloc (size + 1); - size = vfs_fread (buf, 1, size, file); - } - else - { - size = 0; - - size_t bufsize = 4096; - buf = g_malloc (bufsize); - - size_t readsize; - while ((readsize = vfs_fread (buf + size, 1, bufsize - 1 - size, file))) - { - size += readsize; - - if (size == bufsize - 1) - { - if (bufsize > SIZE_MAX - 4096) - break; - - bufsize += 4096; - buf = g_realloc (buf, bufsize); - } - } - } - - buf[size] = 0; // nul-terminate - - * bufp = buf; - if (sizep) - * sizep = size; -} - -/** - * Gets contents of the file into a buffer. Buffer of filesize bytes - * is allocated by this function as necessary. - * - * @param filename URI of the file to read in. - * @param buf Pointer to a pointer variable of buffer. - * @param size Pointer to gsize variable that will hold the amount of - * read data e.g. filesize. - */ -EXPORT void vfs_file_get_contents (const char * filename, void * * buf, int64_t * size) -{ - * buf = NULL; - if (size) - * size = 0; - - VFSFile * file = vfs_fopen (filename, "r"); - if (! file) - return; - - vfs_file_read_all (file, buf, size); - vfs_fclose (file); -} diff --git a/src/libaudcore/vfs_local.c b/src/libaudcore/vfs_local.c deleted file mode 100644 index 196cc32..0000000 --- a/src/libaudcore/vfs_local.c +++ /dev/null @@ -1,241 +0,0 @@ -/* - * vfs_local.c - * Copyright 2009-2014 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include "vfs_local.h" - -#include <errno.h> -#include <stdio.h> -#include <string.h> -#include <unistd.h> - -#include <glib.h> -#include <glib/gstdio.h> - -#include "audstrings.h" - -#ifdef _WIN32 -#define fseeko fseeko64 -#define ftello ftello64 -#endif - -typedef enum { - OP_NONE, - OP_READ, - OP_WRITE -} LocalOp; - -typedef struct { - char * path; - FILE * stream; - int64_t cached_size; - LocalOp last_op; -} LocalFile; - -static void * local_fopen (const char * uri, const char * mode) -{ - char * path = uri_to_filename (uri); - g_return_val_if_fail (path, NULL); - - const char * suffix = ""; - -#ifdef _WIN32 - if (! strchr (mode, 'b')) /* binary mode (Windows) */ - suffix = "b"; -#else - if (! strchr (mode, 'e')) /* close on exec (POSIX) */ - suffix = "e"; -#endif - - SCONCAT2 (mode2, mode, suffix); - - FILE * stream = g_fopen (path, mode2); - - if (! stream) - { - perror (path); - str_unref (path); - return NULL; - } - - LocalFile * local = g_slice_new (LocalFile); - - local->path = path; - local->stream = stream; - local->cached_size = -1; - local->last_op = OP_NONE; - - return local; -} - -static int local_fclose (VFSFile * file) -{ - LocalFile * local = vfs_get_handle (file); - - int result = fclose (local->stream); - if (result < 0) - perror (local->path); - - str_unref (local->path); - g_slice_free (LocalFile, local); - - return result; -} - -static int64_t local_fread (void * ptr, int64_t size, int64_t nitems, VFSFile * file) -{ - LocalFile * local = vfs_get_handle (file); - - if (local->last_op == OP_WRITE) - { - if (fseeko (local->stream, 0, SEEK_CUR) < 0) /* flush buffers */ - { - perror (local->path); - return 0; - } - } - - local->last_op = OP_READ; - - clearerr (local->stream); - - int64_t result = fread (ptr, size, nitems, local->stream); - if (result < nitems && ferror (local->stream)) - perror (local->path); - - return result; -} - -static int64_t local_fwrite (const void * ptr, int64_t size, int64_t nitems, VFSFile * file) -{ - LocalFile * local = vfs_get_handle (file); - - if (local->last_op == OP_READ) - { - if (fseeko (local->stream, 0, SEEK_CUR) < 0) /* flush buffers */ - { - perror (local->path); - return 0; - } - } - - local->last_op = OP_WRITE; - local->cached_size = -1; - - clearerr (local->stream); - - int64_t result = fwrite (ptr, size, nitems, local->stream); - if (result < nitems && ferror (local->stream)) - perror (local->path); - - return result; -} - -static int local_fseek (VFSFile * file, int64_t offset, int whence) -{ - LocalFile * local = vfs_get_handle (file); - - int result = fseeko (local->stream, offset, whence); - if (result < 0) - perror (local->path); - - if (result == 0) - local->last_op = OP_NONE; - - return result; -} - -static int64_t local_ftell (VFSFile * file) -{ - LocalFile * local = vfs_get_handle (file); - return ftello (local->stream); -} - -static bool_t local_feof (VFSFile * file) -{ - LocalFile * local = vfs_get_handle (file); - return feof (local->stream); -} - -static int local_ftruncate (VFSFile * file, int64_t length) -{ - LocalFile * local = vfs_get_handle (file); - - if (local->last_op != OP_NONE) - { - if (fseeko (local->stream, 0, SEEK_CUR) < 0) /* flush buffers */ - { - perror (local->path); - return 0; - } - } - - int result = ftruncate (fileno (local->stream), length); - if (result < 0) - perror (local->path); - - if (result == 0) - { - local->last_op = OP_NONE; - local->cached_size = length; - } - - return result; -} - -static int64_t local_fsize (VFSFile * file) -{ - LocalFile * local = vfs_get_handle (file); - - if (local->cached_size < 0) - { - int64_t saved_pos = ftello (local->stream); - if (ftello < 0) - goto ERR; - - if (local_fseek (file, 0, SEEK_END) < 0) - goto ERR; - - int64_t length = ftello (local->stream); - if (length < 0) - goto ERR; - - if (local_fseek (file, saved_pos, SEEK_SET) < 0) - goto ERR; - - local->cached_size = length; - } - - return local->cached_size; - -ERR: - perror (local->path); - return -1; -} - -VFSConstructor vfs_local_vtable = { - .vfs_fopen_impl = local_fopen, - .vfs_fclose_impl = local_fclose, - .vfs_fread_impl = local_fread, - .vfs_fwrite_impl = local_fwrite, - .vfs_fseek_impl = local_fseek, - .vfs_ftell_impl = local_ftell, - .vfs_feof_impl = local_feof, - .vfs_ftruncate_impl = local_ftruncate, - .vfs_fsize_impl = local_fsize -}; diff --git a/src/libaudcore/vfs_local.cc b/src/libaudcore/vfs_local.cc new file mode 100644 index 0000000..feb48d4 --- /dev/null +++ b/src/libaudcore/vfs_local.cc @@ -0,0 +1,263 @@ +/* + * vfs_local.c + * Copyright 2009-2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#define WANT_VFS_STDIO_COMPAT +#include "vfs_local.h" + +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include <glib/gstdio.h> + +#include "audstrings.h" +#include "i18n.h" +#include "runtime.h" + +#ifdef _WIN32 +#define fseeko fseeko64 +#define ftello ftello64 +#endif + +#define perror(s) AUDERR ("%s: %s\n", (const char *) (s), strerror (errno)) + +enum LocalOp { + OP_NONE, + OP_READ, + OP_WRITE +}; + +class LocalFile : public VFSImpl +{ +public: + LocalFile (const char * path, FILE * stream) : + m_path (path), + m_stream (stream), + m_cached_size (-1), + m_last_op (OP_NONE) {} + + ~LocalFile (); + +protected: + int64_t fread (void * ptr, int64_t size, int64_t nmemb); + int fseek (int64_t offset, VFSSeekType whence); + + int64_t ftell (); + int64_t fsize (); + bool feof (); + + int64_t fwrite (const void * ptr, int64_t size, int64_t nmemb); + int ftruncate (int64_t length); + int fflush (); + +private: + String m_path; + FILE * m_stream; + int64_t m_cached_size; + LocalOp m_last_op; +}; + +VFSImpl * vfs_local_fopen (const char * uri, const char * mode, String & error) +{ + StringBuf path = uri_to_filename (uri); + + if (! path) + { + error = String (_("Invalid file name")); + return nullptr; + } + + const char * suffix = ""; + +#ifdef _WIN32 + if (! strchr (mode, 'b')) /* binary mode (Windows) */ + suffix = "b"; +#else + if (! strchr (mode, 'e')) /* close on exec (POSIX) */ + suffix = "e"; +#endif + + StringBuf mode2 = str_concat ({mode, suffix}); + + FILE * stream = g_fopen (path, mode2); + + if (! stream) + { + int errsave = errno; + +#ifndef _WIN32 + if (errsave == ENOENT && ! g_get_charset (nullptr)) + { + /* on a legacy system, try opening as UTF-8 */ + StringBuf path2 = uri_to_filename (uri, false); + if (path2 && strcmp (path, path2)) + stream = g_fopen (path2, mode2); + } +#endif + + if (! stream) + { + perror (path); + error = String (strerror (errsave)); + return nullptr; + } + } + + return new LocalFile (path, stream); +} + +LocalFile::~LocalFile () +{ + if (fclose (m_stream) < 0) + perror (m_path); +} + +int64_t LocalFile::fread (void * ptr, int64_t size, int64_t nitems) +{ + if (m_last_op == OP_WRITE) + { + if (::fflush (m_stream) < 0) + { + perror (m_path); + return 0; + } + } + + m_last_op = OP_READ; + + clearerr (m_stream); + + int64_t result = ::fread (ptr, size, nitems, m_stream); + if (result < nitems && ferror (m_stream)) + perror (m_path); + + return result; +} + +int64_t LocalFile::fwrite (const void * ptr, int64_t size, int64_t nitems) +{ + if (m_last_op == OP_READ) + { + if (::fflush (m_stream) < 0) + { + perror (m_path); + return 0; + } + } + + m_last_op = OP_WRITE; + m_cached_size = -1; + + clearerr (m_stream); + + int64_t result = ::fwrite (ptr, size, nitems, m_stream); + if (result < nitems && ferror (m_stream)) + perror (m_path); + + return result; +} + +int LocalFile::fseek (int64_t offset, VFSSeekType whence) +{ + int result = fseeko (m_stream, offset, from_vfs_seek_type (whence)); + if (result < 0) + perror (m_path); + + if (result == 0) + m_last_op = OP_NONE; + + return result; +} + +int64_t LocalFile::ftell () +{ + return ftello (m_stream); +} + +bool LocalFile::feof () +{ + return ::feof (m_stream); +} + +int LocalFile::ftruncate (int64_t length) +{ + if (m_last_op != OP_NONE) + { + if (::fflush (m_stream) < 0) + { + perror (m_path); + return -1; + } + } + + int result = ::ftruncate (fileno (m_stream), length); + if (result < 0) + perror (m_path); + + if (result == 0) + { + m_last_op = OP_NONE; + m_cached_size = length; + } + + return result; +} + +int LocalFile::fflush () +{ + if (m_last_op != OP_WRITE) + return 0; + + int result = ::fflush (m_stream); + if (result < 0) + perror (m_path); + + if (result == 0) + m_last_op = OP_NONE; + + return result; +} + +int64_t LocalFile::fsize () +{ + if (m_cached_size < 0) + { + int64_t saved_pos = ftello (m_stream); + if (saved_pos < 0) + goto ERR; + + if (fseek (0, VFS_SEEK_END) < 0) + goto ERR; + + int64_t length = ftello (m_stream); + if (length < 0) + goto ERR; + + if (fseek (saved_pos, VFS_SEEK_SET) < 0) + goto ERR; + + m_cached_size = length; + } + + return m_cached_size; + +ERR: + perror (m_path); + return -1; +} diff --git a/src/libaudcore/vfs_local.h b/src/libaudcore/vfs_local.h index 6fa3686..bdbf114 100644 --- a/src/libaudcore/vfs_local.h +++ b/src/libaudcore/vfs_local.h @@ -22,6 +22,6 @@ #include "vfs.h" -extern VFSConstructor vfs_local_vtable; +VFSImpl * vfs_local_fopen (const char * uri, const char * mode, String & error); #endif /* LIBAUDCORE_VFS_LOCAL_H */ diff --git a/src/audacious/vis_runner.c b/src/libaudcore/vis-runner.cc index ba8390c..163c315 100644 --- a/src/audacious/vis_runner.c +++ b/src/libaudcore/vis-runner.cc @@ -17,165 +17,102 @@ * the use of this software. */ +#include "internal.h" + #include <assert.h> #include <pthread.h> #include <stdint.h> #include <string.h> -#include <glib.h> - +#include "list.h" +#include "mainloop.h" #include "output.h" -#include "vis_runner.h" -#include "visualization.h" #define INTERVAL 30 /* milliseconds */ #define FRAMES_PER_NODE 512 -struct _VisNode { - struct _VisNode * next; - int channels; +struct VisNode : public ListNode +{ + explicit VisNode (int channels) : + channels (channels), + data (new float[channels * FRAMES_PER_NODE]) {} + + ~VisNode () + { delete[] data; } + + const int channels; int time; - float data[]; + float * data; }; -typedef struct _VisNode VisNode; - static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -static bool_t enabled = FALSE; -static bool_t playing = FALSE, paused = FALSE, active = FALSE; -static VisNode * current_node = NULL; +static bool enabled = false; +static bool playing = false, paused = false, active = false; +static VisNode * current_node = nullptr; static int current_frames; -static VisNode * vis_list = NULL; -static VisNode * vis_list_tail = NULL; -static VisNode * vis_pool = NULL; -static int send_source = 0, clear_source = 0; - -static VisNode * alloc_node_locked (int channels) -{ - VisNode * node; - - if (vis_pool) - { - node = vis_pool; - assert (node->channels == channels); - vis_pool = node->next; - } - else - { - node = g_malloc (offsetof (VisNode, data) + sizeof (float) * channels * FRAMES_PER_NODE); - node->channels = channels; - } - - node->next = NULL; - return node; -} - -static void free_node_locked (VisNode * node) -{ - node->next = vis_pool; - vis_pool = node; -} - -static void push_node_locked (VisNode * node) -{ - if (vis_list) - vis_list_tail->next = node; - else - vis_list = node; - - vis_list_tail = node; -} - -static VisNode * pop_node_locked (void) -{ - VisNode * node = vis_list; - vis_list = node->next; - node->next = NULL; +static List<VisNode> vis_list; +static List<VisNode> vis_pool; +static QueuedFunc queued_clear; +static QueuedFunc send_timer; - if (vis_list_tail == node) - vis_list_tail = NULL; - - return node; -} - -static bool_t send_audio (void * unused) +static void send_audio (void * unused) { /* call before locking mutex to avoid deadlock */ int outputted = output_get_raw_time (); pthread_mutex_lock (& mutex); - if (! send_source) + if (! send_timer.running ()) { pthread_mutex_unlock (& mutex); - return FALSE; + return; } - VisNode * vis_node = NULL; + VisNode * node = nullptr; + VisNode * next; - while (vis_list) + while ((next = vis_list.head ())) { /* If we are considering a node, stop searching and use it if it is the * most recent (that is, the next one is in the future). Otherwise, * consider the next node if it is not in the future by more than the * length of an interval. */ - if (vis_list->time > outputted + (vis_node ? 0 : INTERVAL)) + if (next->time > outputted + (node ? 0 : INTERVAL)) break; - if (vis_node) - free_node_locked (vis_node); + if (node) + vis_pool.prepend (node); - vis_node = pop_node_locked (); + node = next; + vis_list.remove (node); } pthread_mutex_unlock (& mutex); - if (! vis_node) - return TRUE; + if (! node) + return; - vis_send_audio (vis_node->data, vis_node->channels); + vis_send_audio (node->data, node->channels); pthread_mutex_lock (& mutex); - free_node_locked (vis_node); + vis_pool.prepend (node); pthread_mutex_unlock (& mutex); - - return TRUE; } -static bool_t send_clear (void * unused) +static void send_clear (void * unused) { - pthread_mutex_lock (& mutex); - clear_source = 0; - pthread_mutex_unlock (& mutex); - vis_send_clear (); - - return FALSE; } static void flush_locked (void) { - g_free (current_node); - current_node = NULL; - - while (vis_list) - { - VisNode * node = vis_list; - vis_list = node->next; - g_free (node); - } - - vis_list_tail = NULL; + delete current_node; + current_node = nullptr; - while (vis_pool) - { - VisNode * node = vis_pool; - vis_pool = node->next; - g_free (node); - } + vis_list.clear (); + vis_pool.clear (); - if (! clear_source) - clear_source = g_timeout_add (0, send_clear, NULL); + queued_clear.queue (send_clear, nullptr); } void vis_runner_flush (void) @@ -185,44 +122,37 @@ void vis_runner_flush (void) pthread_mutex_unlock (& mutex); } -static void start_stop_locked (bool_t new_playing, bool_t new_paused) +static void start_stop_locked (bool new_playing, bool new_paused) { playing = new_playing; paused = new_paused; active = playing && enabled; - if (send_source) - { - g_source_remove (send_source); - send_source = 0; - } - - if (clear_source) - { - g_source_remove (clear_source); - clear_source = 0; - } + send_timer.stop (); + queued_clear.stop (); if (! active) flush_locked (); else if (! paused) - send_source = g_timeout_add (INTERVAL, send_audio, NULL); + send_timer.start (INTERVAL, send_audio, nullptr); } -void vis_runner_start_stop (bool_t new_playing, bool_t new_paused) +void vis_runner_start_stop (bool new_playing, bool new_paused) { pthread_mutex_lock (& mutex); start_stop_locked (new_playing, new_paused); pthread_mutex_unlock (& mutex); } -void vis_runner_pass_audio (int time, float * data, int samples, int - channels, int rate) +void vis_runner_pass_audio (int time, const Index<float> & data, int channels, int rate) { pthread_mutex_lock (& mutex); if (! active) - goto UNLOCK; + { + pthread_mutex_unlock (& mutex); + return; + } /* We can build a single node from multiple calls; we can also build * multiple nodes from the same call. If current_node is present, it was @@ -245,17 +175,27 @@ void vis_runner_pass_audio (int time, float * data, int samples, int * queue, we are at the beginning of the song or had an underrun, * and we want to copy the earliest audio data we have. */ - if (vis_list_tail) - node_time = vis_list_tail->time + INTERVAL; + VisNode * tail = vis_list.tail (); + if (tail) + node_time = tail->time + INTERVAL; at = channels * (int) ((int64_t) (node_time - time) * rate / 1000); if (at < 0) at = 0; - if (at >= samples) + if (at >= data.len ()) break; - current_node = alloc_node_locked (channels); + current_node = vis_pool.head (); + + if (current_node) + { + assert (current_node->channels == channels); + vis_pool.remove (current_node); + } + else + current_node = new VisNode (channels); + current_node->time = node_time; current_frames = 0; } @@ -265,22 +205,21 @@ void vis_runner_pass_audio (int time, float * data, int samples, int * wait for more data to be passed in the next call. If we do fill the * node, we loop and start building a new one. */ - int copy = MIN (samples - at, channels * (FRAMES_PER_NODE - current_frames)); - memcpy (current_node->data + channels * current_frames, data + at, sizeof (float) * copy); + int copy = aud::min (data.len () - at, channels * (FRAMES_PER_NODE - current_frames)); + memcpy (current_node->data + channels * current_frames, & data[at], sizeof (float) * copy); current_frames += copy / channels; if (current_frames < FRAMES_PER_NODE) break; - push_node_locked (current_node); - current_node = NULL; + vis_list.append (current_node); + current_node = nullptr; } -UNLOCK: pthread_mutex_unlock (& mutex); } -void vis_runner_enable (bool_t enable) +void vis_runner_enable (bool enable) { pthread_mutex_lock (& mutex); enabled = enable; diff --git a/src/libaudcore/visualization.cc b/src/libaudcore/visualization.cc new file mode 100644 index 0000000..2f7a768 --- /dev/null +++ b/src/libaudcore/visualization.cc @@ -0,0 +1,179 @@ +/* + * visualization.c + * Copyright 2010-2011 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "interface.h" +#include "internal.h" + +#include <string.h> + +#include "plugin.h" +#include "plugins.h" +#include "runtime.h" + +static Index<Visualizer *> visualizers; + +static int running = false; +static int num_enabled = 0; + +EXPORT void aud_visualizer_add (Visualizer * vis) +{ + visualizers.append (vis); + + num_enabled ++; + if (num_enabled == 1) + vis_runner_enable (true); +} + +EXPORT void aud_visualizer_remove (Visualizer * vis) +{ + int num_disabled = 0; + + auto is_match = [&] (Visualizer * vis2) + { + if (vis2 != vis) + return false; + + num_disabled ++; + return true; + }; + + visualizers.remove_if (is_match, true); + + num_enabled -= num_disabled; + if (! num_enabled) + vis_runner_enable (false); +} + +void vis_send_clear () +{ + for (Visualizer * vis : visualizers) + vis->clear (); +} + +static void pcm_to_mono (const float * data, float * mono, int channels) +{ + if (channels == 1) + memcpy (mono, data, sizeof (float) * 512); + else + { + float * set = mono; + while (set < & mono[512]) + { + * set ++ = (data[0] + data[1]) / 2; + data += channels; + } + } +} + +void vis_send_audio (const float * data, int channels) +{ + auto is_active = [] (int type_mask) + { + for (Visualizer * vis : visualizers) + { + if ((vis->type_mask & type_mask)) + return true; + } + + return false; + }; + + float mono[512]; + float freq[256]; + + if (is_active (Visualizer::MonoPCM | Visualizer::Freq)) + pcm_to_mono (data, mono, channels); + if (is_active (Visualizer::Freq)) + calc_freq (mono, freq); + + for (Visualizer * vis : visualizers) + { + if ((vis->type_mask & Visualizer::MonoPCM)) + vis->render_mono_pcm (mono); + if ((vis->type_mask & Visualizer::MultiPCM)) + vis->render_multi_pcm (data, channels); + if ((vis->type_mask & Visualizer::Freq)) + vis->render_freq (freq); + } +} + +static bool vis_load (PluginHandle * plugin) +{ + AUDINFO ("Activating %s.\n", aud_plugin_get_name (plugin)); + VisPlugin * header = (VisPlugin *) aud_plugin_get_header (plugin); + if (! header) + return false; + + aud_visualizer_add (header); + return true; +} + +static void vis_unload (PluginHandle * plugin) +{ + AUDINFO ("Deactivating %s.\n", aud_plugin_get_name (plugin)); + VisPlugin * header = (VisPlugin *) aud_plugin_get_header (plugin); + if (! header) + return; + + header->clear (); + aud_visualizer_remove (header); +} + +void vis_activate (bool activate) +{ + if (! activate == ! running) + return; + + for (PluginHandle * plugin : aud_plugin_list (PluginType::Vis)) + { + if (! aud_plugin_get_enabled (plugin)) + continue; + + if (activate) + vis_load (plugin); + else + vis_unload (plugin); + } + + running = activate; +} + +bool vis_plugin_start (PluginHandle * plugin) +{ + VisPlugin * vp = (VisPlugin *) aud_plugin_get_header (plugin); + if (! vp || ! vp->init ()) + return false; + + if (running) + vis_load (plugin); + + return true; +} + +void vis_plugin_stop (PluginHandle * plugin) +{ + VisPlugin * vp = (VisPlugin *) aud_plugin_get_header (plugin); + if (! vp) + return; + + if (running) + vis_unload (plugin); + + vp->cleanup (); +} diff --git a/src/libaudcore/visualizer.h b/src/libaudcore/visualizer.h new file mode 100644 index 0000000..d99eaff --- /dev/null +++ b/src/libaudcore/visualizer.h @@ -0,0 +1,49 @@ +/* + * visualizer.h + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_VISUALIZER_H +#define LIBAUDCORE_VISUALIZER_H + +class Visualizer +{ +public: + enum { + MonoPCM = (1 << 0), + MultiPCM = (1 << 1), + Freq = (1 << 2) + }; + + const int type_mask; + constexpr Visualizer (int type_mask) : + type_mask (type_mask) {} + + /* reset internal state and clear display */ + virtual void clear () = 0; + + /* 512 frames of a single-channel PCM signal */ + virtual void render_mono_pcm (const float * pcm) {} + + /* 512 frames of an interleaved multi-channel PCM signal */ + virtual void render_multi_pcm (const float * pcm, int channels) {} + + /* intensity of frequencies 1/512, 2/512, ..., 256/512 of sample rate */ + virtual void render_freq (const float * freq) {} +}; + +#endif /* LIBAUDCORE_VISUALIZER_H */ diff --git a/src/libaudgui/Makefile b/src/libaudgui/Makefile index 9da027f..3a16dac 100644 --- a/src/libaudgui/Makefile +++ b/src/libaudgui/Makefile @@ -1,27 +1,32 @@ SHARED_LIB = ${LIB_PREFIX}audgui${LIB_SUFFIX} -LIB_MAJOR = 2 +LIB_MAJOR = 3 LIB_MINOR = 0 -SRCS = about.c \ - confirm.c \ - equalizer.c \ - infopopup.c \ - infowin.c \ - init.c \ - jump-to-time.c \ - list.c \ - menu.c \ - pixbufs.c \ - playlists.c \ - queue-manager.c \ - scaled-image.c \ - ui_fileopener.c \ - ui_jumptotrack.c \ - ui_jumptotrack_cache.c \ - ui_playlist_manager.c \ - urilist.c \ - url-opener.c \ - util.c +SRCS = about.cc \ + confirm.cc \ + equalizer.cc \ + file-opener.cc \ + infopopup.cc \ + infowin.cc \ + init.cc \ + jump-to-time.cc \ + jump-to-track.cc \ + jump-to-track-cache.cc \ + list.cc \ + menu.cc \ + pixbufs.cc \ + playlists.cc \ + plugin-menu.cc \ + plugin-prefs.cc \ + plugin-view.cc \ + prefs-widget.cc \ + prefs-window.cc \ + queue-manager.cc \ + scaled-image.cc \ + status.cc \ + urilist.cc \ + url-opener.cc \ + util.cc INCLUDES = libaudgui.h \ libaudgui-gtk.h \ @@ -33,10 +38,13 @@ include ../../extra.mk includesubdir = libaudgui +LD = ${CXX} + CPPFLAGS := -I.. -I../.. \ ${CPPFLAGS} \ - ${GLIB_CFLASG} \ - ${GTK_CFLAGS} + ${GLIB_CFLAGS} \ + ${GTK_CFLAGS} \ + ${LIBGUESS_CFLAGS} CFLAGS += ${LIB_CFLAGS} diff --git a/src/libaudgui/about.c b/src/libaudgui/about.cc index 56cdbef..8b3603d 100644 --- a/src/libaudgui/about.c +++ b/src/libaudgui/about.cc @@ -19,17 +19,15 @@ #include <gtk/gtk.h> -#include <audacious/i18n.h> -#include <audacious/misc.h> #include <libaudcore/audstrings.h> +#include <libaudcore/i18n.h> +#include <libaudcore/runtime.h> -#include "init.h" +#include "internal.h" +#include "libaudgui.h" #include "libaudgui-gtk.h" -static const char about_text[] = - "<big><b>Audacious " VERSION "</b></big>\n" - "Copyright © 2001-2014 Audacious developers and others"; - +static const char about_text[] = "<big><b>Audacious " VERSION "</b></big>\n" COPYRIGHT; static const char website[] = "http://audacious-media-player.org"; static GtkWidget * create_credits_notebook (const char * credits, const char * license) @@ -43,14 +41,16 @@ static GtkWidget * create_credits_notebook (const char * credits, const char * l { GtkWidget * label = gtk_label_new (titles[i]); - GtkWidget * scrolled = gtk_scrolled_window_new (NULL, NULL); + GtkWidget * scrolled = gtk_scrolled_window_new (nullptr, nullptr); gtk_widget_set_size_request (scrolled, -1, 200); + gtk_scrolled_window_set_policy ((GtkScrolledWindow *) scrolled, + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - GtkTextBuffer * buffer = gtk_text_buffer_new (NULL); + GtkTextBuffer * buffer = gtk_text_buffer_new (nullptr); gtk_text_buffer_set_text (buffer, text[i], -1); GtkWidget * text = gtk_text_view_new_with_buffer (buffer); - gtk_text_view_set_editable ((GtkTextView *) text, FALSE); - gtk_text_view_set_cursor_visible ((GtkTextView *) text, FALSE); + gtk_text_view_set_editable ((GtkTextView *) text, false); + gtk_text_view_set_cursor_visible ((GtkTextView *) text, false); gtk_text_view_set_left_margin ((GtkTextView *) text, 6); gtk_text_view_set_right_margin ((GtkTextView *) text, 6); gtk_container_add ((GtkContainer *) scrolled, text); @@ -61,49 +61,51 @@ static GtkWidget * create_credits_notebook (const char * credits, const char * l return notebook; } -static GtkWidget * create_about_window (void) +static GtkWidget * create_about_window () { - const char * data_dir = aud_get_path (AUD_PATH_DATA_DIR); + const char * data_dir = aud_get_path (AudPath::DataDir); GtkWidget * about_window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title ((GtkWindow *) about_window, _("About Audacious")); - gtk_window_set_resizable ((GtkWindow *) about_window, FALSE); + gtk_window_set_resizable ((GtkWindow *) about_window, false); gtk_container_set_border_width ((GtkContainer *) about_window, 3); audgui_destroy_on_escape (about_window); - GtkWidget * vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + GtkWidget * vbox = gtk_vbox_new (false, 6); gtk_container_add ((GtkContainer *) about_window, vbox); - SCONCAT2 (logo_path, data_dir, "/images/about-logo.png"); + StringBuf logo_path = filename_build ({data_dir, "images", "about-logo.png"}); GtkWidget * image = gtk_image_new_from_file (logo_path); - gtk_box_pack_start ((GtkBox *) vbox, image, FALSE, FALSE, 0); + gtk_box_pack_start ((GtkBox *) vbox, image, false, false, 0); - GtkWidget * label = gtk_label_new (NULL); + GtkWidget * label = gtk_label_new (nullptr); gtk_label_set_markup ((GtkLabel *) label, about_text); gtk_label_set_justify ((GtkLabel *) label, GTK_JUSTIFY_CENTER); - gtk_box_pack_start ((GtkBox *) vbox, label, FALSE, FALSE, 0); + gtk_box_pack_start ((GtkBox *) vbox, label, false, false, 0); + + GtkWidget * align = gtk_alignment_new (0.5, 0.5, 0, 0); + gtk_box_pack_start ((GtkBox *) vbox, align, false, false, 0); GtkWidget * button = gtk_link_button_new (website); - gtk_widget_set_halign (button, GTK_ALIGN_CENTER); - gtk_box_pack_start ((GtkBox *) vbox, button, FALSE, FALSE, 0); + gtk_container_add ((GtkContainer *) align, button); char * credits, * license; - SCONCAT2 (credits_path, data_dir, "/AUTHORS"); - if (! g_file_get_contents (credits_path, & credits, NULL, NULL)) - credits = g_strdup_printf ("Unable to load %s; check your installation.", credits_path); + StringBuf credits_path = filename_build ({data_dir, "AUTHORS"}); + if (! g_file_get_contents (credits_path, & credits, nullptr, nullptr)) + credits = g_strdup_printf ("Unable to load %s; check your installation.", (const char *) credits_path); - SCONCAT2 (license_path, data_dir, "/COPYING"); - if (! g_file_get_contents (license_path, & license, NULL, NULL)) - license = g_strdup_printf ("Unable to load %s; check your installation.", license_path); + StringBuf license_path = filename_build ({data_dir, "COPYING"}); + if (! g_file_get_contents (license_path, & license, nullptr, nullptr)) + license = g_strdup_printf ("Unable to load %s; check your installation.", (const char *) license_path); g_strchomp (credits); g_strchomp (license); GtkWidget * notebook = create_credits_notebook (credits, license); - gtk_widget_set_size_request (notebook, 600, 250); - gtk_box_pack_start ((GtkBox *) vbox, notebook, TRUE, TRUE, 0); + gtk_widget_set_size_request (notebook, 585, 250); + gtk_box_pack_start ((GtkBox *) vbox, notebook, true, true, 0); g_free (credits); g_free (license); @@ -111,13 +113,13 @@ static GtkWidget * create_about_window (void) return about_window; } -EXPORT void audgui_show_about_window (void) +EXPORT void audgui_show_about_window () { if (! audgui_reshow_unique_window (AUDGUI_ABOUT_WINDOW)) audgui_show_unique_window (AUDGUI_ABOUT_WINDOW, create_about_window ()); } -EXPORT void audgui_hide_about_window (void) +EXPORT void audgui_hide_about_window () { audgui_hide_unique_window (AUDGUI_ABOUT_WINDOW); } diff --git a/src/libaudgui/confirm.c b/src/libaudgui/confirm.cc index fb92da4..bfcc176 100644 --- a/src/libaudgui/confirm.c +++ b/src/libaudgui/confirm.cc @@ -19,11 +19,12 @@ #include <gtk/gtk.h> -#include <audacious/i18n.h> -#include <audacious/misc.h> -#include <audacious/playlist.h> #include <libaudcore/audstrings.h> +#include <libaudcore/i18n.h> +#include <libaudcore/playlist.h> +#include <libaudcore/runtime.h> +#include "libaudgui.h" #include "libaudgui-gtk.h" static void no_confirm_cb (GtkToggleButton * toggle) @@ -47,20 +48,19 @@ EXPORT void audgui_confirm_playlist_delete (int playlist) return; } - char * title = aud_playlist_get_title (playlist); - SPRINTF (message, _("Do you want to permanently remove â%sâ?"), title); - str_unref (title); + StringBuf message = str_printf (_("Do you want to permanently remove â%sâ?"), + (const char *) aud_playlist_get_title (playlist)); int id = aud_playlist_get_unique_id (playlist); GtkWidget * button1 = audgui_button_new (_("_Remove"), "edit-delete", confirm_delete_cb, GINT_TO_POINTER (id)); - GtkWidget * button2 = audgui_button_new (_("_Cancel"), "process-stop", NULL, NULL); + GtkWidget * button2 = audgui_button_new (_("_Cancel"), "process-stop", nullptr, nullptr); GtkWidget * dialog = audgui_dialog_new (GTK_MESSAGE_QUESTION, _("Remove Playlist"), message, button1, button2); GtkWidget * check = gtk_check_button_new_with_mnemonic (_("_Donât ask again")); - g_signal_connect (check, "toggled", (GCallback) no_confirm_cb, NULL); + g_signal_connect (check, "toggled", (GCallback) no_confirm_cb, nullptr); audgui_dialog_add_widget (dialog, check); gtk_widget_show_all (dialog); @@ -77,17 +77,15 @@ static void rename_cb (void * entry) EXPORT void audgui_show_playlist_rename (int playlist) { - char * title = aud_playlist_get_title (playlist); - GtkWidget * entry = gtk_entry_new (); - gtk_entry_set_text ((GtkEntry *) entry, title); - gtk_entry_set_activates_default ((GtkEntry *) entry, TRUE); + gtk_entry_set_text ((GtkEntry *) entry, aud_playlist_get_title (playlist)); + gtk_entry_set_activates_default ((GtkEntry *) entry, true); int id = aud_playlist_get_unique_id (playlist); g_object_set_data ((GObject *) entry, "playlist-id", GINT_TO_POINTER (id)); GtkWidget * button1 = audgui_button_new (_("_Rename"), "insert-text", rename_cb, entry); - GtkWidget * button2 = audgui_button_new (_("_Cancel"), "process-stop", NULL, NULL); + GtkWidget * button2 = audgui_button_new (_("_Cancel"), "process-stop", nullptr, nullptr); GtkWidget * dialog = audgui_dialog_new (GTK_MESSAGE_QUESTION, _("Rename Playlist"), _("What would you like to call this playlist?"), @@ -96,6 +94,4 @@ EXPORT void audgui_show_playlist_rename (int playlist) audgui_dialog_add_widget (dialog, entry); gtk_widget_show_all (dialog); - - str_unref (title); } diff --git a/src/libaudgui/equalizer.c b/src/libaudgui/equalizer.cc index 1c3a082..d0a8d92 100644 --- a/src/libaudgui/equalizer.c +++ b/src/libaudgui/equalizer.cc @@ -19,152 +19,151 @@ #include <math.h> -#include <audacious/i18n.h> -#include <audacious/misc.h> -#include <audacious/types.h> #include <libaudcore/audstrings.h> +#include <libaudcore/equalizer.h> #include <libaudcore/hook.h> +#include <libaudcore/i18n.h> +#include <libaudcore/runtime.h> -#include "init.h" +#include "internal.h" +#include "libaudgui.h" #include "libaudgui-gtk.h" -static void on_off_cb (GtkToggleButton * on_off, void * unused) +static void on_off_cb (GtkToggleButton * on_off) { - aud_set_bool (NULL, "equalizer_active", gtk_toggle_button_get_active (on_off)); + aud_set_bool (nullptr, "equalizer_active", gtk_toggle_button_get_active (on_off)); } -static void on_off_update (void * unused, GtkWidget * on_off) +static void on_off_update (void *, GtkWidget * on_off) { gtk_toggle_button_set_active ((GtkToggleButton *) on_off, aud_get_bool - (NULL, "equalizer_active")); + (nullptr, "equalizer_active")); } -static GtkWidget * create_on_off (void) +static GtkWidget * create_on_off () { GtkWidget * on_off = gtk_check_button_new_with_mnemonic (_("_Enable")); - g_signal_connect ((GObject *) on_off, "toggled", (GCallback) on_off_cb, NULL); + g_signal_connect (on_off, "toggled", (GCallback) on_off_cb, nullptr); hook_associate ("set equalizer_active", (HookFunction) on_off_update, on_off); - on_off_update (NULL, on_off); + on_off_update (nullptr, on_off); return on_off; } -static void slider_moved (GtkRange * slider, void * unused) +static void slider_moved (GtkRange * slider) { int band = GPOINTER_TO_INT (g_object_get_data ((GObject *) slider, "band")); double value = round (gtk_range_get_value (slider)); if (band == -1) - aud_set_double (NULL, "equalizer_preamp", value); + aud_set_double (nullptr, "equalizer_preamp", value); else aud_eq_set_band (band, value); } static GtkWidget * create_slider (const char * name, int band, GtkWidget * hbox) { - GtkWidget * vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + GtkWidget * vbox = gtk_vbox_new (false, 6); GtkWidget * label = gtk_label_new (name); gtk_label_set_angle ((GtkLabel *) label, 90); - gtk_box_pack_start ((GtkBox *) vbox, label, TRUE, FALSE, 0); + gtk_box_pack_start ((GtkBox *) vbox, label, true, false, 0); - GtkWidget * slider = gtk_scale_new_with_range (GTK_ORIENTATION_VERTICAL, - -EQUALIZER_MAX_GAIN, EQUALIZER_MAX_GAIN, 1); - gtk_scale_set_draw_value ((GtkScale *) slider, TRUE); + GtkWidget * slider = gtk_vscale_new_with_range (-AUD_EQ_MAX_GAIN, AUD_EQ_MAX_GAIN, 1); + gtk_scale_set_draw_value ((GtkScale *) slider, true); gtk_scale_set_value_pos ((GtkScale *) slider, GTK_POS_BOTTOM); - gtk_range_set_inverted ((GtkRange *) slider, TRUE); + gtk_range_set_inverted ((GtkRange *) slider, true); gtk_widget_set_size_request (slider, -1, 120); g_object_set_data ((GObject *) slider, "band", GINT_TO_POINTER (band)); - g_signal_connect ((GObject *) slider, "value-changed", (GCallback) slider_moved, NULL); + g_signal_connect (slider, "value-changed", (GCallback) slider_moved, nullptr); - gtk_box_pack_start ((GtkBox *) vbox, slider, FALSE, FALSE, 0); - gtk_box_pack_start ((GtkBox *) hbox, vbox, FALSE, FALSE, 0); + gtk_box_pack_start ((GtkBox *) vbox, slider, false, false, 0); + gtk_box_pack_start ((GtkBox *) hbox, vbox, false, false, 0); return slider; } static void set_slider (GtkWidget * slider, double value) { - g_signal_handlers_block_by_func (slider, (void *) slider_moved, NULL); + g_signal_handlers_block_by_func (slider, (void *) slider_moved, nullptr); gtk_range_set_value ((GtkRange *) slider, round (value)); - g_signal_handlers_unblock_by_func (slider, (void *) slider_moved, NULL); + g_signal_handlers_unblock_by_func (slider, (void *) slider_moved, nullptr); } -static void update_sliders (void * unused, GtkWidget * window) +static void update_sliders (void *, GtkWidget * window) { - GtkWidget * preamp = g_object_get_data ((GObject *) window, "preamp"); - set_slider (preamp, aud_get_double (NULL, "equalizer_preamp")); + GtkWidget * preamp = (GtkWidget *) g_object_get_data ((GObject *) window, "preamp"); + set_slider (preamp, aud_get_double (nullptr, "equalizer_preamp")); - double values[AUD_EQUALIZER_NBANDS]; + double values[AUD_EQ_NBANDS]; aud_eq_get_bands (values); - for (int i = 0; i < AUD_EQUALIZER_NBANDS; i ++) + for (int i = 0; i < AUD_EQ_NBANDS; i ++) { - SPRINTF (slider_id, "slider%d", i); - GtkWidget * slider = g_object_get_data ((GObject *) window, slider_id); + StringBuf slider_id = str_printf ("slider%d", i); + GtkWidget * slider = (GtkWidget *) g_object_get_data ((GObject *) window, slider_id); set_slider (slider, values[i]); } } -static void destroy_cb (void) +static void destroy_cb () { hook_dissociate ("set equalizer_active", (HookFunction) on_off_update); hook_dissociate ("set equalizer_bands", (HookFunction) update_sliders); hook_dissociate ("set equalizer_preamp", (HookFunction) update_sliders); } -static GtkWidget * create_window (void) +static GtkWidget * create_window () { - const char * const names[AUD_EQUALIZER_NBANDS] = {N_("31 Hz"), N_("63 Hz"), + const char * const names[AUD_EQ_NBANDS] = {N_("31 Hz"), N_("63 Hz"), N_("125 Hz"), N_("250 Hz"), N_("500 Hz"), N_("1 kHz"), N_("2 kHz"), N_("4 kHz"), N_("8 kHz"), N_("16 kHz")}; GtkWidget * window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title ((GtkWindow *) window, _("Equalizer")); gtk_window_set_type_hint ((GtkWindow *) window, GDK_WINDOW_TYPE_HINT_DIALOG); - gtk_window_set_resizable ((GtkWindow *) window, FALSE); + gtk_window_set_resizable ((GtkWindow *) window, false); gtk_container_set_border_width ((GtkContainer *) window, 6); audgui_destroy_on_escape (window); - GtkWidget * vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + GtkWidget * vbox = gtk_vbox_new (false, 6); gtk_container_add ((GtkContainer *) window, vbox); - gtk_box_pack_start ((GtkBox *) vbox, create_on_off (), FALSE, FALSE, 0); + gtk_box_pack_start ((GtkBox *) vbox, create_on_off (), false, false, 0); - GtkWidget * hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); - gtk_box_pack_start ((GtkBox *) vbox, hbox, FALSE, FALSE, 0); + GtkWidget * hbox = gtk_hbox_new (false, 6); + gtk_box_pack_start ((GtkBox *) vbox, hbox, false, false, 0); GtkWidget * preamp = create_slider (_("Preamp"), -1, hbox); g_object_set_data ((GObject *) window, "preamp", preamp); - gtk_box_pack_start ((GtkBox *) hbox, - gtk_separator_new (GTK_ORIENTATION_VERTICAL), FALSE, FALSE, 0); + gtk_box_pack_start ((GtkBox *) hbox, gtk_vseparator_new (), false, false, 0); - for (int i = 0; i < AUD_EQUALIZER_NBANDS; i ++) + for (int i = 0; i < AUD_EQ_NBANDS; i ++) { + StringBuf slider_id = str_printf ("slider%d", i); GtkWidget * slider = create_slider (_(names[i]), i, hbox); - SPRINTF (slider_id, "slider%d", i); g_object_set_data ((GObject *) window, slider_id, slider); } - update_sliders (NULL, window); + update_sliders (nullptr, window); hook_associate ("set equalizer_preamp", (HookFunction) update_sliders, window); hook_associate ("set equalizer_bands", (HookFunction) update_sliders, window); - g_signal_connect (window, "destroy", (GCallback) destroy_cb, NULL); + g_signal_connect (window, "destroy", (GCallback) destroy_cb, nullptr); return window; } -EXPORT void audgui_show_equalizer_window (void) +EXPORT void audgui_show_equalizer_window () { if (! audgui_reshow_unique_window (AUDGUI_EQUALIZER_WINDOW)) audgui_show_unique_window (AUDGUI_EQUALIZER_WINDOW, create_window ()); } -EXPORT void audgui_hide_equalizer_window (void) +EXPORT void audgui_hide_equalizer_window () { audgui_hide_unique_window (AUDGUI_EQUALIZER_WINDOW); } diff --git a/src/libaudgui/ui_fileopener.c b/src/libaudgui/file-opener.cc index ae0ad1f..4417f3a 100644 --- a/src/libaudgui/ui_fileopener.c +++ b/src/libaudgui/file-opener.cc @@ -1,5 +1,5 @@ /* - * ui_fileopener.c + * file-opener.c * Copyright 2007-2013 Michael FĂ€rber and John Lindgren * * Redistribution and use in source and binary forms, with or without @@ -19,21 +19,22 @@ #include <gtk/gtk.h> -#include <audacious/i18n.h> -#include <audacious/drct.h> -#include <audacious/misc.h> +#include <libaudcore/drct.h> +#include <libaudcore/i18n.h> +#include <libaudcore/runtime.h> +#include <libaudcore/tuple.h> -#include "init.h" +#include "internal.h" #include "libaudgui.h" #include "libaudgui-gtk.h" -static Index * get_files (GtkWidget * chooser) +static Index<PlaylistAddItem> get_files (GtkWidget * chooser) { - Index * index = index_new (); + Index<PlaylistAddItem> index; GSList * list = gtk_file_chooser_get_uris ((GtkFileChooser *) chooser); for (GSList * node = list; node; node = node->next) - index_insert (index, -1, str_get (node->data)); + index.append (String ((const char *) node->data)); g_slist_free_full (list, g_free); return index; @@ -41,16 +42,16 @@ static Index * get_files (GtkWidget * chooser) static void open_cb (void * data) { - GtkWidget * chooser = data; - Index * files = get_files (chooser); - bool_t open = GPOINTER_TO_INT (g_object_get_data ((GObject *) chooser, "do-open")); + GtkWidget * chooser = (GtkWidget *) data; + Index<PlaylistAddItem> files = get_files (chooser); + gboolean open = GPOINTER_TO_INT (g_object_get_data ((GObject *) chooser, "do-open")); if (open) - aud_drct_pl_open_list (files); + aud_drct_pl_open_list (std::move (files)); else - aud_drct_pl_add_list (files, -1); + aud_drct_pl_add_list (std::move (files), -1); - GtkWidget * toggle = g_object_get_data ((GObject *) chooser, "toggle-button"); + GtkWidget * toggle = (GtkWidget *) g_object_get_data ((GObject *) chooser, "toggle-button"); if (gtk_toggle_button_get_active ((GtkToggleButton *) toggle)) audgui_hide_filebrowser (); } @@ -70,7 +71,7 @@ static void toggled_cb (GtkToggleButton * toggle, void * option) aud_set_bool ("audgui", (const char *) option, gtk_toggle_button_get_active (toggle)); } -static GtkWidget * create_filebrowser (bool_t open) +static GtkWidget * create_filebrowser (gboolean open) { const char * window_title, * verb, * icon, * toggle_text, * option; @@ -91,65 +92,64 @@ static GtkWidget * create_filebrowser (bool_t open) option = "close_dialog_add"; } - GtkWidget * window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DIALOG); - gtk_window_set_title(GTK_WINDOW(window), window_title); - gtk_window_set_default_size(GTK_WINDOW(window), 700, 450); - gtk_container_set_border_width(GTK_CONTAINER(window), 10); + GtkWidget * window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_type_hint ((GtkWindow *) window, GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_title ((GtkWindow *) window, window_title); + gtk_window_set_default_size ((GtkWindow *) window, 700, 450); + gtk_container_set_border_width ((GtkContainer *) window, 10); - GtkWidget * vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - gtk_container_add(GTK_CONTAINER(window), vbox); + GtkWidget * vbox = gtk_vbox_new (false, 0); + gtk_container_add ((GtkContainer *) window, vbox); - GtkWidget * chooser = gtk_file_chooser_widget_new(GTK_FILE_CHOOSER_ACTION_OPEN); - gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(chooser), TRUE); + GtkWidget * chooser = gtk_file_chooser_widget_new (GTK_FILE_CHOOSER_ACTION_OPEN); + gtk_file_chooser_set_select_multiple ((GtkFileChooser *) chooser, true); - char * path = aud_get_str ("audgui", "filesel_path"); + String path = aud_get_str ("audgui", "filesel_path"); if (path[0]) gtk_file_chooser_set_current_folder ((GtkFileChooser *) chooser, path); - str_unref (path); - gtk_box_pack_start(GTK_BOX(vbox), chooser, TRUE, TRUE, 3); + gtk_box_pack_start ((GtkBox *) vbox, chooser, true, true, 3); - GtkWidget * hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); - gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 3); + GtkWidget * hbox = gtk_hbox_new (false, 0); + gtk_box_pack_end ((GtkBox *) vbox, hbox, false, false, 3); GtkWidget * toggle = gtk_check_button_new_with_mnemonic (toggle_text); gtk_toggle_button_set_active ((GtkToggleButton *) toggle, aud_get_bool ("audgui", option)); g_signal_connect (toggle, "toggled", (GCallback) toggled_cb, (void *) option); - gtk_box_pack_start(GTK_BOX(hbox), toggle, TRUE, TRUE, 3); + gtk_box_pack_start ((GtkBox *) hbox, toggle, true, true, 0); - GtkWidget * bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_set_spacing(GTK_BOX(bbox), 6); - gtk_box_pack_end(GTK_BOX(hbox), bbox, TRUE, TRUE, 3); + GtkWidget * bbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout ((GtkButtonBox *) bbox, GTK_BUTTONBOX_END); + gtk_box_set_spacing ((GtkBox *) bbox, 6); + gtk_box_pack_end ((GtkBox *) hbox, bbox, true, true, 0); GtkWidget * action_button = audgui_button_new (verb, icon, open_cb, chooser); GtkWidget * close_button = audgui_button_new (_("_Close"), "window-close", - (AudguiCallback) audgui_hide_filebrowser, NULL); + (AudguiCallback) audgui_hide_filebrowser, nullptr); - gtk_container_add(GTK_CONTAINER(bbox), close_button); - gtk_container_add(GTK_CONTAINER(bbox), action_button); + gtk_container_add ((GtkContainer *) bbox, close_button); + gtk_container_add ((GtkContainer *) bbox, action_button); - gtk_widget_set_can_default (action_button, TRUE); + gtk_widget_set_can_default (action_button, true); gtk_widget_grab_default (action_button); g_object_set_data ((GObject *) chooser, "toggle-button", toggle); g_object_set_data ((GObject *) chooser, "do-open", GINT_TO_POINTER (open)); - g_signal_connect (chooser, "file-activated", (GCallback) open_cb, NULL); - g_signal_connect (chooser, "destroy", (GCallback) destroy_cb, NULL); + g_signal_connect (chooser, "file-activated", (GCallback) open_cb, nullptr); + g_signal_connect (chooser, "destroy", (GCallback) destroy_cb, nullptr); audgui_destroy_on_escape (window); return window; } -EXPORT void audgui_run_filebrowser (bool_t open) +EXPORT void audgui_run_filebrowser (bool open) { audgui_show_unique_window (AUDGUI_FILEBROWSER_WINDOW, create_filebrowser (open)); } -EXPORT void audgui_hide_filebrowser (void) +EXPORT void audgui_hide_filebrowser () { audgui_hide_unique_window (AUDGUI_FILEBROWSER_WINDOW); } diff --git a/src/libaudgui/infopopup.c b/src/libaudgui/infopopup.cc index d0f6fb0..3051225 100644 --- a/src/libaudgui/infopopup.c +++ b/src/libaudgui/infopopup.cc @@ -21,19 +21,25 @@ #include <gtk/gtk.h> #include <string.h> -#include <audacious/drct.h> -#include <audacious/i18n.h> -#include <audacious/misc.h> -#include <audacious/playlist.h> #include <libaudcore/audstrings.h> +#include <libaudcore/drct.h> #include <libaudcore/hook.h> +#include <libaudcore/i18n.h> +#include <libaudcore/playlist.h> +#include <libaudcore/runtime.h> +#include <libaudcore/tuple.h> -#include "init.h" +#include "internal.h" #include "libaudgui.h" #include "libaudgui-gtk.h" #define IMAGE_SIZE 96 +static void infopopup_move_to_mouse (GtkWidget * infopopup); + +static const GdkColor gray = {0, 40960, 40960, 40960}; +static const GdkColor white = {0, 65535, 65535, 65535}; + static struct { GtkWidget * title_header, * title_label; GtkWidget * artist_header, * artist_label; @@ -46,26 +52,39 @@ static struct { GtkWidget * progress; } widgets; -static char * current_file = NULL; +static String current_file; +static GtkWidget * infopopup_queued; static int progress_source = 0; -static void infopopup_display_image (const char * filename) +/* returns false if album art fetch was queued */ +static bool infopopup_display_image (const char * filename) { - if (! current_file || strcmp (filename, current_file)) - return; - - GdkPixbuf * pb = audgui_pixbuf_request (filename); + bool queued; + GdkPixbuf * pb = audgui_pixbuf_request (filename, & queued); if (! pb) - pb = audgui_pixbuf_fallback (); + return ! queued; audgui_pixbuf_scale_within (& pb, IMAGE_SIZE); gtk_image_set_from_pixbuf ((GtkImage *) widgets.image, pb); + gtk_widget_show (widgets.image); + g_object_unref (pb); + return true; +} + +static void infopopup_art_ready (const char * filename) +{ + if (! infopopup_queued || strcmp (filename, current_file)) + return; + + infopopup_display_image (filename); + audgui_show_unique_window (AUDGUI_INFOPOPUP_WINDOW, infopopup_queued); + infopopup_queued = nullptr; } -static bool_t infopopup_progress_cb (void * unused) +static gboolean infopopup_progress_cb (void *) { - char * filename = NULL; + String filename; int length = 0, time = 0; if (aud_drct_get_playing ()) @@ -75,47 +94,77 @@ static bool_t infopopup_progress_cb (void * unused) time = aud_drct_get_time (); } - if (aud_get_bool (NULL, "filepopup_showprogressbar") && filename && + if (aud_get_bool (nullptr, "filepopup_showprogressbar") && filename && current_file && ! strcmp (filename, current_file) && length > 0) { - gtk_progress_bar_set_fraction ((GtkProgressBar *) widgets.progress, - time / (float) length); - - char time_str[16]; - audgui_format_time (time_str, sizeof time_str, time); - gtk_progress_bar_set_text ((GtkProgressBar *) widgets.progress, time_str); - + gtk_progress_bar_set_fraction ((GtkProgressBar *) widgets.progress, time / (float) length); + gtk_progress_bar_set_text ((GtkProgressBar *) widgets.progress, str_format_time (time)); gtk_widget_show (widgets.progress); } else gtk_widget_hide (widgets.progress); - str_unref (filename); - return TRUE; + return true; +} + +static void infopopup_realized (GtkWidget * widget) +{ + GdkWindow * window = gtk_widget_get_window (widget); + gdk_window_set_back_pixmap (window, nullptr, false); + infopopup_move_to_mouse (widget); +} + +/* borrowed from the gtkui infoarea */ +static gboolean infopopup_draw_bg (GtkWidget * widget) +{ + GtkAllocation alloc; + gtk_widget_get_allocation (widget, & alloc); + + cairo_t * cr = gdk_cairo_create (gtk_widget_get_window (widget)); + + cairo_pattern_t * gradient = cairo_pattern_create_linear (0, 0, 0, alloc.height); + cairo_pattern_add_color_stop_rgb (gradient, 0, 0.25, 0.25, 0.25); + cairo_pattern_add_color_stop_rgb (gradient, 0.5, 0.15, 0.15, 0.15); + cairo_pattern_add_color_stop_rgb (gradient, 0.5, 0.1, 0.1, 0.1); + cairo_pattern_add_color_stop_rgb (gradient, 1, 0, 0, 0); + + cairo_set_source (cr, gradient); + cairo_rectangle (cr, 0, 0, alloc.width, alloc.height); + cairo_fill (cr); + + cairo_pattern_destroy (gradient); + cairo_destroy (cr); + return false; } static void infopopup_add_category (GtkWidget * grid, int position, const char * text, GtkWidget * * header, GtkWidget * * label) { - * header = gtk_label_new (NULL); - * label = gtk_label_new (NULL); + * header = gtk_label_new (nullptr); + * label = gtk_label_new (nullptr); - gtk_misc_set_alignment ((GtkMisc *) * header, 0, 0.5); + gtk_misc_set_alignment ((GtkMisc *) * header, 1, 0.5); gtk_misc_set_alignment ((GtkMisc *) * label, 0, 0.5); - gtk_misc_set_padding ((GtkMisc *) * header, 0, 1); - gtk_misc_set_padding ((GtkMisc *) * label, 0, 1); + + gtk_widget_modify_fg (* header, GTK_STATE_NORMAL, & gray); + gtk_widget_modify_fg (* label, GTK_STATE_NORMAL, & white); char * markup = g_markup_printf_escaped ("<span style=\"italic\">%s</span>", text); gtk_label_set_markup ((GtkLabel *) * header, markup); g_free (markup); - gtk_grid_attach ((GtkGrid *) grid, * header, 0, position, 1, 1); - gtk_grid_attach ((GtkGrid *) grid, * label, 1, position, 1, 1); + gtk_table_attach ((GtkTable *) grid, * header, 0, 1, position, position + 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_table_attach ((GtkTable *) grid, * label, 1, 2, position, position + 1, + GTK_FILL, GTK_FILL, 0, 0); + + gtk_widget_set_no_show_all (* header, true); + gtk_widget_set_no_show_all (* label, true); } -static void infopopup_destroyed (void) +static void infopopup_destroyed () { - hook_dissociate ("art ready", (HookFunction) infopopup_display_image); + hook_dissociate ("art ready", (HookFunction) infopopup_art_ready); if (progress_source) { @@ -125,27 +174,28 @@ static void infopopup_destroyed (void) memset (& widgets, 0, sizeof widgets); - str_unref (current_file); - current_file = NULL; + current_file = String (); + infopopup_queued = nullptr; } -static GtkWidget * infopopup_create (void) +static GtkWidget * infopopup_create () { GtkWidget * infopopup = gtk_window_new (GTK_WINDOW_POPUP); gtk_window_set_type_hint ((GtkWindow *) infopopup, GDK_WINDOW_TYPE_HINT_TOOLTIP); - gtk_window_set_decorated ((GtkWindow *) infopopup, FALSE); + gtk_window_set_decorated ((GtkWindow *) infopopup, false); gtk_container_set_border_width ((GtkContainer *) infopopup, 4); - GtkWidget * hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + GtkWidget * hbox = gtk_hbox_new (false, 6); gtk_container_add ((GtkContainer *) infopopup, hbox); widgets.image = gtk_image_new (); gtk_widget_set_size_request (widgets.image, IMAGE_SIZE, IMAGE_SIZE); - gtk_box_pack_start ((GtkBox *) hbox, widgets.image, FALSE, FALSE, 0); + gtk_box_pack_start ((GtkBox *) hbox, widgets.image, false, false, 0); + gtk_widget_set_no_show_all (widgets.image, true); - GtkWidget * grid = gtk_grid_new (); - gtk_grid_set_column_spacing ((GtkGrid *) grid, 6); - gtk_box_pack_start ((GtkBox *) hbox, grid, TRUE, TRUE, 0); + GtkWidget * grid = gtk_table_new (0, 0, false); + gtk_table_set_col_spacings ((GtkTable *) grid, 6); + gtk_box_pack_start ((GtkBox *) hbox, grid, true, true, 0); infopopup_add_category (grid, 0, _("Title"), & widgets.title_header, & widgets.title_label); infopopup_add_category (grid, 1, _("Artist"), & widgets.artist_header, & widgets.artist_label); @@ -157,25 +207,32 @@ static GtkWidget * infopopup_create (void) /* track progress */ widgets.progress = gtk_progress_bar_new (); - gtk_widget_set_margin_top (widgets.progress, 6); - gtk_progress_bar_set_show_text ((GtkProgressBar *) widgets.progress, TRUE); gtk_progress_bar_set_text ((GtkProgressBar *) widgets.progress, ""); - gtk_grid_attach ((GtkGrid *) grid, widgets.progress, 0, 7, 2, 1); + gtk_table_set_row_spacing ((GtkTable *) grid, 6, 4); + gtk_table_attach ((GtkTable *) grid, widgets.progress, 0, 2, 7, 8, + GTK_FILL, GTK_FILL, 0, 0); /* do not show the track progress */ - gtk_widget_set_no_show_all (widgets.progress, TRUE); + gtk_widget_set_no_show_all (widgets.progress, true); + + /* override background drawing */ + gtk_widget_set_app_paintable (infopopup, true); + + GtkStyle * style = gtk_style_new (); + gtk_widget_set_style (infopopup, style); + g_object_unref (style); + + g_signal_connect (infopopup, "realize", (GCallback) infopopup_realized, nullptr); + g_signal_connect (infopopup, "expose-event", (GCallback) infopopup_draw_bg, nullptr); return infopopup; } -/* calls str_unref() on <text> */ -static void infopopup_set_field (GtkWidget * header, GtkWidget * label, char * text) +static void infopopup_set_field (GtkWidget * header, GtkWidget * label, const char * text) { if (text) { gtk_label_set_text ((GtkLabel *) label, text); - str_unref (text); - gtk_widget_show (header); gtk_widget_show (label); } @@ -186,63 +243,50 @@ static void infopopup_set_field (GtkWidget * header, GtkWidget * label, char * t } } -static void infopopup_set_fields (const Tuple * tuple, const char * title) +static void infopopup_set_fields (const Tuple & tuple) { - /* use title from tuple if possible */ - char * title2 = tuple_get_str (tuple, FIELD_TITLE); - if (! title2) - title2 = str_get (title); - - char * artist = tuple_get_str (tuple, FIELD_ARTIST); - char * album = tuple_get_str (tuple, FIELD_ALBUM); - char * genre = tuple_get_str (tuple, FIELD_GENRE); + String title = tuple.get_str (Tuple::Title); + String artist = tuple.get_str (Tuple::Artist); + String album = tuple.get_str (Tuple::Album); + String genre = tuple.get_str (Tuple::Genre); - infopopup_set_field (widgets.title_header, widgets.title_label, title2); + infopopup_set_field (widgets.title_header, widgets.title_label, title); infopopup_set_field (widgets.artist_header, widgets.artist_label, artist); infopopup_set_field (widgets.album_header, widgets.album_label, album); infopopup_set_field (widgets.genre_header, widgets.genre_label, genre); - int value; - char * tmp; + int value = tuple.get_int (Tuple::Length); + infopopup_set_field (widgets.length_header, widgets.length_label, + (value > 0) ? (const char *) str_format_time (value) : nullptr); - value = tuple_get_int (tuple, FIELD_LENGTH); + value = tuple.get_int (Tuple::Year); + infopopup_set_field (widgets.year_header, widgets.year_label, + (value > 0) ? (const char *) int_to_str (value) : nullptr); - if (value > 0) - { - char buf[16]; - audgui_format_time (buf, sizeof buf, value); - tmp = str_get (buf); - } - else - tmp = NULL; - - infopopup_set_field (widgets.length_header, widgets.length_label, tmp); - - value = tuple_get_int (tuple, FIELD_YEAR); - tmp = (value > 0) ? int_to_str (value) : NULL; - infopopup_set_field (widgets.year_header, widgets.year_label, tmp); - - value = tuple_get_int (tuple, FIELD_TRACK_NUMBER); - tmp = (value > 0) ? int_to_str (value) : NULL; - infopopup_set_field (widgets.track_header, widgets.track_label, tmp); + value = tuple.get_int (Tuple::Track); + infopopup_set_field (widgets.track_header, widgets.track_label, + (value > 0) ? (const char *) int_to_str (value) : nullptr); } static void infopopup_move_to_mouse (GtkWidget * infopopup) { + GdkScreen * screen = gtk_widget_get_screen (infopopup); + GdkRectangle geom; int x, y, h, w; - audgui_get_mouse_coords (NULL, & x, & y); + audgui_get_mouse_coords (screen, & x, & y); + audgui_get_monitor_geometry (screen, x, y, & geom); gtk_window_get_size ((GtkWindow *) infopopup, & w, & h); /* If we show the popup right under the cursor, the underlying window gets * a "leave-notify-event" and immediately hides the popup again. So, we * offset the popup slightly. */ - if (x + w > gdk_screen_width ()) + if (x + w > geom.x + geom.width) x -= w + 3; else x += 3; - if (y + h > gdk_screen_height ()) + if (y + h > geom.y + geom.height) y -= h + 3; else y += 3; @@ -250,51 +294,43 @@ static void infopopup_move_to_mouse (GtkWidget * infopopup) gtk_window_move ((GtkWindow *) infopopup, x, y); } -static void infopopup_show (const char * filename, const Tuple * tuple, - const char * title) +static void infopopup_show (const char * filename, const Tuple & tuple) { - audgui_hide_unique_window (AUDGUI_INFOPOPUP_WINDOW); + audgui_infopopup_hide (); - str_unref (current_file); - current_file = str_get (filename); + current_file = String (filename); GtkWidget * infopopup = infopopup_create (); - infopopup_set_fields (tuple, title); - infopopup_display_image (filename); + infopopup_set_fields (tuple); - hook_associate ("art ready", (HookFunction) infopopup_display_image, NULL); + hook_associate ("art ready", (HookFunction) infopopup_art_ready, nullptr); - g_signal_connect (infopopup, "destroy", (GCallback) infopopup_destroyed, NULL); + g_signal_connect (infopopup, "destroy", (GCallback) infopopup_destroyed, nullptr); /* start a timer that updates a progress bar if the tooltip is shown for the song that is being currently played */ if (! progress_source) - progress_source = g_timeout_add (500, infopopup_progress_cb, NULL); + progress_source = g_timeout_add (500, infopopup_progress_cb, nullptr); /* immediately run the callback once to update progressbar status */ - infopopup_progress_cb (NULL); - - infopopup_move_to_mouse (infopopup); + infopopup_progress_cb (nullptr); - audgui_show_unique_window (AUDGUI_INFOPOPUP_WINDOW, infopopup); + if (infopopup_display_image (filename)) + audgui_show_unique_window (AUDGUI_INFOPOPUP_WINDOW, infopopup); + else + infopopup_queued = infopopup; } EXPORT void audgui_infopopup_show (int playlist, int entry) { - char * filename = aud_playlist_entry_get_filename (playlist, entry); - char * title = aud_playlist_entry_get_title (playlist, entry, FALSE); - Tuple * tuple = aud_playlist_entry_get_tuple (playlist, entry, FALSE); + String filename = aud_playlist_entry_get_filename (playlist, entry); + Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry); - if (filename && title && tuple) - infopopup_show (filename, tuple, title); - - str_unref (filename); - str_unref (title); - if (tuple) - tuple_unref (tuple); + if (filename && tuple) + infopopup_show (filename, tuple); } -EXPORT void audgui_infopopup_show_current (void) +EXPORT void audgui_infopopup_show_current () { int playlist = aud_playlist_get_playing (); if (playlist < 0) @@ -307,7 +343,10 @@ EXPORT void audgui_infopopup_show_current (void) audgui_infopopup_show (playlist, position); } -EXPORT void audgui_infopopup_hide (void) +EXPORT void audgui_infopopup_hide () { audgui_hide_unique_window (AUDGUI_INFOPOPUP_WINDOW); + + if (infopopup_queued) + gtk_widget_destroy (infopopup_queued); } diff --git a/src/libaudgui/infowin.c b/src/libaudgui/infowin.c deleted file mode 100644 index 530a65e..0000000 --- a/src/libaudgui/infowin.c +++ /dev/null @@ -1,485 +0,0 @@ -/* - * libaudgui/infowin.c - * Copyright 2006-2013 William Pitcock, Tomasz MoĆ, Eugene Zagidullin, - * John Lindgren, and Thomas Lange - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <gtk/gtk.h> -#include <stdarg.h> -#include <stdlib.h> -#include <string.h> - -#include <audacious/i18n.h> -#include <audacious/misc.h> -#include <audacious/playlist.h> -#include <libaudcore/audstrings.h> -#include <libaudcore/hook.h> - -#include "init.h" -#include "libaudgui.h" -#include "libaudgui-gtk.h" - -#define AUDGUI_STATUS_TIMEOUT 3000 - -enum { - CODEC_FORMAT, - CODEC_QUALITY, - CODEC_BITRATE, - CODEC_ITEMS -}; - -static const char * codec_labels[CODEC_ITEMS] = { - N_("Format:"), - N_("Quality:"), - N_("Bitrate:") -}; - -static struct { - GtkWidget * location; - GtkWidget * title; - GtkWidget * artist; - GtkWidget * album; - GtkWidget * comment; - GtkWidget * year; - GtkWidget * track; - GtkWidget * genre; - GtkWidget * image; - GtkWidget * codec[3]; - GtkWidget * apply; - GtkWidget * ministatus; -} widgets; - -static char * current_file = NULL; -static PluginHandle * current_decoder = NULL; -static bool_t can_write = FALSE; -static int timeout_source = 0; - -/* This is by no means intended to be a complete list. If it is not short, it - * is useless: scrolling through ten pages of dropdown list is more work than - * typing out the genre. */ - -static const char * genre_table[] = { - N_("Acid Jazz"), - N_("Acid Rock"), - N_("Ambient"), - N_("Bebop"), - N_("Bluegrass"), - N_("Blues"), - N_("Chamber Music"), - N_("Classical"), - N_("Country"), - N_("Death Metal"), - N_("Disco"), - N_("Easy Listening"), - N_("Folk"), - N_("Funk"), - N_("Gangsta Rap"), - N_("Gospel"), - N_("Grunge"), - N_("Hard Rock"), - N_("Heavy Metal"), - N_("Hip-hop"), - N_("House"), - N_("Jazz"), - N_("Jungle"), - N_("Metal"), - N_("New Age"), - N_("New Wave"), - N_("Noise"), - N_("Pop"), - N_("Punk Rock"), - N_("Rap"), - N_("Reggae"), - N_("Rock"), - N_("Rock and Roll"), - N_("Rhythm and Blues"), - N_("Ska"), - N_("Soul"), - N_("Swing"), - N_("Techno"), - N_("Trip-hop")}; - -static GtkWidget * small_label_new (const char * text) -{ - static PangoAttrList * attrs = NULL; - - if (! attrs) - { - attrs = pango_attr_list_new (); - pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL)); - } - - GtkWidget * label = gtk_label_new (text); - gtk_label_set_attributes ((GtkLabel *) label, attrs); - gtk_misc_set_alignment ((GtkMisc *) label, 0, 0.5); - - return label; -} - -static void set_entry_str_from_field (GtkWidget * widget, const Tuple * tuple, - int fieldn, bool_t editable) -{ - char * text = tuple_get_str (tuple, fieldn); - - gtk_entry_set_text ((GtkEntry *) widget, text != NULL ? text : ""); - gtk_editable_set_editable ((GtkEditable *) widget, editable); - - str_unref (text); -} - -static void set_entry_int_from_field (GtkWidget * widget, const Tuple * tuple, - int fieldn, bool_t editable) -{ - char scratch[32]; - - if (tuple_get_value_type (tuple, fieldn) == TUPLE_INT) - str_itoa (tuple_get_int (tuple, fieldn), scratch, sizeof scratch); - else - scratch[0] = 0; - - gtk_entry_set_text ((GtkEntry *) widget, scratch); - gtk_editable_set_editable ((GtkEditable *) widget, editable); -} - -static void set_field_str_from_entry (Tuple * tuple, int fieldn, GtkWidget * - widget) -{ - const char * text = gtk_entry_get_text ((GtkEntry *) widget); - - if (text[0]) - tuple_set_str (tuple, fieldn, text); - else - tuple_unset (tuple, fieldn); -} - -static void set_field_int_from_entry (Tuple * tuple, int fieldn, GtkWidget * - widget) -{ - const char * text = gtk_entry_get_text ((GtkEntry *) widget); - - if (text[0]) - tuple_set_int (tuple, fieldn, atoi (text)); - else - tuple_unset (tuple, fieldn); -} - -static void entry_changed (GtkEditable * editable, void * unused) -{ - if (can_write) - gtk_widget_set_sensitive (widgets.apply, TRUE); -} - -static bool_t ministatus_timeout_proc (void) -{ - gtk_label_set_text ((GtkLabel *) widgets.ministatus, NULL); - - timeout_source = 0; - return FALSE; -} - -static void ministatus_display_message (const char * text) -{ - gtk_label_set_text ((GtkLabel *) widgets.ministatus, text); - - if (timeout_source) - g_source_remove (timeout_source); - - timeout_source = g_timeout_add (AUDGUI_STATUS_TIMEOUT, (GSourceFunc) - ministatus_timeout_proc, NULL); -} - -static void infowin_update_tuple (void * unused) -{ - Tuple * tuple = tuple_new_from_filename (current_file); - - set_field_str_from_entry (tuple, FIELD_TITLE, widgets.title); - set_field_str_from_entry (tuple, FIELD_ARTIST, widgets.artist); - set_field_str_from_entry (tuple, FIELD_ALBUM, widgets.album); - set_field_str_from_entry (tuple, FIELD_COMMENT, widgets.comment); - set_field_str_from_entry (tuple, FIELD_GENRE, gtk_bin_get_child ((GtkBin *) - widgets.genre)); - set_field_int_from_entry (tuple, FIELD_YEAR, widgets.year); - set_field_int_from_entry (tuple, FIELD_TRACK_NUMBER, widgets.track); - - if (aud_file_write_tuple (current_file, current_decoder, tuple)) - { - ministatus_display_message (_("Save successful")); - gtk_widget_set_sensitive (widgets.apply, FALSE); - } - else - ministatus_display_message (_("Save error")); - - tuple_unref (tuple); -} - -static bool_t genre_fill (GtkWidget * combo) -{ - GList * list = NULL; - GList * node; - int i; - - for (i = 0; i < ARRAY_LEN (genre_table); i ++) - list = g_list_prepend (list, _(genre_table[i])); - - list = g_list_sort (list, (GCompareFunc) strcmp); - - for (node = list; node != NULL; node = node->next) - gtk_combo_box_text_append_text ((GtkComboBoxText *) combo, node->data); - - g_list_free (list); - return FALSE; -} - -static void infowin_display_image (const char * filename) -{ - if (! current_file || strcmp (filename, current_file)) - return; - - GdkPixbuf * pb = audgui_pixbuf_request (filename); - if (! pb) - pb = audgui_pixbuf_fallback (); - - if (pb) - { - audgui_scaled_image_set (widgets.image, pb); - g_object_unref (pb); - } -} - -static void infowin_destroyed (void) -{ - hook_dissociate ("art ready", (HookFunction) infowin_display_image); - - if (timeout_source) - { - g_source_remove (timeout_source); - timeout_source = 0; - } - - memset (& widgets, 0, sizeof widgets); - - str_unref (current_file); - current_file = NULL; - current_decoder = NULL; - can_write = FALSE; -} - -static void add_entry (GtkWidget * grid, const char * title, GtkWidget * entry, - int x, int y, int span) -{ - GtkWidget * label = small_label_new (title); - - if (y > 0) - gtk_widget_set_margin_top (label, 6); - - gtk_grid_attach ((GtkGrid *) grid, label, x, y, span, 1); - gtk_grid_attach ((GtkGrid *) grid, entry, x, y + 1, span, 1); - - g_signal_connect (entry, "changed", (GCallback) entry_changed, NULL); -} - -static GtkWidget * create_infowin (void) -{ - GtkWidget * infowin = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_container_set_border_width ((GtkContainer *) infowin, 6); - gtk_window_set_title ((GtkWindow *) infowin, _("Song Info")); - gtk_window_set_type_hint ((GtkWindow *) infowin, - GDK_WINDOW_TYPE_HINT_DIALOG); - - GtkWidget * main_grid = gtk_grid_new (); - gtk_grid_set_column_spacing ((GtkGrid *) main_grid, 6); - gtk_grid_set_row_spacing ((GtkGrid *) main_grid, 6); - gtk_container_add ((GtkContainer *) infowin, main_grid); - - widgets.image = audgui_scaled_image_new (NULL); - gtk_widget_set_hexpand (widgets.image, TRUE); - gtk_widget_set_vexpand (widgets.image, TRUE); - gtk_grid_attach ((GtkGrid *) main_grid, widgets.image, 0, 0, 1, 1); - - widgets.location = gtk_label_new (""); - gtk_label_set_max_width_chars ((GtkLabel *) widgets.location, 40); - gtk_label_set_line_wrap ((GtkLabel *) widgets.location, TRUE); - gtk_label_set_line_wrap_mode ((GtkLabel *) widgets.location, PANGO_WRAP_WORD_CHAR); - gtk_label_set_selectable ((GtkLabel *) widgets.location, TRUE); - gtk_grid_attach ((GtkGrid *) main_grid, widgets.location, 0, 1, 1, 1); - - GtkWidget * codec_grid = gtk_grid_new (); - gtk_grid_set_row_spacing ((GtkGrid *) codec_grid, 3); - gtk_grid_set_column_spacing ((GtkGrid *) codec_grid, 12); - gtk_grid_attach ((GtkGrid *) main_grid, codec_grid, 0, 2, 1, 1); - - for (int row = 0; row < CODEC_ITEMS; row ++) - { - GtkWidget * label = small_label_new (_(codec_labels[row])); - gtk_grid_attach ((GtkGrid *) codec_grid, label, 0, row, 1, 1); - - widgets.codec[row] = small_label_new (NULL); - gtk_grid_attach ((GtkGrid *) codec_grid, widgets.codec[row], 1, row, 1, 1); - } - - GtkWidget * grid = gtk_grid_new (); - gtk_grid_set_column_homogeneous ((GtkGrid *) grid, TRUE); - gtk_grid_set_column_spacing ((GtkGrid *) grid, 6); - gtk_grid_attach ((GtkGrid *) main_grid, grid, 1, 0, 1, 3); - - widgets.title = gtk_entry_new (); - add_entry (grid, _("Title"), widgets.title, 0, 0, 2); - - widgets.artist = gtk_entry_new (); - add_entry (grid, _("Artist"), widgets.artist, 0, 2, 2); - - widgets.album = gtk_entry_new (); - add_entry (grid, _("Album"), widgets.album, 0, 4, 2); - - widgets.comment = gtk_entry_new (); - add_entry (grid, _("Comment"), widgets.comment, 0, 6, 2); - - widgets.genre = gtk_combo_box_text_new_with_entry (); - add_entry (grid, _("Genre"), widgets.genre, 0, 8, 2); - g_idle_add ((GSourceFunc) genre_fill, widgets.genre); - - widgets.year = gtk_entry_new (); - add_entry (grid, _("Year"), widgets.year, 0, 10, 1); - - widgets.track = gtk_entry_new (); - add_entry (grid, _("Track Number"), widgets.track, 1, 10, 1); - - GtkWidget * bottom_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); - gtk_grid_attach ((GtkGrid *) main_grid, bottom_hbox, 0, 3, 2, 1); - - widgets.ministatus = small_label_new (NULL); - gtk_box_pack_start ((GtkBox *) bottom_hbox, widgets.ministatus, TRUE, TRUE, 0); - - widgets.apply = audgui_button_new (_("_Save"), "document-save", - (AudguiCallback) infowin_update_tuple, NULL); - - GtkWidget * close_button = audgui_button_new (_("_Close"), "window-close", - (AudguiCallback) audgui_infowin_hide, NULL); - - gtk_box_pack_end ((GtkBox *) bottom_hbox, close_button, FALSE, FALSE, 0); - gtk_box_pack_end ((GtkBox *) bottom_hbox, widgets.apply, FALSE, FALSE, 0); - - audgui_destroy_on_escape (infowin); - g_signal_connect (infowin, "destroy", (GCallback) infowin_destroyed, NULL); - - hook_associate ("art ready", (HookFunction) infowin_display_image, NULL); - - return infowin; -} - -static void infowin_show (int list, int entry, const char * filename, - const Tuple * tuple, PluginHandle * decoder, bool_t updating_enabled) -{ - audgui_hide_unique_window (AUDGUI_INFO_WINDOW); - - GtkWidget * infowin = create_infowin (); - - str_unref (current_file); - current_file = str_get (filename); - current_decoder = decoder; - can_write = updating_enabled; - - set_entry_str_from_field (widgets.title, tuple, FIELD_TITLE, updating_enabled); - set_entry_str_from_field (widgets.artist, tuple, FIELD_ARTIST, updating_enabled); - set_entry_str_from_field (widgets.album, tuple, FIELD_ALBUM, updating_enabled); - set_entry_str_from_field (widgets.comment, tuple, FIELD_COMMENT, updating_enabled); - set_entry_str_from_field (gtk_bin_get_child ((GtkBin *) widgets.genre), - tuple, FIELD_GENRE, updating_enabled); - - char * tmp = uri_to_display (filename); - gtk_label_set_text ((GtkLabel *) widgets.location, tmp); - str_unref (tmp); - - set_entry_int_from_field (widgets.year, tuple, FIELD_YEAR, updating_enabled); - set_entry_int_from_field (widgets.track, tuple, FIELD_TRACK_NUMBER, updating_enabled); - - char * codec_values[CODEC_ITEMS] = { - [CODEC_FORMAT] = tuple_get_str (tuple, FIELD_CODEC), - [CODEC_QUALITY] = tuple_get_str (tuple, FIELD_QUALITY) - }; - - if (tuple_get_value_type (tuple, FIELD_BITRATE) == TUPLE_INT) - { - int bitrate = tuple_get_int (tuple, FIELD_BITRATE); - codec_values[CODEC_BITRATE] = str_printf (_("%d kb/s"), bitrate); - } - - for (int row = 0; row < CODEC_ITEMS; row ++) - { - const char * text = codec_values[row] ? codec_values[row] : _("N/A"); - gtk_label_set_text ((GtkLabel *) widgets.codec[row], text); - str_unref (codec_values[row]); - } - - infowin_display_image (filename); - - /* nothing has been changed yet */ - gtk_widget_set_sensitive (widgets.apply, FALSE); - - audgui_show_unique_window (AUDGUI_INFO_WINDOW, infowin); -} - -EXPORT void audgui_infowin_show (int playlist, int entry) -{ - char * filename = aud_playlist_entry_get_filename (playlist, entry); - g_return_if_fail (filename != NULL); - - PluginHandle * decoder = aud_playlist_entry_get_decoder (playlist, entry, - FALSE); - if (decoder == NULL) - goto FREE; - - if (aud_custom_infowin (filename, decoder)) - goto FREE; - - Tuple * tuple = aud_playlist_entry_get_tuple (playlist, entry, FALSE); - - if (tuple == NULL) - { - SPRINTF (message, _("No info available for %s.\n"), filename); - aud_interface_show_error (message); - goto FREE; - } - - infowin_show (playlist, entry, filename, tuple, decoder, - aud_file_can_write_tuple (filename, decoder)); - tuple_unref (tuple); - -FREE: - str_unref (filename); -} - -EXPORT void audgui_infowin_show_current (void) -{ - int playlist = aud_playlist_get_playing (); - int position; - - if (playlist == -1) - playlist = aud_playlist_get_active (); - - position = aud_playlist_get_position (playlist); - - if (position == -1) - return; - - audgui_infowin_show (playlist, position); -} - -EXPORT void audgui_infowin_hide (void) -{ - audgui_hide_unique_window (AUDGUI_INFO_WINDOW); -} diff --git a/src/libaudgui/infowin.cc b/src/libaudgui/infowin.cc new file mode 100644 index 0000000..1eabdb6 --- /dev/null +++ b/src/libaudgui/infowin.cc @@ -0,0 +1,519 @@ +/* + * infowin.c + * Copyright 2006-2013 William Pitcock, Tomasz MoĆ, Eugene Zagidullin, + * John Lindgren, and Thomas Lange + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <gtk/gtk.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> + +#include <libaudcore/audstrings.h> +#include <libaudcore/hook.h> +#include <libaudcore/i18n.h> +#include <libaudcore/interface.h> +#include <libaudcore/playlist.h> +#include <libaudcore/probe.h> +#include <libaudcore/runtime.h> +#include <libaudcore/tuple.h> + +#include "internal.h" +#include "libaudgui.h" +#include "libaudgui-gtk.h" + +#define AUDGUI_STATUS_TIMEOUT 3000 + +enum { + CODEC_FORMAT, + CODEC_QUALITY, + CODEC_BITRATE, + CODEC_ITEMS +}; + +static const char * codec_labels[CODEC_ITEMS] = { + N_("Format:"), + N_("Quality:"), + N_("Bitrate:") +}; + +static struct { + GtkWidget * location; + GtkWidget * title; + GtkWidget * artist; + GtkWidget * album; + GtkWidget * album_artist; + GtkWidget * comment; + GtkWidget * year; + GtkWidget * track; + GtkWidget * genre; + GtkWidget * image; + GtkWidget * codec[3]; + GtkWidget * apply; + GtkWidget * clear; + GtkWidget * ministatus; +} widgets; + +static GtkWidget * infowin; +static int current_playlist_id, current_entry; +static String current_file; +static PluginHandle * current_decoder = nullptr; +static bool can_write = false; +static int timeout_source = 0; + +/* This is by no means intended to be a complete list. If it is not short, it + * is useless: scrolling through ten pages of dropdown list is more work than + * typing out the genre. */ + +static const char * genre_table[] = { + N_("Acid Jazz"), + N_("Acid Rock"), + N_("Ambient"), + N_("Bebop"), + N_("Bluegrass"), + N_("Blues"), + N_("Chamber Music"), + N_("Classical"), + N_("Country"), + N_("Death Metal"), + N_("Disco"), + N_("Easy Listening"), + N_("Folk"), + N_("Funk"), + N_("Gangsta Rap"), + N_("Gospel"), + N_("Grunge"), + N_("Hard Rock"), + N_("Heavy Metal"), + N_("Hip-hop"), + N_("House"), + N_("Jazz"), + N_("Jungle"), + N_("Metal"), + N_("New Age"), + N_("New Wave"), + N_("Noise"), + N_("Pop"), + N_("Punk Rock"), + N_("Rap"), + N_("Reggae"), + N_("Rock"), + N_("Rock and Roll"), + N_("Rhythm and Blues"), + N_("Ska"), + N_("Soul"), + N_("Swing"), + N_("Techno"), + N_("Trip-hop")}; + +static GtkWidget * small_label_new (const char * text) +{ + static PangoAttrList * attrs = nullptr; + + if (! attrs) + { + attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL)); + } + + GtkWidget * label = gtk_label_new (text); + gtk_label_set_attributes ((GtkLabel *) label, attrs); + gtk_misc_set_alignment ((GtkMisc *) label, 0, 0.5); + + return label; +} + +static void set_entry_str_from_field (GtkWidget * widget, const Tuple & tuple, + Tuple::Field field, bool editable, bool clear) +{ + String text = tuple.get_str (field); + if (! text && ! clear) + return; + + gtk_entry_set_text ((GtkEntry *) widget, text ? text : ""); + gtk_editable_set_editable ((GtkEditable *) widget, editable); +} + +static void set_entry_int_from_field (GtkWidget * widget, const Tuple & tuple, + Tuple::Field field, bool editable, bool clear) +{ + int value = tuple.get_int (field); + if (value <= 0 && ! clear) + return; + + gtk_entry_set_text ((GtkEntry *) widget, (value > 0) ? (const char *) int_to_str (value) : ""); + gtk_editable_set_editable ((GtkEditable *) widget, editable); +} + +static void set_field_str_from_entry (Tuple & tuple, Tuple::Field field, GtkWidget * widget) +{ + const char * text = gtk_entry_get_text ((GtkEntry *) widget); + + if (text[0]) + tuple.set_str (field, text); + else + tuple.unset (field); +} + +static void set_field_int_from_entry (Tuple & tuple, Tuple::Field field, GtkWidget * widget) +{ + const char * text = gtk_entry_get_text ((GtkEntry *) widget); + + if (text[0]) + tuple.set_int (field, atoi (text)); + else + tuple.unset (field); +} + +static void entry_changed (GtkEditable * editable) +{ + if (can_write) + gtk_widget_set_sensitive (widgets.apply, true); +} + +static gboolean ministatus_timeout_proc () +{ + gtk_widget_hide (widgets.ministatus); + gtk_widget_show (widgets.clear); + + timeout_source = 0; + return G_SOURCE_REMOVE; +} + +static void ministatus_display_message (const char * text) +{ + gtk_label_set_text ((GtkLabel *) widgets.ministatus, text); + gtk_widget_hide (widgets.clear); + gtk_widget_show (widgets.ministatus); + + if (timeout_source) + g_source_remove (timeout_source); + + timeout_source = g_timeout_add (AUDGUI_STATUS_TIMEOUT, (GSourceFunc) + ministatus_timeout_proc, nullptr); +} + +static void infowin_update_tuple () +{ + Tuple tuple; + tuple.set_filename (current_file); + + set_field_str_from_entry (tuple, Tuple::Title, widgets.title); + set_field_str_from_entry (tuple, Tuple::Artist, widgets.artist); + set_field_str_from_entry (tuple, Tuple::Album, widgets.album); + set_field_str_from_entry (tuple, Tuple::AlbumArtist, widgets.album_artist); + set_field_str_from_entry (tuple, Tuple::Comment, widgets.comment); + set_field_str_from_entry (tuple, Tuple::Genre, gtk_bin_get_child ((GtkBin *) + widgets.genre)); + set_field_int_from_entry (tuple, Tuple::Year, widgets.year); + set_field_int_from_entry (tuple, Tuple::Track, widgets.track); + + if (aud_file_write_tuple (current_file, current_decoder, tuple)) + { + ministatus_display_message (_("Save successful")); + gtk_widget_set_sensitive (widgets.apply, false); + } + else + ministatus_display_message (_("Save error")); +} + +static void infowin_next () +{ + int list = aud_playlist_by_unique_id (current_playlist_id); + + if (list >= 0 && current_entry + 1 < aud_playlist_entry_count (list)) + audgui_infowin_show (list, current_entry + 1); + else + audgui_infowin_hide (); +} + +static gboolean genre_fill (GtkWidget * combo) +{ + GList * list = nullptr; + GList * node; + + for (const char * genre : genre_table) + list = g_list_prepend (list, _(genre)); + + list = g_list_sort (list, (GCompareFunc) strcmp); + + for (node = list; node != nullptr; node = node->next) + gtk_combo_box_text_append_text ((GtkComboBoxText *) combo, (const char *) node->data); + + g_list_free (list); + return G_SOURCE_REMOVE; +} + +static void clear_toggled (GtkToggleButton * toggle) +{ + aud_set_bool ("audgui", "clear_song_fields", gtk_toggle_button_get_active (toggle)); +} + +static void infowin_display_image (const char * filename) +{ + if (! current_file || strcmp (filename, current_file)) + return; + + GdkPixbuf * pb = audgui_pixbuf_request (filename); + if (! pb) + pb = audgui_pixbuf_fallback (); + + if (pb) + { + audgui_scaled_image_set (widgets.image, pb); + g_object_unref (pb); + } +} + +static void infowin_destroyed () +{ + hook_dissociate ("art ready", (HookFunction) infowin_display_image); + + if (timeout_source) + { + g_source_remove (timeout_source); + timeout_source = 0; + } + + memset (& widgets, 0, sizeof widgets); + + infowin = nullptr; + current_file = String (); + current_decoder = nullptr; +} + +static void add_entry (GtkWidget * grid, const char * title, GtkWidget * entry, + int x, int y, int span) +{ + GtkWidget * label = small_label_new (title); + + gtk_table_attach ((GtkTable *) grid, label, x, x + span, y, y + 1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_table_attach ((GtkTable *) grid, entry, x, x + span, y + 1, y + 2, + GTK_FILL, GTK_FILL, 0, 0); + + g_signal_connect (entry, "changed", (GCallback) entry_changed, nullptr); +} + +static void create_infowin () +{ + infowin = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width ((GtkContainer *) infowin, 6); + gtk_window_set_title ((GtkWindow *) infowin, _("Song Info")); + gtk_window_set_type_hint ((GtkWindow *) infowin, + GDK_WINDOW_TYPE_HINT_DIALOG); + + GtkWidget * main_grid = gtk_table_new (0, 0, false); + gtk_table_set_col_spacings ((GtkTable *) main_grid, 6); + gtk_table_set_row_spacings ((GtkTable *) main_grid, 6); + gtk_container_add ((GtkContainer *) infowin, main_grid); + + widgets.image = audgui_scaled_image_new (nullptr); + gtk_table_attach_defaults ((GtkTable *) main_grid, widgets.image, 0, 1, 0, 1); + + widgets.location = gtk_label_new (""); + gtk_widget_set_size_request (widgets.location, 200, -1); + gtk_label_set_line_wrap ((GtkLabel *) widgets.location, true); + gtk_label_set_line_wrap_mode ((GtkLabel *) widgets.location, PANGO_WRAP_WORD_CHAR); + gtk_label_set_selectable ((GtkLabel *) widgets.location, true); + gtk_table_attach ((GtkTable *) main_grid, widgets.location, 0, 1, 1, 2, + GTK_FILL, GTK_FILL, 0, 0); + + GtkWidget * codec_grid = gtk_table_new (0, 0, false); + gtk_table_set_row_spacings ((GtkTable *) codec_grid, 2); + gtk_table_set_col_spacings ((GtkTable *) codec_grid, 12); + gtk_table_attach ((GtkTable *) main_grid, codec_grid, 0, 1, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + + for (int row = 0; row < CODEC_ITEMS; row ++) + { + GtkWidget * label = small_label_new (_(codec_labels[row])); + gtk_table_attach ((GtkTable *) codec_grid, label, 0, 1, row, row + 1, + GTK_FILL, GTK_FILL, 0, 0); + + widgets.codec[row] = small_label_new (nullptr); + gtk_table_attach ((GtkTable *) codec_grid, widgets.codec[row], 1, 2, row, row + 1, + GTK_FILL, GTK_FILL, 0, 0); + } + + GtkWidget * grid = gtk_table_new (0, 0, false); + gtk_table_set_row_spacings ((GtkTable *) grid, 2); + gtk_table_set_col_spacings ((GtkTable *) grid, 6); + gtk_table_attach ((GtkTable *) main_grid, grid, 1, 2, 0, 3, + GTK_FILL, GTK_FILL, 0, 0); + + widgets.title = gtk_entry_new (); + add_entry (grid, _("Title"), widgets.title, 0, 0, 2); + + widgets.artist = gtk_entry_new (); + add_entry (grid, _("Artist"), widgets.artist, 0, 2, 2); + + widgets.album = gtk_entry_new (); + add_entry (grid, _("Album"), widgets.album, 0, 4, 2); + + widgets.album_artist = gtk_entry_new (); + add_entry (grid, _("Album Artist"), widgets.album_artist, 0, 6, 2); + + widgets.comment = gtk_entry_new (); + add_entry (grid, _("Comment"), widgets.comment, 0, 8, 2); + + widgets.genre = gtk_combo_box_text_new_with_entry (); + add_entry (grid, _("Genre"), widgets.genre, 0, 10, 2); + g_idle_add ((GSourceFunc) genre_fill, widgets.genre); + + widgets.year = gtk_entry_new (); + add_entry (grid, _("Year"), widgets.year, 0, 12, 1); + + widgets.track = gtk_entry_new (); + add_entry (grid, _("Track Number"), widgets.track, 1, 12, 1); + + GtkWidget * bottom_hbox = gtk_hbox_new (false, 6); + gtk_table_attach ((GtkTable *) main_grid, bottom_hbox, 0, 2, 3, 4, + GTK_FILL, GTK_FILL, 0, 0); + + widgets.clear = gtk_check_button_new_with_mnemonic + (_("Clea_r fields when moving to next song")); + + gtk_toggle_button_set_active ((GtkToggleButton *) widgets.clear, + aud_get_bool ("audgui", "clear_song_fields")); + g_signal_connect (widgets.clear, "toggled", (GCallback) clear_toggled, nullptr); + + gtk_widget_set_no_show_all (widgets.clear, true); + gtk_widget_show (widgets.clear); + gtk_box_pack_start ((GtkBox *) bottom_hbox, widgets.clear, false, false, 0); + + widgets.ministatus = small_label_new (nullptr); + gtk_widget_set_no_show_all (widgets.ministatus, true); + gtk_box_pack_start ((GtkBox *) bottom_hbox, widgets.ministatus, true, true, 0); + + widgets.apply = audgui_button_new (_("_Save"), "document-save", + (AudguiCallback) infowin_update_tuple, nullptr); + + GtkWidget * close_button = audgui_button_new (_("_Close"), "window-close", + (AudguiCallback) audgui_infowin_hide, nullptr); + + GtkWidget * next_button = audgui_button_new (_("_Next"), "go-next", + (AudguiCallback) infowin_next, nullptr); + + gtk_box_pack_end ((GtkBox *) bottom_hbox, close_button, false, false, 0); + gtk_box_pack_end ((GtkBox *) bottom_hbox, next_button, false, false, 0); + gtk_box_pack_end ((GtkBox *) bottom_hbox, widgets.apply, false, false, 0); + + audgui_destroy_on_escape (infowin); + g_signal_connect (infowin, "destroy", (GCallback) infowin_destroyed, nullptr); + + hook_associate ("art ready", (HookFunction) infowin_display_image, nullptr); +} + +static void infowin_show (int list, int entry, const char * filename, + const Tuple & tuple, PluginHandle * decoder, bool writable) +{ + if (! infowin) + create_infowin (); + + current_playlist_id = aud_playlist_get_unique_id (list); + current_entry = entry; + current_file = String (filename); + current_decoder = decoder; + can_write = writable; + + bool clear = aud_get_bool ("audgui", "clear_song_fields"); + + set_entry_str_from_field (widgets.title, tuple, Tuple::Title, writable, clear); + set_entry_str_from_field (widgets.artist, tuple, Tuple::Artist, writable, clear); + set_entry_str_from_field (widgets.album, tuple, Tuple::Album, writable, clear); + set_entry_str_from_field (widgets.album_artist, tuple, Tuple::AlbumArtist, writable, clear); + set_entry_str_from_field (widgets.comment, tuple, Tuple::Comment, writable, clear); + set_entry_str_from_field (gtk_bin_get_child ((GtkBin *) widgets.genre), + tuple, Tuple::Genre, writable, clear); + + gtk_label_set_text ((GtkLabel *) widgets.location, uri_to_display (filename)); + + set_entry_int_from_field (widgets.year, tuple, Tuple::Year, writable, clear); + set_entry_int_from_field (widgets.track, tuple, Tuple::Track, writable, clear); + + String codec_values[CODEC_ITEMS]; + + codec_values[CODEC_FORMAT] = tuple.get_str (Tuple::Codec); + codec_values[CODEC_QUALITY] = tuple.get_str (Tuple::Quality); + + if (tuple.get_value_type (Tuple::Bitrate) == Tuple::Int) + codec_values[CODEC_BITRATE] = String (str_printf (_("%d kb/s"), + tuple.get_int (Tuple::Bitrate))); + + for (int row = 0; row < CODEC_ITEMS; row ++) + { + const char * text = codec_values[row] ? (const char *) codec_values[row] : _("N/A"); + gtk_label_set_text ((GtkLabel *) widgets.codec[row], text); + } + + infowin_display_image (filename); + + /* nothing has been changed yet */ + gtk_widget_set_sensitive (widgets.apply, false); + + gtk_widget_grab_focus (widgets.title); + + if (! audgui_reshow_unique_window (AUDGUI_INFO_WINDOW)) + audgui_show_unique_window (AUDGUI_INFO_WINDOW, infowin); +} + +EXPORT void audgui_infowin_show (int playlist, int entry) +{ + String filename = aud_playlist_entry_get_filename (playlist, entry); + g_return_if_fail (filename != nullptr); + + String error; + PluginHandle * decoder = aud_playlist_entry_get_decoder (playlist, entry, + Playlist::Wait, & error); + + if (decoder && ! aud_custom_infowin (filename, decoder)) + { + Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry, Playlist::Wait, & error); + if (tuple) + { + tuple.delete_fallbacks (); + infowin_show (playlist, entry, filename, tuple, decoder, + aud_file_can_write_tuple (filename, decoder)); + } + } + + if (error) + aud_ui_show_error (str_printf (_("Error opening %s:\n%s"), + (const char *) filename, (const char *) error)); +} + +EXPORT void audgui_infowin_show_current () +{ + int playlist = aud_playlist_get_playing (); + int position; + + if (playlist == -1) + playlist = aud_playlist_get_active (); + + position = aud_playlist_get_position (playlist); + + if (position == -1) + return; + + audgui_infowin_show (playlist, position); +} + +EXPORT void audgui_infowin_hide () +{ + audgui_hide_unique_window (AUDGUI_INFO_WINDOW); +} diff --git a/src/libaudgui/init.c b/src/libaudgui/init.cc index 3128a55..1d33745 100644 --- a/src/libaudgui/init.c +++ b/src/libaudgui/init.cc @@ -17,46 +17,47 @@ * the use of this software. */ -#include <stdio.h> +#include <assert.h> #include <stdlib.h> -#include <audacious/misc.h> -#include <audacious/playlist.h> #include <libaudcore/audstrings.h> #include <libaudcore/hook.h> +#include <libaudcore/playlist.h> +#include <libaudcore/plugins.h> +#include <libaudcore/runtime.h> -#include "init.h" +#include "internal.h" #include "libaudgui.h" #include "libaudgui-gtk.h" static const char * const audgui_defaults[] = { - "close_dialog_add", "FALSE", - "close_dialog_open", "TRUE", - "close_jtf_dialog", "TRUE", - "playlist_manager_close_on_activate", "FALSE", - "remember_jtf_entry", "TRUE", - NULL}; + "clear_song_fields", "TRUE", + "close_dialog_add", "FALSE", + "close_dialog_open", "TRUE", + "close_jtf_dialog", "TRUE", + "remember_jtf_entry", "TRUE", + nullptr +}; static const char * const window_names[AUDGUI_NUM_UNIQUE_WINDOWS] = { - "about_win", - "equalizer_win", - "filebrowser_win", - NULL, /* infopopup position is not saved */ - "info_win", - "jump_to_time_win", - "jump_to_track_win", - "playlist_export_win", - "playlist_import_win", - "playlist_manager_win", - "queue_manager_win", - "url_opener_win" + "about_win", + "equalizer_win", + "filebrowser_win", + nullptr, /* infopopup position is not saved */ + "info_win", + "jump_to_time_win", + "jump_to_track_win", + "playlist_export_win", + "playlist_import_win", + "queue_manager_win", + "url_opener_win" }; -AudAPITable * _aud_api_table = NULL; +static int init_count = 0; static GtkWidget * windows[AUDGUI_NUM_UNIQUE_WINDOWS]; -static bool_t configure_cb (GtkWidget * window, GdkEventConfigure * event, const char * name) +static gboolean configure_cb (GtkWidget * window, GdkEventConfigure * event, const char * name) { if (gtk_widget_get_visible (window)) { @@ -64,12 +65,10 @@ static bool_t configure_cb (GtkWidget * window, GdkEventConfigure * event, const gtk_window_get_position ((GtkWindow *) window, & pos[0], & pos[1]); gtk_window_get_size ((GtkWindow *) window, & pos[2], & pos[3]); - char * str = int_array_to_str (pos, 4); - aud_set_str ("audgui", name, str); - str_unref (str); + aud_set_str ("audgui", name, int_array_to_str (pos, 4)); } - return FALSE; + return false; } void audgui_show_unique_window (int id, GtkWidget * widget) @@ -84,7 +83,7 @@ void audgui_show_unique_window (int id, GtkWidget * widget) if (window_names[id]) { - char * str = aud_get_str ("audgui", window_names[id]); + String str = aud_get_str ("audgui", window_names[id]); int pos[4]; if (str_to_int_array (str, pos, 4)) @@ -95,22 +94,20 @@ void audgui_show_unique_window (int id, GtkWidget * widget) g_signal_connect (widget, "configure-event", (GCallback) configure_cb, (void *) window_names[id]); - - str_unref (str); } gtk_widget_show_all (widget); } -bool_t audgui_reshow_unique_window (int id) +bool audgui_reshow_unique_window (int id) { - g_return_val_if_fail (id >= 0 && id < AUDGUI_NUM_UNIQUE_WINDOWS, FALSE); + g_return_val_if_fail (id >= 0 && id < AUDGUI_NUM_UNIQUE_WINDOWS, false); if (! windows[id]) - return FALSE; + return false; gtk_window_present ((GtkWindow *) windows[id]); - return TRUE; + return true; } void audgui_hide_unique_window (int id) @@ -121,43 +118,55 @@ void audgui_hide_unique_window (int id) gtk_widget_destroy (windows[id]); } -static void playlist_set_playing_cb (void * unused, void * unused2) +static void playlist_set_playing_cb (void *, void *) { audgui_pixbuf_uncache (); } -static void playlist_position_cb (void * list, void * unused) +static void playlist_position_cb (void * list, void *) { - if (GPOINTER_TO_INT (list) == aud_playlist_get_playing ()) + if (aud::from_ptr<int> (list) == aud_playlist_get_playing ()) audgui_pixbuf_uncache (); } -EXPORT void audgui_init (AudAPITable * table, int version) +EXPORT void audgui_init () { - if (version != _AUD_PLUGIN_VERSION) - { - fprintf (stderr, "libaudgui version mismatch\n"); - abort (); - } + assert (aud_get_mainloop_type () == MainloopType::GLib); + + if (init_count ++) + return; + + gtk_init (nullptr, nullptr); - _aud_api_table = table; aud_config_set_defaults ("audgui", audgui_defaults); - hook_associate ("playlist set playing", playlist_set_playing_cb, NULL); - hook_associate ("playlist position", playlist_position_cb, NULL); + status_init (); + + hook_associate ("playlist set playing", playlist_set_playing_cb, nullptr); + hook_associate ("playlist position", playlist_position_cb, nullptr); #ifndef _WIN32 gtk_window_set_default_icon_name ("audacious"); #endif } -EXPORT void audgui_cleanup (void) +EXPORT void audgui_cleanup () { + if (-- init_count) + return; + hook_dissociate ("playlist set playing", playlist_set_playing_cb); hook_dissociate ("playlist position", playlist_position_cb); + status_cleanup (); + for (int id = 0; id < AUDGUI_NUM_UNIQUE_WINDOWS; id ++) audgui_hide_unique_window (id); - _aud_api_table = NULL; + audgui_hide_prefs_window (); + audgui_infopopup_hide (); + + plugin_menu_cleanup (); + plugin_prefs_cleanup (); + urilist_cleanup (); } diff --git a/src/libaudgui/init.h b/src/libaudgui/internal.h index 32632cf..da1a158 100644 --- a/src/libaudgui/init.h +++ b/src/libaudgui/internal.h @@ -1,5 +1,5 @@ /* - * init.h + * internal.h * Copyright 2010-2013 John Lindgren * * Redistribution and use in source and binary forms, with or without @@ -17,12 +17,12 @@ * the use of this software. */ -#ifndef AUDGUI_INIT_H -#define AUDGUI_INIT_H +#ifndef AUDGUI_INTERNAL_H +#define AUDGUI_INTERNAL_H #include <gtk/gtk.h> -#include <audacious/api.h> +enum class PluginType; enum { AUDGUI_ABOUT_WINDOW, @@ -34,20 +34,32 @@ enum { AUDGUI_JUMP_TO_TRACK_WINDOW, AUDGUI_PLAYLIST_EXPORT_WINDOW, AUDGUI_PLAYLIST_IMPORT_WINDOW, - AUDGUI_PLAYLIST_MANAGER_WINDOW, AUDGUI_QUEUE_MANAGER_WINDOW, AUDGUI_URL_OPENER_WINDOW, AUDGUI_NUM_UNIQUE_WINDOWS }; void audgui_show_unique_window (int id, GtkWidget * widget); -bool_t audgui_reshow_unique_window (int id); +bool audgui_reshow_unique_window (int id); void audgui_hide_unique_window (int id); -void audgui_init (AudAPITable * table, int version); -void audgui_cleanup (void); - /* pixbufs.c */ -void audgui_pixbuf_uncache (void); +void audgui_pixbuf_uncache (); + +/* plugin-menu.c */ +void plugin_menu_cleanup (); + +/* plugin-prefs.c */ +void plugin_prefs_cleanup (); + +/* plugin-view.c */ +GtkWidget * plugin_view_new (PluginType type); + +/* status.c */ +void status_init (); +void status_cleanup (); + +/* urilist.c */ +void urilist_cleanup (); -#endif /* AUDGUI_INIT_H */ +#endif /* AUDGUI_INTERNAL_H */ diff --git a/src/libaudgui/jump-to-time.c b/src/libaudgui/jump-to-time.cc index 357b422..bd4925a 100644 --- a/src/libaudgui/jump-to-time.c +++ b/src/libaudgui/jump-to-time.cc @@ -17,14 +17,13 @@ * the use of this software. */ -#include <stdio.h> #include <gtk/gtk.h> -#include <audacious/drct.h> -#include <audacious/i18n.h> #include <libaudcore/audstrings.h> +#include <libaudcore/drct.h> +#include <libaudcore/i18n.h> -#include "init.h" +#include "internal.h" #include "libaudgui.h" #include "libaudgui-gtk.h" @@ -37,16 +36,16 @@ static void jump_cb (void * entry) aud_drct_seek ((minutes * 60 + seconds) * 1000); } -EXPORT void audgui_jump_to_time (void) +EXPORT void audgui_jump_to_time () { if (audgui_reshow_unique_window (AUDGUI_JUMP_TO_TIME_WINDOW)) return; GtkWidget * entry = gtk_entry_new (); - gtk_entry_set_activates_default ((GtkEntry *) entry, TRUE); + gtk_entry_set_activates_default ((GtkEntry *) entry, true); GtkWidget * button1 = audgui_button_new (_("_Jump"), "go-jump", jump_cb, entry); - GtkWidget * button2 = audgui_button_new (_("_Cancel"), "process-stop", NULL, NULL); + GtkWidget * button2 = audgui_button_new (_("_Cancel"), "process-stop", nullptr, nullptr); GtkWidget * dialog = audgui_dialog_new (GTK_MESSAGE_OTHER, _("Jump to Time"), _("Enter time (minutes:seconds):"), button1, button2); @@ -56,8 +55,7 @@ EXPORT void audgui_jump_to_time (void) if (aud_drct_get_playing ()) { int time = aud_drct_get_time () / 1000; - SPRINTF (buf, "%u:%02u", time / 60, time % 60); - gtk_entry_set_text ((GtkEntry *) entry, buf); + gtk_entry_set_text ((GtkEntry *) entry, str_printf ("%u:%02u", time / 60, time % 60)); } audgui_show_unique_window (AUDGUI_JUMP_TO_TIME_WINDOW, dialog); diff --git a/src/libaudgui/jump-to-track-cache.cc b/src/libaudgui/jump-to-track-cache.cc new file mode 100644 index 0000000..db07995 --- /dev/null +++ b/src/libaudgui/jump-to-track-cache.cc @@ -0,0 +1,214 @@ +/* + * jump-to-track-cache.c + * Copyright 2008-2014 Jussi Judin and John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "jump-to-track-cache.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include <glib.h> /* for GRegex */ + +#include <libaudcore/audstrings.h> +#include <libaudcore/playlist.h> +#include <libaudcore/runtime.h> + +/** + * Creates an regular expression list usable in searches from search keyword. + * + * In searches, every regular expression on this list is matched against + * the search title and if they all match, the title is declared as + * matching one. + * + * Regular expressions in list are formed by splitting the 'keyword' to words + * by splitting the keyword string with space character. + */ +Index<GRegex *> jump_to_track_cache_regex_list_create (const char * keyword) +{ + Index<GRegex *> regex_list; + + /* Chop the key string into ' '-separated key regex-pattern strings */ + Index<String> words = str_list_to_index (keyword, " "); + + /* create a list of regex using the regex-pattern strings */ + for (const char * word : words) + { + // Ignore empty words. + if (! word[0]) + continue; + + GRegex * regex = g_regex_new (word, G_REGEX_CASELESS, (GRegexMatchFlags) 0, nullptr); + if (regex) + regex_list.append (regex); + } + + return regex_list; +} + +/** + * Checks if 'song' matches all regular expressions in 'regex_list'. + */ +static bool jump_to_track_match (const char * name, Index<GRegex *> & regex_list) +{ + if (! name) + return false; + + for (GRegex * regex : regex_list) + { + if (! g_regex_match (regex, name, (GRegexMatchFlags) 0, nullptr)) + return false; + } + + return true; +} + +/** + * Returns all songs that match 'keyword'. + * + * Searches are conducted against entries in 'search_space' variable + * and after the search, search result is added to 'cache'. + * + * @param cache The result of this search is added to cache. + * @param search_space Entries inside which the search is conducted. + * @param keyword Normalized string for searches. + */ +const KeywordMatches * JumpToTrackCache::search_within + (const KeywordMatches * subset, const char * keyword) +{ + Index<GRegex *> regex_list = jump_to_track_cache_regex_list_create (keyword); + + KeywordMatches * k = add (String (keyword), KeywordMatches ()); + + for (const KeywordMatch & item : * subset) + { + if (! regex_list.len () || + jump_to_track_match (item.title, regex_list) || + jump_to_track_match (item.artist, regex_list) || + jump_to_track_match (item.album, regex_list) || + jump_to_track_match (item.path, regex_list)) + k->append (item); + } + + for (GRegex * regex : regex_list) + g_regex_unref (regex); + + return k; +} + +/** + * Creates a new song search cache. + * + * Returned value should be freed with ui_jump_to_track_cache_free() function. + */ +void JumpToTrackCache::init () +{ + int playlist = aud_playlist_get_active (); + int entries = aud_playlist_entry_count (playlist); + + // the empty string will match all playlist entries + KeywordMatches & k = * add (String (""), KeywordMatches ()); + + k.insert (0, entries); + + for (int entry = 0; entry < entries; entry ++) + { + KeywordMatch & item = k[entry]; + item.entry = entry; + item.path = String (uri_to_display (aud_playlist_entry_get_filename (playlist, entry))); + + Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry, Playlist::Guess); + item.title = tuple.get_str (Tuple::Title); + item.artist = tuple.get_str (Tuple::Artist); + item.album = tuple.get_str (Tuple::Album); + } +} + +/** + * Searches 'keyword' inside 'playlist' by using 'cache' to speed up searching. + * + * Searches are basically conducted as follows: + * + * Cache is checked if it has the information about right playlist and + * initialized with playlist data if needed. + * + * Keyword is normalized for searching (Unicode NFKD, case folding) + * + * Cache is checked if it has keyword and if it has, we can immediately get + * the search results and return. If not, searching goes as follows: + * + * Search for the longest word that is in cache that matches the beginning + * of keyword and use the cached matches as base for the current search. + * The shortest word that can be matched against is the empty string "", so + * there should always be matches in cache. + * + * After that conduct the search by splitting keyword into words separated + * by space and using regular expressions. + * + * When the keyword is searched, search result is added to cache to + * corresponding keyword that can be used as base for new searches. + * + * The motivation for caching is that to search word 'some cool song' one + * has to type following strings that are all searched individually: + * + * s + * so + * som + * some + * some + * some c + * some co + * some coo + * some cool + * some cool + * some cool s + * some cool so + * some cool son + * some cool song + * + * If the search results are cached in every phase and the result of + * the maximum length matching string is used as base for concurrent + * searches, we can probably get the matches reduced to some hundreds + * after a few letters typed on playlists with thousands of songs and + * reduce useless iteration quite a lot. + * + * Return: GArray of int + */ +const KeywordMatches * JumpToTrackCache::search (const char * keyword) +{ + if (! n_items ()) + init (); + + StringBuf match_string = str_copy (keyword); + const KeywordMatches * matches; + + while (! (matches = lookup (String (match_string)))) + { + // try to reuse the result of a previous search + // (the empty string is always present as a fallback) + assert (match_string[0]); + match_string[strlen (match_string) - 1] = 0; + } + + // exact match? + if (! strcmp (match_string, keyword)) + return matches; + + // search within the previous result + return search_within (matches, keyword); +} diff --git a/src/audacious/api-declare-end.h b/src/libaudgui/jump-to-track-cache.h index fa73092..d265eb1 100644 --- a/src/audacious/api-declare-end.h +++ b/src/libaudgui/jump-to-track-cache.h @@ -1,6 +1,6 @@ /* - * api-declare-end.h - * Copyright 2010-2011 John Lindgren + * jump-to-track-cache.h + * Copyright 2008 Jussi Judin * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -17,30 +17,30 @@ * the use of this software. */ -#if ! defined AUD_API_NAME || ! defined AUD_API_SYMBOL || ! defined AUD_API_DECLARE_H -#error Bad usage of api-declare-end.h -#endif +#ifndef LIBAUDGUI_JUMPTOTRACK_CACHE_H +#define LIBAUDGUI_JUMPTOTRACK_CACHE_H + +#include <libaudcore/index.h> +#include <libaudcore/multihash.h> +#include <libaudcore/objects.h> +// Struct to keep information about matches from searches. +struct KeywordMatch { + int entry; + String title, artist, album, path; }; -#undef AUD_API_DECLARE_H +typedef Index<KeywordMatch> KeywordMatches; -#undef AUD_FUNC0 -#undef AUD_FUNC1 -#undef AUD_FUNC2 -#undef AUD_FUNC3 -#undef AUD_FUNC4 -#undef AUD_FUNC5 -#undef AUD_FUNC6 -#undef AUD_FUNC7 -#undef AUD_FUNC8 +class JumpToTrackCache : private SimpleHash<String, KeywordMatches> +{ +public: + const KeywordMatches * search (const char * keyword); + using SimpleHash::clear; -#undef AUD_VFUNC0 -#undef AUD_VFUNC1 -#undef AUD_VFUNC2 -#undef AUD_VFUNC3 -#undef AUD_VFUNC4 -#undef AUD_VFUNC5 -#undef AUD_VFUNC6 -#undef AUD_VFUNC7 -#undef AUD_VFUNC8 +private: + void init (); + const KeywordMatches * search_within (const KeywordMatches * subset, const char * keyword); +}; + +#endif diff --git a/src/libaudgui/jump-to-track.cc b/src/libaudgui/jump-to-track.cc new file mode 100644 index 0000000..5d16e26 --- /dev/null +++ b/src/libaudgui/jump-to-track.cc @@ -0,0 +1,343 @@ +/* + * jump-to-track.c + * Copyright 2007-2014 Yoshiki Yazawa, John Lindgren, and Thomas Lange + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> + +#include <libaudcore/hook.h> +#include <libaudcore/i18n.h> +#include <libaudcore/playlist.h> +#include <libaudcore/runtime.h> + +#include "internal.h" +#include "libaudgui.h" +#include "libaudgui-gtk.h" +#include "list.h" +#include "jump-to-track-cache.h" + +static void update_cb (void * data, void *); +static void activate_cb (void * data, void *); + +static JumpToTrackCache cache; +static const KeywordMatches * search_matches; +static GtkWidget * treeview, * filter_entry, * queue_button, * jump_button; +static bool watching = false; + +static void destroy_cb () +{ + if (watching) + { + hook_dissociate ("playlist update", update_cb); + hook_dissociate ("playlist activate", activate_cb); + watching = false; + } + + cache.clear (); + + search_matches = nullptr; +} + +static int get_selected_entry () +{ + g_return_val_if_fail (treeview && search_matches, -1); + + GtkTreeModel * model = gtk_tree_view_get_model ((GtkTreeView *) treeview); + GtkTreeSelection * selection = gtk_tree_view_get_selection ((GtkTreeView *) treeview); + GtkTreeIter iter; + + if (! gtk_tree_selection_get_selected (selection, nullptr, & iter)) + return -1; + + GtkTreePath * path = gtk_tree_model_get_path (model, & iter); + int row = gtk_tree_path_get_indices (path)[0]; + gtk_tree_path_free (path); + + g_return_val_if_fail (row >= 0 && row < search_matches->len (), -1); + return (* search_matches)[row].entry; +} + +static void do_jump (void *) +{ + int entry = get_selected_entry (); + if (entry < 0) + return; + + int playlist = aud_playlist_get_active (); + aud_playlist_set_position (playlist, entry); + aud_playlist_play (playlist); + + if (aud_get_bool ("audgui", "close_jtf_dialog")) + audgui_jump_to_track_hide (); +} + +static void update_queue_button (int entry) +{ + g_return_if_fail (queue_button); + + if (entry < 0) + { + gtk_button_set_label ((GtkButton *) queue_button, _("_Queue")); + gtk_widget_set_sensitive (queue_button, false); + } + else + { + if (aud_playlist_queue_find_entry (aud_playlist_get_active (), entry) != -1) + gtk_button_set_label ((GtkButton *) queue_button, _("Un_queue")); + else + gtk_button_set_label ((GtkButton *) queue_button, _("_Queue")); + + gtk_widget_set_sensitive (queue_button, true); + } +} + +static void do_queue (void *) +{ + int playlist = aud_playlist_get_active (); + int entry = get_selected_entry (); + if (entry < 0) + return; + + int queued = aud_playlist_queue_find_entry (playlist, entry); + if (queued >= 0) + aud_playlist_queue_delete (playlist, queued, 1); + else + aud_playlist_queue_insert (playlist, -1, entry); + + update_queue_button (entry); +} + +static void selection_changed () +{ + int entry = get_selected_entry (); + gtk_widget_set_sensitive (jump_button, entry >= 0); + + update_queue_button (entry); +} + +static gboolean keypress_cb (GtkWidget * widget, GdkEventKey * event) +{ + if (event->keyval == GDK_KEY_Escape) + { + audgui_jump_to_track_hide (); + return true; + } + + return false; +} + +static void fill_list () +{ + g_return_if_fail (treeview && filter_entry); + + search_matches = cache.search (gtk_entry_get_text ((GtkEntry *) filter_entry)); + + audgui_list_delete_rows (treeview, 0, audgui_list_row_count (treeview)); + audgui_list_insert_rows (treeview, 0, search_matches->len ()); + + if (search_matches->len () >= 1) + { + GtkTreeSelection * sel = gtk_tree_view_get_selection ((GtkTreeView *) treeview); + GtkTreePath * path = gtk_tree_path_new_from_indices (0, -1); + gtk_tree_selection_select_path (sel, path); + gtk_tree_path_free (path); + } +} + +static void update_cb (void * data, void *) +{ + g_return_if_fail (treeview); + + GtkTreeModel * model; + GtkTreeIter iter; + GtkTreePath * path = nullptr; + + auto level = aud::from_ptr<Playlist::UpdateLevel> (data); + if (level <= Playlist::Selection) + return; + + cache.clear (); + + /* If it's only a metadata update, save and restore the cursor position. */ + if (level <= Playlist::Metadata && + gtk_tree_selection_get_selected (gtk_tree_view_get_selection + ((GtkTreeView *) treeview), & model, & iter)) + path = gtk_tree_model_get_path (model, & iter); + + fill_list (); + + if (path != nullptr) + { + gtk_tree_selection_select_path (gtk_tree_view_get_selection + ((GtkTreeView *) treeview), path); + gtk_tree_view_scroll_to_cell ((GtkTreeView *) treeview, path, nullptr, true, 0.5, 0); + gtk_tree_path_free (path); + } +} + +static void activate_cb (void * data, void *) +{ + update_cb (aud::to_ptr (Playlist::Structure), nullptr); +} + +static void filter_icon_cb (GtkEntry * entry) +{ + gtk_entry_set_text (entry, ""); +} + +static void toggle_button_cb (GtkToggleButton * toggle) +{ + aud_set_bool ("audgui", "close_jtf_dialog", gtk_toggle_button_get_active (toggle)); +} + +static void list_get_value (void * user, int row, int column, GValue * value) +{ + g_return_if_fail (search_matches); + g_return_if_fail (column >= 0 && column < 2); + g_return_if_fail (row >= 0 && row < search_matches->len ()); + + int playlist = aud_playlist_get_active (); + int entry = (* search_matches)[row].entry; + + switch (column) + { + case 0: + g_value_set_int (value, 1 + entry); + break; + case 1: + Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry, Playlist::Guess); + g_value_set_string (value, tuple.get_str (Tuple::FormattedTitle)); + break; + } +} + +static const AudguiListCallbacks callbacks = { + list_get_value +}; + +static GtkWidget * create_window () +{ + GtkWidget * jump_to_track_win = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_type_hint ((GtkWindow *) jump_to_track_win, GDK_WINDOW_TYPE_HINT_DIALOG); + + gtk_window_set_title ((GtkWindow *) jump_to_track_win, _("Jump to Song")); + + g_signal_connect (jump_to_track_win, "key_press_event", (GCallback) keypress_cb, nullptr); + g_signal_connect (jump_to_track_win, "destroy", (GCallback) destroy_cb, nullptr); + + gtk_container_set_border_width ((GtkContainer *) jump_to_track_win, 10); + gtk_window_set_default_size ((GtkWindow *) jump_to_track_win, 600, 500); + + GtkWidget * vbox = gtk_vbox_new (false, 6); + gtk_container_add ((GtkContainer *) jump_to_track_win, vbox); + + treeview = audgui_list_new (& callbacks, nullptr, 0); + gtk_tree_view_set_headers_visible ((GtkTreeView *) treeview, false); + + audgui_list_add_column (treeview, nullptr, 0, G_TYPE_INT, 7); + audgui_list_add_column (treeview, nullptr, 1, G_TYPE_STRING, -1); + + g_signal_connect (gtk_tree_view_get_selection ((GtkTreeView *) treeview), + "changed", (GCallback) selection_changed, nullptr); + g_signal_connect (treeview, "row-activated", (GCallback) do_jump, nullptr); + + GtkWidget * hbox = gtk_hbox_new (false, 6); + gtk_box_pack_start ((GtkBox *) vbox, hbox, false, false, 3); + + /* filter box */ + GtkWidget * search_label = gtk_label_new (_("Filter: ")); + gtk_label_set_markup_with_mnemonic ((GtkLabel *) search_label, _("_Filter:")); + gtk_box_pack_start ((GtkBox *) hbox, search_label, false, false, 0); + + filter_entry = gtk_entry_new (); + gtk_entry_set_icon_from_icon_name ((GtkEntry *) filter_entry, + GTK_ENTRY_ICON_SECONDARY, "edit-clear"); + gtk_label_set_mnemonic_widget ((GtkLabel *) search_label, filter_entry); + g_signal_connect (filter_entry, "changed", (GCallback) fill_list, nullptr); + g_signal_connect (filter_entry, "icon-press", (GCallback) filter_icon_cb, nullptr); + gtk_entry_set_activates_default ((GtkEntry *) filter_entry, true); + gtk_box_pack_start ((GtkBox *) hbox, filter_entry, true, true, 0); + + GtkWidget * scrollwin = gtk_scrolled_window_new (nullptr, nullptr); + gtk_container_add ((GtkContainer *) scrollwin, treeview); + gtk_scrolled_window_set_policy ((GtkScrolledWindow *) scrollwin, + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type ((GtkScrolledWindow *) scrollwin, GTK_SHADOW_IN); + gtk_box_pack_start ((GtkBox *) vbox, scrollwin, true, true, 0); + + GtkWidget * hbox2 = gtk_hbox_new (false, 0); + gtk_box_pack_end ((GtkBox *) vbox, hbox2, false, false, 0); + + GtkWidget * bbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout ((GtkButtonBox *) bbox, GTK_BUTTONBOX_END); + gtk_box_set_spacing ((GtkBox *) bbox, 6); + + GtkWidget * alignment = gtk_alignment_new (0.5, 0.5, 1, 1); + gtk_alignment_set_padding ((GtkAlignment *) alignment, 0, 0, 6, 0); + gtk_container_add ((GtkContainer *) alignment, bbox); + gtk_box_pack_end ((GtkBox *) hbox2, alignment, true, true, 0); + + /* close dialog toggle */ + GtkWidget * toggle = gtk_check_button_new_with_mnemonic (_("C_lose on jump")); + gtk_toggle_button_set_active ((GtkToggleButton *) toggle, aud_get_bool + ("audgui", "close_jtf_dialog")); + gtk_container_add ((GtkContainer *) hbox2, toggle); + g_signal_connect (toggle, "clicked", (GCallback) toggle_button_cb, nullptr); + + /* queue button */ + queue_button = audgui_button_new (_("_Queue"), nullptr, do_queue, nullptr); + gtk_container_add ((GtkContainer *) bbox, queue_button); + + /* close button */ + GtkWidget * close = audgui_button_new (_("_Close"), "window-close", + (AudguiCallback) audgui_jump_to_track_hide, nullptr); + gtk_container_add ((GtkContainer *) bbox, close); + + /* jump button */ + jump_button = audgui_button_new (_("_Jump"), "go-jump", do_jump, nullptr); + gtk_container_add ((GtkContainer *) bbox, jump_button); + gtk_widget_set_can_default (jump_button, true); + gtk_widget_grab_default (jump_button); + + return jump_to_track_win; +} + +EXPORT void audgui_jump_to_track () +{ + if (audgui_reshow_unique_window (AUDGUI_JUMP_TO_TRACK_WINDOW)) + return; + + GtkWidget * jump_to_track_win = create_window (); + + if (! watching) + { + fill_list (); + hook_associate ("playlist update", update_cb, nullptr); + hook_associate ("playlist activate", activate_cb, nullptr); + watching = true; + } + + gtk_widget_grab_focus (filter_entry); + + audgui_show_unique_window (AUDGUI_JUMP_TO_TRACK_WINDOW, jump_to_track_win); +} + +EXPORT void audgui_jump_to_track_hide () +{ + audgui_hide_unique_window (AUDGUI_JUMP_TO_TRACK_WINDOW); +} diff --git a/src/libaudgui/libaudgui-gtk.h b/src/libaudgui/libaudgui-gtk.h index 7e65a03..4d12b7d 100644 --- a/src/libaudgui/libaudgui-gtk.h +++ b/src/libaudgui/libaudgui-gtk.h @@ -22,24 +22,39 @@ #include <stdint.h> #include <gtk/gtk.h> -#include <libaudcore/core.h> + +#include <libaudcore/objects.h> + +#define audgui_create_widgets(b, w) audgui_create_widgets_with_domain (b, w, PACKAGE) + +enum class AudMenuID; +struct PreferencesWidget; typedef void (* AudguiCallback) (void * data); /* pixbufs.c */ GdkPixbuf * audgui_pixbuf_from_data (const void * data, int64_t size); -GdkPixbuf * audgui_pixbuf_fallback (void); +GdkPixbuf * audgui_pixbuf_fallback (); void audgui_pixbuf_scale_within (GdkPixbuf * * pixbuf, int size); -GdkPixbuf * audgui_pixbuf_request (const char * filename); -GdkPixbuf * audgui_pixbuf_request_current (void); +GdkPixbuf * audgui_pixbuf_request (const char * filename, bool * queued = nullptr); +GdkPixbuf * audgui_pixbuf_request_current (bool * queued = nullptr); + +/* plugin-menu.c */ +GtkWidget * audgui_get_plugin_menu (AudMenuID id); + +/* prefs-widget.c */ +void audgui_create_widgets_with_domain (GtkWidget * box, + ArrayRef<PreferencesWidget> widgets, const char * domain); -/* scaled-image.c */ +/* scaled-image.c -- okay to use without audgui_init() */ GtkWidget * audgui_scaled_image_new (GdkPixbuf * pixbuf); void audgui_scaled_image_set (GtkWidget * widget, GdkPixbuf * pixbuf); -/* util.c */ +/* util.c -- okay to use without audgui_init() */ int audgui_get_digit_width (GtkWidget * widget); void audgui_get_mouse_coords (GtkWidget * widget, int * x, int * y); +void audgui_get_mouse_coords (GdkScreen * screen, int * x, int * y); +void audgui_get_monitor_geometry (GdkScreen * screen, int x, int y, GdkRectangle * geom); void audgui_destroy_on_escape (GtkWidget * widget); void audgui_simple_message (GtkWidget * * widget, GtkMessageType type, const char * title, const char * text); diff --git a/src/libaudgui/libaudgui.h b/src/libaudgui/libaudgui.h index db88ed1..de9719e 100644 --- a/src/libaudgui/libaudgui.h +++ b/src/libaudgui/libaudgui.h @@ -20,58 +20,76 @@ #ifndef LIBAUDGUI_H #define LIBAUDGUI_H -#include <stdint.h> -#include <libaudcore/core.h> +#include <libaudcore/index.h> +#include <libaudcore/objects.h> -void audgui_show_add_url_window(bool_t open); - -void audgui_jump_to_track(void); -void audgui_jump_to_track_hide(void); - -void audgui_run_filebrowser(bool_t open); -void audgui_hide_filebrowser(void); +enum class AudMenuID; +enum class PluginType; +class PluginHandle; /* about.c */ -void audgui_show_about_window (void); -void audgui_hide_about_window (void); +void audgui_show_about_window (); +void audgui_hide_about_window (); /* confirm.c */ void audgui_confirm_playlist_delete (int playlist); void audgui_show_playlist_rename (int playlist); /* equalizer.c */ -void audgui_show_equalizer_window (void); -void audgui_hide_equalizer_window (void); +void audgui_show_equalizer_window (); +void audgui_hide_equalizer_window (); /* infopopup.c */ void audgui_infopopup_show (int playlist, int entry); -void audgui_infopopup_show_current (void); -void audgui_infopopup_hide (void); +void audgui_infopopup_show_current (); +void audgui_infopopup_hide (); + +/* file-opener.c */ +void audgui_run_filebrowser (bool open); +void audgui_hide_filebrowser (); /* infowin.c */ void audgui_infowin_show (int playlist, int entry); -void audgui_infowin_show_current (void); -void audgui_infowin_hide (void); +void audgui_infowin_show_current (); +void audgui_infowin_hide (); + +/* init.c */ +void audgui_init (); +void audgui_cleanup (); /* jump-to-time.c */ -void audgui_jump_to_time (void); +void audgui_jump_to_time (); + +/* jump-to-track.c */ +void audgui_jump_to_track (); +void audgui_jump_to_track_hide (); /* playlists.c */ -void audgui_import_playlist (void); -void audgui_export_playlist (void); +void audgui_import_playlist (); +void audgui_export_playlist (); -/* queue-manager.c */ -void audgui_queue_manager_show (void); +/* plugin-menu.c */ +void audgui_plugin_menu_add (AudMenuID id, void (* func) (void), const char * name, const char * icon); +void audgui_plugin_menu_remove (AudMenuID id, void (* func) (void)); + +/* plugin-prefs.c */ +void audgui_show_plugin_about (PluginHandle * plugin); +void audgui_show_plugin_prefs (PluginHandle * plugin); -/* ui_playlist_manager.c */ -void audgui_playlist_manager (void); +/* prefs-window.c */ +void audgui_show_prefs_window (); +void audgui_show_prefs_for_plugin_type (PluginType type); +void audgui_hide_prefs_window (); + +/* queue-manager.c */ +void audgui_queue_manager_show (); /* urilist.c */ void audgui_urilist_open (const char * list); void audgui_urilist_insert (int playlist, int position, const char * list); -char * audgui_urilist_create_from_selected (int playlist); +Index<char> audgui_urilist_create_from_selected (int playlist); -/* util.c */ -void audgui_format_time (char * buf, int bufsize, int64_t milliseconds); +/* url-opener.c */ +void audgui_show_add_url_window (bool open); #endif /* LIBAUDGUI_H */ diff --git a/src/libaudgui/list.c b/src/libaudgui/list.cc index aa3cb7d..a7b0022 100644 --- a/src/libaudgui/list.c +++ b/src/libaudgui/list.cc @@ -20,17 +20,22 @@ #include <stddef.h> #include <gtk/gtk.h> +#include <libaudcore/objects.h> + #include "libaudgui-gtk.h" #include "list.h" -enum {HIGHLIGHT_COLUMN, RESERVED_COLUMNS}; +enum { + HIGHLIGHT_COLUMN, + RESERVED_COLUMNS +}; #define MODEL_HAS_CB(m, cb) \ - ((m)->cbs_size > offsetof (AudguiListCallbacks, cb) && (m)->cbs->cb) + ((m)->cbs_size > (int) offsetof (AudguiListCallbacks, cb) && (m)->cbs->cb) #define PATH_IS_SELECTED(w, p) (gtk_tree_selection_path_is_selected \ (gtk_tree_view_get_selection ((GtkTreeView *) (w)), (p))) -typedef struct { +struct ListModel { GObject parent; const AudguiListCallbacks * cbs; int cbs_size; @@ -39,11 +44,12 @@ typedef struct { int rows, highlight; int columns; GList * column_types; - bool_t frozen, blocked; - bool_t dragging; - bool_t clicked_row, receive_row; + bool resizable; + bool frozen, blocked; + bool dragging; + int clicked_row, receive_row; int scroll_source, scroll_speed; -} ListModel; +}; /* ==== MODEL ==== */ @@ -69,21 +75,21 @@ static GType list_model_get_column_type (GtkTreeModel * _model, int column) RESERVED_COLUMNS)); } -static bool_t list_model_get_iter (GtkTreeModel * model, GtkTreeIter * iter, +static gboolean list_model_get_iter (GtkTreeModel * model, GtkTreeIter * iter, GtkTreePath * path) { int row = gtk_tree_path_get_indices (path)[0]; if (row < 0 || row >= ((ListModel *) model)->rows) - return FALSE; + return false; iter->user_data = GINT_TO_POINTER (row); - return TRUE; + return true; } static GtkTreePath * list_model_get_path (GtkTreeModel * model, GtkTreeIter * iter) { int row = GPOINTER_TO_INT (iter->user_data); - g_return_val_if_fail (row >= 0 && row < ((ListModel *) model)->rows, NULL); + g_return_val_if_fail (row >= 0 && row < ((ListModel *) model)->rows, nullptr); return gtk_tree_path_new_from_indices (row, -1); } @@ -108,30 +114,30 @@ static void list_model_get_value (GtkTreeModel * _model, GtkTreeIter * iter, model->cbs->get_value (model->user, row, column - RESERVED_COLUMNS, value); } -static bool_t list_model_iter_next (GtkTreeModel * _model, GtkTreeIter * iter) +static gboolean list_model_iter_next (GtkTreeModel * _model, GtkTreeIter * iter) { ListModel * model = (ListModel *) _model; int row = GPOINTER_TO_INT (iter->user_data); - g_return_val_if_fail (row >= 0 && row < model->rows, FALSE); + g_return_val_if_fail (row >= 0 && row < model->rows, false); if (row + 1 >= model->rows) - return FALSE; + return false; iter->user_data = GINT_TO_POINTER (row + 1); - return TRUE; + return true; } -static bool_t list_model_iter_children (GtkTreeModel * model, +static gboolean list_model_iter_children (GtkTreeModel * model, GtkTreeIter * iter, GtkTreeIter * parent) { if (parent || ((ListModel *) model)->rows < 1) - return FALSE; + return false; iter->user_data = GINT_TO_POINTER (0); - return TRUE; + return true; } -static bool_t list_model_iter_has_child (GtkTreeModel * model, +static gboolean list_model_iter_has_child (GtkTreeModel * model, GtkTreeIter * iter) { - return FALSE; + return false; } static int list_model_iter_n_children (GtkTreeModel * model, GtkTreeIter * iter) @@ -139,19 +145,19 @@ static int list_model_iter_n_children (GtkTreeModel * model, GtkTreeIter * iter) return iter ? 0 : ((ListModel *) model)->rows; } -static bool_t list_model_iter_nth_child (GtkTreeModel * model, +static gboolean list_model_iter_nth_child (GtkTreeModel * model, GtkTreeIter * iter, GtkTreeIter * parent, int n) { if (parent || n < 0 || n >= ((ListModel *) model)->rows) - return FALSE; + return false; iter->user_data = GINT_TO_POINTER (n); - return TRUE; + return true; } -static bool_t list_model_iter_parent (GtkTreeModel * model, +static gboolean list_model_iter_parent (GtkTreeModel * model, GtkTreeIter * iter, GtkTreeIter * child) { - return FALSE; + return false; } static void iface_init (GtkTreeModelIface * iface) @@ -170,20 +176,17 @@ static void iface_init (GtkTreeModelIface * iface) iface->iter_parent = list_model_iter_parent; } -static const GInterfaceInfo iface_info = -{ - .interface_init = (GInterfaceInitFunc) iface_init, - .interface_finalize = NULL, - .interface_data = NULL, +static const GInterfaceInfo iface_info = { + (GInterfaceInitFunc) iface_init }; -static GType list_model_get_type (void) +static GType list_model_get_type () { static GType type = G_TYPE_INVALID; if (type == G_TYPE_INVALID) { type = g_type_register_static_simple (G_TYPE_OBJECT, "AudguiListModel", - sizeof (GObjectClass), NULL, sizeof (ListModel), NULL, 0); + sizeof (GObjectClass), nullptr, sizeof (ListModel), nullptr, (GTypeFlags) 0); g_type_add_interface_static (type, GTK_TYPE_TREE_MODEL, & iface_info); } return type; @@ -191,8 +194,8 @@ static GType list_model_get_type (void) /* ==== CALLBACKS ==== */ -static bool_t select_allow_cb (GtkTreeSelection * sel, GtkTreeModel * model, - GtkTreePath * path, bool_t was, void * user) +static gboolean select_allow_cb (GtkTreeSelection * sel, GtkTreeModel * model, + GtkTreePath * path, gboolean was, void * user) { return ! ((ListModel *) model)->frozen; } @@ -203,15 +206,15 @@ static void select_row_cb (GtkTreeModel * _model, GtkTreePath * path, ListModel * model = (ListModel *) _model; int row = gtk_tree_path_get_indices (path)[0]; g_return_if_fail (row >= 0 && row < model->rows); - model->cbs->set_selected (model->user, row, TRUE); + model->cbs->set_selected (model->user, row, true); } static void select_cb (GtkTreeSelection * sel, ListModel * model) { if (model->blocked) return; - model->cbs->select_all (model->user, FALSE); - gtk_tree_selection_selected_foreach (sel, select_row_cb, NULL); + model->cbs->select_all (model->user, false); + gtk_tree_selection_selected_foreach (sel, select_row_cb, nullptr); } static void focus_cb (GtkTreeView * tree, ListModel * model) @@ -229,12 +232,12 @@ static void activate_cb (GtkTreeView * tree, GtkTreePath * path, model->cbs->activate_row (model->user, row); } -static bool_t button_press_cb (GtkWidget * widget, GdkEventButton * event, +static gboolean button_press_cb (GtkWidget * widget, GdkEventButton * event, ListModel * model) { - GtkTreePath * path = NULL; + GtkTreePath * path = nullptr; gtk_tree_view_get_path_at_pos ((GtkTreeView *) widget, event->x, event->y, - & path, NULL, NULL, NULL); + & path, nullptr, nullptr, nullptr); if (event->type == GDK_BUTTON_PRESS && event->button == 3 && MODEL_HAS_CB (model, right_click)) @@ -244,16 +247,16 @@ static bool_t button_press_cb (GtkWidget * widget, GdkEventButton * event, if (path) { if (PATH_IS_SELECTED (widget, path)) - model->frozen = TRUE; - gtk_tree_view_set_cursor ((GtkTreeView *) widget, path, NULL, FALSE); - model->frozen = FALSE; + model->frozen = true; + gtk_tree_view_set_cursor ((GtkTreeView *) widget, path, nullptr, false); + model->frozen = false; } model->cbs->right_click (model->user, event); if (path) gtk_tree_path_free (path); - return TRUE; + return true; } /* Only allow GTK to select this row if it is not already selected. If we @@ -263,7 +266,7 @@ static bool_t button_press_cb (GtkWidget * widget, GdkEventButton * event, if (event->type == GDK_BUTTON_PRESS && event->button == 1 && ! (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) && path && PATH_IS_SELECTED (widget, path)) - model->frozen = TRUE; + model->frozen = true; if (path) model->clicked_row = gtk_tree_path_get_indices (path)[0]; @@ -272,10 +275,10 @@ static bool_t button_press_cb (GtkWidget * widget, GdkEventButton * event, if (path) gtk_tree_path_free (path); - return FALSE; + return false; } -static bool_t button_release_cb (GtkWidget * widget, GdkEventButton * event, +static gboolean button_release_cb (GtkWidget * widget, GdkEventButton * event, ListModel * model) { /* If button_press_cb set "frozen", and we were not dragging, we need to @@ -283,26 +286,25 @@ static bool_t button_release_cb (GtkWidget * widget, GdkEventButton * event, if (model->frozen && model->clicked_row >= 0 && model->clicked_row < model->rows) { - model->frozen = FALSE; - GtkTreePath * path = gtk_tree_path_new_from_indices (model->clicked_row, - -1); - gtk_tree_view_set_cursor ((GtkTreeView *) widget, path, NULL, FALSE); + model->frozen = false; + GtkTreePath * path = gtk_tree_path_new_from_indices (model->clicked_row, -1); + gtk_tree_view_set_cursor ((GtkTreeView *) widget, path, nullptr, false); gtk_tree_path_free (path); } - return FALSE; + return false; } -static bool_t key_press_cb (GtkWidget * widget, GdkEventKey * event, ListModel * model) +static gboolean key_press_cb (GtkWidget * widget, GdkEventKey * event, ListModel * model) { /* GTK thinks the spacebar should activate a row; I (jlindgren) disagree */ if (event->keyval == ' ' && ! (event->state & GDK_CONTROL_MASK)) - return TRUE; + return true; - return FALSE; + return false; } -static bool_t motion_notify_cb (GtkWidget * widget, GdkEventMotion * event, ListModel * model) +static gboolean motion_notify_cb (GtkWidget * widget, GdkEventMotion * event, ListModel * model) { if (MODEL_HAS_CB (model, mouse_motion)) { @@ -314,10 +316,10 @@ static bool_t motion_notify_cb (GtkWidget * widget, GdkEventMotion * event, List model->cbs->mouse_motion (model->user, event, row); } - return FALSE; + return false; } -static bool_t leave_notify_cb (GtkWidget * widget, GdkEventMotion * event, ListModel * model) +static gboolean leave_notify_cb (GtkWidget * widget, GdkEventMotion * event, ListModel * model) { if (MODEL_HAS_CB (model, mouse_leave)) { @@ -329,7 +331,7 @@ static bool_t leave_notify_cb (GtkWidget * widget, GdkEventMotion * event, ListM model->cbs->mouse_leave (model->user, event, row); } - return FALSE; + return false; } /* ==== DRAG AND DROP ==== */ @@ -339,7 +341,7 @@ static void drag_begin (GtkWidget * widget, GdkDragContext * context, { g_signal_stop_emission_by_name (widget, "drag-begin"); - model->dragging = TRUE; + model->dragging = true; } static void drag_end (GtkWidget * widget, GdkDragContext * context, @@ -347,21 +349,18 @@ static void drag_end (GtkWidget * widget, GdkDragContext * context, { g_signal_stop_emission_by_name (widget, "drag-end"); - model->dragging = FALSE; + model->dragging = false; model->clicked_row = -1; } static void drag_data_get (GtkWidget * widget, GdkDragContext * context, - GtkSelectionData * sel, unsigned int info, unsigned int time, ListModel * model) + GtkSelectionData * sel, unsigned info, unsigned time, ListModel * model) { g_signal_stop_emission_by_name (widget, "drag-data-get"); - void * data = NULL; - int length = 0; - model->cbs->get_data (model->user, & data, & length); - gtk_selection_data_set (sel, gdk_atom_intern (model->cbs->data_type, FALSE), - 8, data, length); - g_free (data); + Index<char> data = model->cbs->get_data (model->user); + gtk_selection_data_set (sel, gdk_atom_intern (model->cbs->data_type, false), + 8, (const unsigned char *) data.begin (), data.len ()); } static int calc_drop_row (ListModel * model, GtkWidget * widget, int x, int y) @@ -382,29 +381,35 @@ static void stop_autoscroll (ListModel * model) model->scroll_speed = 0; } -static bool_t autoscroll (GtkWidget * widget) +static gboolean autoscroll (GtkWidget * widget) { ListModel * model = (ListModel *) gtk_tree_view_get_model ((GtkTreeView *) widget); - GtkAdjustment * adj = gtk_scrollable_get_vadjustment ((GtkScrollable *) widget); + GtkAdjustment * adj = gtk_tree_view_get_vadjustment ((GtkTreeView *) widget); if (! adj) - return FALSE; + { + stop_autoscroll (model); + return false; + } - int new = gtk_adjustment_get_value (adj) + model->scroll_speed; - int clamped = CLAMP (new, 0, gtk_adjustment_get_upper (adj) - + int pos = gtk_adjustment_get_value (adj) + model->scroll_speed; + int clamped = aud::clamp<int> (pos, 0, gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj)); gtk_adjustment_set_value (adj, clamped); - if (clamped != new) /* reached top or bottom? */ - return FALSE; + if (clamped != pos) /* reached top or bottom? */ + { + stop_autoscroll (model); + return false; + } if (model->scroll_speed > 0) - model->scroll_speed = MIN (model->scroll_speed + 2, 100); + model->scroll_speed = aud::min (model->scroll_speed + 2, 100); else - model->scroll_speed = MAX (model->scroll_speed - 2, -100); + model->scroll_speed = aud::max (model->scroll_speed - 2, -100); - return TRUE; + return true; } static void start_autoscroll (ListModel * model, GtkWidget * widget, int speed) @@ -416,21 +421,21 @@ static void start_autoscroll (ListModel * model, GtkWidget * widget, int speed) model->scroll_speed = speed; } -static bool_t drag_motion (GtkWidget * widget, GdkDragContext * context, - int x, int y, unsigned int time, ListModel * model) +static gboolean drag_motion (GtkWidget * widget, GdkDragContext * context, + int x, int y, unsigned time, ListModel * model) { g_signal_stop_emission_by_name (widget, "drag-motion"); /* If button_press_cb preserved a multiple selection, tell button_release_cb * not to clear it. */ - model->frozen = FALSE; + model->frozen = false; if (model->dragging && MODEL_HAS_CB (model, shift_rows)) /* dragging within same list */ gdk_drag_status (context, GDK_ACTION_MOVE, time); else if (MODEL_HAS_CB (model, data_type)) /* cross-widget dragging */ gdk_drag_status (context, GDK_ACTION_COPY, time); else - return FALSE; + return false; if (model->rows > 0) { @@ -453,11 +458,11 @@ static bool_t drag_motion (GtkWidget * widget, GdkDragContext * context, int height; gdk_window_get_geometry (gtk_tree_view_get_bin_window ((GtkTreeView *) - widget), NULL, NULL, NULL, & height); + widget), nullptr, nullptr, nullptr, & height, nullptr); gtk_tree_view_convert_widget_to_bin_window_coords ((GtkTreeView *) widget, x, y, & x, & y); - int hotspot = MIN (height / 4, 24); + int hotspot = aud::min (height / 4, 24); if (y >= 0 && y < hotspot) start_autoscroll (model, widget, -2); @@ -466,24 +471,24 @@ static bool_t drag_motion (GtkWidget * widget, GdkDragContext * context, else stop_autoscroll (model); - return TRUE; + return true; } static void drag_leave (GtkWidget * widget, GdkDragContext * context, - unsigned int time, ListModel * model) + unsigned time, ListModel * model) { g_signal_stop_emission_by_name (widget, "drag-leave"); - gtk_tree_view_set_drag_dest_row ((GtkTreeView *) widget, NULL, 0); + gtk_tree_view_set_drag_dest_row ((GtkTreeView *) widget, nullptr, (GtkTreeViewDropPosition) 0); stop_autoscroll (model); } -static bool_t drag_drop (GtkWidget * widget, GdkDragContext * context, int x, - int y, unsigned int time, ListModel * model) +static gboolean drag_drop (GtkWidget * widget, GdkDragContext * context, int x, + int y, unsigned time, ListModel * model) { g_signal_stop_emission_by_name (widget, "drag-drop"); - bool_t success = TRUE; + gboolean success = true; int row = calc_drop_row (model, widget, x, y); if (model->dragging && MODEL_HAS_CB (model, shift_rows)) /* dragging within same list */ @@ -491,32 +496,32 @@ static bool_t drag_drop (GtkWidget * widget, GdkDragContext * context, int x, if (model->clicked_row >= 0 && model->clicked_row < model->rows) model->cbs->shift_rows (model->user, model->clicked_row, row); else - success = FALSE; + success = false; } else if (MODEL_HAS_CB (model, data_type)) /* cross-widget dragging */ { model->receive_row = row; gtk_drag_get_data (widget, context, gdk_atom_intern - (model->cbs->data_type, FALSE), time); + (model->cbs->data_type, false), time); } else - success = FALSE; + success = false; - gtk_drag_finish (context, success, FALSE, time); - gtk_tree_view_set_drag_dest_row ((GtkTreeView *) widget, NULL, 0); + gtk_drag_finish (context, success, false, time); + gtk_tree_view_set_drag_dest_row ((GtkTreeView *) widget, nullptr, (GtkTreeViewDropPosition) 0); stop_autoscroll (model); - return TRUE; + return true; } static void drag_data_received (GtkWidget * widget, GdkDragContext * context, int x, - int y, GtkSelectionData * sel, unsigned int info, unsigned int time, ListModel * model) + int y, GtkSelectionData * sel, unsigned info, unsigned time, ListModel * model) { g_signal_stop_emission_by_name (widget, "drag-data-received"); g_return_if_fail (model->receive_row >= 0 && model->receive_row <= model->rows); - const unsigned char * data = gtk_selection_data_get_data (sel); + auto data = (const char *) gtk_selection_data_get_data (sel); int length = gtk_selection_data_get_length (sel); if (data && length) @@ -529,10 +534,6 @@ static void drag_data_received (GtkWidget * widget, GdkDragContext * context, in static void destroy_cb (GtkWidget * list, ListModel * model) { - /* workaround for Gnome bug #679291 */ - g_signal_handlers_disconnect_matched (list, G_SIGNAL_MATCH_DATA, 0, 0, NULL, - NULL, model); - stop_autoscroll (model); g_list_free (model->column_types); g_object_unref (model); @@ -541,44 +542,45 @@ static void destroy_cb (GtkWidget * list, ListModel * model) static void update_selection (GtkWidget * list, ListModel * model, int at, int rows) { - model->blocked = TRUE; + model->blocked = true; GtkTreeSelection * sel = gtk_tree_view_get_selection ((GtkTreeView *) list); for (int i = at; i < at + rows; i ++) { - GtkTreeIter iter = {.user_data = GINT_TO_POINTER (i)}; + GtkTreeIter iter = {0, GINT_TO_POINTER (i)}; if (model->cbs->get_selected (model->user, i)) gtk_tree_selection_select_iter (sel, & iter); else gtk_tree_selection_unselect_iter (sel, & iter); } - model->blocked = FALSE; + model->blocked = false; } EXPORT GtkWidget * audgui_list_new_real (const AudguiListCallbacks * cbs, int cbs_size, void * user, int rows) { - g_return_val_if_fail (cbs->get_value, NULL); + g_return_val_if_fail (cbs->get_value, nullptr); - ListModel * model = (ListModel *) g_object_new (list_model_get_type (), NULL); + ListModel * model = (ListModel *) g_object_new (list_model_get_type (), nullptr); model->cbs = cbs; model->cbs_size = cbs_size; model->user = user; model->rows = rows; model->highlight = -1; model->columns = RESERVED_COLUMNS; - model->column_types = NULL; - model->frozen = FALSE; - model->blocked = FALSE; - model->dragging = FALSE; + model->column_types = nullptr; + model->resizable = true; + model->frozen = false; + model->blocked = false; + model->dragging = false; model->clicked_row = -1; model->receive_row = -1; model->scroll_source = 0; model->scroll_speed = 0; GtkWidget * list = gtk_tree_view_new_with_model ((GtkTreeModel *) model); - gtk_tree_view_set_fixed_height_mode ((GtkTreeView *) list, TRUE); + gtk_tree_view_set_fixed_height_mode ((GtkTreeView *) list, true); g_signal_connect (list, "destroy", (GCallback) destroy_cb, model); model->charwidth = audgui_get_digit_width (list); @@ -589,7 +591,7 @@ EXPORT GtkWidget * audgui_list_new_real (const AudguiListCallbacks * cbs, int cb GtkTreeSelection * sel = gtk_tree_view_get_selection ((GtkTreeView *) list); gtk_tree_selection_set_mode (sel, GTK_SELECTION_MULTIPLE); - gtk_tree_selection_set_select_function (sel, select_allow_cb, NULL, NULL); + gtk_tree_selection_set_select_function (sel, select_allow_cb, nullptr, nullptr); g_signal_connect (sel, "changed", (GCallback) select_cb, model); update_selection (list, model, 0, rows); @@ -607,7 +609,7 @@ EXPORT GtkWidget * audgui_list_new_real (const AudguiListCallbacks * cbs, int cb g_signal_connect (list, "motion-notify-event", (GCallback) motion_notify_cb, model); g_signal_connect (list, "leave-notify-event", (GCallback) leave_notify_cb, model); - bool_t supports_drag = FALSE; + gboolean supports_drag = false; if (MODEL_HAS_CB (model, data_type) && MODEL_HAS_CB (model, get_data) && MODEL_HAS_CB (model, receive_data)) @@ -616,20 +618,19 @@ EXPORT GtkWidget * audgui_list_new_real (const AudguiListCallbacks * cbs, int cb gtk_drag_source_set (list, GDK_BUTTON1_MASK, & target, 1, GDK_ACTION_COPY); - gtk_drag_dest_set (list, 0, & target, 1, GDK_ACTION_COPY); + gtk_drag_dest_set (list, (GtkDestDefaults) 0, & target, 1, GDK_ACTION_COPY); - g_signal_connect (list, "drag-data-get", (GCallback) drag_data_get, - model); + g_signal_connect (list, "drag-data-get", (GCallback) drag_data_get, model); g_signal_connect (list, "drag-data-received", (GCallback) drag_data_received, model); - supports_drag = TRUE; + supports_drag = true; } else if (MODEL_HAS_CB (model, shift_rows)) { - gtk_drag_source_set (list, GDK_BUTTON1_MASK, NULL, 0, GDK_ACTION_COPY); - gtk_drag_dest_set (list, 0, NULL, 0, GDK_ACTION_COPY); - supports_drag = TRUE; + gtk_drag_source_set (list, GDK_BUTTON1_MASK, nullptr, 0, GDK_ACTION_COPY); + gtk_drag_dest_set (list, (GtkDestDefaults) 0, nullptr, 0, GDK_ACTION_COPY); + supports_drag = true; } if (supports_drag) @@ -665,30 +666,32 @@ EXPORT void audgui_list_add_column (GtkWidget * list, const char * title, GtkCellRenderer * renderer = gtk_cell_renderer_text_new (); GtkTreeViewColumn * tree_column = gtk_tree_view_column_new_with_attributes (title, renderer, "text", RESERVED_COLUMNS + column, "weight", - HIGHLIGHT_COLUMN, NULL); + HIGHLIGHT_COLUMN, nullptr); gtk_tree_view_column_set_sizing (tree_column, GTK_TREE_VIEW_COLUMN_FIXED); - gtk_tree_view_column_set_resizable (tree_column, TRUE); int pad1, pad2, pad3; - gtk_widget_style_get (list, "horizontal-separator", & pad1, "focus-line-width", & pad2, NULL); - gtk_cell_renderer_get_padding (renderer, & pad3, NULL); + gtk_widget_style_get (list, "horizontal-separator", & pad1, "focus-line-width", & pad2, nullptr); + gtk_cell_renderer_get_padding (renderer, & pad3, nullptr); int padding = pad1 + 2 * pad2 + 2 * pad3; - if (width < 1) + if (width < 0) { - gtk_tree_view_column_set_min_width (tree_column, - 6 * model->charwidth + model->charwidth / 2 + padding); - gtk_tree_view_column_set_expand (tree_column, TRUE); - g_object_set ((GObject *) renderer, "ellipsize-set", TRUE, "ellipsize", - PANGO_ELLIPSIZE_END, NULL); + gtk_tree_view_column_set_expand (tree_column, true); + model->resizable = false; // columns to the right will not be resizable } else { + gtk_tree_view_column_set_resizable (tree_column, model->resizable); gtk_tree_view_column_set_min_width (tree_column, width * model->charwidth + model->charwidth / 2 + padding); - g_object_set ((GObject *) renderer, "xalign", (float) 1, NULL); } + if (width >= 0 && width < 10) + g_object_set ((GObject *) renderer, "xalign", (float) 1, nullptr); + else + g_object_set ((GObject *) renderer, "ellipsize-set", true, "ellipsize", + PANGO_ELLIPSIZE_END, nullptr); + gtk_tree_view_append_column ((GtkTreeView *) list, tree_column); } @@ -707,7 +710,7 @@ EXPORT void audgui_list_insert_rows (GtkWidget * list, int at, int rows) if (model->highlight >= at) model->highlight += rows; - GtkTreeIter iter = {.user_data = GINT_TO_POINTER (at)}; + GtkTreeIter iter = {0, GINT_TO_POINTER (at)}; GtkTreePath * path = gtk_tree_path_new_from_indices (at, -1); for (int i = rows; i --; ) @@ -725,7 +728,7 @@ EXPORT void audgui_list_update_rows (GtkWidget * list, int at, int rows) ((GtkTreeView *) list); g_return_if_fail (at >= 0 && rows >= 0 && at + rows <= model->rows); - GtkTreeIter iter = {.user_data = GINT_TO_POINTER (at)}; + GtkTreeIter iter = {0, GINT_TO_POINTER (at)}; GtkTreePath * path = gtk_tree_path_new_from_indices (at, -1); while (rows --) @@ -750,8 +753,8 @@ EXPORT void audgui_list_delete_rows (GtkWidget * list, int at, int rows) else if (model->highlight >= at) model->highlight = -1; - model->frozen = TRUE; - model->blocked = TRUE; + model->frozen = true; + model->blocked = true; int focus = audgui_list_get_focus (list); @@ -777,8 +780,8 @@ EXPORT void audgui_list_delete_rows (GtkWidget * list, int at, int rows) gtk_tree_path_free (path); - model->frozen = FALSE; - model->blocked = FALSE; + model->frozen = false; + model->blocked = false; } EXPORT void audgui_list_update_selection (GtkWidget * list, int at, int rows) @@ -816,8 +819,8 @@ EXPORT void audgui_list_set_highlight (GtkWidget * list, int row) EXPORT int audgui_list_get_focus (GtkWidget * list) { - GtkTreePath * path = NULL; - gtk_tree_view_get_cursor ((GtkTreeView *) list, & path, NULL); + GtkTreePath * path = nullptr; + gtk_tree_view_get_cursor ((GtkTreeView *) list, & path, nullptr); if (! path) return -1; @@ -837,25 +840,25 @@ EXPORT void audgui_list_set_focus (GtkWidget * list, int row) if (row < 0 || row == audgui_list_get_focus (list)) return; - model->frozen = TRUE; - model->blocked = TRUE; + model->frozen = true; + model->blocked = true; GtkTreePath * path = gtk_tree_path_new_from_indices (row, -1); - gtk_tree_view_set_cursor ((GtkTreeView *) list, path, NULL, FALSE); - gtk_tree_view_scroll_to_cell ((GtkTreeView *) list, path, NULL, FALSE, 0, 0); + gtk_tree_view_set_cursor ((GtkTreeView *) list, path, nullptr, false); + gtk_tree_view_scroll_to_cell ((GtkTreeView *) list, path, nullptr, false, 0, 0); gtk_tree_path_free (path); - model->frozen = FALSE; - model->blocked = FALSE; + model->frozen = false; + model->blocked = false; } EXPORT int audgui_list_row_at_point (GtkWidget * list, int x, int y) { ListModel * model = (ListModel *) gtk_tree_view_get_model ((GtkTreeView *) list); - GtkTreePath * path = NULL; + GtkTreePath * path = nullptr; gtk_tree_view_convert_widget_to_bin_window_coords ((GtkTreeView *) list, x, y, & x, & y); - gtk_tree_view_get_path_at_pos ((GtkTreeView *) list, x, y, & path, NULL, NULL, NULL); + gtk_tree_view_get_path_at_pos ((GtkTreeView *) list, x, y, & path, nullptr, nullptr, nullptr); if (! path) return -1; @@ -871,9 +874,9 @@ EXPORT int audgui_list_row_at_point_rounded (GtkWidget * list, int x, int y) { ListModel * model = (ListModel *) gtk_tree_view_get_model ((GtkTreeView *) list); - GtkTreePath * path = NULL; + GtkTreePath * path = nullptr; gtk_tree_view_convert_widget_to_bin_window_coords ((GtkTreeView *) list, x, y, & x, & y); - gtk_tree_view_get_path_at_pos ((GtkTreeView *) list, x, y, & path, NULL, NULL, NULL); + gtk_tree_view_get_path_at_pos ((GtkTreeView *) list, x, y, & path, nullptr, nullptr, nullptr); if (! path) return -1; @@ -882,7 +885,7 @@ EXPORT int audgui_list_row_at_point_rounded (GtkWidget * list, int x, int y) g_return_val_if_fail (row >= 0 && row < model->rows, -1); GdkRectangle rect; - gtk_tree_view_get_background_area ((GtkTreeView *) list, path, NULL, & rect); + gtk_tree_view_get_background_area ((GtkTreeView *) list, path, nullptr, & rect); if (y > rect.y + rect.height / 2) row ++; diff --git a/src/libaudgui/list.h b/src/libaudgui/list.h index 56d2d82..df9d52d 100644 --- a/src/libaudgui/list.h +++ b/src/libaudgui/list.h @@ -20,37 +20,39 @@ #ifndef AUDGUI_LIST_H #define AUDGUI_LIST_H +/* okay to use without audgui_init() */ + #include <gtk/gtk.h> -#include <libaudcore/core.h> + +#include <libaudcore/index.h> /* New callbacks should be added to the end of this struct. The * audgui_list_new() macro tells us the size of the callback struct as it was * defined when the caller code was compiled, allowing us to expand the struct * without breaking backward compatibility. */ -typedef struct { +struct AudguiListCallbacks { void (* get_value) (void * user, int row, int column, GValue * value); /* selection (optional) */ - bool_t (* get_selected) (void * user, int row); - void (* set_selected) (void * user, int row, bool_t selected); - void (* select_all) (void * user, bool_t selected); + bool (* get_selected) (void * user, int row); + void (* set_selected) (void * user, int row, bool selected); + void (* select_all) (void * user, bool selected); void (* activate_row) (void * user, int row); /* optional */ void (* right_click) (void * user, GdkEventButton * event); /* optional */ void (* shift_rows) (void * user, int row, int before); /* optional */ /* cross-widget drag and drop (optional) */ - /* the list will handle free()ing data returned by get_data() */ const char * data_type; - void (* get_data) (void * user, void * * data, int * length); - void (* receive_data) (void * user, int row, const void * data, int length); + Index<char> (* get_data) (void * user); + void (* receive_data) (void * user, int row, const char * data, int len); void (* mouse_motion) (void * user, GdkEventMotion * event, int row); /* optional */ void (* mouse_leave) (void * user, GdkEventMotion * event, int row); /* optional */ void (* focus_change) (void * user, int row); /* optional */ -} AudguiListCallbacks; +}; GtkWidget * audgui_list_new_real (const AudguiListCallbacks * cbs, int cbs_size, void * user, int rows); diff --git a/src/libaudgui/menu.c b/src/libaudgui/menu.cc index 6fe882f..3069d8b 100644 --- a/src/libaudgui/menu.c +++ b/src/libaudgui/menu.cc @@ -19,13 +19,9 @@ #include "menu.h" -#include <audacious/i18n.h> -#include <audacious/misc.h> #include <libaudcore/hook.h> - -/* we still use GtkImageMenuItem until there is a good alternative */ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#include <libaudcore/i18n.h> +#include <libaudcore/runtime.h> static GtkWidget * image_menu_item_new (const char * text, const char * icon) { @@ -40,11 +36,9 @@ static GtkWidget * image_menu_item_new (const char * text, const char * icon) return widget; } -#pragma GCC diagnostic pop - static void toggled_cb (GtkCheckMenuItem * check, const AudguiMenuItem * item) { - bool_t on = gtk_check_menu_item_get_active (check); + gboolean on = gtk_check_menu_item_get_active (check); if (aud_get_bool (item->csect, item->cname) == on) return; @@ -57,26 +51,27 @@ static void toggled_cb (GtkCheckMenuItem * check, const AudguiMenuItem * item) static void hook_cb (void * data, GtkWidget * check) { - const AudguiMenuItem * item = g_object_get_data ((GObject *) check, "item"); + const AudguiMenuItem * item = (const AudguiMenuItem *) g_object_get_data + ((GObject *) check, "item"); gtk_check_menu_item_set_active ((GtkCheckMenuItem *) check, aud_get_bool (item->csect, item->cname)); } static void unhook_cb (GtkCheckMenuItem * check, const AudguiMenuItem * item) { - hook_dissociate_full (item->hook, (HookFunction) hook_cb, check); + hook_dissociate (item->hook, (HookFunction) hook_cb, check); } EXPORT GtkWidget * audgui_menu_item_new_with_domain (const AudguiMenuItem * item, GtkAccelGroup * accel, const char * domain) { const char * name = domain ? dgettext (domain, item->name) : item->name; - GtkWidget * widget = NULL; + GtkWidget * widget = nullptr; if (name && item->func && ! item->cname) /* normal widget */ { widget = image_menu_item_new (name, item->icon); - g_signal_connect (widget, "activate", item->func, NULL); + g_signal_connect (widget, "activate", item->func, nullptr); } else if (name && item->cname) /* toggle widget */ { @@ -92,7 +87,7 @@ EXPORT GtkWidget * audgui_menu_item_new_with_domain g_signal_connect (widget, "destroy", (GCallback) unhook_cb, (void *) item); } } - else if (name && (item->items || item->get_sub)) /* submenu */ + else if (name && (item->items.len || item->get_sub)) /* submenu */ { widget = image_menu_item_new (name, item->icon); @@ -103,7 +98,7 @@ EXPORT GtkWidget * audgui_menu_item_new_with_domain else { sub = gtk_menu_new (); - audgui_menu_init_with_domain (sub, item->items, item->n_items, accel, domain); + audgui_menu_init_with_domain (sub, item->items, accel, domain); } gtk_menu_item_set_submenu ((GtkMenuItem *) widget, sub); @@ -119,12 +114,12 @@ EXPORT GtkWidget * audgui_menu_item_new_with_domain } EXPORT void audgui_menu_init_with_domain (GtkWidget * shell, - const AudguiMenuItem * items, int n_items, GtkAccelGroup * accel, + ArrayRef<AudguiMenuItem> items, GtkAccelGroup * accel, const char * domain) { - for (int i = 0; i < n_items; i ++) + for (const AudguiMenuItem & item : items) { - GtkWidget * widget = audgui_menu_item_new_with_domain (& items[i], accel, domain); + GtkWidget * widget = audgui_menu_item_new_with_domain (& item, accel, domain); if (! widget) continue; diff --git a/src/libaudgui/menu.h b/src/libaudgui/menu.h index fc27ae5..d6a0cd3 100644 --- a/src/libaudgui/menu.h +++ b/src/libaudgui/menu.h @@ -20,10 +20,12 @@ #ifndef AUDGUI_MENU_H #define AUDGUI_MENU_H +/* okay to use without audgui_init() */ + #include <gtk/gtk.h> -#include <libaudcore/core.h> +#include <libaudcore/objects.h> -typedef struct _AudguiMenuItem { +struct AudguiMenuItem { const char * name; const char * icon; unsigned key; @@ -38,25 +40,44 @@ typedef struct _AudguiMenuItem { const char * hook; /* for submenus */ - const struct _AudguiMenuItem * items; - int n_items; + ArrayRef<AudguiMenuItem> items; /* for custom submenus */ GtkWidget * (* get_sub) (void); /* for separators */ - bool_t sep; -} AudguiMenuItem; + bool sep; +}; + +constexpr AudguiMenuItem MenuCommand (const char * name, const char * icon, + unsigned key, GdkModifierType mod, void (* func) (void)) + { return {name, icon, key, mod, func}; } + +constexpr AudguiMenuItem MenuToggle (const char * name, const char * icon, + unsigned key, GdkModifierType mod, const char * csect, const char * cname, + void (* func) (void) = 0, const char * hook = 0) + { return {name, icon, key, mod, func, csect, cname, hook}; } + +constexpr AudguiMenuItem MenuSub (const char * name, const char * icon, + ArrayRef<AudguiMenuItem> items) + { return {name, icon, 0, (GdkModifierType) 0, 0, 0, 0, 0, items}; } + +constexpr AudguiMenuItem MenuSub (const char * name, const char * icon, + GtkWidget * (* get_sub) (void)) + { return {name, icon, 0, (GdkModifierType) 0, 0, 0, 0, 0, 0, get_sub}; } + +constexpr AudguiMenuItem MenuSep () + { return {0, 0, 0, (GdkModifierType) 0, 0, 0, 0, 0, 0, 0, true}; } -/* use NULL for domain to skip translation */ +/* use nullptr for domain to skip translation */ GtkWidget * audgui_menu_item_new_with_domain (const AudguiMenuItem * item, GtkAccelGroup * accel, const char * domain); void audgui_menu_init_with_domain (GtkWidget * shell, - const AudguiMenuItem * items, int n_items, GtkAccelGroup * accel, + ArrayRef<AudguiMenuItem> items, GtkAccelGroup * accel, const char * domain); #define audgui_menu_item_new(i, a) audgui_menu_item_new_with_domain (i, a, PACKAGE) -#define audgui_menu_init(s, i, n, a) audgui_menu_init_with_domain (s, i, n, a, PACKAGE) +#define audgui_menu_init(s, i, a) audgui_menu_init_with_domain (s, i, a, PACKAGE) #endif /* AUDGUI_MENU_H */ diff --git a/src/libaudgui/pixbufs.c b/src/libaudgui/pixbufs.cc index 550dce0..498ed85 100644 --- a/src/libaudgui/pixbufs.c +++ b/src/libaudgui/pixbufs.cc @@ -19,25 +19,23 @@ #include <gdk-pixbuf/gdk-pixbuf.h> -#include <audacious/debug.h> -#include <audacious/misc.h> -#include <audacious/playlist.h> #include <libaudcore/audstrings.h> +#include <libaudcore/playlist.h> +#include <libaudcore/probe.h> +#include <libaudcore/runtime.h> +#include "internal.h" #include "libaudgui-gtk.h" static GdkPixbuf * current_pixbuf; -EXPORT GdkPixbuf * audgui_pixbuf_fallback (void) +EXPORT GdkPixbuf * audgui_pixbuf_fallback () { - static GdkPixbuf * fallback = NULL; + static GdkPixbuf * fallback = nullptr; if (! fallback) - { - const char * data_dir = aud_get_path (AUD_PATH_DATA_DIR); - SCONCAT2 (path, data_dir, "/images/album.png"); - fallback = gdk_pixbuf_new_from_file (path, NULL); - } + fallback = gdk_pixbuf_new_from_file (filename_build + ({aud_get_path (AudPath::DataDir), "images", "album.png"}), nullptr); if (fallback) g_object_ref ((GObject *) fallback); @@ -45,12 +43,12 @@ EXPORT GdkPixbuf * audgui_pixbuf_fallback (void) return fallback; } -void audgui_pixbuf_uncache (void) +void audgui_pixbuf_uncache () { if (current_pixbuf) { g_object_unref ((GObject *) current_pixbuf); - current_pixbuf = NULL; + current_pixbuf = nullptr; } } @@ -83,33 +81,32 @@ EXPORT void audgui_pixbuf_scale_within (GdkPixbuf * * pixbuf, int size) * pixbuf = pixbuf2; } -EXPORT GdkPixbuf * audgui_pixbuf_request (const char * filename) +EXPORT GdkPixbuf * audgui_pixbuf_request (const char * filename, bool * queued) { - const void * data; - int64_t size; - - aud_art_request_data (filename, & data, & size); + const Index<char> * data = aud_art_request_data (filename, queued); if (! data) - return NULL; + return nullptr; - GdkPixbuf * p = audgui_pixbuf_from_data (data, size); + GdkPixbuf * p = audgui_pixbuf_from_data (data->begin (), data->len ()); aud_art_unref (filename); return p; } -EXPORT GdkPixbuf * audgui_pixbuf_request_current (void) +EXPORT GdkPixbuf * audgui_pixbuf_request_current (bool * queued) { + if (queued) + * queued = false; + if (! current_pixbuf) { int list = aud_playlist_get_playing (); int entry = aud_playlist_get_position (list); if (entry < 0) - return NULL; + return nullptr; - char * filename = aud_playlist_entry_get_filename (list, entry); - current_pixbuf = audgui_pixbuf_request (filename); - str_unref (filename); + String filename = aud_playlist_entry_get_filename (list, entry); + current_pixbuf = audgui_pixbuf_request (filename, queued); } if (current_pixbuf) @@ -120,19 +117,19 @@ EXPORT GdkPixbuf * audgui_pixbuf_request_current (void) EXPORT GdkPixbuf * audgui_pixbuf_from_data (const void * data, int64_t size) { - GdkPixbuf * pixbuf = NULL; + GdkPixbuf * pixbuf = nullptr; GdkPixbufLoader * loader = gdk_pixbuf_loader_new (); - GError * error = NULL; + GError * error = nullptr; - if (gdk_pixbuf_loader_write (loader, data, size, & error) && - gdk_pixbuf_loader_close (loader, & error)) + if (gdk_pixbuf_loader_write (loader, (const unsigned char *) data, size, + & error) && gdk_pixbuf_loader_close (loader, & error)) { if ((pixbuf = gdk_pixbuf_loader_get_pixbuf (loader))) g_object_ref (pixbuf); } else { - AUDDBG ("error while loading pixbuf: %s\n", error->message); + AUDWARN ("While loading pixbuf: %s\n", error->message); g_error_free (error); } diff --git a/src/libaudgui/playlists.c b/src/libaudgui/playlists.cc index fa3fa05..c27b1f3 100644 --- a/src/libaudgui/playlists.c +++ b/src/libaudgui/playlists.cc @@ -19,29 +19,28 @@ #include <gtk/gtk.h> -#include <audacious/i18n.h> -#include <audacious/misc.h> -#include <audacious/playlist.h> #include <libaudcore/audstrings.h> +#include <libaudcore/i18n.h> +#include <libaudcore/playlist.h> +#include <libaudcore/runtime.h> +#include <libaudcore/tuple.h> #include <libaudcore/vfs.h> -#include "init.h" +#include "internal.h" #include "libaudgui.h" #include "libaudgui-gtk.h" -typedef struct -{ - bool_t save; +struct ImportExportJob { + bool save; int list_id; char * filename; GtkWidget * selector, * confirm; -} -ImportExportJob; +}; /* "destroy" callback; do not call directly */ static void cleanup_job (void * data) { - ImportExportJob * job = data; + ImportExportJob * job = (ImportExportJob *) data; char * folder = gtk_file_chooser_get_current_folder_uri ((GtkFileChooser *) job->selector); @@ -59,19 +58,23 @@ static void cleanup_job (void * data) static void finish_job (void * data) { - ImportExportJob * job = data; + ImportExportJob * job = (ImportExportJob *) data; int list = aud_playlist_by_unique_id (job->list_id); + Playlist::GetMode mode = Playlist::Wait; + if (aud_get_bool (nullptr, "metadata_on_play")) + mode = Playlist::Nothing; + if (list >= 0) { aud_playlist_set_filename (list, job->filename); if (job->save) - aud_playlist_save (list, job->filename); + aud_playlist_save (list, job->filename, mode); else { aud_playlist_entry_delete (list, 0, aud_playlist_entry_count (list)); - aud_playlist_entry_insert (list, 0, job->filename, NULL, FALSE); + aud_playlist_entry_insert (list, 0, job->filename, Tuple (), false); } } @@ -83,13 +86,12 @@ static void confirm_overwrite (ImportExportJob * job) if (job->confirm) gtk_widget_destroy (job->confirm); - SPRINTF (message, _("Overwrite %s?"), job->filename); - GtkWidget * button1 = audgui_button_new (_("_Overwrite"), "document-save", finish_job, job); - GtkWidget * button2 = audgui_button_new (_("_Cancel"), "process-stop", NULL, NULL); + GtkWidget * button2 = audgui_button_new (_("_Cancel"), "process-stop", nullptr, nullptr); job->confirm = audgui_dialog_new (GTK_MESSAGE_QUESTION, - _("Confirm Overwrite"), message, button1, button2); + _("Confirm Overwrite"), str_printf (_("Overwrite %s?"), job->filename), + button1, button2); g_signal_connect (job->confirm, "destroy", (GCallback) gtk_widget_destroyed, & job->confirm); @@ -98,14 +100,14 @@ static void confirm_overwrite (ImportExportJob * job) static void check_overwrite (void * data) { - ImportExportJob * job = data; + ImportExportJob * job = (ImportExportJob *) data; job->filename = gtk_file_chooser_get_uri ((GtkFileChooser *) job->selector); if (! job->filename) return; - if (job->save && vfs_file_test (job->filename, G_FILE_TEST_EXISTS)) + if (job->save && VFSFile::test_file (job->filename, VFS_EXISTS)) confirm_overwrite (job); else finish_job (data); @@ -131,7 +133,7 @@ static void create_selector (ImportExportJob * job, const char * filename, const action = GTK_FILE_CHOOSER_ACTION_OPEN; } - job->selector = gtk_file_chooser_dialog_new (title, NULL, action, NULL, NULL); + job->selector = gtk_file_chooser_dialog_new (title, nullptr, action, nullptr, nullptr); if (filename) gtk_file_chooser_set_uri ((GtkFileChooser *) job->selector, filename); @@ -145,7 +147,7 @@ static void create_selector (ImportExportJob * job, const char * filename, const gtk_dialog_add_action_widget ((GtkDialog *) job->selector, button2, GTK_RESPONSE_NONE); gtk_dialog_add_action_widget ((GtkDialog *) job->selector, button1, GTK_RESPONSE_NONE); - gtk_widget_set_can_default (button1, TRUE); + gtk_widget_set_can_default (button1, true); gtk_widget_grab_default (button1); g_signal_connect_swapped (job->selector, "destroy", (GCallback) cleanup_job, job); @@ -153,32 +155,29 @@ static void create_selector (ImportExportJob * job, const char * filename, const gtk_widget_show_all (job->selector); } -static GtkWidget * start_job (bool_t save) +static GtkWidget * start_job (bool save) { int list = aud_playlist_get_active (); - char * filename = aud_playlist_get_filename (list); - char * folder = aud_get_str ("audgui", "playlist_path"); + String filename = aud_playlist_get_filename (list); + String folder = aud_get_str ("audgui", "playlist_path"); ImportExportJob * job = g_slice_new0 (ImportExportJob); job->save = save; job->list_id = aud_playlist_get_unique_id (list); - create_selector (job, filename, folder[0] ? folder : NULL); - - str_unref (filename); - str_unref (folder); + create_selector (job, filename, folder[0] ? (const char *) folder : nullptr); return job->selector; } -EXPORT void audgui_import_playlist (void) +EXPORT void audgui_import_playlist () { - audgui_show_unique_window (AUDGUI_PLAYLIST_IMPORT_WINDOW, start_job (FALSE)); + audgui_show_unique_window (AUDGUI_PLAYLIST_IMPORT_WINDOW, start_job (false)); } -EXPORT void audgui_export_playlist (void) +EXPORT void audgui_export_playlist () { - audgui_show_unique_window (AUDGUI_PLAYLIST_EXPORT_WINDOW, start_job (TRUE)); + audgui_show_unique_window (AUDGUI_PLAYLIST_EXPORT_WINDOW, start_job (true)); } diff --git a/src/audacious/ui_plugin_menu.c b/src/libaudgui/plugin-menu.cc index 9d81b65..268ad55 100644 --- a/src/audacious/ui_plugin_menu.c +++ b/src/libaudgui/plugin-menu.cc @@ -1,5 +1,5 @@ /* - * ui_plugin_menu.c + * plugin-menu.c * Copyright 2009-2011 John Lindgren * * Redistribution and use in source and binary forms, with or without @@ -17,37 +17,39 @@ * the use of this software. */ -#include <glib.h> #include <gtk/gtk.h> -#include <libaudgui/menu.h> +#include <libaudcore/i18n.h> +#include <libaudcore/interface.h> +#include <libaudcore/plugins.h> -#include "i18n.h" -#include "misc.h" +#include "internal.h" +#include "libaudgui.h" +#include "libaudgui-gtk.h" +#include "menu.h" -static GList * items[AUD_MENU_COUNT]; /* of AudguiMenuItem */ -static GtkWidget * menus[AUD_MENU_COUNT]; +static aud::array<AudMenuID, GList *> items; /* of AudguiMenuItem */ +static aud::array<AudMenuID, GtkWidget *> menus; -static void configure_plugins (void) +static void configure_plugins () { - show_prefs_for_plugin_type (PLUGIN_TYPE_GENERAL); + audgui_show_prefs_for_plugin_type (PluginType::General); } static const AudguiMenuItem main_items[] = { - {N_("_Plugins ..."), .func = configure_plugins}, - {.sep = TRUE} + MenuCommand (N_("_Plugins ..."), 0, 0, (GdkModifierType) 0, configure_plugins), + MenuSep () }; static void add_to_menu (GtkWidget * menu, const AudguiMenuItem * item) { - GtkWidget * widget = audgui_menu_item_new_with_domain (item, NULL, NULL); + GtkWidget * widget = audgui_menu_item_new_with_domain (item, nullptr, nullptr); g_object_set_data ((GObject *) widget, "func", (void *) item->func); gtk_widget_show (widget); gtk_menu_shell_append ((GtkMenuShell *) menu, widget); } -/* GtkWidget * get_plugin_menu (int id) */ -void * get_plugin_menu (int id) +EXPORT GtkWidget * audgui_get_plugin_menu (AudMenuID id) { if (! menus[id]) { @@ -55,18 +57,18 @@ void * get_plugin_menu (int id) g_signal_connect (menus[id], "destroy", (GCallback) gtk_widget_destroyed, & menus[id]); - if (id == AUD_MENU_MAIN) - audgui_menu_init (menus[id], main_items, ARRAY_LEN (main_items), NULL); + if (id == AudMenuID::Main) + audgui_menu_init (menus[id], main_items, nullptr); for (GList * node = items[id]; node; node = node->next) - add_to_menu (menus[id], node->data); + add_to_menu (menus[id], (const AudguiMenuItem *) node->data); } return menus[id]; } -void plugin_menu_add (int id, MenuFunc func, const char * name, - const char * icon) +EXPORT void audgui_plugin_menu_add (AudMenuID id, void (* func) (void), + const char * name, const char * icon) { AudguiMenuItem * item = g_slice_new0 (AudguiMenuItem); item->name = name; @@ -79,13 +81,13 @@ void plugin_menu_add (int id, MenuFunc func, const char * name, add_to_menu (menus[id], item); } -static void remove_cb (GtkWidget * widget, MenuFunc func) +static void remove_cb (GtkWidget * widget, void (* func) (void)) { - if ((MenuFunc) g_object_get_data ((GObject *) widget, "func") == func) + if (g_object_get_data ((GObject *) widget, "func") == (void *) func) gtk_widget_destroy (widget); } -void plugin_menu_remove (int id, MenuFunc func) +EXPORT void audgui_plugin_menu_remove (AudMenuID id, void (* func) (void)) { if (menus[id]) gtk_container_foreach ((GtkContainer *) menus[id], (GtkCallback) @@ -103,3 +105,14 @@ void plugin_menu_remove (int id, MenuFunc func) } } } + +void plugin_menu_cleanup () +{ + for (AudMenuID id : aud::range<AudMenuID> ()) + { + g_warn_if_fail (! items[id]); + + if (menus[id]) + gtk_widget_destroy (menus[id]); + } +} diff --git a/src/libaudgui/plugin-prefs.cc b/src/libaudgui/plugin-prefs.cc new file mode 100644 index 0000000..ac27ae2 --- /dev/null +++ b/src/libaudgui/plugin-prefs.cc @@ -0,0 +1,191 @@ +/* + * plugin-prefs.c + * Copyright 2012-2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <libaudcore/audstrings.h> +#include <libaudcore/i18n.h> +#include <libaudcore/plugin.h> +#include <libaudcore/plugins.h> +#include <libaudcore/preferences.h> + +#include "internal.h" +#include "libaudgui.h" +#include "libaudgui-gtk.h" + +static GList * about_windows; +static GList * config_windows; + +static int find_cb (GtkWidget * window, PluginHandle * plugin) +{ + return (g_object_get_data ((GObject *) window, "plugin-id") != plugin); +} + +static bool watch_cb (PluginHandle * plugin, void * window); + +/* window destroyed before plugin disabled */ +static void destroy_cb (GtkWidget * window, PluginHandle * plugin) +{ + GList * * list = & config_windows; + GList * node = g_list_find (* list, window); + + if (! node) + { + list = & about_windows; + node = g_list_find (* list, nullptr); /* set to nullptr by audgui_simple_message() */ + g_return_if_fail (node); + } + + aud_plugin_remove_watch (plugin, watch_cb, window); + + * list = g_list_delete_link (* list, node); +} + +/* plugin disabled before window destroyed */ +static bool watch_cb (PluginHandle * plugin, void * window) +{ + if (aud_plugin_get_enabled (plugin)) + return true; + + GList * * list = & about_windows; + GList * node = g_list_find (* list, window); + + if (! node) + { + list = & config_windows; + node = g_list_find (* list, window); + g_return_val_if_fail (node, false); + } + + g_signal_handlers_disconnect_by_func (window, (void *) destroy_cb, plugin); + gtk_widget_destroy ((GtkWidget *) window); + + * list = g_list_delete_link (* list, node); + + return false; +} + +EXPORT void audgui_show_plugin_about (PluginHandle * plugin) +{ + GList * node = g_list_find_custom (about_windows, plugin, (GCompareFunc) find_cb); + + if (node) + { + gtk_window_present ((GtkWindow *) node->data); + return; + } + + Plugin * header = (Plugin *) aud_plugin_get_header (plugin); + g_return_if_fail (header); + + const char * name = header->info.name; + const char * text = header->info.about; + if (! text) + return; + + if (header->info.domain) + { + name = dgettext (header->info.domain, name); + text = dgettext (header->info.domain, text); + } + + about_windows = node = g_list_prepend (about_windows, nullptr); + + audgui_simple_message ((GtkWidget * *) & node->data, GTK_MESSAGE_INFO, + str_printf (_("About %s"), name), text); + g_object_set_data ((GObject *) node->data, "plugin-id", plugin); + + g_signal_connect_after (node->data, "destroy", (GCallback) destroy_cb, plugin); + aud_plugin_add_watch (plugin, watch_cb, node->data); +} + +static void response_cb (GtkWidget * window, int response, const PluginPreferences * p) +{ + if (response == GTK_RESPONSE_OK && p->apply) + p->apply (); + + gtk_widget_destroy (window); +} + +static void cleanup_cb (GtkWidget * window, const PluginPreferences * p) +{ + if (p->cleanup) + p->cleanup (); +} + +EXPORT void audgui_show_plugin_prefs (PluginHandle * plugin) +{ + GList * node = g_list_find_custom (config_windows, plugin, (GCompareFunc) find_cb); + + if (node) + { + gtk_window_present ((GtkWindow *) node->data); + return; + } + + Plugin * header = (Plugin *) aud_plugin_get_header (plugin); + g_return_if_fail (header); + + const PluginPreferences * p = header->info.prefs; + if (! p) + return; + + if (p->init) + p->init (); + + const char * name = header->info.name; + if (header->info.domain) + name = dgettext (header->info.domain, name); + + GtkWidget * window = gtk_dialog_new (); + gtk_window_set_title ((GtkWindow *) window, str_printf (_("%s Settings"), name)); + + if (p->apply) + { + GtkWidget * button1 = audgui_button_new (_("_Set"), "system-run", nullptr, nullptr); + GtkWidget * button2 = audgui_button_new (_("_Cancel"), "process-stop", nullptr, nullptr); + gtk_dialog_add_action_widget ((GtkDialog *) window, button2, GTK_RESPONSE_CANCEL); + gtk_dialog_add_action_widget ((GtkDialog *) window, button1, GTK_RESPONSE_OK); + } + else + { + GtkWidget * button = audgui_button_new (_("_Close"), "window-close", nullptr, nullptr); + gtk_dialog_add_action_widget ((GtkDialog *) window, button, GTK_RESPONSE_CLOSE); + } + + GtkWidget * content = gtk_dialog_get_content_area ((GtkDialog *) window); + GtkWidget * box = gtk_vbox_new (false, 0); + audgui_create_widgets_with_domain (box, p->widgets, header->info.domain); + gtk_box_pack_start ((GtkBox *) content, box, true, true, 0); + + g_signal_connect (window, "response", (GCallback) response_cb, (void *) p); + g_signal_connect (window, "destroy", (GCallback) cleanup_cb, (void *) p); + + gtk_widget_show_all (window); + + g_object_set_data ((GObject *) window, "plugin-id", plugin); + config_windows = g_list_prepend (config_windows, window); + + g_signal_connect_after (window, "destroy", (GCallback) destroy_cb, plugin); + aud_plugin_add_watch (plugin, watch_cb, window); +} + +void plugin_prefs_cleanup () +{ + g_list_foreach (about_windows, (GFunc) gtk_widget_destroy, nullptr); + g_list_foreach (config_windows, (GFunc) gtk_widget_destroy, nullptr); +} diff --git a/src/audacious/plugin-view.c b/src/libaudgui/plugin-view.cc index 36959af..8cf237e 100644 --- a/src/audacious/plugin-view.c +++ b/src/libaudgui/plugin-view.cc @@ -19,42 +19,43 @@ #include <gtk/gtk.h> -#include <libaudgui/libaudgui-gtk.h> +#include <libaudcore/i18n.h> +#include <libaudcore/plugin.h> +#include <libaudcore/plugins.h> -#include "i18n.h" -#include "plugin.h" -#include "plugins.h" -#include "ui_preferences.h" +#include "internal.h" +#include "libaudgui.h" +#include "libaudgui-gtk.h" enum { - PVIEW_COL_NODE, - PVIEW_COL_ENABLED, - PVIEW_COL_NAME, - PVIEW_COLS + PVIEW_COL_NODE, + PVIEW_COL_ENABLED, + PVIEW_COL_NAME, + PVIEW_COLS }; -typedef struct { +struct Node { PluginHandle * p; GtkTreeModel * model; GtkTreePath * path; -} Node; +}; static PluginHandle * get_selected_plugin (GtkTreeView * tree) { - Node * n = NULL; + Node * n = nullptr; GtkTreeSelection * sel = gtk_tree_view_get_selection (tree); /* the treeview may not have a model yet */ if (! sel) - return NULL; + return nullptr; GtkTreeModel * model; GtkTreeIter iter; if (gtk_tree_selection_get_selected (sel, & model, & iter)) gtk_tree_model_get (model, & iter, PVIEW_COL_NODE, & n, -1); - return n == NULL ? NULL : n->p; + return n == nullptr ? nullptr : n->p; } static void do_enable (GtkCellRendererToggle * cell, const char * path_str, @@ -65,41 +66,42 @@ static void do_enable (GtkCellRendererToggle * cell, const char * path_str, gtk_tree_model_get_iter (model, & iter, path); gtk_tree_path_free (path); - Node * n = NULL; - bool_t enabled; + Node * n = nullptr; + gboolean enabled; gtk_tree_model_get (model, & iter, PVIEW_COL_NODE, & n, PVIEW_COL_ENABLED, & enabled, -1); - g_return_if_fail (n != NULL); + g_return_if_fail (n != nullptr); - plugin_enable (n->p, ! enabled); + aud_plugin_enable (n->p, ! enabled); } -static bool_t list_watcher (PluginHandle * p, Node * n) +static bool list_watcher (PluginHandle * p, void * data) { + auto n = (Node *) data; + GtkTreeIter iter; gtk_tree_model_get_iter (n->model, & iter, n->path); gtk_list_store_set ((GtkListStore *) n->model, & iter, PVIEW_COL_ENABLED, - plugin_get_enabled (n->p), -1); - return TRUE; + aud_plugin_get_enabled (n->p), -1); + + return true; } -static bool_t fill_cb (PluginHandle * p, GtkTreeModel * model) +static void add_to_list (GtkTreeModel * model, PluginHandle * p) { - Node * n = g_slice_new (Node); + Node * n = new Node; GtkTreeIter iter; gtk_list_store_append ((GtkListStore *) model, & iter); gtk_list_store_set ((GtkListStore *) model, & iter, PVIEW_COL_NODE, n, - PVIEW_COL_ENABLED, plugin_get_enabled (p), PVIEW_COL_NAME, - plugin_get_name (p), -1); + PVIEW_COL_ENABLED, aud_plugin_get_enabled (p), PVIEW_COL_NAME, + aud_plugin_get_name (p), -1); n->p = p; n->model = model; n->path = gtk_tree_model_get_path (model, & iter); - plugin_add_watch (p, (PluginForEachFunc) list_watcher, n); - - return TRUE; + aud_plugin_add_watch (p, list_watcher, n); } static void list_fill (GtkTreeView * tree, void * type) @@ -110,32 +112,33 @@ static void list_fill (GtkTreeView * tree, void * type) GtkTreeViewColumn * col = gtk_tree_view_column_new (); gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_GROW_ONLY); - gtk_tree_view_column_set_resizable (col, FALSE); + gtk_tree_view_column_set_resizable (col, false); gtk_tree_view_append_column (tree, col); GtkCellRenderer * rend = gtk_cell_renderer_toggle_new (); g_signal_connect (rend, "toggled", (GCallback) do_enable, model); - gtk_tree_view_column_pack_start (col, rend, FALSE); + gtk_tree_view_column_pack_start (col, rend, false); gtk_tree_view_column_set_attributes (col, rend, "active", PVIEW_COL_ENABLED, - NULL); + nullptr); col = gtk_tree_view_column_new (); gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_FIXED); - gtk_tree_view_column_set_expand (col, TRUE); - gtk_tree_view_column_set_resizable (col, FALSE); + gtk_tree_view_column_set_expand (col, true); + gtk_tree_view_column_set_resizable (col, false); gtk_tree_view_append_column (tree, col); rend = gtk_cell_renderer_text_new (); - gtk_tree_view_column_pack_start (col, rend, FALSE); - gtk_tree_view_column_set_attributes (col, rend, "text", PVIEW_COL_NAME, NULL); + gtk_tree_view_column_pack_start (col, rend, false); + gtk_tree_view_column_set_attributes (col, rend, "text", PVIEW_COL_NAME, nullptr); - plugin_for_each (GPOINTER_TO_INT (type), (PluginForEachFunc) fill_cb, model); + for (PluginHandle * plugin : aud_plugin_list (aud::from_ptr<PluginType> (type))) + add_to_list (model, plugin); } static void list_destroy (GtkTreeView * tree) { GtkTreeModel * model = gtk_tree_view_get_model (tree); - if (model == NULL) + if (model == nullptr) return; GtkTreeIter iter; @@ -143,13 +146,13 @@ static void list_destroy (GtkTreeView * tree) { do { - Node * n = NULL; + Node * n = nullptr; gtk_tree_model_get (model, & iter, PVIEW_COL_NODE, & n, -1); - g_return_if_fail (n != NULL); + g_return_if_fail (n != nullptr); - plugin_remove_watch (n->p, (PluginForEachFunc) list_watcher, n); + aud_plugin_remove_watch (n->p, list_watcher, n); gtk_tree_path_free (n->path); - g_slice_free (Node, n); + delete n; } while (gtk_tree_model_iter_next (model, & iter)); } @@ -157,102 +160,91 @@ static void list_destroy (GtkTreeView * tree) g_object_unref ((GObject *) model); } -static bool_t config_watcher (PluginHandle * p, GtkWidget * config) +static bool watcher (PluginHandle * p, void * b) { - gtk_widget_set_sensitive (config, plugin_has_configure (p) && - plugin_get_enabled (p)); - return TRUE; -} + bool is_about = GPOINTER_TO_INT (g_object_get_data ((GObject *) b, "is_about")); -static bool_t about_watcher (PluginHandle * p, GtkWidget * about) -{ - gtk_widget_set_sensitive (about, plugin_has_about (p) && plugin_get_enabled - (p)); - return TRUE; + if (is_about) + gtk_widget_set_sensitive ((GtkWidget *) b, + aud_plugin_has_about (p) && aud_plugin_get_enabled (p)); + else + gtk_widget_set_sensitive ((GtkWidget *) b, + aud_plugin_has_configure (p) && aud_plugin_get_enabled (p)); + + return true; } static void button_update (GtkTreeView * tree, GtkWidget * b) { - PluginForEachFunc watcher = (PluginForEachFunc) g_object_get_data - ((GObject *) b, "watcher"); - g_return_if_fail (watcher != NULL); - - PluginHandle * p = g_object_steal_data ((GObject *) b, "plugin"); - if (p != NULL) - plugin_remove_watch (p, watcher, b); + PluginHandle * p = (PluginHandle *) g_object_steal_data ((GObject *) b, "plugin"); + if (p != nullptr) + aud_plugin_remove_watch (p, watcher, b); p = get_selected_plugin (tree); - if (p != NULL) + if (p != nullptr) { g_object_set_data ((GObject *) b, "plugin", p); watcher (p, b); - plugin_add_watch (p, watcher, b); + aud_plugin_add_watch (p, watcher, b); } else - gtk_widget_set_sensitive (b, FALSE); + gtk_widget_set_sensitive (b, false); } static void do_config (void * tree) { - PluginHandle * plugin = get_selected_plugin (tree); - g_return_if_fail (plugin != NULL); - plugin_do_configure (plugin); + PluginHandle * plugin = get_selected_plugin ((GtkTreeView *) tree); + g_return_if_fail (plugin != nullptr); + audgui_show_plugin_prefs (plugin); } static void do_about (void * tree) { - PluginHandle * plugin = get_selected_plugin (tree); - g_return_if_fail (plugin != NULL); - plugin_do_about (plugin); + PluginHandle * plugin = get_selected_plugin ((GtkTreeView *) tree); + g_return_if_fail (plugin != nullptr); + audgui_show_plugin_about (plugin); } static void button_destroy (GtkWidget * b) { - PluginForEachFunc watcher = (PluginForEachFunc) g_object_get_data - ((GObject *) b, "watcher"); - g_return_if_fail (watcher != NULL); - - PluginHandle * p = g_object_steal_data ((GObject *) b, "plugin"); - if (p != NULL) - plugin_remove_watch (p, watcher, b); + PluginHandle * p = (PluginHandle *) g_object_steal_data ((GObject *) b, "plugin"); + if (p != nullptr) + aud_plugin_remove_watch (p, watcher, b); } -GtkWidget * plugin_view_new (int type) +GtkWidget * plugin_view_new (PluginType type) { - GtkWidget * vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + GtkWidget * vbox = gtk_vbox_new (false, 6); gtk_container_set_border_width ((GtkContainer *) vbox, 6); - GtkWidget * scrolled = gtk_scrolled_window_new (NULL, NULL); - gtk_box_pack_start ((GtkBox *) vbox, scrolled, TRUE, TRUE, 0); + GtkWidget * scrolled = gtk_scrolled_window_new (nullptr, nullptr); + gtk_box_pack_start ((GtkBox *) vbox, scrolled, true, true, 0); gtk_scrolled_window_set_policy ((GtkScrolledWindow *) scrolled, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_shadow_type ((GtkScrolledWindow *) scrolled, - GTK_SHADOW_IN); + gtk_scrolled_window_set_shadow_type ((GtkScrolledWindow *) scrolled, GTK_SHADOW_IN); GtkWidget * tree = gtk_tree_view_new (); gtk_container_add ((GtkContainer *) scrolled, tree); - gtk_tree_view_set_headers_visible ((GtkTreeView *) tree, FALSE); - g_signal_connect (tree, "realize", (GCallback) list_fill, GINT_TO_POINTER - (type)); - g_signal_connect (tree, "destroy", (GCallback) list_destroy, NULL); - - GtkWidget * hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); - gtk_box_pack_start ((GtkBox *) vbox, hbox, FALSE, FALSE, 0); - - GtkWidget * config = audgui_button_new (_("_Settings"), - "preferences-system", do_config, tree); - gtk_box_pack_start ((GtkBox *) hbox, config, FALSE, FALSE, 0); - gtk_widget_set_sensitive (config, FALSE); - g_object_set_data ((GObject *) config, "watcher", (void *) config_watcher); + gtk_tree_view_set_headers_visible ((GtkTreeView *) tree, false); + g_signal_connect (tree, "realize", (GCallback) list_fill, aud::to_ptr (type)); + g_signal_connect (tree, "destroy", (GCallback) list_destroy, nullptr); + + GtkWidget * hbox = gtk_hbox_new (false, 6); + gtk_box_pack_start ((GtkBox *) vbox, hbox, false, false, 0); + + GtkWidget * config = audgui_button_new (_("_Settings"), "preferences-system", do_config, tree); + gtk_box_pack_start ((GtkBox *) hbox, config, false, false, 0); + gtk_widget_set_sensitive (config, false); + g_object_set_data ((GObject *) config, "is_about", GINT_TO_POINTER (false)); g_signal_connect (tree, "cursor-changed", (GCallback) button_update, config); - g_signal_connect (config, "destroy", (GCallback) button_destroy, NULL); + g_signal_connect (config, "destroy", (GCallback) button_destroy, nullptr); GtkWidget * about = audgui_button_new (_("_About"), "help-about", do_about, tree); - gtk_box_pack_start ((GtkBox *) hbox, about, FALSE, FALSE, 0); - gtk_widget_set_sensitive (about, FALSE); - g_object_set_data ((GObject *) about, "watcher", (void *) about_watcher); + gtk_box_pack_start ((GtkBox *) hbox, about, false, false, 0); + gtk_widget_set_sensitive (about, false); + g_object_set_data ((GObject *) about, "is_about", GINT_TO_POINTER (true)); g_signal_connect (tree, "cursor-changed", (GCallback) button_update, about); - g_signal_connect (about, "destroy", (GCallback) button_destroy, NULL); + g_signal_connect (about, "destroy", (GCallback) button_destroy, nullptr); return vbox; } diff --git a/src/libaudgui/prefs-widget.cc b/src/libaudgui/prefs-widget.cc new file mode 100644 index 0000000..c9674e9 --- /dev/null +++ b/src/libaudgui/prefs-widget.cc @@ -0,0 +1,570 @@ +/* + * prefs-widget.c + * Copyright 2007-2014 Tomasz MoĆ, William Pitcock, and John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <string.h> +#include <gtk/gtk.h> + +#include <libaudcore/audstrings.h> +#include <libaudcore/hook.h> +#include <libaudcore/i18n.h> +#include <libaudcore/preferences.h> +#include <libaudcore/runtime.h> + +#include "libaudgui-gtk.h" + +static void widget_changed (GtkWidget * widget, const PreferencesWidget * w) +{ + switch (w->type) + { + case PreferencesWidget::CheckButton: + { + gboolean set = gtk_toggle_button_get_active ((GtkToggleButton *) widget); + w->cfg.set_bool (set); + + auto child = (GtkWidget *) g_object_get_data ((GObject *) widget, "child"); + if (child) + gtk_widget_set_sensitive (child, set); + + break; + } + + case PreferencesWidget::RadioButton: + if (gtk_toggle_button_get_active ((GtkToggleButton *) widget)) + w->cfg.set_int (w->data.radio_btn.value); + + break; + + case PreferencesWidget::SpinButton: + if (w->cfg.type == WidgetConfig::Int) + w->cfg.set_int (gtk_spin_button_get_value_as_int ((GtkSpinButton *) widget)); + else if (w->cfg.type == WidgetConfig::Float) + w->cfg.set_float (gtk_spin_button_get_value ((GtkSpinButton *) widget)); + + break; + + case PreferencesWidget::FontButton: + w->cfg.set_string (gtk_font_button_get_font_name ((GtkFontButton *) widget)); + break; + + case PreferencesWidget::Entry: + w->cfg.set_string (gtk_entry_get_text ((GtkEntry *) widget)); + break; + + case PreferencesWidget::ComboBox: + { + auto items = (const ComboItem *) g_object_get_data ((GObject *) widget, "comboitems"); + int idx = gtk_combo_box_get_active ((GtkComboBox *) widget); + + if (w->cfg.type == WidgetConfig::Int) + w->cfg.set_int (items[idx].num); + else if (w->cfg.type == WidgetConfig::String) + w->cfg.set_string (items[idx].str); + + break; + } + + default: + break; + } +} + +static void combobox_update (GtkWidget * combobox, const PreferencesWidget * widget); + +static void widget_update (void *, void * widget) +{ + auto w = (const PreferencesWidget *) g_object_get_data ((GObject *) widget, "prefswidget"); + + g_signal_handlers_block_by_func (widget, (void *) widget_changed, (void *) w); + + switch (w->type) + { + case PreferencesWidget::CheckButton: + gtk_toggle_button_set_active ((GtkToggleButton *) widget, w->cfg.get_bool ()); + break; + + case PreferencesWidget::RadioButton: + if (w->cfg.get_int () == w->data.radio_btn.value) + gtk_toggle_button_set_active ((GtkToggleButton *) widget, true); + + break; + + case PreferencesWidget::SpinButton: + if (w->cfg.type == WidgetConfig::Int) + gtk_spin_button_set_value ((GtkSpinButton *) widget, w->cfg.get_int ()); + else if (w->cfg.type == WidgetConfig::Float) + gtk_spin_button_set_value ((GtkSpinButton *) widget, w->cfg.get_float ()); + + break; + + case PreferencesWidget::FontButton: + gtk_font_button_set_font_name ((GtkFontButton *) widget, w->cfg.get_string ()); + break; + + case PreferencesWidget::Entry: + gtk_entry_set_text ((GtkEntry *) widget, w->cfg.get_string ()); + break; + + case PreferencesWidget::ComboBox: + combobox_update ((GtkWidget *) widget, w); + break; + + default: + break; + } + + g_signal_handlers_unblock_by_func (widget, (void *) widget_changed, (void *) w); +} + +static void widget_unhook (GtkWidget * widget, const PreferencesWidget * w) +{ + hook_dissociate (w->cfg.hook, widget_update, widget); +} + +static void widget_init (GtkWidget * widget, const PreferencesWidget * w) +{ + g_object_set_data ((GObject *) widget, "prefswidget", (void *) w); + + widget_update (nullptr, widget); + + switch (w->type) + { + case PreferencesWidget::CheckButton: + case PreferencesWidget::RadioButton: + g_signal_connect (widget, "toggled", (GCallback) widget_changed, (void *) w); + break; + + case PreferencesWidget::SpinButton: + g_signal_connect (widget, "value_changed", (GCallback) widget_changed, (void *) w); + break; + + case PreferencesWidget::FontButton: + g_signal_connect (widget, "font_set", (GCallback) widget_changed, (void *) w); + break; + + case PreferencesWidget::Entry: + case PreferencesWidget::ComboBox: + g_signal_connect (widget, "changed", (GCallback) widget_changed, (void *) w); + break; + + default: + break; + } + + if (w->cfg.hook) + { + hook_associate (w->cfg.hook, widget_update, widget); + g_signal_connect (widget, "destroy", (GCallback) widget_unhook, (void *) w); + } +} + +/* WIDGET_LABEL */ + +static void create_label (const PreferencesWidget * widget, GtkWidget * * label, + GtkWidget * * icon, const char * domain) +{ + * label = gtk_label_new_with_mnemonic (dgettext (domain, widget->label)); + gtk_label_set_use_markup ((GtkLabel *) * label, true); + gtk_label_set_line_wrap ((GtkLabel *) * label, true); + gtk_misc_set_alignment ((GtkMisc *) * label, 0, 0.5); +} + +/* WIDGET_SPIN_BTN */ + +static void create_spin_button (const PreferencesWidget * widget, + GtkWidget * * label_pre, GtkWidget * * spin_btn, GtkWidget * * label_past, + const char * domain) +{ + * label_pre = gtk_label_new (dgettext (domain, widget->label)); + * spin_btn = gtk_spin_button_new_with_range (widget->data.spin_btn.min, + widget->data.spin_btn.max, widget->data.spin_btn.step); + + if (widget->data.spin_btn.right_label) + * label_past = gtk_label_new (dgettext (domain, widget->data.spin_btn.right_label)); + + widget_init (* spin_btn, widget); +} + +/* WIDGET_FONT_BTN */ + +void create_font_btn (const PreferencesWidget * widget, GtkWidget * * label, + GtkWidget * * font_btn, const char * domain) +{ + * font_btn = gtk_font_button_new (); + gtk_font_button_set_use_font ((GtkFontButton *) * font_btn, true); + gtk_font_button_set_use_size ((GtkFontButton *) * font_btn, true); + + if (widget->label) + { + * label = gtk_label_new (dgettext (domain, widget->label)); + gtk_misc_set_alignment ((GtkMisc *) * label, 1, 0.5); + } + + if (widget->data.font_btn.title) + gtk_font_button_set_title ((GtkFontButton *) * font_btn, + dgettext (domain, widget->data.font_btn.title)); + + widget_init (* font_btn, widget); +} + +/* WIDGET_ENTRY */ + +static void create_entry (const PreferencesWidget * widget, GtkWidget * * label, + GtkWidget * * entry, const char * domain) +{ + * entry = gtk_entry_new (); + gtk_entry_set_visibility ((GtkEntry *) * entry, ! widget->data.entry.password); + + if (widget->label) + { + * label = gtk_label_new (dgettext (domain, widget->label)); + gtk_misc_set_alignment ((GtkMisc *) * label, 1, 0.5); + } + + widget_init (* entry, widget); +} + +/* WIDGET_COMBO_BOX */ + +static void combobox_update (GtkWidget * combobox, const PreferencesWidget * widget) +{ + auto domain = (const char *) g_object_get_data ((GObject *) combobox, "combodomain"); + + ArrayRef<ComboItem> items = widget->data.combo.elems; + if (widget->data.combo.fill) + items = widget->data.combo.fill (); + + g_object_set_data ((GObject *) combobox, "comboitems", (void *) items.data); + + /* no gtk_combo_box_text_clear()? */ + gtk_list_store_clear ((GtkListStore *) gtk_combo_box_get_model ((GtkComboBox *) combobox)); + + for (const ComboItem & item : items) + gtk_combo_box_text_append_text ((GtkComboBoxText *) combobox, + dgettext (domain, item.label)); + + if (widget->cfg.type == WidgetConfig::Int) + { + int num = widget->cfg.get_int (); + + for (int i = 0; i < items.len; i ++) + { + if (items.data[i].num == num) + { + gtk_combo_box_set_active ((GtkComboBox *) combobox, i); + break; + } + } + } + else if (widget->cfg.type == WidgetConfig::String) + { + String str = widget->cfg.get_string (); + + for (int i = 0; i < items.len; i ++) + { + if (! strcmp_safe (items.data[i].str, str)) + { + gtk_combo_box_set_active ((GtkComboBox *) combobox, i); + break; + } + } + } +} + +static void create_cbox (const PreferencesWidget * widget, GtkWidget * * label, + GtkWidget * * combobox, const char * domain) +{ + * combobox = gtk_combo_box_text_new (); + + if (widget->label) + * label = gtk_label_new (dgettext (domain, widget->label)); + + g_object_set_data ((GObject *) * combobox, "combodomain", (void *) domain); + widget_init (* combobox, widget); +} + +/* WIDGET_TABLE */ + +static void fill_table (GtkWidget * table, + ArrayRef<PreferencesWidget> widgets, const char * domain) +{ + for (const PreferencesWidget & w : widgets) + { + GtkWidget * widget_left = nullptr, * widget_middle = nullptr, * widget_right = nullptr; + GtkAttachOptions middle_policy = (GtkAttachOptions) (GTK_FILL); + + switch (w.type) + { + case PreferencesWidget::SpinButton: + create_spin_button (& w, & widget_left, + & widget_middle, & widget_right, domain); + break; + + case PreferencesWidget::Label: + create_label (& w, & widget_middle, & widget_left, domain); + break; + + case PreferencesWidget::FontButton: + create_font_btn (& w, & widget_left, & widget_middle, domain); + middle_policy = (GtkAttachOptions) (GTK_EXPAND | GTK_FILL); + break; + + case PreferencesWidget::Entry: + create_entry (& w, & widget_left, & widget_middle, domain); + middle_policy = (GtkAttachOptions) (GTK_EXPAND | GTK_FILL); + break; + + case PreferencesWidget::ComboBox: + create_cbox (& w, & widget_left, & widget_middle, domain); + middle_policy = (GtkAttachOptions) (GTK_EXPAND | GTK_FILL); + break; + + default: + break; + } + + int i = & w - widgets.data; + + if (widget_left) + gtk_table_attach ((GtkTable *) table, widget_left, 0, 1, i, i + 1, + GTK_FILL, GTK_FILL, 0, 0); + + if (widget_middle) + gtk_table_attach ((GtkTable *) table, widget_middle, 1, 2, i, i + 1, + middle_policy, GTK_FILL, 0, 0); + + if (widget_right) + gtk_table_attach ((GtkTable *) table, widget_right, 2, 3, i, i + 1, + GTK_FILL, GTK_FILL, 0, 0); + } +} + +/* ALL WIDGETS */ + +/* box: a GtkBox */ +void audgui_create_widgets_with_domain (GtkWidget * box, + ArrayRef<PreferencesWidget> widgets, const char * domain) +{ + GtkWidget * widget = nullptr, * child_box = nullptr; + GSList * radio_btn_group = nullptr; + + for (const PreferencesWidget & w : widgets) + { + GtkWidget * label = nullptr; + + if (widget && w.child) + { + if (! child_box) + { + child_box = gtk_vbox_new (false, 0); + g_object_set_data ((GObject *) widget, "child", child_box); + + GtkWidget * alignment = gtk_alignment_new (0.5, 0.5, 1, 1); + gtk_box_pack_start ((GtkBox *) box, alignment, false, false, 0); + gtk_alignment_set_padding ((GtkAlignment *) alignment, 0, 0, 12, 0); + gtk_container_add ((GtkContainer *) alignment, child_box); + + if (GTK_IS_TOGGLE_BUTTON (widget)) + gtk_widget_set_sensitive (child_box, + gtk_toggle_button_get_active ((GtkToggleButton *) widget)); + } + } + else + child_box = nullptr; + + GtkWidget * alignment = gtk_alignment_new (0.5, 0.5, 1, 1); + gtk_alignment_set_padding ((GtkAlignment *) alignment, 6, 0, 12, 0); + gtk_box_pack_start ((GtkBox *) (child_box ? child_box : box), alignment, false, false, 0); + + widget = nullptr; + + if (radio_btn_group && w.type != PreferencesWidget::RadioButton) + radio_btn_group = nullptr; + + switch (w.type) + { + case PreferencesWidget::Button: + widget = audgui_button_new (dgettext (domain, w.label), + w.data.button.icon, (AudguiCallback) w.data.button.callback, nullptr); + break; + + case PreferencesWidget::CheckButton: + widget = gtk_check_button_new_with_mnemonic (dgettext (domain, w.label)); + widget_init (widget, & w); + break; + + case PreferencesWidget::Label: + { + if (strstr (w.label, "<b>")) + gtk_alignment_set_padding ((GtkAlignment *) alignment, + (& w == widgets.data) ? 0 : 12, 0, 0, 0); + + GtkWidget * icon = nullptr; + create_label (& w, & label, & icon, domain); + + if (icon) + { + widget = gtk_hbox_new (false, 6); + gtk_box_pack_start ((GtkBox *) widget, icon, false, false, 0); + gtk_box_pack_start ((GtkBox *) widget, label, false, false, 0); + } + else + widget = label; + + break; + } + + case PreferencesWidget::RadioButton: + widget = gtk_radio_button_new_with_mnemonic (radio_btn_group, + dgettext (domain, w.label)); + radio_btn_group = gtk_radio_button_get_group ((GtkRadioButton *) widget); + widget_init (widget, & w); + break; + + case PreferencesWidget::SpinButton: + { + widget = gtk_hbox_new (false, 6); + + GtkWidget * label_pre = nullptr, * spin_btn = nullptr, * label_past = nullptr; + create_spin_button (& w, & label_pre, & spin_btn, & label_past, domain); + + if (label_pre) + gtk_box_pack_start ((GtkBox *) widget, label_pre, false, false, 0); + if (spin_btn) + gtk_box_pack_start ((GtkBox *) widget, spin_btn, false, false, 0); + if (label_past) + gtk_box_pack_start ((GtkBox *) widget, label_past, false, false, 0); + + break; + } + + case PreferencesWidget::CustomGTK: + if (w.data.populate) + widget = (GtkWidget *) w.data.populate (); + + break; + + case PreferencesWidget::FontButton: + { + widget = gtk_hbox_new (false, 6); + + GtkWidget * font_btn = nullptr; + create_font_btn (& w, & label, & font_btn, domain); + + if (label) + gtk_box_pack_start ((GtkBox *) widget, label, false, false, 0); + if (font_btn) + gtk_box_pack_start ((GtkBox *) widget, font_btn, false, false, 0); + + break; + } + + case PreferencesWidget::Table: + widget = gtk_table_new (0, 0, false); + gtk_table_set_col_spacings ((GtkTable *) widget, 6); + gtk_table_set_row_spacings ((GtkTable *) widget, 6); + + fill_table (widget, w.data.table.widgets, domain); + + break; + + case PreferencesWidget::Entry: + { + widget = gtk_hbox_new (false, 6); + + GtkWidget * entry = nullptr; + create_entry (& w, & label, & entry, domain); + + if (label) + gtk_box_pack_start ((GtkBox *) widget, label, false, false, 0); + if (entry) + gtk_box_pack_start ((GtkBox *) widget, entry, true, true, 0); + + break; + } + + case PreferencesWidget::ComboBox: + { + widget = gtk_hbox_new (false, 6); + + GtkWidget * combo = nullptr; + create_cbox (& w, & label, & combo, domain); + + if (label) + gtk_box_pack_start ((GtkBox *) widget, label, false, false, 0); + if (combo) + gtk_box_pack_start ((GtkBox *) widget, combo, false, false, 0); + + break; + } + + case PreferencesWidget::Box: + if (w.data.box.horizontal) + widget = gtk_hbox_new (false, 6); + else + widget = gtk_vbox_new (false, 0); + + audgui_create_widgets_with_domain (widget, w.data.box.widgets, domain); + + if (w.data.box.frame) + { + GtkWidget * frame = gtk_frame_new (dgettext (domain, w.label)); + gtk_container_add ((GtkContainer *) frame, widget); + widget = frame; + } + + break; + + case PreferencesWidget::Notebook: + gtk_alignment_set_padding ((GtkAlignment *) alignment, 0, 0, 0, 0); + + widget = gtk_notebook_new (); + + for (const NotebookTab & tab : w.data.notebook.tabs) + { + GtkWidget * vbox = gtk_vbox_new (false, 0); + gtk_container_set_border_width ((GtkContainer *) vbox, 6); + + audgui_create_widgets_with_domain (vbox, tab.widgets, domain); + + gtk_notebook_append_page ((GtkNotebook *) widget, vbox, + gtk_label_new (dgettext (domain, tab.name))); + } + + break; + + case PreferencesWidget::Separator: + widget = w.data.separator.horizontal ? + gtk_hseparator_new () : gtk_vseparator_new (); + break; + + default: + break; + } + + if (widget) + { + /* use uniform spacing for horizontal boxes */ + if (gtk_orientable_get_orientation ((GtkOrientable *) box) == + GTK_ORIENTATION_HORIZONTAL) + gtk_alignment_set_padding ((GtkAlignment *) alignment, 0, 0, 0, 0); + + gtk_container_add ((GtkContainer *) alignment, widget); + } + } +} diff --git a/src/libaudgui/prefs-window.cc b/src/libaudgui/prefs-window.cc new file mode 100644 index 0000000..90269a1 --- /dev/null +++ b/src/libaudgui/prefs-window.cc @@ -0,0 +1,814 @@ +/* + * prefs-window.c + * Copyright 2006-2014 William Pitcock, Tomasz MoĆ, Michael FĂ€rber, and + * John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <string.h> + +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> + +#include <libaudcore/audstrings.h> +#include <libaudcore/drct.h> +#include <libaudcore/hook.h> +#include <libaudcore/i18n.h> +#include <libaudcore/playlist.h> +#include <libaudcore/plugin.h> +#include <libaudcore/plugins.h> +#include <libaudcore/preferences.h> +#include <libaudcore/runtime.h> + +#include "internal.h" +#include "libaudgui.h" +#include "libaudgui-gtk.h" + +#ifdef USE_CHARDET +#include <libguess.h> +#endif + +enum CategoryViewCols { + CATEGORY_VIEW_COL_ICON, + CATEGORY_VIEW_COL_NAME, + CATEGORY_VIEW_N_COLS +}; + +struct Category { + const char * icon_path; + const char * name; +}; + +struct PluginCategory { + PluginType type; + const char * name; +}; + +struct TitleFieldTag { + const char * name; + const char * tag; +}; + +static const char aud_version_string[] = + "<span size='small'>Audacious " VERSION " (" BUILDSTAMP ")</span>"; + +static GtkWidget * prefswin; +static GtkWidget * category_treeview, * category_notebook, * plugin_notebook; +static GtkWidget * titlestring_entry; + +enum { + CATEGORY_APPEARANCE = 0, + CATEGORY_AUDIO, + CATEGORY_NETWORK, + CATEGORY_PLAYLIST, + CATEGORY_SONG_INFO, + CATEGORY_PLUGINS +}; + +static const Category categories[] = { + { "appearance.png", N_("Appearance") }, + { "audio.png", N_("Audio") }, + { "connectivity.png", N_("Network") }, + { "playlist.png", N_("Playlist")} , + { "info.png", N_("Song Info") }, + { "plugins.png", N_("Plugins") } +}; + +static const PluginCategory plugin_categories[] = { + { PluginType::General, N_("General") }, + { PluginType::Effect, N_("Effect") }, + { PluginType::Vis, N_("Visualization") }, + { PluginType::Input, N_("Input") }, + { PluginType::Playlist, N_("Playlist") }, + { PluginType::Transport, N_("Transport") } +}; + +static const TitleFieldTag title_field_tags[] = { + { N_("Artist") , "${artist}" }, + { N_("Album") , "${album}" }, + { N_("Title") , "${title}" }, + { N_("Track number"), "${track-number}" }, + { N_("Genre") , "${genre}" }, + { N_("File name") , "${file-name}" }, + { N_("File path") , "${file-path}" }, + { N_("Date") , "${date}" }, + { N_("Year") , "${year}" }, + { N_("Comment") , "${comment}" }, + { N_("Codec") , "${codec}" }, + { N_("Quality") , "${quality}" } +}; + +#ifdef USE_CHARDET +static const ComboItem chardet_detector_presets[] = { + ComboItem (N_("None"), ""), + ComboItem (N_("Arabic"), GUESS_REGION_AR), + ComboItem (N_("Baltic"), GUESS_REGION_BL), + ComboItem (N_("Chinese"), GUESS_REGION_CN), + ComboItem (N_("Greek"), GUESS_REGION_GR), + ComboItem (N_("Hebrew"), GUESS_REGION_HW), + ComboItem (N_("Japanese"), GUESS_REGION_JP), + ComboItem (N_("Korean"), GUESS_REGION_KR), + ComboItem (N_("Polish"), GUESS_REGION_PL), + ComboItem (N_("Russian"), GUESS_REGION_RU), + ComboItem (N_("Taiwanese"), GUESS_REGION_TW), + ComboItem (N_("Turkish"), GUESS_REGION_TR) +}; +#endif + +static const ComboItem bitdepth_elements[] = { + ComboItem ("16", 16), + ComboItem ("24", 24), + ComboItem ("32", 32), + ComboItem (N_("Floating point"), 0) +}; + +static Index<ComboItem> iface_combo_elements; +static int iface_combo_selected; +static GtkWidget * iface_prefs_box; + +static ArrayRef<ComboItem> iface_combo_fill (); +static void iface_combo_changed (); +static void * iface_create_prefs_box (); + +static const PreferencesWidget appearance_page_widgets[] = { + WidgetLabel (N_("<b>Interface Settings</b>")), + WidgetCombo (N_("Interface plugin:"), + WidgetInt (iface_combo_selected, iface_combo_changed), + {0, iface_combo_fill}), + WidgetCustomGTK (iface_create_prefs_box) +}; + +static Index<ComboItem> output_combo_elements; +static int output_combo_selected; +static GtkWidget * output_config_button; +static GtkWidget * output_about_button; + +static ArrayRef<ComboItem> output_combo_fill (); +static void output_combo_changed (); +static void * output_create_config_button (); +static void * output_create_about_button (); +static void output_bit_depth_changed (); + +static const PreferencesWidget output_combo_widgets[] = { + WidgetCombo (N_("Output plugin:"), + WidgetInt (output_combo_selected, output_combo_changed), + {0, output_combo_fill}), + WidgetCustomGTK (output_create_config_button), + WidgetCustomGTK (output_create_about_button) +}; + +static const PreferencesWidget audio_page_widgets[] = { + WidgetLabel (N_("<b>Output Settings</b>")), + WidgetBox ({{output_combo_widgets}, true}), + WidgetCombo (N_("Bit depth:"), + WidgetInt (0, "output_bit_depth", output_bit_depth_changed), + {{bitdepth_elements}}), + WidgetSpin (N_("Buffer size:"), + WidgetInt (0, "output_buffer_size"), + {100, 10000, 1000, N_("ms")}), + WidgetCheck (N_("Soft clipping"), + WidgetBool (0, "soft_clipping")), + WidgetCheck (N_("Use software volume control (not recommended)"), + WidgetBool (0, "software_volume_control")), + WidgetLabel (N_("<b>Replay Gain</b>")), + WidgetCheck (N_("Enable Replay Gain"), + WidgetBool (0, "enable_replay_gain")), + WidgetCheck (N_("Album mode"), + WidgetBool (0, "replay_gain_album"), + WIDGET_CHILD), + WidgetCheck (N_("Prevent clipping (recommended)"), + WidgetBool (0, "enable_clipping_prevention"), + WIDGET_CHILD), + WidgetLabel (N_("<b>Adjust Levels</b>"), + WIDGET_CHILD), + WidgetSpin (N_("Amplify all files:"), + WidgetFloat (0, "replay_gain_preamp"), + {-15, 15, 0.1, N_("dB")}, + WIDGET_CHILD), + WidgetSpin (N_("Amplify untagged files:"), + WidgetFloat (0, "default_gain"), + {-15, 15, 0.1, N_("dB")}, + WIDGET_CHILD) +}; + +static const PreferencesWidget proxy_host_port_elements[] = { + WidgetEntry (N_("Proxy hostname:"), + WidgetString (0, "proxy_host")), + WidgetEntry (N_("Proxy port:"), + WidgetString (0, "proxy_port")) +}; + +static const PreferencesWidget proxy_auth_elements[] = { + WidgetEntry (N_("Proxy username:"), + WidgetString (0, "proxy_user")), + WidgetEntry (N_("Proxy password:"), + WidgetString (0, "proxy_pass"), + {true}) +}; + +static const PreferencesWidget connectivity_page_widgets[] = { + WidgetLabel (N_("<b>Network Settings</b>")), + WidgetSpin (N_("Buffer size:"), + WidgetInt (0, "net_buffer_kb"), + {16, 1024, 16, N_("KiB")}), + WidgetLabel (N_("<b>Proxy Configuration</b>")), + WidgetCheck (N_("Enable proxy usage"), + WidgetBool (0, "use_proxy")), + WidgetTable ({{proxy_host_port_elements}}, + WIDGET_CHILD), + WidgetCheck (N_("Use authentication with proxy"), + WidgetBool (0, "use_proxy_auth")), + WidgetTable ({{proxy_auth_elements}}, + WIDGET_CHILD) +}; + +static const PreferencesWidget chardet_elements[] = { +#ifdef USE_CHARDET + WidgetCombo (N_("Auto character encoding detector for:"), + WidgetString (0, "chardet_detector"), + {{chardet_detector_presets}}), +#endif + WidgetEntry (N_("Fallback character encodings:"), + WidgetString (0, "chardet_fallback")) +}; + +static void send_title_change (); +static void * create_titlestring_table (); + +static const PreferencesWidget playlist_page_widgets[] = { + WidgetLabel (N_("<b>Behavior</b>")), + WidgetCheck (N_("Resume playback on startup"), + WidgetBool (0, "resume_playback_on_startup")), + WidgetCheck (N_("Pause instead of resuming immediately"), + WidgetBool (0, "always_resume_paused"), + WIDGET_CHILD), + WidgetCheck (N_("Advance when the current song is deleted"), + WidgetBool (0, "advance_on_delete")), + WidgetCheck (N_("Clear the playlist when opening files"), + WidgetBool (0, "clear_playlist")), + WidgetCheck (N_("Open files in a temporary playlist"), + WidgetBool (0, "open_to_temporary")), + WidgetLabel (N_("<b>Compatibility</b>")), + WidgetCheck (N_("Interpret \\ (backward slash) as a folder delimiter"), + WidgetBool (0, "convert_backslash")), + WidgetTable ({{chardet_elements}}), + WidgetLabel (N_("<b>Song Display</b>")), + WidgetCheck (N_("Show song numbers"), + WidgetBool (0, "show_numbers_in_pl", send_title_change)), + WidgetCheck (N_("Show leading zeroes (02:00 instead of 2:00)"), + WidgetBool (0, "leading_zero", send_title_change)), + WidgetCustomGTK (create_titlestring_table), + WidgetLabel (N_("<b>Advanced</b>")), + WidgetCheck (N_("Do not load metadata for songs until played"), + WidgetBool (0, "metadata_on_play")), + WidgetCheck (N_("Probe content of files with no recognized file name extension"), + WidgetBool (0, "slow_probe")) +}; + +static const PreferencesWidget song_info_page_widgets[] = { + WidgetLabel (N_("<b>Album Art</b>")), + WidgetLabel (N_("Search for images matching these words (comma-separated):")), + WidgetEntry (0, WidgetString (0, "cover_name_include")), + WidgetLabel (N_("Exclude images matching these words (comma-separated):")), + WidgetEntry (0, WidgetString (0, "cover_name_exclude")), + WidgetCheck (N_("Search for images matching song file name"), + WidgetBool (0, "use_file_cover")), + WidgetCheck (N_("Search recursively"), + WidgetBool (0, "recurse_for_cover")), + WidgetSpin (N_("Search depth:"), + WidgetInt (0, "recurse_for_cover_depth"), + {0, 100, 1}, + WIDGET_CHILD), + WidgetLabel (N_("<b>Popup Information</b>")), + WidgetCheck (N_("Show popup information"), + WidgetBool (0, "show_filepopup_for_tuple")), + WidgetSpin (N_("Popup delay (tenths of a second):"), + WidgetInt (0, "filepopup_delay"), + {0, 100, 1}, + WIDGET_CHILD), + WidgetCheck (N_("Show time scale for current song"), + WidgetBool (0, "filepopup_showprogressbar"), + WIDGET_CHILD) +}; + +#define TITLESTRING_NPRESETS 6 + +static const char * const titlestring_presets[TITLESTRING_NPRESETS] = { + "${title}", + "${?artist:${artist} - }${title}", + "${?artist:${artist} - }${?album:${album} - }${title}", + "${?artist:${artist} - }${?album:${album} - }${?track-number:${track-number}. }${title}", + "${?artist:${artist} }${?album:[ ${album} ] }${?artist:- }${?track-number:${track-number}. }${title}", + "${?album:${album} - }${title}" +}; + +static const char * const titlestring_preset_names[TITLESTRING_NPRESETS] = { + N_("TITLE"), + N_("ARTIST - TITLE"), + N_("ARTIST - ALBUM - TITLE"), + N_("ARTIST - ALBUM - TRACK. TITLE"), + N_("ARTIST [ ALBUM ] - TRACK. TITLE"), + N_("ALBUM - TITLE") +}; + +static Index<ComboItem> fill_plugin_combo (PluginType type) +{ + Index<ComboItem> elems; + int i = 0; + + for (PluginHandle * plugin : aud_plugin_list (type)) + elems.append (aud_plugin_get_name (plugin), i ++); + + return elems; +} + +static void change_category (int category) +{ + if (aud_get_headless_mode () && category > CATEGORY_APPEARANCE) + category --; + + GtkTreeSelection * selection = gtk_tree_view_get_selection ((GtkTreeView *) category_treeview); + GtkTreePath * path = gtk_tree_path_new_from_indices (category, -1); + gtk_tree_selection_select_path (selection, path); + gtk_tree_path_free (path); +} + +static void category_changed (GtkTreeSelection * selection) +{ + GtkTreeModel * model; + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected (selection, & model, & iter)) + { + GtkTreePath * path = gtk_tree_model_get_path (model, & iter); + int category = gtk_tree_path_get_indices (path)[0]; + gtk_notebook_set_current_page ((GtkNotebook *) category_notebook, category); + gtk_tree_path_free (path); + } +} + +static void send_title_change () +{ + if (aud_drct_get_ready ()) + hook_call ("title change", nullptr); +} + +static void titlestring_tag_menu_cb (GtkMenuItem * menuitem, void * data) +{ + const char * separator = " - "; + auto tag = (const TitleFieldTag *) data; + int pos = gtk_editable_get_position ((GtkEditable *) titlestring_entry); + + /* insert separator as needed */ + if (gtk_entry_get_text ((GtkEntry *) titlestring_entry)[0]) + gtk_editable_insert_text ((GtkEditable *) titlestring_entry, separator, -1, & pos); + + gtk_editable_insert_text ((GtkEditable *) titlestring_entry, _(tag->tag), -1, & pos); + gtk_editable_set_position ((GtkEditable *) titlestring_entry, pos); +} + +static void on_titlestring_help_button_clicked (GtkButton * button, void * menu) +{ + gtk_menu_popup ((GtkMenu *) menu, nullptr, nullptr, nullptr, nullptr, 0, GDK_CURRENT_TIME); +} + +static void update_titlestring_cbox (GtkComboBox * cbox, const char * format) +{ + int preset; + for (preset = 0; preset < TITLESTRING_NPRESETS; preset ++) + { + if (! strcmp (titlestring_presets[preset], format)) + break; + } + + if (gtk_combo_box_get_active (cbox) != preset) + gtk_combo_box_set_active (cbox, preset); +} + +static void on_titlestring_entry_changed (GtkEntry * entry, GtkComboBox * cbox) +{ + const char * format = gtk_entry_get_text (entry); + aud_set_str (nullptr, "generic_title_format", format); + update_titlestring_cbox (cbox, format); +} + +static void on_titlestring_cbox_changed (GtkComboBox * cbox, GtkEntry * entry) +{ + int preset = gtk_combo_box_get_active (cbox); + if (preset < TITLESTRING_NPRESETS) + gtk_entry_set_text (entry, titlestring_presets[preset]); +} + +static void fill_category_list (GtkTreeView * treeview, GtkNotebook * notebook) +{ + GtkTreeViewColumn * column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, _("Category")); + gtk_tree_view_append_column (treeview, column); + gtk_tree_view_column_set_spacing (column, 2); + + GtkCellRenderer * renderer = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, renderer, false); + gtk_tree_view_column_set_attributes (column, renderer, "pixbuf", 0, nullptr); + + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, renderer, false); + gtk_tree_view_column_set_attributes (column, renderer, "text", 1, nullptr); + + g_object_set ((GObject *) renderer, "wrap-width", 106, "wrap-mode", + PANGO_WRAP_WORD_CHAR, nullptr); + + GtkListStore * store = gtk_list_store_new (CATEGORY_VIEW_N_COLS, + GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_INT); + gtk_tree_view_set_model (treeview, (GtkTreeModel *) store); + + const char * data_dir = aud_get_path (AudPath::DataDir); + + for (const Category & category : categories) + { + if (& category == & categories[CATEGORY_APPEARANCE] && aud_get_headless_mode ()) + continue; + + GtkTreeIter iter; + gtk_list_store_append (store, & iter); + gtk_list_store_set (store, & iter, CATEGORY_VIEW_COL_NAME, + gettext (category.name), -1); + + StringBuf path = filename_build ({data_dir, "images", category.icon_path}); + GdkPixbuf * img = gdk_pixbuf_new_from_file (path, nullptr); + + if (img) + { + gtk_list_store_set (store, & iter, CATEGORY_VIEW_COL_ICON, img, -1); + g_object_unref (img); + } + } + + g_object_unref (store); + + GtkTreeSelection * selection = gtk_tree_view_get_selection (treeview); + g_signal_connect (selection, "changed", (GCallback) category_changed, nullptr); +} + +static GtkWidget * create_titlestring_tag_menu () +{ + GtkWidget * titlestring_tag_menu = gtk_menu_new (); + + for (const TitleFieldTag & tag : title_field_tags) + { + GtkWidget * menu_item = gtk_menu_item_new_with_label (_(tag.name)); + gtk_menu_shell_append ((GtkMenuShell *) titlestring_tag_menu, menu_item); + g_signal_connect (menu_item, "activate", + (GCallback) titlestring_tag_menu_cb, (void *) & tag); + } + + gtk_widget_show_all (titlestring_tag_menu); + + return titlestring_tag_menu; +} + +static void create_titlestring_widgets (GtkWidget * * cbox, GtkWidget * * entry) +{ + * cbox = gtk_combo_box_text_new (); + for (int i = 0; i < TITLESTRING_NPRESETS; i ++) + gtk_combo_box_text_append_text ((GtkComboBoxText *) * cbox, _(titlestring_preset_names[i])); + gtk_combo_box_text_append_text ((GtkComboBoxText *) * cbox, _("Custom")); + + * entry = gtk_entry_new (); + + String format = aud_get_str (nullptr, "generic_title_format"); + update_titlestring_cbox ((GtkComboBox *) * cbox, format); + gtk_entry_set_text ((GtkEntry *) * entry, format); + + g_signal_connect (* cbox, "changed", (GCallback) on_titlestring_cbox_changed, * entry); + g_signal_connect (* entry, "changed", (GCallback) on_titlestring_entry_changed, * cbox); +} + +static void * create_titlestring_table () +{ + GtkWidget * grid = gtk_table_new (0, 0, false); + gtk_table_set_row_spacings ((GtkTable *) grid, 6); + gtk_table_set_col_spacings ((GtkTable *) grid, 6); + + GtkWidget * label = gtk_label_new (_("Title format:")); + gtk_misc_set_alignment ((GtkMisc *) label, 1, 0.5); + gtk_table_attach ((GtkTable *) grid, label, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + + label = gtk_label_new (_("Custom string:")); + gtk_misc_set_alignment ((GtkMisc *) label, 1, 0.5); + gtk_table_attach ((GtkTable *) grid, label, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0); + + GtkWidget * titlestring_cbox; + create_titlestring_widgets (& titlestring_cbox, & titlestring_entry); + gtk_table_attach_defaults ((GtkTable *) grid, titlestring_cbox, 1, 2, 0, 1); + gtk_table_attach_defaults ((GtkTable *) grid, titlestring_entry, 1, 2, 1, 2); + + GtkWidget * titlestring_help_button = gtk_button_new (); + gtk_widget_set_can_focus (titlestring_help_button, false); + gtk_button_set_focus_on_click ((GtkButton *) titlestring_help_button, false); + gtk_button_set_relief ((GtkButton *) titlestring_help_button, GTK_RELIEF_HALF); + gtk_table_attach ((GtkTable *) grid, titlestring_help_button, 2, 3, 1, 2, + GTK_FILL, GTK_FILL, 0, 0); + + GtkWidget * titlestring_tag_menu = create_titlestring_tag_menu (); + + g_signal_connect (titlestring_help_button, "clicked", + (GCallback) on_titlestring_help_button_clicked, titlestring_tag_menu); + + GtkWidget * image = gtk_image_new_from_icon_name ("list-add", GTK_ICON_SIZE_BUTTON); + gtk_container_add ((GtkContainer *) titlestring_help_button, image); + + return grid; +} + +static void create_playlist_category () +{ + GtkWidget * vbox = gtk_vbox_new (false, 0); + gtk_container_add ((GtkContainer *) category_notebook, vbox); + audgui_create_widgets (vbox, playlist_page_widgets); +} + +static void create_song_info_category () +{ + GtkWidget * vbox = gtk_vbox_new (false, 0); + gtk_container_add ((GtkContainer *) category_notebook, vbox); + audgui_create_widgets (vbox, song_info_page_widgets); +} + +static void iface_fill_prefs_box () +{ + Plugin * header = (Plugin *) aud_plugin_get_header (aud_plugin_get_current (PluginType::Iface)); + if (header && header->info.prefs) + audgui_create_widgets_with_domain (iface_prefs_box, + header->info.prefs->widgets, header->info.domain); +} + +static int iface_combo_changed_finish (void *) +{ + iface_fill_prefs_box (); + gtk_widget_show_all (iface_prefs_box); + + audgui_cleanup (); + + return G_SOURCE_REMOVE; +} + +static void iface_combo_changed () +{ + /* prevent audgui from being shut down during the switch */ + audgui_init (); + + gtk_container_foreach ((GtkContainer *) iface_prefs_box, + (GtkCallback) gtk_widget_destroy, nullptr); + + aud_plugin_enable (aud_plugin_list (PluginType::Iface)[iface_combo_selected], true); + + /* now wait till we have restarted into the new main loop */ + g_idle_add_full (G_PRIORITY_HIGH, iface_combo_changed_finish, nullptr, nullptr); +} + +static ArrayRef<ComboItem> iface_combo_fill () +{ + if (! iface_combo_elements.len ()) + { + iface_combo_elements = fill_plugin_combo (PluginType::Iface); + iface_combo_selected = aud_plugin_list (PluginType::Iface). + find (aud_plugin_get_current (PluginType::Iface)); + } + + return {iface_combo_elements.begin (), iface_combo_elements.len ()}; +} + +static void * iface_create_prefs_box () +{ + iface_prefs_box = gtk_vbox_new (false, 0); + iface_fill_prefs_box (); + return iface_prefs_box; +} + +static void create_appearance_category () +{ + GtkWidget * vbox = gtk_vbox_new (false, 0); + gtk_container_add ((GtkContainer *) category_notebook, vbox); + audgui_create_widgets (vbox, appearance_page_widgets); +} + +static void output_combo_changed () +{ + PluginHandle * plugin = aud_plugin_list (PluginType::Output)[output_combo_selected]; + + if (aud_plugin_enable (plugin, true)) + { + gtk_widget_set_sensitive (output_config_button, aud_plugin_has_configure (plugin)); + gtk_widget_set_sensitive (output_about_button, aud_plugin_has_about (plugin)); + } +} + +static ArrayRef<ComboItem> output_combo_fill () +{ + if (! output_combo_elements.len ()) + { + output_combo_elements = fill_plugin_combo (PluginType::Output); + output_combo_selected = aud_plugin_list (PluginType::Output) + .find (aud_plugin_get_current (PluginType::Output)); + } + + return {output_combo_elements.begin (), output_combo_elements.len ()}; +} + +static void output_bit_depth_changed () +{ + aud_output_reset (OutputReset::ReopenStream); +} + +static void output_do_config (void *) +{ + audgui_show_plugin_prefs (aud_plugin_get_current (PluginType::Output)); +} + +static void output_do_about (void *) +{ + audgui_show_plugin_about (aud_plugin_get_current (PluginType::Output)); +} + +static void * output_create_config_button () +{ + gboolean enabled = aud_plugin_has_configure (aud_plugin_get_current (PluginType::Output)); + + output_config_button = audgui_button_new (_("_Settings"), + "preferences-system", output_do_config, nullptr); + gtk_widget_set_sensitive (output_config_button, enabled); + + return output_config_button; +} + +static void * output_create_about_button () +{ + gboolean enabled = aud_plugin_has_about (aud_plugin_get_current (PluginType::Output)); + + output_about_button = audgui_button_new (_("_About"), "help-about", output_do_about, nullptr); + gtk_widget_set_sensitive (output_about_button, enabled); + + return output_about_button; +} + +static void create_audio_category () +{ + GtkWidget * audio_page_vbox = gtk_vbox_new (false, 0); + audgui_create_widgets (audio_page_vbox, audio_page_widgets); + gtk_container_add ((GtkContainer *) category_notebook, audio_page_vbox); +} + +static void create_connectivity_category () +{ + GtkWidget * connectivity_page_vbox = gtk_vbox_new (false, 0); + gtk_container_add ((GtkContainer *) category_notebook, connectivity_page_vbox); + + GtkWidget * vbox = gtk_vbox_new (false, 0); + gtk_box_pack_start ((GtkBox *) connectivity_page_vbox, vbox, true, true, 0); + + audgui_create_widgets (vbox, connectivity_page_widgets); +} + +static void create_plugin_category () +{ + plugin_notebook = gtk_notebook_new (); + gtk_container_add ((GtkContainer *) category_notebook, plugin_notebook); + + for (const PluginCategory & category : plugin_categories) + gtk_notebook_append_page ((GtkNotebook *) plugin_notebook, + plugin_view_new (category.type), gtk_label_new (_(category.name))); +} + +static void destroy_cb () +{ + prefswin = nullptr; + category_treeview = nullptr; + category_notebook = nullptr; + titlestring_entry = nullptr; + + iface_combo_elements.clear (); + output_combo_elements.clear (); +} + +static void create_prefs_window () +{ + prefswin = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_type_hint ((GtkWindow *) prefswin, GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_container_set_border_width ((GtkContainer *) prefswin, 12); + gtk_window_set_title ((GtkWindow *) prefswin, _("Audacious Settings")); + gtk_window_set_default_size ((GtkWindow *) prefswin, 680, 400); + + GtkWidget * vbox = gtk_vbox_new (false, 0); + gtk_container_add ((GtkContainer *) prefswin, vbox); + + GtkWidget * hbox = gtk_hbox_new (false, 6); + gtk_box_pack_start ((GtkBox *) vbox, hbox, true, true, 0); + + GtkWidget * scrolledwindow = gtk_scrolled_window_new (nullptr, nullptr); + gtk_box_pack_start ((GtkBox *) hbox, scrolledwindow, false, false, 0); + gtk_scrolled_window_set_policy ((GtkScrolledWindow *) scrolledwindow, + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type ((GtkScrolledWindow *) scrolledwindow, GTK_SHADOW_IN); + + category_treeview = gtk_tree_view_new (); + gtk_container_add ((GtkContainer *) scrolledwindow, category_treeview); + gtk_widget_set_size_request (scrolledwindow, 168, -1); + gtk_tree_view_set_headers_visible ((GtkTreeView *) category_treeview, false); + + category_notebook = gtk_notebook_new (); + gtk_box_pack_start ((GtkBox *) hbox, category_notebook, true, true, 0); + + gtk_widget_set_can_focus (category_notebook, false); + gtk_notebook_set_show_tabs ((GtkNotebook *) category_notebook, false); + gtk_notebook_set_show_border ((GtkNotebook *) category_notebook, false); + + if (! aud_get_headless_mode ()) + create_appearance_category (); + + create_audio_category (); + create_connectivity_category (); + create_playlist_category (); + create_song_info_category (); + create_plugin_category (); + + GtkWidget * hseparator = gtk_hseparator_new (); + gtk_box_pack_start ((GtkBox *) vbox, hseparator, false, false, 6); + + hbox = gtk_hbox_new (false, 0); + gtk_box_pack_start ((GtkBox *) vbox, hbox, false, false, 0); + + GtkWidget * audversionlabel = gtk_label_new (aud_version_string); + gtk_box_pack_start ((GtkBox *) hbox, audversionlabel, false, false, 0); + gtk_label_set_use_markup ((GtkLabel *) audversionlabel, true); + + GtkWidget * prefswin_button_box = gtk_hbutton_box_new (); + gtk_box_pack_start ((GtkBox *) hbox, prefswin_button_box, true, true, 0); + gtk_button_box_set_layout ((GtkButtonBox *) prefswin_button_box, GTK_BUTTONBOX_END); + gtk_box_set_spacing ((GtkBox *) prefswin_button_box, 6); + + GtkWidget * close = audgui_button_new (_("_Close"), "window-close", + (AudguiCallback) gtk_widget_destroy, prefswin); + gtk_container_add ((GtkContainer *) prefswin_button_box, close); + gtk_widget_set_can_default (close, true); + + fill_category_list ((GtkTreeView *) category_treeview, (GtkNotebook *) category_notebook); + + gtk_widget_show_all (vbox); + + g_signal_connect (prefswin, "destroy", (GCallback) destroy_cb, nullptr); + + audgui_destroy_on_escape (prefswin); +} + +EXPORT void audgui_show_prefs_window () +{ + if (! prefswin) + create_prefs_window (); + + change_category (CATEGORY_APPEARANCE); + + gtk_window_present ((GtkWindow *) prefswin); +} + +EXPORT void audgui_show_prefs_for_plugin_type (PluginType type) +{ + if (! prefswin) + create_prefs_window (); + + if (type == PluginType::Iface) + change_category (CATEGORY_APPEARANCE); + else if (type == PluginType::Output) + change_category (CATEGORY_AUDIO); + else + { + change_category (CATEGORY_PLUGINS); + + for (const PluginCategory & category : plugin_categories) + { + if (category.type == type) + gtk_notebook_set_current_page ((GtkNotebook *) plugin_notebook, + & category - plugin_categories); + } + } + + gtk_window_present ((GtkWindow *) prefswin); +} + +EXPORT void audgui_hide_prefs_window () +{ + if (prefswin) + gtk_widget_destroy (prefswin); +} diff --git a/src/libaudgui/queue-manager.c b/src/libaudgui/queue-manager.cc index 8718365..4e6ad74 100644 --- a/src/libaudgui/queue-manager.c +++ b/src/libaudgui/queue-manager.cc @@ -20,18 +20,19 @@ #include <gdk/gdkkeysyms.h> #include <gtk/gtk.h> -#include <audacious/i18n.h> -#include <audacious/playlist.h> #include <libaudcore/hook.h> +#include <libaudcore/i18n.h> +#include <libaudcore/playlist.h> -#include "init.h" +#include "internal.h" #include "libaudgui.h" #include "libaudgui-gtk.h" #include "list.h" enum { - COLUMN_ENTRY, - COLUMN_TITLE}; + COLUMN_ENTRY, + COLUMN_TITLE +}; static void get_value (void * user, int row, int column, GValue * value) { @@ -44,26 +45,25 @@ static void get_value (void * user, int row, int column, GValue * value) g_value_set_int (value, 1 + entry); break; case COLUMN_TITLE:; - char * title = aud_playlist_entry_get_title (list, entry, TRUE); - g_value_set_string (value, title); - str_unref (title); + Tuple tuple = aud_playlist_entry_get_tuple (list, entry, Playlist::Guess); + g_value_set_string (value, tuple.get_str (Tuple::FormattedTitle)); break; } } -static bool_t get_selected (void * user, int row) +static bool get_selected (void * user, int row) { int list = aud_playlist_get_active (); return aud_playlist_entry_get_selected (list, aud_playlist_queue_get_entry (list, row)); } -static void set_selected (void * user, int row, bool_t selected) +static void set_selected (void * user, int row, bool selected) { int list = aud_playlist_get_active (); aud_playlist_entry_set_selected (list, aud_playlist_queue_get_entry (list, row), selected); } -static void select_all (void * user, bool_t selected) +static void select_all (void * user, bool selected) { int list = aud_playlist_get_active (); int count = aud_playlist_queue_count (list); @@ -74,7 +74,7 @@ static void select_all (void * user, bool_t selected) static void shift_rows (void * user, int row, int before) { - GArray * shift = g_array_new (FALSE, FALSE, sizeof (int)); + GArray * shift = g_array_new (false, false, sizeof (int)); int list = aud_playlist_get_active (); int count = aud_playlist_queue_count (list); @@ -93,20 +93,23 @@ static void shift_rows (void * user, int row, int before) aud_playlist_queue_delete_selected (list); - for (int i = 0; i < shift->len; i ++) + for (unsigned i = 0; i < shift->len; i ++) aud_playlist_queue_insert (list, before + i, g_array_index (shift, int, i)); - g_array_free (shift, TRUE); + g_array_free (shift, true); } static const AudguiListCallbacks callbacks = { - .get_value = get_value, - .get_selected = get_selected, - .set_selected = set_selected, - .select_all = select_all, - .shift_rows = shift_rows}; - -static void remove_selected (void * unused) + get_value, + get_selected, + set_selected, + select_all, + 0, // activate_row + 0, // right_click + shift_rows +}; + +static void remove_selected (void *) { int list = aud_playlist_get_active (); int count = aud_playlist_queue_count (list); @@ -118,7 +121,7 @@ static void remove_selected (void * unused) if (aud_playlist_entry_get_selected (list, entry)) { aud_playlist_queue_delete (list, i, 1); - aud_playlist_entry_set_selected (list, entry, FALSE); + aud_playlist_entry_set_selected (list, entry, false); count --; } else @@ -128,14 +131,14 @@ static void remove_selected (void * unused) static void update_hook (void * data, void * user) { - GtkWidget * qm_list = user; + GtkWidget * qm_list = (GtkWidget *) user; int oldrows = audgui_list_row_count (qm_list); int newrows = aud_playlist_queue_count (aud_playlist_get_active ()); int focus = audgui_list_get_focus (qm_list); - audgui_list_update_rows (qm_list, 0, MIN (oldrows, newrows)); - audgui_list_update_selection (qm_list, 0, MIN (oldrows, newrows)); + audgui_list_update_rows (qm_list, 0, aud::min (oldrows, newrows)); + audgui_list_update_selection (qm_list, 0, aud::min (oldrows, newrows)); if (newrows > oldrows) audgui_list_insert_rows (qm_list, oldrows, newrows - oldrows); @@ -146,27 +149,27 @@ static void update_hook (void * data, void * user) audgui_list_set_focus (qm_list, newrows - 1); } -static void destroy_cb (void) +static void destroy_cb () { hook_dissociate ("playlist activate", update_hook); hook_dissociate ("playlist update", update_hook); } -static bool_t keypress_cb (GtkWidget * widget, GdkEventKey * event) +static gboolean keypress_cb (GtkWidget * widget, GdkEventKey * event) { if (event->keyval == GDK_KEY_A && (event->state & GDK_CONTROL_MASK)) - select_all (NULL, TRUE); + select_all (nullptr, true); else if (event->keyval == GDK_KEY_Delete) - remove_selected (NULL); + remove_selected (nullptr); else if (event->keyval == GDK_KEY_Escape) gtk_widget_destroy (widget); else - return FALSE; + return false; - return TRUE; + return true; } -static GtkWidget * create_queue_manager (void) +static GtkWidget * create_queue_manager () { GtkWidget * qm_win = gtk_dialog_new (); gtk_window_set_title ((GtkWindow *) qm_win, _("Queue Manager")); @@ -174,18 +177,20 @@ static GtkWidget * create_queue_manager (void) GtkWidget * vbox = gtk_dialog_get_content_area ((GtkDialog *) qm_win); - GtkWidget * scrolled = gtk_scrolled_window_new (NULL, NULL); + GtkWidget * scrolled = gtk_scrolled_window_new (nullptr, nullptr); gtk_scrolled_window_set_shadow_type ((GtkScrolledWindow *) scrolled, GTK_SHADOW_IN); - gtk_box_pack_start ((GtkBox *) vbox, scrolled, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy ((GtkScrolledWindow *) scrolled, + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_box_pack_start ((GtkBox *) vbox, scrolled, true, true, 0); int count = aud_playlist_queue_count (aud_playlist_get_active ()); - GtkWidget * qm_list = audgui_list_new (& callbacks, NULL, count); - gtk_tree_view_set_headers_visible ((GtkTreeView *) qm_list, FALSE); - audgui_list_add_column (qm_list, NULL, 0, G_TYPE_INT, 7); - audgui_list_add_column (qm_list, NULL, 1, G_TYPE_STRING, -1); + GtkWidget * qm_list = audgui_list_new (& callbacks, nullptr, count); + gtk_tree_view_set_headers_visible ((GtkTreeView *) qm_list, false); + audgui_list_add_column (qm_list, nullptr, 0, G_TYPE_INT, 7); + audgui_list_add_column (qm_list, nullptr, 1, G_TYPE_STRING, -1); gtk_container_add ((GtkContainer *) scrolled, qm_list); - GtkWidget * button1 = audgui_button_new (_("_Unqueue"), "list-remove", remove_selected, NULL); + GtkWidget * button1 = audgui_button_new (_("_Unqueue"), "list-remove", remove_selected, nullptr); GtkWidget * button2 = audgui_button_new (_("_Close"), "window-close", (AudguiCallback) gtk_widget_destroy, qm_win); @@ -195,13 +200,13 @@ static GtkWidget * create_queue_manager (void) hook_associate ("playlist activate", update_hook, qm_list); hook_associate ("playlist update", update_hook, qm_list); - g_signal_connect (qm_win, "destroy", (GCallback) destroy_cb, NULL); - g_signal_connect (qm_win, "key-press-event", (GCallback) keypress_cb, NULL); + g_signal_connect (qm_win, "destroy", (GCallback) destroy_cb, nullptr); + g_signal_connect (qm_win, "key-press-event", (GCallback) keypress_cb, nullptr); return qm_win; } -EXPORT void audgui_queue_manager_show (void) +EXPORT void audgui_queue_manager_show () { if (! audgui_reshow_unique_window (AUDGUI_QUEUE_MANAGER_WINDOW)) audgui_show_unique_window (AUDGUI_QUEUE_MANAGER_WINDOW, create_queue_manager ()); diff --git a/src/libaudgui/scaled-image.c b/src/libaudgui/scaled-image.cc index 70adcce..41c804a 100644 --- a/src/libaudgui/scaled-image.c +++ b/src/libaudgui/scaled-image.cc @@ -21,10 +21,10 @@ static GdkPixbuf * get_scaled (GtkWidget * widget, int maxwidth, int maxheight) { - GdkPixbuf * unscaled = g_object_get_data ((GObject *) widget, "pixbuf-unscaled"); + GdkPixbuf * unscaled = (GdkPixbuf *) g_object_get_data ((GObject *) widget, "pixbuf-unscaled"); if (! unscaled) - return NULL; + return nullptr; int width = gdk_pixbuf_get_width (unscaled); int height = gdk_pixbuf_get_height (unscaled); @@ -43,7 +43,7 @@ static GdkPixbuf * get_scaled (GtkWidget * widget, int maxwidth, int maxheight) } } - GdkPixbuf * scaled = g_object_get_data ((GObject *) widget, "pixbuf-scaled"); + GdkPixbuf * scaled = (GdkPixbuf *) g_object_get_data ((GObject *) widget, "pixbuf-scaled"); if (scaled) { @@ -59,7 +59,7 @@ static GdkPixbuf * get_scaled (GtkWidget * widget, int maxwidth, int maxheight) return scaled; } -static bool_t draw_cb (GtkWidget * widget, cairo_t * cr) +static int expose_cb (GtkWidget * widget, GdkEventExpose * event) { GdkRectangle rect; gtk_widget_get_allocation (widget, & rect); @@ -70,43 +70,45 @@ static bool_t draw_cb (GtkWidget * widget, cairo_t * cr) { int x = (rect.width - gdk_pixbuf_get_width (scaled)) / 2; int y = (rect.height - gdk_pixbuf_get_height (scaled)) / 2; + + cairo_t * cr = gdk_cairo_create (gtk_widget_get_window (widget)); gdk_cairo_set_source_pixbuf (cr, scaled, x, y); cairo_paint (cr); + cairo_destroy (cr); } - return TRUE; + return true; } EXPORT void audgui_scaled_image_set (GtkWidget * widget, GdkPixbuf * pixbuf) { GdkPixbuf * old; - if ((old = g_object_get_data ((GObject *) widget, "pixbuf-unscaled"))) + if ((old = (GdkPixbuf *) g_object_get_data ((GObject *) widget, "pixbuf-unscaled"))) g_object_unref (old); - if ((old = g_object_get_data ((GObject *) widget, "pixbuf-scaled"))) + if ((old = (GdkPixbuf *) g_object_get_data ((GObject *) widget, "pixbuf-scaled"))) g_object_unref (old); if (pixbuf) g_object_ref (pixbuf); g_object_set_data ((GObject *) widget, "pixbuf-unscaled", pixbuf); - g_object_set_data ((GObject *) widget, "pixbuf-scaled", NULL); + g_object_set_data ((GObject *) widget, "pixbuf-scaled", nullptr); - if (! gtk_widget_in_destruction (widget)) - gtk_widget_queue_draw (widget); + gtk_widget_queue_draw (widget); } static void destroy_cb (GtkWidget * widget) { - audgui_scaled_image_set (widget, NULL); + audgui_scaled_image_set (widget, nullptr); } EXPORT GtkWidget * audgui_scaled_image_new (GdkPixbuf * pixbuf) { GtkWidget * widget = gtk_drawing_area_new (); - g_signal_connect (widget, "draw", (GCallback) draw_cb, NULL); - g_signal_connect (widget, "destroy", (GCallback) destroy_cb, NULL); + g_signal_connect (widget, "expose-event", (GCallback) expose_cb, nullptr); + g_signal_connect (widget, "destroy", (GCallback) destroy_cb, nullptr); audgui_scaled_image_set (widget, pixbuf); diff --git a/src/libaudgui/status.cc b/src/libaudgui/status.cc new file mode 100644 index 0000000..fa04fd3 --- /dev/null +++ b/src/libaudgui/status.cc @@ -0,0 +1,107 @@ +/* + * status.c + * Copyright 2014 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <gtk/gtk.h> + +#include <libaudcore/hook.h> +#include <libaudcore/i18n.h> + +#include "internal.h" +#include "libaudgui-gtk.h" + +static GtkWidget * progress_window; +static GtkWidget * progress_label, * progress_label_2; +static GtkWidget * error_window; + +static void create_progress_window () +{ + progress_window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_type_hint ((GtkWindow *) progress_window, GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_title ((GtkWindow *) progress_window, _("Working ...")); + gtk_window_set_resizable ((GtkWindow *) progress_window, false); + gtk_container_set_border_width ((GtkContainer *) progress_window, 6); + + GtkWidget * vbox = gtk_vbox_new (false, 6); + gtk_container_add ((GtkContainer *) progress_window, vbox); + + progress_label = gtk_label_new (nullptr); + gtk_label_set_width_chars ((GtkLabel *) progress_label, 40); + gtk_label_set_max_width_chars ((GtkLabel *) progress_label, 40); + gtk_label_set_ellipsize ((GtkLabel *) progress_label, PANGO_ELLIPSIZE_MIDDLE); + gtk_box_pack_start ((GtkBox *) vbox, progress_label, false, false, 0); + + progress_label_2 = gtk_label_new (nullptr); + gtk_label_set_width_chars ((GtkLabel *) progress_label_2, 40); + gtk_label_set_max_width_chars ((GtkLabel *) progress_label_2, 40); + gtk_label_set_ellipsize ((GtkLabel *) progress_label, PANGO_ELLIPSIZE_MIDDLE); + gtk_box_pack_start ((GtkBox *) vbox, progress_label_2, false, false, 0); + + gtk_widget_show_all (progress_window); + + g_signal_connect (progress_window, "destroy", + (GCallback) gtk_widget_destroyed, & progress_window); +} + +static void show_progress (void * data, void * user) +{ + if (! progress_window) + create_progress_window (); + + gtk_label_set_text ((GtkLabel *) progress_label, (const char *) data); +} + +static void show_progress_2 (void * data, void * user) +{ + if (! progress_window) + create_progress_window (); + + gtk_label_set_text ((GtkLabel *) progress_label_2, (const char *) data); +} + +static void hide_progress (void * data, void * user) +{ + if (progress_window) + gtk_widget_destroy (progress_window); +} + +static void show_error (void * data, void * user) +{ + audgui_simple_message (& error_window, GTK_MESSAGE_ERROR, _("Error"), (const char *) data); +} + +void status_init () +{ + hook_associate ("ui show progress", show_progress, nullptr); + hook_associate ("ui show progress 2", show_progress_2, nullptr); + hook_associate ("ui hide progress", hide_progress, nullptr); + hook_associate ("ui show error", show_error, nullptr); +} + +void status_cleanup () +{ + hook_dissociate ("ui show progress", show_progress); + hook_dissociate ("ui show progress 2", show_progress_2); + hook_dissociate ("ui hide progress", hide_progress); + hook_dissociate ("ui show error", show_error); + + if (progress_window) + gtk_widget_destroy (progress_window); + if (error_window) + gtk_widget_destroy (error_window); +} diff --git a/src/libaudgui/ui_jumptotrack.c b/src/libaudgui/ui_jumptotrack.c deleted file mode 100644 index 83addd4..0000000 --- a/src/libaudgui/ui_jumptotrack.c +++ /dev/null @@ -1,342 +0,0 @@ -/* - * ui_jumptotrack.c - * Copyright 2007-2012 Yoshiki Yazawa and John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <gdk/gdkkeysyms.h> -#include <gtk/gtk.h> - -#include <audacious/drct.h> -#include <audacious/i18n.h> -#include <audacious/misc.h> -#include <audacious/playlist.h> -#include <libaudcore/hook.h> - -#include "init.h" -#include "libaudgui.h" -#include "libaudgui-gtk.h" -#include "list.h" -#include "ui_jumptotrack_cache.h" - -static void update_cb (void * data, void * user); -static void activate_cb (void * data, void * user); - -static JumpToTrackCache* cache = NULL; -static const GArray * search_matches; -static GtkWidget * treeview, * filter_entry, * queue_button; -static bool_t watching = FALSE; - -static void destroy_cb (void) -{ - if (watching) - { - hook_dissociate ("playlist update", update_cb); - hook_dissociate ("playlist activate", activate_cb); - watching = FALSE; - } - - if (cache != NULL) - { - ui_jump_to_track_cache_free (cache); - cache = NULL; - } - - search_matches = NULL; -} - -static int get_selected_entry (void) -{ - g_return_val_if_fail (treeview && search_matches, -1); - - GtkTreeModel * model = gtk_tree_view_get_model ((GtkTreeView *) treeview); - GtkTreeSelection * selection = gtk_tree_view_get_selection ((GtkTreeView *) treeview); - GtkTreeIter iter; - - if (! gtk_tree_selection_get_selected (selection, NULL, & iter)) - return -1; - - GtkTreePath * path = gtk_tree_model_get_path (model, & iter); - int row = gtk_tree_path_get_indices (path)[0]; - gtk_tree_path_free (path); - - g_return_val_if_fail (row >= 0 && row < search_matches->len, -1); - return g_array_index (search_matches, int, row); -} - -static void do_jump (void * unused) -{ - int entry = get_selected_entry (); - if (entry < 0) - return; - - int playlist = aud_playlist_get_active (); - aud_playlist_set_position (playlist, entry); - aud_playlist_set_playing (playlist); - aud_drct_play (); - - if (aud_get_bool ("audgui", "close_jtf_dialog")) - audgui_jump_to_track_hide(); -} - -static void update_queue_button (int entry) -{ - g_return_if_fail (queue_button); - - if (entry < 0) - { - gtk_button_set_label ((GtkButton *) queue_button, _("_Queue")); - gtk_widget_set_sensitive (queue_button, FALSE); - } - else - { - if (aud_playlist_queue_find_entry (aud_playlist_get_active (), entry) != -1) - gtk_button_set_label ((GtkButton *) queue_button, _("Un_queue")); - else - gtk_button_set_label ((GtkButton *) queue_button, _("_Queue")); - - gtk_widget_set_sensitive (queue_button, TRUE); - } -} - -static void do_queue (void * unused) -{ - int playlist = aud_playlist_get_active (); - int entry = get_selected_entry (); - if (entry < 0) - return; - - int queued = aud_playlist_queue_find_entry (playlist, entry); - if (queued >= 0) - aud_playlist_queue_delete (playlist, queued, 1); - else - aud_playlist_queue_insert (playlist, -1, entry); - - update_queue_button (entry); -} - -static void selection_changed (void) -{ - update_queue_button (get_selected_entry ()); -} - -static bool_t keypress_cb (GtkWidget * widget, GdkEventKey * event) -{ - if (event->keyval == GDK_KEY_Escape) - { - audgui_jump_to_track_hide(); - return TRUE; - } - - return FALSE; -} - -static void fill_list (void) -{ - g_return_if_fail (treeview && filter_entry); - - if (! cache) - cache = ui_jump_to_track_cache_new(); - - search_matches = ui_jump_to_track_cache_search (cache, gtk_entry_get_text - ((GtkEntry *) filter_entry)); - - audgui_list_delete_rows (treeview, 0, audgui_list_row_count (treeview)); - audgui_list_insert_rows (treeview, 0, search_matches->len); - - if (search_matches->len >= 1) - { - GtkTreeSelection * sel = gtk_tree_view_get_selection ((GtkTreeView *) treeview); - GtkTreePath * path = gtk_tree_path_new_from_indices (0, -1); - gtk_tree_selection_select_path (sel, path); - gtk_tree_path_free (path); - } -} - -static void update_cb (void * data, void * user) -{ - g_return_if_fail (treeview); - - GtkTreeModel * model; - GtkTreeIter iter; - GtkTreePath * path = NULL; - - if (GPOINTER_TO_INT (data) <= PLAYLIST_UPDATE_SELECTION) - return; - - if (cache != NULL) - { - ui_jump_to_track_cache_free (cache); - cache = NULL; - } - - /* If it's only a metadata update, save and restore the cursor position. */ - if (GPOINTER_TO_INT (data) <= PLAYLIST_UPDATE_METADATA && - gtk_tree_selection_get_selected (gtk_tree_view_get_selection - ((GtkTreeView *) treeview), & model, & iter)) - path = gtk_tree_model_get_path (model, & iter); - - fill_list (); - - if (path != NULL) - { - gtk_tree_selection_select_path (gtk_tree_view_get_selection - ((GtkTreeView *) treeview), path); - gtk_tree_view_scroll_to_cell ((GtkTreeView *) treeview, path, NULL, TRUE, 0.5, 0); - gtk_tree_path_free (path); - } -} - -static void activate_cb (void * data, void * user) -{ - update_cb (GINT_TO_POINTER (PLAYLIST_UPDATE_STRUCTURE), NULL); -} - -static void toggle_button_cb (GtkToggleButton * toggle, const char * setting) -{ - aud_set_bool ("audgui", setting, gtk_toggle_button_get_active (toggle)); -} - -static void list_get_value (void * user, int row, int column, GValue * value) -{ - g_return_if_fail (search_matches); - g_return_if_fail (column >= 0 && column < 2); - g_return_if_fail (row >= 0 && row < search_matches->len); - - int playlist = aud_playlist_get_active (); - int entry = g_array_index (search_matches, int, row); - - switch (column) - { - case 0: - g_value_set_int (value, 1 + entry); - break; - case 1:; - char * title = aud_playlist_entry_get_title (playlist, entry, TRUE); - g_return_if_fail (title); - g_value_set_string (value, title); - str_unref (title); - break; - } -} - -static const AudguiListCallbacks callbacks = { - .get_value = list_get_value}; - -static GtkWidget * create_window (void) -{ - GtkWidget * jump_to_track_win = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_type_hint(GTK_WINDOW(jump_to_track_win), - GDK_WINDOW_TYPE_HINT_DIALOG); - - gtk_window_set_title(GTK_WINDOW(jump_to_track_win), _("Jump to Song")); - - g_signal_connect (jump_to_track_win, "key_press_event", (GCallback) keypress_cb, NULL); - g_signal_connect (jump_to_track_win, "destroy", (GCallback) destroy_cb, NULL); - - gtk_container_set_border_width(GTK_CONTAINER(jump_to_track_win), 10); - gtk_window_set_default_size(GTK_WINDOW(jump_to_track_win), 600, 500); - - GtkWidget * vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5); - gtk_container_add(GTK_CONTAINER(jump_to_track_win), vbox); - - treeview = audgui_list_new (& callbacks, NULL, 0); - gtk_tree_view_set_headers_visible ((GtkTreeView *) treeview, FALSE); - - audgui_list_add_column (treeview, NULL, 0, G_TYPE_INT, 7); - audgui_list_add_column (treeview, NULL, 1, G_TYPE_STRING, -1); - - g_signal_connect (gtk_tree_view_get_selection ((GtkTreeView *) treeview), - "changed", (GCallback) selection_changed, NULL); - g_signal_connect (treeview, "row-activated", (GCallback) do_jump, NULL); - - GtkWidget * hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); - gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3); - - /* filter box */ - GtkWidget * search_label = gtk_label_new (_("Filter: ")); - gtk_label_set_markup_with_mnemonic(GTK_LABEL(search_label), _("_Filter:")); - gtk_box_pack_start(GTK_BOX(hbox), search_label, FALSE, FALSE, 0); - - filter_entry = gtk_entry_new (); - gtk_label_set_mnemonic_widget ((GtkLabel *) search_label, filter_entry); - g_signal_connect (filter_entry, "changed", (GCallback) fill_list, NULL); - gtk_entry_set_activates_default ((GtkEntry *) filter_entry, TRUE); - gtk_box_pack_start ((GtkBox *) hbox, filter_entry, TRUE, TRUE, 0); - - GtkWidget * scrollwin = gtk_scrolled_window_new (NULL, NULL); - gtk_container_add(GTK_CONTAINER(scrollwin), treeview); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin), - GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrollwin), - GTK_SHADOW_IN); - gtk_box_pack_start(GTK_BOX(vbox), scrollwin, TRUE, TRUE, 0); - - GtkWidget * bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_set_spacing(GTK_BOX(bbox), 4); - gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0); - - /* close dialog toggle */ - GtkWidget * toggle = gtk_check_button_new_with_mnemonic (_("C_lose on jump")); - gtk_toggle_button_set_active ((GtkToggleButton *) toggle, aud_get_bool - ("audgui", "close_jtf_dialog")); - gtk_box_pack_start(GTK_BOX(bbox), toggle, FALSE, FALSE, 0); - g_signal_connect (toggle, "clicked", (GCallback) toggle_button_cb, "close_jtf_dialog"); - - /* queue button */ - queue_button = audgui_button_new (_("_Queue"), NULL, do_queue, NULL); - gtk_box_pack_start ((GtkBox *) bbox, queue_button, FALSE, FALSE, 0); - - /* jump button */ - GtkWidget * jump = audgui_button_new (_("_Jump"), "go-jump", do_jump, NULL); - gtk_box_pack_start(GTK_BOX(bbox), jump, FALSE, FALSE, 0); - - gtk_widget_set_can_default(jump, TRUE); - gtk_widget_grab_default(jump); - - /* close button */ - GtkWidget * close = audgui_button_new (_("_Close"), "window-close", - (AudguiCallback) audgui_jump_to_track_hide, NULL); - gtk_box_pack_start(GTK_BOX(bbox), close, FALSE, FALSE, 0); - - return jump_to_track_win; -} - -EXPORT void audgui_jump_to_track (void) -{ - if (audgui_reshow_unique_window (AUDGUI_JUMP_TO_TRACK_WINDOW)) - return; - - GtkWidget * jump_to_track_win = create_window (); - - if (! watching) - { - fill_list (); - hook_associate ("playlist update", update_cb, NULL); - hook_associate ("playlist activate", activate_cb, NULL); - watching = TRUE; - } - - gtk_widget_grab_focus (filter_entry); - - audgui_show_unique_window (AUDGUI_JUMP_TO_TRACK_WINDOW, jump_to_track_win); -} - -EXPORT void audgui_jump_to_track_hide (void) -{ - audgui_hide_unique_window (AUDGUI_JUMP_TO_TRACK_WINDOW); -} diff --git a/src/libaudgui/ui_jumptotrack_cache.c b/src/libaudgui/ui_jumptotrack_cache.c deleted file mode 100644 index 21d4370..0000000 --- a/src/libaudgui/ui_jumptotrack_cache.c +++ /dev/null @@ -1,358 +0,0 @@ -/* - * ui_jumptotrack_cache.c - * Copyright 2008-2012 Jussi Judin and John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> - -#include <stdlib.h> -#include <string.h> -#include <assert.h> - -#include <audacious/debug.h> -#include <audacious/playlist.h> -#include <libaudcore/audstrings.h> - -#include "ui_jumptotrack_cache.h" - -// Struct to keep information about matches from searches. -typedef struct -{ - GArray * entries; // int - GArray * titles, * artists, * albums, * paths; // char * (pooled) -} KeywordMatches; - -static void ui_jump_to_track_cache_init (JumpToTrackCache * cache); - -static KeywordMatches * keyword_matches_new (void) -{ - KeywordMatches * k = g_slice_new (KeywordMatches); - k->entries = g_array_new (FALSE, FALSE, sizeof (int)); - k->titles = g_array_new (FALSE, FALSE, sizeof (char *)); - k->artists = g_array_new (FALSE, FALSE, sizeof (char *)); - k->albums = g_array_new (FALSE, FALSE, sizeof (char *)); - k->paths = g_array_new (FALSE, FALSE, sizeof (char *)); - return k; -} - -static void keyword_matches_free (KeywordMatches * k) -{ - g_array_free (k->entries, TRUE); - g_array_free (k->titles, TRUE); - g_array_free (k->artists, TRUE); - g_array_free (k->albums, TRUE); - g_array_free (k->paths, TRUE); - g_slice_free (KeywordMatches, k); -} - -/** - * Creates an regular expression list usable in searches from search keyword. - * - * In searches, every regular expression on this list is matched against - * the search title and if they all match, the title is declared as - * matching one. - * - * Regular expressions in list are formed by splitting the 'keyword' to words - * by splitting the keyword string with space character. - */ -static GSList* -ui_jump_to_track_cache_regex_list_create(const GString* keyword) -{ - GSList *regex_list = NULL; - char **words = NULL; - int i = -1; - /* Chop the key string into ' '-separated key regex-pattern strings */ - words = g_strsplit(keyword->str, " ", 0); - - /* create a list of regex using the regex-pattern strings */ - while ( words[++i] != NULL ) - { - // Ignore empty words. - if (words[i][0] == 0) { - continue; - } - - GRegex * regex = g_regex_new (words[i], G_REGEX_CASELESS, 0, NULL); - if (regex) - regex_list = g_slist_append (regex_list, regex); - } - - g_strfreev(words); - - return regex_list; -} - -/** - * Checks if 'song' matches all regular expressions in 'regex_list'. - */ -static bool_t -ui_jump_to_track_match(const char * song, GSList *regex_list) -{ - if ( song == NULL ) - return FALSE; - - for ( ; regex_list ; regex_list = g_slist_next(regex_list) ) - { - GRegex * regex = regex_list->data; - if (! g_regex_match (regex, song, 0, NULL)) - return FALSE; - } - - return TRUE; -} - -/** - * Returns all songs that match 'keyword'. - * - * Searches are conducted against entries in 'search_space' variable - * and after the search, search result is added to 'cache'. - * - * @param cache The result of this search is added to cache. - * @param search_space Entries inside which the search is conducted. - * @param keyword Normalized string for searches. - */ -static GArray* -ui_jump_to_track_cache_match_keyword(JumpToTrackCache* cache, - const KeywordMatches* search_space, - const GString* keyword) -{ - GSList* regex_list = ui_jump_to_track_cache_regex_list_create(keyword); - - KeywordMatches * k = keyword_matches_new (); - - for (int i = 0; i < search_space->entries->len; i ++) - { - char * title = g_array_index (search_space->titles, char *, i); - char * artist = g_array_index (search_space->artists, char *, i); - char * album = g_array_index (search_space->albums, char *, i); - char * path = g_array_index (search_space->paths, char *, i); - bool_t match; - - if (regex_list != NULL) - match = ui_jump_to_track_match (title, regex_list) - || ui_jump_to_track_match (artist, regex_list) - || ui_jump_to_track_match (album, regex_list) - || ui_jump_to_track_match (path, regex_list); - else - match = TRUE; - - if (match) { - g_array_append_val (k->entries, g_array_index (search_space->entries, int, i)); - g_array_append_val (k->titles, title); - g_array_append_val (k->artists, artist); - g_array_append_val (k->albums, album); - g_array_append_val (k->paths, path); - } - } - - g_hash_table_insert (cache->keywords, GINT_TO_POINTER (g_string_hash (keyword)), k); - - g_slist_free_full (regex_list, (GDestroyNotify) g_regex_unref); - - return k->entries; -} - -/** - * Frees the possibly allocated data in KeywordMatches. - */ -static void -ui_jump_to_track_cache_free_keywordmatch_data(KeywordMatches* match_entry) -{ - for (int i = 0; i < match_entry->entries->len; i ++) - { - str_unref (g_array_index (match_entry->titles, char *, i)); - str_unref (g_array_index (match_entry->artists, char *, i)); - str_unref (g_array_index (match_entry->albums, char *, i)); - str_unref (g_array_index (match_entry->paths, char *, i)); - } -} - -/** - * Creates a new song search cache. - * - * Returned value should be freed with ui_jump_to_track_cache_free() function. - */ -JumpToTrackCache* -ui_jump_to_track_cache_new() -{ - JumpToTrackCache * cache = g_slice_new (JumpToTrackCache); - - cache->keywords = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) keyword_matches_free); - ui_jump_to_track_cache_init (cache); - return cache; -} - -/** - * Clears the search cache. - */ -static void -ui_jump_to_track_cache_clear(JumpToTrackCache* cache) -{ - GString* empty_keyword = g_string_new(""); - gpointer found_keyword = NULL; - - // All normalized titles reside in an empty key "" so we'll free them - // first. - found_keyword = g_hash_table_lookup(cache->keywords, - GINT_TO_POINTER(g_string_hash(empty_keyword))); - g_string_free(empty_keyword, - TRUE); - if (found_keyword != NULL) - { - KeywordMatches* all_titles = (KeywordMatches*)found_keyword; - ui_jump_to_track_cache_free_keywordmatch_data(all_titles); - } - // Now when all normalized strings are freed, no need to worry about - // double frees or memory leaks. - g_hash_table_remove_all(cache->keywords); -} - -/** - * Initializes the search cache if cache is empty or has wrong playlist. - */ -static void ui_jump_to_track_cache_init (JumpToTrackCache * cache) -{ - // Reset cache state - ui_jump_to_track_cache_clear(cache); - - // Initialize cache with playlist data - int playlist = aud_playlist_get_active (); - int entries = aud_playlist_entry_count (playlist); - - KeywordMatches * k = keyword_matches_new (); - - for (int entry = 0; entry < entries; entry ++) - { - char * title, * artist, * album; - aud_playlist_entry_describe (playlist, entry, & title, & artist, & album, TRUE); - - char * uri = aud_playlist_entry_get_filename (playlist, entry); - char * decoded = uri_to_display (uri); - str_unref (uri); - - g_array_append_val (k->entries, entry); - g_array_append_val (k->titles, title); - g_array_append_val (k->artists, artist); - g_array_append_val (k->albums, album); - g_array_append_val (k->paths, decoded); - } - - // Finally insert all titles into cache into an empty key "" so that - // the matchable data has specified place to be. - GString * empty_keyword = g_string_new (""); - g_hash_table_insert (cache->keywords, GINT_TO_POINTER (g_string_hash (empty_keyword)), k); - g_string_free (empty_keyword, TRUE); -} - -/** - * Searches 'keyword' inside 'playlist' by using 'cache' to speed up searching. - * - * Searches are basically conducted as follows: - * - * Cache is checked if it has the information about right playlist and - * initialized with playlist data if needed. - * - * Keyword is normalized for searching (Unicode NFKD, case folding) - * - * Cache is checked if it has keyword and if it has, we can immediately get - * the search results and return. If not, searching goes as follows: - * - * Search for the longest word that is in cache that matches the beginning - * of keyword and use the cached matches as base for the current search. - * The shortest word that can be matched against is the empty string "", so - * there should always be matches in cache. - * - * After that conduct the search by splitting keyword into words separated - * by space and using regular expressions. - * - * When the keyword is searched, search result is added to cache to - * corresponding keyword that can be used as base for new searches. - * - * The motivation for caching is that to search word 'some cool song' one - * has to type following strings that are all searched individually: - * - * s - * so - * som - * some - * some - * some c - * some co - * some coo - * some cool - * some cool - * some cool s - * some cool so - * some cool son - * some cool song - * - * If the search results are cached in every phase and the result of - * the maximum length matching string is used as base for concurrent - * searches, we can probably get the matches reduced to some hundreds - * after a few letters typed on playlists with thousands of songs and - * reduce useless iteration quite a lot. - * - * Return: GArray of int - */ -const GArray * ui_jump_to_track_cache_search (JumpToTrackCache * cache, const - char * keyword) -{ - GString* keyword_string = g_string_new(keyword); - GString* match_string = g_string_new(keyword); - int match_string_length = keyword_string->len; - - while (match_string_length >= 0) - { - gpointer string_ptr = GINT_TO_POINTER(g_string_hash(match_string)); - gpointer result_entries = g_hash_table_lookup(cache->keywords, - string_ptr); - if (result_entries != NULL) - { - KeywordMatches* matched_entries = (KeywordMatches*)result_entries; - // if keyword matches something we have, we'll just return the list - // of matches that the keyword has. - if (match_string_length == keyword_string->len) { - g_string_free(keyword_string, TRUE); - g_string_free(match_string, TRUE); - return matched_entries->entries; - } - - // Do normal search by using the result of previous search - // as search space. - GArray* result = ui_jump_to_track_cache_match_keyword(cache, - matched_entries, - keyword_string); - g_string_free(keyword_string, TRUE); - g_string_free(match_string, TRUE); - return result; - } - match_string_length--; - g_string_set_size(match_string, match_string_length); - } - // This should never, ever get to this point because there is _always_ - // the empty string to match against. - AUDDBG("One should never get to this point. Something is really wrong with \ -cache->keywords hash table."); - abort (); -} - -void ui_jump_to_track_cache_free (JumpToTrackCache * cache) -{ - ui_jump_to_track_cache_clear (cache); - g_hash_table_unref (cache->keywords); - g_slice_free (JumpToTrackCache, cache); -} diff --git a/src/libaudgui/ui_playlist_manager.c b/src/libaudgui/ui_playlist_manager.c deleted file mode 100644 index 7e42947..0000000 --- a/src/libaudgui/ui_playlist_manager.c +++ /dev/null @@ -1,282 +0,0 @@ -/* - * ui_playlist_manager.c - * Copyright 2006-2012 Giacomo Lozito, John Lindgren, and Thomas Lange - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <string.h> -#include <gtk/gtk.h> - -#include <audacious/i18n.h> -#include <audacious/misc.h> -#include <audacious/drct.h> -#include <audacious/playlist.h> -#include <libaudcore/audstrings.h> -#include <libaudcore/hook.h> - -#include "init.h" -#include "libaudgui.h" -#include "libaudgui-gtk.h" -#include "list.h" - -static void activate_row (void * user, int row); - -static void play_cb (void * unused) -{ - activate_row (NULL, aud_playlist_get_active ()); -} - -static void rename_cb (void * unused) -{ - audgui_show_playlist_rename (aud_playlist_get_active ()); -} - -static void new_cb (void * unused) -{ - aud_playlist_insert (aud_playlist_get_active () + 1); - aud_playlist_set_active (aud_playlist_get_active () + 1); -} - -static void delete_cb (void * unused) -{ - audgui_confirm_playlist_delete (aud_playlist_get_active ()); -} - -static void get_value (void * user, int row, int column, GValue * value) -{ - switch (column) - { - case 0:; - char * title = aud_playlist_get_title (row); - g_value_set_string (value, title); - str_unref (title); - break; - case 1: - g_value_set_int (value, aud_playlist_entry_count (row)); - break; - } -} - -static bool_t get_selected (void * user, int row) -{ - return (row == aud_playlist_get_active ()); -} - -static void set_selected (void * user, int row, bool_t selected) -{ - if (selected) - aud_playlist_set_active (row); -} - -static void select_all (void * user, bool_t selected) -{ -} - -static void activate_row (void * user, int row) -{ - aud_playlist_set_active (row); - aud_drct_play_playlist (row); - - if (aud_get_bool ("audgui", "playlist_manager_close_on_activate")) - audgui_hide_unique_window (AUDGUI_PLAYLIST_MANAGER_WINDOW); -} - -static void shift_rows (void * user, int row, int before) -{ - if (before < row) - aud_playlist_reorder (row, before, 1); - else if (before - 1 > row) - aud_playlist_reorder (row, before - 1, 1); -} - -static const AudguiListCallbacks callbacks = { - .get_value = get_value, - .get_selected = get_selected, - .set_selected = set_selected, - .select_all = select_all, - .activate_row = activate_row, - .right_click = NULL, - .shift_rows = shift_rows, - .data_type = NULL, - .get_data = NULL, - .receive_data = NULL}; - -static bool_t search_cb (GtkTreeModel * model, int column, const char * key, - GtkTreeIter * iter, void * user) -{ - GtkTreePath * path = gtk_tree_model_get_path (model, iter); - g_return_val_if_fail (path, TRUE); - int row = gtk_tree_path_get_indices (path)[0]; - gtk_tree_path_free (path); - - char * title = aud_playlist_get_title (row); - g_return_val_if_fail (title, TRUE); - - Index * keys = str_list_to_index (key, " "); - int count = index_count (keys); - - bool_t match = FALSE; - - for (int i = 0; i < count; i ++) - { - if (strstr_nocase_utf8 (title, index_get (keys, i))) - match = TRUE; - else - { - match = FALSE; - break; - } - } - - index_free_full (keys, (IndexFreeFunc) str_unref); - str_unref (title); - - return ! match; /* TRUE == not matched, FALSE == matched */ -} - -static bool_t position_changed = FALSE; -static bool_t playlist_activated = FALSE; - -static void update_hook (void * data, void * list) -{ - int rows = aud_playlist_count (); - - if (GPOINTER_TO_INT (data) == PLAYLIST_UPDATE_STRUCTURE) - { - int old_rows = audgui_list_row_count (list); - - if (rows < old_rows) - audgui_list_delete_rows (list, rows, old_rows - rows); - else if (rows > old_rows) - audgui_list_insert_rows (list, old_rows, rows - old_rows); - - position_changed = TRUE; - playlist_activated = TRUE; - } - - if (GPOINTER_TO_INT (data) >= PLAYLIST_UPDATE_METADATA) - audgui_list_update_rows (list, 0, rows); - - if (playlist_activated) - { - audgui_list_set_focus (list, aud_playlist_get_active ()); - audgui_list_update_selection (list, 0, rows); - playlist_activated = FALSE; - } - - if (position_changed) - { - audgui_list_set_highlight (list, aud_playlist_get_playing ()); - position_changed = FALSE; - } -} - -static void activate_hook (void * data, void * list) -{ - if (aud_playlist_update_pending ()) - playlist_activated = TRUE; - else - { - audgui_list_set_focus (list, aud_playlist_get_active ()); - audgui_list_update_selection (list, 0, aud_playlist_count ()); - } -} - -static void position_hook (void * data, void * list) -{ - if (aud_playlist_update_pending ()) - position_changed = TRUE; - else - audgui_list_set_highlight (list, aud_playlist_get_playing ()); -} - -static void close_on_activate_cb (GtkToggleButton * toggle) -{ - aud_set_bool ("audgui", "playlist_manager_close_on_activate", - gtk_toggle_button_get_active (toggle)); -} - -static void destroy_cb (GtkWidget * window) -{ - hook_dissociate ("playlist update", update_hook); - hook_dissociate ("playlist activate", activate_hook); - hook_dissociate ("playlist set playing", position_hook); -} - -static GtkWidget * create_playlist_manager (void) -{ - GtkWidget * playman_win = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_window_set_type_hint ((GtkWindow *) playman_win, GDK_WINDOW_TYPE_HINT_DIALOG); - gtk_window_set_title ((GtkWindow *) playman_win, _("Playlist Manager")); - gtk_container_set_border_width ((GtkContainer *) playman_win, 6); - gtk_widget_set_size_request (playman_win, 400, 250); - - g_signal_connect (playman_win, "destroy", (GCallback) destroy_cb, NULL); - audgui_destroy_on_escape (playman_win); - - GtkWidget * playman_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); - gtk_container_add ((GtkContainer *) playman_win, playman_vbox); - - /* ListView */ - GtkWidget * playman_pl_lv = audgui_list_new (& callbacks, NULL, aud_playlist_count ()); - audgui_list_add_column (playman_pl_lv, _("Title"), 0, G_TYPE_STRING, -1); - audgui_list_add_column (playman_pl_lv, _("Entries"), 1, G_TYPE_INT, 7); - audgui_list_set_highlight (playman_pl_lv, aud_playlist_get_playing ()); - gtk_tree_view_set_search_equal_func ((GtkTreeView *) playman_pl_lv, - search_cb, NULL, NULL); - hook_associate ("playlist update", update_hook, playman_pl_lv); - hook_associate ("playlist activate", activate_hook, playman_pl_lv); - hook_associate ("playlist set playing", position_hook, playman_pl_lv); - - GtkWidget * playman_pl_lv_sw = gtk_scrolled_window_new (NULL, NULL); - gtk_scrolled_window_set_shadow_type ((GtkScrolledWindow *) playman_pl_lv_sw, - GTK_SHADOW_IN); - gtk_scrolled_window_set_policy ((GtkScrolledWindow *) playman_pl_lv_sw, - GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_container_add ((GtkContainer *) playman_pl_lv_sw, playman_pl_lv); - gtk_box_pack_start ((GtkBox *) playman_vbox, playman_pl_lv_sw, TRUE, TRUE, 0); - - /* ButtonBox */ - GtkWidget * playman_button_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); - GtkWidget * new_button = audgui_button_new (_("_New"), "document-new", new_cb, NULL); - GtkWidget * delete_button = audgui_button_new (_("_Remove"), "edit-delete", delete_cb, NULL); - GtkWidget * rename_button = audgui_button_new (_("Ren_ame"), "insert-text", rename_cb, NULL); - GtkWidget * play_button = audgui_button_new (_("_Play"), "media-playback-start", play_cb, NULL); - - gtk_container_add ((GtkContainer *) playman_button_hbox, new_button); - gtk_container_add ((GtkContainer *) playman_button_hbox, delete_button); - gtk_box_pack_end ((GtkBox *) playman_button_hbox, play_button, FALSE, FALSE, 0); - gtk_box_pack_end ((GtkBox *) playman_button_hbox, rename_button, FALSE, FALSE, 0); - gtk_container_add ((GtkContainer *) playman_vbox, playman_button_hbox); - - /* CheckButton */ - GtkWidget * hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); - gtk_box_pack_start ((GtkBox *) playman_vbox, hbox, FALSE, FALSE, 0); - GtkWidget * check_button = gtk_check_button_new_with_mnemonic - (_("_Close dialog on activating playlist")); - gtk_box_pack_start ((GtkBox *) hbox, check_button, FALSE, FALSE, 0); - gtk_toggle_button_set_active ((GtkToggleButton *) check_button, aud_get_bool - ("audgui", "playlist_manager_close_on_activate")); - g_signal_connect (check_button, "toggled", (GCallback) close_on_activate_cb, NULL); - - return playman_win; -} - -EXPORT void audgui_playlist_manager (void) -{ - if (! audgui_reshow_unique_window (AUDGUI_PLAYLIST_MANAGER_WINDOW)) - audgui_show_unique_window (AUDGUI_PLAYLIST_MANAGER_WINDOW, create_playlist_manager ()); -} diff --git a/src/libaudgui/urilist.c b/src/libaudgui/urilist.c deleted file mode 100644 index 4f75764..0000000 --- a/src/libaudgui/urilist.c +++ /dev/null @@ -1,123 +0,0 @@ -/* - * urilist.c - * Copyright 2010-2011 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <string.h> -#include <glib.h> - -#include <audacious/drct.h> -#include <audacious/playlist.h> -#include <libaudcore/audstrings.h> -#include <libaudcore/vfs.h> - -#include "libaudgui.h" - -typedef void (* ForEachFunc) (char *, void *); - -static char * check_uri (char * name) -{ - char * new; - - if (strstr (name, "://") || ! (new = filename_to_uri (name))) - return name; - - str_unref (name); - return new; -} - -static void urilist_for_each (const char * list, ForEachFunc func, void * user) -{ - const char * end, * next; - - while (list[0]) - { - if ((end = strstr (list, "\r\n"))) - next = end + 2; - else if ((end = strchr (list, '\n'))) - next = end + 1; - else - next = end = strchr (list, 0); - - func (check_uri (str_nget (list, end - list)), user); - list = next; - } -} - -static void add_to_index (char * name, Index * index) -{ - index_insert (index, -1, name); -} - -EXPORT void audgui_urilist_open (const char * list) -{ - Index * filenames = index_new (); - urilist_for_each (list, (ForEachFunc) add_to_index, filenames); - aud_drct_pl_open_list (filenames); -} - -EXPORT void audgui_urilist_insert (int playlist, int at, const char * list) -{ - Index * filenames = index_new (); - urilist_for_each (list, (ForEachFunc) add_to_index, filenames); - aud_playlist_entry_insert_batch (playlist, at, filenames, NULL, FALSE); -} - -EXPORT char * audgui_urilist_create_from_selected (int playlist) -{ - int entries = aud_playlist_entry_count (playlist); - int space = 0; - int count, length; - char * name; - char * buffer, * set; - - for (count = 0; count < entries; count ++) - { - if (! aud_playlist_entry_get_selected (playlist, count)) - continue; - - name = aud_playlist_entry_get_filename (playlist, count); - g_return_val_if_fail (name != NULL, NULL); - space += strlen (name) + 1; - str_unref (name); - } - - if (! space) - return NULL; - - buffer = g_malloc (space); - set = buffer; - - for (count = 0; count < entries; count ++) - { - if (! aud_playlist_entry_get_selected (playlist, count)) - continue; - - name = aud_playlist_entry_get_filename (playlist, count); - g_return_val_if_fail (name != NULL, NULL); - length = strlen (name); - g_return_val_if_fail (length + 1 <= space, NULL); - memcpy (set, name, length); - set += length; - * set ++ = '\n'; - space -= length + 1; - str_unref (name); - } - - * -- set = 0; /* last newline replaced with null */ - return buffer; -} diff --git a/src/libaudgui/urilist.cc b/src/libaudgui/urilist.cc new file mode 100644 index 0000000..4b9193c --- /dev/null +++ b/src/libaudgui/urilist.cc @@ -0,0 +1,114 @@ +/* + * urilist.c + * Copyright 2010-2011 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <string.h> + +#include <libaudcore/audstrings.h> +#include <libaudcore/drct.h> +#include <libaudcore/mainloop.h> +#include <libaudcore/multihash.h> +#include <libaudcore/playlist.h> +#include <libaudcore/tuple.h> +#include <libaudcore/vfs.h> + +#include "libaudgui.h" + +static SimpleHash<String, Tuple> tuple_cache; +static QueuedFunc cleanup_timer; + +void urilist_cleanup () +{ + tuple_cache.clear (); + cleanup_timer.stop (); +} + +static String check_uri (const char * name) +{ + if (! strstr (name, "://")) + { + StringBuf uri = filename_to_uri (name); + if (uri) + return String (uri); + } + + return String (name); +} + +static Index<PlaylistAddItem> urilist_to_index (const char * list) +{ + Index<PlaylistAddItem> index; + const char * end, * next; + + while (list[0]) + { + if ((end = strchr (list, '\n'))) + { + next = end + 1; + if (end > list && end[-1] == '\r') + end --; + } + else + next = end = strchr (list, 0); + + String filename = check_uri (str_copy (list, end - list)); + const Tuple * tuple = tuple_cache.lookup (filename); + + index.append (filename, tuple ? tuple->ref () : Tuple ()); + + list = next; + } + + return index; +} + +EXPORT void audgui_urilist_open (const char * list) +{ + aud_drct_pl_open_list (urilist_to_index (list)); +} + +EXPORT void audgui_urilist_insert (int playlist, int at, const char * list) +{ + aud_playlist_entry_insert_batch (playlist, at, urilist_to_index (list), false); +} + +EXPORT Index<char> audgui_urilist_create_from_selected (int playlist) +{ + Index<char> buf; + int entries = aud_playlist_entry_count (playlist); + + for (int count = 0; count < entries; count ++) + { + if (aud_playlist_entry_get_selected (playlist, count)) + { + if (buf.len ()) + buf.append ('\n'); + + String filename = aud_playlist_entry_get_filename (playlist, count); + Tuple tuple = aud_playlist_entry_get_tuple (playlist, count, Playlist::Nothing); + + buf.insert (filename, -1, strlen (filename)); + if (tuple) + tuple_cache.add (filename, std::move (tuple)); + } + } + + cleanup_timer.start (30000, [] (void *) { urilist_cleanup (); }, nullptr); + + return buf; +} diff --git a/src/libaudgui/url-opener.c b/src/libaudgui/url-opener.cc index 3baf7e8..e092a4f 100644 --- a/src/libaudgui/url-opener.c +++ b/src/libaudgui/url-opener.cc @@ -19,18 +19,18 @@ #include <gtk/gtk.h> -#include <audacious/drct.h> -#include <audacious/i18n.h> -#include <audacious/misc.h> +#include <libaudcore/drct.h> +#include <libaudcore/i18n.h> +#include <libaudcore/runtime.h> -#include "init.h" +#include "internal.h" #include "libaudgui.h" #include "libaudgui-gtk.h" static void open_cb (void * entry) { const char * text = gtk_entry_get_text ((GtkEntry *) entry); - bool_t open = GPOINTER_TO_INT (g_object_get_data ((GObject *) entry, "open")); + gboolean open = GPOINTER_TO_INT (g_object_get_data ((GObject *) entry, "open")); if (open) aud_drct_pl_open (text); @@ -40,7 +40,7 @@ static void open_cb (void * entry) aud_history_add (text); } -static GtkWidget * create_url_opener (bool_t open) +static GtkWidget * create_url_opener (bool open) { const char * title, * verb, * icon; @@ -59,16 +59,21 @@ static GtkWidget * create_url_opener (bool_t open) GtkWidget * combo = gtk_combo_box_text_new_with_entry (); GtkWidget * entry = gtk_bin_get_child ((GtkBin *) combo); - gtk_entry_set_activates_default ((GtkEntry *) entry, TRUE); + gtk_entry_set_activates_default ((GtkEntry *) entry, true); + + for (int i = 0;; i++) + { + String item = aud_history_get (i); + if (! item) + break; - const char * item; - for (int i = 0; (item = aud_history_get (i)); i++) gtk_combo_box_text_append_text ((GtkComboBoxText *) combo, item); + } g_object_set_data ((GObject *) entry, "open", GINT_TO_POINTER (open)); GtkWidget * button1 = audgui_button_new (verb, icon, open_cb, entry); - GtkWidget * button2 = audgui_button_new (_("_Cancel"), "process-stop", NULL, NULL); + GtkWidget * button2 = audgui_button_new (_("_Cancel"), "process-stop", nullptr, nullptr); GtkWidget * dialog = audgui_dialog_new (GTK_MESSAGE_OTHER, title, _("Enter URL:"), button1, button2); @@ -78,7 +83,7 @@ static GtkWidget * create_url_opener (bool_t open) return dialog; } -EXPORT void audgui_show_add_url_window (bool_t open) +EXPORT void audgui_show_add_url_window (bool open) { audgui_show_unique_window (AUDGUI_URL_OPENER_WINDOW, create_url_opener (open)); } diff --git a/src/libaudgui/util.c b/src/libaudgui/util.cc index 5fee885..e545540 100644 --- a/src/libaudgui/util.c +++ b/src/libaudgui/util.cc @@ -22,13 +22,12 @@ #include <gdk/gdkkeysyms.h> #include <gtk/gtk.h> -#include <audacious/debug.h> -#include <audacious/i18n.h> -#include <audacious/misc.h> #include <libaudcore/audstrings.h> #include <libaudcore/hook.h> +#include <libaudcore/i18n.h> +#include <libaudcore/runtime.h> -#include "init.h" +#include "internal.h" #include "libaudgui.h" #include "libaudgui-gtk.h" @@ -39,7 +38,7 @@ EXPORT int audgui_get_digit_width (GtkWidget * widget) PangoFontDescription * desc = pango_font_description_new (); pango_font_description_set_weight (desc, PANGO_WEIGHT_BOLD); pango_layout_set_font_description (layout, desc); - pango_layout_get_pixel_size (layout, & width, NULL); + pango_layout_get_pixel_size (layout, & width, nullptr); pango_font_description_free (desc); g_object_unref (layout); return (width + 9) / 10; @@ -47,45 +46,46 @@ EXPORT int audgui_get_digit_width (GtkWidget * widget) EXPORT void audgui_get_mouse_coords (GtkWidget * widget, int * x, int * y) { - if (widget) - { - int xwin, ywin; - GdkRectangle alloc; + gtk_widget_get_pointer (widget, x, y); +} - GdkWindow * window = gtk_widget_get_window (widget); - GdkDisplay * display = gdk_window_get_display (window); - GdkDeviceManager * manager = gdk_display_get_device_manager (display); - GdkDevice * device = gdk_device_manager_get_client_pointer (manager); +EXPORT void audgui_get_mouse_coords (GdkScreen * screen, int * x, int * y) +{ + gdk_display_get_pointer (gdk_screen_get_display (screen), nullptr, x, y, nullptr); +} - gdk_window_get_device_position (window, device, & xwin, & ywin, NULL); - gtk_widget_get_allocation (widget, & alloc); +EXPORT void audgui_get_monitor_geometry (GdkScreen * screen, int x, int y, GdkRectangle * geom) +{ + int monitors = gdk_screen_get_n_monitors (screen); - * x = xwin - alloc.x; - * y = ywin - alloc.y; - } - else + for (int i = 0; i < monitors; i ++) { - GdkDisplay * display = gdk_display_get_default (); - GdkDeviceManager * manager = gdk_display_get_device_manager (display); - GdkDevice * device = gdk_device_manager_get_client_pointer (manager); - gdk_device_get_position (device, NULL, x, y); + gdk_screen_get_monitor_geometry (screen, i, geom); + if (x >= geom->x && x < geom->x + geom->width && y >= geom->y && y < geom->y + geom->height) + return; } + + /* fall back to entire screen */ + geom->x = 0; + geom->y = 0; + geom->width = gdk_screen_get_width (screen); + geom->height = gdk_screen_get_height (screen); } -static bool_t escape_destroy_cb (GtkWidget * widget, GdkEventKey * event) +static gboolean escape_destroy_cb (GtkWidget * widget, GdkEventKey * event) { if (event->keyval == GDK_KEY_Escape) { gtk_widget_destroy (widget); - return TRUE; + return true; } - return FALSE; + return false; } EXPORT void audgui_destroy_on_escape (GtkWidget * widget) { - g_signal_connect (widget, "key-press-event", (GCallback) escape_destroy_cb, NULL); + g_signal_connect (widget, "key-press-event", (GCallback) escape_destroy_cb, nullptr); } EXPORT GtkWidget * audgui_button_new (const char * text, const char * icon, @@ -105,35 +105,13 @@ EXPORT GtkWidget * audgui_button_new (const char * text, const char * icon, return button; } -static const char * icon_for_message_type (GtkMessageType type) -{ - switch (type) - { - case GTK_MESSAGE_INFO: return "dialog-information"; - case GTK_MESSAGE_WARNING: return "dialog-warning"; - case GTK_MESSAGE_QUESTION: return "dialog-question"; - case GTK_MESSAGE_ERROR: return "dialog-error"; - default: return NULL; - } -} - -/* style choices should not be enforced by deprecating API functions */ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - EXPORT GtkWidget * audgui_dialog_new (GtkMessageType type, const char * title, const char * text, GtkWidget * button1, GtkWidget * button2) { - GtkWidget * dialog = gtk_message_dialog_new (NULL, 0, type, GTK_BUTTONS_NONE, "%s", text); + GtkWidget * dialog = gtk_message_dialog_new (nullptr, (GtkDialogFlags) 0, type, + GTK_BUTTONS_NONE, "%s", text); gtk_window_set_title ((GtkWindow *) dialog, title); - const char * icon = icon_for_message_type (type); - if (icon) - { - GtkWidget * image = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_DIALOG); - gtk_message_dialog_set_image ((GtkMessageDialog *) dialog, image); - } - if (button2) { gtk_dialog_add_action_widget ((GtkDialog *) dialog, button2, GTK_RESPONSE_NONE); @@ -143,29 +121,32 @@ EXPORT GtkWidget * audgui_dialog_new (GtkMessageType type, const char * title, gtk_dialog_add_action_widget ((GtkDialog *) dialog, button1, GTK_RESPONSE_NONE); g_signal_connect_swapped (button1, "clicked", (GCallback) gtk_widget_destroy, dialog); - gtk_widget_set_can_default (button1, TRUE); + gtk_widget_set_can_default (button1, true); gtk_widget_grab_default (button1); return dialog; } -#pragma GCC diagnostic pop - EXPORT void audgui_dialog_add_widget (GtkWidget * dialog, GtkWidget * widget) { GtkWidget * box = gtk_message_dialog_get_message_area ((GtkMessageDialog *) dialog); - gtk_box_pack_start ((GtkBox *) box, widget, FALSE, FALSE, 0); + gtk_box_pack_start ((GtkBox *) box, widget, false, false, 0); } EXPORT void audgui_simple_message (GtkWidget * * widget, GtkMessageType type, const char * title, const char * text) { - AUDDBG ("%s\n", text); + if (type == GTK_MESSAGE_ERROR) + AUDERR ("%s\n", text); + else if (type == GTK_MESSAGE_WARNING) + AUDWARN ("%s\n", text); + else if (type == GTK_MESSAGE_INFO) + AUDINFO ("%s\n", text); if (* widget) { - const char * old = NULL; - g_object_get ((GObject *) * widget, "text", & old, NULL); + const char * old = nullptr; + g_object_get ((GObject *) * widget, "text", & old, nullptr); g_return_if_fail (old); int messages = GPOINTER_TO_INT (g_object_get_data ((GObject *) * widget, "messages")); @@ -174,8 +155,8 @@ EXPORT void audgui_simple_message (GtkWidget * * widget, GtkMessageType type, if (! strstr (old, text)) { - SCONCAT3 (both, old, "\n", text); - g_object_set ((GObject *) * widget, "text", both, NULL); + StringBuf both = str_concat ({old, "\n", text}); + g_object_set ((GObject *) * widget, "text", (const char *) both, nullptr); g_object_set_data ((GObject *) * widget, "messages", GINT_TO_POINTER (messages + 1)); } @@ -183,8 +164,8 @@ EXPORT void audgui_simple_message (GtkWidget * * widget, GtkMessageType type, } else { - GtkWidget * button = audgui_button_new (_("_Close"), "window-close", NULL, NULL); - * widget = audgui_dialog_new (type, title, text, button, NULL); + GtkWidget * button = audgui_button_new (_("_Close"), "window-close", nullptr, nullptr); + * widget = audgui_dialog_new (type, title, text, button, nullptr); g_object_set_data ((GObject *) * widget, "messages", GINT_TO_POINTER (1)); g_signal_connect (* widget, "destroy", (GCallback) gtk_widget_destroyed, widget); @@ -192,18 +173,3 @@ EXPORT void audgui_simple_message (GtkWidget * * widget, GtkMessageType type, gtk_widget_show_all (* widget); } } - -EXPORT void audgui_format_time (char * buf, int bufsize, int64_t milliseconds) -{ - int hours = milliseconds / 3600000; - int minutes = (milliseconds / 60000) % 60; - int seconds = (milliseconds / 1000) % 60; - - if (hours) - snprintf (buf, bufsize, "%d:%02d:%02d", hours, minutes, seconds); - else - { - bool_t zero = aud_get_bool (NULL, "leading_zero"); - snprintf (buf, bufsize, zero ? "%02d:%02d" : "%d:%02d", minutes, seconds); - } -} diff --git a/src/libaudqt/Makefile b/src/libaudqt/Makefile new file mode 100644 index 0000000..42606d9 --- /dev/null +++ b/src/libaudqt/Makefile @@ -0,0 +1,45 @@ +SHARED_LIB = ${LIB_PREFIX}audqt${LIB_SUFFIX} +LIB_MAJOR = 0 +LIB_MINOR = 0 + +SRCS = about.cc \ + art.cc \ + equalizer.cc \ + fileopener.cc \ + infowin.cc \ + info-widget.cc \ + log-inspector.cc \ + menu.cc \ + playlist-management.cc \ + plugin-menu.cc \ + prefs-builder.cc \ + prefs-plugin.cc \ + prefs-widget.cc \ + prefs-window.cc \ + prefs-pluginlist-model.cc \ + queue-manager.cc \ + util.cc \ + volumebutton.cc + +INCLUDES = libaudqt.h iface.h volumebutton.h info-widget.h menu.h + +include ../../buildsys.mk +include ../../extra.mk + +includesubdir = libaudqt + +LD = ${CXX} + +CPPFLAGS := -I.. -I../.. \ + ${CPPFLAGS} \ + ${QT_CFLAGS} \ + ${LIBGUESS_CFLAGS} + +CFLAGS += ${LIB_CFLAGS} + +LIBS := -L../libaudcore -laudcore \ + ${LIBS} -lm \ + ${QT_LIBS} + +%.moc: %.h + moc $< -o $@ diff --git a/src/libaudqt/about.cc b/src/libaudqt/about.cc new file mode 100644 index 0000000..ef6bb35 --- /dev/null +++ b/src/libaudqt/about.cc @@ -0,0 +1,119 @@ +/* + * about.cc + * Copyright 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <QDialog> +#include <QFile> +#include <QLabel> +#include <QPlainTextEdit> +#include <QTabWidget> +#include <QTextStream> +#include <QVBoxLayout> + +#include <libaudcore/audstrings.h> +#include <libaudcore/i18n.h> +#include <libaudcore/runtime.h> + +#include "libaudqt.h" + +static QTabWidget * buildCreditsNotebook (QWidget * parent) +{ + const char * data_dir = aud_get_path (AudPath::DataDir); + const char * titles[2] = {_("Credits"), _("License")}; + const char * filenames[2] = {"AUTHORS", "COPYING"}; + + auto tabs = new QTabWidget (parent); + + for (int i = 0; i < 2; i ++) + { + QFile f (QString (filename_build ({data_dir, filenames[i]}))); + if (! f.open (QIODevice::ReadOnly)) + continue; + + QTextStream in (& f); + + auto edit = new QPlainTextEdit (in.readAll ().trimmed (), parent); + edit->setReadOnly (true); + tabs->addTab (edit, titles[i]); + + f.close (); + } + + return tabs; +} + +static QDialog * buildAboutWindow () +{ + const char * data_dir = aud_get_path (AudPath::DataDir); + const char * logo_path = filename_build ({data_dir, "images", "about-logo.png"}); + const char * about_text = "<big><b>Audacious " VERSION "</b></big><br>" COPYRIGHT; + const char * website = "http://audacious-media-player.org"; + + auto window = new QDialog; + + auto logo = new QLabel (window); + logo->setPixmap (QPixmap (logo_path)); + logo->setAlignment (Qt::AlignHCenter); + + auto text = new QLabel (about_text, window); + text->setAlignment (Qt::AlignHCenter); + + auto anchor = QString ("<a href='%1'>%1</a>").arg (website); + auto link_label = new QLabel (anchor, window); + link_label->setAlignment (Qt::AlignHCenter); + link_label->setContentsMargins (0, 5, 0, 0); + link_label->setOpenExternalLinks (true); + + auto layout = new QVBoxLayout (window); + layout->addWidget (logo); + layout->addWidget (text); + layout->addWidget (link_label); + layout->addWidget (buildCreditsNotebook (window)); + + window->setWindowTitle (_("About Audacious")); + window->setFixedSize (590, 450); + + return window; +} + +static QDialog * s_aboutwin = nullptr; + +namespace audqt { + +EXPORT void aboutwindow_show () +{ + if (! s_aboutwin) + { + s_aboutwin = buildAboutWindow (); + s_aboutwin->setAttribute (Qt::WA_DeleteOnClose); + + QObject::connect (s_aboutwin, & QObject::destroyed, [] () { + s_aboutwin = nullptr; + }); + } + + window_bring_to_front (s_aboutwin); +} + +EXPORT void aboutwindow_hide () +{ + if (s_aboutwin) + delete s_aboutwin; +} + +} // namespace audqt diff --git a/src/libaudqt/art.cc b/src/libaudqt/art.cc new file mode 100644 index 0000000..d636ac4 --- /dev/null +++ b/src/libaudqt/art.cc @@ -0,0 +1,69 @@ +/* + * art.cc + * Copyright 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <QApplication> +#include <QPixmap> +#include <QImage> + +#include <libaudcore/audstrings.h> +#include <libaudcore/playlist.h> +#include <libaudcore/probe.h> +#include <libaudcore/runtime.h> + +namespace audqt { + +EXPORT QPixmap art_request (const char * filename, unsigned int w, unsigned int h, bool want_hidpi) +{ + const Index<char> * data = aud_art_request_data (filename); + + if (! data) + { + QString fallback = QString (filename_build + ({aud_get_path (AudPath::DataDir), "images", "album.png"})); + + return QPixmap (fallback); + } + + QImage img = QImage::fromData ((const uchar *) data->begin (), data->len ()); + + aud_art_unref (filename); + + if (! want_hidpi) + return QPixmap::fromImage (img.scaled (w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + + qreal r = qApp->devicePixelRatio (); + + QPixmap pm = QPixmap::fromImage (img.scaled (w * r, h * r, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + pm.setDevicePixelRatio (r); + + return pm; +} + +EXPORT QPixmap art_request_current (unsigned int w, unsigned int h, bool want_hidpi) +{ + int list = aud_playlist_get_playing (); + int entry = aud_playlist_get_position (list); + if (entry < 0) + return QPixmap (); + + String filename = aud_playlist_entry_get_filename (list, entry); + return art_request (filename, w, h, want_hidpi); +} + +} // namespace audqt diff --git a/src/libaudqt/equalizer.cc b/src/libaudqt/equalizer.cc new file mode 100644 index 0000000..cb0556b --- /dev/null +++ b/src/libaudqt/equalizer.cc @@ -0,0 +1,167 @@ +/* + * equalizer.cc + * Copyright 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <QCheckBox> +#include <QDialog> +#include <QFrame> +#include <QHBoxLayout> +#include <QLabel> +#include <QSlider> +#include <QVBoxLayout> + +#include <libaudcore/equalizer.h> +#include <libaudcore/hook.h> +#include <libaudcore/i18n.h> +#include <libaudcore/runtime.h> + +#include "libaudqt.h" + +class EqualizerSlider : public QWidget +{ +public: + EqualizerSlider (const char * label, QWidget * parent); + QSlider slider; +}; + +class EqualizerWindow : public QDialog +{ +public: + EqualizerWindow (); + +private: + QCheckBox m_onoff_checkbox; + EqualizerSlider * m_preamp_slider; + EqualizerSlider * m_sliders[AUD_EQ_NBANDS]; + + void updateActive (); + void updatePreamp (); + void updateBands (); + + const HookReceiver<EqualizerWindow> + hook1 {"set equalizer_active", this, & EqualizerWindow::updateActive}, + hook2 {"set equalizer_preamp", this, & EqualizerWindow::updatePreamp}, + hook3 {"set equalizer_bands", this, & EqualizerWindow::updateBands}; +}; + +EqualizerWindow::EqualizerWindow () : + m_onoff_checkbox (audqt::translate_str (N_("_Enable"))) +{ + const char * const names[AUD_EQ_NBANDS] = {N_("31 Hz"), N_("63 Hz"), + N_("125 Hz"), N_("250 Hz"), N_("500 Hz"), N_("1 kHz"), N_("2 kHz"), + N_("4 kHz"), N_("8 kHz"), N_("16 kHz")}; + + auto slider_container = new QWidget (this); + auto slider_layout = new QHBoxLayout (slider_container); + + m_preamp_slider = new EqualizerSlider (_("Preamp"), this); + slider_layout->addWidget (m_preamp_slider); + + auto line = new QFrame (this); + line->setFrameShape (QFrame::VLine); + line->setFrameShadow (QFrame::Sunken); + slider_layout->addWidget (line); + + for (int i = 0; i < AUD_EQ_NBANDS; i++) + { + m_sliders[i] = new EqualizerSlider (names[i], this); + slider_layout->addWidget (m_sliders[i]); + } + + auto layout = new QVBoxLayout (this); + layout->addWidget (& m_onoff_checkbox); + layout->addWidget (slider_container); + + setWindowTitle (_("Equalizer")); + + updateActive (); + updatePreamp (); + updateBands (); + + connect (& m_onoff_checkbox, & QCheckBox::stateChanged, [] (int state) { + aud_set_bool (nullptr, "equalizer_active", (state == Qt::Checked)); + }); + + connect (& m_preamp_slider->slider, & QSlider::valueChanged, [] (int value) { + aud_set_int (nullptr, "equalizer_preamp", value); + }); + + for (int i = 0; i < AUD_EQ_NBANDS; i++) + { + connect (& m_sliders[i]->slider, & QSlider::valueChanged, [i] (int value) { + aud_eq_set_band (i, value); + }); + } +} + +void EqualizerWindow::updateActive () +{ + bool active = aud_get_bool (nullptr, "equalizer_active"); + m_onoff_checkbox.setCheckState (active ? Qt::Checked : Qt::Unchecked); +} + +void EqualizerWindow::updatePreamp () +{ + m_preamp_slider->slider.setValue (aud_get_int (nullptr, "equalizer_preamp")); +} + +void EqualizerWindow::updateBands () +{ + double values[AUD_EQ_NBANDS]; + aud_eq_get_bands (values); + + for (int i = 0; i < AUD_EQ_NBANDS; i++) + m_sliders[i]->slider.setValue (values[i]); +} + +EqualizerSlider::EqualizerSlider (const char * label, QWidget * parent) : + QWidget (parent), + slider (Qt::Vertical) +{ + slider.setRange (-AUD_EQ_MAX_GAIN, AUD_EQ_MAX_GAIN); + + auto layout = new QVBoxLayout (this); + layout->addWidget (& slider); + layout->addWidget (new QLabel (label, this)); +} + +static EqualizerWindow * s_equalizer = nullptr; + +namespace audqt { + +EXPORT void equalizer_show (void) +{ + if (! s_equalizer) + { + s_equalizer = new EqualizerWindow; + s_equalizer->setAttribute (Qt::WA_DeleteOnClose); + + QObject::connect (s_equalizer, & QObject::destroyed, [] () { + s_equalizer = nullptr; + }); + } + + window_bring_to_front (s_equalizer); +} + +EXPORT void equalizer_hide (void) +{ + delete s_equalizer; +} + +} // namespace audqt diff --git a/src/libaudqt/fileopener.cc b/src/libaudqt/fileopener.cc new file mode 100644 index 0000000..c4d6b5c --- /dev/null +++ b/src/libaudqt/fileopener.cc @@ -0,0 +1,88 @@ +/* + * fileopener.cc + * Copyright 2014 MichaĆ Lipski + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <QFileDialog> + +#include <libaudcore/drct.h> +#include <libaudcore/i18n.h> +#include <libaudcore/runtime.h> + +#include <libaudqt/libaudqt.h> + +namespace audqt { + +static aud::array<FileMode, QFileDialog *> s_dialogs; + +EXPORT void fileopener_show (FileMode mode) +{ + QFileDialog * & dialog = s_dialogs[mode]; + + if (! dialog) + { + static constexpr aud::array<FileMode, const char *> titles { + N_("Open Files"), + N_("Open Folder"), + N_("Add Files"), + N_("Add Folder") + }; + + static constexpr aud::array<FileMode, const char *> labels { + N_("Open"), + N_("Open"), + N_("Add"), + N_("Add") + }; + + static constexpr aud::array<FileMode, QFileDialog::FileMode> modes { + QFileDialog::ExistingFiles, + QFileDialog::Directory, + QFileDialog::ExistingFiles, + QFileDialog::Directory + }; + + String path = aud_get_str ("audgui", "filesel_path"); + dialog = new QFileDialog (nullptr, _(titles[mode]), QString (path)); + + dialog->setAttribute (Qt::WA_DeleteOnClose); + dialog->setFileMode (modes[mode]); + dialog->setLabelText (QFileDialog::Accept, _(labels[mode])); + + QObject::connect (dialog, & QFileDialog::directoryEntered, [] (const QString & path) + { aud_set_str ("audgui", "filesel_path", path.toUtf8 ().constData ()); }); + + QObject::connect (dialog, & QFileDialog::accepted, [dialog, mode] () + { + Index<PlaylistAddItem> files; + for (const QUrl & url : dialog->selectedUrls ()) + files.append (String (url.toEncoded ().constData ())); + + if (mode == FileMode::Add || mode == FileMode::AddFolder) + aud_drct_pl_add_list (std::move (files), -1); + else + aud_drct_pl_open_list (std::move (files)); + }); + + QObject::connect (dialog, & QObject::destroyed, [& dialog] () + { dialog = nullptr; }); + } + + window_bring_to_front (dialog); +} + +} // namespace audqt diff --git a/src/libaudqt/iface.h b/src/libaudqt/iface.h new file mode 100644 index 0000000..ef5e054 --- /dev/null +++ b/src/libaudqt/iface.h @@ -0,0 +1,47 @@ +/* + * iface.h + * Copyright 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDQT_IFACE_H +#define LIBAUDQT_IFACE_H + +#include <libaudcore/plugin.h> +#include <libaudqt/libaudqt.h> + +namespace audqt { + +class QtIfacePlugin : public IfacePlugin +{ +public: + constexpr QtIfacePlugin (PluginInfo info) : IfacePlugin (info) {} + + void show_about_window () { aboutwindow_show (); } + void hide_about_window () { aboutwindow_hide (); } + void show_filebrowser (bool open) { fileopener_show (open ? FileMode::Open : FileMode::Add); } + void hide_filebrowser () {} + void show_jump_to_song () {} + void hide_jump_to_song () {} + void show_prefs_window () { prefswin_show (); } + void hide_prefs_window () { prefswin_hide (); } + void plugin_menu_add (AudMenuID id, void func (), const char * name, const char * icon) {} + void plugin_menu_remove (AudMenuID id, void func ()) {} +}; + +} // namespace audqt + +#endif diff --git a/src/libaudqt/info-widget.cc b/src/libaudqt/info-widget.cc new file mode 100644 index 0000000..24f622d --- /dev/null +++ b/src/libaudqt/info-widget.cc @@ -0,0 +1,205 @@ +/* + * info-widget.h + * Copyright 2006-2014 William Pitcock, Tomasz MoĆ, Eugene Zagidullin, + * John Lindgren, and Thomas Lange + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "info-widget.h" +#include "libaudqt.h" + +#include <QHeaderView> + +#include <libaudcore/i18n.h> +#include <libaudcore/probe.h> + +namespace audqt { + +struct TupleFieldMap { + const char * name; + Tuple::Field field; + bool editable; +}; +const TupleFieldMap tuple_field_map[] = { + {N_("Metadata"), Tuple::Invalid, false}, + {N_("Artist"), Tuple::Artist, true}, + {N_("Album"), Tuple::Album, true}, + {N_("Title"), Tuple::Title, true}, + {N_("Track Number"), Tuple::Track, true}, + {N_("Genre"), Tuple::Genre, true}, + {N_("Comment"), Tuple::Comment, true}, + {N_("Album Artist"), Tuple::AlbumArtist, true}, + {N_("Composer"), Tuple::Composer, true}, + {N_("Performer"), Tuple::Performer, true}, + {N_("Recording Year"), Tuple::Year, true}, + {N_("Recording Date"), Tuple::Date, true}, + + {"", Tuple::Invalid, false}, + {N_("Technical"), Tuple::Invalid, false}, + {N_("Length"), Tuple::Length, false}, + {N_("MIME Type"), Tuple::MIMEType, false}, + {N_("Codec"), Tuple::Codec, false}, + {N_("Quality"), Tuple::Quality, false}, + {N_("Bitrate"), Tuple::Bitrate, false}, +}; + +EXPORT InfoWidget::InfoWidget (QWidget * parent) : QTreeView (parent) +{ + setModel (& m_model); + header ()->hide (); + setEditTriggers (QAbstractItemView::SelectedClicked); + setIndentation (0); +} + +EXPORT InfoWidget::~InfoWidget () +{ +} + +EXPORT void InfoWidget::fillInfo (int playlist, int entry, const char * filename, const Tuple & tuple, + PluginHandle * decoder, bool updating_enabled) +{ + m_model.setTupleData (tuple, String (filename), decoder); +} + +EXPORT bool InfoWidget::updateFile () +{ + return m_model.updateFile (); +} + +InfoModel::InfoModel (QObject * parent) : QAbstractTableModel (parent) +{ +} + +int InfoModel::rowCount (const QModelIndex & parent) const +{ + auto r = ArrayRef<TupleFieldMap> (tuple_field_map); + return r.len; +} + +int InfoModel::columnCount (const QModelIndex & parent) const +{ + return 2; +} + +bool InfoModel::updateFile () const +{ + if (! m_dirty) + return true; + + Tuple t = m_tuple.ref (); + t.set_filename (m_filename); + + return aud_file_write_tuple (m_filename, m_plugin, t); +} + +bool InfoModel::setData (const QModelIndex & index, const QVariant & value, int role) +{ + if (role != Qt::EditRole) + return false; + + Tuple::Field field_id = tuple_field_map [index.row ()].field; + if (field_id == Tuple::Invalid) + return false; + + m_dirty = true; + + auto t = Tuple::field_get_type (field_id); + if (t == Tuple::String) + { + m_tuple.set_str (field_id, value.toString ().toLocal8Bit ()); + emit dataChanged (index, index, {role}); + return true; + } + else if (t == Tuple::Int) + { + m_tuple.set_int (field_id, value.toInt ()); + emit dataChanged (index, index, {role}); + return true; + } + + return false; +} + +QVariant InfoModel::data (const QModelIndex & index, int role) const +{ + Tuple::Field field_id = tuple_field_map [index.row ()].field; + + if (role == Qt::DisplayRole || role == Qt::EditRole) + { + if (index.column () == 0) + return translate_str (tuple_field_map [index.row ()].name); + else if (index.column () == 1 && m_tuple) + { + if (field_id == Tuple::Invalid) + return QVariant (); + + auto t = Tuple::field_get_type (field_id); + + if (t == Tuple::String) + { + const char * res = m_tuple.get_str (field_id); + if (res) + return QString (res); + } + else if (t == Tuple::Int) + { + int res = m_tuple.get_int (field_id); + if (res == -1) + return QVariant (); + return res; + } + } + } + else if (role == Qt::FontRole) + { + if (field_id == Tuple::Invalid) + { + QFont f; + f.setBold (true); + return f; + } + return QVariant (); + } + + return QVariant (); +} + +Qt::ItemFlags InfoModel::flags (const QModelIndex & index) const +{ + if (index.column () == 1) + { + auto & t = tuple_field_map [index.row ()]; + + if (t.field == Tuple::Invalid) + return Qt::ItemNeverHasChildren; + else if (t.editable) + return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled; + + return Qt::ItemIsEnabled; + } + + return Qt::ItemNeverHasChildren; +} + +void InfoModel::setTupleData (const Tuple & tuple, String filename, PluginHandle * plugin) +{ + m_tuple = tuple.ref (); + m_filename = filename; + m_plugin = plugin; + m_dirty = false; +} + +} // namespace audqt diff --git a/src/libaudqt/info-widget.h b/src/libaudqt/info-widget.h new file mode 100644 index 0000000..849b0ca --- /dev/null +++ b/src/libaudqt/info-widget.h @@ -0,0 +1,63 @@ +/* + * info-widget.h + * Copyright 2006-2014 William Pitcock, Tomasz MoĆ, Eugene Zagidullin, + * John Lindgren, and Thomas Lange + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <QAbstractTableModel> +#include <QTreeView> + +#include <libaudcore/tuple.h> + +class PluginHandle; + +namespace audqt { + +class InfoModel : public QAbstractTableModel { +public: + InfoModel (QObject * parent = nullptr); + + int rowCount (const QModelIndex & parent = QModelIndex()) const; + int columnCount (const QModelIndex & parent = QModelIndex()) const; + QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const; + bool setData (const QModelIndex & index, const QVariant & value, int role = Qt::EditRole); + Qt::ItemFlags flags (const QModelIndex & index) const; + + void setTupleData (const Tuple & tuple, String filename, PluginHandle * plugin); + bool updateFile () const; + +private: + Tuple m_tuple; + String m_filename; + PluginHandle * m_plugin; + bool m_dirty; +}; + +class InfoWidget : public QTreeView { +public: + InfoWidget (QWidget * parent = nullptr); + ~InfoWidget (); + + void fillInfo (int playlist, int entry, const char * filename, const Tuple & tuple, + PluginHandle * decoder, bool updating_enabled); + bool updateFile (); + +private: + InfoModel m_model; +}; + +} // namespace audqt diff --git a/src/libaudqt/infowin.cc b/src/libaudqt/infowin.cc new file mode 100644 index 0000000..e71038a --- /dev/null +++ b/src/libaudqt/infowin.cc @@ -0,0 +1,158 @@ +/* + * infowin.cc + * Copyright 2006-2014 William Pitcock, Tomasz MoĆ, Eugene Zagidullin, + * John Lindgren, and Thomas Lange + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <QDialog> +#include <QDialogButtonBox> +#include <QHBoxLayout> +#include <QImage> +#include <QLabel> +#include <QPixmap> +#include <QPushButton> +#include <QVBoxLayout> + +#include <libaudcore/audstrings.h> +#include <libaudcore/hook.h> +#include <libaudcore/i18n.h> +#include <libaudcore/interface.h> +#include <libaudcore/playlist.h> +#include <libaudcore/probe.h> + +#include "info-widget.h" +#include "libaudqt.h" + +namespace audqt { + +class InfoWindow : public QDialog { +public: + InfoWindow (QWidget * parent = nullptr); + + void fillInfo (int playlist, int entry, const char * filename, const Tuple & tuple, + PluginHandle * decoder, bool updating_enabled); + +private: + String m_filename; + QLabel m_image; + InfoWidget m_infowidget; + + void displayImage (const char * filename); + + const HookReceiver<InfoWindow, const char *> + art_hook {"art ready", this, & InfoWindow::displayImage}; +}; + +InfoWindow::InfoWindow (QWidget * parent) : QDialog (parent) +{ + setWindowTitle (_("Song Info")); + + auto hbox = new QHBoxLayout; + hbox->addWidget (& m_image); + hbox->addWidget (& m_infowidget); + + auto vbox = new QVBoxLayout (this); + vbox->addLayout (hbox); + + auto bbox = new QDialogButtonBox (QDialogButtonBox::Save | QDialogButtonBox::Close, this); + bbox->button (QDialogButtonBox::Save)->setText (translate_str (N_("_Save"))); + bbox->button (QDialogButtonBox::Close)->setText (translate_str (N_("_Close"))); + vbox->addWidget (bbox); + + QObject::connect (bbox, & QDialogButtonBox::accepted, [this] () { + m_infowidget.updateFile (); + deleteLater (); + }); + + QObject::connect (bbox, & QDialogButtonBox::rejected, this, & QObject::deleteLater); +} + +void InfoWindow::fillInfo (int playlist, int entry, const char * filename, const Tuple & tuple, + PluginHandle * decoder, bool updating_enabled) +{ + m_filename = String (filename); + displayImage (filename); + m_infowidget.fillInfo (playlist, entry, filename, tuple, decoder, updating_enabled); +} + +void InfoWindow::displayImage (const char * filename) +{ + if (! strcmp_safe (filename, m_filename)) + m_image.setPixmap (art_request (filename)); +} + +static InfoWindow * s_infowin = nullptr; + +EXPORT void infowin_show (int playlist, int entry) +{ + if (! s_infowin) + { + s_infowin = new InfoWindow; + s_infowin->setAttribute (Qt::WA_DeleteOnClose); + + QObject::connect (s_infowin, & QObject::destroyed, [] () { + s_infowin = nullptr; + }); + } + + String filename = aud_playlist_entry_get_filename (playlist, entry); + if (! filename) + return; + + PluginHandle * decoder = aud_playlist_entry_get_decoder (playlist, entry); + if (! decoder) + return; + + Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry); + + if (tuple) + { + tuple.delete_fallbacks (); + s_infowin->fillInfo (playlist, entry, filename, tuple, decoder, + aud_file_can_write_tuple (filename, decoder)); + } + else + aud_ui_show_error (str_printf (_("No info available for %s.\n"), + (const char *) filename)); + + s_infowin->resize (700, 300); + + window_bring_to_front (s_infowin); +} + +EXPORT void infowin_show_current () +{ + int playlist = aud_playlist_get_playing (); + int position; + + if (playlist == -1) + playlist = aud_playlist_get_active (); + + position = aud_playlist_get_position (playlist); + + if (position == -1) + return; + + infowin_show (playlist, position); +} + +EXPORT void infowin_hide () +{ + delete s_infowin; +} + +} // namespace audqt diff --git a/src/libaudqt/libaudqt.h b/src/libaudqt/libaudqt.h new file mode 100644 index 0000000..d646c1f --- /dev/null +++ b/src/libaudqt/libaudqt.h @@ -0,0 +1,104 @@ +/* + * libaudqt.h + * Copyright 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDQT_H +#define LIBAUDQT_H + +#include <QString> +#include <libaudcore/objects.h> + +class QBoxLayout; +class QPixmap; +class QWidget; + +enum class PluginType; +class PluginHandle; +struct PreferencesWidget; + +namespace audqt { + +enum class FileMode { + Open, + OpenFolder, + Add, + AddFolder, + count +}; + +struct MenuItem; + +/* about.cc */ +void aboutwindow_show (); +void aboutwindow_hide (); + +/* playlist-management.cc */ +void playlist_confirm_delete (int playlist); +void playlist_confirm_rename (int playlist); + +/* equalizer.cc */ +void equalizer_show (); +void equalizer_hide (); + +/* fileopener.cc */ +void fileopener_show (FileMode mode); + +/* util.cc */ +void cleanup (); +void window_bring_to_front (QWidget * win); +void simple_message (const char * title, const char * text); +QString translate_str (const char * str, const char * domain); + +#ifdef PACKAGE +static inline QString translate_str (const char * str) + { return translate_str (str, PACKAGE); } +#endif + +/* prefs-builder.cc */ +void prefs_populate (QBoxLayout * layout, ArrayRef<PreferencesWidget> widgets, const char * domain); + +/* prefs-plugin.cc */ +void plugin_about (PluginHandle * ph); +void plugin_prefs (PluginHandle * ph); + +/* prefs-window.cc */ +void prefswin_show (); +void prefswin_hide (); +void prefswin_show_page (int id, bool show = true); +void prefswin_show_plugin_page (PluginType type); + +/* log-inspector.cc */ +void log_inspector_show (); +void log_inspector_hide (); + +/* art.cc */ +QPixmap art_request (const char * filename, unsigned int w = 256, unsigned int h = 256, bool want_hidpi = true); +QPixmap art_request_current (unsigned int w = 256, unsigned int h = 256, bool want_hidpi = true); + +/* infowin.cc */ +void infowin_show (int playlist, int entry); +void infowin_show_current (); +void infowin_hide (); + +/* queue-manager.cc */ +void queue_manager_show (); +void queue_manager_hide (); + +} // namespace audqt + +#endif diff --git a/src/libaudqt/log-inspector.cc b/src/libaudqt/log-inspector.cc new file mode 100644 index 0000000..eeaf31c --- /dev/null +++ b/src/libaudqt/log-inspector.cc @@ -0,0 +1,283 @@ +/* + * log-inspector.cc + * Copyright 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "log-inspector.h" +#include "libaudqt.h" + +#include <QComboBox> +#include <QDialog> +#include <QHBoxLayout> +#include <QLabel> +#include <QVBoxLayout> +#include <QTreeView> +#include <QWidget> + +#include <libaudcore/i18n.h> +#include <libaudcore/index.h> +#include <libaudcore/runtime.h> + +namespace audqt { + +const int LOGENTRY_MAX = 1000; + +enum LogEntryColumn { + Level, + File, + Line, + Function, + Message, + Count +}; + +struct LogEntry +{ + audlog::Level level; + const char * filename; + unsigned int line; + const char * function; + char * message; +}; + +static Index<SmartPtr<LogEntry>> entries; + +static void log_handler (audlog::Level level, const char * file, int line, + const char * func, const char * message); + +/* log entry model */ +LogEntryModel::LogEntryModel (QObject * parent) : QAbstractListModel (parent) +{ +} + +LogEntryModel::~LogEntryModel () +{ +} + +int LogEntryModel::rowCount (const QModelIndex & parent) const +{ + return entries.len (); +} + +int LogEntryModel::columnCount (const QModelIndex & parent) const +{ + return LogEntryColumn::Count; +} + +QVariant LogEntryModel::data (const QModelIndex & index, int role) const +{ + auto & e = entries [index.row ()]; + + if (e && role == Qt::DisplayRole) + { + switch (index.column ()) + { + case LogEntryColumn::Level: return QString (audlog::get_level_name (e->level)); + case LogEntryColumn::File: return QString (e->filename); + case LogEntryColumn::Line: return e->line; + case LogEntryColumn::Function: return QString (e->function); + case LogEntryColumn::Message: return QString (e->message); + } + } + + return QVariant (); +} + +QVariant LogEntryModel::headerData (int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) + { + switch (section) + { + case LogEntryColumn::Level: return QString (_("Level")); + case LogEntryColumn::File: return QString (_("Filename")); + case LogEntryColumn::Line: return QString (_("Line")); + case LogEntryColumn::Function: return QString (_("Function")); + case LogEntryColumn::Message: return QString (_("Message")); + } + } + + return QVariant (); +} + +bool LogEntryModel::insertRows (int row, int count, const QModelIndex & parent) +{ + int last = row + count - 1; + beginInsertRows (parent, row, last); + endInsertRows (); + return true; +} + +bool LogEntryModel::removeRows (int row, int count, const QModelIndex & parent) +{ + int last = row + count - 1; + beginRemoveRows (parent, row, last); + endRemoveRows (); + return true; +} + +void LogEntryModel::updateRows (int row, int count) +{ + int bottom = row + count - 1; + auto topLeft = createIndex (row, 0); + auto bottomRight = createIndex (bottom, columnCount () - 1); + emit dataChanged (topLeft, bottomRight); +} + +void LogEntryModel::updateRow (int row) +{ + updateRows (row, 1); +} + +/* log entry inspector */ +class LogEntryInspector : public QDialog +{ +public: + LogEntryInspector (QWidget * parent = nullptr); + ~LogEntryInspector (); + + audlog::Level m_level; + + void pop (); + void push (); + +private: + QVBoxLayout m_layout; + LogEntryModel * m_model; + QTreeView * m_view; + + QWidget m_bottom_container; + QHBoxLayout m_bottom_layout; + + QComboBox m_level_combobox; + QLabel m_level_label; + + void setLogLevel (audlog::Level level); +}; + +LogEntryInspector::LogEntryInspector (QWidget * parent) : + QDialog (parent) +{ + setWindowTitle (_("Log Inspector")); + setLayout (& m_layout); + + m_model = new LogEntryModel (this); + m_view = new QTreeView (this); + m_view->setModel (m_model); + + m_layout.addWidget (m_view); + + m_bottom_layout.setContentsMargins (0, 0, 0, 0); + + m_level_label.setText (_("Log Level:")); + m_bottom_layout.addWidget (& m_level_label); + + m_level_combobox.addItem (_("Debug"), audlog::Debug); + m_level_combobox.addItem (_("Info"), audlog::Info); + m_level_combobox.addItem (_("Warning"), audlog::Warning); + m_level_combobox.addItem (_("Error"), audlog::Error); + + QObject::connect (& m_level_combobox, + static_cast <void (QComboBox::*) (int)> (&QComboBox::currentIndexChanged), + [this] (int idx) { setLogLevel ((audlog::Level) idx); }); + + m_bottom_layout.addWidget (& m_level_combobox); + + m_bottom_container.setLayout (& m_bottom_layout); + m_layout.addWidget (& m_bottom_container); + + resize (800, 350); + + setLogLevel (audlog::Info); +} + +LogEntryInspector::~LogEntryInspector () +{ + audlog::unsubscribe (log_handler); +} + +static LogEntryInspector * s_inspector = nullptr; + +static void log_handler (audlog::Level level, const char * file, int line, + const char * func, const char * message) +{ + LogEntry * l = new LogEntry; + + l->level = level; + l->filename = file; + l->line = line; + l->function = func; + + l->message = strdup(message); + l->message[strlen (l->message) - 1] = 0; + + entries.append (SmartPtr<LogEntry> (l)); + + if (entries.len () > LOGENTRY_MAX) + { + s_inspector->pop (); + entries.erase (0, 1); + } + + s_inspector->push (); +} + +void LogEntryInspector::setLogLevel (audlog::Level level) +{ + m_level = level; + + audlog::unsubscribe (log_handler); + audlog::subscribe (log_handler, level); + + m_level_combobox.setCurrentIndex (m_level); +} + +void LogEntryInspector::pop () +{ + m_model->removeRows (0, 1); +} + +void LogEntryInspector::push () +{ + m_model->insertRows (entries.len (), 1); +#ifdef XXX_NOTYET + auto index = m_model->index (entries.len () - 1); + m_view->scrollTo (index); +#endif +} + +EXPORT void log_inspector_show () +{ + if (! s_inspector) + { + s_inspector = new LogEntryInspector; + s_inspector->setAttribute (Qt::WA_DeleteOnClose); + + QObject::connect (s_inspector, & QObject::destroyed, [] () { + s_inspector = nullptr; + }); + } + + window_bring_to_front (s_inspector); +} + +EXPORT void log_inspector_hide () +{ + delete s_inspector; +} + +} // namespace audqt diff --git a/src/libaudqt/log-inspector.h b/src/libaudqt/log-inspector.h new file mode 100644 index 0000000..15beb78 --- /dev/null +++ b/src/libaudqt/log-inspector.h @@ -0,0 +1,46 @@ +/* + * log-inspector.h + * Copyright 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <QAbstractListModel> + +#ifndef LIBAUDQT_LOG_INSPECTOR_H +#define LIBAUDQT_LOG_INSPECTOR_H + +namespace audqt { + +class LogEntryModel : public QAbstractListModel +{ +public: + LogEntryModel (QObject * parent = nullptr); + ~LogEntryModel (); + + int rowCount (const QModelIndex & parent = QModelIndex ()) const; + int columnCount (const QModelIndex & parent = QModelIndex ()) const; + QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const; + QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + bool insertRows (int row, int count, const QModelIndex & parent = QModelIndex ()); + bool removeRows (int row, int count, const QModelIndex & parent = QModelIndex ()); + void updateRows (int row, int count); + void updateRow (int row); +}; + +} // namespace audqt + +#endif diff --git a/src/libaudqt/menu.cc b/src/libaudqt/menu.cc new file mode 100644 index 0000000..2d04ab1 --- /dev/null +++ b/src/libaudqt/menu.cc @@ -0,0 +1,125 @@ +/* + * menu.h + * Copyright 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "libaudqt.h" +#include "libaudqt/menu.h" + +#include <QAction> +#include <QIcon> +#include <QMenu> +#include <QMenuBar> + +#include <libaudcore/hook.h> +#include <libaudcore/runtime.h> + +namespace audqt { + +class MenuAction : public QAction +{ +public: + MenuAction (const MenuItem & item, const char * domain, QWidget * parent); + +private: + void toggle (bool checked); + void update (); + + const MenuItem & m_item; + SmartPtr<HookReceiver<MenuAction>> m_hook; +}; + +MenuAction::MenuAction (const MenuItem & item, const char * domain, QWidget * parent) : + QAction (parent), + m_item (item) +{ + if (item.sep) + { + setSeparator (true); + return; + } + + setText (translate_str (item.text.name, domain)); + + if (item.func) + QObject::connect (this, & QAction::triggered, item.func); + else if (item.cfg.name) + { + setCheckable (true); + setChecked (aud_get_bool (item.cfg.sect, item.cfg.name)); + + QObject::connect (this, & QAction::toggled, this, & MenuAction::toggle); + + if (item.cfg.hook) + m_hook.capture (new HookReceiver<MenuAction> (item.cfg.hook, this, & MenuAction::update)); + } + else if (item.items.len) + setMenu (menu_build (item.items, domain, parent)); + else if (item.submenu) + setMenu (item.submenu ()); + +#ifndef Q_OS_MAC + if (item.text.icon && QIcon::hasThemeIcon (item.text.icon)) + setIcon (QIcon::fromTheme (item.text.icon)); +#endif + + if (item.text.shortcut) + setShortcut (QString (item.text.shortcut)); +} + +void MenuAction::toggle (bool checked) +{ + if (aud_get_bool (m_item.cfg.sect, m_item.cfg.name) != checked) + { + aud_set_bool (m_item.cfg.sect, m_item.cfg.name, checked); + + if (m_item.func) + m_item.func (); + } +} + +void MenuAction::update () +{ + setChecked (aud_get_bool (m_item.cfg.sect, m_item.cfg.name)); +} + +EXPORT QAction * menu_action (const MenuItem & menu_item, const char * domain, QWidget * parent) +{ + return new MenuAction (menu_item, domain, parent); +} + +EXPORT QMenu * menu_build (ArrayRef<MenuItem> menu_items, const char * domain, QWidget * parent) +{ + QMenu * m = new QMenu (parent); + + for (auto & it : menu_items) + m->addAction (new MenuAction (it, domain, m)); + + return m; +} + +EXPORT QMenuBar * menubar_build (ArrayRef<MenuItem> menu_items, const char * domain, QWidget * parent) +{ + QMenuBar * m = new QMenuBar (parent); + + for (auto & it : menu_items) + m->addAction (new MenuAction (it, domain, m)); + + return m; +} + +} // namespace audqt diff --git a/src/libaudqt/menu.h b/src/libaudqt/menu.h new file mode 100644 index 0000000..b7f95c3 --- /dev/null +++ b/src/libaudqt/menu.h @@ -0,0 +1,95 @@ +/* + * menu.h + * Copyright 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "libaudqt.h" + +#ifndef LIBAUDQT_MENU_H +#define LIBAUDQT_MENU_H + +#include <libaudcore/objects.h> + +class QAction; +class QMenu; +class QMenuBar; +class QWidget; + +enum class AudMenuID; + +namespace audqt { + +struct MenuItemText { + const char * name; + const char * icon; + const char * shortcut; +}; + +struct MenuItemConfig { + const char * sect; + const char * name; + const char * hook; +}; + +struct MenuItem { + MenuItemText text; + void (* func) (); + + /* for toggle items */ + MenuItemConfig cfg; + + /* for submenus */ + ArrayRef<MenuItem> items; + + /* for custom submenus */ + QMenu * (* submenu) (); + + /* for separators */ + bool sep; +}; + +constexpr MenuItem MenuCommand (MenuItemText text, void (* func) ()) + { return {text, func}; } +constexpr MenuItem MenuToggle (MenuItemText text, MenuItemConfig cfg, void (* func) () = nullptr) + { return {text, func, cfg}; } +constexpr MenuItem MenuSub (MenuItemText text, ArrayRef<MenuItem> items) + { return {text, nullptr, {}, items}; } +constexpr MenuItem MenuSub (MenuItemText text, QMenu * (* submenu) ()) + { return {text, nullptr, {}, nullptr, submenu}; } +constexpr MenuItem MenuSep () + { return {{}, nullptr, {}, nullptr, nullptr, true}; } + +/* menu.cc */ +QAction * menu_action (const MenuItem & menu_item, const char * domain, QWidget * parent = nullptr); +QMenu * menu_build (ArrayRef<MenuItem> menu_items, const char * domain, QWidget * parent = nullptr); +QMenuBar * menubar_build (ArrayRef<MenuItem> menu_items, const char * domain, QWidget * parent = nullptr); + +#ifdef PACKAGE +static inline QMenu * menu_build (ArrayRef<MenuItem> menu_items, QWidget * parent = nullptr) + { return menu_build (menu_items, PACKAGE, parent); } +static inline QMenuBar * menubar_build (ArrayRef<MenuItem> menu_items, QWidget * parent = nullptr) + { return menubar_build (menu_items, PACKAGE, parent); } +#endif + +/* plugin-menu.cc */ +QMenu * menu_get_by_id (AudMenuID id); +void menu_add (AudMenuID id, void (* func) (void), const char * name, const char * icon); +void menu_remove (AudMenuID id, void (* func) (void)); + +} // namespace audqt + +#endif diff --git a/src/libaudqt/playlist-management.cc b/src/libaudqt/playlist-management.cc new file mode 100644 index 0000000..04417f7 --- /dev/null +++ b/src/libaudqt/playlist-management.cc @@ -0,0 +1,90 @@ +/* + * playlist-management.cc + * Copyright 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "libaudqt.h" + +#include <QCheckBox> +#include <QDialog> +#include <QDialogButtonBox> +#include <QLabel> +#include <QPushButton> +#include <QVBoxLayout> + +#include <libaudcore/audstrings.h> +#include <libaudcore/i18n.h> +#include <libaudcore/runtime.h> +#include <libaudcore/playlist.h> + +namespace audqt { + +static QDialog * buildDeleteDialog (int playlist) +{ + auto dialog = new QDialog; + + auto prompt = new QLabel ((const char *) str_printf + (_("Do you want to permanently remove â%sâ?"), + (const char *) aud_playlist_get_title (playlist)), dialog); + + auto skip_prompt = new QCheckBox (translate_str (N_("_Donât ask again")), dialog); + + auto remove = new QPushButton (translate_str (N_("_Remove")), dialog); + auto cancel = new QPushButton (translate_str (N_("_Cancel")), dialog); + + auto buttonbox = new QDialogButtonBox (dialog); + buttonbox->addButton (remove, QDialogButtonBox::AcceptRole); + buttonbox->addButton (cancel, QDialogButtonBox::RejectRole); + + int id = aud_playlist_get_unique_id (playlist); + QObject::connect (buttonbox, &QDialogButtonBox::accepted, [dialog, id] () { + int list = aud_playlist_by_unique_id (id); + if (list >= 0) + aud_playlist_delete (list); + dialog->close (); + }); + + QObject::connect (buttonbox, &QDialogButtonBox::rejected, dialog, &QDialog::close); + + QObject::connect (skip_prompt, &QCheckBox::stateChanged, [] (int state) { + aud_set_bool ("audgui", "no_confirm_playlist_delete", (state == Qt::Checked)); + }); + + auto layout = new QVBoxLayout (dialog); + layout->addWidget (prompt); + layout->addWidget (skip_prompt); + layout->addWidget (buttonbox); + + dialog->setWindowTitle (_("Remove Playlist")); + + return dialog; +} + +EXPORT void playlist_confirm_delete (int playlist) +{ + if (aud_get_bool ("audgui", "no_confirm_playlist_delete")) + { + aud_playlist_delete (playlist); + return; + } + + auto dialog = buildDeleteDialog (playlist); + dialog->setAttribute (Qt::WA_DeleteOnClose); + dialog->show (); +} + +} // namespace audqt diff --git a/src/libaudqt/plugin-menu.cc b/src/libaudqt/plugin-menu.cc new file mode 100644 index 0000000..2557e55 --- /dev/null +++ b/src/libaudqt/plugin-menu.cc @@ -0,0 +1,78 @@ +/* + * plugin-menu.cc + * Copyright 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "libaudqt.h" +#include "libaudqt/menu.h" + +#include <QMenu> + +#include <libaudcore/i18n.h> +#include <libaudcore/interface.h> +#include <libaudcore/plugins.h> + +namespace audqt { + +static aud::array<AudMenuID, Index<SmartPtr<MenuItem>>> items; +static aud::array<AudMenuID, QMenu *> menus; + +static void show_prefs (void) +{ + prefswin_show_plugin_page (PluginType::General); +} + +MenuItem default_menu_items[] = { + MenuCommand ({N_("Plugins ...")}, show_prefs), + MenuSep (), +}; + +EXPORT QMenu * menu_get_by_id (AudMenuID id) +{ + if (menus[id]) + return menus[id]; + + menus[id] = new QMenu (translate_str ("Services")); + + for (auto & item : default_menu_items) + menus[id]->addAction (menu_action (item, PACKAGE, menus[id])); + + for (auto & item : items[id]) + menus[id]->addAction (menu_action (* item, nullptr, menus[id])); + + return menus[id]; +} + +EXPORT void menu_add (AudMenuID id, void (* func) (void), const char * name, const char * icon) +{ + MenuItem * item = new MenuItem (MenuCommand ({name, icon}, func)); + items[id].append (item); + + if (menus[id]) + menus[id]->addAction (menu_action (* item, nullptr, menus[id])); +} + +EXPORT void menu_remove (AudMenuID id, void (* func) (void)) +{ + // FIXME: remove the QAction + auto is_match = [func] (SmartPtr<MenuItem> & item) + { return item->func == func; }; + + items[id].remove_if (is_match, true); +} + +} // namespace audqt diff --git a/src/libaudqt/prefs-builder.cc b/src/libaudqt/prefs-builder.cc new file mode 100644 index 0000000..f3ad994 --- /dev/null +++ b/src/libaudqt/prefs-builder.cc @@ -0,0 +1,142 @@ +/* + * prefs-builder.cc + * Copyright 2014 William Pitcock and John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "libaudqt.h" +#include "prefs-widget.h" + +#include <QButtonGroup> +#include <QFrame> +#include <QLayout> + +#include <libaudcore/i18n.h> +#include <libaudcore/preferences.h> +#include <libaudcore/runtime.h> + +namespace audqt { + +void prefs_populate (QBoxLayout * layout, ArrayRef<PreferencesWidget> widgets, const char * domain) +{ + QButtonGroup * radio_btn_group = nullptr; + + for (const PreferencesWidget & w : widgets) + { + if (radio_btn_group && w.type != PreferencesWidget::RadioButton) + radio_btn_group = nullptr; + + switch (w.type) + { + case PreferencesWidget::Button: { + ButtonWidget * bw = new ButtonWidget (& w, domain); + layout->addWidget (bw->widget ()); + break; + } + case PreferencesWidget::CheckButton: { + BooleanWidget * bw = new BooleanWidget (& w, domain); + layout->addWidget (bw->widget ()); + break; + } + case PreferencesWidget::Label: { + LabelWidget * lw = new LabelWidget (& w, domain); + layout->addWidget (lw->widget ()); + break; + } + case PreferencesWidget::SpinButton: { + switch (w.cfg.type) { + case WidgetConfig::Int: { + IntegerWidget * iw = new IntegerWidget (& w, domain); + layout->addWidget (iw->widget ()); + break; + } + case WidgetConfig::Float: { + DoubleWidget * dw = new DoubleWidget (& w, domain); + layout->addWidget (dw->widget ()); + break; + } + default: + AUDDBG("encountered unhandled configuration type %d for PreferencesWidget::SpinButton\n", w.cfg.type); + break; + } + break; + } + case PreferencesWidget::Entry: { + StringWidget * sw = new StringWidget (& w, domain); + layout->addWidget (sw->widget ()); + break; + } + case PreferencesWidget::RadioButton: { + if (! radio_btn_group) + radio_btn_group = new QButtonGroup; + + RadioButtonWidget * rw = new RadioButtonWidget (& w, domain); + layout->addWidget (rw->widget (radio_btn_group)); + break; + } + case PreferencesWidget::FontButton: { + /* XXX: unimplemented */ + AUDDBG("font buttons are unimplemented\n"); + break; + } + case PreferencesWidget::ComboBox: { + ComboBoxWidget * cw = new ComboBoxWidget (& w, domain); + layout->addWidget (cw->widget ()); + break; + } + case PreferencesWidget::CustomQt: { + if (w.data.populate) + { + QWidget * wid = (QWidget *) w.data.populate (); + layout->addWidget (wid); + } + break; + } + + /* layout widgets follow */ + case PreferencesWidget::Box: { + BoxWidget * bw = new BoxWidget (& w, domain); + layout->addWidget (bw->widget ()); + break; + } + case PreferencesWidget::Table: { + TableWidget * tw = new TableWidget (& w, domain); + layout->addWidget (tw->widget ()); + break; + } + case PreferencesWidget::Notebook: { + NotebookWidget * nw = new NotebookWidget (& w, domain); + layout->addWidget (nw->widget ()); + break; + } + case PreferencesWidget::Separator: { + QFrame * f = new QFrame; + f->setFrameShape (w.data.separator.horizontal ? QFrame::HLine : QFrame::VLine); + layout->addWidget (f); + break; + } + + /* stub handler */ + default: + AUDDBG("invoked stub handler for PreferencesWidget type %d\n", w.type); + break; + } + } + + layout->addStretch (1); +} + +} // namespace audqt diff --git a/src/libaudqt/prefs-plugin.cc b/src/libaudqt/prefs-plugin.cc new file mode 100644 index 0000000..a014fbb --- /dev/null +++ b/src/libaudqt/prefs-plugin.cc @@ -0,0 +1,145 @@ +/* + * prefs-plugin.cc + * Copyright 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <QDialog> +#include <QDialogButtonBox> +#include <QPushButton> +#include <QVBoxLayout> + +#include <libaudcore/audstrings.h> +#include <libaudcore/i18n.h> +#include <libaudcore/plugin.h> +#include <libaudcore/plugins.h> +#include <libaudcore/preferences.h> +#include <libaudcore/runtime.h> + +#include "libaudqt.h" + +namespace audqt { + +EXPORT void plugin_about (PluginHandle * ph) +{ + Plugin * header = (Plugin *) aud_plugin_get_header (ph); + + if (! header) + return; + + const char * name = header->info.name; + const char * text = header->info.about; + if (! text) + return; + + if (header->info.domain) + { + name = dgettext (header->info.domain, name); + text = dgettext (header->info.domain, text); + } + + AUDDBG ("name = %s\n", name); + + simple_message (str_printf (_("About %s"), name), text); +} + +struct ConfigWindow { + PluginHandle * ph; + QDialog * root; +}; + +static Index<ConfigWindow *> config_windows; + +static ConfigWindow * find_config_window (PluginHandle * ph) +{ + for (ConfigWindow * cw : config_windows) + { + if (cw && cw->ph == ph) + return cw; + } + + return nullptr; +} + +EXPORT void plugin_prefs (PluginHandle * ph) +{ + ConfigWindow * cw = find_config_window (ph); + + if (cw) + { + window_bring_to_front (cw->root); + return; + } + + Plugin * header = (Plugin *) aud_plugin_get_header (ph); + if (! header) + return; + + const PluginPreferences * p = header->info.prefs; + if (! p) + return; + + cw = new ConfigWindow; + config_windows.append (cw); + + cw->ph = ph; + cw->root = new QDialog; + + if (p->init) + p->init (); + + const char * name = header->info.name; + if (header->info.domain) + name = dgettext (header->info.domain, name); + + cw->root->setWindowTitle ((const char *) str_printf(_("%s Settings"), name)); + + QVBoxLayout * vbox = new QVBoxLayout (cw->root); + + vbox->setSpacing (4); + prefs_populate (vbox, p->widgets, header->info.domain); + + QDialogButtonBox * bbox = new QDialogButtonBox; + + if (p->apply) + { + bbox->setStandardButtons (QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + bbox->button (QDialogButtonBox::Ok)->setText (translate_str (N_("_Set"))); + bbox->button (QDialogButtonBox::Cancel)->setText (translate_str (N_("_Cancel"))); + + QObject::connect (bbox, &QDialogButtonBox::accepted, [=] () { + if (p->apply) + p->apply (); + + cw->root->hide (); + }); + + QObject::connect (bbox, &QDialogButtonBox::rejected, cw->root, &QWidget::hide); + } + else + { + bbox->setStandardButtons (QDialogButtonBox::Close); + bbox->button (QDialogButtonBox::Close)->setText (translate_str (N_("_Close"))); + + QObject::connect (bbox, &QDialogButtonBox::rejected, cw->root, &QWidget::hide); + } + + vbox->addWidget (bbox); + + window_bring_to_front (cw->root); +} + +} // namespace audqt diff --git a/src/libaudqt/prefs-pluginlist-model.cc b/src/libaudqt/prefs-pluginlist-model.cc new file mode 100644 index 0000000..af542ba --- /dev/null +++ b/src/libaudqt/prefs-pluginlist-model.cc @@ -0,0 +1,138 @@ +/* + * prefs-pluginlist-model.cc + * Copyright 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "prefs-pluginlist-model.h" + +#include <QIcon> +#include <libaudcore/plugins.h> + +namespace audqt { + +PluginListModel::PluginListModel (QObject * parent, PluginType category_id) : QAbstractListModel (parent), + m_list (aud_plugin_list (category_id)) +{ + +} + +PluginListModel::~PluginListModel () +{ + +} + +int PluginListModel::rowCount (const QModelIndex & parent) const +{ + return m_list.len (); +} + +int PluginListModel::columnCount (const QModelIndex & parent) const +{ + return NumColumns; +} + +QVariant PluginListModel::headerData (int section, Qt::Orientation orientation, int role) const +{ + return QVariant (); +} + +QVariant PluginListModel::data (const QModelIndex &index, int role) const +{ + int row = index.row (); + if (row < 0 || row >= m_list.len ()) + return QVariant (); + + PluginHandle * p = m_list[row]; + bool enabled = aud_plugin_get_enabled (p); + + switch (index.column ()) + { + case NameColumn: + if (role == Qt::DisplayRole) + return QString (aud_plugin_get_name (p)); + if (role == Qt::CheckStateRole) + return enabled ? Qt::Checked : Qt::Unchecked; + + break; + + case AboutColumn: + if (role == Qt::DecorationRole && enabled && aud_plugin_has_about (p)) + return QIcon::fromTheme ("dialog-information"); + + break; + + case SettingsColumn: + if (role == Qt::DecorationRole && enabled && aud_plugin_has_configure (p)) + return QIcon::fromTheme ("preferences-system"); + + break; + + } + + return QVariant (); +} + +bool PluginListModel::setData (const QModelIndex &index, const QVariant &value, int role) +{ + int row = index.row (); + if (row < 0 || row >= m_list.len ()) + return false; + + if (role == Qt::CheckStateRole) + { + aud_plugin_enable (m_list[row], value.toUInt() != Qt::Unchecked); + } + + emit dataChanged (index, index.sibling (index.row (), NumColumns - 1)); + return true; +} + +Qt::ItemFlags PluginListModel::flags (const QModelIndex & index) const +{ + return (Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); +} + +bool PluginListModel::insertRows (int row, int count, const QModelIndex & parent) +{ + int last = row + count - 1; + beginInsertRows (parent, row, last); + endInsertRows (); + return true; +} + +bool PluginListModel::removeRows (int row, int count, const QModelIndex & parent) +{ + int last = row + count - 1; + beginRemoveRows (parent, row, last); + endRemoveRows (); + return true; +} + +void PluginListModel::updateRows (int row, int count) +{ + int bottom = row + count - 1; + auto topLeft = createIndex (row, 0); + auto bottomRight = createIndex (bottom, columnCount () - 1); + emit dataChanged (topLeft, bottomRight); +} + +void PluginListModel::updateRow (int row) +{ + updateRows (row, 1); +} + +} // namespace audqt diff --git a/src/libaudqt/prefs-pluginlist-model.h b/src/libaudqt/prefs-pluginlist-model.h new file mode 100644 index 0000000..0d2f093 --- /dev/null +++ b/src/libaudqt/prefs-pluginlist-model.h @@ -0,0 +1,62 @@ +/* + * prefs-pluginlist-model.h + * Copyright 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef PREFS_PLUGINLIST_MODEL_H +#define PREFS_PLUGINLIST_MODEL_H + +#include <QAbstractListModel> +#include <libaudcore/index.h> + +enum class PluginType; +class PluginHandle; + +namespace audqt { + +class PluginListModel : public QAbstractListModel +{ +public: + enum { + NameColumn, + AboutColumn, + SettingsColumn, + NumColumns + }; + + PluginListModel (QObject * parent, PluginType category_id); + ~PluginListModel (); + + int rowCount (const QModelIndex & parent = QModelIndex ()) const; + int columnCount (const QModelIndex & parent = QModelIndex ()) const; + QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const; + QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + bool setData (const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole); + Qt::ItemFlags flags (const QModelIndex & parent = QModelIndex ()) const; + + bool insertRows (int row, int count, const QModelIndex & parent = QModelIndex ()); + bool removeRows (int row, int count, const QModelIndex & parent = QModelIndex ()); + void updateRows (int row, int count); + void updateRow (int row); + +private: + const Index<PluginHandle *> & m_list; +}; + +} // namespace audqt + +#endif diff --git a/src/libaudqt/prefs-widget.cc b/src/libaudqt/prefs-widget.cc new file mode 100644 index 0000000..f15a214 --- /dev/null +++ b/src/libaudqt/prefs-widget.cc @@ -0,0 +1,356 @@ +/* + * prefs-widget.cc + * Copyright 2007-2014 Tomasz MoĆ, William Pitcock, and John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "prefs-widget.h" +#include "libaudqt.h" + +#include <assert.h> + +#include <QButtonGroup> +#include <QCheckBox> +#include <QComboBox> +#include <QHBoxLayout> +#include <QLabel> +#include <QLineEdit> +#include <QPushButton> +#include <QRadioButton> +#include <QSpinBox> +#include <QTabWidget> +#include <QVBoxLayout> + +#include <libaudcore/audstrings.h> +#include <libaudcore/i18n.h> + +namespace audqt { + +/* button */ +QWidget * ButtonWidget::widget () +{ + auto button = new QPushButton (translate_str (m_parent->label, m_domain)); + QObject::connect (button, & QPushButton::clicked, m_parent->data.button.callback); + return button; +} + +/* boolean widget (checkbox) */ +QWidget * BooleanWidget::widget () +{ + m_checkbox = new QCheckBox (translate_str (m_parent->label, m_domain)); + + update (); + + QObject::connect (m_checkbox, & QCheckBox::stateChanged, [this] (int state) { + m_parent->cfg.set_bool ((state != Qt::Unchecked)); + }); + + return m_checkbox; +} + +void BooleanWidget::update () +{ + m_checkbox->setCheckState (m_parent->cfg.get_bool () ? Qt::Checked : Qt::Unchecked); +} + +/* label */ +QWidget * LabelWidget::widget () +{ + return new QLabel (translate_str (m_parent->label, m_domain)); +} + +/* integer (radio button) */ +QWidget * RadioButtonWidget::widget (QButtonGroup * btn_group) +{ + m_radio = new QRadioButton (translate_str (m_parent->label, m_domain)); + + if (btn_group) + btn_group->addButton (m_radio, m_parent->data.radio_btn.value); + + update (); + + QObject::connect (m_radio, & QAbstractButton::clicked, [this] (bool checked) { + if (checked) + m_parent->cfg.set_int (m_parent->data.radio_btn.value); + }); + + return m_radio; +} + +void RadioButtonWidget::update () +{ + if (m_parent->cfg.get_int () == m_parent->data.radio_btn.value) + m_radio->setChecked (true); +} + +/* integer (spinbox) */ +QWidget * IntegerWidget::widget () +{ + auto container = new QWidget; + auto layout = new QHBoxLayout (container); + + layout->setContentsMargins (0, 0, 0, 0); + layout->setSpacing (4); + + if (m_parent->label) + layout->addWidget (new QLabel (translate_str (m_parent->label, m_domain))); + + m_spinner = new QSpinBox; + layout->addWidget (m_spinner); + + if (m_parent->data.spin_btn.right_label) + layout->addWidget (new QLabel (translate_str + (m_parent->data.spin_btn.right_label, m_domain))); + + layout->addStretch (1); + + update (); + + /* + * Qt has two different valueChanged signals for spin boxes. So we have to do an explicit + * cast to the type of the correct valueChanged signal. --kaniini. + */ + void (QSpinBox::* signal) (int) = & QSpinBox::valueChanged; + QObject::connect (m_spinner, signal, [this] (int value) { + m_parent->cfg.set_int (value); + }); + + return container; +} + +void IntegerWidget::update () +{ + m_spinner->setRange ((int) m_parent->data.spin_btn.min, (int) m_parent->data.spin_btn.max); + m_spinner->setSingleStep ((int) m_parent->data.spin_btn.step); + m_spinner->setValue (m_parent->cfg.get_int ()); +} + +/* double (spinbox) */ +QWidget * DoubleWidget::widget () +{ + auto container = new QWidget; + auto layout = new QHBoxLayout (container); + + layout->setContentsMargins (0, 0, 0, 0); + layout->setSpacing (4); + + if (m_parent->label) + layout->addWidget (new QLabel (translate_str (m_parent->label, m_domain))); + + m_spinner = new QDoubleSpinBox; + layout->addWidget (m_spinner); + + if (m_parent->data.spin_btn.right_label) + layout->addWidget (new QLabel (translate_str + (m_parent->data.spin_btn.right_label, m_domain))); + + layout->addStretch (1); + + update (); + + void (QDoubleSpinBox::* signal) (double) = & QDoubleSpinBox::valueChanged; + QObject::connect (m_spinner, signal, [this] (double value) { + m_parent->cfg.set_float (value); + }); + + return container; +} + +void DoubleWidget::update () +{ + m_spinner->setRange (m_parent->data.spin_btn.min, m_parent->data.spin_btn.max); + m_spinner->setSingleStep (m_parent->data.spin_btn.step); + m_spinner->setValue (m_parent->cfg.get_float ()); +} + +/* string (lineedit) */ +QWidget * StringWidget::widget () +{ + auto container = new QWidget; + auto layout = new QHBoxLayout (container); + + layout->setContentsMargins (0, 0, 0, 0); + layout->setSpacing (4); + + if (m_parent->label) + layout->addWidget (new QLabel (translate_str (m_parent->label, m_domain))); + + m_lineedit = new QLineEdit; + if (m_parent->data.entry.password) + m_lineedit->setEchoMode (QLineEdit::Password); + + layout->addWidget (m_lineedit, 1); + + update (); + + QObject::connect (m_lineedit, & QLineEdit::textChanged, [this] (const QString & value) { + m_parent->cfg.set_string (value.toUtf8 ()); + }); + + return container; +} + +void StringWidget::update () +{ + m_lineedit->setText ((const char *) m_parent->cfg.get_string ()); +} + +/* combo box widget (string or int) */ +QWidget * ComboBoxWidget::widget () +{ + auto container = new QWidget; + auto layout = new QHBoxLayout (container); + + layout->setContentsMargins (0, 0, 0, 0); + layout->setSpacing (4); + + if (m_parent->label) + layout->addWidget (new QLabel (translate_str (m_parent->label, m_domain))); + + m_combobox = new QComboBox; + layout->addWidget (m_combobox); + layout->addStretch (1); + + update (); + + void (QComboBox::* signal) (int) = & QComboBox::currentIndexChanged; + QObject::connect (m_combobox, signal, [this] (int idx) { + QVariant data = m_combobox->itemData (idx); + + switch (m_parent->cfg.type) { + case WidgetConfig::Int: + m_parent->cfg.set_int (data.toInt ()); + break; + case WidgetConfig::String: + m_parent->cfg.set_string (data.toString ().toUtf8 ()); + break; + default: + break; + } + }); + + return container; +} + +void ComboBoxWidget::update () +{ + ArrayRef<ComboItem> items = m_parent->data.combo.elems; + + if (m_parent->data.combo.fill) + items = m_parent->data.combo.fill (); + + m_combobox->clear (); + + /* add combobox items */ + switch (m_parent->cfg.type) { + case WidgetConfig::Int: + for (const ComboItem & item : items) + m_combobox->addItem (translate_str (item.label, m_domain), item.num); + break; + case WidgetConfig::String: + for (const ComboItem & item : items) + m_combobox->addItem (translate_str (item.label, m_domain), item.str); + break; + default: + break; + } + + /* set selected index */ + switch (m_parent->cfg.type) { + case WidgetConfig::Int: { + int num = m_parent->cfg.get_int (); + + for (int i = 0; i < items.len; i++) + { + if (items.data[i].num == num) + { + m_combobox->setCurrentIndex (i); + break; + } + } + + break; + } + case WidgetConfig::String: { + String str = m_parent->cfg.get_string (); + + for (int i = 0; i < items.len; i++) + { + if (! strcmp_safe (items.data[i].str, str)) + { + m_combobox->setCurrentIndex (i); + break; + } + } + + break; + } + default: + break; + } +} + +/* layout widgets */ +QWidget * BoxWidget::widget () +{ + auto container = new QWidget; + + QBoxLayout * layout; + if (m_parent->data.box.horizontal) + layout = new QHBoxLayout (container); + else + layout = new QVBoxLayout (container); + + layout->setContentsMargins (0, 0, 0, 0); + layout->setSpacing (4); + prefs_populate (layout, m_parent->data.box.widgets, m_domain); + + return container; +} + +QWidget * TableWidget::widget () +{ + // TODO: proper table layout + auto container = new QWidget; + auto layout = new QVBoxLayout (container); + + layout->setContentsMargins (0, 0, 0, 0); + layout->setSpacing (4); + prefs_populate (layout, m_parent->data.table.widgets, m_domain); + + return container; +} + +QWidget * NotebookWidget::widget () +{ + auto tabs = new QTabWidget; + + for (const NotebookTab & tab : m_parent->data.notebook.tabs) + { + auto widget = new QWidget (tabs); + auto layout = new QVBoxLayout (widget); + + layout->setContentsMargins (0, 0, 0, 0); + layout->setSpacing (4); + prefs_populate (layout, tab.widgets, nullptr); + + tabs->addTab (widget, translate_str (tab.name, m_domain)); + } + + return tabs; +} + +} // namespace audqt diff --git a/src/libaudqt/prefs-widget.h b/src/libaudqt/prefs-widget.h new file mode 100644 index 0000000..a26c755 --- /dev/null +++ b/src/libaudqt/prefs-widget.h @@ -0,0 +1,195 @@ +/* + * prefs-widget.cc + * Copyright 2007-2014 Tomasz MoĆ, William Pitcock, and John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDQT_PREFS_WIDGET_H +#define LIBAUDQT_PREFS_WIDGET_H + +#include <libaudcore/preferences.h> +#include <libaudcore/hook.h> + +class QButtonGroup; +class QCheckBox; +class QComboBox; +class QDoubleSpinBox; +class QLineEdit; +class QRadioButton; +class QSpinBox; +class QWidget; + +namespace audqt { + +/* + * basic idea is this. create classes which wrap the PreferencesWidgets. + * Each should have it's own get(), set() and widget() methods. those are the + * functions that we really care about. + * get() and set() allow for introspection and manipulation of the underlying + * objects. they also handle pinging the plugin which owns the PreferencesWidget, + * i.e. calling PreferencesWidget::callback(). + * widget() builds the actual Qt side of the widget, hooks up the relevant signals + * to slots, etc. the result of widget() is not const as it is linked into a + * layout manager or shown or whatever. + */ + +/* base class which provides plumbing for hooks. */ +class HookableWidget { +protected: + HookableWidget (const PreferencesWidget * parent, const char * domain) : + m_parent (parent), m_domain (domain) + { + if (m_parent->cfg.hook) + hook.capture (new HookReceiver<HookableWidget> + {m_parent->cfg.hook, this, & HookableWidget::update}); + } + + virtual ~HookableWidget () {} + virtual void update () {} + + const PreferencesWidget * const m_parent; + const char * const m_domain; + +private: + SmartPtr<HookReceiver<HookableWidget>> hook; +}; + +/* button widget */ +class ButtonWidget : HookableWidget { +public: + ButtonWidget (const PreferencesWidget * parent, const char * domain) : + HookableWidget (parent, domain) {} + + QWidget * widget (); +}; + +/* boolean widget (checkbox) */ +class BooleanWidget : HookableWidget { +public: + BooleanWidget (const PreferencesWidget * parent, const char * domain) : + HookableWidget (parent, domain) {} + + QWidget * widget (); + void update (); + +private: + QCheckBox * m_checkbox; +}; + +/* label, no get or set functions needed. */ +class LabelWidget : HookableWidget { +public: + LabelWidget (const PreferencesWidget * parent, const char * domain) : + HookableWidget (parent, domain) {} + + QWidget * widget (); +}; + +/* integer widget (spinner) */ +class IntegerWidget : HookableWidget { +public: + IntegerWidget (const PreferencesWidget * parent, const char * domain) : + HookableWidget (parent, domain) {} + + QWidget * widget (); + void update (); + +private: + QSpinBox * m_spinner; +}; + +/* integer widget (radio button) */ +class RadioButtonWidget : HookableWidget { +public: + RadioButtonWidget (const PreferencesWidget * parent, const char * domain) : + HookableWidget (parent, domain) {} + + QWidget * widget (QButtonGroup * btn_group = nullptr); + void update (); + +private: + QRadioButton * m_radio; +}; + +/* double widget (spinner) */ +class DoubleWidget : HookableWidget { +public: + DoubleWidget (const PreferencesWidget * parent, const char * domain = nullptr) : + HookableWidget (parent, domain) {} + + QWidget * widget (); + void update (); + +private: + QDoubleSpinBox * m_spinner; +}; + +/* string widget (lineedit) */ +class StringWidget : HookableWidget { +public: + StringWidget (const PreferencesWidget * parent, const char * domain) : + HookableWidget (parent, domain) {} + + QWidget * widget (); + void update (); + +private: + QLineEdit * m_lineedit; +}; + +/* combo box (string or int) */ +class ComboBoxWidget : HookableWidget { +public: + ComboBoxWidget (const PreferencesWidget * parent, const char * domain) : + HookableWidget (parent, domain) {} + + QWidget * widget (); + void update (); + +private: + QComboBox * m_combobox; +}; + +/* box container widget */ +class BoxWidget : HookableWidget { +public: + BoxWidget (const PreferencesWidget * parent, const char * domain) : + HookableWidget (parent, domain) {} + + QWidget * widget (); +}; + +/* table container widget */ +class TableWidget : HookableWidget { +public: + TableWidget (const PreferencesWidget * parent, const char * domain) : + HookableWidget (parent, domain) {} + + QWidget * widget (); +}; + +/* notebook widget */ +class NotebookWidget : HookableWidget { +public: + NotebookWidget (const PreferencesWidget * parent, const char * domain) : + HookableWidget (parent, domain) {} + + QWidget * widget (); +}; + +} // namespace audqt + +#endif diff --git a/src/libaudqt/prefs-window.cc b/src/libaudqt/prefs-window.cc new file mode 100644 index 0000000..8798969 --- /dev/null +++ b/src/libaudqt/prefs-window.cc @@ -0,0 +1,753 @@ +/* + * prefs-window.cc + * Copyright 2006-2014 William Pitcock, Tomasz MoĆ, Michael FĂ€rber, and + * John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <QAction> +#include <QComboBox> +#include <QDialog> +#include <QDialogButtonBox> +#include <QGridLayout> +#include <QHeaderView> +#include <QIcon> +#include <QItemSelectionModel> +#include <QLabel> +#include <QLineEdit> +#include <QMenu> +#include <QPushButton> +#include <QSignalMapper> +#include <QStackedWidget> +#include <QTabWidget> +#include <QToolBar> +#include <QTreeView> +#include <QVBoxLayout> + +#include <libaudcore/audstrings.h> +#include <libaudcore/drct.h> +#include <libaudcore/hook.h> +#include <libaudcore/i18n.h> +#include <libaudcore/playlist.h> +#include <libaudcore/plugin.h> +#include <libaudcore/plugins.h> +#include <libaudcore/preferences.h> +#include <libaudcore/runtime.h> + +#include "libaudqt.h" +#include "prefs-pluginlist-model.h" + +#ifdef USE_CHARDET +#include <libguess.h> +#endif + +namespace audqt { + +struct Category { + const char * icon_path; + const char * name; +}; + +struct PluginCategory { + PluginType type; + const char * name; +}; + +struct TitleFieldTag { + const char * name; + const char * tag; +}; + +enum { + CATEGORY_APPEARANCE = 0, + CATEGORY_AUDIO, + CATEGORY_NETWORK, + CATEGORY_PLAYLIST, + CATEGORY_SONG_INFO, + CATEGORY_PLUGINS, + CATEGORY_COUNT +}; + +static const Category categories[] = { + { "appearance.png", N_("Appearance") }, + { "audio.png", N_("Audio") }, + { "connectivity.png", N_("Network") }, + { "playlist.png", N_("Playlist")} , + { "info.png", N_("Song Info") }, + { "plugins.png", N_("Plugins") } +}; + +static const PluginCategory plugin_categories[] = { + { PluginType::General, N_("General") }, + { PluginType::Effect, N_("Effect") }, + { PluginType::Vis, N_("Visualization") }, + { PluginType::Input, N_("Input") }, + { PluginType::Playlist, N_("Playlist") }, + { PluginType::Transport, N_("Transport") } +}; + +static const TitleFieldTag title_field_tags[] = { + { N_("Artist") , "${artist}" }, + { N_("Album") , "${album}" }, + { N_("Title") , "${title}" }, + { N_("Track number"), "${track-number}" }, + { N_("Genre") , "${genre}" }, + { N_("File name") , "${file-name}" }, + { N_("File path") , "${file-path}" }, + { N_("Date") , "${date}" }, + { N_("Year") , "${year}" }, + { N_("Comment") , "${comment}" }, + { N_("Codec") , "${codec}" }, + { N_("Quality") , "${quality}" } +}; + +#ifdef USE_CHARDET +static const ComboItem chardet_detector_presets[] = { + ComboItem (N_("None"), ""), + ComboItem (N_("Arabic"), GUESS_REGION_AR), + ComboItem (N_("Baltic"), GUESS_REGION_BL), + ComboItem (N_("Chinese"), GUESS_REGION_CN), + ComboItem (N_("Greek"), GUESS_REGION_GR), + ComboItem (N_("Hebrew"), GUESS_REGION_HW), + ComboItem (N_("Japanese"), GUESS_REGION_JP), + ComboItem (N_("Korean"), GUESS_REGION_KR), + ComboItem (N_("Polish"), GUESS_REGION_PL), + ComboItem (N_("Russian"), GUESS_REGION_RU), + ComboItem (N_("Taiwanese"), GUESS_REGION_TW), + ComboItem (N_("Turkish"), GUESS_REGION_TR) +}; +#endif + +static const ComboItem bitdepth_elements[] = { + ComboItem ("16", 16), + ComboItem ("24", 24), + ComboItem ("32", 32), + ComboItem (N_("Floating point"), 0) +}; + +static Index<ComboItem> iface_combo_elements; +static int iface_combo_selected; +static QWidget * iface_prefs_box; + +static ArrayRef<ComboItem> iface_combo_fill (); +static void iface_combo_changed (void); +static void * iface_create_prefs_box (void); + +static const PreferencesWidget appearance_page_widgets[] = { + WidgetLabel (N_("<b>Interface Settings</b>")), + WidgetCombo (N_("Interface plugin:"), + WidgetInt (iface_combo_selected, iface_combo_changed), + {0, iface_combo_fill}), + WidgetCustomQt (iface_create_prefs_box) +}; + +static Index<ComboItem> output_combo_elements; +static int output_combo_selected; +static QPushButton * output_config_button; +static QPushButton * output_about_button; + +static ArrayRef<ComboItem> output_combo_fill (); +static void output_combo_changed (void); +static void * output_create_config_button (void); +static void * output_create_about_button (void); +static void output_bit_depth_changed (void); + +static const PreferencesWidget output_combo_widgets[] = { + WidgetCombo (N_("Output plugin:"), + WidgetInt (output_combo_selected, output_combo_changed), + {0, output_combo_fill}), + WidgetCustomQt (output_create_config_button), + WidgetCustomQt (output_create_about_button) +}; + +static const PreferencesWidget audio_page_widgets[] = { + WidgetLabel (N_("<b>Output Settings</b>")), + WidgetBox ({{output_combo_widgets}, true}), + WidgetCombo (N_("Bit depth:"), + WidgetInt (0, "output_bit_depth", output_bit_depth_changed), + {{bitdepth_elements}}), + WidgetSpin (N_("Buffer size:"), + WidgetInt (0, "output_buffer_size"), + {100, 10000, 1000, N_("ms")}), + WidgetCheck (N_("Soft clipping"), + WidgetBool (0, "soft_clipping")), + WidgetCheck (N_("Use software volume control (not recommended)"), + WidgetBool (0, "software_volume_control")), + WidgetLabel (N_("<b>Replay Gain</b>")), + WidgetCheck (N_("Enable Replay Gain"), + WidgetBool (0, "enable_replay_gain")), + WidgetCheck (N_("Album mode"), + WidgetBool (0, "replay_gain_album"), + WIDGET_CHILD), + WidgetCheck (N_("Prevent clipping (recommended)"), + WidgetBool (0, "enable_clipping_prevention"), + WIDGET_CHILD), + WidgetLabel (N_("<b>Adjust Levels</b>"), + WIDGET_CHILD), + WidgetSpin (N_("Amplify all files:"), + WidgetFloat (0, "replay_gain_preamp"), + {-15, 15, 0.1, N_("dB")}, + WIDGET_CHILD), + WidgetSpin (N_("Amplify untagged files:"), + WidgetFloat (0, "default_gain"), + {-15, 15, 0.1, N_("dB")}, + WIDGET_CHILD) +}; + +static const PreferencesWidget proxy_host_port_elements[] = { + WidgetEntry (N_("Proxy hostname:"), + WidgetString (0, "proxy_host")), + WidgetEntry (N_("Proxy port:"), + WidgetString (0, "proxy_port")) +}; + +static const PreferencesWidget proxy_auth_elements[] = { + WidgetEntry (N_("Proxy username:"), + WidgetString (0, "proxy_user")), + WidgetEntry (N_("Proxy password:"), + WidgetString (0, "proxy_pass"), + {true}) +}; + +static const PreferencesWidget connectivity_page_widgets[] = { + WidgetLabel (N_("<b>Network Settings</b>")), + WidgetSpin (N_("Buffer size:"), + WidgetInt (0, "net_buffer_kb"), + {16, 1024, 16, N_("KiB")}), + WidgetLabel (N_("<b>Proxy Configuration</b>")), + WidgetCheck (N_("Enable proxy usage"), + WidgetBool (0, "use_proxy")), + WidgetTable ({{proxy_host_port_elements}}, + WIDGET_CHILD), + WidgetCheck (N_("Use authentication with proxy"), + WidgetBool (0, "use_proxy_auth")), + WidgetTable ({{proxy_auth_elements}}, + WIDGET_CHILD) +}; + +static const PreferencesWidget chardet_elements[] = { +#ifdef USE_CHARDET + WidgetCombo (N_("Auto character encoding detector for:"), + WidgetString (0, "chardet_detector"), + {{chardet_detector_presets}}), +#endif + WidgetEntry (N_("Fallback character encodings:"), + WidgetString (0, "chardet_fallback")) +}; + + +static void send_title_change (void); +static void * create_titlestring_table (void); + +static const PreferencesWidget playlist_page_widgets[] = { + WidgetLabel (N_("<b>Behavior</b>")), + WidgetCheck (N_("Resume playback on startup"), + WidgetBool (0, "resume_playback_on_startup")), + WidgetCheck (N_("Pause instead of resuming immediately"), + WidgetBool (0, "always_resume_paused"), + WIDGET_CHILD), + WidgetCheck (N_("Advance when the current song is deleted"), + WidgetBool (0, "advance_on_delete")), + WidgetCheck (N_("Clear the playlist when opening files"), + WidgetBool (0, "clear_playlist")), + WidgetCheck (N_("Open files in a temporary playlist"), + WidgetBool (0, "open_to_temporary")), + WidgetCheck (N_("Do not load metadata for songs until played"), + WidgetBool (0, "metadata_on_play")), + WidgetLabel (N_("<b>Compatibility</b>")), + WidgetCheck (N_("Interpret \\ (backward slash) as a folder delimiter"), + WidgetBool (0, "convert_backslash")), + WidgetTable ({{chardet_elements}}), + WidgetLabel (N_("<b>Song Display</b>")), + WidgetCheck (N_("Show song numbers"), + WidgetBool (0, "show_numbers_in_pl", send_title_change)), + WidgetCheck (N_("Show leading zeroes (02:00 instead of 2:00)"), + WidgetBool (0, "leading_zero", send_title_change)), + WidgetCustomQt (create_titlestring_table), + WidgetLabel (N_("<b>Advanced</b>")), + WidgetCheck (N_("Do not load metadata for songs until played"), + WidgetBool (0, "metadata_on_play")), + WidgetCheck (N_("Probe content of files with no recognized file name extension"), + WidgetBool (0, "slow_probe")) +}; + +static const PreferencesWidget song_info_page_widgets[] = { + WidgetLabel (N_("<b>Album Art</b>")), + WidgetLabel (N_("Search for images matching these words (comma-separated):")), + WidgetEntry (0, WidgetString (0, "cover_name_include")), + WidgetLabel (N_("Exclude images matching these words (comma-separated):")), + WidgetEntry (0, WidgetString (0, "cover_name_exclude")), + WidgetCheck (N_("Search for images matching song file name"), + WidgetBool (0, "use_file_cover")), + WidgetCheck (N_("Search recursively"), + WidgetBool (0, "recurse_for_cover")), + WidgetSpin (N_("Search depth:"), + WidgetInt (0, "recurse_for_cover_depth"), + {0, 100, 1}, + WIDGET_CHILD), + WidgetLabel (N_("<b>Popup Information</b>")), + WidgetCheck (N_("Show popup information"), + WidgetBool (0, "show_filepopup_for_tuple")), + WidgetSpin (N_("Popup delay (tenths of a second):"), + WidgetInt (0, "filepopup_delay"), + {0, 100, 1}, + WIDGET_CHILD), + WidgetCheck (N_("Show time scale for current song"), + WidgetBool (0, "filepopup_showprogressbar"), + WIDGET_CHILD) +}; + +#define TITLESTRING_NPRESETS 6 + +static const char * const titlestring_presets[TITLESTRING_NPRESETS] = { + "${title}", + "${?artist:${artist} - }${title}", + "${?artist:${artist} - }${?album:${album} - }${title}", + "${?artist:${artist} - }${?album:${album} - }${?track-number:${track-number}. }${title}", + "${?artist:${artist} }${?album:[ ${album} ] }${?artist:- }${?track-number:${track-number}. }${title}", + "${?album:${album} - }${title}" +}; + +static const char * const titlestring_preset_names[TITLESTRING_NPRESETS] = { + N_("TITLE"), + N_("ARTIST - TITLE"), + N_("ARTIST - ALBUM - TITLE"), + N_("ARTIST - ALBUM - TRACK. TITLE"), + N_("ARTIST [ ALBUM ] - TRACK. TITLE"), + N_("ALBUM - TITLE") +}; + +static void * create_titlestring_table (void) +{ + QWidget * w = new QWidget; + QGridLayout * l = new QGridLayout (w); + + QLabel * lbl = new QLabel (_("Title format:"), w); + l->addWidget (lbl, 0, 0); + + QComboBox * cbox = new QComboBox (w); + l->addWidget (cbox, 0, 1); + + for (int i = 0; i < TITLESTRING_NPRESETS; i++) + cbox->addItem (translate_str (titlestring_preset_names [i]), i); + cbox->addItem (_("Custom"), TITLESTRING_NPRESETS); + cbox->setCurrentIndex (TITLESTRING_NPRESETS); + + lbl = new QLabel (_("Custom string:"), w); + l->addWidget (lbl, 1, 0); + + QLineEdit * le = new QLineEdit (w); + l->addWidget (le, 1, 1); + + String format = aud_get_str (nullptr, "generic_title_format"); + le->setText ((const char *) format); + for (int i = 0; i < TITLESTRING_NPRESETS; i++) + { + if (! strcmp (titlestring_presets [i], format)) + { + cbox->setCurrentIndex (i); + } + } + + QObject::connect (le, &QLineEdit::textChanged, [=] (const QString & text) { + aud_set_str (nullptr, "generic_title_format", text.toLocal8Bit ().data ()); + }); + + QObject::connect (cbox, + static_cast <void (QComboBox::*) (int)> (&QComboBox::currentIndexChanged), + [=] (int idx) { + if (idx < TITLESTRING_NPRESETS) + le->setText (titlestring_presets [idx]); + }); + + /* build menu */ + QPushButton * btn_mnu = new QPushButton (w); + btn_mnu->setIcon (QIcon::fromTheme ("list-add")); + l->addWidget (btn_mnu, 1, 2); + + QMenu * mnu_fields = new QMenu (w); + + for (auto & t : title_field_tags) + { + QAction * a = mnu_fields->addAction (_(t.name)); + QObject::connect (a, &QAction::triggered, [=] () { + le->insert (t.tag); + }); + } + + QObject::connect (btn_mnu, &QAbstractButton::clicked, [=] () { + mnu_fields->popup (btn_mnu->mapToGlobal (QPoint (0, 0))); + }); + + return w; +} + +static Index<ComboItem> fill_plugin_combo (PluginType type) +{ + Index<ComboItem> elems; + int i = 0; + + for (PluginHandle * plugin : aud_plugin_list (type)) + elems.append (aud_plugin_get_name (plugin), i ++); + + return elems; +} + +static void send_title_change (void) +{ + if (aud_drct_get_ready ()) + hook_call ("title change", nullptr); +} + +static void iface_fill_prefs_box (void) +{ + Plugin * header = (Plugin *) aud_plugin_get_header (aud_plugin_get_current (PluginType::Iface)); + if (header && header->info.prefs) + { + QVBoxLayout * vbox = new QVBoxLayout (iface_prefs_box); + + vbox->setContentsMargins (0, 0, 0, 0); + vbox->setSpacing (4); + prefs_populate (vbox, header->info.prefs->widgets, header->info.domain); + } +} + +#ifdef XXX_NOTYET +static int iface_combo_changed_finish (void *) +{ + iface_fill_prefs_box (); + gtk_widget_show_all (iface_prefs_box); + + audgui_cleanup (); + + return G_SOURCE_REMOVE; +} +#endif + +static void iface_combo_changed (void) +{ +#ifdef XXX_NOTYET + /* prevent audgui from being shut down during the switch */ + audgui_init (); + + gtk_container_foreach ((GtkContainer *) iface_prefs_box, + (GtkCallback) gtk_widget_destroy, nullptr); + + aud_plugin_enable (aud_plugin_by_index (PluginType::Iface, iface_combo_selected), true); + + /* now wait till we have restarted into the new main loop */ + g_idle_add_full (G_PRIORITY_HIGH, iface_combo_changed_finish, nullptr, nullptr); +#endif +} + +static ArrayRef<ComboItem> iface_combo_fill () +{ + if (! iface_combo_elements.len ()) + { + iface_combo_elements = fill_plugin_combo (PluginType::Iface); + iface_combo_selected = aud_plugin_list (PluginType::Iface) + .find (aud_plugin_get_current (PluginType::Iface)); + } + + return {iface_combo_elements.begin (), iface_combo_elements.len ()}; +} + +static void * iface_create_prefs_box (void) +{ + iface_prefs_box = new QWidget; + iface_fill_prefs_box (); + return iface_prefs_box; +} + +static void output_combo_changed (void) +{ + PluginHandle * plugin = aud_plugin_list (PluginType::Output)[output_combo_selected]; + + if (aud_plugin_enable (plugin, true)) + { + output_config_button->setEnabled (aud_plugin_has_configure (plugin)); + output_about_button->setEnabled (aud_plugin_has_about (plugin)); + } +} + +static void * output_create_config_button (void) +{ + bool enabled = aud_plugin_has_configure (aud_plugin_get_current (PluginType::Output)); + + output_config_button = new QPushButton (translate_str (N_("_Settings"))); + output_config_button->setEnabled (enabled); + + QObject::connect (output_config_button, &QAbstractButton::clicked, [=] (bool) { + plugin_prefs (aud_plugin_get_current (PluginType::Output)); + }); + + return output_config_button; +} + +static void * output_create_about_button (void) +{ + bool enabled = aud_plugin_has_about (aud_plugin_get_current (PluginType::Output)); + + output_about_button = new QPushButton (translate_str (N_("_About"))); + output_about_button->setEnabled (enabled); + + QObject::connect (output_about_button, &QAbstractButton::clicked, [=] (bool) { + plugin_about (aud_plugin_get_current (PluginType::Output)); + }); + + return output_about_button; +} + +static ArrayRef<ComboItem> output_combo_fill () +{ + if (! output_combo_elements.len ()) + { + output_combo_elements = fill_plugin_combo (PluginType::Output); + output_combo_selected = aud_plugin_list (PluginType::Output) + .find (aud_plugin_get_current (PluginType::Output)); + } + + return {output_combo_elements.begin (), output_combo_elements.len ()}; +} + +static void output_bit_depth_changed (void) +{ + aud_output_reset (OutputReset::ReopenStream); +} + +static void create_appearance_category (QStackedWidget * category_notebook) +{ + QWidget * w = new QWidget; + QVBoxLayout * vbox = new QVBoxLayout (w); + + vbox->setContentsMargins (0, 0, 0, 0); + vbox->setSpacing (4); + prefs_populate (vbox, appearance_page_widgets, nullptr); + + category_notebook->addWidget (w); +} + +static void create_audio_category (QStackedWidget * category_notebook) +{ + QWidget * audio_page = new QWidget; + QVBoxLayout * audio_page_vbox = new QVBoxLayout; + + audio_page_vbox->setContentsMargins (0, 0, 0, 0); + audio_page_vbox->setSpacing (4); + prefs_populate (audio_page_vbox, audio_page_widgets, nullptr); + + audio_page->setLayout (audio_page_vbox); + category_notebook->addWidget (audio_page); +} + +static void create_connectivity_category (QStackedWidget * category_notebook) +{ + QWidget * connectivity_page = new QWidget; + QVBoxLayout * connectivity_page_vbox = new QVBoxLayout (connectivity_page); + + connectivity_page_vbox->setContentsMargins (0, 0, 0, 0); + connectivity_page_vbox->setSpacing (4); + prefs_populate (connectivity_page_vbox, connectivity_page_widgets, nullptr); + + category_notebook->addWidget (connectivity_page); +} + +static void create_playlist_category (QStackedWidget * category_notebook) +{ + QWidget * playlist_page = new QWidget; + QVBoxLayout * playlist_page_vbox = new QVBoxLayout (playlist_page); + + playlist_page_vbox->setContentsMargins (0, 0, 0, 0); + playlist_page_vbox->setSpacing (4); + prefs_populate (playlist_page_vbox, playlist_page_widgets, nullptr); + + category_notebook->addWidget (playlist_page); +} + +static void create_song_info_category (QStackedWidget * category_notebook) +{ + QWidget * song_info_page = new QWidget; + QVBoxLayout * song_info_page_vbox = new QVBoxLayout (song_info_page); + + song_info_page_vbox->setContentsMargins (0, 0, 0, 0); + song_info_page_vbox->setSpacing (4); + prefs_populate (song_info_page_vbox, song_info_page_widgets, nullptr); + + category_notebook->addWidget (song_info_page); +} + +static void create_plugin_category_page (PluginType category_id, const char * category_name, QTabWidget * parent) +{ + QTreeView * view = new QTreeView; + QHeaderView * header = view->header (); + + view->setIndentation (0); + view->setModel (new PluginListModel (nullptr, category_id)); + view->setSelectionMode (view->NoSelection); + + header->hide (); + header->setSectionResizeMode (header->ResizeToContents); + header->setStretchLastSection (false); + + parent->addTab (view, category_name); + + QObject::connect (view, & QAbstractItemView::clicked, + [category_id] (const QModelIndex & index) + { + int row = index.row (); + auto & list = aud_plugin_list (category_id); + + if (row < 0 || row >= list.len () || ! aud_plugin_get_enabled (list[row])) + return; + + switch (index.column ()) + { + case PluginListModel::AboutColumn: + plugin_about (list[row]); + break; + case PluginListModel::SettingsColumn: + plugin_prefs (list[row]); + break; + } + }); +} + +static QTabWidget * plugin_tabs = nullptr; + +static void create_plugin_category (QStackedWidget * parent) +{ + plugin_tabs = new QTabWidget; + + for (const PluginCategory & w : plugin_categories) + { + create_plugin_category_page (w.type, _(w.name), plugin_tabs); + } + + parent->addWidget (plugin_tabs); +} + +static QDialog * s_prefswin = nullptr; +static QStackedWidget * s_category_notebook = nullptr; + +static void create_prefs_window () +{ + s_prefswin = new QDialog; + s_prefswin->setWindowTitle (_("Audacious Settings")); + s_prefswin->setAttribute (Qt::WA_DeleteOnClose); + + QObject::connect (s_prefswin, & QObject::destroyed, [] () { + s_prefswin = nullptr; + }); + + QVBoxLayout * vbox_parent = new QVBoxLayout (s_prefswin); + + vbox_parent->setSpacing (0); + vbox_parent->setContentsMargins (0, 0, 0, 0); + + QToolBar * toolbar = new QToolBar; + toolbar->setToolButtonStyle (Qt::ToolButtonTextUnderIcon); + vbox_parent->addWidget (toolbar); + + QWidget * child = new QWidget; + QVBoxLayout * child_vbox = new QVBoxLayout (child); + vbox_parent->addWidget (child); + + s_category_notebook = new QStackedWidget; + child_vbox->addWidget (s_category_notebook); + + create_appearance_category (s_category_notebook); + create_audio_category (s_category_notebook); + create_connectivity_category (s_category_notebook); + create_playlist_category (s_category_notebook); + create_song_info_category (s_category_notebook); + create_plugin_category (s_category_notebook); + + QDialogButtonBox * bbox = new QDialogButtonBox (QDialogButtonBox::Close); + bbox->button (QDialogButtonBox::Close)->setText (translate_str (N_("_Close"))); + child_vbox->addWidget (bbox); + + QObject::connect (bbox, &QDialogButtonBox::rejected, s_prefswin, &QObject::deleteLater); + + QSignalMapper * mapper = new QSignalMapper; + const char * data_dir = aud_get_path (AudPath::DataDir); + + QObject::connect (mapper, static_cast <void (QSignalMapper::*)(int)>(&QSignalMapper::mapped), + s_category_notebook, static_cast <void (QStackedWidget::*)(int)>(&QStackedWidget::setCurrentIndex)); + + for (int i = 0; i < CATEGORY_COUNT; i++) + { + QIcon ico (QString (filename_build ({data_dir, "images", categories[i].icon_path}))); + QAction * a = new QAction (ico, translate_str (categories[i].name), nullptr); + + toolbar->addAction (a); + + mapper->setMapping (a, i); + + QObject::connect (a, &QAction::triggered, mapper, static_cast <void (QSignalMapper::*)()>(&QSignalMapper::map)); + } +} + +EXPORT void prefswin_show () +{ + if (! s_prefswin) + create_prefs_window (); + + window_bring_to_front (s_prefswin); +} + +EXPORT void prefswin_hide () +{ + delete s_prefswin; +} + +EXPORT void prefswin_show_page (int id, bool show) +{ + if (id < 0 || id > CATEGORY_COUNT) + return; + + if (! s_prefswin) + create_prefs_window (); + + s_category_notebook->setCurrentIndex (id); + + if (show) + window_bring_to_front (s_prefswin); +} + +EXPORT void prefswin_show_plugin_page (PluginType type) +{ + if (! s_prefswin) + create_prefs_window (); + + if (type == PluginType::Iface) + return prefswin_show_page (CATEGORY_APPEARANCE); + else if (type == PluginType::Output) + return prefswin_show_page (CATEGORY_AUDIO); + else + { + prefswin_show_page (CATEGORY_PLUGINS, false); + + for (const PluginCategory & category : plugin_categories) + { + if (category.type == type) + plugin_tabs->setCurrentIndex (& category - plugin_categories); + } + + window_bring_to_front (s_prefswin); + } +} + +} // namespace audqt diff --git a/src/libaudqt/queue-manager.cc b/src/libaudqt/queue-manager.cc new file mode 100644 index 0000000..4bf201c --- /dev/null +++ b/src/libaudqt/queue-manager.cc @@ -0,0 +1,198 @@ +/* + * queue-manager.cc + * Copyright 2014 William Pitcock, John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "libaudqt.h" + +#include <QAbstractListModel> +#include <QDialog> +#include <QDialogButtonBox> +#include <QPushButton> +#include <QHeaderView> +#include <QItemSelectionModel> +#include <QTreeView> +#include <QVBoxLayout> + +#include <libaudcore/audstrings.h> +#include <libaudcore/playlist.h> +#include <libaudcore/hook.h> +#include <libaudcore/i18n.h> + +/* + * TODO: + * - shifting of selection entries + */ + +namespace audqt { + +class QueueManagerModel : public QAbstractListModel { +public: + QueueManagerModel (QObject * parent = nullptr) : QAbstractListModel (parent) {} + + int rowCount (const QModelIndex & parent = QModelIndex()) const; + int columnCount (const QModelIndex & parent = QModelIndex()) const; + QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const; + + void reset (); +}; + +int QueueManagerModel::rowCount (const QModelIndex & parent) const +{ + return aud_playlist_queue_count (aud_playlist_get_active ()); +} + +int QueueManagerModel::columnCount (const QModelIndex & parent) const +{ + return 2; +} + +QVariant QueueManagerModel::data (const QModelIndex & index, int role) const +{ + if (role == Qt::DisplayRole) + { + int list = aud_playlist_get_active (); + int entry = aud_playlist_queue_get_entry (list, index.row ()); + + if (index.column () == 0) + return entry; + else + { + Tuple tuple = aud_playlist_entry_get_tuple (list, entry, Playlist::Guess); + return QString ((const char *) tuple.get_str (Tuple::FormattedTitle)); + } + } + else if (role == Qt::TextAlignmentRole && index.column () == 0) + return Qt::AlignRight; + + return QVariant (); +} + +void QueueManagerModel::reset () +{ + beginResetModel (); + endResetModel (); +} + +class QueueManagerDialog : public QDialog { +public: + QueueManagerDialog (QWidget * parent = nullptr); + +private: + QVBoxLayout m_layout; + QTreeView m_treeview; + QDialogButtonBox m_buttonbox; + QPushButton m_btn_unqueue; + QPushButton m_btn_close; + QueueManagerModel m_model; + + void update (Playlist::UpdateLevel level); + void removeSelected (); + + const HookReceiver<QueueManagerDialog, Playlist::UpdateLevel> + update_hook {"playlist update", this, & QueueManagerDialog::update}; + const HookReceiver<QueueManagerModel> + activate_hook {"playlist activate", & m_model, & QueueManagerModel::reset}; +}; + +QueueManagerDialog::QueueManagerDialog (QWidget * parent) : + QDialog (parent) +{ + m_btn_unqueue.setText (translate_str (N_("_Unqueue"))); + m_btn_close.setText (translate_str (N_("_Close"))); + + connect (& m_btn_close, &QAbstractButton::clicked, this, &QWidget::hide); + connect (& m_btn_unqueue, &QAbstractButton::clicked, this, &QueueManagerDialog::removeSelected); + + m_buttonbox.addButton (& m_btn_close, QDialogButtonBox::AcceptRole); + m_buttonbox.addButton (& m_btn_unqueue, QDialogButtonBox::AcceptRole); + + m_layout.addWidget (& m_treeview); + m_layout.addWidget (& m_buttonbox); + + m_treeview.setIndentation (0); + m_treeview.setModel (& m_model); + m_treeview.setSelectionMode (QAbstractItemView::ExtendedSelection); + m_treeview.header ()->hide (); + + setLayout (& m_layout); + setWindowTitle (_("Queue Manager")); + + QItemSelectionModel * model = m_treeview.selectionModel (); + connect (model, &QItemSelectionModel::selectionChanged, [=] (const QItemSelection & selected, const QItemSelection & deselected) { + int list = aud_playlist_get_active (); + + for (auto & index : selected.indexes ()) + aud_playlist_entry_set_selected (list, aud_playlist_queue_get_entry (list, index.row ()), true); + + for (auto & index : deselected.indexes ()) + aud_playlist_entry_set_selected (list, aud_playlist_queue_get_entry (list, index.row ()), false); + }); + + resize (500, 250); +} + +void QueueManagerDialog::update (Playlist::UpdateLevel level) +{ + /* resetting the model due to selection updates causes breakage, so don't do it. */ + if (level != Playlist::Selection) + m_model.reset (); +} + +void QueueManagerDialog::removeSelected () +{ + int list = aud_playlist_get_active (); + int count = aud_playlist_queue_count (list); + + for (int i = 0; i < count; ) + { + int entry = aud_playlist_queue_get_entry (list, i); + + if (aud_playlist_entry_get_selected (list, entry)) + { + aud_playlist_queue_delete (list, i, 1); + aud_playlist_entry_set_selected (list, entry, false); + count --; + } + else + i ++; + } +} + +static QueueManagerDialog * s_queuemgr = nullptr; + +EXPORT void queue_manager_show () +{ + if (! s_queuemgr) + { + s_queuemgr = new QueueManagerDialog; + s_queuemgr->setAttribute (Qt::WA_DeleteOnClose); + + QObject::connect (s_queuemgr, & QObject::destroyed, [] () { + s_queuemgr = nullptr; + }); + } + + window_bring_to_front (s_queuemgr); +} + +EXPORT void queue_manager_hide () +{ + delete s_queuemgr; +} + +} // namespace audqt diff --git a/src/libaudqt/util.cc b/src/libaudqt/util.cc new file mode 100644 index 0000000..ff71380 --- /dev/null +++ b/src/libaudqt/util.cc @@ -0,0 +1,90 @@ +/* + * util.cc + * Copyright 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <QDialog> +#include <QDialogButtonBox> +#include <QLabel> +#include <QPushButton> +#include <QVBoxLayout> + +#include <libaudcore/audstrings.h> +#include <libaudcore/i18n.h> +#include <libaudcore/runtime.h> + +#include "libaudqt.h" + +namespace audqt { + +EXPORT void cleanup () +{ + aboutwindow_hide (); + equalizer_hide (); + infowin_hide (); + log_inspector_hide (); + prefswin_hide (); + queue_manager_hide (); +} + +/* the goal is to force a window to come to the front on any Qt platform */ +EXPORT void window_bring_to_front (QWidget * window) +{ + window->show (); + + Qt::WindowStates state = window->windowState (); + + state &= ~Qt::WindowMinimized; + state |= Qt::WindowActive; + + window->setWindowState (state); + window->raise (); + window->activateWindow (); +} + +EXPORT void simple_message (const char * title, const char * text) +{ + QDialog msgbox; + QVBoxLayout vbox; + QLabel label; + QDialogButtonBox bbox; + + label.setText (text); + label.setTextInteractionFlags (Qt::TextSelectableByMouse); + + bbox.setStandardButtons (QDialogButtonBox::Close); + bbox.button (QDialogButtonBox::Close)->setText (translate_str (N_("_Close"))); + + QObject::connect (& bbox, & QDialogButtonBox::rejected, & msgbox, & QDialog::reject); + + vbox.setSizeConstraint (QLayout::SetFixedSize); + vbox.addWidget (& label); + vbox.addWidget (& bbox); + + msgbox.setWindowTitle (title); + msgbox.setLayout (& vbox); + msgbox.exec (); +} + +/* translate GTK+ accelerators and also handle dgettext() */ +EXPORT QString translate_str (const char * str, const char * domain) +{ + /* translate the GTK+ accelerator (_) into a Qt accelerator (&) */ + return QString (dgettext (domain, str)).replace ('_', '&'); +} + +} // namespace audqt diff --git a/src/libaudqt/volumebutton.cc b/src/libaudqt/volumebutton.cc new file mode 100644 index 0000000..32f2d4d --- /dev/null +++ b/src/libaudqt/volumebutton.cc @@ -0,0 +1,103 @@ +/* + * volumebutton.cc + * Copyright 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "volumebutton.h" +#include "libaudqt.h" + +#include <QIcon> +#include <QSlider> +#include <QVBoxLayout> +#include <QWheelEvent> + +#include <libaudcore/drct.h> + +namespace audqt { + +EXPORT VolumeButton::VolumeButton (QWidget * parent) : + QToolButton (parent) +{ + setFocusPolicy (Qt::NoFocus); + + auto layout = new QVBoxLayout (this); + layout->setContentsMargins (6, 6, 6, 6); + + m_slider = new QSlider (Qt::Vertical, this); + m_slider->setRange (0, 100); + + layout->addWidget (m_slider); + + m_container = new QWidget; + m_container->setLayout (layout); + + updateVolume (); + + connect (this, & QAbstractButton::clicked, this, & VolumeButton::showSlider); + connect (m_slider, & QAbstractSlider::valueChanged, this, & VolumeButton::setVolume); +} + +void VolumeButton::updateIcon (int val) +{ + if (val == 0) + setIcon (QIcon::fromTheme ("audio-volume-muted")); + else if (val > 0 && val < 35) + setIcon (QIcon::fromTheme ("audio-volume-low")); + else if (val >= 35 && val < 70) + setIcon (QIcon::fromTheme ("audio-volume-medium")); + else if (val >= 70) + setIcon (QIcon::fromTheme ("audio-volume-high")); + + setToolTip (QString ("%1 %").arg (val)); +} + +void VolumeButton::updateVolume () +{ + int val = aud_drct_get_volume_main (); + updateIcon (val); + m_slider->setValue (val); +} + +void VolumeButton::showSlider () +{ + m_container->setWindowFlags (Qt::Popup); + m_container->move (mapToGlobal (QPoint (0, 0))); + + window_bring_to_front (m_container); +} + +void VolumeButton::setVolume (int val) +{ + aud_drct_set_volume_main (val); + updateIcon (val); +} + +void VolumeButton::wheelEvent (QWheelEvent * event) +{ + int val = m_slider->value (); + int y = event->angleDelta ().y (); + + if (y < 0 && val > 0) + val--; + else if (y > 0 && val < 100) + val++; + + setVolume (val); + m_slider->setValue (val); +} + +} // namespace audqt diff --git a/src/libaudgui/ui_jumptotrack_cache.h b/src/libaudqt/volumebutton.h index 8452845..f58a49c 100644 --- a/src/libaudgui/ui_jumptotrack_cache.h +++ b/src/libaudqt/volumebutton.h @@ -1,6 +1,6 @@ /* - * ui_jumptotrack_cache.h - * Copyright 2008 Jussi Judin + * volumebutton.h + * Copyright 2014 William Pitcock * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -17,21 +17,32 @@ * the use of this software. */ -#ifndef LIBAUDGUI_UI_JUMPTOTRACK_CACHE_H -#define LIBAUDGUI_UI_JUMPTOTRACK_CACHE_H +#ifndef LIBAUDQT_VOLUMEBUTTON_H +#define LIBAUDQT_VOLUMEBUTTON_H -#include <glib.h> +#include <QToolButton> -typedef struct _JumpToTrackCache JumpToTrackCache; +class QSlider; -struct _JumpToTrackCache +namespace audqt { + +class VolumeButton : public QToolButton { - GHashTable* keywords; +public: + VolumeButton (QWidget * parent = nullptr); + +private: + void updateIcon (int val); + void updateVolume (); + void showSlider (); + void setVolume (int val); + + void wheelEvent (QWheelEvent * event); + + QSlider * m_slider; + QWidget * m_container; }; -extern JumpToTrackCache* ui_jump_to_track_cache_new(void); -extern const GArray * ui_jump_to_track_cache_search (JumpToTrackCache * cache, - const char * keyword); -extern void ui_jump_to_track_cache_free(JumpToTrackCache* cache); +} // namespace audqt #endif diff --git a/src/libaudtag/Makefile b/src/libaudtag/Makefile index 98086d7..9a200f4 100644 --- a/src/libaudtag/Makefile +++ b/src/libaudtag/Makefile @@ -1,21 +1,23 @@ SHARED_LIB = ${LIB_PREFIX}audtag${LIB_SUFFIX} -LIB_MAJOR = 1 +LIB_MAJOR = 2 LIB_MINOR = 0 -SRCS = audtag.c \ - util.c \ - tag_module.c \ - id3/id3-common.c \ - id3/id3v1.c \ - id3/id3v22.c \ - id3/id3v24.c \ - ape/ape.c +SRCS = audtag.cc \ + util.cc \ + tag_module.cc \ + id3/id3-common.cc \ + id3/id3v1.cc \ + id3/id3v22.cc \ + id3/id3v24.cc \ + ape/ape.cc INCLUDES = audtag.h include ../../buildsys.mk include ../../extra.mk +LD = ${CXX} + CPPFLAGS := -I.. -I../.. \ ${CPPFLAGS} \ ${GLIB_CFLAGS} diff --git a/src/libaudtag/ape/ape.c b/src/libaudtag/ape/ape.c deleted file mode 100644 index b755a04..0000000 --- a/src/libaudtag/ape/ape.c +++ /dev/null @@ -1,505 +0,0 @@ -/* - * ape.c - * Copyright 2010 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -/* TODO: - * - Support updating files that have their tag at the beginning? - */ - -#include <glib.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <libaudcore/audstrings.h> -#include <libaudcore/vfs.h> - -#include "ape.h" - -#pragma pack(push) /* must be byte-aligned */ -#pragma pack(1) -typedef struct -{ - char magic[8]; - uint32_t version; /* LE */ - uint32_t length; /* LE */ - uint32_t items; /* LE */ - uint32_t flags; /* LE */ - uint64_t reserved; -} -APEHeader; -#pragma pack(pop) - -typedef struct -{ - char * key, * value; -} -ValuePair; - -#define APE_FLAG_HAS_HEADER (1 << 31) -#define APE_FLAG_HAS_NO_FOOTER (1 << 30) -#define APE_FLAG_IS_HEADER (1 << 29) - -static bool_t ape_read_header (VFSFile * handle, APEHeader * header) -{ - if (vfs_fread (header, 1, sizeof (APEHeader), handle) != sizeof (APEHeader)) - return FALSE; - - if (strncmp (header->magic, "APETAGEX", 8)) - return FALSE; - - header->version = GUINT32_FROM_LE (header->version); - header->length = GUINT32_FROM_LE (header->length); - header->items = GUINT32_FROM_LE (header->items); - header->flags = GUINT32_FROM_LE (header->flags); - - if (header->length < sizeof (APEHeader)) - return FALSE; - - return TRUE; -} - -static bool_t ape_find_header (VFSFile * handle, APEHeader * header, - int * start, int * length, int * data_start, int * data_length) -{ - APEHeader secondary; - - if (vfs_fseek (handle, 0, SEEK_SET)) - return FALSE; - - if (ape_read_header (handle, header)) - { - TAGDBG ("Found header at 0, length = %d, version = %d.\n", - (int) header->length, (int) header->version); - - * start = 0; - * length = header->length; - * data_start = sizeof (APEHeader); - * data_length = header->length - sizeof (APEHeader); - - if (! (header->flags & APE_FLAG_HAS_HEADER) || ! (header->flags & APE_FLAG_IS_HEADER)) - { - TAGDBG ("Invalid header flags (%u).\n", (unsigned int) header->flags); - return FALSE; - } - - if (! (header->flags & APE_FLAG_HAS_NO_FOOTER)) - { - if (vfs_fseek (handle, header->length, SEEK_CUR)) - return FALSE; - - if (! ape_read_header (handle, & secondary)) - { - TAGDBG ("Expected footer, but found none.\n"); - return FALSE; - } - - * length += sizeof (APEHeader); - } - - return TRUE; - } - - if (vfs_fseek (handle, -(int) sizeof (APEHeader), SEEK_END)) - return FALSE; - - if (! ape_read_header (handle, header)) - { - /* APE tag may be followed by an ID3v1 tag */ - if (vfs_fseek (handle, -128 - (int) sizeof (APEHeader), SEEK_END)) - return FALSE; - - if (! ape_read_header (handle, header)) - { - TAGDBG ("No header found.\n"); - return FALSE; - } - } - - TAGDBG ("Found footer at %d, length = %d, version = %d.\n", - (int) vfs_ftell (handle) - (int) sizeof (APEHeader), (int) header->length, - (int) header->version); - - * start = vfs_ftell (handle) - header->length; - * length = header->length; - * data_start = vfs_ftell (handle) - header->length; - * data_length = header->length - sizeof (APEHeader); - - if ((header->flags & APE_FLAG_HAS_NO_FOOTER) || (header->flags & APE_FLAG_IS_HEADER)) - { - TAGDBG ("Invalid footer flags (%u).\n", (unsigned) header->flags); - return FALSE; - } - - if (header->flags & APE_FLAG_HAS_HEADER) - { - if (vfs_fseek (handle, -(int) header->length - sizeof (APEHeader), SEEK_CUR)) - return FALSE; - - if (! ape_read_header (handle, & secondary)) - { - TAGDBG ("Expected header, but found none.\n"); - return FALSE; - } - - * start -= sizeof (APEHeader); - * length += sizeof (APEHeader); - } - - return TRUE; -} - -static bool_t ape_is_our_file (VFSFile * handle) -{ - APEHeader header; - int start, length, data_start, data_length; - - return ape_find_header (handle, & header, & start, & length, & data_start, - & data_length); -} - -static ValuePair * ape_read_item (void * * data, int length) -{ - uint32_t * header = * data; - char * value; - ValuePair * pair; - - if (length < 8) - { - TAGDBG ("Expected item, but only %d bytes remain in tag.\n", length); - return NULL; - } - - value = memchr ((char *) (* data) + 8, 0, length - 8); - - if (value == NULL) - { - TAGDBG ("Unterminated item key (max length = %d).\n", length - 8); - return NULL; - } - - value ++; - - if (header[0] > (char *) (* data) + length - value) - { - TAGDBG ("Item value of length %d, but only %d bytes remain in tag.\n", - (int) header[0], (int) ((char *) (* data) + length - value)); - return NULL; - } - - pair = g_slice_new (ValuePair); - pair->key = str_get ((char *) (* data) + 8); - pair->value = str_nget (value, header[0]); - - * data = value + header[0]; - - return pair; -} - -static GList * ape_read_items (VFSFile * handle) -{ - GList * list = NULL; - APEHeader header; - int start, length, data_start, data_length; - void * data, * item; - - if (! ape_find_header (handle, & header, & start, & length, & data_start, - & data_length)) - return NULL; - - if (vfs_fseek (handle, data_start, SEEK_SET)) - return NULL; - - data = g_malloc (data_length); - - if (vfs_fread (data, 1, data_length, handle) != data_length) - { - g_free (data); - return NULL; - } - - TAGDBG ("Reading %d items:\n", header.items); - item = data; - - while (header.items --) - { - ValuePair * pair = ape_read_item (& item, (char *) data + data_length - - (char *) item); - - if (pair == NULL) - break; - - TAGDBG ("Read: %s = %s.\n", pair->key, pair->value); - list = g_list_prepend (list, pair); - } - - g_free (data); - return g_list_reverse (list); -} - -static void free_value_pair (ValuePair * pair) -{ - str_unref (pair->key); - str_unref (pair->value); - g_slice_free (ValuePair, pair); -} - -static void parse_gain_text (const char * text, int * value, int * unit) -{ - int sign = 1; - - * value = 0; - * unit = 1; - - if (* text == '-') - { - sign = -1; - text ++; - } - - while (* text >= '0' && * text <= '9') - { - * value = * value * 10 + (* text - '0'); - text ++; - } - - if (* text == '.') - { - text ++; - - while (* text >= '0' && * text <= '9' && * value < G_MAXINT / 10) - { - * value = * value * 10 + (* text - '0'); - * unit = * unit * 10; - text ++; - } - } - - * value = * value * sign; -} - -static void set_gain_info (Tuple * tuple, int field, int unit_field, - const char * text) -{ - int value, unit; - - parse_gain_text (text, & value, & unit); - - if (tuple_get_value_type (tuple, unit_field) == TUPLE_INT) - value = value * (int64_t) tuple_get_int (tuple, unit_field) / unit; - else - tuple_set_int (tuple, unit_field, unit); - - tuple_set_int (tuple, field, value); -} - -static bool_t ape_read_tag (Tuple * tuple, VFSFile * handle) -{ - GList * list = ape_read_items (handle), * node; - - for (node = list; node != NULL; node = node->next) - { - char * key = ((ValuePair *) node->data)->key; - char * value = ((ValuePair *) node->data)->value; - - if (! strcmp (key, "Artist")) - tuple_set_str (tuple, FIELD_ARTIST, value); - else if (! strcmp (key, "Title")) - tuple_set_str (tuple, FIELD_TITLE, value); - else if (! strcmp (key, "Album")) - tuple_set_str (tuple, FIELD_ALBUM, value); - else if (! strcmp (key, "Comment")) - tuple_set_str (tuple, FIELD_COMMENT, value); - else if (! strcmp (key, "Genre")) - tuple_set_str (tuple, FIELD_GENRE, value); - else if (! strcmp (key, "Track")) - tuple_set_int (tuple, FIELD_TRACK_NUMBER, atoi (value)); - else if (! strcmp (key, "Year")) - tuple_set_int (tuple, FIELD_YEAR, atoi (value)); - else if (! g_ascii_strcasecmp (key, "REPLAYGAIN_TRACK_GAIN")) - set_gain_info (tuple, FIELD_GAIN_TRACK_GAIN, FIELD_GAIN_GAIN_UNIT, value); - else if (! g_ascii_strcasecmp (key, "REPLAYGAIN_TRACK_PEAK")) - set_gain_info (tuple, FIELD_GAIN_TRACK_PEAK, FIELD_GAIN_PEAK_UNIT, value); - else if (! g_ascii_strcasecmp (key, "REPLAYGAIN_ALBUM_GAIN")) - set_gain_info (tuple, FIELD_GAIN_ALBUM_GAIN, FIELD_GAIN_GAIN_UNIT, value); - else if (! g_ascii_strcasecmp (key, "REPLAYGAIN_ALBUM_PEAK")) - set_gain_info (tuple, FIELD_GAIN_ALBUM_PEAK, FIELD_GAIN_PEAK_UNIT, value); - } - - g_list_free_full (list, (GDestroyNotify) free_value_pair); - return TRUE; -} - -static bool_t ape_write_item (VFSFile * handle, const char * key, - const char * value, int * written_length) -{ - int key_len = strlen (key) + 1; - int value_len = strlen (value); - uint32_t header[2]; - - TAGDBG ("Write: %s = %s.\n", key, value); - - header[0] = GUINT32_TO_LE (value_len); - header[1] = 0; - - if (vfs_fwrite (header, 1, 8, handle) != 8) - return FALSE; - - if (vfs_fwrite (key, 1, key_len, handle) != key_len) - return FALSE; - - if (vfs_fwrite (value, 1, value_len, handle) != value_len) - return FALSE; - - * written_length += 8 + key_len + value_len; - return TRUE; -} - -static bool_t write_string_item (const Tuple * tuple, int field, VFSFile * - handle, const char * key, int * written_length, int * written_items) -{ - char * value = tuple_get_str (tuple, field); - - if (value == NULL) - return TRUE; - - bool_t success = ape_write_item (handle, key, value, written_length); - - if (success) - (* written_items) ++; - - str_unref (value); - return success; -} - -static bool_t write_integer_item (const Tuple * tuple, int field, VFSFile * - handle, const char * key, int * written_length, int * written_items) -{ - int value = tuple_get_int (tuple, field); - char scratch[32]; - - if (value <= 0) - return TRUE; - - str_itoa (value, scratch, sizeof scratch); - - if (! ape_write_item (handle, key, scratch, written_length)) - return FALSE; - - (* written_items) ++; - return TRUE; -} - -static bool_t write_header (int data_length, int items, bool_t is_header, - VFSFile * handle) -{ - APEHeader header; - - memcpy (header.magic, "APETAGEX", 8); - header.version = GUINT32_TO_LE (2000); - header.length = GUINT32_TO_LE (data_length + sizeof (APEHeader)); - header.items = GUINT32_TO_LE (items); - header.flags = is_header ? GUINT32_TO_LE (APE_FLAG_HAS_HEADER | - APE_FLAG_IS_HEADER) : GUINT32_TO_LE (APE_FLAG_HAS_HEADER); - header.reserved = 0; - - return vfs_fwrite (& header, 1, sizeof (APEHeader), handle) == sizeof - (APEHeader); -} - -static bool_t ape_write_tag (const Tuple * tuple, VFSFile * handle) -{ - GList * list = ape_read_items (handle), * node; - APEHeader header; - int start, length, data_start, data_length, items; - - if (ape_find_header (handle, & header, & start, & length, & data_start, - & data_length)) - { - if (start + length != vfs_fsize (handle)) - { - TAGDBG ("Writing tags is only supported at end of file.\n"); - goto ERR; - } - - if (vfs_ftruncate (handle, start)) - goto ERR; - } - else - { - start = vfs_fsize (handle); - - if (start < 0) - goto ERR; - } - - if (vfs_fseek (handle, start, SEEK_SET) || ! write_header (0, 0, TRUE, - handle)) - goto ERR; - - length = 0; - items = 0; - - if (! write_string_item (tuple, FIELD_ARTIST, handle, "Artist", & length, - & items) || ! write_string_item (tuple, FIELD_TITLE, handle, "Title", - & length, & items) || ! write_string_item (tuple, FIELD_ALBUM, handle, - "Album", & length, & items) || ! write_string_item (tuple, FIELD_COMMENT, - handle, "Comment", & length, & items) || ! write_string_item (tuple, - FIELD_GENRE, handle, "Genre", & length, & items) || ! write_integer_item - (tuple, FIELD_TRACK_NUMBER, handle, "Track", & length, & items) || - ! write_integer_item (tuple, FIELD_YEAR, handle, "Year", & length, & items)) - goto ERR; - - for (node = list; node != NULL; node = node->next) - { - char * key = ((ValuePair *) node->data)->key; - char * value = ((ValuePair *) node->data)->value; - - if (! strcmp (key, "Artist") || ! strcmp (key, "Title") || ! strcmp - (key, "Album") || ! strcmp (key, "Comment") || ! strcmp (key, "Genre") - || ! strcmp (key, "Track") || ! strcmp (key, "Year")) - continue; - - if (! ape_write_item (handle, key, value, & length)) - goto ERR; - - items ++; - } - - TAGDBG ("Wrote %d items, %d bytes.\n", items, length); - - if (! write_header (length, items, FALSE, handle) || vfs_fseek (handle, - start, SEEK_SET) || ! write_header (length, items, TRUE, handle)) - goto ERR; - - g_list_free_full (list, (GDestroyNotify) free_value_pair); - return TRUE; - -ERR: - g_list_free_full (list, (GDestroyNotify) free_value_pair); - return FALSE; -} - -tag_module_t ape = -{ - .name = "APE", - .type = TAG_TYPE_APE, - .can_handle_file = ape_is_our_file, - .read_tag = ape_read_tag, - .write_tag = ape_write_tag, -}; diff --git a/src/libaudtag/ape/ape.cc b/src/libaudtag/ape/ape.cc new file mode 100644 index 0000000..c8657b4 --- /dev/null +++ b/src/libaudtag/ape/ape.cc @@ -0,0 +1,466 @@ +/* + * ape.c + * Copyright 2010 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +/* TODO: + * - Support updating files that have their tag at the beginning? + */ + +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +#define WANT_AUD_BSWAP +#include <libaudcore/audio.h> +#include <libaudcore/audstrings.h> +#include <libaudcore/runtime.h> +#include <libaudcore/vfs.h> +#include <libaudtag/builtin.h> + +#pragma pack(push) /* must be byte-aligned */ +#pragma pack(1) +struct APEHeader { + char magic[8]; + uint32_t version; /* LE */ + uint32_t length; /* LE */ + uint32_t items; /* LE */ + uint32_t flags; /* LE */ + uint64_t reserved; +}; +#pragma pack(pop) + +struct ValuePair { + String key, value; +}; + +#define APE_FLAG_HAS_HEADER (1 << 31) +#define APE_FLAG_HAS_NO_FOOTER (1 << 30) +#define APE_FLAG_IS_HEADER (1 << 29) + +namespace audtag { + +static bool ape_read_header (VFSFile & handle, APEHeader * header) +{ + if (handle.fread (header, 1, sizeof (APEHeader)) != sizeof (APEHeader)) + return false; + + if (strncmp (header->magic, "APETAGEX", 8)) + return false; + + header->version = FROM_LE32 (header->version); + header->length = FROM_LE32 (header->length); + header->items = FROM_LE32 (header->items); + header->flags = FROM_LE32 (header->flags); + + if (header->length < sizeof (APEHeader)) + return false; + + return true; +} + +static bool ape_find_header (VFSFile & handle, APEHeader * header, + int * start, int * length, int * data_start, int * data_length) +{ + APEHeader secondary; + + if (handle.fseek (0, VFS_SEEK_SET)) + return false; + + if (ape_read_header (handle, header)) + { + AUDDBG ("Found header at 0, length = %d, version = %d.\n", + (int) header->length, (int) header->version); + + * start = 0; + * length = header->length; + * data_start = sizeof (APEHeader); + * data_length = header->length - sizeof (APEHeader); + + if (! (header->flags & APE_FLAG_HAS_HEADER) || ! (header->flags & APE_FLAG_IS_HEADER)) + { + AUDWARN ("Invalid header flags (%u).\n", (unsigned) header->flags); + return false; + } + + if (! (header->flags & APE_FLAG_HAS_NO_FOOTER)) + { + if (handle.fseek (header->length, VFS_SEEK_CUR)) + return false; + + if (! ape_read_header (handle, & secondary)) + { + AUDWARN ("Expected footer, but found none.\n"); + return false; + } + + * length += sizeof (APEHeader); + } + + return true; + } + + if (handle.fseek (-(int) sizeof (APEHeader), VFS_SEEK_END)) + return false; + + if (! ape_read_header (handle, header)) + { + /* APE tag may be followed by an ID3v1 tag */ + if (handle.fseek (-128 - (int) sizeof (APEHeader), VFS_SEEK_END)) + return false; + + if (! ape_read_header (handle, header)) + { + AUDDBG ("No header found.\n"); + return false; + } + } + + AUDDBG ("Found footer at %d, length = %d, version = %d.\n", + (int) handle.ftell () - (int) sizeof (APEHeader), (int) header->length, + (int) header->version); + + * start = handle.ftell () - header->length; + * length = header->length; + * data_start = handle.ftell () - header->length; + * data_length = header->length - sizeof (APEHeader); + + if ((header->flags & APE_FLAG_HAS_NO_FOOTER) || (header->flags & APE_FLAG_IS_HEADER)) + { + AUDWARN ("Invalid footer flags (%u).\n", (unsigned) header->flags); + return false; + } + + if (header->flags & APE_FLAG_HAS_HEADER) + { + if (handle.fseek (-(int) header->length - sizeof (APEHeader), VFS_SEEK_CUR)) + return false; + + if (! ape_read_header (handle, & secondary)) + { + AUDDBG ("Expected header, but found none.\n"); + return false; + } + + * start -= sizeof (APEHeader); + * length += sizeof (APEHeader); + } + + return true; +} + +bool APETagModule::can_handle_file (VFSFile & handle) +{ + APEHeader header; + int start, length, data_start, data_length; + + return ape_find_header (handle, & header, & start, & length, & data_start, + & data_length); +} + +/* returns start of next item or nullptr */ +static const char * ape_read_item (const char * data, int length, ValuePair & pair) +{ + auto header = (const uint32_t *) data; + const char * value; + + if (length < 8) + { + AUDWARN ("Expected item, but only %d bytes remain in tag.\n", length); + return nullptr; + } + + value = (const char *) memchr (data + 8, 0, length - 8); + + if (! value) + { + AUDWARN ("Unterminated item key (max length = %d).\n", length - 8); + return nullptr; + } + + value ++; + + if (header[0] > (unsigned) (data + length - value)) + { + AUDWARN ("Item value of length %d, but only %d bytes remain in tag.\n", + (int) header[0], (int) (data + length - value)); + return nullptr; + } + + pair.key = String (data + 8); + pair.value = String (str_copy (value, header[0])); + + return value + header[0]; +} + +static Index<ValuePair> ape_read_items (VFSFile & handle) +{ + Index<ValuePair> list; + APEHeader header; + int start, length, data_start, data_length; + + if (! ape_find_header (handle, & header, & start, & length, & data_start, & data_length)) + return list; + + if (handle.fseek (data_start, VFS_SEEK_SET)) + return list; + + Index<char> data; + data.insert (0, data_length); + + if (handle.fread (data.begin (), 1, data_length) != data_length) + return list; + + AUDDBG ("Reading %d items:\n", header.items); + const char * item = data.begin (); + + while (header.items --) + { + ValuePair pair; + if (! (item = ape_read_item (item, data.end () - item, pair))) + break; + + AUDDBG ("Read: %s = %s.\n", (const char *) pair.key, (const char *) pair.value); + list.append (std::move (pair)); + } + + return list; +} + +static void parse_gain_text (const char * text, int * value, int * unit) +{ + int sign = 1; + + * value = 0; + * unit = 1; + + if (* text == '-') + { + sign = -1; + text ++; + } + + while (* text >= '0' && * text <= '9') + { + * value = * value * 10 + (* text - '0'); + text ++; + } + + if (* text == '.') + { + text ++; + + while (* text >= '0' && * text <= '9' && * value < INT_MAX / 10) + { + * value = * value * 10 + (* text - '0'); + * unit = * unit * 10; + text ++; + } + } + + * value = * value * sign; +} + +static void set_gain_info (Tuple & tuple, Tuple::Field field, + Tuple::Field unit_field, const char * text) +{ + int value, unit; + + parse_gain_text (text, & value, & unit); + + if (tuple.get_value_type (unit_field) == Tuple::Int) + value = value * (int64_t) tuple.get_int (unit_field) / unit; + else + tuple.set_int (unit_field, unit); + + tuple.set_int (field, value); +} + +bool APETagModule::read_tag (Tuple & tuple, VFSFile & handle) +{ + Index<ValuePair> list = ape_read_items (handle); + + for (const ValuePair & pair : list) + { + if (! strcmp (pair.key, "Artist")) + tuple.set_str (Tuple::Artist, pair.value); + else if (! strcmp (pair.key, "Title")) + tuple.set_str (Tuple::Title, pair.value); + else if (! strcmp (pair.key, "Album")) + tuple.set_str (Tuple::Album, pair.value); + else if (! strcmp (pair.key, "Comment")) + tuple.set_str (Tuple::Comment, pair.value); + else if (! strcmp (pair.key, "Genre")) + tuple.set_str (Tuple::Genre, pair.value); + else if (! strcmp (pair.key, "Track")) + tuple.set_int (Tuple::Track, atoi (pair.value)); + else if (! strcmp (pair.key, "Year")) + tuple.set_int (Tuple::Year, atoi (pair.value)); + else if (! strcmp_nocase (pair.key, "REPLAYGAIN_TRACK_GAIN")) + set_gain_info (tuple, Tuple::TrackGain, Tuple::GainDivisor, pair.value); + else if (! strcmp_nocase (pair.key, "REPLAYGAIN_TRACK_PEAK")) + set_gain_info (tuple, Tuple::TrackPeak, Tuple::PeakDivisor, pair.value); + else if (! strcmp_nocase (pair.key, "REPLAYGAIN_ALBUM_GAIN")) + set_gain_info (tuple, Tuple::AlbumGain, Tuple::GainDivisor, pair.value); + else if (! strcmp_nocase (pair.key, "REPLAYGAIN_ALBUM_PEAK")) + set_gain_info (tuple, Tuple::AlbumPeak, Tuple::PeakDivisor, pair.value); + } + + return true; +} + +static bool ape_write_item (VFSFile & handle, const char * key, + const char * value, int * written_length) +{ + int key_len = strlen (key) + 1; + int value_len = strlen (value); + uint32_t header[2]; + + AUDDBG ("Write: %s = %s.\n", key, value); + + header[0] = TO_LE32 (value_len); + header[1] = 0; + + if (handle.fwrite (header, 1, 8) != 8) + return false; + + if (handle.fwrite (key, 1, key_len) != key_len) + return false; + + if (handle.fwrite (value, 1, value_len) != value_len) + return false; + + * written_length += 8 + key_len + value_len; + return true; +} + +static bool write_string_item (const Tuple & tuple, Tuple::Field field, + VFSFile & handle, const char * key, int * written_length, int * written_items) +{ + String value = tuple.get_str (field); + + if (! value) + return true; + + bool success = ape_write_item (handle, key, value, written_length); + + if (success) + (* written_items) ++; + + return success; +} + +static bool write_integer_item (const Tuple & tuple, Tuple::Field field, + VFSFile & handle, const char * key, int * written_length, int * written_items) +{ + int value = tuple.get_int (field); + + if (value <= 0) + return true; + + if (! ape_write_item (handle, key, int_to_str (value), written_length)) + return false; + + (* written_items) ++; + return true; +} + +static bool write_header (int data_length, int items, bool is_header, + VFSFile & handle) +{ + APEHeader header; + + memcpy (header.magic, "APETAGEX", 8); + header.version = TO_LE32 (2000); + header.length = TO_LE32 (data_length + sizeof (APEHeader)); + header.items = TO_LE32 (items); + header.flags = is_header ? TO_LE32 (APE_FLAG_HAS_HEADER | + APE_FLAG_IS_HEADER) : TO_LE32 (APE_FLAG_HAS_HEADER); + header.reserved = 0; + + return handle.fwrite (& header, 1, sizeof (APEHeader)) == sizeof (APEHeader); +} + +bool APETagModule::write_tag (const Tuple & tuple, VFSFile & handle) +{ + Index<ValuePair> list = ape_read_items (handle); + APEHeader header; + int start, length, data_start, data_length, items; + + if (ape_find_header (handle, & header, & start, & length, & data_start, & data_length)) + { + if (start + length != handle.fsize ()) + { + AUDERR ("Writing tags is only supported at end of file.\n"); + return false; + } + + if (handle.ftruncate (start)) + return false; + } + else + { + start = handle.fsize (); + + if (start < 0) + return false; + } + + if (handle.fseek (start, VFS_SEEK_SET) || ! write_header (0, 0, true, handle)) + return false; + + length = 0; + items = 0; + + if (! write_string_item (tuple, Tuple::Artist, handle, "Artist", & length, & items) || + ! write_string_item (tuple, Tuple::Title, handle, "Title", & length, & items) || + ! write_string_item (tuple, Tuple::Album, handle, "Album", & length, & items) || + ! write_string_item (tuple, Tuple::Comment, handle, "Comment", & length, & items) || + ! write_string_item (tuple, Tuple::Genre, handle, "Genre", & length, & items) || + ! write_integer_item (tuple, Tuple::Track, handle, "Track", & length, & items) || + ! write_integer_item (tuple, Tuple::Year, handle, "Year", & length, & items)) + return false; + + for (const ValuePair & pair : list) + { + if (! strcmp (pair.key, "Artist") || ! strcmp (pair.key, "Title") || + ! strcmp (pair.key, "Album") || ! strcmp (pair.key, "Comment") || + ! strcmp (pair.key, "Genre") || ! strcmp (pair.key, "Track") || + ! strcmp (pair.key, "Year")) + continue; + + if (! ape_write_item (handle, pair.key, pair.value, & length)) + return false; + + items ++; + } + + AUDDBG ("Wrote %d items, %d bytes.\n", items, length); + + if (! write_header (length, items, false, handle)) + return false; + + if (handle.fseek (start, VFS_SEEK_SET) < 0) + return false; + + if (! write_header (length, items, true, handle)) + return false; + + return true; +} + +} diff --git a/src/libaudtag/ape/ape.h b/src/libaudtag/ape/ape.h deleted file mode 100644 index 0f22843..0000000 --- a/src/libaudtag/ape/ape.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * ape.h - * Copyright 2010 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDTAG_APE_H -#define AUDTAG_APE_H - -#include "../audtag.h" -#include "../tag_module.h" -#include "../util.h" - -extern tag_module_t ape; - -#endif diff --git a/src/libaudtag/audtag.c b/src/libaudtag/audtag.c deleted file mode 100644 index 0ef0f3e..0000000 --- a/src/libaudtag/audtag.c +++ /dev/null @@ -1,127 +0,0 @@ -/* - * audtag.c - * Copyright 2009-2011 Paula Stanciu and John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <stdlib.h> -#include <string.h> - -#include <glib.h> - -#include <libaudcore/tuple.h> - -#include "audtag.h" -#include "tag_module.h" -#include "util.h" - -bool_t tag_verbose = FALSE; - -EXPORT void tag_set_verbose (bool_t verbose) -{ - tag_verbose = verbose; -} - -/* The tuple's file-related attributes are already set */ - -EXPORT bool_t tag_tuple_read (Tuple * tuple, VFSFile * handle) -{ - tag_module_t * module = find_tag_module (handle, TAG_TYPE_NONE); - - if (! module || ! module->read_tag) - { - TAGDBG ("read_tag() not supported for %s\n", vfs_get_filename (handle)); - return FALSE; - } - - return module->read_tag (tuple, handle); -} - -EXPORT bool_t tag_image_read (VFSFile * handle, void * * data, int64_t * size) -{ - tag_module_t * module = find_tag_module (handle, TAG_TYPE_NONE); - - if (! module || ! module->read_image) - { - TAGDBG ("read_image() not supported for %s\n", vfs_get_filename (handle)); - return FALSE; - } - - return module->read_image (handle, data, size); -} - -EXPORT bool_t tag_tuple_write (const Tuple * tuple, VFSFile * handle, int new_type) -{ - tag_module_t * module = find_tag_module (handle, new_type); - - if (! module || ! module->write_tag) - { - TAGDBG ("write_tag() not supported for %s\n", vfs_get_filename (handle)); - return FALSE; - } - - return module->write_tag (tuple, handle); -} - -EXPORT bool_t tag_update_stream_metadata (Tuple * tuple, VFSFile * handle) -{ - bool_t updated = FALSE; - char * old, * new; - int value; - - old = tuple_get_str (tuple, FIELD_TITLE); - new = vfs_get_metadata (handle, "track-name"); - - if (new && (! old || strcmp (old, new))) - { - tuple_set_str (tuple, FIELD_TITLE, new); - updated = TRUE; - } - - str_unref (old); - str_unref (new); - - old = tuple_get_str (tuple, FIELD_ARTIST); - new = vfs_get_metadata (handle, "stream-name"); - - if (new && (! old || strcmp (old, new))) - { - tuple_set_str (tuple, FIELD_ARTIST, new); - updated = TRUE; - } - - str_unref (old); - str_unref (new); - - new = vfs_get_metadata (handle, "content-bitrate"); - value = new ? atoi (new) / 1000 : 0; - - if (value && value != tuple_get_int (tuple, FIELD_BITRATE)) - { - tuple_set_int (tuple, FIELD_BITRATE, value); - updated = TRUE; - } - - str_unref (new); - - return updated; -} - -/* deprecated */ -EXPORT bool_t tag_tuple_write_to_file (Tuple * tuple, VFSFile * handle) -{ - return tag_tuple_write (tuple, handle, TAG_TYPE_NONE); -} diff --git a/src/libaudtag/audtag.cc b/src/libaudtag/audtag.cc new file mode 100644 index 0000000..ee04bcf --- /dev/null +++ b/src/libaudtag/audtag.cc @@ -0,0 +1,68 @@ +/* + * audtag.c + * Copyright 2009-2011 Paula Stanciu and John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <libaudcore/runtime.h> + +#include "audtag.h" +#include "tag_module.h" + +/* The tuple's file-related attributes are already set */ + +namespace audtag { + +EXPORT bool tuple_read (Tuple & tuple, VFSFile & handle) +{ + TagModule * module = find_tag_module (handle, TagType::None); + + if (! module) + { + AUDINFO ("read_tag() not supported for %s\n", handle.filename ()); + return false; + } + + return module->read_tag (tuple, handle); +} + +EXPORT Index<char> image_read (VFSFile & handle) +{ + TagModule * module = find_tag_module (handle, TagType::None); + + if (! module) + { + AUDINFO ("read_image() not supported for %s\n", handle.filename ()); + return Index<char> (); + } + + return module->read_image (handle); +} + +EXPORT bool tuple_write (const Tuple & tuple, VFSFile & handle, TagType new_type) +{ + TagModule * module = find_tag_module (handle, new_type); + + if (! module) + { + AUDINFO ("write_tag() not supported for %s\n", handle.filename ()); + return false; + } + + return module->write_tag (tuple, handle); +} + +} diff --git a/src/libaudtag/audtag.h b/src/libaudtag/audtag.h index b746feb..be08837 100644 --- a/src/libaudtag/audtag.h +++ b/src/libaudtag/audtag.h @@ -23,25 +23,22 @@ #include <libaudcore/tuple.h> #include <libaudcore/vfs.h> -enum +namespace audtag { + +enum class TagType { - TAG_TYPE_NONE = 0, - TAG_TYPE_APE, - TAG_TYPE_ID3V2 + None, + APE, + ID3v2 }; -void tag_set_verbose (bool_t verbose); - -bool_t tag_tuple_read (Tuple * tuple, VFSFile *fd); -bool_t tag_image_read (VFSFile * handle, void * * data, int64_t * size); +bool tuple_read (Tuple & tuple, VFSFile &fd); +Index<char> image_read (VFSFile & handle); -/* new_type specifies the type of tag (see the TAG_TYPE_* enum) that should be +/* new_type specifies the type of tag (see the TagType enum) that should be * written if the file does not have any existing tag. */ -bool_t tag_tuple_write (const Tuple * tuple, VFSFile * handle, int new_type); - -bool_t tag_update_stream_metadata (Tuple * tuple, VFSFile * handle); +bool tuple_write (const Tuple & tuple, VFSFile & handle, TagType new_type); -/* deprecated, use tag_tuple_write */ -bool_t tag_tuple_write_to_file (Tuple * tuple, VFSFile * handle); +} #endif /* AUDTAG_H */ diff --git a/src/libaudtag/builtin.h b/src/libaudtag/builtin.h new file mode 100644 index 0000000..b54617a --- /dev/null +++ b/src/libaudtag/builtin.h @@ -0,0 +1,63 @@ +/* + * builtin.h + * Copyright (c) 2014 William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "libaudtag/audtag.h" +#include "libaudtag/tag_module.h" +#include "libaudtag/util.h" + +#ifndef __LIBAUDTAG_BUILTIN_H__ +#define __LIBAUDTAG_BUILTIN_H__ + +namespace audtag { + +struct ID3v1TagModule : TagModule { + ID3v1TagModule() : TagModule("ID3v1", TagType::None) { }; + + bool can_handle_file (VFSFile &fd); + bool read_tag (Tuple & tuple, VFSFile & file); +}; + +struct ID3v22TagModule : TagModule { + ID3v22TagModule() : TagModule("ID3v2.2", TagType::None) { }; + + bool can_handle_file (VFSFile &fd); + bool read_tag (Tuple & tuple, VFSFile & file); + Index<char> read_image (VFSFile & handle); +}; + +struct ID3v24TagModule : TagModule { + ID3v24TagModule() : TagModule("ID3v2.3/v2.4", TagType::ID3v2) { }; + + bool can_handle_file (VFSFile &fd); + bool read_tag (Tuple & tuple, VFSFile & file); + Index<char> read_image (VFSFile & handle); + bool write_tag (const Tuple & tuple, VFSFile & f); +}; + +struct APETagModule : TagModule { + APETagModule() : TagModule("APE", TagType::APE) { }; + + bool can_handle_file (VFSFile &fd); + bool read_tag (Tuple & tuple, VFSFile & file); + bool write_tag (const Tuple & tuple, VFSFile & f); +}; + +} + +#endif diff --git a/src/libaudtag/id3/id3-common.c b/src/libaudtag/id3/id3-common.cc index f7ec19d..99732fe 100644 --- a/src/libaudtag/id3/id3-common.c +++ b/src/libaudtag/id3/id3-common.cc @@ -19,13 +19,11 @@ #include "id3-common.h" -#include <stdio.h> #include <stdlib.h> #include <string.h> -#include <glib.h> - #include <libaudcore/audstrings.h> +#include <libaudcore/runtime.h> #include "../util.h" @@ -45,14 +43,14 @@ static void * memchr16 (const void * mem, int16_t chr, int len) len -= 2; } - return NULL; + return nullptr; } -void id3_strnlen (const char * data, int size, int encoding, +static void id3_strnlen (const char * data, int size, int encoding, int * bytes_without_nul, int * bytes_with_nul) { - bool_t is16 = (encoding == ID3_ENCODING_UTF16 || encoding == ID3_ENCODING_UTF16_BE); - char * nul = is16 ? memchr16 (data, 0, size) : memchr (data, 0, size); + bool is16 = (encoding == ID3_ENCODING_UTF16 || encoding == ID3_ENCODING_UTF16_BE); + char * nul = is16 ? (char *) memchr16 (data, 0, size) : (char *) memchr (data, 0, size); if (nul) { @@ -70,7 +68,7 @@ void id3_strnlen (const char * data, int size, int encoding, } } -char * id3_convert (const char * data, int size, int encoding) +static StringBuf id3_convert (const char * data, int size, int encoding) { if (encoding == ID3_ENCODING_UTF16) return str_convert (data, size, "UTF-16", "UTF-8"); @@ -80,46 +78,60 @@ char * id3_convert (const char * data, int size, int encoding) return str_to_utf8 (data, size); } -char * id3_decode_text (const char * data, int size) +static StringBuf id3_decode_text (const char * data, int size) { if (size < 1) - return NULL; + return StringBuf (); - return id3_convert ((const char *) data + 1, size - 1, data[0]); + int real_size; + id3_strnlen (data + 1, size - 1, data[0], & real_size, nullptr); + return id3_convert (data + 1, real_size, data[0]); } -void id3_associate_string (Tuple * tuple, int field, const char * data, int size) +void id3_associate_string (Tuple & tuple, Tuple::Field field, const char * data, int size) { - char * text = id3_decode_text (data, size); + StringBuf text = id3_decode_text (data, size); if (text && text[0]) { - TAGDBG ("Field %i = %s.\n", field, text); - tuple_set_str (tuple, field, text); + AUDDBG ("Field %i = %s.\n", field, (const char *) text); + tuple.set_str (field, text); } - - str_unref (text); } -void id3_associate_int (Tuple * tuple, int field, const char * data, int size) +void id3_associate_int (Tuple & tuple, Tuple::Field field, const char * data, int size) { - char * text = id3_decode_text (data, size); + StringBuf text = id3_decode_text (data, size); /* Ignore zeros here. In particular, there are many ID3 tags with invalid * TLEN fields floating around, and we want to let mpg123 recalculate the * length in such cases. */ if (text && atoi (text) > 0) { - TAGDBG ("Field %i = %s.\n", field, text); - tuple_set_int (tuple, field, atoi (text)); + AUDDBG ("Field %i = %s.\n", field, (const char *) text); + tuple.set_int (field, atoi (text)); } +} - str_unref (text); +void id3_associate_length (Tuple & tuple, const char * data, int size) +{ + StringBuf text = id3_decode_text (data, size); + int decoder_length = tuple.get_int (Tuple::Length); + int tlen_length; + + AUDDBG ("Length, decoder length: %i, tag length: %s.\n", decoder_length, (const char *) text); + + if (text && (tlen_length = atoi (text))) + { + if (decoder_length <= 0 || + (tlen_length > (decoder_length / 2) && tlen_length < (decoder_length * 2))) + tuple.set_int (Tuple::Length, tlen_length); + } } -void id3_decode_genre (Tuple * tuple, const char * data, int size) +void id3_decode_genre (Tuple & tuple, const char * data, int size) { - char * text = id3_decode_text (data, size); + StringBuf text = id3_decode_text (data, size); int numericgenre; if (! text) @@ -131,14 +143,12 @@ void id3_decode_genre (Tuple * tuple, const char * data, int size) numericgenre = atoi (text); if (numericgenre > 0) - tuple_set_str (tuple, FIELD_GENRE, convert_numericgenre_to_text (numericgenre)); + tuple.set_str (Tuple::Genre, convert_numericgenre_to_text (numericgenre)); else - tuple_set_str (tuple, FIELD_GENRE, text); - - str_unref (text); + tuple.set_str (Tuple::Genre, text); } -void id3_decode_comment (Tuple * tuple, const char * data, int size) +void id3_decode_comment (Tuple & tuple, const char * data, int size) { if (size < 4) return; @@ -147,19 +157,17 @@ void id3_decode_comment (Tuple * tuple, const char * data, int size) id3_strnlen (data + 4, size - 4, data[0], & before_nul, & after_nul); const char * lang = data + 1; - char * type = id3_convert (data + 4, before_nul, data[0]); - char * value = id3_convert (data + 4 + after_nul, size - 4 - after_nul, data[0]); + StringBuf type = id3_convert (data + 4, before_nul, data[0]); + StringBuf value = id3_convert (data + 4 + after_nul, size - 4 - after_nul, data[0]); - TAGDBG ("Comment: lang = %.3s, type = %s, value = %s.\n", lang, type, value); + AUDDBG ("Comment: lang = %.3s, type = %s, value = %s.\n", lang, + (const char *) type, (const char *) value); if (type && ! type[0] && value) /* blank type = actual comment */ - tuple_set_str (tuple, FIELD_COMMENT, value); - - str_unref (type); - str_unref (value); + tuple.set_str (Tuple::Comment, value); } -static bool_t decode_rva_block (const char * * _data, int * _size, +static bool decode_rva_block (const char * * _data, int * _size, int * channel, int * adjustment, int * adjustment_unit, int * peak, int * peak_unit) { @@ -168,38 +176,38 @@ static bool_t decode_rva_block (const char * * _data, int * _size, int peak_bits; if (size < 4) - return FALSE; + return false; - * channel = data[0]; + * channel = (unsigned char) data[0]; * adjustment = (char) data[1]; /* first byte is signed */ - * adjustment = (* adjustment << 8) | data[2]; + * adjustment = (* adjustment << 8) | (unsigned char) data[2]; * adjustment_unit = 512; - peak_bits = data[3]; + peak_bits = (unsigned char) data[3]; data += 4; size -= 4; - TAGDBG ("RVA block: channel = %d, adjustment = %d/%d, peak bits = %d\n", + AUDDBG ("RVA block: channel = %d, adjustment = %d/%d, peak bits = %d\n", * channel, * adjustment, * adjustment_unit, peak_bits); - if (peak_bits > 0 && peak_bits < sizeof (int) * 8) + if (peak_bits > 0 && peak_bits < (int) sizeof (int) * 8) { int bytes = (peak_bits + 7) / 8; int count; if (bytes > size) - return FALSE; + return false; * peak = 0; * peak_unit = 1 << peak_bits; for (count = 0; count < bytes; count ++) - * peak = (* peak << 8) | data[count]; + * peak = (* peak << 8) | (unsigned char) data[count]; data += bytes; size -= count; - TAGDBG ("RVA block: peak = %d/%d\n", * peak, * peak_unit); + AUDDBG ("RVA block: peak = %d/%d\n", * peak, * peak_unit); } else { @@ -209,20 +217,20 @@ static bool_t decode_rva_block (const char * * _data, int * _size, * _data = data; * _size = size; - return TRUE; + return true; } -void id3_decode_rva (Tuple * tuple, const char * data, int size) +void id3_decode_rva (Tuple & tuple, const char * data, int size) { const char * domain; int channel, adjustment, adjustment_unit, peak, peak_unit; - if (memchr (data, 0, size) == NULL) + if (memchr (data, 0, size) == nullptr) return; domain = data; - TAGDBG ("RVA domain: %s\n", domain); + AUDDBG ("RVA domain: %s\n", domain); size -= strlen (domain) + 1; data += strlen (domain) + 1; @@ -236,44 +244,46 @@ void id3_decode_rva (Tuple * tuple, const char * data, int size) if (channel != 1) /* specific channel? */ continue; - if (tuple_get_value_type (tuple, FIELD_GAIN_GAIN_UNIT) == TUPLE_INT) - adjustment = adjustment * (int64_t) tuple_get_int (tuple, - FIELD_GAIN_GAIN_UNIT) / adjustment_unit; + if (tuple.get_value_type (Tuple::GainDivisor) == Tuple::Int) + adjustment = adjustment * (int64_t) tuple.get_int + (Tuple::GainDivisor) / adjustment_unit; else - tuple_set_int (tuple, FIELD_GAIN_GAIN_UNIT, adjustment_unit); + tuple.set_int (Tuple::GainDivisor, adjustment_unit); if (peak_unit) { - if (tuple_get_value_type (tuple, FIELD_GAIN_PEAK_UNIT) == TUPLE_INT) - peak = peak * (int64_t) tuple_get_int (tuple, - FIELD_GAIN_PEAK_UNIT) / peak_unit; + if (tuple.get_value_type (Tuple::PeakDivisor) == Tuple::Int) + peak = peak * (int64_t) tuple.get_int (Tuple::PeakDivisor) / peak_unit; else - tuple_set_int (tuple, FIELD_GAIN_PEAK_UNIT, peak_unit); + tuple.set_int (Tuple::PeakDivisor, peak_unit); } - if (! g_ascii_strcasecmp (domain, "album")) + if (! strcmp_nocase (domain, "album")) { - tuple_set_int (tuple, FIELD_GAIN_ALBUM_GAIN, adjustment); + tuple.set_int (Tuple::AlbumGain, adjustment); if (peak_unit) - tuple_set_int (tuple, FIELD_GAIN_ALBUM_PEAK, peak); + tuple.set_int (Tuple::AlbumPeak, peak); } - else if (! g_ascii_strcasecmp (domain, "track")) + else if (! strcmp_nocase (domain, "track")) { - tuple_set_int (tuple, FIELD_GAIN_TRACK_GAIN, adjustment); + tuple.set_int (Tuple::TrackGain, adjustment); if (peak_unit) - tuple_set_int (tuple, FIELD_GAIN_TRACK_PEAK, peak); + tuple.set_int (Tuple::TrackPeak, peak); } } } -bool_t id3_decode_picture (const char * data, int size, int * type, - void * * image_data, int64_t * image_size) +Index<char> id3_decode_picture (const char * data, int size) { + Index<char> buf; + const char * nul; - if (size < 2 || ! (nul = memchr (data + 1, 0, size - 2))) - return FALSE; + if (size < 2 || ! (nul = (char *) memchr (data + 1, 0, size - 2))) + return buf; + + int type = (unsigned char) nul[1]; const char * body = nul + 2; int body_size = data + size - body; @@ -282,15 +292,15 @@ bool_t id3_decode_picture (const char * data, int size, int * type, id3_strnlen (body, body_size, data[0], & before_nul2, & after_nul2); const char * mime = data + 1; - char * desc = id3_convert (body, before_nul2, data[0]); + StringBuf desc = id3_convert (body, before_nul2, data[0]); + + int image_size = body_size - after_nul2; - * type = nul[1]; - * image_size = body_size - after_nul2; - * image_data = g_memdup (body + after_nul2, * image_size); + AUDDBG ("Picture: mime = %s, type = %d, desc = %s, size = %d.\n", mime, + type, (const char *) desc, image_size); - TAGDBG ("Picture: mime = %s, type = %d, desc = %s, size = %d.\n", mime, - * type, desc, (int) * image_size); + if (type == 3 || type == 0) /* album cover or iTunes */ + buf.insert (body + after_nul2, 0, image_size); - str_unref (desc); - return TRUE; + return buf; } diff --git a/src/libaudtag/id3/id3-common.h b/src/libaudtag/id3/id3-common.h index 1b100ab..1eb3cf5 100644 --- a/src/libaudtag/id3/id3-common.h +++ b/src/libaudtag/id3/id3-common.h @@ -20,16 +20,16 @@ #ifndef AUDTAG_ID3_COMMON_H #define AUDTAG_ID3_COMMON_H -#include <stdint.h> +#include <libaudcore/index.h> #include <libaudcore/tuple.h> -void id3_associate_string (Tuple * tuple, int field, const char * data, int size); -void id3_associate_int (Tuple * tuple, int field, const char * data, int size); -void id3_decode_genre (Tuple * tuple, const char * data, int size); -void id3_decode_comment (Tuple * tuple, const char * data, int size); -void id3_decode_rva (Tuple * tuple, const char * data, int size); +void id3_associate_string (Tuple & tuple, Tuple::Field field, const char * data, int size); +void id3_associate_int (Tuple & tuple, Tuple::Field field, const char * data, int size); +void id3_associate_length (Tuple & tuple, const char * data, int size); +void id3_decode_genre (Tuple & tuple, const char * data, int size); +void id3_decode_comment (Tuple & tuple, const char * data, int size); +void id3_decode_rva (Tuple & tuple, const char * data, int size); -bool_t id3_decode_picture (const char * data, int size, int * type, - void * * image_data, int64_t * image_size); +Index<char> id3_decode_picture (const char * data, int size); #endif diff --git a/src/libaudtag/id3/id3v1.c b/src/libaudtag/id3/id3v1.c deleted file mode 100644 index 16da2d2..0000000 --- a/src/libaudtag/id3/id3v1.c +++ /dev/null @@ -1,142 +0,0 @@ -/* - * id3v1.c - * Copyright 2013 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <glib.h> - -#include <libaudcore/audstrings.h> - -#include "../tag_module.h" -#include "../util.h" - -#include "id3v1.h" - -#pragma pack(push) -#pragma pack(1) - -typedef struct { - char header[3]; - char title[30]; - char artist[30]; - char album[30]; - char year[4]; - char comment[30]; - unsigned char genre; -} ID3v1Tag; - -typedef struct { - char header[4]; - char title[60]; - char artist[60]; - char album[60]; - unsigned char speed; - char genre[30]; - char start[6]; - char end[6]; -} ID3v1Ext; - -#pragma pack(pop) - -static bool_t read_id3v1_tag (VFSFile * file, ID3v1Tag * tag) -{ - if (vfs_fseek (file, -sizeof (ID3v1Tag), SEEK_END) < 0) - return FALSE; - if (vfs_fread (tag, 1, sizeof (ID3v1Tag), file) != sizeof (ID3v1Tag)) - return FALSE; - - return ! strncmp (tag->header, "TAG", 3); -} - -static bool_t read_id3v1_ext (VFSFile * file, ID3v1Ext * ext) -{ - if (vfs_fseek (file, -(sizeof (ID3v1Ext) + sizeof (ID3v1Tag)), SEEK_END) < 0) - return FALSE; - if (vfs_fread (ext, 1, sizeof (ID3v1Ext), file) != sizeof (ID3v1Ext)) - return FALSE; - - return ! strncmp (ext->header, "TAG+", 4); -} - -static bool_t id3v1_can_handle_file (VFSFile * file) -{ - ID3v1Tag tag; - return read_id3v1_tag (file, & tag); -} - -static bool_t combine_string (Tuple * tuple, int field, const char * str1, - int size1, const char * str2, int size2) -{ - char str[size1 + size2 + 1]; - - memset (str, 0, sizeof str); - strncpy (str, str1, size1); - strncpy (str + strlen (str), str2, size2); - - g_strchomp (str); - - if (! str[0]) - return FALSE; - - char * utf8 = str_to_utf8 (str, -1); - if (! utf8) - return FALSE; - - tuple_set_str (tuple, field, utf8); - - str_unref (utf8); - return TRUE; -} - -static bool_t id3v1_read_tag (Tuple * tuple, VFSFile * file) -{ - ID3v1Tag tag; - ID3v1Ext ext; - - if (! read_id3v1_tag (file, & tag)) - return FALSE; - - if (! read_id3v1_ext (file, & ext)) - memset (& ext, 0, sizeof (ID3v1Ext)); - - combine_string (tuple, FIELD_TITLE, tag.title, sizeof tag.title, ext.title, sizeof ext.title); - combine_string (tuple, FIELD_ARTIST, tag.artist, sizeof tag.artist, ext.artist, sizeof ext.artist); - combine_string (tuple, FIELD_ALBUM, tag.album, sizeof tag.album, ext.album, sizeof ext.album); - combine_string (tuple, FIELD_COMMENT, tag.comment, sizeof tag.comment, NULL, 0); - - SNCOPY (year, tag.year, 4); - if (atoi (year)) - tuple_set_int (tuple, FIELD_YEAR, atoi (year)); - - if (! tag.comment[28] && tag.comment[29]) - tuple_set_int (tuple, FIELD_TRACK_NUMBER, (unsigned char) tag.comment[29]); - - if (! combine_string (tuple, FIELD_GENRE, ext.genre, sizeof ext.genre, NULL, 0)) - tuple_set_str (tuple, FIELD_GENRE, convert_numericgenre_to_text (tag.genre)); - - return TRUE; -} - -tag_module_t id3v1 = { - .name = "ID3v1", - .can_handle_file = id3v1_can_handle_file, - .read_tag = id3v1_read_tag, -}; diff --git a/src/libaudtag/id3/id3v1.cc b/src/libaudtag/id3/id3v1.cc new file mode 100644 index 0000000..ecae397 --- /dev/null +++ b/src/libaudtag/id3/id3v1.cc @@ -0,0 +1,131 @@ +/* + * id3v1.c + * Copyright 2013 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <stdlib.h> +#include <string.h> + +#include <glib.h> /* for g_strchomp */ + +#include <libaudcore/audstrings.h> +#include <libaudtag/builtin.h> + +#pragma pack(push) +#pragma pack(1) + +struct ID3v1Tag { + char header[3]; + char title[30]; + char artist[30]; + char album[30]; + char year[4]; + char comment[30]; + unsigned char genre; +}; + +struct ID3v1Ext { + char header[4]; + char title[60]; + char artist[60]; + char album[60]; + unsigned char speed; + char genre[30]; + char start[6]; + char end[6]; +}; + +#pragma pack(pop) + +namespace audtag { + +static bool read_id3v1_tag (VFSFile & file, ID3v1Tag * tag) +{ + if (file.fseek (-sizeof (ID3v1Tag), VFS_SEEK_END) < 0) + return false; + if (file.fread (tag, 1, sizeof (ID3v1Tag)) != sizeof (ID3v1Tag)) + return false; + + return ! strncmp (tag->header, "TAG", 3); +} + +static bool read_id3v1_ext (VFSFile & file, ID3v1Ext * ext) +{ + if (file.fseek (-(sizeof (ID3v1Ext) + sizeof (ID3v1Tag)), VFS_SEEK_END) < 0) + return false; + if (file.fread (ext, 1, sizeof (ID3v1Ext)) != sizeof (ID3v1Ext)) + return false; + + return ! strncmp (ext->header, "TAG+", 4); +} + +bool ID3v1TagModule::can_handle_file (VFSFile & file) +{ + ID3v1Tag tag; + return read_id3v1_tag (file, & tag); +} + +static bool combine_string (Tuple & tuple, Tuple::Field field, + const char * str1, int size1, const char * str2, int size2) +{ + StringBuf str = str_copy (str1, strlen_bounded (str1, size1)); + str.insert (-1, str2, strlen_bounded (str2, size2)); + + g_strchomp (str); + str.resize (strlen (str)); + + if (! str.len ()) + return false; + + tuple.set_str (field, str); + return true; +} + +bool ID3v1TagModule::read_tag (Tuple & tuple, VFSFile & file) +{ + ID3v1Tag tag; + ID3v1Ext ext; + + if (! read_id3v1_tag (file, & tag)) + return false; + + if (! read_id3v1_ext (file, & ext)) + memset (& ext, 0, sizeof (ID3v1Ext)); + + combine_string (tuple, Tuple::Title, tag.title, sizeof tag.title, ext.title, sizeof ext.title); + combine_string (tuple, Tuple::Artist, tag.artist, sizeof tag.artist, ext.artist, sizeof ext.artist); + combine_string (tuple, Tuple::Album, tag.album, sizeof tag.album, ext.album, sizeof ext.album); + combine_string (tuple, Tuple::Comment, tag.comment, sizeof tag.comment, nullptr, 0); + + StringBuf year = str_copy (tag.year, strlen_bounded (tag.year, 4)); + if (atoi (year)) + tuple.set_int (Tuple::Year, atoi (year)); + + if (! tag.comment[28] && tag.comment[29]) + tuple.set_int (Tuple::Track, (unsigned char) tag.comment[29]); + + if (! combine_string (tuple, Tuple::Genre, ext.genre, sizeof ext.genre, nullptr, 0)) + { + const char * genre = convert_numericgenre_to_text (tag.genre); + if (genre) + tuple.set_str (Tuple::Genre, genre); + } + + return true; +} + +} diff --git a/src/libaudtag/id3/id3v1.h b/src/libaudtag/id3/id3v1.h deleted file mode 100644 index 623982d..0000000 --- a/src/libaudtag/id3/id3v1.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * id3v1.h - * Copyright 2010 Tony Vroon - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef ID3V1_H -#define ID3V1_H - -#include "../tag_module.h" - -extern tag_module_t id3v1; - -#endif diff --git a/src/libaudtag/id3/id3v22.c b/src/libaudtag/id3/id3v22.c deleted file mode 100644 index 7e886fd..0000000 --- a/src/libaudtag/id3/id3v22.c +++ /dev/null @@ -1,343 +0,0 @@ -/* - * id3v22.c - * Copyright 2009-2011 Paula Stanciu, Tony Vroon, John Lindgren, - * and William Pitcock - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include <libaudcore/audstrings.h> - -#include "id3-common.h" -#include "id3v22.h" -#include "../util.h" - -enum -{ - ID3_ALBUM = 0, - ID3_TITLE, - ID3_COMPOSER, - ID3_COPYRIGHT, - ID3_DATE, - ID3_LENGTH, - ID3_ARTIST, - ID3_TRACKNR, - ID3_YEAR, - ID3_GENRE, - ID3_COMMENT, - ID3_ENCODER, - ID3_TXX, - ID3_RVA, - ID3_FUCKO_ARTIST, - ID3_TAGS_NO -}; - -static const char * id3_frames[ID3_TAGS_NO] = {"TAL", "TT2", "TCM", "TCR", -"TDA", "TLE", "TPE", "TRK", "TYE", "TCO", "COM", "TSS", "TXX", "RVA", "TP1"}; - -#pragma pack(push) /* must be byte-aligned */ -#pragma pack(1) -typedef struct -{ - char magic[3]; - unsigned char version; - unsigned char revision; - unsigned char flags; - uint32_t size; -} -ID3v2Header; - -typedef struct -{ - char key[3]; - unsigned char size[3]; -} -ID3v2FrameHeader; -#pragma pack(pop) - -typedef struct -{ - char key[5]; - unsigned char * data; - int size; -} -GenericFrame; - -#define ID3_HEADER_SYNCSAFE 0x40 -#define ID3_HEADER_COMPRESSED 0x20 - -#define TAG_SIZE 1 - -#define write_syncsafe_int32(x) vfs_fput_be32 (syncsafe32 (x)) - -static bool_t validate_header (ID3v2Header * header) -{ - if (memcmp (header->magic, "ID3", 3)) - return FALSE; - - if ((header->version != 2)) - return FALSE; - - header->size = unsyncsafe32(GUINT32_FROM_BE(header->size)); - - TAGDBG ("Found ID3v2 header:\n"); - TAGDBG (" magic = %.3s\n", header->magic); - TAGDBG (" version = %d\n", (int) header->version); - TAGDBG (" revision = %d\n", (int) header->revision); - TAGDBG (" flags = %x\n", (int) header->flags); - TAGDBG (" size = %d\n", (int) header->size); - return TRUE; -} - -static bool_t read_header (VFSFile * handle, int * version, bool_t * - syncsafe, gsize * offset, int * header_size, int * data_size) -{ - ID3v2Header header; - - if (vfs_fseek (handle, 0, SEEK_SET)) - return FALSE; - - if (vfs_fread (& header, 1, sizeof (ID3v2Header), handle) != sizeof - (ID3v2Header)) - return FALSE; - - if (validate_header (& header)) - { - * offset = 0; - * version = header.version; - * header_size = sizeof (ID3v2Header); - * data_size = header.size; - } else return FALSE; - - * syncsafe = (header.flags & ID3_HEADER_SYNCSAFE) ? TRUE : FALSE; - - TAGDBG ("Offset = %d, header size = %d, data size = %d\n", - (int) * offset, * header_size, * data_size); - - return TRUE; -} - -static bool_t read_frame (VFSFile * handle, int max_size, int version, - bool_t syncsafe, int * frame_size, char * key, char * * data, int * size) -{ - ID3v2FrameHeader header; - int i; - uint32_t hdrsz = 0; - - if ((max_size -= sizeof (ID3v2FrameHeader)) < 0) - return FALSE; - - if (vfs_fread (& header, 1, sizeof (ID3v2FrameHeader), handle) != sizeof - (ID3v2FrameHeader)) - return FALSE; - - if (! header.key[0]) /* padding */ - return FALSE; - - for (i = 0; i < 3; i++) - { - hdrsz |= (uint32_t) header.size[i] << ((2 - i) * 8); - TAGDBG("header.size[%d] = %d hdrsz %d slot %d\n", i, header.size[i], hdrsz, 2 - i); - } - -// hdrsz = GUINT32_TO_BE(hdrsz); - if (hdrsz > max_size || hdrsz == 0) - return FALSE; - - TAGDBG ("Found frame:\n"); - TAGDBG (" key = %.3s\n", header.key); - TAGDBG (" size = %d\n", (int) hdrsz); - - * frame_size = sizeof (ID3v2FrameHeader) + hdrsz; - g_strlcpy (key, header.key, 4); - - * size = hdrsz; - * data = g_malloc (* size); - - if (vfs_fread (* data, 1, * size, handle) != * size) - return FALSE; - - TAGDBG ("Data size = %d.\n", * size); - return TRUE; -} - - -static int get_frame_id (const char * key) -{ - int id; - - for (id = 0; id < ID3_TAGS_NO; id ++) - { - if (! strcmp (key, id3_frames[id])) - return id; - } - - return -1; -} - -static bool_t id3v22_can_handle_file (VFSFile * handle) -{ - int version, header_size, data_size; - bool_t syncsafe; - gsize offset; - - return read_header (handle, & version, & syncsafe, & offset, & header_size, - & data_size); -} - -bool_t id3v22_read_tag (Tuple * tuple, VFSFile * handle) -{ - int version, header_size, data_size; - bool_t syncsafe; - gsize offset; - int pos; - - if (! read_header (handle, & version, & syncsafe, & offset, & header_size, - & data_size)) - return FALSE; - - TAGDBG ("Reading tags from %i bytes of ID3 data in %s\n", data_size, - vfs_get_filename (handle)); - - for (pos = 0; pos < data_size; ) - { - int frame_size, size, id; - char key[5]; - char * data; - - if (! read_frame (handle, data_size - pos, version, syncsafe, - & frame_size, key, & data, & size)) - { - TAGDBG("read_frame failed at pos %i\n", pos); - break; - } - - id = get_frame_id (key); - - switch (id) - { - case ID3_ALBUM: - id3_associate_string (tuple, FIELD_ALBUM, data, size); - break; - case ID3_TITLE: - id3_associate_string (tuple, FIELD_TITLE, data, size); - break; - case ID3_COMPOSER: - id3_associate_string (tuple, FIELD_COMPOSER, data, size); - break; - case ID3_COPYRIGHT: - id3_associate_string (tuple, FIELD_COPYRIGHT, data, size); - break; - case ID3_DATE: - id3_associate_string (tuple, FIELD_DATE, data, size); - break; - case ID3_LENGTH: - id3_associate_int (tuple, FIELD_LENGTH, data, size); - break; - case ID3_FUCKO_ARTIST: - case ID3_ARTIST: - id3_associate_string (tuple, FIELD_ARTIST, data, size); - break; - case ID3_TRACKNR: - id3_associate_int (tuple, FIELD_TRACK_NUMBER, data, size); - break; - case ID3_YEAR: - id3_associate_int (tuple, FIELD_YEAR, data, size); - break; - case ID3_GENRE: - id3_decode_genre (tuple, data, size); - break; - case ID3_COMMENT: - id3_decode_comment (tuple, data, size); - break; - case ID3_RVA: - id3_decode_rva (tuple, data, size); - break; - default: - TAGDBG ("Ignoring unsupported ID3 frame %s.\n", key); - break; - } - - g_free (data); - pos += frame_size; - } - - return TRUE; -} - -static bool_t id3v22_read_image (VFSFile * handle, void * * image_data, int64_t * image_size) -{ - int version, header_size, data_size, parsed; - bool_t syncsafe; - gsize offset; - bool_t found = FALSE; - - if (! read_header (handle, & version, & syncsafe, & offset, & header_size, - & data_size)) - return FALSE; - - for (parsed = 0; parsed < data_size && ! found; ) - { - int frame_size, size, type; - char key[5]; - char * data; - int frame_length; - - if (! read_frame (handle, data_size - parsed, version, syncsafe, - & frame_size, key, & data, & size)) - break; - - frame_length = size; - - if (! strcmp (key, "PIC") && id3_decode_picture (data, frame_length, - & type, image_data, image_size)) - { - if (type == 3) /* album cover */ - found = TRUE; - else if (type == 0) /* iTunes */ - found = TRUE; - else if (*image_data != NULL) - { - g_free(*image_data); - *image_data = NULL; - } - } - - g_free (data); - parsed += frame_size; - } - - return found; -} - -static bool_t id3v22_write_tag (const Tuple * tuple, VFSFile * f) -{ - fprintf (stderr, "Writing ID3v2.2 tags is not implemented yet, sorry.\n"); - return FALSE; -} - -tag_module_t id3v22 = -{ - .name = "ID3v2.2", - .can_handle_file = id3v22_can_handle_file, - .read_tag = id3v22_read_tag, - .read_image = id3v22_read_image, - .write_tag = id3v22_write_tag, -}; diff --git a/src/libaudtag/id3/id3v22.cc b/src/libaudtag/id3/id3v22.cc new file mode 100644 index 0000000..e905e82 --- /dev/null +++ b/src/libaudtag/id3/id3v22.cc @@ -0,0 +1,315 @@ +/* + * id3v22.c + * Copyright 2009-2014 Paula Stanciu, Tony Vroon, John Lindgren, + * and William Pitcock + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <stdlib.h> +#include <string.h> + +#define WANT_AUD_BSWAP +#include <libaudcore/audio.h> +#include <libaudcore/audstrings.h> +#include <libaudcore/runtime.h> +#include <libaudtag/builtin.h> + +#include "id3-common.h" + +enum +{ + ID3_ALBUM = 0, + ID3_TITLE, + ID3_COMPOSER, + ID3_COPYRIGHT, + ID3_DATE, + ID3_LENGTH, + ID3_ARTIST, + ID3_ALBUM_ARTIST, + ID3_TRACKNR, + ID3_YEAR, + ID3_GENRE, + ID3_COMMENT, + ID3_ENCODER, + ID3_TXX, + ID3_RVA, + ID3_TAGS_NO +}; + +static const char * id3_frames[ID3_TAGS_NO] = { + "TAL", + "TT2", + "TCM", + "TCR", + "TDA", + "TLE", + "TP1", + "TP2", + "TRK", + "TYE", + "TCO", + "COM", + "TSS", + "TXX", + "RVA" +}; + +#pragma pack(push) /* must be byte-aligned */ +#pragma pack(1) +struct ID3v2Header { + char magic[3]; + unsigned char version; + unsigned char revision; + unsigned char flags; + uint32_t size; +}; + +struct ID3v2FrameHeader { + char key[3]; + unsigned char size[3]; +}; +#pragma pack(pop) + +struct GenericFrame : public Index<char> { + String key; +}; + +#define ID3_HEADER_SYNCSAFE 0x40 +#define ID3_HEADER_COMPRESSED 0x20 + +namespace audtag { + +static bool validate_header (ID3v2Header * header) +{ + if (memcmp (header->magic, "ID3", 3)) + return false; + + if ((header->version != 2)) + return false; + + header->size = unsyncsafe32 (FROM_BE32 (header->size)); + + AUDDBG ("Found ID3v2 header:\n"); + AUDDBG (" magic = %.3s\n", header->magic); + AUDDBG (" version = %d\n", (int) header->version); + AUDDBG (" revision = %d\n", (int) header->revision); + AUDDBG (" flags = %x\n", (int) header->flags); + AUDDBG (" size = %d\n", (int) header->size); + return true; +} + +static bool read_header (VFSFile & handle, int * version, bool * + syncsafe, int64_t * offset, int * header_size, int * data_size) +{ + ID3v2Header header; + + if (handle.fseek (0, VFS_SEEK_SET)) + return false; + + if (handle.fread (& header, 1, sizeof (ID3v2Header)) != sizeof + (ID3v2Header)) + return false; + + if (validate_header (& header)) + { + * offset = 0; + * version = header.version; + * header_size = sizeof (ID3v2Header); + * data_size = header.size; + } + else + return false; + + * syncsafe = (header.flags & ID3_HEADER_SYNCSAFE) ? true : false; + + AUDDBG ("Offset = %d, header size = %d, data size = %d\n", + (int) * offset, * header_size, * data_size); + + return true; +} + +static bool read_frame (VFSFile & handle, int max_size, int version, + bool syncsafe, int * frame_size, GenericFrame & frame) +{ + ID3v2FrameHeader header; + uint32_t hdrsz = 0; + + if ((max_size -= sizeof (ID3v2FrameHeader)) < 0) + return false; + + if (handle.fread (& header, 1, sizeof (ID3v2FrameHeader)) != sizeof + (ID3v2FrameHeader)) + return false; + + if (! header.key[0]) /* padding */ + return false; + + for (int i = 0; i < 3; i++) + { + hdrsz |= (uint32_t) header.size[i] << ((2 - i) * 8); + AUDDBG ("header.size[%d] = %d hdrsz %d slot %d\n", i, header.size[i], hdrsz, 2 - i); + } + + if (hdrsz > (unsigned) max_size || hdrsz == 0) + return false; + + AUDDBG ("Found frame:\n"); + AUDDBG (" key = %.3s\n", header.key); + AUDDBG (" size = %d\n", (int) hdrsz); + + * frame_size = sizeof (ID3v2FrameHeader) + hdrsz; + + frame.key = String (str_copy (header.key, 3)); + frame.clear (); + frame.insert (0, hdrsz); + + if (handle.fread (& frame[0], 1, frame.len ()) != frame.len ()) + return false; + + AUDDBG ("Data size = %d.\n", frame.len ()); + return true; +} + + +static int get_frame_id (const char * key) +{ + int id; + + for (id = 0; id < ID3_TAGS_NO; id ++) + { + if (! strcmp (key, id3_frames[id])) + return id; + } + + return -1; +} + +bool ID3v22TagModule::can_handle_file (VFSFile & handle) +{ + int version, header_size, data_size; + bool syncsafe; + int64_t offset; + + return read_header (handle, & version, & syncsafe, & offset, & header_size, + & data_size); +} + +bool ID3v22TagModule::read_tag (Tuple & tuple, VFSFile & handle) +{ + int version, header_size, data_size; + bool syncsafe; + int64_t offset; + int pos; + + if (! read_header (handle, & version, & syncsafe, & offset, & header_size, + & data_size)) + return false; + + AUDDBG ("Reading tags from %i bytes of ID3 data in %s\n", data_size, + handle.filename ()); + + for (pos = 0; pos < data_size; ) + { + int frame_size; + GenericFrame frame; + + if (! read_frame (handle, data_size - pos, version, syncsafe, & frame_size, frame)) + { + AUDDBG("read_frame failed at pos %i\n", pos); + break; + } + + switch (get_frame_id (frame.key)) + { + case ID3_ALBUM: + id3_associate_string (tuple, Tuple::Album, & frame[0], frame.len ()); + break; + case ID3_TITLE: + id3_associate_string (tuple, Tuple::Title, & frame[0], frame.len ()); + break; + case ID3_COMPOSER: + id3_associate_string (tuple, Tuple::Composer, & frame[0], frame.len ()); + break; + case ID3_COPYRIGHT: + id3_associate_string (tuple, Tuple::Copyright, & frame[0], frame.len ()); + break; + case ID3_DATE: + id3_associate_string (tuple, Tuple::Date, & frame[0], frame.len ()); + break; + case ID3_LENGTH: + id3_associate_length (tuple, & frame[0], frame.len ()); + break; + case ID3_ARTIST: + id3_associate_string (tuple, Tuple::Artist, & frame[0], frame.len ()); + break; + case ID3_ALBUM_ARTIST: + id3_associate_string (tuple, Tuple::AlbumArtist, & frame[0], frame.len ()); + break; + case ID3_TRACKNR: + id3_associate_int (tuple, Tuple::Track, & frame[0], frame.len ()); + break; + case ID3_YEAR: + id3_associate_int (tuple, Tuple::Year, & frame[0], frame.len ()); + break; + case ID3_GENRE: + id3_decode_genre (tuple, & frame[0], frame.len ()); + break; + case ID3_COMMENT: + id3_decode_comment (tuple, & frame[0], frame.len ()); + break; + case ID3_RVA: + id3_decode_rva (tuple, & frame[0], frame.len ()); + break; + default: + AUDDBG ("Ignoring unsupported ID3 frame %s.\n", (const char *) frame.key); + break; + } + + pos += frame_size; + } + + return true; +} + +Index<char> ID3v22TagModule::read_image (VFSFile & handle) +{ + int version, header_size, data_size, parsed; + bool syncsafe; + int64_t offset; + Index<char> buf; + + if (! read_header (handle, & version, & syncsafe, & offset, & header_size, + & data_size)) + return buf; + + for (parsed = 0; parsed < data_size && ! buf.len (); ) + { + int frame_size; + GenericFrame frame; + + if (! read_frame (handle, data_size - parsed, version, syncsafe, & frame_size, frame)) + break; + + if (! strcmp (frame.key, "PIC")) + buf = id3_decode_picture (& frame[0], frame.len ()); + + parsed += frame_size; + } + + return buf; +} + +} diff --git a/src/libaudtag/id3/id3v22.h b/src/libaudtag/id3/id3v22.h deleted file mode 100644 index f78f8bb..0000000 --- a/src/libaudtag/id3/id3v22.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * id3v22.h - * Copyright 2010 Tony Vroon - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDTAG_ID3V22_H -#define AUDTAG_ID3V22_H - -#include "../audtag.h" -#include "../tag_module.h" -#include "../util.h" - -extern tag_module_t id3v22; - -#endif diff --git a/src/libaudtag/id3/id3v24.c b/src/libaudtag/id3/id3v24.c deleted file mode 100644 index fac1a44..0000000 --- a/src/libaudtag/id3/id3v24.c +++ /dev/null @@ -1,807 +0,0 @@ -/* - * id3v24.c - * Copyright 2009-2014 Paula Stanciu, Tony Vroon, John Lindgren, - * Mikael Magnusson, and MichaĆ Lipski - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include <libaudcore/audstrings.h> - -#include "id3-common.h" -#include "id3v24.h" -#include "../util.h" - -enum -{ - ID3_ALBUM = 0, - ID3_TITLE, - ID3_COMPOSER, - ID3_COPYRIGHT, - ID3_DATE, - ID3_LENGTH, - ID3_ARTIST, - ID3_TRACKNR, - ID3_YEAR, - ID3_GENRE, - ID3_COMMENT, - ID3_PRIVATE, - ID3_ENCODER, - ID3_RECORDING_TIME, - ID3_TXXX, - ID3_RVA2, - ID3_TAGS_NO -}; - -static const char * id3_frames[ID3_TAGS_NO] = {"TALB", "TIT2", "TCOM", "TCOP", - "TDAT", "TLEN", "TPE1", "TRCK", "TYER", "TCON", "COMM", "PRIV", "TSSE", "TDRC", - "TXXX", "RVA2"}; - -static const unsigned char PRIMARY_CLASS_MUSIC[16] = {0xBC, 0x7D, 0x60, 0xD1, 0x23, - 0xE3, 0xE2, 0x4B, 0x86, 0xA1, 0x48, 0xA4, 0x2A, 0x28, 0x44, 0x1E}; -static const unsigned char PRIMARY_CLASS_AUDIO[16] = {0x29, 0x0F, 0xCD, 0x01, 0x4E, - 0xDA, 0x57, 0x41, 0x89, 0x7B, 0x62, 0x75, 0xD5, 0x0C, 0x4F, 0x11}; -static const unsigned char SECONDARY_CLASS_AUDIOBOOK[16] = {0xEB, 0x6B, 0x23, 0xE0, - 0x81, 0xC2, 0xDE, 0x4E, 0xA3, 0x6D, 0x7A, 0xF7, 0x6A, 0x3D, 0x45, 0xB5}; -static const unsigned char SECONDARY_CLASS_SPOKENWORD[16] = {0x13, 0x2A, 0x17, 0x3A, - 0xD9, 0x2B, 0x31, 0x48, 0x83, 0x5B, 0x11, 0x4F, 0x6A, 0x95, 0x94, 0x3F}; -static const unsigned char SECONDARY_CLASS_NEWS[16] = {0x9B, 0xDB, 0x77, 0x66, 0xA0, - 0xE5, 0x63, 0x40, 0xA1, 0xAD, 0xAC, 0xEB, 0x52, 0x84, 0x0C, 0xF1}; -static const unsigned char SECONDARY_CLASS_TALKSHOW[16] = {0x67, 0x4A, 0x82, 0x1B, - 0x80, 0x3F, 0x3E, 0x4E, 0x9C, 0xDE, 0xF7, 0x36, 0x1B, 0x0F, 0x5F, 0x1B}; -static const unsigned char SECONDARY_CLASS_GAMES_CLIP[16] = {0x68, 0x33, 0x03, 0x00, - 0x09, 0x50, 0xC3, 0x4A, 0xA8, 0x20, 0x5D, 0x2D, 0x09, 0xA4, 0xE7, 0xC1}; -static const unsigned char SECONDARY_CLASS_GAMES_SONG[16] = {0x31, 0xF7, 0x4F, 0xF2, - 0xFC, 0x96, 0x0F, 0x4D, 0xA2, 0xF5, 0x5A, 0x34, 0x83, 0x68, 0x2B, 0x1A}; - -#pragma pack(push) /* must be byte-aligned */ -#pragma pack(1) -typedef struct -{ - char magic[3]; - unsigned char version; - unsigned char revision; - unsigned char flags; - uint32_t size; -} -ID3v2Header; - -typedef struct -{ - char key[4]; - uint32_t size; - uint16_t flags; -} -ID3v2FrameHeader; -#pragma pack(pop) - -typedef struct -{ - char key[5]; - char * data; - int size; -} -GenericFrame; - -#define ID3_HEADER_SYNCSAFE 0x80 -#define ID3_HEADER_HAS_EXTENDED_HEADER 0x40 -#define ID3_HEADER_HAS_FOOTER 0x10 - -#define ID3_FRAME_HAS_GROUP 0x0040 -#define ID3_FRAME_COMPRESSED 0x0008 -#define ID3_FRAME_ENCRYPTED 0x0004 -#define ID3_FRAME_SYNCSAFE 0x0002 -#define ID3_FRAME_HAS_LENGTH 0x0001 - -static bool_t skip_extended_header_3 (VFSFile * handle, int * _size) -{ - uint32_t size; - - if (vfs_fread (& size, 1, 4, handle) != 4) - return FALSE; - - size = GUINT32_FROM_BE (size); - - TAGDBG ("Found v2.3 extended header, size = %d.\n", (int) size); - - if (vfs_fseek (handle, size, SEEK_CUR)) - return FALSE; - - * _size = 4 + size; - return TRUE; -} - -static bool_t skip_extended_header_4 (VFSFile * handle, int * _size) -{ - uint32_t size; - - if (vfs_fread (& size, 1, 4, handle) != 4) - return FALSE; - - size = unsyncsafe32 (GUINT32_FROM_BE (size)); - - TAGDBG ("Found v2.4 extended header, size = %d.\n", (int) size); - - if (vfs_fseek (handle, size - 4, SEEK_CUR)) - return FALSE; - - * _size = size; - return TRUE; -} - -static bool_t validate_header (ID3v2Header * header, bool_t is_footer) -{ - if (memcmp (header->magic, is_footer ? "3DI" : "ID3", 3)) - return FALSE; - - if ((header->version != 3 && header->version != 4) || header->revision != 0) - return FALSE; - - header->size = unsyncsafe32 (GUINT32_FROM_BE (header->size)); - - TAGDBG ("Found ID3v2 %s:\n", is_footer ? "footer" : "header"); - TAGDBG (" magic = %.3s\n", header->magic); - TAGDBG (" version = %d\n", (int) header->version); - TAGDBG (" revision = %d\n", (int) header->revision); - TAGDBG (" flags = %x\n", (int) header->flags); - TAGDBG (" size = %d\n", (int) header->size); - return TRUE; -} - -static bool_t read_header (VFSFile * handle, int * version, bool_t * - syncsafe, int64_t * offset, int * header_size, int * data_size, int * - footer_size) -{ - ID3v2Header header, footer; - - if (vfs_fseek (handle, 0, SEEK_SET)) - return FALSE; - - if (vfs_fread (& header, 1, sizeof (ID3v2Header), handle) != sizeof - (ID3v2Header)) - return FALSE; - - if (validate_header (& header, FALSE)) - { - * offset = 0; - * version = header.version; - * header_size = sizeof (ID3v2Header); - * data_size = header.size; - - if (header.flags & ID3_HEADER_HAS_FOOTER) - { - if (vfs_fseek (handle, header.size, SEEK_CUR)) - return FALSE; - - if (vfs_fread (& footer, 1, sizeof (ID3v2Header), handle) != sizeof - (ID3v2Header)) - return FALSE; - - if (! validate_header (& footer, TRUE)) - return FALSE; - - if (vfs_fseek (handle, sizeof (ID3v2Header), SEEK_SET)) - return FALSE; - - * footer_size = sizeof (ID3v2Header); - } - else - * footer_size = 0; - } - else - { - int64_t end = vfs_fsize (handle); - - if (end < 0) - return FALSE; - - if (vfs_fseek (handle, end - sizeof (ID3v2Header), SEEK_SET)) - return FALSE; - - if (vfs_fread (& footer, 1, sizeof (ID3v2Header), handle) != sizeof - (ID3v2Header)) - return FALSE; - - if (! validate_header (& footer, TRUE)) - return FALSE; - - * offset = end - 2 * sizeof (ID3v2Header) - footer.size; - * version = footer.version; - * header_size = sizeof (ID3v2Header); - * data_size = footer.size; - * footer_size = sizeof (ID3v2Header); - - if (vfs_fseek (handle, * offset, SEEK_SET)) - return FALSE; - - if (vfs_fread (& header, 1, sizeof (ID3v2Header), handle) != sizeof - (ID3v2Header)) - return FALSE; - - if (! validate_header (& header, FALSE)) - return FALSE; - } - - * syncsafe = (header.flags & ID3_HEADER_SYNCSAFE) ? TRUE : FALSE; - - if (header.flags & ID3_HEADER_HAS_EXTENDED_HEADER) - { - int extended_size = 0; - - if (header.version == 3) - { - if (! skip_extended_header_3 (handle, & extended_size)) - return FALSE; - } - else if (header.version == 4) - { - if (! skip_extended_header_4 (handle, & extended_size)) - return FALSE; - } - - * header_size += extended_size; - * data_size -= extended_size; - } - - TAGDBG ("Offset = %d, header size = %d, data size = %d, footer size = " - "%d.\n", (int) * offset, * header_size, * data_size, * footer_size); - - return TRUE; -} - -static int unsyncsafe (char * data, int size) -{ - char * get = data, * set = data; - - while (size --) - { - char c = * set ++ = * get ++; - - if (c == (char) 0xff && size && ! get[0]) - { - size --; - get ++; - } - } - - return set - data; -} - -static bool_t read_frame (VFSFile * handle, int max_size, int version, - bool_t syncsafe, int * frame_size, char * key, char * * data, int * size) -{ - ID3v2FrameHeader header; - int skip = 0; - - if ((max_size -= sizeof (ID3v2FrameHeader)) < 0) - return FALSE; - - if (vfs_fread (& header, 1, sizeof (ID3v2FrameHeader), handle) != sizeof - (ID3v2FrameHeader)) - return FALSE; - - if (! header.key[0]) /* padding */ - return FALSE; - - header.size = (version == 3) ? GUINT32_FROM_BE (header.size) : unsyncsafe32 - (GUINT32_FROM_BE (header.size)); - header.flags = GUINT16_FROM_BE (header.flags); - - if (header.size > max_size || header.size == 0) - return FALSE; - - TAGDBG ("Found frame:\n"); - TAGDBG (" key = %.4s\n", header.key); - TAGDBG (" size = %d\n", (int) header.size); - TAGDBG (" flags = %x\n", (int) header.flags); - - * frame_size = sizeof (ID3v2FrameHeader) + header.size; - g_strlcpy (key, header.key, 5); - - if (header.flags & (ID3_FRAME_COMPRESSED | ID3_FRAME_ENCRYPTED)) - { - TAGDBG ("Hit compressed/encrypted frame %s.\n", key); - return FALSE; - } - - if (header.flags & ID3_FRAME_HAS_GROUP) - skip ++; - if (header.flags & ID3_FRAME_HAS_LENGTH) - skip += 4; - - if ((skip > 0 && vfs_fseek (handle, skip, SEEK_CUR)) || skip >= header.size) - return FALSE; - - * size = header.size - skip; - * data = g_malloc (* size); - - if (vfs_fread (* data, 1, * size, handle) != * size) - return FALSE; - - if (syncsafe || (header.flags & ID3_FRAME_SYNCSAFE)) - * size = unsyncsafe (* data, * size); - - TAGDBG ("Data size = %d.\n", * size); - return TRUE; -} - -static void free_frame (GenericFrame * frame) -{ - g_free (frame->data); - g_slice_free (GenericFrame, frame); -} - -static void free_frame_list (GList * list) -{ - g_list_free_full (list, (GDestroyNotify) free_frame); -} - -static void read_all_frames (VFSFile * handle, int version, bool_t syncsafe, - int data_size, GHashTable * dict) -{ - int pos; - - for (pos = 0; pos < data_size; ) - { - int frame_size, size; - char key[5]; - char * data; - GenericFrame * frame; - - if (! read_frame (handle, data_size - pos, version, syncsafe, - & frame_size, key, & data, & size)) - break; - - pos += frame_size; - - frame = g_slice_new (GenericFrame); - strcpy (frame->key, key); - frame->data = data; - frame->size = size; - - void * key2, * list = NULL; - - if (g_hash_table_lookup_extended (dict, key, & key2, & list)) - g_hash_table_steal (dict, key); - else - key2 = str_get (key); - - list = g_list_append (list, frame); - g_hash_table_insert (dict, key2, list); - } -} - -static bool_t write_frame (int fd, GenericFrame * frame, int version, int * frame_size) -{ - TAGDBG ("Writing frame %s, size %d\n", frame->key, frame->size); - - ID3v2FrameHeader header; - - memcpy (header.key, frame->key, 4); - - uint32_t size = (version == 3) ? frame->size : syncsafe32 (frame->size); - header.size = GUINT32_TO_BE (size); - - header.flags = 0; - - if (write (fd, & header, sizeof (ID3v2FrameHeader)) != sizeof (ID3v2FrameHeader)) - return FALSE; - - if (write (fd, frame->data, frame->size) != frame->size) - return FALSE; - - * frame_size = sizeof (ID3v2FrameHeader) + frame->size; - return TRUE; -} - -typedef struct { - int fd; - int version; - int written_size; -} WriteState; - -static void write_frame_list (void * key, void * list, void * user) -{ - WriteState * state = user; - - for (GList * node = list; node; node = node->next) - { - int size; - if (write_frame (state->fd, node->data, state->version, & size)) - state->written_size += size; - } -} - -static int write_all_frames (int fd, GHashTable * dict, int version) -{ - WriteState state = {fd, version, 0}; - g_hash_table_foreach (dict, write_frame_list, & state); - - TAGDBG ("Total frame bytes written = %d.\n", state.written_size); - return state.written_size; -} - -static bool_t write_header (int fd, int version, int size) -{ - ID3v2Header header; - - memcpy (header.magic, "ID3", 3); - header.version = version; - header.revision = 0; - header.flags = 0; - header.size = syncsafe32 (size); - header.size = GUINT32_TO_BE (header.size); - - return write (fd, & header, sizeof (ID3v2Header)) == sizeof (ID3v2Header); -} - -static int get_frame_id (const char * key) -{ - int id; - - for (id = 0; id < ID3_TAGS_NO; id ++) - { - if (! strcmp (key, id3_frames[id])) - return id; - } - - return -1; -} - -#if 0 -static void decode_private_info (Tuple * tuple, const unsigned char * data, int size) -{ - char * text = g_strndup ((const char *) data, size); - - if (!strncmp(text, "WM/", 3)) - { - char *separator = strchr(text, 0); - if (separator == NULL) - goto DONE; - - char * value = separator + 1; - if (!strncmp(text, "WM/MediaClassPrimaryID", 22)) - { - if (!memcmp(value, PRIMARY_CLASS_MUSIC, 16)) - tuple_set_str (tuple, -1, "media-class", "Music"); - if (!memcmp(value, PRIMARY_CLASS_AUDIO, 16)) - tuple_set_str (tuple, -1, "media-class", "Audio (non-music)"); - } else if (!strncmp(text, "WM/MediaClassSecondaryID", 24)) - { - if (!memcmp(value, SECONDARY_CLASS_AUDIOBOOK, 16)) - tuple_set_str (tuple, -1, "media-class", "Audio Book"); - if (!memcmp(value, SECONDARY_CLASS_SPOKENWORD, 16)) - tuple_set_str (tuple, -1, "media-class", "Spoken Word"); - if (!memcmp(value, SECONDARY_CLASS_NEWS, 16)) - tuple_set_str (tuple, -1, "media-class", "News"); - if (!memcmp(value, SECONDARY_CLASS_TALKSHOW, 16)) - tuple_set_str (tuple, -1, "media-class", "Talk Show"); - if (!memcmp(value, SECONDARY_CLASS_GAMES_CLIP, 16)) - tuple_set_str (tuple, -1, "media-class", "Game Audio (clip)"); - if (!memcmp(value, SECONDARY_CLASS_GAMES_SONG, 16)) - tuple_set_str (tuple, -1, "media-class", "Game Soundtrack"); - } else { - TAGDBG("Unrecognised tag %s (Windows Media) ignored\n", text); - } - } else { - TAGDBG("Unable to decode private data, skipping: %s\n", text); - } - -DONE: - g_free (text); -} -#endif - -static GenericFrame * add_generic_frame (int id, int size, - GHashTable * dict) -{ - GenericFrame * frame = g_slice_new (GenericFrame); - - strcpy (frame->key, id3_frames[id]); - frame->data = g_malloc (size); - frame->size = size; - - GList * list = g_list_append (NULL, frame); - g_hash_table_insert (dict, str_get (id3_frames[id]), list); - - return frame; -} - -static void remove_frame (int id, GHashTable * dict) -{ - TAGDBG ("Deleting frame %s.\n", id3_frames[id]); - g_hash_table_remove (dict, id3_frames[id]); -} - -static void add_text_frame (int id, const char * text, GHashTable * dict) -{ - if (text == NULL) - { - remove_frame (id, dict); - return; - } - - TAGDBG ("Adding text frame %s = %s.\n", id3_frames[id], text); - - long words; - uint16_t * utf16 = g_utf8_to_utf16 (text, -1, NULL, & words, NULL); - g_return_if_fail (utf16); - - GenericFrame * frame = add_generic_frame (id, 3 + 2 * words, dict); - - frame->data[0] = 1; /* UTF-16 encoding */ - * (uint16_t *) (frame->data + 1) = 0xfeff; /* byte order mark */ - memcpy (frame->data + 3, utf16, 2 * words); - - g_free (utf16); -} - -static void add_comment_frame (const char * text, GHashTable * dict) -{ - if (text == NULL) - { - remove_frame (ID3_COMMENT, dict); - return; - } - - TAGDBG ("Adding comment frame = %s.\n", text); - - long words; - uint16_t * utf16 = g_utf8_to_utf16 (text, -1, NULL, & words, NULL); - g_return_if_fail (utf16); - - GenericFrame * frame = add_generic_frame (ID3_COMMENT, 10 + 2 * words, dict); - - frame->data[0] = 1; /* UTF-16 encoding */ - memcpy (frame->data + 1, "eng", 3); /* language */ - * (uint16_t *) (frame->data + 4) = 0xfeff; /* byte order mark */ - * (uint16_t *) (frame->data + 6) = 0; /* end of content description */ - * (uint16_t *) (frame->data + 8) = 0xfeff; /* byte order mark */ - memcpy (frame->data + 10, utf16, 2 * words); - - g_free (utf16); -} - -static void add_frameFromTupleStr (const Tuple * tuple, int field, int - id3_field, GHashTable * dict) -{ - char * str = tuple_get_str (tuple, field); - add_text_frame (id3_field, str, dict); - str_unref (str); -} - -static void add_frameFromTupleInt (const Tuple * tuple, int field, int - id3_field, GHashTable * dict) -{ - if (tuple_get_value_type (tuple, field) != TUPLE_INT) - { - remove_frame (id3_field, dict); - return; - } - - char scratch[16]; - str_itoa (tuple_get_int (tuple, field), scratch, sizeof scratch); - add_text_frame (id3_field, scratch, dict); -} - -static bool_t id3v24_can_handle_file (VFSFile * handle) -{ - int version, header_size, data_size, footer_size; - bool_t syncsafe; - int64_t offset; - - return read_header (handle, & version, & syncsafe, & offset, & header_size, - & data_size, & footer_size); -} - -static bool_t id3v24_read_tag (Tuple * tuple, VFSFile * handle) -{ - int version, header_size, data_size, footer_size; - bool_t syncsafe; - int64_t offset; - int pos; - - if (! read_header (handle, & version, & syncsafe, & offset, & header_size, - & data_size, & footer_size)) - return FALSE; - - for (pos = 0; pos < data_size; ) - { - int frame_size, size, id; - char key[5]; - char * data; - - if (! read_frame (handle, data_size - pos, version, syncsafe, - & frame_size, key, & data, & size)) - break; - - id = get_frame_id (key); - - switch (id) - { - case ID3_ALBUM: - id3_associate_string (tuple, FIELD_ALBUM, data, size); - break; - case ID3_TITLE: - id3_associate_string (tuple, FIELD_TITLE, data, size); - break; - case ID3_COMPOSER: - id3_associate_string (tuple, FIELD_COMPOSER, data, size); - break; - case ID3_COPYRIGHT: - id3_associate_string (tuple, FIELD_COPYRIGHT, data, size); - break; - case ID3_DATE: - id3_associate_string (tuple, FIELD_DATE, data, size); - break; - case ID3_LENGTH: - id3_associate_int (tuple, FIELD_LENGTH, data, size); - break; - case ID3_ARTIST: - id3_associate_string (tuple, FIELD_ARTIST, data, size); - break; - case ID3_TRACKNR: - id3_associate_int (tuple, FIELD_TRACK_NUMBER, data, size); - break; - case ID3_YEAR: - case ID3_RECORDING_TIME: - id3_associate_int (tuple, FIELD_YEAR, data, size); - break; - case ID3_GENRE: - id3_decode_genre (tuple, data, size); - break; - case ID3_COMMENT: - id3_decode_comment (tuple, data, size); - break; -#if 0 - case ID3_PRIVATE: - decode_private_info (tuple, data, size); - break; -#endif - case ID3_RVA2: - id3_decode_rva (tuple, data, size); - break; - default: - TAGDBG ("Ignoring unsupported ID3 frame %s.\n", key); - break; - } - - g_free (data); - pos += frame_size; - } - - return TRUE; -} - -static bool_t id3v24_read_image (VFSFile * handle, void * * image_data, - int64_t * image_size) -{ - int version, header_size, data_size, footer_size, parsed; - bool_t syncsafe; - int64_t offset; - bool_t found = FALSE; - - if (! read_header (handle, & version, & syncsafe, & offset, & header_size, - & data_size, & footer_size)) - return FALSE; - - for (parsed = 0; parsed < data_size && ! found; ) - { - int frame_size, size, type; - char key[5]; - char * data; - - if (! read_frame (handle, data_size - parsed, version, syncsafe, - & frame_size, key, & data, & size)) - break; - - if (! strcmp (key, "APIC") && id3_decode_picture (data, size, & type, - image_data, image_size)) - { - if (type == 3) /* album cover */ - found = TRUE; - else if (type == 0) /* iTunes */ - found = TRUE; - else if (*image_data != NULL) - { - g_free(*image_data); - *image_data = NULL; - } - } - - g_free (data); - parsed += frame_size; - } - - return found; -} - -static bool_t id3v24_write_tag (const Tuple * tuple, VFSFile * f) -{ - int version = 3; - int header_size, data_size, footer_size; - bool_t syncsafe; - int64_t offset; - - //read all frames into generic frames; - GHashTable * dict = g_hash_table_new_full (g_str_hash, g_str_equal, - (GDestroyNotify) str_unref, (GDestroyNotify) free_frame_list); - - if (read_header (f, & version, & syncsafe, & offset, & header_size, & data_size, & footer_size)) - read_all_frames (f, version, syncsafe, data_size, dict); - - //make the new frames from tuple and replace in the dictionary the old frames with the new ones - add_frameFromTupleStr (tuple, FIELD_TITLE, ID3_TITLE, dict); - add_frameFromTupleStr (tuple, FIELD_ARTIST, ID3_ARTIST, dict); - add_frameFromTupleStr (tuple, FIELD_ALBUM, ID3_ALBUM, dict); - add_frameFromTupleInt (tuple, FIELD_YEAR, ID3_YEAR, dict); - add_frameFromTupleInt (tuple, FIELD_TRACK_NUMBER, ID3_TRACKNR, dict); - add_frameFromTupleStr (tuple, FIELD_GENRE, ID3_GENRE, dict); - - char * comment = tuple_get_str (tuple, FIELD_COMMENT); - add_comment_frame (comment, dict); - str_unref (comment); - - /* location and size of non-tag data */ - int64_t mp3_offset = offset ? 0 : header_size + data_size + footer_size; - int64_t mp3_size = offset ? offset : -1; - - TempFile temp = {0}; - if (! open_temp_file_for (& temp, f)) - goto ERR; - - /* write empty header (will be overwritten later) */ - if (! write_header (temp.fd, version, 0)) - goto ERR; - - /* write tag data */ - data_size = write_all_frames (temp.fd, dict, version); - - /* copy non-tag data */ - if (! copy_region_to_temp_file (& temp, f, mp3_offset, mp3_size)) - goto ERR; - - /* go back to beginning and write real header */ - if (lseek (temp.fd, 0, SEEK_SET) < 0 || ! write_header (temp.fd, version, data_size)) - goto ERR; - - if (! replace_with_temp_file (& temp, f)) - goto ERR; - - g_hash_table_destroy (dict); - str_unref (temp.name); - return TRUE; - -ERR: - g_hash_table_destroy (dict); - str_unref (temp.name); - return FALSE; -} - -tag_module_t id3v24 = -{ - .name = "ID3v2.3/4", - .type = TAG_TYPE_ID3V2, - .can_handle_file = id3v24_can_handle_file, - .read_tag = id3v24_read_tag, - .read_image = id3v24_read_image, - .write_tag = id3v24_write_tag, -}; diff --git a/src/libaudtag/id3/id3v24.cc b/src/libaudtag/id3/id3v24.cc new file mode 100644 index 0000000..f272c3b --- /dev/null +++ b/src/libaudtag/id3/id3v24.cc @@ -0,0 +1,782 @@ +/* + * id3v24.c + * Copyright 2009-2014 Paula Stanciu, Tony Vroon, John Lindgren, + * Mikael Magnusson, and MichaĆ Lipski + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <glib.h> /* for g_utf8_to_utf16 */ + +#define WANT_AUD_BSWAP +#include <libaudcore/audio.h> +#include <libaudcore/audstrings.h> +#include <libaudcore/multihash.h> +#include <libaudcore/runtime.h> +#include <libaudtag/builtin.h> + +#include "id3-common.h" + +enum +{ + ID3_ALBUM = 0, + ID3_TITLE, + ID3_COMPOSER, + ID3_COPYRIGHT, + ID3_DATE, + ID3_LENGTH, + ID3_ARTIST, + ID3_ALBUM_ARTIST, + ID3_TRACKNR, + ID3_YEAR, + ID3_GENRE, + ID3_COMMENT, + ID3_PRIVATE, + ID3_ENCODER, + ID3_RECORDING_TIME, + ID3_TXXX, + ID3_RVA2, + ID3_TAGS_NO +}; + +static const char * id3_frames[ID3_TAGS_NO] = { + "TALB", + "TIT2", + "TCOM", + "TCOP", + "TDAT", + "TLEN", + "TPE1", + "TPE2", + "TRCK", + "TYER", + "TCON", + "COMM", + "PRIV", + "TSSE", + "TDRC", + "TXXX", + "RVA2" +}; + +/* +static const unsigned char PRIMARY_CLASS_MUSIC[16] = {0xBC, 0x7D, 0x60, 0xD1, 0x23, + 0xE3, 0xE2, 0x4B, 0x86, 0xA1, 0x48, 0xA4, 0x2A, 0x28, 0x44, 0x1E}; +static const unsigned char PRIMARY_CLASS_AUDIO[16] = {0x29, 0x0F, 0xCD, 0x01, 0x4E, + 0xDA, 0x57, 0x41, 0x89, 0x7B, 0x62, 0x75, 0xD5, 0x0C, 0x4F, 0x11}; +static const unsigned char SECONDARY_CLASS_AUDIOBOOK[16] = {0xEB, 0x6B, 0x23, 0xE0, + 0x81, 0xC2, 0xDE, 0x4E, 0xA3, 0x6D, 0x7A, 0xF7, 0x6A, 0x3D, 0x45, 0xB5}; +static const unsigned char SECONDARY_CLASS_SPOKENWORD[16] = {0x13, 0x2A, 0x17, 0x3A, + 0xD9, 0x2B, 0x31, 0x48, 0x83, 0x5B, 0x11, 0x4F, 0x6A, 0x95, 0x94, 0x3F}; +static const unsigned char SECONDARY_CLASS_NEWS[16] = {0x9B, 0xDB, 0x77, 0x66, 0xA0, + 0xE5, 0x63, 0x40, 0xA1, 0xAD, 0xAC, 0xEB, 0x52, 0x84, 0x0C, 0xF1}; +static const unsigned char SECONDARY_CLASS_TALKSHOW[16] = {0x67, 0x4A, 0x82, 0x1B, + 0x80, 0x3F, 0x3E, 0x4E, 0x9C, 0xDE, 0xF7, 0x36, 0x1B, 0x0F, 0x5F, 0x1B}; +static const unsigned char SECONDARY_CLASS_GAMES_CLIP[16] = {0x68, 0x33, 0x03, 0x00, + 0x09, 0x50, 0xC3, 0x4A, 0xA8, 0x20, 0x5D, 0x2D, 0x09, 0xA4, 0xE7, 0xC1}; +static const unsigned char SECONDARY_CLASS_GAMES_SONG[16] = {0x31, 0xF7, 0x4F, 0xF2, + 0xFC, 0x96, 0x0F, 0x4D, 0xA2, 0xF5, 0x5A, 0x34, 0x83, 0x68, 0x2B, 0x1A}; +*/ + +#pragma pack(push) /* must be byte-aligned */ +#pragma pack(1) +struct ID3v2Header { + char magic[3]; + unsigned char version; + unsigned char revision; + unsigned char flags; + uint32_t size; +}; + +struct ID3v2FrameHeader { + char key[4]; + uint32_t size; + uint16_t flags; +}; +#pragma pack(pop) + +struct GenericFrame : public Index<char> { + String key; +}; + +typedef Index<GenericFrame> FrameList; +typedef SimpleHash<String, FrameList> FrameDict; + +#define ID3_HEADER_SYNCSAFE 0x80 +#define ID3_HEADER_HAS_EXTENDED_HEADER 0x40 +#define ID3_HEADER_HAS_FOOTER 0x10 + +#define ID3_FRAME_HAS_GROUP 0x0040 +#define ID3_FRAME_COMPRESSED 0x0008 +#define ID3_FRAME_ENCRYPTED 0x0004 +#define ID3_FRAME_SYNCSAFE 0x0002 +#define ID3_FRAME_HAS_LENGTH 0x0001 + +namespace audtag { + +static bool skip_extended_header_3 (VFSFile & handle, int * _size) +{ + uint32_t size; + + if (handle.fread (& size, 1, 4) != 4) + return false; + + size = FROM_BE32 (size); + + AUDDBG ("Found v2.3 extended header, size = %d.\n", (int) size); + + if (handle.fseek (size, VFS_SEEK_CUR)) + return false; + + * _size = 4 + size; + return true; +} + +static bool skip_extended_header_4 (VFSFile & handle, int * _size) +{ + uint32_t size; + + if (handle.fread (& size, 1, 4) != 4) + return false; + + size = unsyncsafe32 (FROM_BE32 (size)); + + AUDDBG ("Found v2.4 extended header, size = %d.\n", (int) size); + + if (handle.fseek (size - 4, VFS_SEEK_CUR)) + return false; + + * _size = size; + return true; +} + +static bool validate_header (ID3v2Header * header, bool is_footer) +{ + if (memcmp (header->magic, is_footer ? "3DI" : "ID3", 3)) + return false; + + if ((header->version != 3 && header->version != 4) || header->revision != 0) + return false; + + header->size = unsyncsafe32 (FROM_BE32 (header->size)); + + AUDDBG ("Found ID3v2 %s:\n", is_footer ? "footer" : "header"); + AUDDBG (" magic = %.3s\n", header->magic); + AUDDBG (" version = %d\n", (int) header->version); + AUDDBG (" revision = %d\n", (int) header->revision); + AUDDBG (" flags = %x\n", (int) header->flags); + AUDDBG (" size = %d\n", (int) header->size); + return true; +} + +static bool read_header (VFSFile & handle, int * version, bool * + syncsafe, int64_t * offset, int * header_size, int * data_size, int * + footer_size) +{ + ID3v2Header header, footer; + + if (handle.fseek (0, VFS_SEEK_SET)) + return false; + + if (handle.fread (& header, 1, sizeof (ID3v2Header)) != sizeof + (ID3v2Header)) + return false; + + if (validate_header (& header, false)) + { + * offset = 0; + * version = header.version; + * header_size = sizeof (ID3v2Header); + * data_size = header.size; + + if (header.flags & ID3_HEADER_HAS_FOOTER) + { + if (handle.fseek (header.size, VFS_SEEK_CUR)) + return false; + + if (handle.fread (& footer, 1, sizeof (ID3v2Header)) != sizeof + (ID3v2Header)) + return false; + + if (! validate_header (& footer, true)) + return false; + + if (handle.fseek (sizeof (ID3v2Header), VFS_SEEK_SET)) + return false; + + * footer_size = sizeof (ID3v2Header); + } + else + * footer_size = 0; + } + else + { + int64_t end = handle.fsize (); + + if (end < 0) + return false; + + if (handle.fseek (end - sizeof (ID3v2Header), VFS_SEEK_SET)) + return false; + + if (handle.fread (& footer, 1, sizeof (ID3v2Header)) != sizeof + (ID3v2Header)) + return false; + + if (! validate_header (& footer, true)) + return false; + + * offset = end - 2 * sizeof (ID3v2Header) - footer.size; + * version = footer.version; + * header_size = sizeof (ID3v2Header); + * data_size = footer.size; + * footer_size = sizeof (ID3v2Header); + + if (handle.fseek (* offset, VFS_SEEK_SET)) + return false; + + if (handle.fread (& header, 1, sizeof (ID3v2Header)) != sizeof + (ID3v2Header)) + return false; + + if (! validate_header (& header, false)) + return false; + } + + // this flag indicates tag-level unsynchronisation in ID3v2.3 + // ID3v2.4 uses frame-level unsynchronisation, rendering this flag meaningless + * syncsafe = (* version == 3) && (header.flags & ID3_HEADER_SYNCSAFE); + + if (header.flags & ID3_HEADER_HAS_EXTENDED_HEADER) + { + int extended_size = 0; + + if (header.version == 3) + { + if (! skip_extended_header_3 (handle, & extended_size)) + return false; + } + else if (header.version == 4) + { + if (! skip_extended_header_4 (handle, & extended_size)) + return false; + } + + if (extended_size > * data_size) + return false; + + * header_size += extended_size; + * data_size -= extended_size; + } + + AUDDBG ("Offset = %d, header size = %d, data size = %d, footer size = " + "%d.\n", (int) * offset, * header_size, * data_size, * footer_size); + + return true; +} + +static void unsyncsafe (Index<char> & data) +{ + const char * get = data.begin (), * end = data.end (); + char * set = data.begin (); + const char * c; + + while ((c = (const char *) memchr (get, 0xff, end - get))) + { + c ++; + memmove (set, get, c - get); + set += c - get; + get = c; + + if (get < end && ! get[0]) + get ++; + } + + memmove (set, get, end - get); + set += end - get; + + data.remove (set - data.begin (), -1); +} + +static Index<char> read_tag_data (VFSFile & handle, int size, bool syncsafe) +{ + Index<char> data; + data.resize (size); + data.resize (handle.fread (data.begin (), 1, size)); + + if (syncsafe) + unsyncsafe (data); + + return data; +} + +static bool read_frame (const char * data, int max_size, int version, + int * frame_size, GenericFrame & frame) +{ + ID3v2FrameHeader header; + unsigned skip = 0; + + if ((max_size -= sizeof (ID3v2FrameHeader)) < 0) + return false; + + memcpy (& header, data, sizeof (ID3v2FrameHeader)); + data += sizeof (ID3v2FrameHeader); + + if (! header.key[0]) /* padding */ + return false; + + header.size = (version == 3) ? FROM_BE32 (header.size) : unsyncsafe32 (FROM_BE32 (header.size)); + header.flags = FROM_BE16 (header.flags); + + if (header.size > (unsigned) max_size || header.size == 0) + return false; + + AUDDBG ("Found frame:\n"); + AUDDBG (" key = %.4s\n", header.key); + AUDDBG (" size = %d\n", (int) header.size); + AUDDBG (" flags = %x\n", (int) header.flags); + + if (header.flags & (ID3_FRAME_COMPRESSED | ID3_FRAME_ENCRYPTED)) + { + AUDDBG ("Hit compressed/encrypted frame %.4s.\n", header.key); + return false; + } + + if (header.flags & ID3_FRAME_HAS_GROUP) + skip ++; + if (header.flags & ID3_FRAME_HAS_LENGTH) + skip += 4; + + if (skip >= header.size) + return false; + + * frame_size = sizeof (ID3v2FrameHeader) + header.size; + + frame.key = String (str_copy (header.key, 4)); + frame.clear (); + frame.insert (data + skip, 0, header.size - skip); + + if (header.flags & ID3_FRAME_SYNCSAFE) + unsyncsafe (frame); + + AUDDBG ("Data size = %d.\n", frame.len ()); + return true; +} + +static void read_all_frames (const Index<char> & data, int version, FrameDict & dict) +{ + for (const char * pos = data.begin (); pos < data.end (); ) + { + int frame_size; + GenericFrame frame; + + if (! read_frame (pos, data.end () - pos, version, & frame_size, frame)) + break; + + pos += frame_size; + + FrameList * list = dict.lookup (frame.key); + if (! list) + list = dict.add (frame.key, FrameList ()); + + list->append (std::move (frame)); + } +} + +static bool write_frame (int fd, const GenericFrame & frame, int version, int * frame_size) +{ + AUDDBG ("Writing frame %s, size %d\n", (const char *) frame.key, frame.len ()); + + ID3v2FrameHeader header; + + strncpy (header.key, frame.key, 4); + + uint32_t size = frame.len (); + if (version > 3) + size = syncsafe32 (size); + + header.size = TO_BE32 (size); + header.flags = 0; + + if (write (fd, & header, sizeof (ID3v2FrameHeader)) != sizeof (ID3v2FrameHeader)) + return false; + + if (write (fd, & frame[0], frame.len ()) != frame.len ()) + return false; + + * frame_size = sizeof (ID3v2FrameHeader) + frame.len (); + return true; +} + +struct WriteState { + int fd; + int version; + int written_size; +}; + +static void write_frame_list (const String & key, FrameList & list, void * user) +{ + WriteState * state = (WriteState *) user; + + for (const GenericFrame & frame : list) + { + int size; + if (write_frame (state->fd, frame, state->version, & size)) + state->written_size += size; + } +} + +static int write_all_frames (int fd, FrameDict & dict, int version) +{ + WriteState state = {fd, version, 0}; + dict.iterate (write_frame_list, & state); + + AUDDBG ("Total frame bytes written = %d.\n", state.written_size); + return state.written_size; +} + +static bool write_header (int fd, int version, int size) +{ + ID3v2Header header; + + memcpy (header.magic, "ID3", 3); + header.version = version; + header.revision = 0; + header.flags = 0; + header.size = TO_BE32 (syncsafe32 (size)); + + return write (fd, & header, sizeof (ID3v2Header)) == sizeof (ID3v2Header); +} + +static int get_frame_id (const char * key) +{ + int id; + + for (id = 0; id < ID3_TAGS_NO; id ++) + { + if (! strcmp (key, id3_frames[id])) + return id; + } + + return -1; +} + +#if 0 +static void decode_private_info (Tuple & tuple, const unsigned char * data, int size) +{ + char * text = g_strndup ((const char *) data, size); + + if (!strncmp(text, "WM/", 3)) + { + char *separator = strchr(text, 0); + if (separator == nullptr) + goto DONE; + + char * value = separator + 1; + if (!strncmp(text, "WM/MediaClassPrimaryID", 22)) + { + if (!memcmp(value, PRIMARY_CLASS_MUSIC, 16)) + tuple.set_str (-1, "media-class", "Music"); + if (!memcmp(value, PRIMARY_CLASS_AUDIO, 16)) + tuple.set_str (-1, "media-class", "Audio (non-music)"); + } else if (!strncmp(text, "WM/MediaClassSecondaryID", 24)) + { + if (!memcmp(value, SECONDARY_CLASS_AUDIOBOOK, 16)) + tuple.set_str (-1, "media-class", "Audio Book"); + if (!memcmp(value, SECONDARY_CLASS_SPOKENWORD, 16)) + tuple.set_str (-1, "media-class", "Spoken Word"); + if (!memcmp(value, SECONDARY_CLASS_NEWS, 16)) + tuple.set_str (-1, "media-class", "News"); + if (!memcmp(value, SECONDARY_CLASS_TALKSHOW, 16)) + tuple.set_str (-1, "media-class", "Talk Show"); + if (!memcmp(value, SECONDARY_CLASS_GAMES_CLIP, 16)) + tuple.set_str (-1, "media-class", "Game Audio (clip)"); + if (!memcmp(value, SECONDARY_CLASS_GAMES_SONG, 16)) + tuple.set_str (-1, "media-class", "Game Soundtrack"); + } else { + AUDDBG("Unrecognised tag %s (Windows Media) ignored\n", text); + } + } else { + AUDDBG("Unable to decode private data, skipping: %s\n", text); + } + +DONE: + g_free (text); +} +#endif + +static GenericFrame & add_generic_frame (int id, int size, FrameDict & dict) +{ + String key (id3_frames[id]); + FrameList * list = dict.add (key, FrameList ()); + + GenericFrame & frame = list->append (); + + frame.key = key; + frame.insert (0, size); + + return frame; +} + +static void remove_frame (int id, FrameDict & dict) +{ + AUDDBG ("Deleting frame %s.\n", id3_frames[id]); + dict.remove (String (id3_frames[id])); +} + +static void add_text_frame (int id, const char * text, FrameDict & dict) +{ + if (! text) + { + remove_frame (id, dict); + return; + } + + AUDDBG ("Adding text frame %s = %s.\n", id3_frames[id], text); + + long words; + uint16_t * utf16 = g_utf8_to_utf16 (text, -1, nullptr, & words, nullptr); + g_return_if_fail (utf16); + + GenericFrame & frame = add_generic_frame (id, 3 + 2 * words, dict); + + frame[0] = 1; /* UTF-16 encoding */ + * (uint16_t *) (& frame[1]) = 0xfeff; /* byte order mark */ + memcpy (& frame[3], utf16, 2 * words); + + g_free (utf16); +} + +static void add_comment_frame (const char * text, FrameDict & dict) +{ + if (! text) + { + remove_frame (ID3_COMMENT, dict); + return; + } + + AUDDBG ("Adding comment frame = %s.\n", text); + + long words; + uint16_t * utf16 = g_utf8_to_utf16 (text, -1, nullptr, & words, nullptr); + g_return_if_fail (utf16); + + GenericFrame & frame = add_generic_frame (ID3_COMMENT, 10 + 2 * words, dict); + + frame[0] = 1; /* UTF-16 encoding */ + memcpy (& frame[1], "eng", 3); /* language */ + * (uint16_t *) (& frame[4]) = 0xfeff; /* byte order mark */ + * (uint16_t *) (& frame[6]) = 0; /* end of content description */ + * (uint16_t *) (& frame[8]) = 0xfeff; /* byte order mark */ + memcpy (& frame[10], utf16, 2 * words); + + g_free (utf16); +} + +static void add_frameFromTupleStr (const Tuple & tuple, Tuple::Field field, + int id3_field, FrameDict & dict) +{ + add_text_frame (id3_field, tuple.get_str (field), dict); +} + +static void add_frameFromTupleInt (const Tuple & tuple, Tuple::Field field, + int id3_field, FrameDict & dict) +{ + if (tuple.get_value_type (field) != Tuple::Int) + { + remove_frame (id3_field, dict); + return; + } + + add_text_frame (id3_field, int_to_str (tuple.get_int (field)), dict); +} + +bool ID3v24TagModule::can_handle_file (VFSFile & handle) +{ + int version, header_size, data_size, footer_size; + bool syncsafe; + int64_t offset; + + return read_header (handle, & version, & syncsafe, & offset, & header_size, + & data_size, & footer_size); +} + +bool ID3v24TagModule::read_tag (Tuple & tuple, VFSFile & handle) +{ + int version, header_size, data_size, footer_size; + bool syncsafe; + int64_t offset; + + if (! read_header (handle, & version, & syncsafe, & offset, & header_size, + & data_size, & footer_size)) + return false; + + Index<char> data = read_tag_data (handle, data_size, syncsafe); + + for (const char * pos = data.begin (); pos < data.end (); ) + { + int frame_size; + GenericFrame frame; + + if (! read_frame (pos, data.end () - pos, version, & frame_size, frame)) + break; + + switch (get_frame_id (frame.key)) + { + case ID3_ALBUM: + id3_associate_string (tuple, Tuple::Album, & frame[0], frame.len ()); + break; + case ID3_TITLE: + id3_associate_string (tuple, Tuple::Title, & frame[0], frame.len ()); + break; + case ID3_COMPOSER: + id3_associate_string (tuple, Tuple::Composer, & frame[0], frame.len ()); + break; + case ID3_COPYRIGHT: + id3_associate_string (tuple, Tuple::Copyright, & frame[0], frame.len ()); + break; + case ID3_DATE: + id3_associate_string (tuple, Tuple::Date, & frame[0], frame.len ()); + break; + case ID3_LENGTH: + id3_associate_length (tuple, & frame[0], frame.len ()); + break; + case ID3_ARTIST: + id3_associate_string (tuple, Tuple::Artist, & frame[0], frame.len ()); + break; + case ID3_ALBUM_ARTIST: + id3_associate_string (tuple, Tuple::AlbumArtist, & frame[0], frame.len ()); + break; + case ID3_TRACKNR: + id3_associate_int (tuple, Tuple::Track, & frame[0], frame.len ()); + break; + case ID3_YEAR: + case ID3_RECORDING_TIME: + id3_associate_int (tuple, Tuple::Year, & frame[0], frame.len ()); + break; + case ID3_GENRE: + id3_decode_genre (tuple, & frame[0], frame.len ()); + break; + case ID3_COMMENT: + id3_decode_comment (tuple, & frame[0], frame.len ()); + break; +#if 0 + case ID3_PRIVATE: + decode_private_info (tuple, & frame[0], frame.len ()); + break; +#endif + case ID3_RVA2: + id3_decode_rva (tuple, & frame[0], frame.len ()); + break; + default: + AUDDBG ("Ignoring unsupported ID3 frame %s.\n", (const char *) frame.key); + break; + } + + pos += frame_size; + } + + return true; +} + +Index<char> ID3v24TagModule::read_image (VFSFile & handle) +{ + Index<char> buf; + int version, header_size, data_size, footer_size; + bool syncsafe; + int64_t offset; + + if (! read_header (handle, & version, & syncsafe, & offset, & header_size, + & data_size, & footer_size)) + return buf; + + Index<char> data = read_tag_data (handle, data_size, syncsafe); + + for (const char * pos = data.begin (); pos < data.end (); ) + { + int frame_size; + GenericFrame frame; + + if (! read_frame (pos, data.end () - pos, version, & frame_size, frame)) + break; + + if (! strcmp (frame.key, "APIC")) + buf = id3_decode_picture (& frame[0], frame.len ()); + + pos += frame_size; + } + + return buf; +} + +bool ID3v24TagModule::write_tag (const Tuple & tuple, VFSFile & f) +{ + int version = 3; + int header_size, data_size, footer_size; + bool syncsafe; + int64_t offset; + + //read all frames into generic frames; + FrameDict dict; + + if (read_header (f, & version, & syncsafe, & offset, & header_size, & data_size, & footer_size)) + read_all_frames (read_tag_data (f, data_size, syncsafe), version, dict); + + //make the new frames from tuple and replace in the dictionary the old frames with the new ones + add_frameFromTupleStr (tuple, Tuple::Title, ID3_TITLE, dict); + add_frameFromTupleStr (tuple, Tuple::Artist, ID3_ARTIST, dict); + add_frameFromTupleStr (tuple, Tuple::Album, ID3_ALBUM, dict); + add_frameFromTupleStr (tuple, Tuple::AlbumArtist, ID3_ALBUM_ARTIST, dict); + add_frameFromTupleInt (tuple, Tuple::Year, ID3_YEAR, dict); + add_frameFromTupleInt (tuple, Tuple::Track, ID3_TRACKNR, dict); + add_frameFromTupleStr (tuple, Tuple::Genre, ID3_GENRE, dict); + + String comment = tuple.get_str (Tuple::Comment); + add_comment_frame (comment, dict); + + /* location and size of non-tag data */ + int64_t mp3_offset = offset ? 0 : header_size + data_size + footer_size; + int64_t mp3_size = offset ? offset : -1; + + TempFile temp = TempFile (); + if (! open_temp_file_for (& temp, f)) + return false; + + /* write empty header (will be overwritten later) */ + if (! write_header (temp.fd, version, 0)) + return false; + + /* write tag data */ + data_size = write_all_frames (temp.fd, dict, version); + + /* copy non-tag data */ + if (! copy_region_to_temp_file (& temp, f, mp3_offset, mp3_size)) + return false; + + /* go back to beginning and write real header */ + if (lseek (temp.fd, 0, SEEK_SET) < 0 || ! write_header (temp.fd, version, data_size)) + return false; + + if (! replace_with_temp_file (& temp, f)) + return false; + + return true; +} + +} diff --git a/src/libaudtag/id3/id3v24.h b/src/libaudtag/id3/id3v24.h deleted file mode 100644 index 232c197..0000000 --- a/src/libaudtag/id3/id3v24.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * id3v24.h - * Copyright 2010 John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#ifndef AUDTAG_ID3V2_H -#define AUDTAG_ID3V2_H - -#include "../audtag.h" -#include "../tag_module.h" -#include "../util.h" - -extern tag_module_t id3v24; - -#endif diff --git a/src/libaudtag/tag_module.c b/src/libaudtag/tag_module.c deleted file mode 100644 index 560c5b8..0000000 --- a/src/libaudtag/tag_module.c +++ /dev/null @@ -1,67 +0,0 @@ -/* - * tag_module.c - * Copyright 2009-2011 Paula Stanciu and John Lindgren - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions, and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions, and the following disclaimer in the documentation - * provided with the distribution. - * - * This software is provided "as is" and without any warranty, express or - * implied. In no event shall the authors be liable for any damages arising from - * the use of this software. - */ - -#include <glib.h> -#include <stdio.h> - -#include <libaudcore/tuple.h> -#include <libaudcore/vfs.h> - -#include "audtag.h" -#include "util.h" -#include "tag_module.h" -#include "id3/id3v1.h" -#include "id3/id3v22.h" -#include "id3/id3v24.h" -#include "ape/ape.h" - -static tag_module_t * const modules[] = {& id3v24, & id3v22, & ape, & id3v1}; - -tag_module_t * find_tag_module (VFSFile * fd, int new_type) -{ - int i; - - for (i = 0; i < ARRAY_LEN (modules); i ++) - { - if (vfs_fseek(fd, 0, SEEK_SET)) - { - TAGDBG("not a seekable file\n"); - return NULL; - } - - if (modules[i]->can_handle_file (fd)) - { - TAGDBG ("Module %s accepted file.\n", modules[i]->name); - return modules[i]; - } - } - - /* No existing tag; see if we can create a new one. */ - if (new_type != TAG_TYPE_NONE) - { - for (i = 0; i < ARRAY_LEN (modules); i ++) - { - if (modules[i]->type == new_type) - return modules[i]; - } - } - - TAGDBG("no module found\n"); - return NULL; -} diff --git a/src/libaudtag/tag_module.cc b/src/libaudtag/tag_module.cc new file mode 100644 index 0000000..fd27ac3 --- /dev/null +++ b/src/libaudtag/tag_module.cc @@ -0,0 +1,98 @@ +/* + * tag_module.c + * Copyright 2009-2011 Paula Stanciu and John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include <libaudcore/index.h> +#include <libaudcore/runtime.h> +#include <libaudcore/tuple.h> +#include <libaudcore/vfs.h> + +#include "audtag.h" +#include "util.h" +#include "tag_module.h" +#include "builtin.h" + +namespace audtag { + +static APETagModule ape; +static ID3v1TagModule id3v1; +static ID3v22TagModule id3v22; +static ID3v24TagModule id3v24; + +static TagModule * const modules[] = {& id3v24, & id3v22, & ape, & id3v1}; + +TagModule * find_tag_module (VFSFile & fd, TagType new_type) +{ + for (TagModule * module : modules) + { + if (fd.fseek (0, VFS_SEEK_SET)) + { + AUDDBG("not a seekable file\n"); + return nullptr; + } + + if (module->can_handle_file (fd)) + { + AUDDBG ("Module %s accepted file.\n", module->m_name); + return module; + } + } + + /* No existing tag; see if we can create a new one. */ + if (new_type != TagType::None) + { + for (TagModule * module : modules) + { + if (module->m_type == new_type) + return module; + } + } + + AUDDBG("no module found\n"); + return nullptr; +} + +/************************************************************************************************************** + * tag module object management * + **************************************************************************************************************/ +bool TagModule::can_handle_file (VFSFile & handle) +{ + AUDDBG("Module %s does not support %s (no probing function implemented).\n", m_name, + handle.filename ()); + return false; +} + +Index<char> TagModule::read_image (VFSFile & handle) +{ + AUDDBG("Module %s does not support images.\n", m_name); + return Index<char> (); +} + +bool TagModule::read_tag (Tuple & tuple, VFSFile & handle) +{ + AUDDBG ("%s: read_tag() not implemented.\n", m_name); + return false; +} + +bool TagModule::write_tag (Tuple const & tuple, VFSFile & handle) +{ + AUDDBG ("%s: write_tag() not implemented.\n", m_name); + return false; +} + +} diff --git a/src/libaudtag/tag_module.h b/src/libaudtag/tag_module.h index a520c13..71360f7 100644 --- a/src/libaudtag/tag_module.h +++ b/src/libaudtag/tag_module.h @@ -23,17 +23,23 @@ #include "libaudcore/tuple.h" #include "libaudcore/vfs.h" -typedef Tuple* pTuple; - -typedef struct _module { - char *name; - int type; /* set to TAG_TYPE_NONE if the module cannot create new tags */ - bool_t(*can_handle_file) (VFSFile *fd); - bool_t (* read_tag) (Tuple * tuple, VFSFile * handle); - bool_t (* read_image) (VFSFile * handle, void * * data, int64_t * size); - bool_t (* write_tag) (const Tuple * tuple, VFSFile * handle); -} tag_module_t; - -tag_module_t * find_tag_module (VFSFile * handle, int new_type); +namespace audtag { + +struct TagModule { + const char *m_name; + TagType m_type; /* set to None if the module cannot create new tags */ + + virtual bool can_handle_file (VFSFile &fd); + virtual bool read_tag (Tuple & tuple, VFSFile & handle); + virtual Index<char> read_image (VFSFile & handle); + virtual bool write_tag (const Tuple & tuple, VFSFile & handle); + +protected: + TagModule(const char *name, TagType type) : m_name(name), m_type(type) { }; +}; + +TagModule * find_tag_module (VFSFile & handle, TagType new_type); + +} #endif /* TAG_MODULE_H */ diff --git a/src/libaudtag/util.c b/src/libaudtag/util.cc index 5abd24b..8c7f5f8 100644 --- a/src/libaudtag/util.c +++ b/src/libaudtag/util.cc @@ -17,10 +17,8 @@ * the use of this software. */ -#include <stdio.h> #include <unistd.h> -#include <glib.h> #include <glib/gstdio.h> #include <libaudcore/audstrings.h> @@ -29,7 +27,7 @@ const char *convert_numericgenre_to_text(int numericgenre) { - const struct + static const struct { int numericgenre; const char *genre; @@ -163,15 +161,13 @@ const char *convert_numericgenre_to_text(int numericgenre) {GENRE_EURO_HOUSE, "Euro-House"}, }; - int count; - - for (count = 0; count < ARRAY_LEN (table); count++) + for (auto & pair : table) { - if (table[count].numericgenre == numericgenre) - return table[count].genre; + if (pair.numericgenre == numericgenre) + return pair.genre; } - return "Unknown"; + return nullptr; } uint32_t unsyncsafe32 (uint32_t x) @@ -186,25 +182,23 @@ uint32_t syncsafe32 (uint32_t x) 0xfe00000) << 3); } -bool_t open_temp_file_for (TempFile * temp, VFSFile * file) +bool open_temp_file_for (TempFile * temp, VFSFile & file) { - char * template = filename_build (g_get_tmp_dir (), "audacious-temp-XXXXXX"); - SCOPY (tempname, template); - str_unref (template); + StringBuf tempname = filename_build ({g_get_tmp_dir (), "audacious-temp-XXXXXX"}); temp->fd = g_mkstemp (tempname); if (temp->fd < 0) - return FALSE; + return false; - temp->name = str_get (tempname); + temp->name = String (tempname); - return TRUE; + return true; } -bool_t copy_region_to_temp_file (TempFile * temp, VFSFile * file, int64_t offset, int64_t size) +bool copy_region_to_temp_file (TempFile * temp, VFSFile & file, int64_t offset, int64_t size) { - if (vfs_fseek (file, offset, SEEK_SET) < 0) - return FALSE; + if (file.fseek (offset, VFS_SEEK_SET) < 0) + return false; char buf[16384]; @@ -214,16 +208,16 @@ bool_t copy_region_to_temp_file (TempFile * temp, VFSFile * file, int64_t offset if (size > 0) { - readsize = MIN (size, sizeof buf); - if (vfs_fread (buf, 1, readsize, file) != readsize) - return FALSE; + readsize = aud::min (size, (int64_t) sizeof buf); + if (file.fread (buf, 1, readsize) != readsize) + return false; size -= readsize; } else { /* negative size means copy to EOF */ - readsize = vfs_fread (buf, 1, sizeof buf, file); + readsize = file.fread (buf, 1, sizeof buf); if (! readsize) break; } @@ -233,25 +227,25 @@ bool_t copy_region_to_temp_file (TempFile * temp, VFSFile * file, int64_t offset { int64_t writesize = write (temp->fd, buf + written, readsize - written); if (writesize <= 0) - return FALSE; + return false; written += writesize; } } - return TRUE; + return true; } -bool_t replace_with_temp_file (TempFile * temp, VFSFile * file) +bool replace_with_temp_file (TempFile * temp, VFSFile & file) { if (lseek (temp->fd, 0, SEEK_SET) < 0) - return FALSE; + return false; - if (vfs_fseek (file, 0, SEEK_SET) < 0) - return FALSE; + if (file.fseek (0, VFS_SEEK_SET) < 0) + return false; - if (vfs_ftruncate (file, 0) < 0) - return FALSE; + if (file.ftruncate (0) < 0) + return false; char buf[16384]; @@ -259,17 +253,17 @@ bool_t replace_with_temp_file (TempFile * temp, VFSFile * file) { int64_t readsize = read (temp->fd, buf, sizeof buf); if (readsize < 0) - return FALSE; + return false; if (readsize == 0) break; - if (vfs_fwrite (buf, 1, readsize, file) != readsize) - return FALSE; + if (file.fwrite (buf, 1, readsize) != readsize) + return false; } close (temp->fd); g_unlink (temp->name); - return TRUE; + return true; } diff --git a/src/libaudtag/util.h b/src/libaudtag/util.h index 3b80ad7..711f56e 100644 --- a/src/libaudtag/util.h +++ b/src/libaudtag/util.h @@ -21,9 +21,8 @@ #define TAGUTIL_H #include <stdint.h> -#include <stdio.h> -#include "libaudcore/vfs.h" +#include <libaudcore/vfs.h> enum { GENRE_BLUES = 0, @@ -153,22 +152,18 @@ enum { GENRE_EURO_HOUSE }; -extern bool_t tag_verbose; - -#define TAGDBG(...) do {if (tag_verbose) {printf ("%s:%d [%s]: ", __FILE__, __LINE__, __FUNCTION__); printf (__VA_ARGS__);}} while (0) - const char *convert_numericgenre_to_text(int numericgenre); uint32_t unsyncsafe32 (uint32_t x); uint32_t syncsafe32 (uint32_t x); -typedef struct { - char * name; +struct TempFile { + String name; int fd; -} TempFile; +}; -bool_t open_temp_file_for (TempFile * temp, VFSFile * file); -bool_t copy_region_to_temp_file (TempFile * temp, VFSFile * file, int64_t offset, int64_t size); -bool_t replace_with_temp_file (TempFile * temp, VFSFile * file); +bool open_temp_file_for (TempFile * temp, VFSFile & file); +bool copy_region_to_temp_file (TempFile * temp, VFSFile & file, int64_t offset, int64_t size); +bool replace_with_temp_file (TempFile * temp, VFSFile & file); #endif /* TAGUTIL_H */ |