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. 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** [![Build Status](https://travis-ci.org/shadowsocks/shadowsocks-qt5.svg?branch=master)](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 ------- ![](http://www.gnu.org/graphics/lgplv3-147x51.png) 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 #include #include #include #include #include #include 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(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(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(); 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( "\n" "\n" "\n" "\n" " Label\n" " org.shadowsocks.shadowsocks-qt5.launcher\n" " LimitLoadToSessionType\n" " Aqua\n" " ProgramArguments\n" " \n" " %2\n" " \n" " RunAtLoad\n" " \n" " StandardErrorPath\n" " /dev/null\n" " StandardOutPath\n" " /dev/null\n" "\n" "\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 * * 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 * . */ #ifndef CONFIGHELPER_H #define CONFIGHELPER_H #include #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 #include 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(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(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 * * 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 * . */ #ifndef CONNECTION_H #define CONNECTION_H #include #include #include #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 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 #include 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(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(quot) + static_cast(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 * * 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 * . */ #ifndef CONNECTIONITEM_H #define CONNECTIONITEM_H #include #include #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(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(sender()); int row = items.indexOf(item); emit dataChanged(this->index(row, 3), this->index(row, 3)); } void ConnectionTableModel::onConnectionDataUsageChanged() { ConnectionItem *item = qobject_cast(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 * * 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 * . */ #ifndef CONNECTIONTABLEMODEL_H #define CONNECTIONTABLEMODEL_H #include #include #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 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 * * 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 * . */ #ifndef EDITDIALOG_H #define EDITDIALOG_H #include #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 ================================================ EditDialog 0 0 462 524 Profile Editor QFrame::StyledPanel QFrame::Raised Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Profile Name Server Address Server Port Qt::ImhDigitsOnly|Qt::ImhPreferNumbers Password Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhSensitiveData QLineEdit::PasswordEchoOnEdit Local Address Local Port Qt::ImhDigitsOnly|Qt::ImhPreferNumbers Local Server Type SOCKS&5 true buttonGroup HTTP(S) buttonGroup Encryption Method 0 0 Timeout 0 0 10 3600 10 60 Reset Data Usage after 0 0 true Automation 0 0 Auto connect on application start Qt::Vertical 20 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok nameEdit serverAddrEdit serverPortEdit pwdEdit localAddrEdit localPortEdit socks5RadioButton httpRadioButton encryptComboBox timeoutSpinBox resetDateEdit autoStartCheckBox buttonBox rejected() EditDialog reject() 316 260 286 274 ================================================ FILE: src/i18n/ss-qt5_zh_CN.ts ================================================ ConnectionItem Connected 已连接 Disconnected 未连接 Timeout 超时 Error 错误 Unknown 未知 s ms 毫秒 connected 已连接 disconnected 已断开 timed out 超时 latency test failed 延迟测试失败 Failed to start 无法启动 ConnectionTableModel Name 名称 Server 服务器 Status 状态 Latency 延迟 Local Port 本地端口 Term Usage 本期用量 Total Usage 累计用量 Reset Date 重置日期 Last Used 上次使用 EditDialog Profile Editor 配置编辑器 Local Server Type 本地服务器类型 Timeout 超时 [Deprecated] Turn on one-time authentication and header verification (need server support) [弃用]开启一次验证和消息头验证(需要服务器支持) Turn on one-time authentication and header verification (need server support) 开启一次验证和消息头验证(需要服务器支持) One-time authentication 一次验证 Encryption Method 加密方式 Local Port 本地端口 Local Address 本地地址 Password 密钥 Server Port 服务器端口 Server Address 服务器地址 Profile Name 配置名称 Auto connect on application start 程序启动时自动连接 Debug 调试 Log Level 日志级别 Automation 自动化 Reset Data Usage after 重置数据流量 LogDialog Log Viewer 日志查看器 Save log as a plain text file 保存日志为文本文件 Save As... 保存为... Clear 清空 Save Log As 保存日志为 MainWindow About 关于 Import Connections from gui-config.json 从gui-config.json导入连接 Export Connections as gui-config.json 将所有连接信息导出为gui-config.json QR Code Not Found 未找到二维码 Can't find any QR code image that contains valid URI on your screen(s). 无法在您的屏幕上找到任何包含有效URI的二维码图像。 Open QR Code Image File 打开二维码图像文件 Open config.json 打开 config.json Invalid 无效 The connection's profile is invalid! 当前连接的配置无效! Connection Manager 连接编辑器 &Connection 连接(&C) &Add 添加(&A) Fi&le 文件(&F) Settin&gs 设置(&S) Help 帮助(&H) &Manually 手动(&M) Add connection manually 手动添加连接 &From QR Code Image File 自二维码图像文件(&Q) From QR code image file 自二维码图像文件 View &Log 查看日志(&L) &Scan QR Code on Screen 扫描屏幕上的二维码(&S) Input to filter 输入以过滤 Show Toolbar 显示工具栏 &Help 帮助(&H) &URI &URI Add connection from URI 从 URI 添加连接 &Delete 删除(&D) &Edit 编辑(&E) &Connect 连接(&C) D&isconnect 断开连接(&D) &Quit 退出(&Q) Ctrl+Q &About 关于(&A) About &Qt 关于 &Qt &General Settings 常规设置(&G) &Share 分享(&S) &Report Bug 报告错误(&R) Test the latency of selected connection 测试所选连接的延迟 Test All C&onnections Latency 测试所有连接的延迟(&O) &Show Filter Bar 显示过滤栏(&S) Ctrl+F &Export as gui-config.json 导出为gui-config.json (&E) Scan &QR Code using Capturer 使用捕获器扫描二维码(&C) Scan QR Code using Capturer 使用捕获器扫描二维码 &Force Connect 强制连接(&F) Force Connect 强制连接 Connect to this connection and disconnect any connections currently using the same local port 连接到该连接并断开占用了相同本地端口的连接 From &config.json 自 &config.json &Save Manually 手动保存(&S) Ctrl+Shift+S &Move Up 上移(&U) Mo&ve Down 下移(&V) &Import Connections from gui-config.json 从 gui-config.json导入连接(&I) Import connections from old version configuration file 从旧版配置文件导入连接 &Test Latency 测试延迟(&T) QObject Failed to communicate with previously running instance of Shadowsocks-Qt5 (PID: %1). It might already crashed. 无法与先前运行的Shadowsocks-Qt5实例(PID: %1)通讯。可能已经崩溃了。 Error 错误 Another instance of Shadowsocks-Qt5 (PID: %1) is already running. 另一个 Shadowsocks-Qt5 (PID: %1) 的实例已经在运行了。 Unnamed Profile 未命名配置 QRCodeCapturer QR Code Capturer 二维码捕获器 QR Capturer 二维码捕获器 QRWidget Generating QR code failed. 二维码生成失败。 SettingsDialog General Settings 常规设置 Toolbar Style 工具栏风格 Icons Only 仅图标 Text Only 仅文本 Text Alongside Icons 文本在图标侧 Text Under Icons 文本在图标下 System Style 系统风格 Allow only one instance running 仅允许一个实例运行 Hide window on startup 启动时隐藏窗口 Need to restart the application for this change to take effect 需重启程序以生效 Use native menu bar 使用原生菜单栏 Start at login 登录时启动 ShareDialog Share Profile 分享配置 Save QR code as an Image file 保存二维码为图像文件 Save QR Code 保存二维码 StatusNotifier Minimise 最小化 Quit 退出 Restore 恢复 URIInputDialog URI Input Dialog URI输入对话框 Please input ss:// URI 请输入 ss:// URI ================================================ FILE: src/i18n/ss-qt5_zh_TW.ts ================================================ ConnectionItem Connected 已連線 Disconnected 已斷線 Timeout 逾時 Error 錯誤 Unknown 不明 s ms 毫秒 connected 已連線 disconnected 已斷線 timed out 逾時 latency test failed 延遲測試失敗 Failed to start 啟動失敗 ConnectionTableModel Name 名稱 Server 伺服器 Status 狀態 Latency 延遲 Local Port 本機埠 Term Usage 本期使用量 Total Usage 使用量總計 Reset Date 重設日期 Last Used 上次使用 EditDialog Profile Editor 設定檔編輯器 Local Server Type 本機伺服器型別 Timeout 逾時 [Deprecated] Turn on one-time authentication and header verification (need server support) [棄用]開啟單次驗證與標頭驗證(需要伺服器支援) Turn on one-time authentication and header verification (need server support) 開啟單次驗證與標頭驗證(需要伺服器支援) One-time authentication 單次驗證 Encryption Method 加密方法 Local Port 本機埠 Local Address 本機位址 Password 密碼 Server Port 伺服器埠 Server Address 伺服器位址 Profile Name 設定檔名稱 Auto connect on application start 程式啟動時自動連線 Debug 偵錯 Log Level 記錄檔等級 Automation 自動化 Reset Data Usage after 重設資料使用量之後 LogDialog Log Viewer 記錄檔檢視器 Save log as a plain text file 儲存記錄檔為文字檔 Save As... 另存新檔... Clear 清除 Save Log As 另存記錄檔 MainWindow About 關於 Import Connections from gui-config.json 自 gui-config.json 匯入連線 Export Connections as gui-config.json 匯出連線為 gui-config.json QR Code Not Found 找不到 QR 碼 Can't find any QR code image that contains valid URI on your screen(s). 在你的螢幕上無法找到任何包含有效 URI 的 QR 碼圖片。 Open QR Code Image File 開啟 QR 碼圖檔 Open config.json 開啟 config.json Invalid 無效 The connection's profile is invalid! 此連線的設定檔無效! Connection Manager 連線管理員 &Connection &連線 &Add &新增 Fi&le &檔案 Settin&gs &設定 Help 說明 &Manually &手動 Add connection manually 手動新增連線 &From QR Code Image File &來自 QR 碼圖檔 From QR code image file 來自 QR 碼圖檔 View &Log &檢視記錄檔 &Scan QR Code on Screen &掃描螢幕上的 QR 碼 Input to filter 輸入以篩選 Show Toolbar 顯示工具列 &Help 說明(&H) &URI &URI Add connection from URI 自 URI 新增連線 &Delete &刪除 &Edit &編輯 &Connect &連線 D&isconnect &中斷連線 &Quit &結束 Ctrl+Q &About &關於 About &Qt 關於 &Qt &General Settings &一般設定 &Share &分享 &Report Bug &回報 Bug Test the latency of selected connection 測試已選連線的延遲 Test All C&onnections Latency 測試&所有連線的延遲 &Show Filter Bar &顯示篩選列 Ctrl+F &Export as gui-config.json &匯出為 gui-config.json Scan &QR Code using Capturer &使用捕捉器掃描 QR 碼 Scan QR Code using Capturer 使用捕捉器掃描 QR 碼 &Force Connect 強迫連線(&E) Force Connect 強迫連線 Connect to this connection and disconnect any connections currently using the same local port 連線至此連線並且中斷使用了相同本機埠的連線 From &config.json 自 &config.json &Save Manually &手動儲存 Ctrl+Shift+S &Move Up &向上移動 Mo&ve Down &向下移動 &Import Connections from gui-config.json &自 gui-config.json 匯入連線 Import connections from old version configuration file 自舊版設定檔匯入連線 &Test Latency &測試延遲 QObject Failed to communicate with previously running instance of Shadowsocks-Qt5 (PID: %1). It might already crashed. 與之前執行的 Shadowsocks-Qt5 執行個體(PID: %1)通訊失敗。或許已損毀。 Error 錯誤 Another instance of Shadowsocks-Qt5 (PID: %1) is already running. 另一個 Shadowsocks-Qt5 (PID: %1) 的執行個體已在執行。 Unnamed Profile 未命名的設定檔 QRCodeCapturer QR Code Capturer QR 碼捕捉器 QR Capturer QR 碼捕捉器 QRWidget Generating QR code failed. QR 碼產生失敗。 SettingsDialog General Settings 一般設定 Toolbar Style 工具列樣式 Icons Only 僅圖示 Text Only 僅文字 Text Alongside Icons 文字在圖示邊 Text Under Icons 文字在圖示下 System Style 系統樣式 Allow only one instance running 僅允許一個執行個體執行 Hide window on startup 啟動時隱藏視窗 Need to restart the application for this change to take effect 需要重新啟動程式方能使此變更生效 Use native menu bar 使用原生選單列 Start at login ShareDialog Share Profile 分享設定檔 Save QR code as an Image file 儲存 QR 碼為圖檔 Save QR Code 儲存 QR 碼 StatusNotifier Minimise 最小化 Quit 結束 Restore 還原 URIInputDialog URI Input Dialog URI 輸入對話方塊 Please input ss:// URI 請輸入 ss:// URI ================================================ FILE: src/icons/Breeze/index.theme ================================================ [Icon Theme] Name=Breeze Directories=actions [actions] Size=22 Context=Actions Type=Fixed ================================================ FILE: src/icons.qrc ================================================ icons/Breeze/actions/application-exit.png icons/Breeze/actions/configure.png icons/Breeze/actions/document-open.png icons/Breeze/actions/document-revert.png icons/Breeze/actions/document-edit.png icons/Breeze/actions/document-import.png icons/Breeze/actions/document-export.png icons/Breeze/actions/document-save.png icons/Breeze/actions/document-share.png icons/Breeze/actions/edit-guides.png icons/Breeze/actions/edit-image-face-recognize.png icons/Breeze/actions/flag.png icons/Breeze/actions/go-down.png icons/Breeze/actions/go-up.png icons/Breeze/actions/help-about.png icons/Breeze/actions/list-add.png icons/Breeze/actions/list-remove.png icons/Breeze/actions/network-connect.png icons/Breeze/actions/network-disconnect.png icons/Breeze/actions/text-field.png icons/Breeze/actions/tools-report-bug.png icons/Breeze/actions/view-list-text.png icons/Breeze/index.theme icons/shadowsocks-qt5.png ================================================ 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 * * 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 * . * * 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 class IP4Validator : public QValidator { public: IP4Validator(QObject *parent = 0); State validate(QString &input, int &) const; }; #endif // IP4VALIDATOR_H ================================================ FILE: src/main.cpp ================================================ #include #include #include #include #include #include #include #include #include #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"); 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 #include #include #include #include #include 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 (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 (&MainWindow::checkCurrentIndex)); connect(ui->connectionView, &QTableView::activated, this, static_cast (&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("

Shadowsocks-Qt5

Version %1
" "Using libQtShadowsocks %2

" "

Copyright © 2014-2018 Symeon Huang " "(" "@librehat)

" "

License: " "GNU Lesser General Public License Version 3
" "Project Hosted at " "" "GitHub

") .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(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 * * 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 * . */ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include #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 ================================================ MainWindow 0 0 480 480 400 400 Connection Manager :/icons/icons/shadowsocks-qt5.png:/icons/icons/shadowsocks-qt5.png Qt::ToolButtonFollowStyle Input to filter true QAbstractItemView::NoEditTriggers false QAbstractItemView::SingleSelection QAbstractItemView::SelectRows false true false false false Show Toolbar false Qt::AllToolBarAreas Qt::ToolButtonFollowStyle false TopToolBarArea false 0 0 480 26 &Connection &Add .. Fi&le Settin&gs &Help &Manually Add connection manually &Scan QR Code on Screen .. &From QR Code Image File From QR code image file &URI Add connection from URI .. &Delete &Edit &Connect D&isconnect .. &Quit Ctrl+Q .. &About About &Qt &General Settings &Share &Report Bug &Test Latency Test the latency of selected connection Test All C&onnections Latency &Import Connections from gui-config.json Import connections from old version configuration file .. From &config.json .. &Save Manually Ctrl+Shift+S .. &Move Up .. Mo&ve Down true true &Show Filter Bar Ctrl+F &Export as gui-config.json Scan &QR Code using Capturer Scan QR Code using Capturer &Force Connect Connect to this connection and disconnect any connections currently using the same local port actionShowFilterBar toggled(bool) filterLineEdit setVisible(bool) -1 -1 239 88 ================================================ 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 * * 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 * . */ #ifndef PORTVALIDATOR_H #define PORTVALIDATOR_H #include 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 #include #include #include #include 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 screens = qApp->screens(); for (QList::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 * * 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 * . */ #ifndef QRCODECAPTURER_H #define QRCODECAPTURER_H #include 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 #include #include #include #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 * * 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 * . */ #ifndef QRWIDGET_H #define QRWIDGET_H #include #include 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 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 * * 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 * . */ #ifndef SETTINGSDIALOG_H #define SETTINGSDIALOG_H #include #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 ================================================ SettingsDialog 0 0 340 217 General Settings Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok Toolbar Style 0 0 Icons Only Text Only Text Alongside Icons Text Under Icons System Style Allow only one instance running Hide window on startup Need to restart the application for this change to take effect Use native menu bar Start at login buttonBox rejected() SettingsDialog reject() 316 260 286 274 ================================================ 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 #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 * * 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 * . */ #ifndef SHAREDIALOG_H #define SHAREDIALOG_H #include 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 ================================================ ShareDialog 0 0 300 360 300 360 Share Profile true 0 10 256 256 background-color: "transparent" false true Save QR code as an Image file QRWidget QWidget
qrwidget.h
1
================================================ 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 * * 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 * . */ #ifndef SQPROFILE_H #define SQPROFILE_H #include #include #include #include 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 QStringList SSValidator::supportedMethodList() { std::vector 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 * * 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 * . */ #ifndef SSVALIDATOR_H #define SSVALIDATOR_H #include #include 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 #ifdef Q_OS_LINUX #include #include #include #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 * * 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 * . */ #ifndef STATUSNOTIFIER_H #define STATUSNOTIFIER_H #include #include #include 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 ================================================ i18n/ss-qt5_zh_CN.qm i18n/ss-qt5_zh_TW.qm ================================================ FILE: src/urihelper.cpp ================================================ #include "urihelper.h" #include QImage URIHelper::convertToGrey(const QImage &input) { if (input.isNull()) { return QImage(); } QImage ret(input.width(), input.height(), QImage::Format_Indexed8); QVector 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 * * 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 * . */ #ifndef URIHELPER_H #define URIHELPER_H #include #include 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 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 * * 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 * . */ #ifndef URIINPUTDIALOG_H #define URIINPUTDIALOG_H #include 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 ================================================ URIInputDialog 0 0 400 159 URI Input Dialog Please input ss:// URI 300 0 Qt::Vertical 20 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox rejected() URIInputDialog reject() 316 260 286 274