Add manual proxy configuration

This implementation is based on the one from neochat and tokodon, but it
fixes the "system default" option and adds a "no proxy" option.

Most of these settings now also cover streaming audio through the
different backends.

FEATURE: 467490
This commit is contained in:
Bart De Vries 2024-04-23 16:50:32 +02:00
parent c15fd5b54d
commit 5a42358592
No known key found for this signature in database
GPG Key ID: 7285665DA6E2D42B
4 changed files with 261 additions and 1 deletions

View File

@ -15,6 +15,7 @@
#include <QFile>
#include <QFileInfo>
#include <QNetworkAccessManager>
#include <QNetworkProxy>
#include <QNetworkProxyFactory>
#include <QNetworkReply>
#include <QTime>
@ -43,7 +44,14 @@ Fetcher::Fetcher()
manager->setStrictTransportSecurityEnabled(true);
manager->enableStrictTransportSecurityStore(true);
QNetworkProxyFactory::setUseSystemConfiguration(true);
// First save the original system proxy settings
m_systemHttpProxy = qgetenv("http_proxy");
m_systemHttpsProxy = qgetenv("https_proxy");
m_isSystemProxyDefined = (QNetworkProxy::applicationProxy().type() != QNetworkProxy::ProxyType::NoProxy);
qCDebug(kastsFetcher) << "saved system proxy:" << m_systemHttpProxy << m_systemHttpsProxy << m_isSystemProxyDefined;
// Set network proxy based on saved settings
setNetworkProxy();
// setup update timer if required
initializeUpdateTimer();
@ -286,3 +294,84 @@ void Fetcher::checkUpdateTimer()
qCDebug(kastsFetcher) << "new auto feed update trigger set to" << m_updateTriggerTime;
}
}
void Fetcher::setNetworkProxy()
{
SettingsManager *settings = SettingsManager::self();
QNetworkProxy proxy;
// define network proxy environment variable
// this is needed for the audio backends which don't obey qt's settings
QByteArray appProxy;
if (!settings->proxyUser().isEmpty()) {
appProxy += QUrl::toPercentEncoding(settings->proxyUser());
if (!settings->proxyPassword().isEmpty()) {
appProxy += ":" + QUrl::toPercentEncoding(settings->proxyPassword());
}
appProxy += "@";
}
appProxy += settings->proxyHost().toLocal8Bit() + ":" + QByteArray::number(settings->proxyPort());
// type match to ProxyType from config.ksettings
switch (settings->proxyType()) {
case 1: // No Proxy
proxy.setType(QNetworkProxy::NoProxy);
QNetworkProxy::setApplicationProxy(proxy);
// also reset environment variables if they have been set
qunsetenv("http_proxy");
qunsetenv("https_proxy");
break;
case 2: // HTTP
proxy.setType(QNetworkProxy::HttpProxy);
proxy.setHostName(settings->proxyHost());
proxy.setPort(settings->proxyPort());
proxy.setUser(settings->proxyUser());
proxy.setPassword(settings->proxyPassword());
QNetworkProxy::setApplicationProxy(proxy);
// also set it through environment variables for the audio backends
appProxy.prepend("http://");
qputenv("http_proxy", appProxy);
qputenv("https_proxy", appProxy);
qCDebug(kastsFetcher) << "appProxy environment variable" << appProxy;
break;
case 3: // SOCKS 5
proxy.setType(QNetworkProxy::Socks5Proxy);
proxy.setHostName(settings->proxyHost());
proxy.setPort(settings->proxyPort());
proxy.setUser(settings->proxyUser());
proxy.setPassword(settings->proxyPassword());
QNetworkProxy::setApplicationProxy(proxy);
// also set it through environment variables for the audio backends
appProxy.prepend("socks5://");
qputenv("http_proxy", appProxy);
qputenv("https_proxy", appProxy);
qCDebug(kastsFetcher) << "appProxy environment variable" << appProxy;
break;
case 0: // System Default
default:
QNetworkProxyFactory::setUseSystemConfiguration(true);
// also reset env variables that might have been overridden
if (!m_systemHttpProxy.isEmpty()) {
qputenv("http_proxy", m_systemHttpProxy);
} else {
qunsetenv("http_proxy");
}
if (!m_systemHttpProxy.isEmpty()) {
qputenv("https_proxy", m_systemHttpsProxy);
} else {
qunsetenv("https_proxy");
}
break;
}
qCDebug(kastsFetcher) << "Network proxy set to:" << QNetworkProxy::applicationProxy();
}
bool Fetcher::isSystemProxyDefined()
{
return m_isSystemProxyDefined;
}

View File

@ -55,6 +55,9 @@ public:
void initializeUpdateTimer();
void checkUpdateTimer();
Q_INVOKABLE void setNetworkProxy();
Q_INVOKABLE bool isSystemProxyDefined();
Q_SIGNALS:
void entryAdded(const QString &feedurl, const QString &id);
void entryUpdated(const QString &feedurl, const QString &id);
@ -93,4 +96,8 @@ private:
const qint64 m_checkInterval = 10 * 60 * 1000; // trigger timer every 10 minutes
QTimer *m_updateTimer;
QDateTime m_updateTriggerTime;
QByteArray m_systemHttpProxy;
QByteArray m_systemHttpsProxy;
bool m_isSystemProxyDefined;
};

