LocalRedirectServer: Remove https option and gnutls dependency

This commit is contained in:
Jonas Kvinge 2023-07-18 19:20:51 +02:00
parent dc65753a0b
commit 56180ca419
17 changed files with 18 additions and 266 deletions

View File

@ -47,7 +47,6 @@ jobs:
dbus-1-devel
alsa-devel
libnotify-devel
libgnutls-devel
protobuf-devel
sqlite3-devel
libpulse-devel
@ -210,7 +209,6 @@ jobs:
alsa-lib-devel
pulseaudio-libs-devel
libnotify-devel
gnutls-devel
libicu-devel
qt6-qtbase-devel
qt6-qtbase-private-devel
@ -311,7 +309,6 @@ jobs:
sqlite-devel
libasound-devel
pulseaudio-devel
gnutls-devel
lib64GL-devel
libgst-plugins-base1.0-devel
taglib-devel
@ -424,7 +421,6 @@ jobs:
lib64chromaprint-devel
lib64ebur128-devel
lib64icu-devel
lib64gnutls-devel
lib64fftw-devel
lib64dbus-devel
lib64appstream-devel
@ -520,7 +516,6 @@ jobs:
libprotobuf-dev
protobuf-compiler
libsqlite3-dev
libgnutls28-dev
libasound2-dev
libpulse-dev
libtag1-dev
@ -617,7 +612,6 @@ jobs:
libprotobuf-dev
protobuf-compiler
libsqlite3-dev
libgnutls28-dev
libasound2-dev
libpulse-dev
libtag1-dev
@ -716,7 +710,6 @@ jobs:
libdbus-1-dev
libprotobuf-dev
libsqlite3-dev
libgnutls28-dev
libasound2-dev
libpulse-dev
libtag1-dev
@ -786,7 +779,7 @@ jobs:
with:
usesh: true
mem: 4096
prepare: pkg install -y git cmake pkgconf gettext-tools boost-libs glib gnutls qt6-base qt6-tools sqlite gstreamer1 gstreamer1-plugins chromaprint libebur128 protobuf protobuf-c taglib libcdio libmtp gdk-pixbuf2 libgpod fftw3 googletest iconv icu
prepare: pkg install -y git cmake pkgconf gettext-tools boost-libs glib qt6-base qt6-tools sqlite gstreamer1 gstreamer1-plugins chromaprint libebur128 protobuf protobuf-c taglib libcdio libmtp gdk-pixbuf2 libgpod fftw3 googletest iconv icu
run: |
git config --global --add safe.directory ${GITHUB_WORKSPACE}
cmake -E make_directory build
@ -814,7 +807,7 @@ jobs:
run: |
brew install pkg-config cmake ninja meson bison flex wget create-dmg gettext boost protobuf protobuf-c rsync
brew install glib glib-openssl glib-utils glib-networking gdk-pixbuf gobject-introspection orc
brew install libffi openssl gnutls sqlite fftw libmtp libplist libxml2 libsoup
brew install libffi openssl sqlite fftw libmtp libplist libxml2 libsoup
brew install libogg libvorbis flac wavpack opus speex mpg123 lame twolame taglib chromaprint libebur128 libbs2b libcdio libopenmpt faad2 faac fdk-aac musepack game-music-emu
brew install qt6
@ -959,7 +952,7 @@ jobs:
run: sudo /opt/local/bin/port -v selfupdate
- name: Install packages
run: sudo /opt/local/bin/port -N -p install wget gettext glib2 pkgconfig cmake boost protobuf-cpp sqlite3 gnutls chromaprint libebur128 fftw taglib libcdio libmtp
run: sudo /opt/local/bin/port -N -p install wget gettext glib2 pkgconfig cmake boost protobuf-cpp sqlite3 chromaprint libebur128 fftw taglib libcdio libmtp
- name: Install gstreamer
run: sudo /opt/local/bin/port -N -p install gstreamer1 gstreamer1-gst-plugins-base gstreamer1-gst-plugins-good gstreamer1-gst-plugins-bad gstreamer1-gst-plugins-ugly gstreamer1-gst-libav

View File

@ -116,7 +116,6 @@ if(USE_ICU)
else()
find_package(Iconv)
endif()
find_package(GnuTLS REQUIRED)
find_package(Protobuf CONFIG)
if(NOT Protobuf_FOUND)
find_package(Protobuf REQUIRED)

