summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMateusz Łukasik <mati75@linuxmint.pl>2015-06-05 16:46:36 +0200
committerMateusz Łukasik <mati75@linuxmint.pl>2015-06-05 16:46:36 +0200
commitb541fedc97ad4ed5e658ce34ee50c74ad756f330 (patch)
tree5da244a1063a2529a5419083b2ea23f8fa76cb63 /src
parent5f4fd4397e8b75ad152aec919f59ecb039ac6105 (diff)
Imported Upstream version 3.6.2
Diffstat (limited to 'src')
-rw-r--r--src/Makefile22
-rw-r--r--src/audacious/Makefile98
-rw-r--r--src/audacious/adder.c573
-rw-r--r--src/audacious/api-alias-begin.h46
-rw-r--r--src/audacious/api-alias-end.h44
-rw-r--r--src/audacious/api-declare-begin.h46
-rw-r--r--src/audacious/api-define-begin.h46
-rw-r--r--src/audacious/api-define-end.h46
-rw-r--r--src/audacious/api-local-begin.h44
-rw-r--r--src/audacious/api-local-end.h44
-rw-r--r--src/audacious/api.h55
-rw-r--r--src/audacious/art.c284
-rw-r--r--src/audacious/audacious.rc2
-rw-r--r--src/audacious/chardet.c52
-rw-r--r--src/audacious/config.c453
-rw-r--r--src/audacious/dbus-server.c779
-rw-r--r--src/audacious/dbus-server.cc815
-rw-r--r--src/audacious/drct-api.h78
-rw-r--r--src/audacious/drct.c195
-rw-r--r--src/audacious/drct.h69
-rw-r--r--src/audacious/effect.c260
-rw-r--r--src/audacious/effect.h36
-rw-r--r--src/audacious/equalizer.h28
-rw-r--r--src/audacious/equalizer_preset.c242
-rw-r--r--src/audacious/fft.h25
-rw-r--r--src/audacious/general.c160
-rw-r--r--src/audacious/history.c116
-rw-r--r--src/audacious/images/about-logo.pngbin20019 -> 0 bytes
-rw-r--r--src/audacious/images/album.pngbin5418 -> 0 bytes
-rw-r--r--src/audacious/images/appearance.pngbin2721 -> 0 bytes
-rw-r--r--src/audacious/images/audio.pngbin3224 -> 0 bytes
-rw-r--r--src/audacious/images/connectivity.pngbin4452 -> 0 bytes
-rw-r--r--src/audacious/images/info.pngbin3194 -> 0 bytes
-rw-r--r--src/audacious/images/playlist.pngbin1995 -> 0 bytes
-rw-r--r--src/audacious/images/plugins.pngbin4241 -> 0 bytes
-rw-r--r--src/audacious/input-api.h60
-rw-r--r--src/audacious/input.h69
-rw-r--r--src/audacious/interface.c216
-rw-r--r--src/audacious/interface.h36
-rw-r--r--src/audacious/main.c634
-rw-r--r--src/audacious/main.cc384
-rw-r--r--src/audacious/main.h38
-rw-r--r--src/audacious/misc-api.h128
-rw-r--r--src/audacious/misc.h109
-rw-r--r--src/audacious/output.c642
-rw-r--r--src/audacious/output.h47
-rw-r--r--src/audacious/playback.c652
-rw-r--r--src/audacious/playback.h30
-rw-r--r--src/audacious/playlist-files.c194
-rw-r--r--src/audacious/playlist-new.c2401
-rw-r--r--src/audacious/playlist-utils.c507
-rw-r--r--src/audacious/playlist.h147
-rw-r--r--src/audacious/plugin-init.c333
-rw-r--r--src/audacious/plugin-preferences.c132
-rw-r--r--src/audacious/plugin-registry.c871
-rw-r--r--src/audacious/plugin.h373
-rw-r--r--src/audacious/pluginenum.c210
-rw-r--r--src/audacious/plugins-api.h60
-rw-r--r--src/audacious/plugins.h107
-rw-r--r--src/audacious/preferences.c641
-rw-r--r--src/audacious/preferences.h142
-rw-r--r--src/audacious/probe-buffer.c161
-rw-r--r--src/audacious/probe-buffer.h27
-rw-r--r--src/audacious/probe.c287
-rw-r--r--src/audacious/scanner.c175
-rw-r--r--src/audacious/signals.cc (renamed from src/audacious/signals.c)11
-rw-r--r--src/audacious/types.h67
-rw-r--r--src/audacious/ui_albumart.c188
-rw-r--r--src/audacious/ui_preferences.c814
-rw-r--r--src/audacious/ui_preferences.h38
-rw-r--r--src/audacious/util.c474
-rw-r--r--src/audacious/util.cc (renamed from src/audacious/debug.h)35
-rw-r--r--src/audacious/util.h25
-rw-r--r--src/audacious/vis_runner.h30
-rw-r--r--src/audacious/visualization.c261
-rw-r--r--src/audacious/visualization.h36
-rw-r--r--src/audtool/handlers_playback.c4
-rw-r--r--src/libaudcore/Makefile99
-rw-r--r--src/libaudcore/adder.cc475
-rw-r--r--src/libaudcore/art-search.cc155
-rw-r--r--src/libaudcore/art.cc271
-rw-r--r--src/libaudcore/audio.c242
-rw-r--r--src/libaudcore/audio.cc300
-rw-r--r--src/libaudcore/audio.h.in81
-rw-r--r--src/libaudcore/audstrings.c784
-rw-r--r--src/libaudcore/audstrings.cc974
-rw-r--r--src/libaudcore/audstrings.h127
-rw-r--r--src/libaudcore/charset.c180
-rw-r--r--src/libaudcore/charset.cc226
-rw-r--r--src/libaudcore/config.cc397
-rw-r--r--src/libaudcore/core.h83
-rw-r--r--src/libaudcore/drct.cc195
-rw-r--r--src/libaudcore/drct.h89
-rw-r--r--src/libaudcore/effect.cc268
-rw-r--r--src/libaudcore/equalizer-preset.cc202
-rw-r--r--src/libaudcore/equalizer.cc (renamed from src/audacious/equalizer.c)115
-rw-r--r--src/libaudcore/equalizer.h51
-rw-r--r--src/libaudcore/eventqueue.c122
-rw-r--r--src/libaudcore/eventqueue.cc110
-rw-r--r--src/libaudcore/fft.cc (renamed from src/audacious/fft.c)24
-rw-r--r--src/libaudcore/history.cc (renamed from src/audacious/general.h)36
-rw-r--r--src/libaudcore/hook.c141
-rw-r--r--src/libaudcore/hook.cc141
-rw-r--r--src/libaudcore/hook.h86
-rw-r--r--src/libaudcore/i18n.h (renamed from src/audacious/i18n.h)0
-rw-r--r--src/libaudcore/index.c213
-rw-r--r--src/libaudcore/index.cc196
-rw-r--r--src/libaudcore/index.h274
-rw-r--r--src/libaudcore/inifile.c134
-rw-r--r--src/libaudcore/inifile.cc123
-rw-r--r--src/libaudcore/inifile.h23
-rw-r--r--src/libaudcore/interface.cc256
-rw-r--r--src/libaudcore/interface.h53
-rw-r--r--src/libaudcore/internal.h130
-rw-r--r--src/libaudcore/list.cc77
-rw-r--r--src/libaudcore/list.h82
-rw-r--r--src/libaudcore/logger.cc125
-rw-r--r--src/libaudcore/mainloop.cc218
-rw-r--r--src/libaudcore/mainloop.h50
-rw-r--r--src/libaudcore/multihash.c153
-rw-r--r--src/libaudcore/multihash.cc173
-rw-r--r--src/libaudcore/multihash.h265
-rw-r--r--src/libaudcore/objects.h272
-rw-r--r--src/libaudcore/output.cc582
-rw-r--r--src/libaudcore/output.h44
-rw-r--r--src/libaudcore/playback.cc749
-rw-r--r--src/libaudcore/playlist-files.cc139
-rw-r--r--src/libaudcore/playlist-internal.h53
-rw-r--r--src/libaudcore/playlist-utils.cc442
-rw-r--r--src/libaudcore/playlist.cc2261
-rw-r--r--src/libaudcore/playlist.h (renamed from src/audacious/playlist-api.h)331
-rw-r--r--src/libaudcore/plugin-init.cc351
-rw-r--r--src/libaudcore/plugin-load.cc163
-rw-r--r--src/libaudcore/plugin-registry.cc705
-rw-r--r--src/libaudcore/plugin.h473
-rw-r--r--src/libaudcore/plugins-internal.h54
-rw-r--r--src/libaudcore/plugins.h68
-rw-r--r--src/libaudcore/preferences.cc123
-rw-r--r--src/libaudcore/preferences.h301
-rw-r--r--src/libaudcore/probe-buffer.cc146
-rw-r--r--src/libaudcore/probe.cc215
-rw-r--r--src/libaudcore/probe.h51
-rw-r--r--src/libaudcore/ringbuf.cc197
-rw-r--r--src/libaudcore/ringbuf.h181
-rw-r--r--src/libaudcore/runtime.cc358
-rw-r--r--src/libaudcore/runtime.h113
-rw-r--r--src/libaudcore/scanner.cc66
-rw-r--r--src/libaudcore/scanner.h (renamed from src/audacious/scanner.h)42
-rw-r--r--src/libaudcore/stringbuf.cc223
-rw-r--r--src/libaudcore/strpool.c256
-rw-r--r--src/libaudcore/strpool.cc266
-rw-r--r--src/libaudcore/templates.h279
-rw-r--r--src/libaudcore/tests/Makefile40
-rw-r--r--src/libaudcore/tests/test-mainloop.cc118
-rw-r--r--src/libaudcore/tests/test.cc305
-rw-r--r--src/libaudcore/tinylock.cc (renamed from src/libaudcore/tinylock.c)0
-rw-r--r--src/libaudcore/tuple-compiler.cc549
-rw-r--r--src/libaudcore/tuple-compiler.h (renamed from src/libaudcore/tuple_compiler.h)42
-rw-r--r--src/libaudcore/tuple.c496
-rw-r--r--src/libaudcore/tuple.cc780
-rw-r--r--src/libaudcore/tuple.h325
-rw-r--r--src/libaudcore/tuple_compiler.c708
-rw-r--r--src/libaudcore/tuple_formatter.c96
-rw-r--r--src/libaudcore/util.cc135
-rw-r--r--src/libaudcore/vfs.c483
-rw-r--r--src/libaudcore/vfs.cc340
-rw-r--r--src/libaudcore/vfs.h172
-rw-r--r--src/libaudcore/vfs_async.c73
-rw-r--r--src/libaudcore/vfs_async.cc91
-rw-r--r--src/libaudcore/vfs_async.h6
-rw-r--r--src/libaudcore/vfs_common.c198
-rw-r--r--src/libaudcore/vfs_local.c241
-rw-r--r--src/libaudcore/vfs_local.cc263
-rw-r--r--src/libaudcore/vfs_local.h2
-rw-r--r--src/libaudcore/vis-runner.cc (renamed from src/audacious/vis_runner.c)205
-rw-r--r--src/libaudcore/visualization.cc179
-rw-r--r--src/libaudcore/visualizer.h49
-rw-r--r--src/libaudgui/Makefile54
-rw-r--r--src/libaudgui/about.cc (renamed from src/libaudgui/about.c)64
-rw-r--r--src/libaudgui/confirm.cc (renamed from src/libaudgui/confirm.c)26
-rw-r--r--src/libaudgui/equalizer.cc (renamed from src/libaudgui/equalizer.c)93
-rw-r--r--src/libaudgui/file-opener.cc (renamed from src/libaudgui/ui_fileopener.c)84
-rw-r--r--src/libaudgui/infopopup.cc (renamed from src/libaudgui/infopopup.c)259
-rw-r--r--src/libaudgui/infowin.c485
-rw-r--r--src/libaudgui/infowin.cc519
-rw-r--r--src/libaudgui/init.cc (renamed from src/libaudgui/init.c)107
-rw-r--r--src/libaudgui/internal.h (renamed from src/libaudgui/init.h)34
-rw-r--r--src/libaudgui/jump-to-time.cc (renamed from src/libaudgui/jump-to-time.c)16
-rw-r--r--src/libaudgui/jump-to-track-cache.cc214
-rw-r--r--src/libaudgui/jump-to-track-cache.h (renamed from src/audacious/api-declare-end.h)48
-rw-r--r--src/libaudgui/jump-to-track.cc343
-rw-r--r--src/libaudgui/libaudgui-gtk.h27
-rw-r--r--src/libaudgui/libaudgui.h72
-rw-r--r--src/libaudgui/list.cc (renamed from src/libaudgui/list.c)305
-rw-r--r--src/libaudgui/list.h20
-rw-r--r--src/libaudgui/menu.cc (renamed from src/libaudgui/menu.c)31
-rw-r--r--src/libaudgui/menu.h39
-rw-r--r--src/libaudgui/pixbufs.cc (renamed from src/libaudgui/pixbufs.c)55
-rw-r--r--src/libaudgui/playlists.cc (renamed from src/libaudgui/playlists.c)63
-rw-r--r--src/libaudgui/plugin-menu.cc (renamed from src/audacious/ui_plugin_menu.c)57
-rw-r--r--src/libaudgui/plugin-prefs.cc191
-rw-r--r--src/libaudgui/plugin-view.cc (renamed from src/audacious/plugin-view.c)188
-rw-r--r--src/libaudgui/prefs-widget.cc570
-rw-r--r--src/libaudgui/prefs-window.cc814
-rw-r--r--src/libaudgui/queue-manager.cc (renamed from src/libaudgui/queue-manager.c)89
-rw-r--r--src/libaudgui/scaled-image.cc (renamed from src/libaudgui/scaled-image.c)28
-rw-r--r--src/libaudgui/status.cc107
-rw-r--r--src/libaudgui/ui_jumptotrack.c342
-rw-r--r--src/libaudgui/ui_jumptotrack_cache.c358
-rw-r--r--src/libaudgui/ui_playlist_manager.c282
-rw-r--r--src/libaudgui/urilist.c123
-rw-r--r--src/libaudgui/urilist.cc114
-rw-r--r--src/libaudgui/url-opener.cc (renamed from src/libaudgui/url-opener.c)27
-rw-r--r--src/libaudgui/util.cc (renamed from src/libaudgui/util.c)120
-rw-r--r--src/libaudqt/Makefile45
-rw-r--r--src/libaudqt/about.cc119
-rw-r--r--src/libaudqt/art.cc69
-rw-r--r--src/libaudqt/equalizer.cc167
-rw-r--r--src/libaudqt/fileopener.cc88
-rw-r--r--src/libaudqt/iface.h47
-rw-r--r--src/libaudqt/info-widget.cc205
-rw-r--r--src/libaudqt/info-widget.h63
-rw-r--r--src/libaudqt/infowin.cc158
-rw-r--r--src/libaudqt/libaudqt.h104
-rw-r--r--src/libaudqt/log-inspector.cc283
-rw-r--r--src/libaudqt/log-inspector.h46
-rw-r--r--src/libaudqt/menu.cc125
-rw-r--r--src/libaudqt/menu.h95
-rw-r--r--src/libaudqt/playlist-management.cc90
-rw-r--r--src/libaudqt/plugin-menu.cc78
-rw-r--r--src/libaudqt/prefs-builder.cc142
-rw-r--r--src/libaudqt/prefs-plugin.cc145
-rw-r--r--src/libaudqt/prefs-pluginlist-model.cc138
-rw-r--r--src/libaudqt/prefs-pluginlist-model.h62
-rw-r--r--src/libaudqt/prefs-widget.cc356
-rw-r--r--src/libaudqt/prefs-widget.h195
-rw-r--r--src/libaudqt/prefs-window.cc753
-rw-r--r--src/libaudqt/queue-manager.cc198
-rw-r--r--src/libaudqt/util.cc90
-rw-r--r--src/libaudqt/volumebutton.cc103
-rw-r--r--src/libaudqt/volumebutton.h (renamed from src/libaudgui/ui_jumptotrack_cache.h)35
-rw-r--r--src/libaudtag/Makefile20
-rw-r--r--src/libaudtag/ape/ape.c505
-rw-r--r--src/libaudtag/ape/ape.cc466
-rw-r--r--src/libaudtag/ape/ape.h29
-rw-r--r--src/libaudtag/audtag.c127
-rw-r--r--src/libaudtag/audtag.cc68
-rw-r--r--src/libaudtag/audtag.h25
-rw-r--r--src/libaudtag/builtin.h63
-rw-r--r--src/libaudtag/id3/id3-common.cc (renamed from src/libaudtag/id3/id3-common.c)162
-rw-r--r--src/libaudtag/id3/id3-common.h16
-rw-r--r--src/libaudtag/id3/id3v1.c142
-rw-r--r--src/libaudtag/id3/id3v1.cc131
-rw-r--r--src/libaudtag/id3/id3v1.h27
-rw-r--r--src/libaudtag/id3/id3v22.c343
-rw-r--r--src/libaudtag/id3/id3v22.cc315
-rw-r--r--src/libaudtag/id3/id3v22.h29
-rw-r--r--src/libaudtag/id3/id3v24.c807
-rw-r--r--src/libaudtag/id3/id3v24.cc782
-rw-r--r--src/libaudtag/id3/id3v24.h29
-rw-r--r--src/libaudtag/tag_module.c67
-rw-r--r--src/libaudtag/tag_module.cc98
-rw-r--r--src/libaudtag/tag_module.h30
-rw-r--r--src/libaudtag/util.cc (renamed from src/libaudtag/util.c)64
-rw-r--r--src/libaudtag/util.h19
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
deleted file mode 100644
index 32fb69f..0000000
--- a/src/audacious/images/about-logo.png
+++ /dev/null
Binary files differ
diff --git a/src/audacious/images/album.png b/src/audacious/images/album.png
deleted file mode 100644
index a47bc7d..0000000
--- a/src/audacious/images/album.png
+++ /dev/null
Binary files differ
diff --git a/src/audacious/images/appearance.png b/src/audacious/images/appearance.png
deleted file mode 100644
index f73239a..0000000
--- a/src/audacious/images/appearance.png
+++ /dev/null
Binary files differ
diff --git a/src/audacious/images/audio.png b/src/audacious/images/audio.png
deleted file mode 100644
index a41d51a..0000000
--- a/src/audacious/images/audio.png
+++ /dev/null
Binary files differ
diff --git a/src/audacious/images/connectivity.png b/src/audacious/images/connectivity.png
deleted file mode 100644
index 2d80e79..0000000
--- a/src/audacious/images/connectivity.png
+++ /dev/null
Binary files differ
diff --git a/src/audacious/images/info.png b/src/audacious/images/info.png
deleted file mode 100644
index 91e2c17..0000000
--- a/src/audacious/images/info.png
+++ /dev/null
Binary files differ
diff --git a/src/audacious/images/playlist.png b/src/audacious/images/playlist.png
deleted file mode 100644
index 2574ef0..0000000
--- a/src/audacious/images/playlist.png
+++ /dev/null
Binary files differ
diff --git a/src/audacious/images/plugins.png b/src/audacious/images/plugins.png
deleted file mode 100644
index da9f9e1..0000000
--- a/src/audacious/images/plugins.png
+++ /dev/null
Binary files differ
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 */