Repository: shadowsocks/shadowsocks-qt5
Branch: master
Commit: 2692abea8043
Files: 56
Total size: 179.5 KB
Directory structure:
gitextract_jqfxs2wr/
├── .gitignore
├── .travis.yml
├── CMakeLists.txt
├── LICENSE
├── README.md
└── src/
├── CMakeLists.txt
├── confighelper.cpp
├── confighelper.h
├── connection.cpp
├── connection.h
├── connectionitem.cpp
├── connectionitem.h
├── connectiontablemodel.cpp
├── connectiontablemodel.h
├── editdialog.cpp
├── editdialog.h
├── editdialog.ui
├── i18n/
│ ├── ss-qt5_zh_CN.qm
│ ├── ss-qt5_zh_CN.ts
│ ├── ss-qt5_zh_TW.qm
│ └── ss-qt5_zh_TW.ts
├── icons/
│ ├── Breeze/
│ │ └── index.theme
│ └── shadowsocks-qt5.icns
├── icons.qrc
├── ip4validator.cpp
├── ip4validator.h
├── main.cpp
├── mainwindow.cpp
├── mainwindow.h
├── mainwindow.ui
├── portvalidator.cpp
├── portvalidator.h
├── qrcodecapturer.cpp
├── qrcodecapturer.h
├── qrwidget.cpp
├── qrwidget.h
├── settingsdialog.cpp
├── settingsdialog.h
├── settingsdialog.ui
├── shadowsocks-qt5.desktop
├── sharedialog.cpp
├── sharedialog.h
├── sharedialog.ui
├── sqprofile.cpp
├── sqprofile.h
├── ss-qt5.rc
├── ssvalidator.cpp
├── ssvalidator.h
├── statusnotifier.cpp
├── statusnotifier.h
├── translations.qrc
├── urihelper.cpp
├── urihelper.h
├── uriinputdialog.cpp
├── uriinputdialog.h
└── uriinputdialog.ui
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.user*
*.swp
.directory
build/
================================================
FILE: .travis.yml
================================================
sudo: required
dist: trusty
language: cpp
cache: apt
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:beineri/opt-qt562-trusty'
packages:
- g++-7
- cmake
- qt56base
- libbotan1.10-dev
- libqrencode-dev
- libzbar-dev
env:
- MATRIX_EVAL="CC=gcc-7 && CXX=g++-7"
branches:
only:
- 'master'
before_install:
- eval "${MATRIX_EVAL}"
# Use the latest libQtShadowsocks code
- git clone https://github.com/shadowsocks/libQtShadowsocks.git
- mkdir libQtShadowsocks/build
- cd libQtShadowsocks/build
- cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DQt5Core_DIR="/opt/qt56/lib/cmake/Qt5Core" -DQt5Network_DIR="/opt/qt56/lib/cmake/Qt5Network" -DQt5Test_DIR="/opt/qt56/lib/cmake/Qt5Test"
- make
- sudo make install
- cd -
script:
- mkdir build && cd build
- cmake .. -DQt5Core_DIR="/opt/qt56/lib/cmake/Qt5Core" -DQt5Network_DIR="/opt/qt56/lib/cmake/Qt5Network" -DQt5Gui_DIR="/opt/qt56/lib/cmake/Qt5Gui" -DQt5Widgets_DIR="/opt/qt56/lib/cmake/Qt5Widgets" -DQt5DBus_DIR="/opt/qt56/lib/cmake/Qt5DBus"
- make
================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.1)
project(Shadowsocks-Qt5
VERSION 3.0.1
LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_package(Qt5Core)
find_package(Qt5Gui)
find_package(Qt5Widgets)
find_package(Qt5Network)
if(UNIX AND NOT APPLE)
find_package(Qt5DBus)
endif()
find_package(PkgConfig)
pkg_check_modules(QSS REQUIRED QtShadowsocks>=2.0.0)
find_library(QSS_LIBRARY_VAR
NAMES ${QSS_LIBRARIES}
HINTS ${QSS_LIBRARY_DIRS} ${QSS_LIBDIR})
pkg_check_modules(QRENCODE REQUIRED libqrencode)
find_library(QRENCODE_LIBRARY_VAR
NAMES ${QRENCODE_LIBRARIES}
HINTS ${QRENCODE_LIBRARY_DIRS} ${QRENCODE_LIBDIR})
pkg_check_modules(ZBAR REQUIRED zbar)
find_library(ZBAR_LIBRARY_VAR
NAMES ${ZBAR_LIBRARIES}
HINTS ${ZBAR_LIBRARY_DIRS} ${ZBAR_LIBDIR})
if(WIN32 OR APPLE)
add_definitions(-DFD_SETSIZE=1024)
endif()
add_subdirectory(src)
================================================
FILE: LICENSE
================================================
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
================================================
FILE: README.md
================================================
Shadowsocks-Qt5
===============
**This project is no longer being maintained**
[](https://travis-ci.org/shadowsocks/shadowsocks-qt5)
Please check [project's wiki](https://github.com/shadowsocks/shadowsocks-qt5/wiki) for "how-tos".
Introduction
------------
Shadowsocks-Qt5 is a native and cross-platform [shadowsocks](http://shadowsocks.org) GUI client with advanced features.
Features
--------
- Shadowsocks-Qt5 is written in C++ with Qt 5.
- Support traffic statistics
- Support server latency (lag) test
- Use multiple profiles simultaneously
- `config.ini` is located under `~/.config/shadowsocks-qt5/` on \*nix platforms, or under the application's directory on Windows.
LICENSE
-------

Copyright © 2014-2017 Symeon Huang
This project is licensed under version 3 of the GNU Lesser General Public License.
================================================
FILE: src/CMakeLists.txt
================================================
set(APP_NAME "ss-qt5")
add_definitions(-DAPP_VERSION="${PROJECT_VERSION}")
set(SOURCE
icons.qrc
translations.qrc
main.cpp
mainwindow.cpp
ip4validator.cpp
portvalidator.cpp
ssvalidator.cpp
qrwidget.cpp
sharedialog.cpp
editdialog.cpp
connection.cpp
confighelper.cpp
urihelper.cpp
uriinputdialog.cpp
sqprofile.cpp
settingsdialog.cpp
statusnotifier.cpp
connectiontablemodel.cpp
connectionitem.cpp
qrcodecapturer.cpp
)
if (WIN32)
list(APPEND SOURCE ss-qt5.rc)
endif()
add_executable(${APP_NAME} WIN32 ${SOURCE})
target_link_libraries(${APP_NAME}
PUBLIC Qt5::Core
PUBLIC Qt5::Gui
PUBLIC Qt5::Widgets
PUBLIC Qt5::Network
PRIVATE ${QSS_LIBRARY_VAR}
PRIVATE ${QRENCODE_LIBRARY_VAR}
PRIVATE ${ZBAR_LIBRARY_VAR})
target_include_directories(${APP_NAME}
PRIVATE ${QSS_INCLUDE_DIRS}
PRIVATE ${QRENCODE_INCLUDE_DIRS}
PRIVATE ${ZBAR_INCLUDE_DIRS})
if (UNIX AND NOT APPLE)
target_link_libraries(${APP_NAME} PRIVATE Qt5::DBus)
endif()
install(TARGETS ${APP_NAME}
RUNTIME DESTINATION bin)
if (UNIX)
install(FILES shadowsocks-qt5.desktop DESTINATION share/applications)
install(FILES icons/shadowsocks-qt5.png DESTINATION share/icons/hicolor/512x512/apps)
endif()
================================================
FILE: src/confighelper.cpp
================================================
#include "confighelper.h"
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QJsonParseError>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
ConfigHelper::ConfigHelper(const QString &configuration, QObject *parent) :
QObject(parent),
configFile(configuration)
{
settings = new QSettings(configFile, QSettings::IniFormat, this);
readGeneralSettings();
}
const QString ConfigHelper::profilePrefix = "Profile";
void ConfigHelper::save(const ConnectionTableModel &model)
{
int size = model.rowCount();
settings->beginWriteArray(profilePrefix);
for (int i = 0; i < size; ++i) {
settings->setArrayIndex(i);
Connection *con = model.getItem(i)->getConnection();
QVariant value = QVariant::fromValue<SQProfile>(con->getProfile());
settings->setValue("SQProfile", value);
}
settings->endArray();
settings->setValue("ToolbarStyle", QVariant(toolbarStyle));
settings->setValue("HideWindowOnStartup", QVariant(hideWindowOnStartup));
settings->setValue("StartAtLogin", QVariant(startAtLogin));
settings->setValue("OnlyOneInstance", QVariant(onlyOneInstace));
settings->setValue("ShowToolbar", QVariant(showToolbar));
settings->setValue("ShowFilterBar", QVariant(showFilterBar));
settings->setValue("NativeMenuBar", QVariant(nativeMenuBar));
settings->setValue("ConfigVersion", QVariant(2.6));
}
void ConfigHelper::importGuiConfigJson(ConnectionTableModel *model, const QString &file)
{
QFile JSONFile(file);
JSONFile.open(QIODevice::ReadOnly | QIODevice::Text);
if (!JSONFile.isOpen()) {
qCritical() << "Error: cannot open " << file;
return;
}
if(!JSONFile.isReadable()) {
qCritical() << "Error: cannot read " << file;
return;
}
QJsonParseError pe;
QJsonDocument JSONDoc = QJsonDocument::fromJson(JSONFile.readAll(), &pe);
JSONFile.close();
if (pe.error != QJsonParseError::NoError) {
qCritical() << pe.errorString();
}
if (JSONDoc.isEmpty()) {
qCritical() << "JSON Document" << file << "is empty!";
return;
}
QJsonObject JSONObj = JSONDoc.object();
QJsonArray CONFArray = JSONObj["configs"].toArray();
if (CONFArray.isEmpty()) {
qWarning() << "configs in " << file << " is empty.";
return;
}
for (QJsonArray::iterator it = CONFArray.begin(); it != CONFArray.end(); ++it) {
QJsonObject json = (*it).toObject();
SQProfile p;
if (!json["server_port"].isString()) {
/*
* shadowsocks-csharp uses integers to store ports directly.
*/
p.name = json["remarks"].toString();
p.serverPort = json["server_port"].toInt();
//shadowsocks-csharp has only global local port (all profiles use the same port)
p.localPort = JSONObj["localPort"].toInt();
if (JSONObj["shareOverLan"].toBool()) {
/*
* it can only configure share over LAN or not (also a global value)
* which is basically 0.0.0.0 or 127.0.0.1 (which is the default)
*/
p.localAddress = QString("0.0.0.0");
}
} else {
/*
* Otherwise, the gui-config is from legacy shadowsocks-qt5 (v0.x)
*/
p.name = json["profile"].toString();
p.serverPort = json["server_port"].toString().toUShort();
p.localAddress = json["local_address"].toString();
p.localPort = json["local_port"].toString().toUShort();
p.timeout = json["timeout"].toString().toInt();
}
p.serverAddress = json["server"].toString();
p.method = json["method"].toString();
p.password = json["password"].toString();
Connection *con = new Connection(p, this);
model->appendConnection(con);
}
}
void ConfigHelper::exportGuiConfigJson(const ConnectionTableModel &model, const QString &file)
{
QJsonArray confArray;
int size = model.rowCount();
for (int i = 0; i < size; ++i) {
Connection *con = model.getItem(i)->getConnection();
QJsonObject json;
json["remarks"] = QJsonValue(con->profile.name);
json["method"] = QJsonValue(con->profile.method.toLower());
json["password"] = QJsonValue(con->profile.password);
json["server_port"] = QJsonValue(con->profile.serverPort);
json["server"] = QJsonValue(con->profile.serverAddress);
confArray.append(QJsonValue(json));
}
QJsonObject JSONObj;
JSONObj["configs"] = QJsonValue(confArray);
JSONObj["localPort"] = QJsonValue(1080);
JSONObj["shareOverLan"] = QJsonValue(false);
QJsonDocument JSONDoc(JSONObj);
QFile JSONFile(file);
JSONFile.open(QIODevice::WriteOnly | QIODevice::Text);
if (!JSONFile.isOpen()) {
qCritical() << "Error: cannot open " << file;
return;
}
if(!JSONFile.isWritable()) {
qCritical() << "Error: cannot write into " << file;
return;
}
JSONFile.write(JSONDoc.toJson());
JSONFile.close();
}
Connection* ConfigHelper::configJsonToConnection(const QString &file)
{
QFile JSONFile(file);
JSONFile.open(QIODevice::ReadOnly | QIODevice::Text);
if (!JSONFile.isOpen()) {
qCritical() << "Error: cannot open " << file;
}
if(!JSONFile.isReadable()) {
qCritical() << "Error: cannot read " << file;
}
QJsonParseError pe;
QJsonDocument JSONDoc = QJsonDocument::fromJson(JSONFile.readAll(), &pe);
JSONFile.close();
if (pe.error != QJsonParseError::NoError) {
qCritical() << pe.errorString();
}
if (JSONDoc.isEmpty()) {
qCritical() << "JSON Document" << file << "is empty!";
return nullptr;
}
QJsonObject configObj = JSONDoc.object();
SQProfile p;
p.serverAddress = configObj["server"].toString();
p.serverPort = configObj["server_port"].toInt();
p.localAddress = configObj["local_address"].toString();
p.localPort = configObj["local_port"].toInt();
p.method = configObj["method"].toString();
p.password = configObj["password"].toString();
p.timeout = configObj["timeout"].toInt();
Connection *con = new Connection(p, this);
return con;
}
int ConfigHelper::getToolbarStyle() const
{
return toolbarStyle;
}
bool ConfigHelper::isHideWindowOnStartup() const
{
return hideWindowOnStartup;
}
bool ConfigHelper::isStartAtLogin() const
{
return startAtLogin;
}
bool ConfigHelper::isOnlyOneInstance() const
{
return onlyOneInstace;
}
bool ConfigHelper::isShowToolbar() const
{
return showToolbar;
}
bool ConfigHelper::isShowFilterBar() const
{
return showFilterBar;
}
bool ConfigHelper::isNativeMenuBar() const
{
return nativeMenuBar;
}
void ConfigHelper::setGeneralSettings(int ts, bool hide, bool sal, bool oneInstance, bool nativeMB)
{
if (toolbarStyle != ts) {
emit toolbarStyleChanged(static_cast<Qt::ToolButtonStyle>(ts));
}
toolbarStyle = ts;
hideWindowOnStartup = hide;
startAtLogin = sal;
onlyOneInstace = oneInstance;
nativeMenuBar = nativeMB;
}
void ConfigHelper::setShowToolbar(bool show)
{
showToolbar = show;
}
void ConfigHelper::setShowFilterBar(bool show)
{
showFilterBar = show;
}
void ConfigHelper::read(ConnectionTableModel *model)
{
qreal configVer = settings->value("ConfigVersion", QVariant(2.4)).toReal();
int size = settings->beginReadArray(profilePrefix);
for (int i = 0; i < size; ++i) {
settings->setArrayIndex(i);
QVariant value = settings->value("SQProfile");
SQProfile profile = value.value<SQProfile>();
checkProfileDataUsageReset(profile);
Connection *con = new Connection(profile, this);
model->appendConnection(con);
}
settings->endArray();
readGeneralSettings();
}
void ConfigHelper::readGeneralSettings()
{
toolbarStyle = settings->value("ToolbarStyle", QVariant(4)).toInt();
startAtLogin = settings->value("StartAtLogin").toBool();
hideWindowOnStartup = settings->value("HideWindowOnStartup").toBool();
onlyOneInstace = settings->value("OnlyOneInstance", QVariant(true)).toBool();
showToolbar = settings->value("ShowToolbar", QVariant(true)).toBool();
showFilterBar = settings->value("ShowFilterBar", QVariant(true)).toBool();
nativeMenuBar = settings->value("NativeMenuBar", QVariant(false)).toBool();
}
void ConfigHelper::checkProfileDataUsageReset(SQProfile &profile)
{
QDate currentDate = QDate::currentDate();
if (profile.nextResetDate.isNull()){//invalid if the config.ini is old
//the default reset day is 1 of every month
profile.nextResetDate = QDate(currentDate.year(), currentDate.month(), 1);
qDebug() << "config.ini upgraded from old version";
profile.totalUsage += profile.currentUsage;//we used to use sent and received
}
if (profile.nextResetDate < currentDate) {//not <= because that'd casue multiple reset on this day
profile.currentUsage = 0;
while (profile.nextResetDate <= currentDate) {
profile.nextResetDate = profile.nextResetDate.addMonths(1);
}
}
}
void ConfigHelper::startAllAutoStart(const ConnectionTableModel& model)
{
int size = model.rowCount();
for (int i = 0; i < size; ++i) {
Connection *con = model.getItem(i)->getConnection();
if (con->profile.autoStart) {
con->start();
}
}
}
void ConfigHelper::setStartAtLogin()
{
QString applicationName = "Shadowsocks-Qt5";
QString applicationFilePath = QDir::toNativeSeparators(QCoreApplication::applicationFilePath());
#if defined(Q_OS_WIN)
QSettings settings("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat);
#elif defined(Q_OS_LINUX)
QFile file(QDir::homePath() + "/.config/autostart/shadowsocks-qt5.desktop");
QString fileContent(
"[Desktop Entry]\n"
"Name=%1\n"
"Exec=%2\n"
"Type=Application\n"
"Terminal=false\n"
"X-GNOME-Autostart-enabled=true\n");
#elif defined(Q_OS_MAC)
QFile file(QDir::homePath() + "/Library/LaunchAgents/org.shadowsocks.shadowsocks-qt5.launcher.plist");
QString fileContent(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
"<plist version=\"1.0\">\n"
"<dict>\n"
" <key>Label</key>\n"
" <string>org.shadowsocks.shadowsocks-qt5.launcher</string>\n"
" <key>LimitLoadToSessionType</key>\n"
" <string>Aqua</string>\n"
" <key>ProgramArguments</key>\n"
" <array>\n"
" <string>%2</string>\n"
" </array>\n"
" <key>RunAtLoad</key>\n"
" <true/>\n"
" <key>StandardErrorPath</key>\n"
" <string>/dev/null</string>\n"
" <key>StandardOutPath</key>\n"
" <string>/dev/null</string>\n"
"</dict>\n"
"</plist>\n");
#else
QFile file;
QString fileContent;
#endif
if (this->isStartAtLogin()) {
// Create start up item
#if defined(Q_OS_WIN)
settings.setValue(applicationName, applicationFilePath);
#else
fileContent.replace("%1", applicationName);
fileContent.replace("%2", applicationFilePath);
if ( file.open(QIODevice::WriteOnly) ) {
file.write(fileContent.toUtf8());
file.close();
}
#endif
} else {
// Delete start up item
#if defined(Q_OS_WIN)
settings.remove(applicationName);
#else
if ( file.exists() ) {
file.remove();
}
#endif
}
}
QByteArray ConfigHelper::getMainWindowGeometry() const
{
return settings->value("MainWindowGeometry").toByteArray();
}
QByteArray ConfigHelper::getMainWindowState() const
{
return settings->value("MainWindowState").toByteArray();
}
QByteArray ConfigHelper::getTableGeometry() const
{
return settings->value("MainTableGeometry").toByteArray();
}
QByteArray ConfigHelper::getTableState() const
{
return settings->value("MainTableState").toByteArray();
}
void ConfigHelper::setMainWindowGeometry(const QByteArray &geometry)
{
settings->setValue("MainWindowGeometry", QVariant(geometry));
}
void ConfigHelper::setMainWindowState(const QByteArray &state)
{
settings->setValue("MainWindowState", QVariant(state));
}
void ConfigHelper::setTableGeometry(const QByteArray &geometry)
{
settings->setValue("MainTableGeometry", QVariant(geometry));
}
void ConfigHelper::setTableState(const QByteArray &state)
{
settings->setValue("MainTableState", QVariant(state));
}
================================================
FILE: src/confighelper.h
================================================
/*
* Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef CONFIGHELPER_H
#define CONFIGHELPER_H
#include <QSettings>
#include "connectiontablemodel.h"
#include "connection.h"
class ConfigHelper : public QObject
{
Q_OBJECT
public:
/*
* Construct a ConfigHelper object using specified configuration file
* This constructor will call readGeneralSettings().
*/
explicit ConfigHelper(const QString &configuration, QObject *parent = nullptr);
/*
* Call read() function to read all connection profiles into
* specified ConnectionTableModel.
* This function also calls readGeneralSettings().
*/
void read(ConnectionTableModel *model);
/*
* readGeneralSettings() only reads General settings and store them into
* member variables.
*/
void readGeneralSettings();
void save(const ConnectionTableModel &model);
void importGuiConfigJson(ConnectionTableModel *model, const QString &file);
//the format is only compatible with shadowsocks-csharp (shadowsocks-windows)
void exportGuiConfigJson(const ConnectionTableModel& model, const QString &file);
Connection* configJsonToConnection(const QString &file);
//start those connections marked as auto-start
void startAllAutoStart(const ConnectionTableModel& model);
//create or delete start up item for shadowsocks-qt5
void setStartAtLogin();
/* some functions used to communicate with SettingsDialog */
int getToolbarStyle() const;
bool isHideWindowOnStartup() const;
bool isStartAtLogin() const;
bool isOnlyOneInstance() const;
bool isShowToolbar() const;
bool isShowFilterBar() const;
bool isNativeMenuBar() const;
void setGeneralSettings(int ts, bool hide, bool automaticStartUp, bool oneInstance, bool nativeMB);
void setMainWindowGeometry(const QByteArray &geometry);
void setMainWindowState(const QByteArray &state);
void setTableGeometry(const QByteArray &geometry);
void setTableState(const QByteArray &state);
QByteArray getMainWindowGeometry() const;
QByteArray getMainWindowState() const;
QByteArray getTableGeometry() const;
QByteArray getTableState() const;
public slots:
void setShowToolbar(bool show);
void setShowFilterBar(bool show);
signals:
void toolbarStyleChanged(const Qt::ToolButtonStyle);
private:
int toolbarStyle;
bool hideWindowOnStartup;
bool startAtLogin;
bool onlyOneInstace;
bool showToolbar;
bool showFilterBar;
bool nativeMenuBar;
QSettings *settings;
QString configFile;
void checkProfileDataUsageReset(SQProfile &profile);
static const QString profilePrefix;
};
#endif // CONFIGHELPER_H
================================================
FILE: src/connection.cpp
================================================
#include "connection.h"
#include "ssvalidator.h"
#include <QHostInfo>
#include <QHostAddress>
Connection::Connection(QObject *parent) :
QObject(parent),
running(false)
{}
Connection::Connection(const SQProfile &_profile, QObject *parent) :
Connection(parent)
{
profile = _profile;
}
Connection::Connection(QString uri, QObject *parent) :
Connection(parent)
{
profile = SQProfile(uri);
}
Connection::~Connection()
{
stop();
}
const SQProfile& Connection::getProfile() const
{
return profile;
}
const QString& Connection::getName() const
{
return profile.name;
}
QByteArray Connection::getURI() const
{
std::string uri = profile.toProfile().toUriSip002();
return QByteArray(uri.data(), uri.length());
}
bool Connection::isValid() const
{
if (profile.serverAddress.isEmpty() || profile.localAddress.isEmpty() || profile.timeout < 1 || !SSValidator::validateMethod(profile.method)) {
return false;
}
else {
return true;
}
}
const bool &Connection::isRunning() const
{
return running;
}
void Connection::latencyTest()
{
QHostAddress serverAddr(profile.serverAddress);
if (serverAddr.isNull()) {
QHostInfo::lookupHost(profile.serverAddress, this, SLOT(onServerAddressLookedUp(QHostInfo)));
} else {
testAddressLatency(serverAddr);
}
}
void Connection::start()
{
profile.lastTime = QDateTime::currentDateTime();
//perform a latency test if the latency is unknown
if (profile.latency == SQProfile::LATENCY_UNKNOWN) {
latencyTest();
}
controller = std::make_unique<QSS::Controller>(profile.toProfile(), true, false);
connect(controller.get(), &QSS::Controller::runningStateChanged, [&](bool run){
running = run;
emit stateChanged(run);
});
connect(controller.get(), &QSS::Controller::tcpLatencyAvailable, this, &Connection::onLatencyAvailable);
connect(controller.get(), &QSS::Controller::newBytesReceived, this, &Connection::onNewBytesTransmitted);
connect(controller.get(), &QSS::Controller::newBytesSent, this, &Connection::onNewBytesTransmitted);
if (!controller->start()) {
emit startFailed();
}
}
void Connection::stop()
{
if (running) {
controller.reset();
}
}
void Connection::testAddressLatency(const QHostAddress &addr)
{
QSS::AddressTester *addrTester = new QSS::AddressTester(addr, profile.serverPort, this);
connect(addrTester, &QSS::AddressTester::connectivityTestFinished, this, &Connection::onConnectivityTestFinished, Qt::QueuedConnection);
connect(addrTester, &QSS::AddressTester::lagTestFinished, this, &Connection::onLatencyAvailable, Qt::QueuedConnection);
QSS::Profile qProfile = profile.toProfile();
addrTester->startConnectivityTest(qProfile.method(), qProfile.password());
}
void Connection::onNewBytesTransmitted(const quint64 &b)
{
profile.currentUsage += b;
profile.totalUsage += b;
emit dataUsageChanged(profile.currentUsage, profile.totalUsage);
}
void Connection::onServerAddressLookedUp(const QHostInfo &host)
{
if (host.error() == QHostInfo::NoError) {
testAddressLatency(host.addresses().first());
} else {
onLatencyAvailable(SQProfile::LATENCY_ERROR);
}
}
void Connection::onLatencyAvailable(const int latency)
{
profile.latency = latency;
emit latencyAvailable(latency);
}
void Connection::onConnectivityTestFinished(bool con)
{
QSS::AddressTester* tester = qobject_cast<QSS::AddressTester*>(sender());
if (!con) {
disconnect(tester, &QSS::AddressTester::lagTestFinished, this, &Connection::onLatencyAvailable);
this->onLatencyAvailable(SQProfile::LATENCY_ERROR);
qWarning("Internet connectivity test failed. Please check the connection's profile and your firewall settings.");
}
tester->deleteLater();
}
================================================
FILE: src/connection.h
================================================
/*
* Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef CONNECTION_H
#define CONNECTION_H
#include <QObject>
#include <QtShadowsocks>
#include <memory>
#include "sqprofile.h"
class Connection : public QObject
{
Q_OBJECT
public:
Connection(QObject *parent = 0);
Connection(const SQProfile &_profile, QObject *parent = 0);
Connection(QString uri, QObject *parent = 0);
~Connection();
Connection(const Connection&) = delete;
Connection(Connection&&) = default;
const SQProfile &getProfile() const;
const QString &getName() const;
QByteArray getURI() const;
bool isValid() const;
const bool &isRunning() const;
void latencyTest();
signals:
void stateChanged(bool started);
void latencyAvailable(const int);
void newLogAvailable(const QString &);
void dataUsageChanged(const quint64 ¤t, const quint64 &total);
void startFailed();
public slots:
void start();
void stop();
private:
std::unique_ptr<QSS::Controller> controller;
SQProfile profile;
bool running;
void testAddressLatency(const QHostAddress &addr);
friend class EditDialog;
friend class ConfigHelper;
friend class StatusDialog;
friend class ConnectionItem;
private slots:
void onNewBytesTransmitted(const quint64 &);
void onServerAddressLookedUp(const QHostInfo &host);
void onLatencyAvailable(const int);
void onConnectivityTestFinished(bool);
};
Q_DECLARE_METATYPE(Connection*)
#endif // CONNECTION_H
================================================
FILE: src/connectionitem.cpp
================================================
#include "connectionitem.h"
#include <QFont>
#include <cmath>
ConnectionItem::ConnectionItem(Connection *_con, QObject *parent) :
QObject(parent),
con(_con)
{
if (con) {
con->setParent(this);
connect(con, &Connection::stateChanged, this, &ConnectionItem::onConnectionStateChanged);
connect(con, &Connection::stateChanged, this, &ConnectionItem::stateChanged);
connect(con, &Connection::dataUsageChanged, this, &ConnectionItem::dataUsageChanged);
connect(con, &Connection::latencyAvailable, this, &ConnectionItem::onConnectionPingFinished);
connect(con, &Connection::latencyAvailable, this, &ConnectionItem::latencyChanged);
connect(con, &Connection::startFailed, this, &ConnectionItem::onStartFailed);
}
}
const QStringList ConnectionItem::bytesUnits = QStringList()
<< " B" << " KiB" << " MiB" << " GiB" << " TiB"
<< " PiB" << " EiB" << " ZiB" << " YiB";
int ConnectionItem::columnCount()
{
return 9;
}
QVariant ConnectionItem::data(int column, int role) const
{
if (!con) {
return QVariant();
}
if (role == Qt::DisplayRole || role == Qt::EditRole) {
switch (column) {
case 0://name
return QVariant(con->profile.name);
case 1://server
return QVariant(con->profile.serverAddress);
case 2://status
return con->isRunning() ? QVariant(tr("Connected"))
: QVariant(tr("Disconnected"));
case 3://latency
if (role == Qt::DisplayRole) {
return QVariant(convertLatencyToString(con->profile.latency));
} else {
return QVariant(con->profile.latency);
}
case 4://local port
return QVariant(con->profile.localPort);
case 5://data usage (term)
if (role == Qt::DisplayRole) {
return QVariant(convertBytesToHumanReadable(con->profile.currentUsage));
} else {
return QVariant(con->profile.currentUsage);
}
case 6://data usage (total)
if (role == Qt::DisplayRole) {
return QVariant(convertBytesToHumanReadable(con->profile.totalUsage));
} else {
return QVariant(con->profile.totalUsage);
}
case 7://reset date
if (role == Qt::DisplayRole) {
return QVariant(con->profile.nextResetDate.toString(Qt::SystemLocaleShortDate));
} else {
return QVariant(con->profile.nextResetDate);
}
case 8://last used
if (role == Qt::DisplayRole) {
return QVariant(con->profile.lastTime.toString(Qt::SystemLocaleShortDate));
} else {
return QVariant(con->profile.lastTime);
}
default:
return QVariant();
}
} else if (role == Qt::FontRole) {
QFont font;
font.setBold(con->isRunning());
return QVariant(font);
}
return QVariant();
}
QString ConnectionItem::convertLatencyToString(const int latency)
{
QString latencyStr;
switch (latency) {
case SQProfile::LATENCY_TIMEOUT:
latencyStr = tr("Timeout");
break;
case SQProfile::LATENCY_ERROR:
latencyStr = tr("Error");
break;
case SQProfile::LATENCY_UNKNOWN:
latencyStr = tr("Unknown");
break;
default:
if (latency >= 1000) {
latencyStr = QString::number(static_cast<double>(latency) / 1000.0)
+ QStringLiteral(" ") + tr("s");
} else {
latencyStr = QString::number(latency) + QStringLiteral(" ") + tr("ms");
}
}
return latencyStr;
}
QString ConnectionItem::convertBytesToHumanReadable(quint64 quot)
{
int unitId = 0;
quint64 rem = 0;
for (; quot > 1024; ++unitId) {
rem = quot % 1024;//the previous rem would be negligible
quot /= 1024;
}
double output = static_cast<double>(quot)
+ static_cast<double>(rem) / 1024.0;
return QString("%1 %2").arg(output, 0, 'f', 2).arg(bytesUnits.at(unitId));
}
void ConnectionItem::testLatency()
{
con->latencyTest();
}
Connection* ConnectionItem::getConnection()
{
return con;
}
void ConnectionItem::onConnectionStateChanged(bool running)
{
if (running) {
emit message(con->getName() + " " + tr("connected"));
} else {
emit message(con->getName() + " " + tr("disconnected"));
}
}
void ConnectionItem::onConnectionPingFinished(const int latency)
{
if (latency == SQProfile::LATENCY_TIMEOUT) {
emit message(con->getName() + " " + tr("timed out"));
} else if (latency == SQProfile::LATENCY_ERROR) {
emit message(con->getName() + " " + tr("latency test failed"));
}
}
void ConnectionItem::onStartFailed()
{
emit message(tr("Failed to start") + " " + con->getName());
}
================================================
FILE: src/connectionitem.h
================================================
/*
* Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef CONNECTIONITEM_H
#define CONNECTIONITEM_H
#include <QObject>
#include <QStringList>
#include "connection.h"
class ConnectionItem : public QObject
{
Q_OBJECT
public:
explicit ConnectionItem(Connection *_con, QObject *parent = nullptr);
static int columnCount();
QVariant data(int column, int role = Qt::DisplayRole) const;
Connection* getConnection();
void testLatency();
signals:
void message(const QString&);
void stateChanged(bool);
void dataUsageChanged(const quint64 ¤t, const quint64 &total);
void latencyChanged();
private:
Connection *con;
static QString convertLatencyToString(const int latency);
static QString convertBytesToHumanReadable(quint64 bytes);
static const QStringList bytesUnits;
private slots:
void onConnectionStateChanged(bool running);
void onConnectionPingFinished(const int latency);
void onStartFailed();
};
#endif // CONNECTIONITEM_H
================================================
FILE: src/connectiontablemodel.cpp
================================================
#include "connectiontablemodel.h"
ConnectionTableModel::ConnectionTableModel(QObject *parent) :
QAbstractTableModel(parent)
{}
ConnectionTableModel::~ConnectionTableModel()
{}
ConnectionItem *ConnectionTableModel::getItem(const int &row) const
{
return items.at(row);
}
int ConnectionTableModel::rowCount(const QModelIndex &) const
{
return items.count();
}
int ConnectionTableModel::columnCount(const QModelIndex &) const
{
return ConnectionItem::columnCount();
}
QVariant ConnectionTableModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
ConnectionItem *item = getItem(index.row());
return item->data(index.column(), role);
}
QVariant ConnectionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Vertical || role != Qt::DisplayRole) {
return QVariant();
}
switch (section) {
case 0:
return QVariant(tr("Name"));
case 1:
return QVariant(tr("Server"));
case 2:
return QVariant(tr("Status"));
case 3:
return QVariant(tr("Latency"));
case 4:
return QVariant(tr("Local Port"));
case 5:
return QVariant(tr("Term Usage"));
case 6:
return QVariant(tr("Total Usage"));
case 7:
return QVariant(tr("Reset Date"));
case 8:
return QVariant(tr("Last Used"));
default:
return QVariant();
}
}
QModelIndex ConnectionTableModel::index(int row, int column, const QModelIndex &) const
{
if (row < 0 || row >= items.size()) {
return QModelIndex();
} else {
ConnectionItem* item = items.at(row);
return createIndex(row, column, item);//column is ignored (all columns have the same effect)
}
}
bool ConnectionTableModel::removeRows(int row, int count, const QModelIndex &parent)
{
if (row < 0 || count <= 0 || count + row > items.count()) {
return false;
}
beginRemoveRows(parent, row, row + count - 1);
items.erase(items.begin() + row, items.begin() + row + count);
endRemoveRows();
return true;
}
bool ConnectionTableModel::move(int row, int target, const QModelIndex &parent)
{
if (row < 0 || row >= rowCount() || target < 0 || target >= rowCount() || row == target) {
return false;
}
//http://doc.qt.io/qt-5/qabstractitemmodel.html#beginMoveRows
int movTarget = target;
if (target - row > 0) {
movTarget++;
}
beginMoveRows(parent, row, row, parent, movTarget);
items.move(row, target);
endMoveRows();
return true;
}
bool ConnectionTableModel::appendConnection(Connection *con, const QModelIndex &parent)
{
ConnectionItem* newItem = new ConnectionItem(con, this);
connect(newItem, &ConnectionItem::message, this, &ConnectionTableModel::message);
connect(newItem, &ConnectionItem::stateChanged, this, &ConnectionTableModel::onConnectionStateChanged);
connect(newItem, &ConnectionItem::latencyChanged, this, &ConnectionTableModel::onConnectionLatencyChanged);
connect(newItem, &ConnectionItem::dataUsageChanged, this, &ConnectionTableModel::onConnectionDataUsageChanged);
beginInsertRows(parent, items.count(), items.count());
items.append(newItem);
endInsertRows();
return true;
}
void ConnectionTableModel::disconnectConnectionsAt(const QString &addr, quint16 port)
{
bool anyAddr = (addr.compare("0.0.0.0") == 0);
for (auto &i : items) {
Connection *con = i->getConnection();
if (con->isRunning() && con->getProfile().localPort == port) {
if ((con->getProfile().localAddress == addr) ||
(con->getProfile().localAddress.compare("0.0.0.0") == 0) ||
anyAddr) {
con->stop();
}
}
}
}
void ConnectionTableModel::testAllLatency()
{
for (auto &i : items) {
i->testLatency();
}
}
void ConnectionTableModel::onConnectionStateChanged(bool running)
{
ConnectionItem *item = qobject_cast<ConnectionItem*>(sender());
int row = items.indexOf(item);
emit dataChanged(this->index(row, 0),
this->index(row, ConnectionItem::columnCount() - 1));
emit rowStatusChanged(row, running);
}
void ConnectionTableModel::onConnectionLatencyChanged()
{
ConnectionItem *item = qobject_cast<ConnectionItem*>(sender());
int row = items.indexOf(item);
emit dataChanged(this->index(row, 3), this->index(row, 3));
}
void ConnectionTableModel::onConnectionDataUsageChanged()
{
ConnectionItem *item = qobject_cast<ConnectionItem*>(sender());
int row = items.indexOf(item);
emit dataChanged(this->index(row, 5), this->index(row, 6));
}
================================================
FILE: src/connectiontablemodel.h
================================================
/*
* Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef CONNECTIONTABLEMODEL_H
#define CONNECTIONTABLEMODEL_H
#include <QAbstractTableModel>
#include <QList>
#include "connectionitem.h"
class ConnectionTableModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit ConnectionTableModel(QObject *parent = nullptr);
~ConnectionTableModel();
ConnectionItem *getItem(const int &row) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
bool removeRows(int row, int count, const QModelIndex & parent = QModelIndex()) Q_DECL_OVERRIDE;
bool move(int row, int target, const QModelIndex &parent = QModelIndex());
bool appendConnection(Connection *con, const QModelIndex &parent = QModelIndex());
void disconnectConnectionsAt(const QString &addr, quint16 port);
public slots:
void testAllLatency();
signals:
void message(const QString &);
void rowStatusChanged(int row, bool running);
private:
QList<ConnectionItem*> items;
static QString convertLatencyToString(const int latency);
private slots:
void onConnectionStateChanged(bool running);
void onConnectionLatencyChanged();
void onConnectionDataUsageChanged();
};
#endif // CONNECTIONTABLEMODEL_H
================================================
FILE: src/editdialog.cpp
================================================
#include "editdialog.h"
#include "ui_editdialog.h"
#include "ssvalidator.h"
#include "ip4validator.h"
#include "portvalidator.h"
EditDialog::EditDialog(Connection *_connection, QWidget *parent) :
QDialog(parent),
ui(new Ui::EditDialog),
connection(_connection)
{
ui->setupUi(this);
/* initialisation and validator setup */
static const QStringList supportedMethodList =
SSValidator::supportedMethodList();
ui->encryptComboBox->addItems(supportedMethodList);
IP4Validator *addrValidator = new IP4Validator(this);
PortValidator *portValidator = new PortValidator(this);
ui->serverPortEdit->setValidator(portValidator);
ui->localPortEdit->setValidator(portValidator);
//Maybe we shouldn't validate local address using IPv4 format?
ui->localAddrEdit->setValidator(addrValidator);
ui->nameEdit->setText(connection->profile.name);
ui->serverAddrEdit->setText(connection->profile.serverAddress);
ui->serverPortEdit->setText(QString::number(connection->profile.serverPort));
ui->localAddrEdit->setText(connection->profile.localAddress);
ui->localPortEdit->setText(QString::number(connection->profile.localPort));
ui->httpRadioButton->setChecked(connection->profile.httpMode);
ui->pwdEdit->setText(connection->profile.password);
ui->encryptComboBox->setCurrentText(connection->profile.method.toUpper());
ui->timeoutSpinBox->setValue(connection->profile.timeout);
ui->resetDateEdit->setDate(connection->profile.nextResetDate);
ui->resetDateEdit->setMinimumDate(QDate::currentDate());
ui->autoStartCheckBox->setChecked(connection->profile.autoStart);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &EditDialog::save);
this->adjustSize();
}
EditDialog::~EditDialog()
{
delete ui;
}
void EditDialog::save()
{
connection->profile.name = ui->nameEdit->text();
connection->profile.serverAddress = ui->serverAddrEdit->text().trimmed();
connection->profile.serverPort = ui->serverPortEdit->text().toUShort();
connection->profile.localAddress = ui->localAddrEdit->text();
connection->profile.localPort = ui->localPortEdit->text().toUShort();
connection->profile.httpMode = ui->httpRadioButton->isChecked();
connection->profile.password = ui->pwdEdit->text();
connection->profile.method = ui->encryptComboBox->currentText();
connection->profile.timeout = ui->timeoutSpinBox->value();
connection->profile.nextResetDate = ui->resetDateEdit->date();
connection->profile.autoStart = ui->autoStartCheckBox->isChecked();
this->accept();
}
================================================
FILE: src/editdialog.h
================================================
/*
* Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef EDITDIALOG_H
#define EDITDIALOG_H
#include <QDialog>
#include "connection.h"
namespace Ui {
class EditDialog;
}
class EditDialog : public QDialog
{
Q_OBJECT
public:
explicit EditDialog(Connection *_connection, QWidget *parent = 0);
~EditDialog();
private:
Ui::EditDialog *ui;
Connection *connection;
private slots:
void save();
};
#endif // EDITDIALOG_H
================================================
FILE: src/editdialog.ui
================================================
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EditDialog</class>
<widget class="QDialog" name="EditDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>462</width>
<height>524</height>
</rect>
</property>
<property name="windowTitle">
<string>Profile Editor</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="editFrame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="labelAlignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>Profile Name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="nameEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="serverAddrLabel">
<property name="text">
<string>Server Address</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="serverAddrEdit"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="serverPortLabel">
<property name="text">
<string>Server Port</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="serverPortEdit">
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly|Qt::ImhPreferNumbers</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="pwdLabel">
<property name="text">
<string>Password</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="pwdEdit">
<property name="inputMethodHints">
<set>Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhSensitiveData</set>
</property>
<property name="echoMode">
<enum>QLineEdit::PasswordEchoOnEdit</enum>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="localAddrLabel">
<property name="text">
<string>Local Address</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="localAddrEdit"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="localPortLabel">
<property name="text">
<string>Local Port</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="localPortEdit">
<property name="inputMethodHints">
<set>Qt::ImhDigitsOnly|Qt::ImhPreferNumbers</set>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="localServerTypeLabel">
<property name="text">
<string>Local Server Type</string>
</property>
</widget>
</item>
<item row="6" column="1">
<layout class="QHBoxLayout" name="localTypeHorizontalLayout" stretch="1,1">
<item>
<widget class="QRadioButton" name="socks5RadioButton">
<property name="text">
<string notr="true">SOCKS&5</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">buttonGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="httpRadioButton">
<property name="text">
<string notr="true">HTTP(S)</string>
</property>
<attribute name="buttonGroup">
<string notr="true">buttonGroup</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item row="7" column="0">
<widget class="QLabel" name="encryptLabel">
<property name="text">
<string>Encryption Method</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QComboBox" name="encryptComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="timeoutLabel">
<property name="text">
<string>Timeout</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QSpinBox" name="timeoutSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>3600</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="value">
<number>60</number>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="resetDateLabel">
<property name="text">
<string>Reset Data Usage after</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QDateEdit" name="resetDateEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="calendarPopup">
<bool>true</bool>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="autoStartLabel">
<property name="text">
<string>Automation</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QCheckBox" name="autoStartCheckBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Auto connect on application start</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>nameEdit</tabstop>
<tabstop>serverAddrEdit</tabstop>
<tabstop>serverPortEdit</tabstop>
<tabstop>pwdEdit</tabstop>
<tabstop>localAddrEdit</tabstop>
<tabstop>localPortEdit</tabstop>
<tabstop>socks5RadioButton</tabstop>
<tabstop>httpRadioButton</tabstop>
<tabstop>encryptComboBox</tabstop>
<tabstop>timeoutSpinBox</tabstop>
<tabstop>resetDateEdit</tabstop>
<tabstop>autoStartCheckBox</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>EditDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="buttonGroup"/>
</buttongroups>
</ui>
================================================
FILE: src/i18n/ss-qt5_zh_CN.ts
================================================
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
<name>ConnectionItem</name>
<message>
<location filename="../connectionitem.cpp" line="41"/>
<source>Connected</source>
<translation>已连接</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="42"/>
<source>Disconnected</source>
<translation>未连接</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="92"/>
<source>Timeout</source>
<translation>超时</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="95"/>
<source>Error</source>
<translation>错误</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="98"/>
<source>Unknown</source>
<translation>未知</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="103"/>
<source>s</source>
<translation>秒</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="105"/>
<source>ms</source>
<translation>毫秒</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="137"/>
<source>connected</source>
<translation>已连接</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="139"/>
<source>disconnected</source>
<translation>已断开</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="146"/>
<source>timed out</source>
<translation>超时</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="148"/>
<source>latency test failed</source>
<translation>延迟测试失败</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="154"/>
<source>Failed to start</source>
<translation>无法启动</translation>
</message>
</context>
<context>
<name>ConnectionTableModel</name>
<message>
<location filename="../connectiontablemodel.cpp" line="43"/>
<source>Name</source>
<translation>名称</translation>
</message>
<message>
<location filename="../connectiontablemodel.cpp" line="45"/>
<source>Server</source>
<translation>服务器</translation>
</message>
<message>
<location filename="../connectiontablemodel.cpp" line="47"/>
<source>Status</source>
<translation>状态</translation>
</message>
<message>
<location filename="../connectiontablemodel.cpp" line="49"/>
<source>Latency</source>
<translation>延迟</translation>
</message>
<message>
<location filename="../connectiontablemodel.cpp" line="51"/>
<source>Local Port</source>
<translation>本地端口</translation>
</message>
<message>
<location filename="../connectiontablemodel.cpp" line="53"/>
<source>Term Usage</source>
<translation>本期用量</translation>
</message>
<message>
<location filename="../connectiontablemodel.cpp" line="55"/>
<source>Total Usage</source>
<translation>累计用量</translation>
</message>
<message>
<location filename="../connectiontablemodel.cpp" line="57"/>
<source>Reset Date</source>
<translation>重置日期</translation>
</message>
<message>
<location filename="../connectiontablemodel.cpp" line="59"/>
<source>Last Used</source>
<translation>上次使用</translation>
</message>
</context>
<context>
<name>EditDialog</name>
<message>
<location filename="../editdialog.ui" line="14"/>
<source>Profile Editor</source>
<translation>配置编辑器</translation>
</message>
<message>
<location filename="../editdialog.ui" line="107"/>
<source>Local Server Type</source>
<translation>本地服务器类型</translation>
</message>
<message>
<location filename="../editdialog.ui" line="158"/>
<source>Timeout</source>
<translation>超时</translation>
</message>
<message>
<location filename="../editdialog.ui" line="247"/>
<source>[Deprecated] Turn on one-time authentication and header verification (need server support)</source>
<translation>[弃用]开启一次验证和消息头验证(需要服务器支持)</translation>
</message>
<message>
<source>Turn on one-time authentication and header verification (need server support)</source>
<translation type="vanished">开启一次验证和消息头验证(需要服务器支持)</translation>
</message>
<message>
<location filename="../editdialog.ui" line="250"/>
<source>One-time authentication</source>
<translation>一次验证</translation>
</message>
<message>
<location filename="../editdialog.ui" line="141"/>
<source>Encryption Method</source>
<translation>加密方式</translation>
</message>
<message>
<location filename="../editdialog.ui" line="93"/>
<source>Local Port</source>
<translation>本地端口</translation>
</message>
<message>
<location filename="../editdialog.ui" line="83"/>
<source>Local Address</source>
<translation>本地地址</translation>
</message>
<message>
<location filename="../editdialog.ui" line="66"/>
<source>Password</source>
<translation>密钥</translation>
</message>
<message>
<location filename="../editdialog.ui" line="52"/>
<source>Server Port</source>
<translation>服务器端口</translation>
</message>
<message>
<location filename="../editdialog.ui" line="42"/>
<source>Server Address</source>
<translation>服务器地址</translation>
</message>
<message>
<location filename="../editdialog.ui" line="32"/>
<source>Profile Name</source>
<translation>配置名称</translation>
</message>
<message>
<location filename="../editdialog.ui" line="220"/>
<source>Auto connect on application start</source>
<translation>程序启动时自动连接</translation>
</message>
<message>
<location filename="../editdialog.ui" line="240"/>
<source>Debug</source>
<translation>调试</translation>
</message>
<message>
<location filename="../editdialog.ui" line="227"/>
<source>Log Level</source>
<translation>日志级别</translation>
</message>
<message>
<location filename="../editdialog.ui" line="207"/>
<source>Automation</source>
<translation>自动化</translation>
</message>
<message>
<location filename="../editdialog.ui" line="187"/>
<source>Reset Data Usage after</source>
<translation>重置数据流量</translation>
</message>
</context>
<context>
<name>LogDialog</name>
<message>
<location filename="../logdialog.ui" line="14"/>
<source>Log Viewer</source>
<translation>日志查看器</translation>
</message>
<message>
<location filename="../logdialog.ui" line="22"/>
<source>Save log as a plain text file</source>
<translation>保存日志为文本文件</translation>
</message>
<message>
<location filename="../logdialog.ui" line="25"/>
<source>Save As...</source>
<translation>保存为...</translation>
</message>
<message>
<location filename="../logdialog.ui" line="45"/>
<source>Clear</source>
<translation>清空</translation>
</message>
<message>
<location filename="../logdialog.cpp" line="34"/>
<source>Save Log As</source>
<translation>保存日志为</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.cpp" line="460"/>
<source>About</source>
<translation>关于</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="170"/>
<source>Import Connections from gui-config.json</source>
<translation>从gui-config.json导入连接</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="182"/>
<source>Export Connections as gui-config.json</source>
<translation>将所有连接信息导出为gui-config.json</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="207"/>
<location filename="../mainwindow.cpp" line="239"/>
<source>QR Code Not Found</source>
<translation>未找到二维码</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="208"/>
<location filename="../mainwindow.cpp" line="240"/>
<source>Can't find any QR code image that contains valid URI on your screen(s).</source>
<translation>无法在您的屏幕上找到任何包含有效URI的二维码图像。</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="231"/>
<source>Open QR Code Image File</source>
<translation>打开二维码图像文件</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="263"/>
<source>Open config.json</source>
<translation>打开 config.json</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="305"/>
<location filename="../mainwindow.cpp" line="319"/>
<source>Invalid</source>
<translation>无效</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="306"/>
<location filename="../mainwindow.cpp" line="320"/>
<source>The connection's profile is invalid!</source>
<translation>当前连接的配置无效!</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="20"/>
<source>Connection Manager</source>
<translation>连接编辑器</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="118"/>
<source>&Connection</source>
<translation>连接(&C)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="122"/>
<source>&Add</source>
<translation>添加(&A)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="153"/>
<source>Fi&le</source>
<translation>文件(&F)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="163"/>
<source>Settin&gs</source>
<translation>设置(&S)</translation>
</message>
<message>
<source>Help</source>
<translation type="vanished">帮助(&H)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="185"/>
<source>&Manually</source>
<translation>手动(&M)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="188"/>
<source>Add connection manually</source>
<translation>手动添加连接</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="202"/>
<source>&From QR Code Image File</source>
<translation>自二维码图像文件(&Q)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="205"/>
<source>From QR code image file</source>
<translation>自二维码图像文件</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="283"/>
<source>View &Log</source>
<translation>查看日志(&L)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="193"/>
<source>&Scan QR Code on Screen</source>
<translation>扫描屏幕上的二维码(&S)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="34"/>
<source>Input to filter</source>
<translation>输入以过滤</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="76"/>
<source>Show Toolbar</source>
<translation>显示工具栏</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="171"/>
<source>&Help</source>
<translation>帮助(&H)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="210"/>
<source>&URI</source>
<translation>&URI</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="213"/>
<source>Add connection from URI</source>
<translation>从 URI 添加连接</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="222"/>
<source>&Delete</source>
<translation>删除(&D)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="227"/>
<source>&Edit</source>
<translation>编辑(&E)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="232"/>
<source>&Connect</source>
<translation>连接(&C)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="237"/>
<source>D&isconnect</source>
<translation>断开连接(&D)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="246"/>
<source>&Quit</source>
<translation>退出(&Q)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="249"/>
<source>Ctrl+Q</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="258"/>
<source>&About</source>
<translation>关于(&A)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="263"/>
<source>About &Qt</source>
<translation>关于 &Qt</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="268"/>
<source>&General Settings</source>
<translation>常规设置(&G)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="273"/>
<source>&Share</source>
<translation>分享(&S)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="278"/>
<source>&Report Bug</source>
<translation>报告错误(&R)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="291"/>
<source>Test the latency of selected connection</source>
<translation>测试所选连接的延迟</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="296"/>
<source>Test All C&onnections Latency</source>
<translation>测试所有连接的延迟(&O)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="354"/>
<source>&Show Filter Bar</source>
<translation>显示过滤栏(&S)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="357"/>
<source>Ctrl+F</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="362"/>
<source>&Export as gui-config.json</source>
<translation>导出为gui-config.json (&E)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="367"/>
<source>Scan &QR Code using Capturer</source>
<translation>使用捕获器扫描二维码(&C)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="370"/>
<source>Scan QR Code using Capturer</source>
<translation>使用捕获器扫描二维码</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="375"/>
<source>&Force Connect</source>
<translation>强制连接(&F)</translation>
</message>
<message>
<source>Force Connect</source>
<translation type="vanished">强制连接</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="378"/>
<source>Connect to this connection and disconnect any connections currently using the same local port</source>
<translation>连接到该连接并断开占用了相同本地端口的连接</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="313"/>
<source>From &config.json</source>
<translation>自 &config.json</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="322"/>
<source>&Save Manually</source>
<translation>手动保存(&S)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="325"/>
<source>Ctrl+Shift+S</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="334"/>
<source>&Move Up</source>
<translation>上移(&U)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="343"/>
<source>Mo&ve Down</source>
<translation>下移(&V)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="301"/>
<source>&Import Connections from gui-config.json</source>
<translation>从 gui-config.json导入连接(&I)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="304"/>
<source>Import connections from old version configuration file</source>
<translation>从旧版配置文件导入连接</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="288"/>
<source>&Test Latency</source>
<translation>测试延迟(&T)</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<source>Failed to communicate with previously running instance of Shadowsocks-Qt5 (PID: %1). It might already crashed.</source>
<translation type="vanished">无法与先前运行的Shadowsocks-Qt5实例(PID: %1)通讯。可能已经崩溃了。</translation>
</message>
<message>
<source>Error</source>
<translation type="vanished">错误</translation>
</message>
<message>
<source>Another instance of Shadowsocks-Qt5 (PID: %1) is already running.</source>
<translation type="vanished">另一个 Shadowsocks-Qt5 (PID: %1) 的实例已经在运行了。</translation>
</message>
<message>
<location filename="../sqprofile.cpp" line="9"/>
<source>Unnamed Profile</source>
<translation>未命名配置</translation>
</message>
</context>
<context>
<name>QRCodeCapturer</name>
<message>
<source>QR Code Capturer</source>
<translation type="vanished">二维码捕获器</translation>
</message>
<message>
<location filename="../qrcodecapturer.cpp" line="24"/>
<source>QR Capturer</source>
<translation>二维码捕获器</translation>
</message>
</context>
<context>
<name>QRWidget</name>
<message>
<location filename="../qrwidget.cpp" line="37"/>
<source>Generating QR code failed.</source>
<translation>二维码生成失败。</translation>
</message>
</context>
<context>
<name>SettingsDialog</name>
<message>
<location filename="../settingsdialog.ui" line="14"/>
<source>General Settings</source>
<translation>常规设置</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="30"/>
<source>Toolbar Style</source>
<translation>工具栏风格</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="44"/>
<source>Icons Only</source>
<translation>仅图标</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="49"/>
<source>Text Only</source>
<translation>仅文本</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="54"/>
<source>Text Alongside Icons</source>
<translation>文本在图标侧</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="59"/>
<source>Text Under Icons</source>
<translation>文本在图标下</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="64"/>
<source>System Style</source>
<translation>系统风格</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="72"/>
<source>Allow only one instance running</source>
<translation>仅允许一个实例运行</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="79"/>
<source>Hide window on startup</source>
<translation>启动时隐藏窗口</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="86"/>
<source>Need to restart the application for this change to take effect</source>
<translation>需重启程序以生效</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="89"/>
<source>Use native menu bar</source>
<translation>使用原生菜单栏</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="96"/>
<source>Start at login</source>
<translation>登录时启动</translation>
</message>
</context>
<context>
<name>ShareDialog</name>
<message>
<location filename="../sharedialog.ui" line="20"/>
<source>Share Profile</source>
<translation>分享配置</translation>
</message>
<message>
<location filename="../sharedialog.ui" line="58"/>
<source>Save QR code as an Image file</source>
<translation>保存二维码为图像文件</translation>
</message>
<message>
<location filename="../sharedialog.cpp" line="27"/>
<source>Save QR Code</source>
<translation>保存二维码</translation>
</message>
</context>
<context>
<name>StatusNotifier</name>
<message>
<location filename="../statusnotifier.cpp" line="21"/>
<location filename="../statusnotifier.cpp" line="131"/>
<source>Minimise</source>
<translation>最小化</translation>
</message>
<message>
<location filename="../statusnotifier.cpp" line="24"/>
<source>Quit</source>
<translation>退出</translation>
</message>
<message>
<location filename="../statusnotifier.cpp" line="21"/>
<location filename="../statusnotifier.cpp" line="131"/>
<source>Restore</source>
<translation>恢复</translation>
</message>
</context>
<context>
<name>URIInputDialog</name>
<message>
<location filename="../uriinputdialog.ui" line="14"/>
<source>URI Input Dialog</source>
<translation>URI输入对话框</translation>
</message>
<message>
<location filename="../uriinputdialog.ui" line="20"/>
<source>Please input ss:// URI</source>
<translation>请输入 ss:// URI</translation>
</message>
</context>
</TS>
================================================
FILE: src/i18n/ss-qt5_zh_TW.ts
================================================
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_TW">
<context>
<name>ConnectionItem</name>
<message>
<location filename="../connectionitem.cpp" line="41"/>
<source>Connected</source>
<translation>已連線</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="42"/>
<source>Disconnected</source>
<translation>已斷線</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="92"/>
<source>Timeout</source>
<translation>逾時</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="95"/>
<source>Error</source>
<translation>錯誤</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="98"/>
<source>Unknown</source>
<translation>不明</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="103"/>
<source>s</source>
<translation>秒</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="105"/>
<source>ms</source>
<translation>毫秒</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="137"/>
<source>connected</source>
<translation>已連線</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="139"/>
<source>disconnected</source>
<translation>已斷線</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="146"/>
<source>timed out</source>
<translation>逾時</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="148"/>
<source>latency test failed</source>
<translation>延遲測試失敗</translation>
</message>
<message>
<location filename="../connectionitem.cpp" line="154"/>
<source>Failed to start</source>
<translation>啟動失敗</translation>
</message>
</context>
<context>
<name>ConnectionTableModel</name>
<message>
<location filename="../connectiontablemodel.cpp" line="43"/>
<source>Name</source>
<translation>名稱</translation>
</message>
<message>
<location filename="../connectiontablemodel.cpp" line="45"/>
<source>Server</source>
<translation>伺服器</translation>
</message>
<message>
<location filename="../connectiontablemodel.cpp" line="47"/>
<source>Status</source>
<translation>狀態</translation>
</message>
<message>
<location filename="../connectiontablemodel.cpp" line="49"/>
<source>Latency</source>
<translation>延遲</translation>
</message>
<message>
<location filename="../connectiontablemodel.cpp" line="51"/>
<source>Local Port</source>
<translation>本機埠</translation>
</message>
<message>
<location filename="../connectiontablemodel.cpp" line="53"/>
<source>Term Usage</source>
<translation>本期使用量</translation>
</message>
<message>
<location filename="../connectiontablemodel.cpp" line="55"/>
<source>Total Usage</source>
<translation>使用量總計</translation>
</message>
<message>
<location filename="../connectiontablemodel.cpp" line="57"/>
<source>Reset Date</source>
<translation>重設日期</translation>
</message>
<message>
<location filename="../connectiontablemodel.cpp" line="59"/>
<source>Last Used</source>
<translation>上次使用</translation>
</message>
</context>
<context>
<name>EditDialog</name>
<message>
<location filename="../editdialog.ui" line="14"/>
<source>Profile Editor</source>
<translation>設定檔編輯器</translation>
</message>
<message>
<location filename="../editdialog.ui" line="107"/>
<source>Local Server Type</source>
<translation>本機伺服器型別</translation>
</message>
<message>
<location filename="../editdialog.ui" line="158"/>
<source>Timeout</source>
<translation>逾時</translation>
</message>
<message>
<location filename="../editdialog.ui" line="247"/>
<source>[Deprecated] Turn on one-time authentication and header verification (need server support)</source>
<translation>[棄用]開啟單次驗證與標頭驗證(需要伺服器支援)</translation>
</message>
<message>
<source>Turn on one-time authentication and header verification (need server support)</source>
<translation type="vanished">開啟單次驗證與標頭驗證(需要伺服器支援)</translation>
</message>
<message>
<location filename="../editdialog.ui" line="250"/>
<source>One-time authentication</source>
<translation>單次驗證</translation>
</message>
<message>
<location filename="../editdialog.ui" line="141"/>
<source>Encryption Method</source>
<translation>加密方法</translation>
</message>
<message>
<location filename="../editdialog.ui" line="93"/>
<source>Local Port</source>
<translation>本機埠</translation>
</message>
<message>
<location filename="../editdialog.ui" line="83"/>
<source>Local Address</source>
<translation>本機位址</translation>
</message>
<message>
<location filename="../editdialog.ui" line="66"/>
<source>Password</source>
<translation>密碼</translation>
</message>
<message>
<location filename="../editdialog.ui" line="52"/>
<source>Server Port</source>
<translation>伺服器埠</translation>
</message>
<message>
<location filename="../editdialog.ui" line="42"/>
<source>Server Address</source>
<translation>伺服器位址</translation>
</message>
<message>
<location filename="../editdialog.ui" line="32"/>
<source>Profile Name</source>
<translation>設定檔名稱</translation>
</message>
<message>
<location filename="../editdialog.ui" line="220"/>
<source>Auto connect on application start</source>
<translation>程式啟動時自動連線</translation>
</message>
<message>
<location filename="../editdialog.ui" line="240"/>
<source>Debug</source>
<translation>偵錯</translation>
</message>
<message>
<location filename="../editdialog.ui" line="227"/>
<source>Log Level</source>
<translation>記錄檔等級</translation>
</message>
<message>
<location filename="../editdialog.ui" line="207"/>
<source>Automation</source>
<translation>自動化</translation>
</message>
<message>
<location filename="../editdialog.ui" line="187"/>
<source>Reset Data Usage after</source>
<translation>重設資料使用量之後</translation>
</message>
</context>
<context>
<name>LogDialog</name>
<message>
<location filename="../logdialog.ui" line="14"/>
<source>Log Viewer</source>
<translation>記錄檔檢視器</translation>
</message>
<message>
<location filename="../logdialog.ui" line="22"/>
<source>Save log as a plain text file</source>
<translation>儲存記錄檔為文字檔</translation>
</message>
<message>
<location filename="../logdialog.ui" line="25"/>
<source>Save As...</source>
<translation>另存新檔...</translation>
</message>
<message>
<location filename="../logdialog.ui" line="45"/>
<source>Clear</source>
<translation>清除</translation>
</message>
<message>
<location filename="../logdialog.cpp" line="34"/>
<source>Save Log As</source>
<translation>另存記錄檔</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.cpp" line="460"/>
<source>About</source>
<translation>關於</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="170"/>
<source>Import Connections from gui-config.json</source>
<translation>自 gui-config.json 匯入連線</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="182"/>
<source>Export Connections as gui-config.json</source>
<translation>匯出連線為 gui-config.json</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="207"/>
<location filename="../mainwindow.cpp" line="239"/>
<source>QR Code Not Found</source>
<translation>找不到 QR 碼</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="208"/>
<location filename="../mainwindow.cpp" line="240"/>
<source>Can't find any QR code image that contains valid URI on your screen(s).</source>
<translation>在你的螢幕上無法找到任何包含有效 URI 的 QR 碼圖片。</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="231"/>
<source>Open QR Code Image File</source>
<translation>開啟 QR 碼圖檔</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="263"/>
<source>Open config.json</source>
<translation>開啟 config.json</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="305"/>
<location filename="../mainwindow.cpp" line="319"/>
<source>Invalid</source>
<translation>無效</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="306"/>
<location filename="../mainwindow.cpp" line="320"/>
<source>The connection's profile is invalid!</source>
<translation>此連線的設定檔無效!</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="20"/>
<source>Connection Manager</source>
<translation>連線管理員</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="118"/>
<source>&Connection</source>
<translation>&連線</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="122"/>
<source>&Add</source>
<translation>&新增</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="153"/>
<source>Fi&le</source>
<translation>&檔案</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="163"/>
<source>Settin&gs</source>
<translation>&設定</translation>
</message>
<message>
<source>Help</source>
<translation type="vanished">說明</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="185"/>
<source>&Manually</source>
<translation>&手動</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="188"/>
<source>Add connection manually</source>
<translation>手動新增連線</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="202"/>
<source>&From QR Code Image File</source>
<translation>&來自 QR 碼圖檔</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="205"/>
<source>From QR code image file</source>
<translation>來自 QR 碼圖檔</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="283"/>
<source>View &Log</source>
<translation>&檢視記錄檔</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="193"/>
<source>&Scan QR Code on Screen</source>
<translation>&掃描螢幕上的 QR 碼</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="34"/>
<source>Input to filter</source>
<translation>輸入以篩選</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="76"/>
<source>Show Toolbar</source>
<translation>顯示工具列</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="171"/>
<source>&Help</source>
<translation>說明(&H)</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="210"/>
<source>&URI</source>
<translation>&URI</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="213"/>
<source>Add connection from URI</source>
<translation>自 URI 新增連線</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="222"/>
<source>&Delete</source>
<translation>&刪除</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="227"/>
<source>&Edit</source>
<translation>&編輯</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="232"/>
<source>&Connect</source>
<translation>&連線</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="237"/>
<source>D&isconnect</source>
<translation>&中斷連線</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="246"/>
<source>&Quit</source>
<translation>&結束</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="249"/>
<source>Ctrl+Q</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="258"/>
<source>&About</source>
<translation>&關於</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="263"/>
<source>About &Qt</source>
<translation>關於 &Qt</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="268"/>
<source>&General Settings</source>
<translation>&一般設定</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="273"/>
<source>&Share</source>
<translation>&分享</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="278"/>
<source>&Report Bug</source>
<translation>&回報 Bug</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="291"/>
<source>Test the latency of selected connection</source>
<translation>測試已選連線的延遲</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="296"/>
<source>Test All C&onnections Latency</source>
<translation>測試&所有連線的延遲</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="354"/>
<source>&Show Filter Bar</source>
<translation>&顯示篩選列</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="357"/>
<source>Ctrl+F</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="362"/>
<source>&Export as gui-config.json</source>
<translation>&匯出為 gui-config.json</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="367"/>
<source>Scan &QR Code using Capturer</source>
<translation>&使用捕捉器掃描 QR 碼</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="370"/>
<source>Scan QR Code using Capturer</source>
<translation>使用捕捉器掃描 QR 碼</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="375"/>
<source>&Force Connect</source>
<translation>強迫連線(&E)</translation>
</message>
<message>
<source>Force Connect</source>
<translation type="vanished">強迫連線</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="378"/>
<source>Connect to this connection and disconnect any connections currently using the same local port</source>
<translation>連線至此連線並且中斷使用了相同本機埠的連線</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="313"/>
<source>From &config.json</source>
<translation>自 &config.json</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="322"/>
<source>&Save Manually</source>
<translation>&手動儲存</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="325"/>
<source>Ctrl+Shift+S</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="334"/>
<source>&Move Up</source>
<translation>&向上移動</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="343"/>
<source>Mo&ve Down</source>
<translation>&向下移動</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="301"/>
<source>&Import Connections from gui-config.json</source>
<translation>&自 gui-config.json 匯入連線</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="304"/>
<source>Import connections from old version configuration file</source>
<translation>自舊版設定檔匯入連線</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="288"/>
<source>&Test Latency</source>
<translation>&測試延遲</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<source>Failed to communicate with previously running instance of Shadowsocks-Qt5 (PID: %1). It might already crashed.</source>
<translation type="vanished">與之前執行的 Shadowsocks-Qt5 執行個體(PID: %1)通訊失敗。或許已損毀。</translation>
</message>
<message>
<source>Error</source>
<translation type="vanished">錯誤</translation>
</message>
<message>
<source>Another instance of Shadowsocks-Qt5 (PID: %1) is already running.</source>
<translation type="vanished">另一個 Shadowsocks-Qt5 (PID: %1) 的執行個體已在執行。</translation>
</message>
<message>
<location filename="../sqprofile.cpp" line="9"/>
<source>Unnamed Profile</source>
<translation>未命名的設定檔</translation>
</message>
</context>
<context>
<name>QRCodeCapturer</name>
<message>
<source>QR Code Capturer</source>
<translation type="vanished">QR 碼捕捉器</translation>
</message>
<message>
<location filename="../qrcodecapturer.cpp" line="24"/>
<source>QR Capturer</source>
<translation>QR 碼捕捉器</translation>
</message>
</context>
<context>
<name>QRWidget</name>
<message>
<location filename="../qrwidget.cpp" line="37"/>
<source>Generating QR code failed.</source>
<translation>QR 碼產生失敗。</translation>
</message>
</context>
<context>
<name>SettingsDialog</name>
<message>
<location filename="../settingsdialog.ui" line="14"/>
<source>General Settings</source>
<translation>一般設定</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="30"/>
<source>Toolbar Style</source>
<translation>工具列樣式</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="44"/>
<source>Icons Only</source>
<translation>僅圖示</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="49"/>
<source>Text Only</source>
<translation>僅文字</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="54"/>
<source>Text Alongside Icons</source>
<translation>文字在圖示邊</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="59"/>
<source>Text Under Icons</source>
<translation>文字在圖示下</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="64"/>
<source>System Style</source>
<translation>系統樣式</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="72"/>
<source>Allow only one instance running</source>
<translation>僅允許一個執行個體執行</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="79"/>
<source>Hide window on startup</source>
<translation>啟動時隱藏視窗</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="86"/>
<source>Need to restart the application for this change to take effect</source>
<translation>需要重新啟動程式方能使此變更生效</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="89"/>
<source>Use native menu bar</source>
<translation>使用原生選單列</translation>
</message>
<message>
<location filename="../settingsdialog.ui" line="96"/>
<source>Start at login</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ShareDialog</name>
<message>
<location filename="../sharedialog.ui" line="20"/>
<source>Share Profile</source>
<translation>分享設定檔</translation>
</message>
<message>
<location filename="../sharedialog.ui" line="58"/>
<source>Save QR code as an Image file</source>
<translation>儲存 QR 碼為圖檔</translation>
</message>
<message>
<location filename="../sharedialog.cpp" line="27"/>
<source>Save QR Code</source>
<translation>儲存 QR 碼</translation>
</message>
</context>
<context>
<name>StatusNotifier</name>
<message>
<location filename="../statusnotifier.cpp" line="21"/>
<location filename="../statusnotifier.cpp" line="131"/>
<source>Minimise</source>
<translation>最小化</translation>
</message>
<message>
<location filename="../statusnotifier.cpp" line="24"/>
<source>Quit</source>
<translation>結束</translation>
</message>
<message>
<location filename="../statusnotifier.cpp" line="21"/>
<location filename="../statusnotifier.cpp" line="131"/>
<source>Restore</source>
<translation>還原</translation>
</message>
</context>
<context>
<name>URIInputDialog</name>
<message>
<location filename="../uriinputdialog.ui" line="14"/>
<source>URI Input Dialog</source>
<translation>URI 輸入對話方塊</translation>
</message>
<message>
<location filename="../uriinputdialog.ui" line="20"/>
<source>Please input ss:// URI</source>
<translation>請輸入 ss:// URI</translation>
</message>
</context>
</TS>
================================================
FILE: src/icons/Breeze/index.theme
================================================
[Icon Theme]
Name=Breeze
Directories=actions
[actions]
Size=22
Context=Actions
Type=Fixed
================================================
FILE: src/icons.qrc
================================================
<RCC>
<qresource prefix="/icons">
<file alias="actions/application-exit.png">icons/Breeze/actions/application-exit.png</file>
<file alias="actions/configure.png">icons/Breeze/actions/configure.png</file>
<file alias="actions/document-open.png">icons/Breeze/actions/document-open.png</file>
<file alias="actions/document-revert.png">icons/Breeze/actions/document-revert.png</file>
<file alias="actions/document-edit.png">icons/Breeze/actions/document-edit.png</file>
<file alias="actions/document-import.png">icons/Breeze/actions/document-import.png</file>
<file alias="actions/document-export.png">icons/Breeze/actions/document-export.png</file>
<file alias="actions/document-save.png">icons/Breeze/actions/document-save.png</file>
<file alias="actions/document-share.png">icons/Breeze/actions/document-share.png</file>
<file alias="actions/edit-guides.png">icons/Breeze/actions/edit-guides.png</file>
<file alias="actions/edit-image-face-recognize.png">icons/Breeze/actions/edit-image-face-recognize.png</file>
<file alias="actions/flag.png">icons/Breeze/actions/flag.png</file>
<file alias="actions/go-down.png">icons/Breeze/actions/go-down.png</file>
<file alias="actions/go-up.png">icons/Breeze/actions/go-up.png</file>
<file alias="actions/help-about.png">icons/Breeze/actions/help-about.png</file>
<file alias="actions/list-add.png">icons/Breeze/actions/list-add.png</file>
<file alias="actions/list-remove.png">icons/Breeze/actions/list-remove.png</file>
<file alias="actions/network-connect.png">icons/Breeze/actions/network-connect.png</file>
<file alias="actions/network-disconnect.png">icons/Breeze/actions/network-disconnect.png</file>
<file alias="actions/text-field.png">icons/Breeze/actions/text-field.png</file>
<file alias="actions/tools-report-bug.png">icons/Breeze/actions/tools-report-bug.png</file>
<file alias="actions/view-list-text.png">icons/Breeze/actions/view-list-text.png</file>
<file alias="index.theme">icons/Breeze/index.theme</file>
<file>icons/shadowsocks-qt5.png</file>
</qresource>
</RCC>
================================================
FILE: src/ip4validator.cpp
================================================
#include "ip4validator.h"
IP4Validator::IP4Validator(QObject *parent)
: QValidator(parent)
{}
QValidator::State IP4Validator::validate(QString &input, int &) const
{
if (input.isEmpty()) {
return Acceptable;
}
QStringList slist = input.split(".");
int number = slist.size();
if (number > 4) {
return Invalid;
}
bool emptyGroup = false;
for (int i = 0; i < number; i++) {
bool ok;
if(slist[i].isEmpty()){
emptyGroup = true;
continue;
}
ushort value = slist[i].toUShort(&ok);
if(!ok || value > 255) {
return Invalid;
}
}
if(number < 4 || emptyGroup) {
return Intermediate;
}
return Acceptable;
}
================================================
FILE: src/ip4validator.h
================================================
/*
* Copyright (C) 2014-2016 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*
* This class is based on a piece of code posted on Qt Centre.
* http://www.qtcentre.org/threads/6228-Ip-Address-Validation?p=32368#post32368
*/
#ifndef IP4VALIDATOR_H
#define IP4VALIDATOR_H
#include <QValidator>
class IP4Validator : public QValidator
{
public:
IP4Validator(QObject *parent = 0);
State validate(QString &input, int &) const;
};
#endif // IP4VALIDATOR_H
================================================
FILE: src/main.cpp
================================================
#include <QApplication>
#include <QTranslator>
#include <QLibraryInfo>
#include <QLocale>
#include <QMessageBox>
#include <QDebug>
#include <QDir>
#include <QCommandLineParser>
#include <signal.h>
#include "mainwindow.h"
#include "confighelper.h"
MainWindow *mainWindow = nullptr;
static void onSignalRecv(int sig)
{
if (sig == SIGINT || sig == SIGTERM) {
qApp->quit();
} else {
qWarning("Unhandled signal %d", sig);
}
}
void setupApplication(QApplication &a)
{
signal(SIGINT, onSignalRecv);
signal(SIGTERM, onSignalRecv);
QApplication::setFallbackSessionManagementEnabled(false);
a.setApplicationName(QString("shadowsocks-qt5"));
a.setApplicationDisplayName(QString("Shadowsocks-Qt5"));
a.setApplicationVersion(APP_VERSION);
#ifdef Q_OS_WIN
if (QLocale::system().country() == QLocale::China) {
a.setFont(QFont("Microsoft Yahei", 9, QFont::Normal, false));
}
else {
a.setFont(QFont("Segoe UI", 9, QFont::Normal, false));
}
#endif
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
QIcon::setThemeName("Breeze");
#endif
QTranslator *ssqt5t = new QTranslator(&a);
ssqt5t->load(QLocale::system(), "ss-qt5", "_", ":/i18n");
a.installTranslator(ssqt5t);
}
int main(int argc, char *argv[])
{
qRegisterMetaTypeStreamOperators<SQProfile>("SQProfile");
QApplication a(argc, argv);
setupApplication(a);
QCommandLineParser parser;
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption configFileOption("c",
"specify configuration file.",
"config.ini");
parser.addOption(configFileOption);
parser.process(a);
QString configFile = parser.value(configFileOption);
if (configFile.isEmpty()) {
#ifdef Q_OS_WIN
configFile = a.applicationDirPath() + "/config.ini";
#else
QDir configDir = QDir::homePath() + "/.config/shadowsocks-qt5";
configFile = configDir.absolutePath() + "/config.ini";
if (!configDir.exists()) {
configDir.mkpath(configDir.absolutePath());
}
#endif
}
ConfigHelper conf(configFile);
MainWindow w(&conf);
mainWindow = &w;
if (conf.isOnlyOneInstance() && w.isInstanceRunning()) {
return -1;
}
w.startAutoStartConnections();
if (!conf.isHideWindowOnStartup()) {
w.show();
}
return a.exec();
}
================================================
FILE: src/mainwindow.cpp
================================================
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "connection.h"
#include "editdialog.h"
#include "urihelper.h"
#include "uriinputdialog.h"
#include "sharedialog.h"
#include "settingsdialog.h"
#include "qrcodecapturer.h"
#include <QDesktopServices>
#include <QDesktopWidget>
#include <QFileDialog>
#include <QMessageBox>
#include <QCloseEvent>
#include <QLocalSocket>
MainWindow::MainWindow(ConfigHelper *confHelper, QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
configHelper(confHelper),
instanceRunning(false)
{
Q_ASSERT(configHelper);
initSingleInstance();
ui->setupUi(this);
//setup Settings menu
#ifndef Q_OS_DARWIN
ui->menuSettings->addAction(ui->toolBar->toggleViewAction());
#endif
//initialisation
model = new ConnectionTableModel(this);
configHelper->read(model);
proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(model);
proxyModel->setSortRole(Qt::EditRole);
proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
proxyModel->setFilterKeyColumn(-1);//read from all columns
ui->connectionView->setModel(proxyModel);
ui->toolBar->setToolButtonStyle(static_cast<Qt::ToolButtonStyle>
(configHelper->getToolbarStyle()));
setupActionIcon();
notifier = new StatusNotifier(this, configHelper->isHideWindowOnStartup(), this);
connect(configHelper, &ConfigHelper::toolbarStyleChanged,
ui->toolBar, &QToolBar::setToolButtonStyle);
connect(model, &ConnectionTableModel::message,
notifier, &StatusNotifier::showNotification);
connect(model, &ConnectionTableModel::rowStatusChanged,
this, &MainWindow::onConnectionStatusChanged);
connect(ui->actionSaveManually, &QAction::triggered,
this, &MainWindow::onSaveManually);
connect(ui->actionTestAllLatency, &QAction::triggered,
model, &ConnectionTableModel::testAllLatency);
//some UI changes accoding to config
ui->toolBar->setVisible(configHelper->isShowToolbar());
ui->actionShowFilterBar->setChecked(configHelper->isShowFilterBar());
ui->menuBar->setNativeMenuBar(configHelper->isNativeMenuBar());
//Move to the center of the screen
this->move(QApplication::desktop()->screen()->rect().center() -
this->rect().center());
//UI signals
connect(ui->actionImportGUIJson, &QAction::triggered,
this, &MainWindow::onImportGuiJson);
connect(ui->actionExportGUIJson, &QAction::triggered,
this, &MainWindow::onExportGuiJson);
connect(ui->actionQuit, &QAction::triggered, qApp, &QApplication::quit);
connect(ui->actionManually, &QAction::triggered,
this, &MainWindow::onAddManually);
connect(ui->actionQRCode, &QAction::triggered,
this, &MainWindow::onAddScreenQRCode);
connect(ui->actionScanQRCodeCapturer, &QAction::triggered,
this, &MainWindow::onAddScreenQRCodeCapturer);
connect(ui->actionQRCodeFromFile, &QAction::triggered,
this, &MainWindow::onAddQRCodeFile);
connect(ui->actionURI, &QAction::triggered,
this, &MainWindow::onAddFromURI);
connect(ui->actionFromConfigJson, &QAction::triggered,
this, &MainWindow::onAddFromConfigJSON);
connect(ui->actionDelete, &QAction::triggered, this, &MainWindow::onDelete);
connect(ui->actionEdit, &QAction::triggered, this, &MainWindow::onEdit);
connect(ui->actionShare, &QAction::triggered, this, &MainWindow::onShare);
connect(ui->actionConnect, &QAction::triggered,
this, &MainWindow::onConnect);
connect(ui->actionForceConnect, &QAction::triggered,
this, &MainWindow::onForceConnect);
connect(ui->actionDisconnect, &QAction::triggered,
this, &MainWindow::onDisconnect);
connect(ui->actionTestLatency, &QAction::triggered,
this, &MainWindow::onLatencyTest);
connect(ui->actionMoveUp, &QAction::triggered, this, &MainWindow::onMoveUp);
connect(ui->actionMoveDown, &QAction::triggered,
this, &MainWindow::onMoveDown);
connect(ui->actionGeneralSettings, &QAction::triggered,
this, &MainWindow::onGeneralSettings);
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::onAbout);
connect(ui->actionAboutQt, &QAction::triggered,
qApp, &QApplication::aboutQt);
connect(ui->actionReportBug, &QAction::triggered,
this, &MainWindow::onReportBug);
connect(ui->actionShowFilterBar, &QAction::toggled,
configHelper, &ConfigHelper::setShowFilterBar);
connect(ui->actionShowFilterBar, &QAction::toggled,
this, &MainWindow::onFilterToggled);
connect(ui->toolBar, &QToolBar::visibilityChanged,
configHelper, &ConfigHelper::setShowToolbar);
connect(ui->filterLineEdit, &QLineEdit::textChanged,
this, &MainWindow::onFilterTextChanged);
connect(ui->connectionView, &QTableView::clicked,
this, static_cast<void (MainWindow::*)(const QModelIndex&)>
(&MainWindow::checkCurrentIndex));
connect(ui->connectionView, &QTableView::activated,
this, static_cast<void (MainWindow::*)(const QModelIndex&)>
(&MainWindow::checkCurrentIndex));
connect(ui->connectionView, &QTableView::doubleClicked,
this, &MainWindow::onEdit);
/* set custom context menu */
ui->connectionView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->connectionView, &QTableView::customContextMenuRequested,
this, &MainWindow::onCustomContextMenuRequested);
checkCurrentIndex();
// Restore mainWindow's geometry and state
restoreGeometry(configHelper->getMainWindowGeometry());
restoreState(configHelper->getMainWindowState());
ui->connectionView->horizontalHeader()->restoreGeometry(configHelper->getTableGeometry());
ui->connectionView->horizontalHeader()->restoreState(configHelper->getTableState());
}
MainWindow::~MainWindow()
{
configHelper->save(*model);
configHelper->setTableGeometry(ui->connectionView->horizontalHeader()->saveGeometry());
configHelper->setTableState(ui->connectionView->horizontalHeader()->saveState());
configHelper->setMainWindowGeometry(saveGeometry());
configHelper->setMainWindowState(saveState());
// delete ui after everything in case it's deleted while still needed for
// the functions written above
delete ui;
}
const QUrl MainWindow::issueUrl =
QUrl("https://github.com/shadowsocks/shadowsocks-qt5/issues");
void MainWindow::startAutoStartConnections()
{
configHelper->startAllAutoStart(*model);
}
void MainWindow::onImportGuiJson()
{
QString file = QFileDialog::getOpenFileName(
this,
tr("Import Connections from gui-config.json"),
QString(),
"GUI Configuration (gui-config.json)");
if (!file.isNull()) {
configHelper->importGuiConfigJson(model, file);
}
}
void MainWindow::onExportGuiJson()
{
QString file = QFileDialog::getSaveFileName(
this,
tr("Export Connections as gui-config.json"),
QString("gui-config.json"),
"GUI Configuration (gui-config.json)");
if (!file.isNull()) {
configHelper->exportGuiConfigJson(*model, file);
}
}
void MainWindow::onSaveManually()
{
configHelper->save(*model);
}
void MainWindow::onAddManually()
{
Connection *newCon = new Connection;
newProfile(newCon);
}
void MainWindow::onAddScreenQRCode()
{
QString uri = QRCodeCapturer::scanEntireScreen();
if (uri.isNull()) {
QMessageBox::critical(
this,
tr("QR Code Not Found"),
tr("Can't find any QR code image that contains "
"valid URI on your screen(s)."));
} else {
Connection *newCon = new Connection(uri, this);
newProfile(newCon);
}
}
void MainWindow::onAddScreenQRCodeCapturer()
{
QRCodeCapturer *capturer = new QRCodeCapturer(this);
connect(capturer, &QRCodeCapturer::closed,
capturer, &QRCodeCapturer::deleteLater);
connect(capturer, &QRCodeCapturer::qrCodeFound,
this, &MainWindow::onQRCodeCapturerResultFound,
Qt::DirectConnection);
capturer->show();
}
void MainWindow::onAddQRCodeFile()
{
QString qrFile =
QFileDialog::getOpenFileName(this,
tr("Open QR Code Image File"),
QString(),
"Images (*.png *jpg *jpeg *xpm)");
if (!qrFile.isNull()) {
QImage img(qrFile);
QString uri = URIHelper::decodeImage(img);
if (uri.isNull()) {
QMessageBox::critical(this,
tr("QR Code Not Found"),
tr("Can't find any QR code image that "
"contains valid URI on your screen(s)."));
} else {
Connection *newCon = new Connection(uri, this);
newProfile(newCon);
}
}
}
void MainWindow::onAddFromURI()
{
URIInputDialog *inputDlg = new URIInputDialog(this);
connect(inputDlg, &URIInputDialog::finished,
inputDlg, &URIInputDialog::deleteLater);
connect(inputDlg, &URIInputDialog::acceptedURI, [&](const QString &uri){
Connection *newCon = new Connection(uri, this);
newProfile(newCon);
});
inputDlg->exec();
}
void MainWindow::onAddFromConfigJSON()
{
QString file = QFileDialog::getOpenFileName(this, tr("Open config.json"),
QString(), "JSON (*.json)");
if (!file.isNull()) {
Connection *con = configHelper->configJsonToConnection(file);
if (con) {
newProfile(con);
}
}
}
void MainWindow::onDelete()
{
if (model->removeRow(proxyModel->mapToSource(
ui->connectionView->currentIndex()).row())) {
configHelper->save(*model);
}
checkCurrentIndex();
}
void MainWindow::onEdit()
{
editRow(proxyModel->mapToSource(ui->connectionView->currentIndex()).row());
}
void MainWindow::onShare()
{
QByteArray uri = model->getItem(
proxyModel->mapToSource(ui->connectionView->currentIndex()).
row())->getConnection()->getURI();
ShareDialog *shareDlg = new ShareDialog(uri, this);
connect(shareDlg, &ShareDialog::finished,
shareDlg, &ShareDialog::deleteLater);
shareDlg->exec();
}
void MainWindow::onConnect()
{
int row = proxyModel->mapToSource(ui->connectionView->currentIndex()).row();
Connection *con = model->getItem(row)->getConnection();
if (con->isValid()) {
con->start();
} else {
QMessageBox::critical(this, tr("Invalid"),
tr("The connection's profile is invalid!"));
}
}
void MainWindow::onForceConnect()
{
int row = proxyModel->mapToSource(ui->connectionView->currentIndex()).row();
Connection *con = model->getItem(row)->getConnection();
if (con->isValid()) {
model->disconnectConnectionsAt(con->getProfile().localAddress,
con->getProfile().localPort);
con->start();
} else {
QMessageBox::critical(this, tr("Invalid"),
tr("The connection's profile is invalid!"));
}
}
void MainWindow::onDisconnect()
{
int row = proxyModel->mapToSource(ui->connectionView->currentIndex()).row();
model->getItem(row)->getConnection()->stop();
}
void MainWindow::onConnectionStatusChanged(const int row, const bool running)
{
if (proxyModel->mapToSource(
ui->connectionView->currentIndex()).row() == row) {
ui->actionConnect->setEnabled(!running);
ui->actionDisconnect->setEnabled(running);
}
}
void MainWindow::onLatencyTest()
{
model->getItem(proxyModel->mapToSource(ui->connectionView->currentIndex()).
row())->testLatency();
}
void MainWindow::onMoveUp()
{
QModelIndex proxyIndex = ui->connectionView->currentIndex();
int currentRow = proxyModel->mapToSource(proxyIndex).row();
int targetRow = proxyModel->mapToSource(
proxyModel->index(proxyIndex.row() - 1,
proxyIndex.column(),
proxyIndex.parent())
).row();
model->move(currentRow, targetRow);
checkCurrentIndex();
}
void MainWindow::onMoveDown()
{
QModelIndex proxyIndex = ui->connectionView->currentIndex();
int currentRow = proxyModel->mapToSource(proxyIndex).row();
int targetRow = proxyModel->mapToSource(
proxyModel->index(proxyIndex.row() + 1,
proxyIndex.column(),
proxyIndex.parent())
).row();
model->move(currentRow, targetRow);
checkCurrentIndex();
}
void MainWindow::onGeneralSettings()
{
SettingsDialog *sDlg = new SettingsDialog(configHelper, this);
connect(sDlg, &SettingsDialog::finished,
sDlg, &SettingsDialog::deleteLater);
if (sDlg->exec()) {
configHelper->save(*model);
configHelper->setStartAtLogin();
}
}
void MainWindow::newProfile(Connection *newCon)
{
EditDialog *editDlg = new EditDialog(newCon, this);
connect(editDlg, &EditDialog::finished, editDlg, &EditDialog::deleteLater);
if (editDlg->exec()) {//accepted
model->appendConnection(newCon);
configHelper->save(*model);
} else {
newCon->deleteLater();
}
}
void MainWindow::editRow(int row)
{
Connection *con = model->getItem(row)->getConnection();
EditDialog *editDlg = new EditDialog(con, this);
connect(editDlg, &EditDialog::finished, editDlg, &EditDialog::deleteLater);
if (editDlg->exec()) {
configHelper->save(*model);
}
}
void MainWindow::checkCurrentIndex()
{
checkCurrentIndex(ui->connectionView->currentIndex());
}
void MainWindow::checkCurrentIndex(const QModelIndex &_index)
{
QModelIndex index = proxyModel->mapToSource(_index);
const bool valid = index.isValid();
ui->actionTestLatency->setEnabled(valid);
ui->actionEdit->setEnabled(valid);
ui->actionDelete->setEnabled(valid);
ui->actionShare->setEnabled(valid);
ui->actionMoveUp->setEnabled(valid ? _index.row() > 0 : false);
ui->actionMoveDown->setEnabled(valid ?
_index.row() < model->rowCount() - 1 :
false);
if (valid) {
const bool &running =
model->getItem(index.row())->getConnection()->isRunning();
ui->actionConnect->setEnabled(!running);
ui->actionForceConnect->setEnabled(!running);
ui->actionDisconnect->setEnabled(running);
} else {
ui->actionConnect->setEnabled(false);
ui->actionForceConnect->setEnabled(false);
ui->actionDisconnect->setEnabled(false);
}
}
void MainWindow::onAbout()
{
QString text = QString("<h1>Shadowsocks-Qt5</h1><p><b>Version %1</b><br />"
"Using libQtShadowsocks %2</p>"
"<p>Copyright © 2014-2018 Symeon Huang "
"(<a href='https://twitter.com/librehat'>"
"@librehat</a>)</p>"
"<p>License: <a href='http://www.gnu.org/licenses/lgpl.html'>"
"GNU Lesser General Public License Version 3</a><br />"
"Project Hosted at "
"<a href='https://github.com/shadowsocks/shadowsocks-qt5'>"
"GitHub</a></p>")
.arg(QStringLiteral(APP_VERSION))
.arg(QSS::Common::version());
QMessageBox::about(this, tr("About"), text);
}
void MainWindow::onReportBug()
{
QDesktopServices::openUrl(issueUrl);
}
void MainWindow::onCustomContextMenuRequested(const QPoint &pos)
{
this->checkCurrentIndex(ui->connectionView->indexAt(pos));
ui->menuConnection->popup(ui->connectionView->viewport()->mapToGlobal(pos));
}
void MainWindow::onFilterToggled(bool show)
{
if (show) {
ui->filterLineEdit->setFocus();
}
}
void MainWindow::onFilterTextChanged(const QString &text)
{
proxyModel->setFilterWildcard(text);
}
void MainWindow::onQRCodeCapturerResultFound(const QString &uri)
{
QRCodeCapturer* capturer = qobject_cast<QRCodeCapturer*>(sender());
// Disconnect immediately to avoid duplicate signals
disconnect(capturer, &QRCodeCapturer::qrCodeFound,
this, &MainWindow::onQRCodeCapturerResultFound);
Connection *newCon = new Connection(uri, this);
newProfile(newCon);
}
void MainWindow::hideEvent(QHideEvent *e)
{
QMainWindow::hideEvent(e);
notifier->onWindowVisibleChanged(false);
}
void MainWindow::showEvent(QShowEvent *e)
{
QMainWindow::showEvent(e);
notifier->onWindowVisibleChanged(true);
this->setFocus();
}
void MainWindow::closeEvent(QCloseEvent *e)
{
if (e->spontaneous()) {
e->ignore();
hide();
} else {
QMainWindow::closeEvent(e);
}
}
void MainWindow::setupActionIcon()
{
ui->actionConnect->setIcon(QIcon::fromTheme("network-connect",
QIcon::fromTheme("network-vpn")));
ui->actionDisconnect->setIcon(QIcon::fromTheme("network-disconnect",
QIcon::fromTheme("network-offline")));
ui->actionEdit->setIcon(QIcon::fromTheme("document-edit",
QIcon::fromTheme("accessories-text-editor")));
ui->actionShare->setIcon(QIcon::fromTheme("document-share",
QIcon::fromTheme("preferences-system-sharing")));
ui->actionTestLatency->setIcon(QIcon::fromTheme("flag",
QIcon::fromTheme("starred")));
ui->actionImportGUIJson->setIcon(QIcon::fromTheme("document-import",
QIcon::fromTheme("insert-text")));
ui->actionExportGUIJson->setIcon(QIcon::fromTheme("document-export",
QIcon::fromTheme("document-save-as")));
ui->actionManually->setIcon(QIcon::fromTheme("edit-guides",
QIcon::fromTheme("accessories-text-editor")));
ui->actionURI->setIcon(QIcon::fromTheme("text-field",
QIcon::fromTheme("insert-link")));
ui->actionQRCode->setIcon(QIcon::fromTheme("edit-image-face-recognize",
QIcon::fromTheme("insert-image")));
ui->actionScanQRCodeCapturer->setIcon(ui->actionQRCode->icon());
ui->actionGeneralSettings->setIcon(QIcon::fromTheme("configure",
QIcon::fromTheme("preferences-desktop")));
ui->actionReportBug->setIcon(QIcon::fromTheme("tools-report-bug",
QIcon::fromTheme("help-faq")));
}
bool MainWindow::isInstanceRunning() const
{
return instanceRunning;
}
void MainWindow::initSingleInstance()
{
const QString serverName = QCoreApplication::applicationName();
QLocalSocket socket;
socket.connectToServer(serverName);
if (socket.waitForConnected(500)) {
instanceRunning = true;
if (configHelper->isOnlyOneInstance()) {
qWarning() << "An instance of ss-qt5 is already running";
}
QByteArray username = qgetenv("USER");
if (username.isEmpty()) {
username = qgetenv("USERNAME");
}
socket.write(username);
socket.waitForBytesWritten();
return;
}
/* Can't connect to server, indicating it's the first instance of the user */
instanceServer = new QLocalServer(this);
instanceServer->setSocketOptions(QLocalServer::WorldAccessOption);
connect(instanceServer, &QLocalServer::newConnection,
this, &MainWindow::onSingleInstanceConnect);
if (instanceServer->listen(serverName)) {
/* Remove server in case of crashes */
if (instanceServer->serverError() == QAbstractSocket::AddressInUseError &&
QFile::exists(instanceServer->serverName())) {
QFile::remove(instanceServer->serverName());
instanceServer->listen(serverName);
}
}
}
void MainWindow::onSingleInstanceConnect()
{
QLocalSocket *socket = instanceServer->nextPendingConnection();
if (!socket) {
return;
}
if (socket->waitForReadyRead(1000)) {
QByteArray username = qgetenv("USER");
if (username.isEmpty()) {
username = qgetenv("USERNAME");
}
QByteArray recvUsername = socket->readAll();
if (recvUsername == username) {
// Only show the window if it's the same user
show();
} else {
qWarning("Another user is trying to run another instance of ss-qt5");
}
}
socket->deleteLater();
}
================================================
FILE: src/mainwindow.h
================================================
/*
* Copyright (C) 2014-2016 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QSortFilterProxyModel>
#include <QLocalServer>
#include "connectiontablemodel.h"
#include "confighelper.h"
#include "statusnotifier.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(ConfigHelper *confHelper, QWidget *parent = 0);
~MainWindow();
void startAutoStartConnections();
bool isInstanceRunning() const;
private:
Ui::MainWindow *ui;
ConnectionTableModel *model;
QSortFilterProxyModel *proxyModel;
ConfigHelper *configHelper;
StatusNotifier *notifier;
QLocalServer* instanceServer;
bool instanceRunning;
void initSingleInstance();
void newProfile(Connection *);
void editRow(int row);
void blockChildrenSignals(bool);
void checkCurrentIndex();
void setupActionIcon();
static const QUrl issueUrl;
private slots:
void onImportGuiJson();
void onExportGuiJson();
void onSaveManually();
void onAddManually();
void onAddScreenQRCode();
void onAddScreenQRCodeCapturer();
void onAddQRCodeFile();
void onAddFromURI();
void onAddFromConfigJSON();
void onDelete();
void onEdit();
void onShare();
void onConnect();
void onForceConnect();
void onDisconnect();
void onConnectionStatusChanged(const int row, const bool running);
void onLatencyTest();
void onMoveUp();
void onMoveDown();
void onGeneralSettings();
void checkCurrentIndex(const QModelIndex &index);
void onAbout();
void onReportBug();
void onCustomContextMenuRequested(const QPoint &pos);
void onFilterToggled(bool);
void onFilterTextChanged(const QString &text);
void onQRCodeCapturerResultFound(const QString &uri);
void onSingleInstanceConnect();
protected slots:
void hideEvent(QHideEvent *e);
void showEvent(QShowEvent *e);
void closeEvent(QCloseEvent *e);
};
#endif // MAINWINDOW_H
================================================
FILE: src/mainwindow.ui
================================================
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>480</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>400</height>
</size>
</property>
<property name="windowTitle">
<string>Connection Manager</string>
</property>
<property name="windowIcon">
<iconset resource="icons.qrc">
<normaloff>:/icons/icons/shadowsocks-qt5.png</normaloff>:/icons/icons/shadowsocks-qt5.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonFollowStyle</enum>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLineEdit" name="filterLineEdit">
<property name="placeholderText">
<string>Input to filter</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="connectionView">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="tabKeyNavigation">
<bool>false</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>Show Toolbar</string>
</property>
<property name="movable">
<bool>false</bool>
</property>
<property name="allowedAreas">
<set>Qt::AllToolBarAreas</set>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonFollowStyle</enum>
</property>
<property name="floatable">
<bool>false</bool>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionConnect"/>
<addaction name="actionDisconnect"/>
<addaction name="actionTestLatency"/>
<addaction name="separator"/>
<addaction name="actionEdit"/>
<addaction name="actionDelete"/>
<addaction name="actionShare"/>
<addaction name="separator"/>
<addaction name="actionMoveUp"/>
<addaction name="actionMoveDown"/>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>26</height>
</rect>
</property>
<widget class="QMenu" name="menuConnection">
<property name="title">
<string>&Connection</string>
</property>
<widget class="QMenu" name="menuAdd">
<property name="title">
<string>&Add</string>
</property>
<property name="icon">
<iconset theme="list-add">
<normaloff>.</normaloff>.</iconset>
</property>
<addaction name="actionManually"/>
<addaction name="actionURI"/>
<addaction name="actionQRCodeFromFile"/>
<addaction name="actionQRCode"/>
<addaction name="actionScanQRCodeCapturer"/>
<addaction name="actionFromConfigJson"/>
</widget>
<addaction name="menuAdd"/>
<addaction name="actionEdit"/>
<addaction name="actionShare"/>
<addaction name="actionDelete"/>
<addaction name="separator"/>
<addaction name="actionConnect"/>
<addaction name="actionForceConnect"/>
<addaction name="actionDisconnect"/>
<addaction name="actionTestLatency"/>
<addaction name="actionTestAllLatency"/>
<addaction name="separator"/>
<addaction name="separator"/>
<addaction name="actionMoveUp"/>
<addaction name="actionMoveDown"/>
</widget>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>Fi&le</string>
</property>
<addaction name="actionImportGUIJson"/>
<addaction name="actionExportGUIJson"/>
<addaction name="actionSaveManually"/>
<addaction name="separator"/>
<addaction name="actionQuit"/>
</widget>
<widget class="QMenu" name="menuSettings">
<property name="title">
<string>Settin&gs</string>
</property>
<addaction name="actionGeneralSettings"/>
<addaction name="separator"/>
<addaction name="actionShowFilterBar"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>&Help</string>
</property>
<addaction name="actionAbout"/>
<addaction name="actionAboutQt"/>
<addaction name="separator"/>
<addaction name="actionReportBug"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuConnection"/>
<addaction name="menuSettings"/>
<addaction name="menuHelp"/>
</widget>
<action name="actionManually">
<property name="text">
<string>&Manually</string>
</property>
<property name="toolTip">
<string>Add connection manually</string>
</property>
</action>
<action name="actionQRCode">
<property name="text">
<string>&Scan QR Code on Screen</string>
</property>
</action>
<action name="actionQRCodeFromFile">
<property name="icon">
<iconset theme="document-open">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&From QR Code Image File</string>
</property>
<property name="toolTip">
<string>From QR code image file</string>
</property>
</action>
<action name="actionURI">
<property name="text">
<string>&URI</string>
</property>
<property name="toolTip">
<string>Add connection from URI</string>
</property>
</action>
<action name="actionDelete">
<property name="icon">
<iconset theme="list-remove">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&Delete</string>
</property>
</action>
<action name="actionEdit">
<property name="text">
<string>&Edit</string>
</property>
</action>
<action name="actionConnect">
<property name="text">
<string>&Connect</string>
</property>
</action>
<action name="actionDisconnect">
<property name="text">
<string>D&isconnect</string>
</property>
</action>
<action name="actionQuit">
<property name="icon">
<iconset theme="application-exit">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&Quit</string>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
</action>
<action name="actionAbout">
<property name="icon">
<iconset theme="help-about">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&About</string>
</property>
</action>
<action name="actionAboutQt">
<property name="text">
<string>About &Qt</string>
</property>
</action>
<action name="actionGeneralSettings">
<property name="text">
<string>&General Settings</string>
</property>
</action>
<action name="actionShare">
<property name="text">
<string>&Share</string>
</property>
</action>
<action name="actionReportBug">
<property name="text">
<string>&Report Bug</string>
</property>
</action>
<action name="actionTestLatency">
<property name="text">
<string>&Test Latency</string>
</property>
<property name="toolTip">
<string>Test the latency of selected connection</string>
</property>
</action>
<action name="actionTestAllLatency">
<property name="text">
<string>Test All C&onnections Latency</string>
</property>
</action>
<action name="actionImportGUIJson">
<property name="text">
<string>&Import Connections from gui-config.json</string>
</property>
<property name="toolTip">
<string>Import connections from old version configuration file</string>
</property>
</action>
<action name="actionFromConfigJson">
<property name="icon">
<iconset theme="document-open">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>From &config.json</string>
</property>
</action>
<action name="actionSaveManually">
<property name="icon">
<iconset theme="document-save">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&Save Manually</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+S</string>
</property>
</action>
<action name="actionMoveUp">
<property name="icon">
<iconset theme="go-up">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&Move Up</string>
</property>
</action>
<action name="actionMoveDown">
<property name="icon">
<iconset theme="go-down">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Mo&ve Down</string>
</property>
</action>
<action name="actionShowFilterBar">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>&Show Filter Bar</string>
</property>
<property name="shortcut">
<string>Ctrl+F</string>
</property>
</action>
<action name="actionExportGUIJson">
<property name="text">
<string>&Export as gui-config.json</string>
</property>
</action>
<action name="actionScanQRCodeCapturer">
<property name="text">
<string>Scan &QR Code using Capturer</string>
</property>
<property name="toolTip">
<string>Scan QR Code using Capturer</string>
</property>
</action>
<action name="actionForceConnect">
<property name="text">
<string>&Force Connect</string>
</property>
<property name="toolTip">
<string>Connect to this connection and disconnect any connections currently using the same local port</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>
<include location="icons.qrc"/>
</resources>
<connections>
<connection>
<sender>actionShowFilterBar</sender>
<signal>toggled(bool)</signal>
<receiver>filterLineEdit</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>239</x>
<y>88</y>
</hint>
</hints>
</connection>
</connections>
</ui>
================================================
FILE: src/portvalidator.cpp
================================================
#include "portvalidator.h"
#include "ssvalidator.h"
PortValidator::PortValidator(QObject *parent)
: QValidator(parent)
{}
QValidator::State PortValidator::validate(QString &input, int &) const
{
if (SSValidator::validatePort(input)) {
return Acceptable;
}
else
return Invalid;
}
================================================
FILE: src/portvalidator.h
================================================
/*
* Copyright (C) 2014-2016 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef PORTVALIDATOR_H
#define PORTVALIDATOR_H
#include <QValidator>
class PortValidator : public QValidator
{
public:
PortValidator(QObject *parent = 0);
State validate(QString &input, int &) const;
};
#endif // PORTVALIDATOR_H
================================================
FILE: src/qrcodecapturer.cpp
================================================
#include "qrcodecapturer.h"
#include "urihelper.h"
#include <QApplication>
#include <QMoveEvent>
#include <QResizeEvent>
#include <QDesktopWidget>
#include <QScreen>
QRCodeCapturer::QRCodeCapturer(QWidget *parent) :
QMainWindow(parent)
{
#ifdef Q_OS_WIN
/*
* On Windows, it requires Qt::FramelessWindowHint to be set to make
* translucent background work, but we need a window with borders.
* Therefore, we set the entire window semi-transparent so that
* users are still able to see the region below while moving the
* capturer above the QR code image.
*/
this->setWindowOpacity(0.75);
#else
this->setAttribute(Qt::WA_TranslucentBackground, true);
#endif
this->setWindowTitle(tr("QR Capturer"));
this->setMinimumSize(400, 400);
}
QRCodeCapturer::~QRCodeCapturer()
{}
QString QRCodeCapturer::scanEntireScreen()
{
QString uri;
QList<QScreen *> screens = qApp->screens();
for (QList<QScreen *>::iterator sc = screens.begin();
sc != screens.end();
++sc) {
QImage raw_sc = (*sc)->grabWindow(qApp->desktop()->winId()).toImage();
QString result = URIHelper::decodeImage(raw_sc);
if (!result.isNull()) {
uri = result;
}
}
return uri;
}
void QRCodeCapturer::moveEvent(QMoveEvent *e)
{
QMainWindow::moveEvent(e);
decodeCurrentRegion();
}
void QRCodeCapturer::resizeEvent(QResizeEvent *e)
{
QMainWindow::resizeEvent(e);
decodeCurrentRegion();
}
void QRCodeCapturer::closeEvent(QCloseEvent *e)
{
QMainWindow::closeEvent(e);
emit closed();
}
void QRCodeCapturer::decodeCurrentRegion()
{
QScreen *sc = qApp->screens().at(qApp->desktop()->screenNumber(this));
QRect geometry = this->geometry();
QImage raw_sc = sc->grabWindow(qApp->desktop()->winId(),
geometry.x(),
geometry.y(),
geometry.width(),
geometry.height()).toImage();
QString result = URIHelper::decodeImage(raw_sc);
if (!result.isNull()) {
this->close();
// moveEvent and resizeEvent both happen quite frequent
// it's very likely this signal would be emitted multiple times
// the solution is to use Qt::DirectConnection signal-slot connection
// and disconnect such a connection in the slot function
emit qrCodeFound(result);
}
}
================================================
FILE: src/qrcodecapturer.h
================================================
/*
* Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef QRCODECAPTURER_H
#define QRCODECAPTURER_H
#include <QMainWindow>
class QRCodeCapturer : public QMainWindow
{
Q_OBJECT
public:
explicit QRCodeCapturer(QWidget *parent = 0);
~QRCodeCapturer();
static QString scanEntireScreen();
signals:
void qrCodeFound(const QString &result);
void closed();
protected slots:
void moveEvent(QMoveEvent *e);
void resizeEvent(QResizeEvent *e);
void closeEvent(QCloseEvent *e);
private:
void decodeCurrentRegion();
};
#endif // QRCODECAPTURER_H
================================================
FILE: src/qrwidget.cpp
================================================
#include <QPainter>
#include <QStyleOption>
#include <QDebug>
#include <qrencode.h>
#include "qrwidget.h"
QRWidget::QRWidget(QWidget *parent) :
QWidget(parent)
{}
void QRWidget::setQRData(const QByteArray &data)
{
qrImage = QImage(512, 512, QImage::Format_Mono);
QPainter painter(&qrImage);
QRcode *qrcode = QRcode_encodeString(data.constData(), 1, QR_ECLEVEL_L, QR_MODE_8, 1);
if (qrcode) {
QColor fg(Qt::black);
QColor bg(Qt::white);
painter.setBrush(bg);
painter.setPen(Qt::NoPen);
painter.drawRect(0, 0, 512, 512);
painter.setBrush(fg);
const int s = qrcode->width > 0 ? qrcode->width : 1;
const qreal scale = 512.0 / s;
for(int y = 0; y < s; y++){
for(int x = 0; x < s; x++){
if(qrcode->data[y * s + x] & 0x01){
const qreal rx1 = x * scale, ry1 = y * scale;
QRectF r(rx1, ry1, scale, scale);
painter.drawRects(&r,1);
}
}
}
QRcode_free(qrcode);
}
else {
qWarning() << tr("Generating QR code failed.");
}
}
void QRWidget::paintEvent(QPaintEvent *e)
{
QWidget::paintEvent(e);
QStyleOption opt;
opt.init(this);
QPainter painter(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
QSizeF nSize = qrImage.size().scaled(this->size(), Qt::KeepAspectRatio);
painter.translate((this->width() - nSize.width()) / 2, (this->height() - nSize.height()) / 2);
painter.scale(nSize.width() / qrImage.width(), nSize.height() / qrImage.height());
painter.drawImage(0, 0, qrImage);
}
const QImage& QRWidget::getQRImage() const
{
return qrImage;
}
================================================
FILE: src/qrwidget.h
================================================
/*
* Copyright (C) 2014-2016 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef QRWIDGET_H
#define QRWIDGET_H
#include <QWidget>
#include <QPaintEvent>
class QRWidget : public QWidget
{
Q_OBJECT
public:
explicit QRWidget(QWidget *parent = 0);
void setQRData(const QByteArray &data);
const QImage& getQRImage() const;
private:
QImage qrImage;
protected:
void paintEvent(QPaintEvent *);
};
#endif // QRWIDGET_H
================================================
FILE: src/settingsdialog.cpp
================================================
#include "settingsdialog.h"
#include "ui_settingsdialog.h"
#include <QPushButton>
SettingsDialog::SettingsDialog(ConfigHelper *ch, QWidget *parent) :
QDialog(parent),
ui(new Ui::SettingsDialog),
helper(ch)
{
ui->setupUi(this);
ui->toolbarStyleComboBox->setCurrentIndex(helper->getToolbarStyle());
ui->hideCheckBox->setChecked(helper->isHideWindowOnStartup());
ui->startAtLoginCheckbox->setChecked(helper->isStartAtLogin());
ui->oneInstanceCheckBox->setChecked(helper->isOnlyOneInstance());
ui->nativeMenuBarCheckBox->setChecked(helper->isNativeMenuBar());
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::onAccepted);
connect(ui->toolbarStyleComboBox, &QComboBox::currentTextChanged, this, &SettingsDialog::onChanged);
connect(ui->hideCheckBox, &QCheckBox::stateChanged, this, &SettingsDialog::onChanged);
connect(ui->startAtLoginCheckbox, &QCheckBox::stateChanged, this, &SettingsDialog::onChanged);
connect(ui->oneInstanceCheckBox, &QCheckBox::stateChanged, this, &SettingsDialog::onChanged);
connect(ui->nativeMenuBarCheckBox, &QCheckBox::stateChanged, this, &SettingsDialog::onChanged);
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
this->adjustSize();
}
SettingsDialog::~SettingsDialog()
{
delete ui;
}
void SettingsDialog::onAccepted()
{
helper->setGeneralSettings(ui->toolbarStyleComboBox->currentIndex(),
ui->hideCheckBox->isChecked(),
ui->startAtLoginCheckbox->isChecked(),
ui->oneInstanceCheckBox->isChecked(),
ui->nativeMenuBarCheckBox->isChecked());
this->accept();
}
void SettingsDialog::onChanged()
{
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
}
================================================
FILE: src/settingsdialog.h
================================================
/*
* Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef SETTINGSDIALOG_H
#define SETTINGSDIALOG_H
#include <QDialog>
#include "confighelper.h"
namespace Ui {
class SettingsDialog;
}
class SettingsDialog : public QDialog
{
Q_OBJECT
public:
explicit SettingsDialog(ConfigHelper *ch, QWidget *parent = 0);
~SettingsDialog();
private:
Ui::SettingsDialog *ui;
ConfigHelper *helper;
private slots:
void onAccepted();
void onChanged();
};
#endif // SETTINGSDIALOG_H
================================================
FILE: src/settingsdialog.ui
================================================
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsDialog</class>
<widget class="QDialog" name="SettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>340</width>
<height>217</height>
</rect>
</property>
<property name="windowTitle">
<string>General Settings</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="8" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="toolbarStyleLabel">
<property name="text">
<string>Toolbar Style</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="toolbarStyleComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>Icons Only</string>
</property>
</item>
<item>
<property name="text">
<string>Text Only</string>
</property>
</item>
<item>
<property name="text">
<string>Text Alongside Icons</string>
</property>
</item>
<item>
<property name="text">
<string>Text Under Icons</string>
</property>
</item>
<item>
<property name="text">
<string>System Style</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="oneInstanceCheckBox">
<property name="text">
<string>Allow only one instance running</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="hideCheckBox">
<property name="text">
<string>Hide window on startup</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="nativeMenuBarCheckBox">
<property name="toolTip">
<string>Need to restart the application for this change to take effect</string>
</property>
<property name="text">
<string>Use native menu bar</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="startAtLoginCheckbox">
<property name="text">
<string>Start at login</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SettingsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
================================================
FILE: src/shadowsocks-qt5.desktop
================================================
[Desktop Entry]
Name=Shadowsocks-Qt5
GenericName=Shadowsocks-Qt5
Comment=Shadowsocks GUI client
Exec=ss-qt5
Icon=shadowsocks-qt5
Terminal=false
Type=Application
Categories=Network;
StartupNotify=true
================================================
FILE: src/sharedialog.cpp
================================================
#include <QFileDialog>
#include "qrwidget.h"
#include "sharedialog.h"
#include "ui_sharedialog.h"
ShareDialog::ShareDialog(const QByteArray &ssUrl, QWidget *parent) :
QDialog(parent),
ui(new Ui::ShareDialog)
{
ui->setupUi(this);
ui->qrWidget->setQRData(ssUrl);
ui->ssUrlEdit->setText(QString(ssUrl));
ui->ssUrlEdit->setCursorPosition(0);
connect(ui->saveButton, &QPushButton::clicked, this, &ShareDialog::onSaveButtonClicked);
this->adjustSize();
}
ShareDialog::~ShareDialog()
{
delete ui;
}
void ShareDialog::onSaveButtonClicked()
{
QString filename = QFileDialog::getSaveFileName(this, tr("Save QR Code"), QString(), "PNG (*.png)");
if (!filename.isEmpty()) {
ui->qrWidget->getQRImage().save(filename);
}
}
================================================
FILE: src/sharedialog.h
================================================
/*
* Copyright (C) 2014-2016 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef SHAREDIALOG_H
#define SHAREDIALOG_H
#include <QDialog>
namespace Ui {
class ShareDialog;
}
class ShareDialog : public QDialog
{
Q_OBJECT
public:
explicit ShareDialog(const QByteArray &ssUrl, QWidget *parent = 0);
~ShareDialog();
private:
Ui::ShareDialog *ui;
private slots:
void onSaveButtonClicked();
};
#endif // SHAREDIALOG_H
================================================
FILE: src/sharedialog.ui
================================================
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ShareDialog</class>
<widget class="QDialog" name="ShareDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>300</width>
<height>360</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>360</height>
</size>
</property>
<property name="windowTitle">
<string>Share Profile</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRWidget" name="qrWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>10</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>256</width>
<height>256</height>
</size>
</property>
</widget>
</item>
<item alignment="Qt::AlignBottom">
<widget class="QLineEdit" name="ssUrlEdit">
<property name="styleSheet">
<string notr="true">background-color: "transparent"</string>
</property>
<property name="frame">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="saveButton">
<property name="text">
<string>Save QR code as an Image file</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QRWidget</class>
<extends>QWidget</extends>
<header>qrwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
================================================
FILE: src/sqprofile.cpp
================================================
#include "sqprofile.h"
SQProfile::SQProfile()
{
autoStart = false;
debug = false;
serverPort = 8388;
localPort = 1080;
name = QObject::tr("Unnamed Profile");
localAddress = QString("127.0.0.1");
method = QString("RC4-MD5");
timeout = 600;
latency = LATENCY_UNKNOWN;
currentUsage = 0;
totalUsage = 0;
QDate currentDate = QDate::currentDate();
nextResetDate = QDate(currentDate.year(), currentDate.month() + 1, 1);
httpMode = false;
}
SQProfile::SQProfile(const QSS::Profile &profile) : SQProfile()
{
name = QString::fromStdString(profile.name());
localAddress = QString::fromStdString(profile.localAddress());
localPort = profile.localPort();
serverPort = profile.serverPort();
serverAddress = QString::fromStdString(profile.serverAddress());
method = QString::fromStdString(profile.method()).toUpper();
password = QString::fromStdString(profile.password());
timeout = profile.timeout();
httpMode = profile.httpProxy();
debug = profile.debug();
}
SQProfile::SQProfile(const QString &uri)
{
*this = SQProfile(QSS::Profile::fromUri(uri.toStdString()));
}
QSS::Profile SQProfile::toProfile() const
{
QSS::Profile qssprofile;
qssprofile.setName(name.toStdString());
qssprofile.setServerAddress(serverAddress.toStdString());
qssprofile.setServerPort(serverPort);
qssprofile.setLocalAddress(localAddress.toStdString());
qssprofile.setLocalPort(localPort);
qssprofile.setMethod(method.toLower().toStdString());
qssprofile.setPassword(password.toStdString());
qssprofile.setTimeout(timeout);
qssprofile.setHttpProxy(httpMode);
if (debug) {
qssprofile.enableDebug();
} else {
qssprofile.disableDebug();
}
return qssprofile;
}
QDataStream& operator << (QDataStream &out, const SQProfile &p)
{
out << p.autoStart << p.debug << p.serverPort << p.localPort << p.name << p.serverAddress << p.localAddress << p.method << p.password << p.timeout << p.latency << p.currentUsage << p.totalUsage << p.lastTime << p.nextResetDate << p.httpMode;
return out;
}
QDataStream& operator >> (QDataStream &in, SQProfile &p)
{
in >> p.autoStart >> p.debug >> p.serverPort >> p.localPort >> p.name >> p.serverAddress >> p.localAddress >> p.method >> p.password >> p.timeout >> p.latency >> p.currentUsage >> p.totalUsage >> p.lastTime >> p.nextResetDate >> p.httpMode;
return in;
}
================================================
FILE: src/sqprofile.h
================================================
/*
* Copyright (C) 2014-2016 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef SQPROFILE_H
#define SQPROFILE_H
#include <QDataStream>
#include <QDate>
#include <QDateTime>
#include <QtShadowsocks>
struct SQProfile
{
SQProfile();
SQProfile(const QSS::Profile& profile); // Copy values from QSS Profile
SQProfile(const QString& uri); // Construct it using ss protocol
QSS::Profile toProfile() const; // Convert it into a QSS Profile
bool autoStart;
bool debug;
quint16 serverPort;
quint16 localPort;
QString name;
QString serverAddress;
QString localAddress;
QString method;
QString password;
int timeout;
int latency;
quint64 currentUsage;
quint64 totalUsage;
QDateTime lastTime;//last time this connection is used
QDate nextResetDate;//next scheduled date to reset data usage
bool httpMode;
static const int LATENCY_TIMEOUT = -1;
static const int LATENCY_ERROR = -2;
static const int LATENCY_UNKNOWN = -3;
};
Q_DECLARE_METATYPE(SQProfile)
QDataStream& operator << (QDataStream &out, const SQProfile &p);
QDataStream& operator >> (QDataStream &in, SQProfile &p);
#endif // SQPROFILE_H
================================================
FILE: src/ss-qt5.rc
================================================
IDI_ICON1 ICON DISCARDABLE "ss-qt5.ico"
================================================
FILE: src/ssvalidator.cpp
================================================
#include "ssvalidator.h"
#include <QtShadowsocks>
QStringList SSValidator::supportedMethodList()
{
std::vector<std::string> methodBA = QSS::Cipher::supportedMethods();
std::sort(methodBA.begin(), methodBA.end());
QStringList methodList;
for (const std::string& method : methodBA) {
methodList.push_back(QString::fromStdString(method).toUpper());
}
return methodList;
}
bool SSValidator::validate(const QString &input)
{
bool valid = true;
try {
QSS::Profile::fromUri(input.toStdString());
} catch(const std::exception&) {
valid = false;
}
return valid;
}
bool SSValidator::validatePort(const QString &port)
{
bool ok;
port.toUShort(&ok);
return ok;
}
bool SSValidator::validateMethod(const QString &method)
{
static const QStringList validMethodList = supportedMethodList();
return validMethodList.contains(method, Qt::CaseInsensitive);
}
================================================
FILE: src/ssvalidator.h
================================================
/*
* Copyright (C) 2014-2016 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef SSVALIDATOR_H
#define SSVALIDATOR_H
#include <QString>
#include <QStringList>
class SSValidator
{
public:
static bool validate(const QString &input);
static bool validatePort(const QString &port);
static bool validateMethod(const QString &method);
/*
* Return supported encryption method list at run-time
* To avoid repetitive query, please store return result as static.
*/
static QStringList supportedMethodList();
};
#endif // SSVALIDATOR_H
================================================
FILE: src/statusnotifier.cpp
================================================
#include "statusnotifier.h"
#include "mainwindow.h"
#include <QApplication>
#ifdef Q_OS_LINUX
#include <QDBusMessage>
#include <QDBusConnection>
#include <QDBusPendingCall>
#endif
StatusNotifier::StatusNotifier(MainWindow *w, bool startHiden, QObject *parent) :
QObject(parent),
window(w)
{
systray.setIcon(QIcon(":/icons/icons/shadowsocks-qt5.png"));
systray.setToolTip(QString("Shadowsocks-Qt5"));
connect(&systray, &QSystemTrayIcon::activated, [this](QSystemTrayIcon::ActivationReason r) {
if (r != QSystemTrayIcon::Context) {
this->activate();
}
});
minimiseRestoreAction = new QAction(startHiden ? tr("Restore") : tr("Minimise"), this);
connect(minimiseRestoreAction, &QAction::triggered, this, &StatusNotifier::activate);
systrayMenu.addAction(minimiseRestoreAction);
systrayMenu.addAction(QIcon::fromTheme("application-exit", QIcon::fromTheme("exit")), tr("Quit"), qApp, SLOT(quit()));
systray.setContextMenu(&systrayMenu);
systray.show();
}
void StatusNotifier::activate()
{
if (!window->isVisible() || window->isMinimized()) {
window->showNormal();
window->activateWindow();
window->raise();
} else {
window->hide();
}
}
void StatusNotifier::showNotification(const QString &msg)
{
#ifdef Q_OS_LINUX
//Using DBus to send message.
QDBusMessage method = QDBusMessage::createMethodCall("org.freedesktop.Notifications","/org/freedesktop/Notifications", "org.freedesktop.Notifications", "Notify");
QVariantList args;
args << QCoreApplication::applicationName() << quint32(0) << "shadowsocks-qt5" << "Shadowsocks-Qt5" << msg << QStringList () << QVariantMap() << qint32(-1);
method.setArguments(args);
QDBusConnection::sessionBus().asyncCall(method);
#else
systray.showMessage("Shadowsocks-Qt5", msg);
#endif
}
void StatusNotifier::onWindowVisibleChanged(bool visible)
{
minimiseRestoreAction->setText(visible ? tr("Minimise") : tr("Restore"));
}
================================================
FILE: src/statusnotifier.h
================================================
/*
* Copyright (C) 2015-2017 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef STATUSNOTIFIER_H
#define STATUSNOTIFIER_H
#include <QObject>
#include <QSystemTrayIcon>
#include <QMenu>
class MainWindow;
class StatusNotifier : public QObject
{
Q_OBJECT
public:
StatusNotifier(MainWindow *w, bool startHiden, QObject *parent = 0);
public slots:
void activate();
void showNotification(const QString &);
void onWindowVisibleChanged(bool visible);
private:
QMenu systrayMenu;
QAction *minimiseRestoreAction;
QSystemTrayIcon systray;
MainWindow *window;
};
#endif // STATUSNOTIFIER_H
================================================
FILE: src/translations.qrc
================================================
<RCC>
<qresource prefix="/">
<file>i18n/ss-qt5_zh_CN.qm</file>
<file>i18n/ss-qt5_zh_TW.qm</file>
</qresource>
</RCC>
================================================
FILE: src/urihelper.cpp
================================================
#include "urihelper.h"
#include <zbar.h>
QImage URIHelper::convertToGrey(const QImage &input)
{
if (input.isNull()) {
return QImage();
}
QImage ret(input.width(), input.height(), QImage::Format_Indexed8);
QVector<QRgb> gtable(256);
for (int i = 0; i < 256; ++i) {
gtable[i] = qRgb(i, i, i);
}
ret.setColorTable(gtable);
for (int i = 0; i < input.width(); ++i) {
for (int j = 0; j < input.height(); ++j) {
QRgb val = input.pixel(i, j);
ret.setPixel(i, j, qGray(val));
}
}
return ret;
}
QString URIHelper::decodeImage(const QImage &img)
{
QString uri;
QImage gimg = convertToGrey(img);
//use zbar to decode the QR code
zbar::ImageScanner scanner;
zbar::Image image(gimg.bytesPerLine(), gimg.height(), "Y800", gimg.bits(), gimg.byteCount());
scanner.scan(image);
zbar::SymbolSet res_set = scanner.get_results();
for (zbar::SymbolIterator it = res_set.symbol_begin(); it != res_set.symbol_end(); ++it) {
if (it->get_type() == zbar::ZBAR_QRCODE) {
/*
* uri will be overwritten if the result is valid
* this means always the last uri gets used
* therefore, please only leave one QR code for the sake of accuracy
*/
QString result = QString::fromStdString(it->get_data());
if (result.left(5).compare("ss://", Qt::CaseInsensitive) == 0) {
uri = result;
}
}
}
return uri;
}
================================================
FILE: src/urihelper.h
================================================
/*
* Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef URIHELPER_H
#define URIHELPER_H
#include <QString>
#include <QImage>
class URIHelper
{
public:
virtual ~URIHelper() = 0;
static QImage convertToGrey(const QImage &input);
static QString decodeImage(const QImage &img);
};
#endif // URIHELPER_H
================================================
FILE: src/uriinputdialog.cpp
================================================
#include "uriinputdialog.h"
#include "ui_uriinputdialog.h"
#include "ssvalidator.h"
#include <QPushButton>
URIInputDialog::URIInputDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::URIInputDialog)
{
ui->setupUi(this);
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
connect(ui->uriEdit, &QLineEdit::textChanged, this, &URIInputDialog::onURIChanged);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &URIInputDialog::onAccepted);
this->adjustSize();
}
URIInputDialog::~URIInputDialog()
{
delete ui;
}
void URIInputDialog::onURIChanged(const QString &str)
{
if (!SSValidator::validate(str)) {
ui->uriEdit->setStyleSheet("background: pink");
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
}
else {
ui->uriEdit->setStyleSheet("background: #81F279");
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
}
}
void URIInputDialog::onAccepted()
{
emit acceptedURI(ui->uriEdit->text());
this->accept();
}
================================================
FILE: src/uriinputdialog.h
================================================
/*
* Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifndef URIINPUTDIALOG_H
#define URIINPUTDIALOG_H
#include <QDialog>
namespace Ui {
class URIInputDialog;
}
class URIInputDialog : public QDialog
{
Q_OBJECT
signals:
void acceptedURI(const QString &uri);
public:
explicit URIInputDialog(QWidget *parent = 0);
~URIInputDialog();
private:
Ui::URIInputDialog *ui;
private slots:
void onAccepted();
void onURIChanged(const QString &);
};
#endif // URIINPUTDIALOG_H
================================================
FILE: src/uriinputdialog.ui
================================================
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>URIInputDialog</class>
<widget class="QDialog" name="URIInputDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>159</height>
</rect>
</property>
<property name="windowTitle">
<string>URI Input Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="uriLabel">
<property name="text">
<string>Please input ss:// URI</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="uriEdit">
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>URIInputDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
gitextract_jqfxs2wr/
├── .gitignore
├── .travis.yml
├── CMakeLists.txt
├── LICENSE
├── README.md
└── src/
├── CMakeLists.txt
├── confighelper.cpp
├── confighelper.h
├── connection.cpp
├── connection.h
├── connectionitem.cpp
├── connectionitem.h
├── connectiontablemodel.cpp
├── connectiontablemodel.h
├── editdialog.cpp
├── editdialog.h
├── editdialog.ui
├── i18n/
│ ├── ss-qt5_zh_CN.qm
│ ├── ss-qt5_zh_CN.ts
│ ├── ss-qt5_zh_TW.qm
│ └── ss-qt5_zh_TW.ts
├── icons/
│ ├── Breeze/
│ │ └── index.theme
│ └── shadowsocks-qt5.icns
├── icons.qrc
├── ip4validator.cpp
├── ip4validator.h
├── main.cpp
├── mainwindow.cpp
├── mainwindow.h
├── mainwindow.ui
├── portvalidator.cpp
├── portvalidator.h
├── qrcodecapturer.cpp
├── qrcodecapturer.h
├── qrwidget.cpp
├── qrwidget.h
├── settingsdialog.cpp
├── settingsdialog.h
├── settingsdialog.ui
├── shadowsocks-qt5.desktop
├── sharedialog.cpp
├── sharedialog.h
├── sharedialog.ui
├── sqprofile.cpp
├── sqprofile.h
├── ss-qt5.rc
├── ssvalidator.cpp
├── ssvalidator.h
├── statusnotifier.cpp
├── statusnotifier.h
├── translations.qrc
├── urihelper.cpp
├── urihelper.h
├── uriinputdialog.cpp
├── uriinputdialog.h
└── uriinputdialog.ui
SYMBOL INDEX (47 symbols across 26 files)
FILE: src/confighelper.cpp
function Connection (line 145) | Connection* ConfigHelper::configJsonToConnection(const QString &file)
function QByteArray (line 359) | QByteArray ConfigHelper::getMainWindowGeometry() const
function QByteArray (line 364) | QByteArray ConfigHelper::getMainWindowState() const
function QByteArray (line 369) | QByteArray ConfigHelper::getTableGeometry() const
function QByteArray (line 374) | QByteArray ConfigHelper::getTableState() const
FILE: src/confighelper.h
function class (line 26) | class ConfigHelper : public QObject
FILE: src/connection.cpp
function SQProfile (line 28) | const SQProfile& Connection::getProfile() const
function QString (line 33) | const QString& Connection::getName() const
function QByteArray (line 38) | QByteArray Connection::getURI() const
FILE: src/connectionitem.cpp
function QVariant (line 29) | QVariant ConnectionItem::data(int column, int role) const
function QString (line 88) | QString ConnectionItem::convertLatencyToString(const int latency)
function QString (line 112) | QString ConnectionItem::convertBytesToHumanReadable(quint64 quot)
function Connection (line 130) | Connection* ConnectionItem::getConnection()
FILE: src/connectionitem.h
function class (line 26) | class ConnectionItem : public QObject
FILE: src/connectiontablemodel.cpp
function ConnectionItem (line 10) | ConnectionItem *ConnectionTableModel::getItem(const int &row) const
function QVariant (line 25) | QVariant ConnectionTableModel::data(const QModelIndex &index, int role) ...
function QVariant (line 35) | QVariant ConnectionTableModel::headerData(int section, Qt::Orientation o...
function QModelIndex (line 65) | QModelIndex ConnectionTableModel::index(int row, int column, const QMode...
FILE: src/connectiontablemodel.h
function class (line 26) | class ConnectionTableModel : public QAbstractTableModel
FILE: src/editdialog.h
function namespace (line 25) | namespace Ui {
function class (line 29) | class EditDialog : public QDialog
FILE: src/ip4validator.h
function class (line 27) | class IP4Validator : public QValidator
FILE: src/main.cpp
function onSignalRecv (line 15) | static void onSignalRecv(int sig)
function setupApplication (line 24) | void setupApplication(QApplication &a)
function main (line 52) | int main(int argc, char *argv[])
FILE: src/mainwindow.h
function namespace (line 29) | namespace Ui {
function class (line 33) | class MainWindow : public QMainWindow
FILE: src/portvalidator.h
function class (line 24) | class PortValidator : public QValidator
FILE: src/qrcodecapturer.cpp
function QString (line 31) | QString QRCodeCapturer::scanEntireScreen()
FILE: src/qrcodecapturer.h
function class (line 24) | class QRCodeCapturer : public QMainWindow
FILE: src/qrwidget.cpp
function QImage (line 56) | const QImage& QRWidget::getQRImage() const
FILE: src/qrwidget.h
function class (line 25) | class QRWidget : public QWidget
FILE: src/settingsdialog.h
function namespace (line 25) | namespace Ui {
function class (line 29) | class SettingsDialog : public QDialog
FILE: src/sharedialog.h
function namespace (line 24) | namespace Ui {
function class (line 28) | class ShareDialog : public QDialog
FILE: src/sqprofile.cpp
function QDataStream (line 60) | QDataStream& operator << (QDataStream &out, const SQProfile &p)
function QDataStream (line 66) | QDataStream& operator >> (QDataStream &in, SQProfile &p)
FILE: src/sqprofile.h
type SQProfile (line 27) | struct SQProfile
FILE: src/ssvalidator.cpp
function QStringList (line 4) | QStringList SSValidator::supportedMethodList()
FILE: src/ssvalidator.h
function class (line 24) | class SSValidator
FILE: src/statusnotifier.h
function class (line 28) | class StatusNotifier : public QObject
FILE: src/urihelper.cpp
function QImage (line 4) | QImage URIHelper::convertToGrey(const QImage &input)
function QString (line 24) | QString URIHelper::decodeImage(const QImage &img)
FILE: src/urihelper.h
function class (line 25) | class URIHelper
FILE: src/uriinputdialog.h
function namespace (line 24) | namespace Ui {
function class (line 28) | class URIInputDialog : public QDialog
Condensed preview — 56 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (195K chars).
[
{
"path": ".gitignore",
"chars": 32,
"preview": "*.user*\n*.swp\n.directory\nbuild/\n"
},
{
"path": ".travis.yml",
"chars": 1094,
"preview": "sudo: required\ndist: trusty\nlanguage: cpp\ncache: apt\naddons:\n apt:\n sources:\n - ubuntu-toolchain-r-test\n -"
},
{
"path": "CMakeLists.txt",
"chars": 1043,
"preview": "cmake_minimum_required(VERSION 3.1)\n\nproject(Shadowsocks-Qt5\n VERSION 3.0.1\n LANGUAGES CXX)\n\nset(CMAKE_INC"
},
{
"path": "LICENSE",
"chars": 7651,
"preview": " GNU LESSER GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007"
},
{
"path": "README.md",
"chars": 956,
"preview": "Shadowsocks-Qt5\n===============\n\n**This project is no longer being maintained**\n\n[\nadd_definitions(-DAPP_VERSION=\"${PROJECT_VERSION}\")\n\nset(SOURCE\n icons.qrc\n translations.qr"
},
{
"path": "src/confighelper.cpp",
"chars": 12970,
"preview": "#include \"confighelper.h\"\n#include <QCoreApplication>\n#include <QDir>\n#include <QFile>\n#include <QJsonParseError>\n#inclu"
},
{
"path": "src/confighelper.h",
"chars": 3435,
"preview": "/*\n * Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/connection.cpp",
"chars": 3867,
"preview": "#include \"connection.h\"\n#include \"ssvalidator.h\"\n#include <QHostInfo>\n#include <QHostAddress>\n\nConnection::Connection(QO"
},
{
"path": "src/connection.h",
"chars": 2227,
"preview": "/*\n * Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/connectionitem.cpp",
"chars": 4975,
"preview": "#include \"connectionitem.h\"\n#include <QFont>\n#include <cmath>\n\nConnectionItem::ConnectionItem(Connection *_con, QObject "
},
{
"path": "src/connectionitem.h",
"chars": 1727,
"preview": "/*\n * Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/connectiontablemodel.cpp",
"chars": 4732,
"preview": "#include \"connectiontablemodel.h\"\n\nConnectionTableModel::ConnectionTableModel(QObject *parent) :\n QAbstractTableModel"
},
{
"path": "src/connectiontablemodel.h",
"chars": 2388,
"preview": "/*\n * Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/editdialog.cpp",
"chars": 2604,
"preview": "#include \"editdialog.h\"\n#include \"ui_editdialog.h\"\n#include \"ssvalidator.h\"\n#include \"ip4validator.h\"\n#include \"portvali"
},
{
"path": "src/editdialog.h",
"chars": 1165,
"preview": "/*\n * Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/editdialog.ui",
"chars": 8553,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>EditDialog</class>\n <widget class=\"QDialog\" name=\"Edit"
},
{
"path": "src/i18n/ss-qt5_zh_CN.ts",
"chars": 23308,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"zh_CN\">\n<context>\n <name>ConnectionI"
},
{
"path": "src/i18n/ss-qt5_zh_TW.ts",
"chars": 23282,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"zh_TW\">\n<context>\n <name>ConnectionI"
},
{
"path": "src/icons/Breeze/index.theme",
"chars": 91,
"preview": "[Icon Theme]\nName=Breeze\nDirectories=actions\n\n[actions]\nSize=22\nContext=Actions\nType=Fixed\n"
},
{
"path": "src/icons.qrc",
"chars": 2225,
"preview": "<RCC>\n <qresource prefix=\"/icons\">\n <file alias=\"actions/application-exit.png\">icons/Breeze/actions/applicatio"
},
{
"path": "src/ip4validator.cpp",
"chars": 756,
"preview": "#include \"ip4validator.h\"\n\nIP4Validator::IP4Validator(QObject *parent)\n : QValidator(parent)\n{}\n\nQValidator::State IP"
},
{
"path": "src/ip4validator.h",
"chars": 1154,
"preview": "/*\n * Copyright (C) 2014-2016 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/main.cpp",
"chars": 2448,
"preview": "#include <QApplication>\n#include <QTranslator>\n#include <QLibraryInfo>\n#include <QLocale>\n#include <QMessageBox>\n#includ"
},
{
"path": "src/mainwindow.cpp",
"chars": 21087,
"preview": "#include \"mainwindow.h\"\n#include \"ui_mainwindow.h\"\n\n#include \"connection.h\"\n#include \"editdialog.h\"\n#include \"urihelper."
},
{
"path": "src/mainwindow.h",
"chars": 2768,
"preview": "/*\n * Copyright (C) 2014-2016 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/mainwindow.ui",
"chars": 11163,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>MainWindow</class>\n <widget class=\"QMainWindow\" name=\""
},
{
"path": "src/portvalidator.cpp",
"chars": 313,
"preview": "#include \"portvalidator.h\"\n#include \"ssvalidator.h\"\n\nPortValidator::PortValidator(QObject *parent)\n : QValidator(pare"
},
{
"path": "src/portvalidator.h",
"chars": 1013,
"preview": "/*\n * Copyright (C) 2014-2016 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/qrcodecapturer.cpp",
"chars": 2458,
"preview": "#include \"qrcodecapturer.h\"\n#include \"urihelper.h\"\n#include <QApplication>\n#include <QMoveEvent>\n#include <QResizeEvent>"
},
{
"path": "src/qrcodecapturer.h",
"chars": 1302,
"preview": "/*\n * Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/qrwidget.cpp",
"chars": 1741,
"preview": "#include <QPainter>\n#include <QStyleOption>\n#include <QDebug>\n#include <qrencode.h>\n#include \"qrwidget.h\"\n\nQRWidget::QRW"
},
{
"path": "src/qrwidget.h",
"chars": 1138,
"preview": "/*\n * Copyright (C) 2014-2016 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/settingsdialog.cpp",
"chars": 1840,
"preview": "#include \"settingsdialog.h\"\n#include \"ui_settingsdialog.h\"\n#include <QPushButton>\n\nSettingsDialog::SettingsDialog(Config"
},
{
"path": "src/settingsdialog.h",
"chars": 1218,
"preview": "/*\n * Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/settingsdialog.ui",
"chars": 3167,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>SettingsDialog</class>\n <widget class=\"QDialog\" name=\""
},
{
"path": "src/shadowsocks-qt5.desktop",
"chars": 200,
"preview": "[Desktop Entry]\nName=Shadowsocks-Qt5\nGenericName=Shadowsocks-Qt5\nComment=Shadowsocks GUI client\nExec=ss-qt5\nIcon=shadows"
},
{
"path": "src/sharedialog.cpp",
"chars": 770,
"preview": "#include <QFileDialog>\n#include \"qrwidget.h\"\n#include \"sharedialog.h\"\n#include \"ui_sharedialog.h\"\n\nShareDialog::ShareDia"
},
{
"path": "src/sharedialog.h",
"chars": 1136,
"preview": "/*\n * Copyright (C) 2014-2016 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/sharedialog.ui",
"chars": 1821,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>ShareDialog</class>\n <widget class=\"QDialog\" name=\"Sha"
},
{
"path": "src/sqprofile.cpp",
"chars": 2445,
"preview": "#include \"sqprofile.h\"\n\nSQProfile::SQProfile()\n{\n autoStart = false;\n debug = false;\n serverPort = 8388;\n lo"
},
{
"path": "src/sqprofile.h",
"chars": 1884,
"preview": "/*\n * Copyright (C) 2014-2016 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/ss-qt5.rc",
"chars": 62,
"preview": "IDI_ICON1 ICON DISCARDABLE \"ss-qt5.ico\" \n"
},
{
"path": "src/ssvalidator.cpp",
"chars": 931,
"preview": "#include \"ssvalidator.h\"\n#include <QtShadowsocks>\n\nQStringList SSValidator::supportedMethodList()\n{\n std::vector<std:"
},
{
"path": "src/ssvalidator.h",
"chars": 1262,
"preview": "/*\n * Copyright (C) 2014-2016 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/statusnotifier.cpp",
"chars": 2005,
"preview": "#include \"statusnotifier.h\"\n#include \"mainwindow.h\"\n#include <QApplication>\n#ifdef Q_OS_LINUX\n#include <QDBusMessage>\n#i"
},
{
"path": "src/statusnotifier.h",
"chars": 1320,
"preview": "/*\n * Copyright (C) 2015-2017 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/translations.qrc",
"chars": 141,
"preview": "<RCC>\n <qresource prefix=\"/\">\n <file>i18n/ss-qt5_zh_CN.qm</file>\n <file>i18n/ss-qt5_zh_TW.qm</file>\n "
},
{
"path": "src/urihelper.cpp",
"chars": 1537,
"preview": "#include \"urihelper.h\"\n#include <zbar.h>\n\nQImage URIHelper::convertToGrey(const QImage &input)\n{\n if (input.isNull())"
},
{
"path": "src/urihelper.h",
"chars": 1039,
"preview": "/*\n * Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/uriinputdialog.cpp",
"chars": 1045,
"preview": "#include \"uriinputdialog.h\"\n#include \"ui_uriinputdialog.h\"\n#include \"ssvalidator.h\"\n#include <QPushButton>\n\nURIInputDial"
},
{
"path": "src/uriinputdialog.h",
"chars": 1218,
"preview": "/*\n * Copyright (C) 2015-2016 Symeon Huang <hzwhuang@gmail.com>\n *\n * shadowsocks-qt5 is free software; you can redistri"
},
{
"path": "src/uriinputdialog.ui",
"chars": 1786,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>URIInputDialog</class>\n <widget class=\"QDialog\" name=\""
}
]
// ... and 3 more files (download for full content)
About this extraction
This page contains the full source code of the shadowsocks/shadowsocks-qt5 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 56 files (179.5 KB), approximately 46.5k tokens, and a symbol index with 47 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.