Add proxy config with Socks5 and HTTP proxy support

Resolve https://invent.kde.org/network/neochat/-/issues/547

This patch attempts to add proxy config support to NeoChat so people could set a HTTP or Socks5 proxy for anonymity or privacy/censorship-circumvention reason.

Currently this will only allows setting proxy type, host and port (which should be usable for most of the cases). Settings in that page needs to be applied by clicking the Apply button so the proxy setting won't accidentally get changed.

Proxy is disabled (use System Default option) by default.
This commit is contained in:
Gary Wang
2022-11-13 15:13:28 +00:00
committed by Tobias Fella
parent 7a2c4f6c71
commit 6ecc18d985
8 changed files with 162 additions and 1 deletions

View File

@@ -140,6 +140,7 @@ if(ANDROID)
"edit-copy"
"gtk-quit"
"compass"
"computer"
"network-connect"
"list-remove-user"
"org.kde.neochat"

View File

@@ -18,6 +18,7 @@
#include <QFileInfo>
#include <QGuiApplication>
#include <QImageReader>
#include <QNetworkProxy>
#include <QQuickItem>
#include <QQuickTextDocument>
#include <QQuickWindow>
@@ -65,6 +66,8 @@ Controller::Controller(QObject *parent)
Connection::setRoomType<NeoChatRoom>();
Connection::setUserType<NeoChatUser>();
setApplicationProxy();
#ifndef Q_OS_ANDROID
if (NeoChatConfig::self()->systemTray()) {
m_trayIcon = new TrayIcon(this);
@@ -784,3 +787,29 @@ void Controller::forceRefreshTextDocument(QQuickTextDocument *textDocument, QQui
// HACK: Workaround bug QTBUG 93281
connect(textDocument->textDocument(), SIGNAL(imagesLoaded()), item, SLOT(updateWholeDocument()));
}
void Controller::setApplicationProxy()
{
NeoChatConfig *cfg = NeoChatConfig::self();
QNetworkProxy proxy;
// type match to ProxyType from neochatconfig.kcfg
switch (cfg->proxyType()) {
case 1: // HTTP
proxy.setType(QNetworkProxy::HttpProxy);
proxy.setHostName(cfg->proxyHost());
proxy.setPort(cfg->proxyPort());
break;
case 2: // SOCKS 5
proxy.setType(QNetworkProxy::Socks5Proxy);
proxy.setHostName(cfg->proxyHost());
proxy.setPort(cfg->proxyPort());
break;
case 0: // System Default
default:
// do nothing
break;
}
QNetworkProxy::setApplicationProxy(proxy);
}

View File

@@ -99,6 +99,8 @@ public:
Q_INVOKABLE void forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item);
Q_INVOKABLE void setApplicationProxy();
private:
explicit Controller(QObject *parent = nullptr);

View File

@@ -98,5 +98,30 @@
<default>true</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>
</group>
</kcfg>

View File

@@ -0,0 +1,97 @@
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later OR LicenseRef-KDE-Accepted-LGPL
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
Kirigami.ScrollablePage {
title: i18nc("@title:window", "General")
property int currentType
property bool proxyConfigChanged: false
ColumnLayout {
Kirigami.FormLayout {
Layout.fillWidth: true
QQC2.RadioButton {
Kirigami.FormData.label: i18n("Network Proxy")
text: i18n("System Default")
checked: currentType === 0
enabled: !Config.isProxyTypeImmutable
onToggled: {
currentType = 0
}
}
QQC2.RadioButton {
text: i18n("HTTP")
checked: currentType === 1
enabled: !Config.isProxyTypeImmutable
onToggled: {
currentType = 1
}
}
QQC2.RadioButton {
text: i18n("Socks5")
checked: currentType === 2
enabled: !Config.isProxyTypeImmutable
onToggled: {
currentType = 2
}
}
QQC2.TextField {
id: hostField
Kirigami.FormData.label: i18n("Proxy Host")
text: Config.proxyHost
inputMethodHints: Qt.ImhUrlCharactersOnly
onEditingFinished: {
proxyConfigChanged = true
}
}
QQC2.SpinBox {
id: portField
Kirigami.FormData.label: i18n("Proxy Port")
value: Config.proxyPort
from: 0
to: 65536
validator: IntValidator {bottom: from; top: to}
textFromValue: function(value, locale) {
return value // it will add a thousands separator if we don't do this, not sure why
}
onValueChanged: {
proxyConfigChanged = true
}
}
}
}
footer: QQC2.ToolBar {
height: visible ? implicitHeight : 0
contentItem: RowLayout {
Item {
Layout.fillWidth: true
}
QQC2.Button {
text: i18n("Apply")
enabled: currentType !== Config.proxyType || proxyConfigChanged
onClicked: {
Config.proxyType = currentType
Config.proxyHost = hostField.text
Config.proxyPort = portField.value
Config.save()
proxyConfigChanged = false
Controller.setApplicationProxy()
}
}
}
}
Component.onCompleted: {
currentType = Config.proxyType
}
}

View File

@@ -34,8 +34,13 @@ Kirigami.CategorizedSettings {
page: Qt.resolvedUrl("SonnetConfigPage.qml")
},
Kirigami.SettingAction {
text: i18n("Devices")
text: i18n("Network Proxy")
iconName: "network-connect"
page: Qt.resolvedUrl("NetworkProxyPage.qml")
},
Kirigami.SettingAction {
text: i18n("Devices")
iconName: "computer"
page: Qt.resolvedUrl("DevicesPage.qml")
},
Kirigami.SettingAction {

View File

@@ -83,5 +83,6 @@
<file alias="DevicesPage.qml">qml/Settings/DevicesPage.qml</file>
<file alias="About.qml">qml/Settings/About.qml</file>
<file alias="SonnetConfigPage.qml">qml/Settings/SonnetConfigPage.qml</file>
<file alias="NetworkProxyPage.qml">qml/Settings/NetworkProxyPage.qml</file>
</qresource>
</RCC>