From e8243ee69cb788028490b265c52e23b459ed68e5 Mon Sep 17 00:00:00 2001 From: Thomas Lange Date: Mon, 12 Feb 2024 00:57:06 +0100 Subject: [PATCH] lyrics-gtk: Restore lyrics plugin for GTK - Based on the original code which was removed with Audacious 4.1 - Add all new features from the Qt lyrics plugin - Use new dependency "json-glib" to parse JSON strings --- .../actions/install-dependencies/action.yml | 16 +- .../install-dependencies.sh | 7 +- acinclude.m4 | 4 + configure.ac | 4 +- extra.mk.in | 2 + meson.build | 2 +- src/lyrics-gtk/Makefile | 18 ++ src/lyrics-gtk/lyrics-gtk.cc | 253 ++++++++++++++++++ src/lyrics-gtk/meson.build | 22 ++ src/meson.build | 1 + 10 files changed, 315 insertions(+), 14 deletions(-) create mode 100644 src/lyrics-gtk/Makefile create mode 100644 src/lyrics-gtk/lyrics-gtk.cc create mode 100644 src/lyrics-gtk/meson.build diff --git a/.github/actions/install-dependencies/action.yml b/.github/actions/install-dependencies/action.yml index a86119622..9b928c7dc 100644 --- a/.github/actions/install-dependencies/action.yml +++ b/.github/actions/install-dependencies/action.yml @@ -25,11 +25,11 @@ runs: install: >- mingw-w64-i686-autotools mingw-w64-i686-faad2 mingw-w64-i686-ffmpeg mingw-w64-i686-flac mingw-w64-i686-fluidsynth mingw-w64-i686-gcc - mingw-w64-i686-gtk2 mingw-w64-i686-lame mingw-w64-i686-libbs2b - mingw-w64-i686-libcdio-paranoia mingw-w64-i686-libcue - mingw-w64-i686-libmodplug mingw-w64-i686-libopenmpt - mingw-w64-i686-libsamplerate mingw-w64-i686-libsidplayfp - mingw-w64-i686-libsoxr mingw-w64-i686-libvorbis mingw-w64-i686-meson - mingw-w64-i686-mpg123 mingw-w64-i686-neon mingw-w64-i686-opusfile - mingw-w64-i686-pkg-config mingw-w64-i686-qt5-base mingw-w64-i686-SDL2 - mingw-w64-i686-wavpack + mingw-w64-i686-gtk2 mingw-w64-i686-json-glib mingw-w64-i686-lame + mingw-w64-i686-libbs2b mingw-w64-i686-libcdio-paranoia + mingw-w64-i686-libcue mingw-w64-i686-libmodplug + mingw-w64-i686-libopenmpt mingw-w64-i686-libsamplerate + mingw-w64-i686-libsidplayfp mingw-w64-i686-libsoxr + mingw-w64-i686-libvorbis mingw-w64-i686-meson mingw-w64-i686-mpg123 + mingw-w64-i686-neon mingw-w64-i686-opusfile mingw-w64-i686-pkg-config + mingw-w64-i686-qt5-base mingw-w64-i686-SDL2 mingw-w64-i686-wavpack diff --git a/.github/actions/install-dependencies/install-dependencies.sh b/.github/actions/install-dependencies/install-dependencies.sh index 686480651..ec2266fbb 100755 --- a/.github/actions/install-dependencies/install-dependencies.sh +++ b/.github/actions/install-dependencies/install-dependencies.sh @@ -20,9 +20,10 @@ ubuntu_packages='gettext libadplug-dev libasound2-dev libavformat-dev libbinio-dev libbs2b-dev libcddb2-dev libcdio-cdda-dev libcue-dev libcurl4-gnutls-dev libdbus-glib-1-dev libfaad-dev libflac-dev libfluidsynth-dev libgl1-mesa-dev - libjack-jackd2-dev liblircclient-dev libmms-dev libmodplug-dev - libmp3lame-dev libmpg123-dev libneon27-gnutls-dev libnotify-dev - libopenmpt-dev libopusfile-dev libpulse-dev libqt5opengl5-dev + libjack-jackd2-dev libjson-glib-dev liblircclient-dev + libmms-dev libmodplug-dev libmp3lame-dev libmpg123-dev + libneon27-gnutls-dev libnotify-dev libopenmpt-dev + libopusfile-dev libpulse-dev libqt5opengl5-dev libqt5x11extras5-dev libsamplerate0-dev libsdl2-dev libsidplayfp-dev libsndfile1-dev libsndio-dev libsoxr-dev libvorbis-dev libwavpack-dev libxml2-dev qtbase5-dev diff --git a/acinclude.m4 b/acinclude.m4 index 07cb6f8a2..d455db2be 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -207,6 +207,10 @@ fi AC_SUBST(USE_GTK) +if test $USE_GTK = yes ; then + PKG_CHECK_MODULES(JSON_GLIB, json-glib-1.0 >= 1.0) +fi + if test $HAVE_MSWINDOWS = yes ; then PKG_CHECK_MODULES(GIO, gio-2.0 >= 2.32) else diff --git a/configure.ac b/configure.ac index a83645f3d..b6fbbfccb 100644 --- a/configure.ac +++ b/configure.ac @@ -76,7 +76,7 @@ TRANSPORT_PLUGINS="gio" if test "x$USE_GTK" = "xyes" ; then EFFECT_PLUGINS="$EFFECT_PLUGINS ladspa" - GENERAL_PLUGINS="$GENERAL_PLUGINS albumart playlist-manager search-tool statusicon" + GENERAL_PLUGINS="$GENERAL_PLUGINS albumart lyrics-gtk playlist-manager search-tool statusicon" GENERAL_PLUGINS="$GENERAL_PLUGINS gtkui skins" VISUALIZATION_PLUGINS="$VISUALIZATION_PLUGINS blur_scope cairo-spectrum" fi @@ -829,6 +829,7 @@ echo " Ampache browser (requires Qt): $have_ampache" echo " Delete Files: $USE_GTK_OR_QT" echo " libnotify OSD: $have_notify" echo " Linux Infrared Remote Control (LIRC): $have_lirc" +echo " Lyrics Viewer: yes" echo " MPRIS 2 Server: $have_mpris2" echo " Scrobbler 2.0: $have_scrobbler2" echo " Song Change: $have_songchange" @@ -859,7 +860,6 @@ if test "x$USE_QT" = "xyes" ; then echo " Winamp Classic Interface: yes" echo " Album Art: yes" echo " Blur Scope: yes" - echo " Lyrics Viewer: yes" echo " OpenGL Spectrum Analyzer: $have_qtglspectrum" echo " Playlist Manager: yes" echo " Search Tool: yes" diff --git a/extra.mk.in b/extra.mk.in index c5eec0a25..c702945a2 100644 --- a/extra.mk.in +++ b/extra.mk.in @@ -52,6 +52,8 @@ GTK_CFLAGS ?= @GTK_CFLAGS@ GTK_LIBS ?= @GTK_LIBS@ JACK_CFLAGS ?= @JACK_CFLAGS@ JACK_LIBS ?= @JACK_LIBS@ +JSON_GLIB_CFLAGS ?= @JSON_GLIB_CFLAGS@ +JSON_GLIB_LIBS ?= @JSON_GLIB_LIBS@ LIBFLAC_LIBS ?= @LIBFLAC_LIBS@ LIBFLAC_CFLAGS ?= @LIBFLAC_CFLAGS@ MMS_CFLAGS ?= @MMS_CFLAGS@ diff --git a/meson.build b/meson.build index e206f940e..2ae728b90 100644 --- a/meson.build +++ b/meson.build @@ -295,6 +295,7 @@ if meson.version().version_compare('>= 0.53') 'Delete Files': conf.has('USE_GTK_OR_QT'), 'Libnotify OSD': get_variable('have_notify', false), 'Linux Infrared Remote Control (LIRC)': get_variable('have_lirc', false), + 'Lyrics Viewer': get_variable('have_lyrics', false), 'MPRIS 2 Server': get_variable('have_mpris2', false), 'Scrobbler 2.0': get_variable('have_scrobbler2', false), 'Song Change': get_option('songchange'), @@ -307,7 +308,6 @@ if meson.version().version_compare('>= 0.53') 'Winamp Classic Interface': true, 'Album Art': true, 'Blur Scope': true, - 'Lyrics Viewer': get_variable('have_lyrics', false), 'OpenGL Spectrum Analyzer': get_variable('have_qtglspectrum', false), 'Playlist Manager': true, 'Search Tool': true, diff --git a/src/lyrics-gtk/Makefile b/src/lyrics-gtk/Makefile new file mode 100644 index 000000000..951c70e24 --- /dev/null +++ b/src/lyrics-gtk/Makefile @@ -0,0 +1,18 @@ +PLUGIN = lyrics-gtk${PLUGIN_SUFFIX} + +SRCS = ../lyrics-common/chart_lyrics_provider.cc \ + ../lyrics-common/file_provider.cc \ + ../lyrics-common/lyrics_ovh_provider.cc \ + ../lyrics-common/utils.cc \ + lyrics-gtk.cc + +include ../../buildsys.mk +include ../../extra.mk + +plugindir := ${plugindir}/${GENERAL_PLUGIN_DIR} + +LD = ${CXX} + +CFLAGS += ${PLUGIN_CFLAGS} +CPPFLAGS += ${PLUGIN_CPPFLAGS} ${GTK_CFLAGS} ${GLIB_CFLAGS} ${JSON_GLIB_CFLAGS} ${XML_CFLAGS} -I../.. +LIBS += ${GTK_LIBS} ${GLIB_LIBS} ${JSON_GLIB_LIBS} ${XML_LIBS} -laudgui diff --git a/src/lyrics-gtk/lyrics-gtk.cc b/src/lyrics-gtk/lyrics-gtk.cc new file mode 100644 index 000000000..676155aa6 --- /dev/null +++ b/src/lyrics-gtk/lyrics-gtk.cc @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2010-2019 Ariadne Conill + * Copyright (c) 2024 Thomas Lange + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include +#include + +#include "../lyrics-common/lyrics.h" +#include "../lyrics-common/preferences.h" + +class Lyrics : public GeneralPlugin +{ +public: + static const PluginPreferences prefs; + static constexpr PluginInfo info = { + N_("Lyrics"), + PACKAGE, + nullptr, // about + & prefs, + PluginGLibOnly + }; + + constexpr Lyrics () : GeneralPlugin (info, false) {} + + bool init (); + void * get_gtk_widget (); +}; + +EXPORT Lyrics aud_plugin_instance; + +const PluginPreferences Lyrics::prefs = {{widgets}}; + +FileProvider file_provider; +ChartLyricsProvider chart_lyrics_provider; +LyricsOVHProvider lyrics_ovh_provider; + +LyricsState g_state; + +static GtkTextView * textview; +static GtkTextBuffer * textbuffer; + +bool Lyrics::init () +{ + aud_config_set_defaults (CFG_SECTION, defaults); + return true; +} + +void update_lyrics_window (const char * title, const char * artist, const char * lyrics) +{ + GtkTextIter iter; + + if (! textbuffer) + return; + + gtk_text_buffer_set_text (textbuffer, "", -1); + + gtk_text_buffer_get_start_iter (textbuffer, & iter); + + gtk_text_buffer_insert_with_tags_by_name (textbuffer, & iter, title, -1, + "weight_bold", "scale_large", nullptr); + + if (artist) + { + gtk_text_buffer_insert (textbuffer, & iter, "\n", -1); + gtk_text_buffer_insert_with_tags_by_name (textbuffer, & iter, artist, -1, + "style_italic", nullptr); + } + + gtk_text_buffer_insert (textbuffer, & iter, "\n\n", -1); + gtk_text_buffer_insert (textbuffer, & iter, lyrics, -1); + + gtk_text_buffer_get_start_iter (textbuffer, & iter); + gtk_text_view_scroll_to_iter (textview, & iter, 0, true, 0, 0); +} + +bool try_parse_json (const Index & buf, const char * key, String & output) +{ + JsonParser * parser = json_parser_new (); + + if (! json_parser_load_from_data (parser, buf.begin (), buf.len (), nullptr)) + { + g_object_unref (parser); + return false; + } + + JsonNode * root = json_parser_get_root (parser); + JsonReader * reader = json_reader_new (root); + + json_reader_read_member (reader, key); + output = String (json_reader_get_string_value (reader)); + json_reader_end_member (reader); + + g_object_unref (reader); + g_object_unref (parser); + + return true; +} + +static void destroy_cb () +{ + g_state.filename = String (); + g_state.title = String (); + g_state.artist = String (); + g_state.lyrics = String (); + + hook_dissociate ("tuple change", (HookFunction) lyrics_playback_began); + hook_dissociate ("playback ready", (HookFunction) lyrics_playback_began); + + textview = nullptr; + textbuffer = nullptr; +} + +static GtkWidget * append_item_to_menu (GtkWidget * menu, const char * label) +{ + GtkWidget * menu_item = (label != nullptr) + ? gtk_menu_item_new_with_label (label) + : gtk_separator_menu_item_new (); + + gtk_menu_shell_append ((GtkMenuShell *) menu, menu_item); + gtk_widget_show (menu_item); + + return menu_item; +} + +static void append_separator_to_menu (GtkWidget * menu) +{ + append_item_to_menu (menu, nullptr); +} + +static void edit_lyrics_cb (GtkMenuItem * menu_item, void * data) +{ + const char * edit_uri = (const char *) data; +#ifdef USE_GTK3 + gtk_show_uri_on_window (nullptr, edit_uri, GDK_CURRENT_TIME, nullptr); +#else + gtk_show_uri (nullptr, edit_uri, GDK_CURRENT_TIME, nullptr); +#endif +} + +static void save_locally_cb (GtkMenuItem * menu_item, void * data) +{ + file_provider.save (g_state); +} + +static void refresh_cb (GtkMenuItem * menu_item, void * data) +{ + LyricProvider * remote_provider = remote_source (); + if (remote_provider) + remote_provider->match (g_state); +} + +static void populate_popup_cb (GtkTextView * text_view, GtkWidget * menu, void * data) +{ + if (! g_state.artist || ! g_state.title || ! GTK_IS_MENU (menu)) + return; + + GtkWidget * menu_item; + append_separator_to_menu (menu); + + if (g_state.lyrics && g_state.source != LyricsState::Source::Local && ! g_state.error) + { + LyricProvider * remote_provider = remote_source (); + + if (remote_provider) + { + String edit_uri = remote_provider->edit_uri (g_state); + + if (edit_uri && edit_uri[0]) + { + menu_item = append_item_to_menu (menu, _("Edit Lyrics ...")); + g_signal_connect_data (menu_item, "activate", (GCallback) edit_lyrics_cb, + g_strdup (edit_uri), (GClosureNotify) g_free, (GConnectFlags) 0); + } + } + + menu_item = append_item_to_menu (menu, _("Save Locally")); + g_signal_connect (menu_item, "activate", (GCallback) save_locally_cb, nullptr); + } + + if (g_state.source == LyricsState::Source::Local || g_state.error) + { + menu_item = append_item_to_menu (menu, _("Refresh")); + g_signal_connect (menu_item, "activate", (GCallback) refresh_cb, nullptr); + } +} + +static GtkWidget * build_widget () +{ + textview = (GtkTextView *) gtk_text_view_new (); + gtk_text_view_set_editable (textview, false); + gtk_text_view_set_cursor_visible (textview, false); + gtk_text_view_set_left_margin (textview, 4); + gtk_text_view_set_right_margin (textview, 4); + gtk_text_view_set_wrap_mode (textview, GTK_WRAP_WORD); + textbuffer = gtk_text_view_get_buffer (textview); + + GtkWidget * scrollview = gtk_scrolled_window_new (nullptr, nullptr); + gtk_scrolled_window_set_shadow_type ((GtkScrolledWindow *) scrollview, GTK_SHADOW_IN); + gtk_scrolled_window_set_policy ((GtkScrolledWindow *) scrollview, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + GtkWidget * vbox = audgui_vbox_new (6); + + g_signal_connect (textview, "populate-popup", (GCallback) populate_popup_cb, nullptr); + + gtk_container_add ((GtkContainer *) scrollview, (GtkWidget *) textview); + gtk_box_pack_start ((GtkBox *) vbox, scrollview, true, true, 0); + + gtk_widget_show_all (vbox); + + gtk_text_buffer_create_tag (textbuffer, "weight_bold", "weight", PANGO_WEIGHT_BOLD, nullptr); + gtk_text_buffer_create_tag (textbuffer, "scale_large", "scale", PANGO_SCALE_LARGE, nullptr); + gtk_text_buffer_create_tag (textbuffer, "style_italic", "style", PANGO_STYLE_ITALIC, nullptr); + + GtkWidget * hbox = audgui_hbox_new (6); + gtk_box_pack_start ((GtkBox *) vbox, hbox, false, false, 0); + + return vbox; +} + +void * Lyrics::get_gtk_widget () +{ + GtkWidget * vbox = build_widget (); + + hook_associate ("tuple change", (HookFunction) lyrics_playback_began, nullptr); + hook_associate ("playback ready", (HookFunction) lyrics_playback_began, nullptr); + + if (aud_drct_get_ready ()) + lyrics_playback_began (); + + g_signal_connect (vbox, "destroy", (GCallback) destroy_cb, nullptr); + + return vbox; +} diff --git a/src/lyrics-gtk/meson.build b/src/lyrics-gtk/meson.build new file mode 100644 index 000000000..90156cdc9 --- /dev/null +++ b/src/lyrics-gtk/meson.build @@ -0,0 +1,22 @@ +have_lyrics = xml_dep.found() + +json_glib_dep = dependency('json-glib-1.0', version: '>= 1.0', required: true) + +lyrics_deps = [audacious_dep, audgui_dep, gtk_dep, glib_dep, json_glib_dep, xml_dep] +lyrics_src = [ + '../lyrics-common/chart_lyrics_provider.cc', + '../lyrics-common/file_provider.cc', + '../lyrics-common/lyrics_ovh_provider.cc', + '../lyrics-common/utils.cc', + 'lyrics-gtk.cc' +] + +if have_lyrics + shared_module('lyrics-gtk', + lyrics_src, + dependencies: lyrics_deps, + name_prefix: '', + install: true, + install_dir: general_plugin_dir + ) +endif diff --git a/src/meson.build b/src/meson.build index a1fb5e358..50b76626b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -142,6 +142,7 @@ if conf.has('USE_GTK') subdir('cairo-spectrum') subdir('gtkui') subdir('ladspa') + subdir('lyrics-gtk') subdir('playlist-manager') subdir('search-tool') subdir('skins')