View File

@ -1,6 +1,7 @@
/**
* SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
* SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
* SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
@ -11,6 +12,7 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.labs.components as Addons
import org.kde.kasts
import org.kde.kasts.settings
@ -18,6 +20,9 @@ import org.kde.kasts.settings
FormCard.FormCardPage {
id: root
property int currentType: SettingsManager.proxyType
property bool proxyConfigChanged: false
FormCard.FormHeader {
Layout.fillWidth: true
title: i18nc("@title Form header for settings related to network connections", "Network")
@ -89,4 +94,130 @@ FormCard.FormCardPage {
}
}
}
FormCard.FormHeader {
title: i18nc("@title Form header for settings related to network proxies", "Network Proxy")
}
FormCard.FormCard {
FormCard.FormRadioDelegate {
text: i18nc("@option:radio Network proxy selection", "System Default")
checked: currentType === 0
enabled: !SettingsManager.isProxyTypeImmutable
onToggled: {
currentType = 0;
}
}
FormCard.FormRadioDelegate {
text: i18nc("@option:radio Network proxy selection", "No Proxy")
checked: currentType === 1
enabled: !SettingsManager.isProxyTypeImmutable
onToggled: {
currentType = 1;
}
}
FormCard.FormRadioDelegate {
text: i18nc("@option:radio Network proxy selection", "HTTP")
checked: currentType === 2
enabled: !SettingsManager.isProxyTypeImmutable
onToggled: {
currentType = 2;
}
}
FormCard.FormRadioDelegate {
text: i18nc("@option:radio Network proxy selection", "Socks5")
checked: currentType === 3
enabled: !SettingsManager.isProxyTypeImmutable
onToggled: {
currentType = 3;
}
}
FormCard.FormDelegateSeparator {
visible: currentType > 1
}
FormCard.FormTextFieldDelegate {
id: hostField
visible: currentType > 1
label: i18nc("@label:textbox Hostname for proxy config", "Host")
text: SettingsManager.proxyHost
inputMethodHints: Qt.ImhUrlCharactersOnly
onEditingFinished: {
proxyConfigChanged = true;
}
}
FormCard.FormSpinBoxDelegate {
id: portField
visible: currentType > 1
label: i18nc("@label:spinbox Port for proxy config", "Port")
value: SettingsManager.proxyPort
from: 0
to: 65536
textFromValue: (value, locale) => {
return value; // it will add a thousands separator if we don't do this, not sure why
}
onValueChanged: {
proxyConfigChanged = true;
}
}
FormCard.FormTextFieldDelegate {
id: userField
visible: currentType > 1
label: i18nc("@label:textbox Username for proxy config", "User")
text: SettingsManager.proxyUser
inputMethodHints: Qt.ImhUrlCharactersOnly
onEditingFinished: {
proxyConfigChanged = true;
}
}
FormCard.FormTextFieldDelegate {
id: passwordField
visible: currentType > 1
label: i18nc("@label:textbox Password for proxy config", "Password")
text: SettingsManager.proxyPassword
echoMode: TextInput.Password
inputMethodHints: Qt.ImhUrlCharactersOnly
onEditingFinished: {
proxyConfigChanged = true;
}
}
FormCard.FormDelegateSeparator {}
FormCard.FormTextDelegate {
trailing: Controls.Button {
icon.name: "dialog-ok"
text: i18nc("@action:button", "Apply")
enabled: currentType !== SettingsManager.proxyType || proxyConfigChanged
onClicked: {
SettingsManager.proxyType = currentType;
SettingsManager.proxyHost = hostField.text;
SettingsManager.proxyPort = portField.value;
SettingsManager.proxyUser = userField.text;
SettingsManager.proxyPassword = passwordField.text;
SettingsManager.save();
proxyConfigChanged = false;
Fetcher.setNetworkProxy();
}
}
}
}
footer: Addons.Banner {
Layout.fillWidth: true
type: Kirigami.MessageType.Warning
visible: currentType < 2 && Fetcher.isSystemProxyDefined()
text: i18nc("@info:status Warning message related to app proxy settings", "Your system level or app level proxy settings might be ignored by the audio backend when streaming audio. The settings should still be honored by all other network related actions, including downloading episodes.")
}
Component.onCompleted: {
proxyConfigChanged = false;
}
}

View File

@ -237,4 +237,37 @@
<default></default>
</entry>
</group>
<group name="NetworkProxy">
<entry name="ProxyType" type="Enum">
<label>The type of proxy used by the application</label>
<choices>
<choice name="System">
<label>System Default</label>
</choice>
<choice name="HTTP">
<label>HTTP</label>
</choice>
<choice name="Socks5">
<label>Socks5</label>
</choice>
<default>System</default>
</choices>
</entry>
<entry name="ProxyHost" type="String">
<label>IP or hostname of the proxy</label>
<default>127.0.0.1</default>
</entry>
<entry name="ProxyPort" type="int">
<label>The port number of the proxy</label>
<default>1080</default>
</entry>
<entry name="ProxyUser" type="String">
<label>The user of the proxy</label>
<default></default>
</entry>
<entry name="ProxyPassword" type="Password">
<label>The password of the proxy</label>
<default></default>
</entry>
</group>
</kcfg>