View File

@ -80,7 +80,6 @@ To build Strawberry from source you need the following installed on your system
* [ALSA (Required on Linux)](https://www.alsa-project.org/)
* [D-Bus (Required on Linux)](https://www.freedesktop.org/wiki/Software/dbus/)
* [GStreamer](https://gstreamer.freedesktop.org/) or [VLC](https://www.videolan.org)
* [GnuTLS](https://www.gnutls.org/)
* [TagLib 1.11.1 or higher](https://www.taglib.org/) or [TagParser](https://github.com/Martchus/tagparser)
* [ICU](https://unicode-org.github.io/icu/)

1
debian/control.in vendored
View File

@ -11,7 +11,6 @@ Build-Depends: debhelper (>= 11),
protobuf-compiler,
libglib2.0-dev,
libdbus-1-dev,
libgnutls28-dev,
libprotobuf-dev,
libboost-dev,
libsqlite3-dev,

View File

@ -41,7 +41,6 @@ BuildRequires: pkgconfig(gio-2.0)
BuildRequires: pkgconfig(gio-unix-2.0)
BuildRequires: pkgconfig(gthread-2.0)
BuildRequires: pkgconfig(dbus-1)
BuildRequires: pkgconfig(gnutls)
BuildRequires: pkgconfig(alsa)
BuildRequires: pkgconfig(protobuf)
BuildRequires: pkgconfig(sqlite3) >= 3.9

View File

@ -422,7 +422,6 @@ Section "Strawberry" Strawberry
File "glib-2.0-0.dll"
File "gme.dll"
File "gmodule-2.0-0.dll"
File "gnutls.dll"
File "gobject-2.0-0.dll"
File "gstadaptivedemux-1.0-0.dll"
File "gstapp-1.0-0.dll"
@ -979,7 +978,6 @@ Section "Uninstall"
Delete "$INSTDIR\glib-2.0-0.dll"
Delete "$INSTDIR\gme.dll"
Delete "$INSTDIR\gmodule-2.0-0.dll"
Delete "$INSTDIR\gnutls.dll"
Delete "$INSTDIR\gobject-2.0-0.dll"
Delete "$INSTDIR\gstadaptivedemux-1.0-0.dll"
Delete "$INSTDIR\gstapp-1.0-0.dll"

View File

@ -974,7 +974,6 @@ link_directories(
${Boost_LIBRARY_DIRS}
${GLIB_LIBRARY_DIRS}
${GOBJECT_LIBRARY_DIRS}
${GNUTLS_LIBRARY_DIRS}
${SQLITE_LIBRARY_DIRS}
${PROTOBUF_LIBRARY_DIRS}
${SINGLEAPPLICATION_LIBRARY_DIRS}
@ -1067,7 +1066,6 @@ target_include_directories(strawberry_lib SYSTEM PUBLIC
${Boost_INCLUDE_DIRS}
${GLIB_INCLUDE_DIRS}
${GOBJECT_INCLUDE_DIRS}
${GNUTLS_INCLUDE_DIRS}
${SQLITE_INCLUDE_DIRS}
${PROTOBUF_INCLUDE_DIRS}
)
@ -1091,7 +1089,6 @@ target_link_libraries(strawberry_lib PUBLIC
${CMAKE_THREAD_LIBS_INIT}
${GLIB_LIBRARIES}
${GOBJECT_LIBRARIES}
${GNUTLS_LIBRARIES}
${SQLITE_LIBRARIES}
${QT_LIBRARIES}
${Protobuf_LIBRARIES}

View File

@ -103,7 +103,6 @@ void SpotifyCoverProvider::Authenticate() {
if (!server_) {
server_ = new LocalRedirectServer(this);
server_->set_https(false);
int port = redirect_url.port();
int port_max = port + 10;
bool success = false;

View File

@ -21,10 +21,6 @@
#include "localredirectserver.h"
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <gnutls/abstract.h>
#include <QApplication>
#include <QIODevice>
#include <QBuffer>
@ -37,20 +33,14 @@
#include <QRegularExpression>
#include <QStyle>
#include <QHostAddress>
#include <QSsl>
#include <QSslKey>
#include <QSslCertificate>
#include <QSslError>
#include <QTcpServer>
#include <QAbstractSocket>
#include <QTcpSocket>
#include <QSslSocket>
#include <QDateTime>
#include <QRandomGenerator>
LocalRedirectServer::LocalRedirectServer(QObject *parent)
: QTcpServer(parent),
https_(false),
port_(0),
socket_(nullptr) {}
@ -58,187 +48,14 @@ LocalRedirectServer::~LocalRedirectServer() {
if (isListening()) close();
}
bool LocalRedirectServer::GenerateCertificate() {
if (int result = gnutls_global_init() != GNUTLS_E_SUCCESS) {
error_ = QString("Failed to initialize GnuTLS: %1").arg(gnutls_strerror(result));
return false;
}
gnutls_x509_privkey_t key = nullptr;
if (int result = gnutls_x509_privkey_init(&key) != GNUTLS_E_SUCCESS) {
error_ = QString("Failed to initialize the private key structure: %1").arg(gnutls_strerror(result));
gnutls_global_deinit();
return false;
}
unsigned int bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_MEDIUM);
if (int result = gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, bits, 0) != GNUTLS_E_SUCCESS) {
error_ = QString("Failed to generate random private key: %1").arg(gnutls_strerror(result));
gnutls_global_deinit();
return false;
}
char buffer[4096] = "";
size_t buffer_size = sizeof(buffer);
if (int result = gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, buffer, &buffer_size) != GNUTLS_E_SUCCESS) {
error_ = QString("Failed export private key: %1").arg(gnutls_strerror(result));
gnutls_x509_privkey_deinit(key);
gnutls_global_deinit();
return false;
}
QSslKey ssl_key(QByteArray(buffer, static_cast<qint64>(buffer_size)), QSsl::Rsa);
if (ssl_key.isNull()) {
error_ = QString("Failed to generate random private key.");
gnutls_x509_privkey_deinit(key);
gnutls_global_deinit();
return false;
}
gnutls_x509_crt_t crt = nullptr;
if (int result = gnutls_x509_crt_init(&crt) != GNUTLS_E_SUCCESS) {
error_ = QString("Failed to initialize an X.509 certificate structure: %1").arg(gnutls_strerror(result));
gnutls_x509_privkey_deinit(key);
gnutls_global_deinit();
return false;
}
if (int result = gnutls_x509_crt_set_version(crt, 1) != GNUTLS_E_SUCCESS) {
error_ = QString("Failed to set the version of the certificate: %1").arg(gnutls_strerror(result));
gnutls_x509_privkey_deinit(key);
gnutls_x509_crt_deinit(crt);
gnutls_global_deinit();
return false;
}
if (int result = gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_COUNTRY_NAME, 0, "US", 2) != GNUTLS_E_SUCCESS) {
error_ = QString("Failed to set part of the name of the certificate subject: %1").arg(gnutls_strerror(result));
gnutls_x509_privkey_deinit(key);
gnutls_x509_crt_deinit(crt);
gnutls_global_deinit();
return false;
}
if (int result = gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, "Strawberry Music Player", static_cast<int>(strlen("Strawberry Music Player"))) != GNUTLS_E_SUCCESS) {
error_ = QString("Failed to set part of the name of the certificate subject: %1").arg(gnutls_strerror(result));
gnutls_x509_privkey_deinit(key);
gnutls_x509_crt_deinit(crt);
gnutls_global_deinit();
return false;
}
if (int result = gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_COMMON_NAME, 0, "localhost", static_cast<int>(strlen("localhost"))) != GNUTLS_E_SUCCESS) {
error_ = QString("Failed to set part of the name of the certificate subject: %1").arg(gnutls_strerror(result));
gnutls_x509_privkey_deinit(key);
gnutls_x509_crt_deinit(crt);
gnutls_global_deinit();
return false;
}
if (int result = gnutls_x509_crt_set_key(crt, key) != GNUTLS_E_SUCCESS) {
error_ = QString("Failed to set the public parameters from the given private key to the certificate: %1").arg(gnutls_strerror(result));
gnutls_x509_privkey_deinit(key);
gnutls_x509_crt_deinit(crt);
gnutls_global_deinit();
return false;
}
quint64 time = QDateTime::currentDateTime().toSecsSinceEpoch();
gnutls_x509_crt_set_activation_time(crt, static_cast<time_t>(time));
if (int result = gnutls_x509_crt_set_expiration_time(crt, static_cast<time_t>(time + 31536000L)) != GNUTLS_E_SUCCESS) {
error_ = QString("Failed to set the activation time of the certificate: %1").arg(gnutls_strerror(result));
gnutls_x509_privkey_deinit(key);
gnutls_x509_crt_deinit(crt);
gnutls_global_deinit();
return false;
}
const quint64 serial = 9999999 + QRandomGenerator::global()->bounded(1000000);
QByteArray q_serial;
q_serial.setNum(serial);
if (int result = gnutls_x509_crt_set_serial(crt, q_serial.constData(), sizeof(q_serial.size())) != GNUTLS_E_SUCCESS) {
error_ = QString("Failed to set the serial of the certificate: %1").arg(gnutls_strerror(result));
gnutls_x509_privkey_deinit(key);
gnutls_x509_crt_deinit(crt);
gnutls_global_deinit();
return false;
}
gnutls_privkey_t pkey = nullptr;
if (int result = gnutls_privkey_init(&pkey) != GNUTLS_E_SUCCESS) {
error_ = QString("Failed to initialize a private key object: %1").arg(gnutls_strerror(result));
gnutls_x509_privkey_deinit(key);
gnutls_x509_crt_deinit(crt);
gnutls_global_deinit();
return false;
}
if (int result = gnutls_privkey_import_x509(pkey, key, 0) != GNUTLS_E_SUCCESS) {
error_ = QString("Failed to import the given private key to the abstract private key object: %1").arg(gnutls_strerror(result));
gnutls_x509_privkey_deinit(key);
gnutls_x509_crt_deinit(crt);
gnutls_privkey_deinit(pkey);
gnutls_global_deinit();
return false;
}
if (int result = gnutls_x509_crt_privkey_sign(crt, crt, pkey, GNUTLS_DIG_SHA256, 0) != GNUTLS_E_SUCCESS) {
error_ = QString("Failed to sign the certificate with the issuer's private key: %1").arg(gnutls_strerror(result));
gnutls_x509_privkey_deinit(key);
gnutls_x509_crt_deinit(crt);
gnutls_privkey_deinit(pkey);
gnutls_global_deinit();
return false;
}
if (int result = gnutls_x509_crt_sign2(crt, crt, key, GNUTLS_DIG_SHA256, 0) != GNUTLS_E_SUCCESS) {
error_ = QString("Failed to sign the certificate: %1").arg(gnutls_strerror(result));
gnutls_x509_privkey_deinit(key);
gnutls_x509_crt_deinit(crt);
gnutls_privkey_deinit(pkey);
gnutls_global_deinit();
return false;
}
if (int result = gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, buffer, &buffer_size) != GNUTLS_E_SUCCESS) {
error_ = QString("Failed to export the certificate to PEM format: %1").arg(gnutls_strerror(result));
gnutls_x509_privkey_deinit(key);
gnutls_x509_crt_deinit(crt);
gnutls_privkey_deinit(pkey);
gnutls_global_deinit();
return false;
}
gnutls_x509_crt_deinit(crt);
gnutls_x509_privkey_deinit(key);
gnutls_privkey_deinit(pkey);
QSslCertificate ssl_certificate(QByteArray(buffer, static_cast<qint64>(buffer_size)));
if (ssl_certificate.isNull()) {
error_ = "Failed to generate random client certificate.";
gnutls_global_deinit();
return false;
}
gnutls_global_deinit();
ssl_certificate_ = ssl_certificate;
ssl_key_ = ssl_key;
return true;
}
bool LocalRedirectServer::Listen() {
if (https_) {
if (!GenerateCertificate()) return false;
}
if (!listen(QHostAddress::LocalHost, port_)) {
error_ = errorString();
return false;
}
if (https_) url_.setScheme("https");
else url_.setScheme("http");
url_.setScheme("http");
url_.setHost("localhost");
url_.setPort(serverPort());
url_.setPath("/");
@ -265,27 +82,6 @@ void LocalRedirectServer::incomingConnection(qintptr socket_descriptor) {
}
buffer_.clear();
if (https_) {
QSslSocket *ssl_socket = new QSslSocket(this);
if (!ssl_socket->setSocketDescriptor(socket_descriptor)) {
delete ssl_socket;
close();
error_ = "Unable to set socket descriptor";
emit Finished();
return;
}
ssl_socket->ignoreSslErrors(QList<QSslError>() << QSslError(QSslError::SelfSignedCertificate));
ssl_socket->setPrivateKey(ssl_key_);
ssl_socket->setLocalCertificate(ssl_certificate_);
ssl_socket->setProtocol(QSsl::TlsV1_2);
ssl_socket->startServerEncryption();
QObject::connect(ssl_socket, QOverload<const QList<QSslError>&>::of(&QSslSocket::sslErrors), this, &LocalRedirectServer::SSLErrors);
QObject::connect(ssl_socket, &QSslSocket::encrypted, this, &LocalRedirectServer::Encrypted);
socket_ = ssl_socket;
}
else {
QTcpSocket *tcp_socket = new QTcpSocket(this);
if (!tcp_socket->setSocketDescriptor(socket_descriptor)) {
delete tcp_socket;
@ -295,7 +91,6 @@ void LocalRedirectServer::incomingConnection(qintptr socket_descriptor) {
return;
}
socket_ = tcp_socket;
}
QObject::connect(socket_, &QAbstractSocket::connected, this, &LocalRedirectServer::Connected);
QObject::connect(socket_, &QAbstractSocket::disconnected, this, &LocalRedirectServer::Disconnected);
@ -303,8 +98,6 @@ void LocalRedirectServer::incomingConnection(qintptr socket_descriptor) {
}
void LocalRedirectServer::SSLErrors(const QList<QSslError> &errors) { Q_UNUSED(errors); }
void LocalRedirectServer::Encrypted() {}
void LocalRedirectServer::Connected() {}

View File

@ -29,9 +29,6 @@
#include <QString>
#include <QUrl>
#include <QTcpServer>
#include <QSslCertificate>
#include <QSslKey>
#include <QSslError>
class QAbstractSocket;
@ -42,7 +39,6 @@ class LocalRedirectServer : public QTcpServer {
explicit LocalRedirectServer(QObject *parent = nullptr);
~LocalRedirectServer() override;
void set_https(const bool https) { https_ = https; }
void set_port(const int port) { port_ = port; }
bool Listen();
const QUrl &url() const { return url_; }
@ -55,7 +51,6 @@ class LocalRedirectServer : public QTcpServer {
public slots:
void NewConnection();
void incomingConnection(qintptr socket_descriptor) override;
void SSLErrors(const QList<QSslError> &errors);
void Encrypted();
void Connected();
void Disconnected();
@ -71,8 +66,6 @@ class LocalRedirectServer : public QTcpServer {
int port_;
QUrl url_;
QUrl request_url_;
QSslCertificate ssl_certificate_;
QSslKey ssl_key_;
QAbstractSocket *socket_;
QByteArray buffer_;
QString error_;

View File

@ -87,7 +87,6 @@ void GeniusLyricsProvider::Authenticate() {
if (!server_) {
server_ = new LocalRedirectServer(this);
server_->set_https(false);
server_->set_port(redirect_url.port());
if (!server_->Listen()) {
AuthError(server_->error());

View File

@ -160,11 +160,10 @@ void ListenBrainzScrobbler::Logout() {
}
void ListenBrainzScrobbler::Authenticate(const bool https) {
void ListenBrainzScrobbler::Authenticate() {
if (!server_) {
server_ = new LocalRedirectServer(this);
server_->set_https(https);
if (!server_->Listen()) {
AuthError(server_->error());
delete server_;

View File

@ -62,7 +62,7 @@ class ListenBrainzScrobbler : public ScrobblerService {
void Submitted() override { submitted_ = true; }
QString user_token() const { return user_token_; }
void Authenticate(const bool https = false);
void Authenticate();
void Logout();
void ShowConfig();
void Submit() override;

View File

@ -74,7 +74,6 @@ ScrobblingAPI20::ScrobblingAPI20(const QString &name, const QString &settings_gr
cache_(new ScrobblerCache(cache_file, this)),
server_(nullptr),
enabled_(false),
https_(false),
prefer_albumartist_(false),
subscriber_(false),
submitted_(false),
@ -113,7 +112,6 @@ void ScrobblingAPI20::ReloadSettings() {
s.beginGroup(settings_group_);
enabled_ = s.value("enabled", false).toBool();
https_ = s.value("https", false).toBool();
s.endGroup();
s.beginGroup(ScrobblerSettingsPage::kSettingsGroup);
@ -193,11 +191,10 @@ ScrobblingAPI20::ReplyResult ScrobblingAPI20::GetJsonObject(QNetworkReply *reply
}
void ScrobblingAPI20::Authenticate(const bool https) {
void ScrobblingAPI20::Authenticate() {
if (!server_) {
server_ = new LocalRedirectServer(this);
server_->set_https(https);
if (!server_->Listen()) {
AuthError(server_->error());
delete server_;
@ -260,7 +257,7 @@ void ScrobblingAPI20::RedirectArrived() {
}
}
else {
AuthError(tr("Received invalid reply from web browser. Try the HTTPS option, or use another browser like Chromium or Chrome."));
AuthError(tr("Received invalid reply from web browser. Try another browser."));
}
}
else {

View File

@ -54,14 +54,13 @@ class ScrobblingAPI20 : public ScrobblerService {
void LoadSession();
bool IsEnabled() const override { return enabled_; }
bool IsUseHTTPS() const { return https_; }
bool IsAuthenticated() const override { return !username_.isEmpty() && !session_key_.isEmpty(); }
bool IsSubscriber() const { return subscriber_; }
bool IsSubmitted() const override { return submitted_; }
void Submitted() override { submitted_ = true; }
QString username() const { return username_; }
void Authenticate(const bool https = false);
void Authenticate();
void Logout();
void UpdateNowPlaying(const Song &song) override;
void ClearPlaying() override;
@ -148,7 +147,6 @@ class ScrobblingAPI20 : public ScrobblerService {
LocalRedirectServer *server_;
bool enabled_;
bool https_;
bool prefer_albumartist_;
bool subscriber_;

View File

@ -112,7 +112,6 @@ void ScrobblerSettingsPage::Load() {
ui_->checkbox_source_unknown->setChecked(scrobbler_->sources().contains(Song::Source::Unknown));
ui_->checkbox_lastfm_enable->setChecked(lastfmscrobbler_->IsEnabled());
ui_->checkbox_lastfm_https->setChecked(lastfmscrobbler_->IsUseHTTPS());
LastFM_RefreshControls(lastfmscrobbler_->IsAuthenticated());
ui_->checkbox_librefm_enable->setChecked(librefmscrobbler_->IsEnabled());
@ -160,7 +159,6 @@ void ScrobblerSettingsPage::Save() {
s.beginGroup(LastFMScrobbler::kSettingsGroup);
s.setValue("enabled", ui_->checkbox_lastfm_enable->isChecked());
s.setValue("https", ui_->checkbox_lastfm_https->isChecked());
s.endGroup();
s.beginGroup(LibreFMScrobbler::kSettingsGroup);
@ -180,7 +178,7 @@ void ScrobblerSettingsPage::LastFM_Login() {
lastfm_waiting_for_auth_ = true;
ui_->widget_lastfm_login_state->SetLoggedIn(LoginStateWidget::State::LoginInProgress);
lastfmscrobbler_->Authenticate(ui_->checkbox_lastfm_https->isChecked());
lastfmscrobbler_->Authenticate();
}

View File

@ -224,13 +224,6 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_lastfm_https">
<property name="text">
<string>Use HTTPS for local redirectserver</string>
</property>
</widget>
</item>
<item>
<widget class="LoginStateWidget" name="widget_lastfm_login_state" native="true"/>
</item>
@ -438,7 +431,6 @@
<tabstop>checkbox_source_somafm</tabstop>
<tabstop>checkbox_source_radioparadise</tabstop>
<tabstop>checkbox_lastfm_enable</tabstop>
<tabstop>checkbox_lastfm_https</tabstop>
<tabstop>button_lastfm_login</tabstop>
<tabstop>checkbox_librefm_enable</tabstop>
<tabstop>button_librefm_login</tabstop>