├── .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.qrc ├── icons ├── Breeze │ ├── actions │ │ ├── application-exit.png │ │ ├── configure.png │ │ ├── document-edit.png │ │ ├── document-export.png │ │ ├── document-import.png │ │ ├── document-open.png │ │ ├── document-revert.png │ │ ├── document-save.png │ │ ├── document-share.png │ │ ├── edit-guides.png │ │ ├── edit-image-face-recognize.png │ │ ├── flag.png │ │ ├── go-down.png │ │ ├── go-up.png │ │ ├── help-about.png │ │ ├── list-add.png │ │ ├── list-remove.png │ │ ├── network-connect.png │ │ ├── network-disconnect.png │ │ ├── text-field.png │ │ ├── tools-report-bug.png │ │ └── view-list-text.png │ └── index.theme ├── shadowsocks-qt5.icns └── shadowsocks-qt5.png ├── 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.ico ├── ss-qt5.rc ├── ssvalidator.cpp ├── ssvalidator.h ├── statusnotifier.cpp ├── statusnotifier.h ├── translations.qrc ├── urihelper.cpp ├── urihelper.h ├── uriinputdialog.cpp ├── uriinputdialog.h └── uriinputdialog.ui /.gitignore: -------------------------------------------------------------------------------- 1 | *.user* 2 | *.swp 3 | .directory 4 | build/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: cpp 4 | cache: apt 5 | addons: 6 | apt: 7 | sources: 8 | - ubuntu-toolchain-r-test 9 | - sourceline: 'ppa:beineri/opt-qt562-trusty' 10 | packages: 11 | - g++-7 12 | - cmake 13 | - qt56base 14 | - libbotan1.10-dev 15 | - libqrencode-dev 16 | - libzbar-dev 17 | 18 | env: 19 | - MATRIX_EVAL="CC=gcc-7 && CXX=g++-7" 20 | 21 | branches: 22 | only: 23 | - 'master' 24 | 25 | before_install: 26 | - eval "${MATRIX_EVAL}" 27 | # Use the latest libQtShadowsocks code 28 | - git clone https://github.com/shadowsocks/libQtShadowsocks.git 29 | - mkdir libQtShadowsocks/build 30 | - cd libQtShadowsocks/build 31 | - 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" 32 | - make 33 | - sudo make install 34 | - cd - 35 | 36 | script: 37 | - mkdir build && cd build 38 | - 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" 39 | - make 40 | 41 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | project(Shadowsocks-Qt5 4 | VERSION 3.0.1 5 | LANGUAGES CXX) 6 | 7 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 8 | set(CMAKE_AUTOMOC ON) 9 | set(CMAKE_AUTOUIC ON) 10 | set(CMAKE_AUTORCC ON) 11 | set(CMAKE_CXX_STANDARD 14) 12 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 13 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 14 | 15 | find_package(Qt5Core) 16 | find_package(Qt5Gui) 17 | find_package(Qt5Widgets) 18 | find_package(Qt5Network) 19 | if(UNIX AND NOT APPLE) 20 | find_package(Qt5DBus) 21 | endif() 22 | find_package(PkgConfig) 23 | 24 | pkg_check_modules(QSS REQUIRED QtShadowsocks>=2.0.0) 25 | find_library(QSS_LIBRARY_VAR 26 | NAMES ${QSS_LIBRARIES} 27 | HINTS ${QSS_LIBRARY_DIRS} ${QSS_LIBDIR}) 28 | 29 | pkg_check_modules(QRENCODE REQUIRED libqrencode) 30 | find_library(QRENCODE_LIBRARY_VAR 31 | NAMES ${QRENCODE_LIBRARIES} 32 | HINTS ${QRENCODE_LIBRARY_DIRS} ${QRENCODE_LIBDIR}) 33 | 34 | pkg_check_modules(ZBAR REQUIRED zbar) 35 | find_library(ZBAR_LIBRARY_VAR 36 | NAMES ${ZBAR_LIBRARIES} 37 | HINTS ${ZBAR_LIBRARY_DIRS} ${ZBAR_LIBDIR}) 38 | 39 | if(WIN32 OR APPLE) 40 | add_definitions(-DFD_SETSIZE=1024) 41 | endif() 42 | 43 | add_subdirectory(src) 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Shadowsocks-Qt5 2 | =============== 3 | 4 | **This project is no longer being maintained** 5 | 6 | [![Build Status](https://travis-ci.org/shadowsocks/shadowsocks-qt5.svg?branch=master)](https://travis-ci.org/shadowsocks/shadowsocks-qt5) 7 | 8 | Please check [project's wiki](https://github.com/shadowsocks/shadowsocks-qt5/wiki) for "how-tos". 9 | 10 | Introduction 11 | ------------ 12 | 13 | Shadowsocks-Qt5 is a native and cross-platform [shadowsocks](http://shadowsocks.org) GUI client with advanced features. 14 | 15 | Features 16 | -------- 17 | 18 | - Shadowsocks-Qt5 is written in C++ with Qt 5. 19 | - Support traffic statistics 20 | - Support server latency (lag) test 21 | - Use multiple profiles simultaneously 22 | - `config.ini` is located under `~/.config/shadowsocks-qt5/` on \*nix platforms, or under the application's directory on Windows. 23 | 24 | LICENSE 25 | ------- 26 | 27 | ![](http://www.gnu.org/graphics/lgplv3-147x51.png) 28 | 29 | Copyright © 2014-2017 Symeon Huang 30 | 31 | This project is licensed under version 3 of the GNU Lesser General Public License. 32 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(APP_NAME "ss-qt5") 2 | add_definitions(-DAPP_VERSION="${PROJECT_VERSION}") 3 | 4 | set(SOURCE 5 | icons.qrc 6 | translations.qrc 7 | main.cpp 8 | mainwindow.cpp 9 | ip4validator.cpp 10 | portvalidator.cpp 11 | ssvalidator.cpp 12 | qrwidget.cpp 13 | sharedialog.cpp 14 | editdialog.cpp 15 | connection.cpp 16 | confighelper.cpp 17 | urihelper.cpp 18 | uriinputdialog.cpp 19 | sqprofile.cpp 20 | settingsdialog.cpp 21 | statusnotifier.cpp 22 | connectiontablemodel.cpp 23 | connectionitem.cpp 24 | qrcodecapturer.cpp 25 | ) 26 | 27 | if (WIN32) 28 | list(APPEND SOURCE ss-qt5.rc) 29 | endif() 30 | 31 | add_executable(${APP_NAME} WIN32 ${SOURCE}) 32 | 33 | target_link_libraries(${APP_NAME} 34 | PUBLIC Qt5::Core 35 | PUBLIC Qt5::Gui 36 | PUBLIC Qt5::Widgets 37 | PUBLIC Qt5::Network 38 | PRIVATE ${QSS_LIBRARY_VAR} 39 | PRIVATE ${QRENCODE_LIBRARY_VAR} 40 | PRIVATE ${ZBAR_LIBRARY_VAR}) 41 | target_include_directories(${APP_NAME} 42 | PRIVATE ${QSS_INCLUDE_DIRS} 43 | PRIVATE ${QRENCODE_INCLUDE_DIRS} 44 | PRIVATE ${ZBAR_INCLUDE_DIRS}) 45 | 46 | if (UNIX AND NOT APPLE) 47 | target_link_libraries(${APP_NAME} PRIVATE Qt5::DBus) 48 | endif() 49 | 50 | install(TARGETS ${APP_NAME} 51 | RUNTIME DESTINATION bin) 52 | 53 | if (UNIX) 54 | install(FILES shadowsocks-qt5.desktop DESTINATION share/applications) 55 | install(FILES icons/shadowsocks-qt5.png DESTINATION share/icons/hicolor/512x512/apps) 56 | endif() 57 | -------------------------------------------------------------------------------- /src/confighelper.cpp: -------------------------------------------------------------------------------- 1 | #include "confighelper.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | ConfigHelper::ConfigHelper(const QString &configuration, QObject *parent) : 11 | QObject(parent), 12 | configFile(configuration) 13 | { 14 | settings = new QSettings(configFile, QSettings::IniFormat, this); 15 | readGeneralSettings(); 16 | } 17 | 18 | const QString ConfigHelper::profilePrefix = "Profile"; 19 | 20 | void ConfigHelper::save(const ConnectionTableModel &model) 21 | { 22 | int size = model.rowCount(); 23 | settings->beginWriteArray(profilePrefix); 24 | for (int i = 0; i < size; ++i) { 25 | settings->setArrayIndex(i); 26 | Connection *con = model.getItem(i)->getConnection(); 27 | QVariant value = QVariant::fromValue(con->getProfile()); 28 | settings->setValue("SQProfile", value); 29 | } 30 | settings->endArray(); 31 | 32 | settings->setValue("ToolbarStyle", QVariant(toolbarStyle)); 33 | settings->setValue("HideWindowOnStartup", QVariant(hideWindowOnStartup)); 34 | settings->setValue("StartAtLogin", QVariant(startAtLogin)); 35 | settings->setValue("OnlyOneInstance", QVariant(onlyOneInstace)); 36 | settings->setValue("ShowToolbar", QVariant(showToolbar)); 37 | settings->setValue("ShowFilterBar", QVariant(showFilterBar)); 38 | settings->setValue("NativeMenuBar", QVariant(nativeMenuBar)); 39 | settings->setValue("ConfigVersion", QVariant(2.6)); 40 | } 41 | 42 | void ConfigHelper::importGuiConfigJson(ConnectionTableModel *model, const QString &file) 43 | { 44 | QFile JSONFile(file); 45 | JSONFile.open(QIODevice::ReadOnly | QIODevice::Text); 46 | if (!JSONFile.isOpen()) { 47 | qCritical() << "Error: cannot open " << file; 48 | return; 49 | } 50 | if(!JSONFile.isReadable()) { 51 | qCritical() << "Error: cannot read " << file; 52 | return; 53 | } 54 | 55 | QJsonParseError pe; 56 | QJsonDocument JSONDoc = QJsonDocument::fromJson(JSONFile.readAll(), &pe); 57 | JSONFile.close(); 58 | if (pe.error != QJsonParseError::NoError) { 59 | qCritical() << pe.errorString(); 60 | } 61 | if (JSONDoc.isEmpty()) { 62 | qCritical() << "JSON Document" << file << "is empty!"; 63 | return; 64 | } 65 | QJsonObject JSONObj = JSONDoc.object(); 66 | QJsonArray CONFArray = JSONObj["configs"].toArray(); 67 | if (CONFArray.isEmpty()) { 68 | qWarning() << "configs in " << file << " is empty."; 69 | return; 70 | } 71 | 72 | for (QJsonArray::iterator it = CONFArray.begin(); it != CONFArray.end(); ++it) { 73 | QJsonObject json = (*it).toObject(); 74 | SQProfile p; 75 | if (!json["server_port"].isString()) { 76 | /* 77 | * shadowsocks-csharp uses integers to store ports directly. 78 | */ 79 | p.name = json["remarks"].toString(); 80 | p.serverPort = json["server_port"].toInt(); 81 | //shadowsocks-csharp has only global local port (all profiles use the same port) 82 | p.localPort = JSONObj["localPort"].toInt(); 83 | if (JSONObj["shareOverLan"].toBool()) { 84 | /* 85 | * it can only configure share over LAN or not (also a global value) 86 | * which is basically 0.0.0.0 or 127.0.0.1 (which is the default) 87 | */ 88 | p.localAddress = QString("0.0.0.0"); 89 | } 90 | } else { 91 | /* 92 | * Otherwise, the gui-config is from legacy shadowsocks-qt5 (v0.x) 93 | */ 94 | p.name = json["profile"].toString(); 95 | p.serverPort = json["server_port"].toString().toUShort(); 96 | p.localAddress = json["local_address"].toString(); 97 | p.localPort = json["local_port"].toString().toUShort(); 98 | p.timeout = json["timeout"].toString().toInt(); 99 | } 100 | p.serverAddress = json["server"].toString(); 101 | p.method = json["method"].toString(); 102 | p.password = json["password"].toString(); 103 | Connection *con = new Connection(p, this); 104 | model->appendConnection(con); 105 | } 106 | } 107 | 108 | void ConfigHelper::exportGuiConfigJson(const ConnectionTableModel &model, const QString &file) 109 | { 110 | QJsonArray confArray; 111 | int size = model.rowCount(); 112 | for (int i = 0; i < size; ++i) { 113 | Connection *con = model.getItem(i)->getConnection(); 114 | QJsonObject json; 115 | json["remarks"] = QJsonValue(con->profile.name); 116 | json["method"] = QJsonValue(con->profile.method.toLower()); 117 | json["password"] = QJsonValue(con->profile.password); 118 | json["server_port"] = QJsonValue(con->profile.serverPort); 119 | json["server"] = QJsonValue(con->profile.serverAddress); 120 | confArray.append(QJsonValue(json)); 121 | } 122 | 123 | QJsonObject JSONObj; 124 | JSONObj["configs"] = QJsonValue(confArray); 125 | JSONObj["localPort"] = QJsonValue(1080); 126 | JSONObj["shareOverLan"] = QJsonValue(false); 127 | 128 | QJsonDocument JSONDoc(JSONObj); 129 | 130 | QFile JSONFile(file); 131 | JSONFile.open(QIODevice::WriteOnly | QIODevice::Text); 132 | if (!JSONFile.isOpen()) { 133 | qCritical() << "Error: cannot open " << file; 134 | return; 135 | } 136 | if(!JSONFile.isWritable()) { 137 | qCritical() << "Error: cannot write into " << file; 138 | return; 139 | } 140 | 141 | JSONFile.write(JSONDoc.toJson()); 142 | JSONFile.close(); 143 | } 144 | 145 | Connection* ConfigHelper::configJsonToConnection(const QString &file) 146 | { 147 | QFile JSONFile(file); 148 | JSONFile.open(QIODevice::ReadOnly | QIODevice::Text); 149 | if (!JSONFile.isOpen()) { 150 | qCritical() << "Error: cannot open " << file; 151 | } 152 | if(!JSONFile.isReadable()) { 153 | qCritical() << "Error: cannot read " << file; 154 | } 155 | 156 | QJsonParseError pe; 157 | QJsonDocument JSONDoc = QJsonDocument::fromJson(JSONFile.readAll(), &pe); 158 | JSONFile.close(); 159 | if (pe.error != QJsonParseError::NoError) { 160 | qCritical() << pe.errorString(); 161 | } 162 | if (JSONDoc.isEmpty()) { 163 | qCritical() << "JSON Document" << file << "is empty!"; 164 | return nullptr; 165 | } 166 | QJsonObject configObj = JSONDoc.object(); 167 | SQProfile p; 168 | p.serverAddress = configObj["server"].toString(); 169 | p.serverPort = configObj["server_port"].toInt(); 170 | p.localAddress = configObj["local_address"].toString(); 171 | p.localPort = configObj["local_port"].toInt(); 172 | p.method = configObj["method"].toString(); 173 | p.password = configObj["password"].toString(); 174 | p.timeout = configObj["timeout"].toInt(); 175 | Connection *con = new Connection(p, this); 176 | return con; 177 | } 178 | 179 | int ConfigHelper::getToolbarStyle() const 180 | { 181 | return toolbarStyle; 182 | } 183 | 184 | bool ConfigHelper::isHideWindowOnStartup() const 185 | { 186 | return hideWindowOnStartup; 187 | } 188 | 189 | bool ConfigHelper::isStartAtLogin() const 190 | { 191 | return startAtLogin; 192 | } 193 | 194 | bool ConfigHelper::isOnlyOneInstance() const 195 | { 196 | return onlyOneInstace; 197 | } 198 | 199 | bool ConfigHelper::isShowToolbar() const 200 | { 201 | return showToolbar; 202 | } 203 | 204 | bool ConfigHelper::isShowFilterBar() const 205 | { 206 | return showFilterBar; 207 | } 208 | 209 | bool ConfigHelper::isNativeMenuBar() const 210 | { 211 | return nativeMenuBar; 212 | } 213 | 214 | void ConfigHelper::setGeneralSettings(int ts, bool hide, bool sal, bool oneInstance, bool nativeMB) 215 | { 216 | if (toolbarStyle != ts) { 217 | emit toolbarStyleChanged(static_cast(ts)); 218 | } 219 | toolbarStyle = ts; 220 | hideWindowOnStartup = hide; 221 | startAtLogin = sal; 222 | onlyOneInstace = oneInstance; 223 | nativeMenuBar = nativeMB; 224 | } 225 | 226 | void ConfigHelper::setShowToolbar(bool show) 227 | { 228 | showToolbar = show; 229 | } 230 | 231 | void ConfigHelper::setShowFilterBar(bool show) 232 | { 233 | showFilterBar = show; 234 | } 235 | 236 | void ConfigHelper::read(ConnectionTableModel *model) 237 | { 238 | qreal configVer = settings->value("ConfigVersion", QVariant(2.4)).toReal(); 239 | int size = settings->beginReadArray(profilePrefix); 240 | for (int i = 0; i < size; ++i) { 241 | settings->setArrayIndex(i); 242 | QVariant value = settings->value("SQProfile"); 243 | SQProfile profile = value.value(); 244 | checkProfileDataUsageReset(profile); 245 | Connection *con = new Connection(profile, this); 246 | model->appendConnection(con); 247 | } 248 | settings->endArray(); 249 | readGeneralSettings(); 250 | } 251 | 252 | void ConfigHelper::readGeneralSettings() 253 | { 254 | toolbarStyle = settings->value("ToolbarStyle", QVariant(4)).toInt(); 255 | startAtLogin = settings->value("StartAtLogin").toBool(); 256 | hideWindowOnStartup = settings->value("HideWindowOnStartup").toBool(); 257 | onlyOneInstace = settings->value("OnlyOneInstance", QVariant(true)).toBool(); 258 | showToolbar = settings->value("ShowToolbar", QVariant(true)).toBool(); 259 | showFilterBar = settings->value("ShowFilterBar", QVariant(true)).toBool(); 260 | nativeMenuBar = settings->value("NativeMenuBar", QVariant(false)).toBool(); 261 | } 262 | 263 | void ConfigHelper::checkProfileDataUsageReset(SQProfile &profile) 264 | { 265 | QDate currentDate = QDate::currentDate(); 266 | if (profile.nextResetDate.isNull()){//invalid if the config.ini is old 267 | //the default reset day is 1 of every month 268 | profile.nextResetDate = QDate(currentDate.year(), currentDate.month(), 1); 269 | qDebug() << "config.ini upgraded from old version"; 270 | profile.totalUsage += profile.currentUsage;//we used to use sent and received 271 | } 272 | 273 | if (profile.nextResetDate < currentDate) {//not <= because that'd casue multiple reset on this day 274 | profile.currentUsage = 0; 275 | while (profile.nextResetDate <= currentDate) { 276 | profile.nextResetDate = profile.nextResetDate.addMonths(1); 277 | } 278 | } 279 | } 280 | 281 | void ConfigHelper::startAllAutoStart(const ConnectionTableModel& model) 282 | { 283 | int size = model.rowCount(); 284 | for (int i = 0; i < size; ++i) { 285 | Connection *con = model.getItem(i)->getConnection(); 286 | if (con->profile.autoStart) { 287 | con->start(); 288 | } 289 | } 290 | } 291 | 292 | void ConfigHelper::setStartAtLogin() 293 | { 294 | QString applicationName = "Shadowsocks-Qt5"; 295 | QString applicationFilePath = QDir::toNativeSeparators(QCoreApplication::applicationFilePath()); 296 | #if defined(Q_OS_WIN) 297 | QSettings settings("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat); 298 | #elif defined(Q_OS_LINUX) 299 | QFile file(QDir::homePath() + "/.config/autostart/shadowsocks-qt5.desktop"); 300 | QString fileContent( 301 | "[Desktop Entry]\n" 302 | "Name=%1\n" 303 | "Exec=%2\n" 304 | "Type=Application\n" 305 | "Terminal=false\n" 306 | "X-GNOME-Autostart-enabled=true\n"); 307 | #elif defined(Q_OS_MAC) 308 | QFile file(QDir::homePath() + "/Library/LaunchAgents/org.shadowsocks.shadowsocks-qt5.launcher.plist"); 309 | QString fileContent( 310 | "\n" 311 | "\n" 312 | "\n" 313 | "\n" 314 | " Label\n" 315 | " org.shadowsocks.shadowsocks-qt5.launcher\n" 316 | " LimitLoadToSessionType\n" 317 | " Aqua\n" 318 | " ProgramArguments\n" 319 | " \n" 320 | " %2\n" 321 | " \n" 322 | " RunAtLoad\n" 323 | " \n" 324 | " StandardErrorPath\n" 325 | " /dev/null\n" 326 | " StandardOutPath\n" 327 | " /dev/null\n" 328 | "\n" 329 | "\n"); 330 | #else 331 | QFile file; 332 | QString fileContent; 333 | #endif 334 | 335 | if (this->isStartAtLogin()) { 336 | // Create start up item 337 | #if defined(Q_OS_WIN) 338 | settings.setValue(applicationName, applicationFilePath); 339 | #else 340 | fileContent.replace("%1", applicationName); 341 | fileContent.replace("%2", applicationFilePath); 342 | if ( file.open(QIODevice::WriteOnly) ) { 343 | file.write(fileContent.toUtf8()); 344 | file.close(); 345 | } 346 | #endif 347 | } else { 348 | // Delete start up item 349 | #if defined(Q_OS_WIN) 350 | settings.remove(applicationName); 351 | #else 352 | if ( file.exists() ) { 353 | file.remove(); 354 | } 355 | #endif 356 | } 357 | } 358 | 359 | QByteArray ConfigHelper::getMainWindowGeometry() const 360 | { 361 | return settings->value("MainWindowGeometry").toByteArray(); 362 | } 363 | 364 | QByteArray ConfigHelper::getMainWindowState() const 365 | { 366 | return settings->value("MainWindowState").toByteArray(); 367 | } 368 | 369 | QByteArray ConfigHelper::getTableGeometry() const 370 | { 371 | return settings->value("MainTableGeometry").toByteArray(); 372 | } 373 | 374 | QByteArray ConfigHelper::getTableState() const 375 | { 376 | return settings->value("MainTableState").toByteArray(); 377 | } 378 | 379 | void ConfigHelper::setMainWindowGeometry(const QByteArray &geometry) 380 | { 381 | settings->setValue("MainWindowGeometry", QVariant(geometry)); 382 | } 383 | 384 | void ConfigHelper::setMainWindowState(const QByteArray &state) 385 | { 386 | settings->setValue("MainWindowState", QVariant(state)); 387 | } 388 | 389 | void ConfigHelper::setTableGeometry(const QByteArray &geometry) 390 | { 391 | settings->setValue("MainTableGeometry", QVariant(geometry)); 392 | } 393 | 394 | void ConfigHelper::setTableState(const QByteArray &state) 395 | { 396 | settings->setValue("MainTableState", QVariant(state)); 397 | } 398 | -------------------------------------------------------------------------------- /src/confighelper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | */ 18 | 19 | #ifndef CONFIGHELPER_H 20 | #define CONFIGHELPER_H 21 | 22 | #include 23 | #include "connectiontablemodel.h" 24 | #include "connection.h" 25 | 26 | class ConfigHelper : public QObject 27 | { 28 | Q_OBJECT 29 | 30 | public: 31 | /* 32 | * Construct a ConfigHelper object using specified configuration file 33 | * This constructor will call readGeneralSettings(). 34 | */ 35 | explicit ConfigHelper(const QString &configuration, QObject *parent = nullptr); 36 | 37 | /* 38 | * Call read() function to read all connection profiles into 39 | * specified ConnectionTableModel. 40 | * This function also calls readGeneralSettings(). 41 | */ 42 | void read(ConnectionTableModel *model); 43 | 44 | /* 45 | * readGeneralSettings() only reads General settings and store them into 46 | * member variables. 47 | */ 48 | void readGeneralSettings(); 49 | 50 | void save(const ConnectionTableModel &model); 51 | 52 | void importGuiConfigJson(ConnectionTableModel *model, const QString &file); 53 | 54 | //the format is only compatible with shadowsocks-csharp (shadowsocks-windows) 55 | void exportGuiConfigJson(const ConnectionTableModel& model, const QString &file); 56 | 57 | Connection* configJsonToConnection(const QString &file); 58 | 59 | //start those connections marked as auto-start 60 | void startAllAutoStart(const ConnectionTableModel& model); 61 | 62 | //create or delete start up item for shadowsocks-qt5 63 | void setStartAtLogin(); 64 | 65 | /* some functions used to communicate with SettingsDialog */ 66 | int getToolbarStyle() const; 67 | bool isHideWindowOnStartup() const; 68 | bool isStartAtLogin() const; 69 | bool isOnlyOneInstance() const; 70 | bool isShowToolbar() const; 71 | bool isShowFilterBar() const; 72 | bool isNativeMenuBar() const; 73 | void setGeneralSettings(int ts, bool hide, bool automaticStartUp, bool oneInstance, bool nativeMB); 74 | void setMainWindowGeometry(const QByteArray &geometry); 75 | void setMainWindowState(const QByteArray &state); 76 | void setTableGeometry(const QByteArray &geometry); 77 | void setTableState(const QByteArray &state); 78 | 79 | QByteArray getMainWindowGeometry() const; 80 | QByteArray getMainWindowState() const; 81 | QByteArray getTableGeometry() const; 82 | QByteArray getTableState() const; 83 | 84 | public slots: 85 | void setShowToolbar(bool show); 86 | void setShowFilterBar(bool show); 87 | 88 | signals: 89 | void toolbarStyleChanged(const Qt::ToolButtonStyle); 90 | 91 | private: 92 | int toolbarStyle; 93 | bool hideWindowOnStartup; 94 | bool startAtLogin; 95 | bool onlyOneInstace; 96 | bool showToolbar; 97 | bool showFilterBar; 98 | bool nativeMenuBar; 99 | QSettings *settings; 100 | QString configFile; 101 | 102 | void checkProfileDataUsageReset(SQProfile &profile); 103 | 104 | static const QString profilePrefix; 105 | }; 106 | 107 | #endif // CONFIGHELPER_H 108 | -------------------------------------------------------------------------------- /src/connection.cpp: -------------------------------------------------------------------------------- 1 | #include "connection.h" 2 | #include "ssvalidator.h" 3 | #include 4 | #include 5 | 6 | Connection::Connection(QObject *parent) : 7 | QObject(parent), 8 | running(false) 9 | {} 10 | 11 | Connection::Connection(const SQProfile &_profile, QObject *parent) : 12 | Connection(parent) 13 | { 14 | profile = _profile; 15 | } 16 | 17 | Connection::Connection(QString uri, QObject *parent) : 18 | Connection(parent) 19 | { 20 | profile = SQProfile(uri); 21 | } 22 | 23 | Connection::~Connection() 24 | { 25 | stop(); 26 | } 27 | 28 | const SQProfile& Connection::getProfile() const 29 | { 30 | return profile; 31 | } 32 | 33 | const QString& Connection::getName() const 34 | { 35 | return profile.name; 36 | } 37 | 38 | QByteArray Connection::getURI() const 39 | { 40 | std::string uri = profile.toProfile().toUriSip002(); 41 | return QByteArray(uri.data(), uri.length()); 42 | } 43 | 44 | bool Connection::isValid() const 45 | { 46 | if (profile.serverAddress.isEmpty() || profile.localAddress.isEmpty() || profile.timeout < 1 || !SSValidator::validateMethod(profile.method)) { 47 | return false; 48 | } 49 | else { 50 | return true; 51 | } 52 | } 53 | 54 | const bool &Connection::isRunning() const 55 | { 56 | return running; 57 | } 58 | 59 | void Connection::latencyTest() 60 | { 61 | QHostAddress serverAddr(profile.serverAddress); 62 | if (serverAddr.isNull()) { 63 | QHostInfo::lookupHost(profile.serverAddress, this, SLOT(onServerAddressLookedUp(QHostInfo))); 64 | } else { 65 | testAddressLatency(serverAddr); 66 | } 67 | } 68 | 69 | void Connection::start() 70 | { 71 | profile.lastTime = QDateTime::currentDateTime(); 72 | //perform a latency test if the latency is unknown 73 | if (profile.latency == SQProfile::LATENCY_UNKNOWN) { 74 | latencyTest(); 75 | } 76 | 77 | controller = std::make_unique(profile.toProfile(), true, false); 78 | connect(controller.get(), &QSS::Controller::runningStateChanged, [&](bool run){ 79 | running = run; 80 | emit stateChanged(run); 81 | }); 82 | connect(controller.get(), &QSS::Controller::tcpLatencyAvailable, this, &Connection::onLatencyAvailable); 83 | connect(controller.get(), &QSS::Controller::newBytesReceived, this, &Connection::onNewBytesTransmitted); 84 | connect(controller.get(), &QSS::Controller::newBytesSent, this, &Connection::onNewBytesTransmitted); 85 | 86 | if (!controller->start()) { 87 | emit startFailed(); 88 | } 89 | } 90 | 91 | void Connection::stop() 92 | { 93 | if (running) { 94 | controller.reset(); 95 | } 96 | } 97 | 98 | void Connection::testAddressLatency(const QHostAddress &addr) 99 | { 100 | QSS::AddressTester *addrTester = new QSS::AddressTester(addr, profile.serverPort, this); 101 | connect(addrTester, &QSS::AddressTester::connectivityTestFinished, this, &Connection::onConnectivityTestFinished, Qt::QueuedConnection); 102 | connect(addrTester, &QSS::AddressTester::lagTestFinished, this, &Connection::onLatencyAvailable, Qt::QueuedConnection); 103 | QSS::Profile qProfile = profile.toProfile(); 104 | addrTester->startConnectivityTest(qProfile.method(), qProfile.password()); 105 | } 106 | 107 | void Connection::onNewBytesTransmitted(const quint64 &b) 108 | { 109 | profile.currentUsage += b; 110 | profile.totalUsage += b; 111 | emit dataUsageChanged(profile.currentUsage, profile.totalUsage); 112 | } 113 | 114 | void Connection::onServerAddressLookedUp(const QHostInfo &host) 115 | { 116 | if (host.error() == QHostInfo::NoError) { 117 | testAddressLatency(host.addresses().first()); 118 | } else { 119 | onLatencyAvailable(SQProfile::LATENCY_ERROR); 120 | } 121 | } 122 | 123 | void Connection::onLatencyAvailable(const int latency) 124 | { 125 | profile.latency = latency; 126 | emit latencyAvailable(latency); 127 | } 128 | 129 | void Connection::onConnectivityTestFinished(bool con) 130 | { 131 | QSS::AddressTester* tester = qobject_cast(sender()); 132 | if (!con) { 133 | disconnect(tester, &QSS::AddressTester::lagTestFinished, this, &Connection::onLatencyAvailable); 134 | this->onLatencyAvailable(SQProfile::LATENCY_ERROR); 135 | qWarning("Internet connectivity test failed. Please check the connection's profile and your firewall settings."); 136 | } 137 | tester->deleteLater(); 138 | } 139 | -------------------------------------------------------------------------------- /src/connection.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | */ 18 | 19 | #ifndef CONNECTION_H 20 | #define CONNECTION_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include "sqprofile.h" 26 | 27 | class Connection : public QObject 28 | { 29 | Q_OBJECT 30 | public: 31 | Connection(QObject *parent = 0); 32 | Connection(const SQProfile &_profile, QObject *parent = 0); 33 | Connection(QString uri, QObject *parent = 0); 34 | ~Connection(); 35 | 36 | Connection(const Connection&) = delete; 37 | Connection(Connection&&) = default; 38 | 39 | const SQProfile &getProfile() const; 40 | const QString &getName() const; 41 | QByteArray getURI() const; 42 | bool isValid() const; 43 | const bool &isRunning() const; 44 | void latencyTest(); 45 | 46 | signals: 47 | void stateChanged(bool started); 48 | void latencyAvailable(const int); 49 | void newLogAvailable(const QString &); 50 | void dataUsageChanged(const quint64 ¤t, const quint64 &total); 51 | void startFailed(); 52 | 53 | public slots: 54 | void start(); 55 | void stop(); 56 | 57 | private: 58 | std::unique_ptr controller; 59 | SQProfile profile; 60 | bool running; 61 | 62 | void testAddressLatency(const QHostAddress &addr); 63 | 64 | friend class EditDialog; 65 | friend class ConfigHelper; 66 | friend class StatusDialog; 67 | friend class ConnectionItem; 68 | 69 | private slots: 70 | void onNewBytesTransmitted(const quint64 &); 71 | void onServerAddressLookedUp(const QHostInfo &host); 72 | void onLatencyAvailable(const int); 73 | void onConnectivityTestFinished(bool); 74 | }; 75 | Q_DECLARE_METATYPE(Connection*) 76 | 77 | #endif // CONNECTION_H 78 | -------------------------------------------------------------------------------- /src/connectionitem.cpp: -------------------------------------------------------------------------------- 1 | #include "connectionitem.h" 2 | #include 3 | #include 4 | 5 | ConnectionItem::ConnectionItem(Connection *_con, QObject *parent) : 6 | QObject(parent), 7 | con(_con) 8 | { 9 | if (con) { 10 | con->setParent(this); 11 | connect(con, &Connection::stateChanged, this, &ConnectionItem::onConnectionStateChanged); 12 | connect(con, &Connection::stateChanged, this, &ConnectionItem::stateChanged); 13 | connect(con, &Connection::dataUsageChanged, this, &ConnectionItem::dataUsageChanged); 14 | connect(con, &Connection::latencyAvailable, this, &ConnectionItem::onConnectionPingFinished); 15 | connect(con, &Connection::latencyAvailable, this, &ConnectionItem::latencyChanged); 16 | connect(con, &Connection::startFailed, this, &ConnectionItem::onStartFailed); 17 | } 18 | } 19 | 20 | const QStringList ConnectionItem::bytesUnits = QStringList() 21 | << " B" << " KiB" << " MiB" << " GiB" << " TiB" 22 | << " PiB" << " EiB" << " ZiB" << " YiB"; 23 | 24 | int ConnectionItem::columnCount() 25 | { 26 | return 9; 27 | } 28 | 29 | QVariant ConnectionItem::data(int column, int role) const 30 | { 31 | if (!con) { 32 | return QVariant(); 33 | } 34 | 35 | if (role == Qt::DisplayRole || role == Qt::EditRole) { 36 | switch (column) { 37 | case 0://name 38 | return QVariant(con->profile.name); 39 | case 1://server 40 | return QVariant(con->profile.serverAddress); 41 | case 2://status 42 | return con->isRunning() ? QVariant(tr("Connected")) 43 | : QVariant(tr("Disconnected")); 44 | case 3://latency 45 | if (role == Qt::DisplayRole) { 46 | return QVariant(convertLatencyToString(con->profile.latency)); 47 | } else { 48 | return QVariant(con->profile.latency); 49 | } 50 | case 4://local port 51 | return QVariant(con->profile.localPort); 52 | case 5://data usage (term) 53 | if (role == Qt::DisplayRole) { 54 | return QVariant(convertBytesToHumanReadable(con->profile.currentUsage)); 55 | } else { 56 | return QVariant(con->profile.currentUsage); 57 | } 58 | case 6://data usage (total) 59 | if (role == Qt::DisplayRole) { 60 | return QVariant(convertBytesToHumanReadable(con->profile.totalUsage)); 61 | } else { 62 | return QVariant(con->profile.totalUsage); 63 | } 64 | case 7://reset date 65 | if (role == Qt::DisplayRole) { 66 | return QVariant(con->profile.nextResetDate.toString(Qt::SystemLocaleShortDate)); 67 | } else { 68 | return QVariant(con->profile.nextResetDate); 69 | } 70 | case 8://last used 71 | if (role == Qt::DisplayRole) { 72 | return QVariant(con->profile.lastTime.toString(Qt::SystemLocaleShortDate)); 73 | } else { 74 | return QVariant(con->profile.lastTime); 75 | } 76 | default: 77 | return QVariant(); 78 | } 79 | } else if (role == Qt::FontRole) { 80 | QFont font; 81 | font.setBold(con->isRunning()); 82 | return QVariant(font); 83 | } 84 | 85 | return QVariant(); 86 | } 87 | 88 | QString ConnectionItem::convertLatencyToString(const int latency) 89 | { 90 | QString latencyStr; 91 | switch (latency) { 92 | case SQProfile::LATENCY_TIMEOUT: 93 | latencyStr = tr("Timeout"); 94 | break; 95 | case SQProfile::LATENCY_ERROR: 96 | latencyStr = tr("Error"); 97 | break; 98 | case SQProfile::LATENCY_UNKNOWN: 99 | latencyStr = tr("Unknown"); 100 | break; 101 | default: 102 | if (latency >= 1000) { 103 | latencyStr = QString::number(static_cast(latency) / 1000.0) 104 | + QStringLiteral(" ") + tr("s"); 105 | } else { 106 | latencyStr = QString::number(latency) + QStringLiteral(" ") + tr("ms"); 107 | } 108 | } 109 | return latencyStr; 110 | } 111 | 112 | QString ConnectionItem::convertBytesToHumanReadable(quint64 quot) 113 | { 114 | int unitId = 0; 115 | quint64 rem = 0; 116 | for (; quot > 1024; ++unitId) { 117 | rem = quot % 1024;//the previous rem would be negligible 118 | quot /= 1024; 119 | } 120 | double output = static_cast(quot) 121 | + static_cast(rem) / 1024.0; 122 | return QString("%1 %2").arg(output, 0, 'f', 2).arg(bytesUnits.at(unitId)); 123 | } 124 | 125 | void ConnectionItem::testLatency() 126 | { 127 | con->latencyTest(); 128 | } 129 | 130 | Connection* ConnectionItem::getConnection() 131 | { 132 | return con; 133 | } 134 | 135 | void ConnectionItem::onConnectionStateChanged(bool running) 136 | { 137 | if (running) { 138 | emit message(con->getName() + " " + tr("connected")); 139 | } else { 140 | emit message(con->getName() + " " + tr("disconnected")); 141 | } 142 | } 143 | 144 | void ConnectionItem::onConnectionPingFinished(const int latency) 145 | { 146 | if (latency == SQProfile::LATENCY_TIMEOUT) { 147 | emit message(con->getName() + " " + tr("timed out")); 148 | } else if (latency == SQProfile::LATENCY_ERROR) { 149 | emit message(con->getName() + " " + tr("latency test failed")); 150 | } 151 | } 152 | 153 | void ConnectionItem::onStartFailed() 154 | { 155 | emit message(tr("Failed to start") + " " + con->getName()); 156 | } 157 | -------------------------------------------------------------------------------- /src/connectionitem.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | */ 18 | 19 | #ifndef CONNECTIONITEM_H 20 | #define CONNECTIONITEM_H 21 | 22 | #include 23 | #include 24 | #include "connection.h" 25 | 26 | class ConnectionItem : public QObject 27 | { 28 | Q_OBJECT 29 | 30 | public: 31 | explicit ConnectionItem(Connection *_con, QObject *parent = nullptr); 32 | 33 | static int columnCount(); 34 | QVariant data(int column, int role = Qt::DisplayRole) const; 35 | 36 | Connection* getConnection(); 37 | void testLatency(); 38 | 39 | signals: 40 | void message(const QString&); 41 | void stateChanged(bool); 42 | void dataUsageChanged(const quint64 ¤t, const quint64 &total); 43 | void latencyChanged(); 44 | 45 | private: 46 | Connection *con; 47 | 48 | static QString convertLatencyToString(const int latency); 49 | static QString convertBytesToHumanReadable(quint64 bytes); 50 | static const QStringList bytesUnits; 51 | 52 | private slots: 53 | void onConnectionStateChanged(bool running); 54 | void onConnectionPingFinished(const int latency); 55 | void onStartFailed(); 56 | }; 57 | 58 | #endif // CONNECTIONITEM_H 59 | -------------------------------------------------------------------------------- /src/connectiontablemodel.cpp: -------------------------------------------------------------------------------- 1 | #include "connectiontablemodel.h" 2 | 3 | ConnectionTableModel::ConnectionTableModel(QObject *parent) : 4 | QAbstractTableModel(parent) 5 | {} 6 | 7 | ConnectionTableModel::~ConnectionTableModel() 8 | {} 9 | 10 | ConnectionItem *ConnectionTableModel::getItem(const int &row) const 11 | { 12 | return items.at(row); 13 | } 14 | 15 | int ConnectionTableModel::rowCount(const QModelIndex &) const 16 | { 17 | return items.count(); 18 | } 19 | 20 | int ConnectionTableModel::columnCount(const QModelIndex &) const 21 | { 22 | return ConnectionItem::columnCount(); 23 | } 24 | 25 | QVariant ConnectionTableModel::data(const QModelIndex &index, int role) const 26 | { 27 | if (!index.isValid()) { 28 | return QVariant(); 29 | } 30 | 31 | ConnectionItem *item = getItem(index.row()); 32 | return item->data(index.column(), role); 33 | } 34 | 35 | QVariant ConnectionTableModel::headerData(int section, Qt::Orientation orientation, int role) const 36 | { 37 | if (orientation == Qt::Vertical || role != Qt::DisplayRole) { 38 | return QVariant(); 39 | } 40 | 41 | switch (section) { 42 | case 0: 43 | return QVariant(tr("Name")); 44 | case 1: 45 | return QVariant(tr("Server")); 46 | case 2: 47 | return QVariant(tr("Status")); 48 | case 3: 49 | return QVariant(tr("Latency")); 50 | case 4: 51 | return QVariant(tr("Local Port")); 52 | case 5: 53 | return QVariant(tr("Term Usage")); 54 | case 6: 55 | return QVariant(tr("Total Usage")); 56 | case 7: 57 | return QVariant(tr("Reset Date")); 58 | case 8: 59 | return QVariant(tr("Last Used")); 60 | default: 61 | return QVariant(); 62 | } 63 | } 64 | 65 | QModelIndex ConnectionTableModel::index(int row, int column, const QModelIndex &) const 66 | { 67 | if (row < 0 || row >= items.size()) { 68 | return QModelIndex(); 69 | } else { 70 | ConnectionItem* item = items.at(row); 71 | return createIndex(row, column, item);//column is ignored (all columns have the same effect) 72 | } 73 | } 74 | 75 | bool ConnectionTableModel::removeRows(int row, int count, const QModelIndex &parent) 76 | { 77 | if (row < 0 || count <= 0 || count + row > items.count()) { 78 | return false; 79 | } 80 | beginRemoveRows(parent, row, row + count - 1); 81 | items.erase(items.begin() + row, items.begin() + row + count); 82 | endRemoveRows(); 83 | return true; 84 | } 85 | 86 | bool ConnectionTableModel::move(int row, int target, const QModelIndex &parent) 87 | { 88 | if (row < 0 || row >= rowCount() || target < 0 || target >= rowCount() || row == target) { 89 | return false; 90 | } 91 | 92 | //http://doc.qt.io/qt-5/qabstractitemmodel.html#beginMoveRows 93 | int movTarget = target; 94 | if (target - row > 0) { 95 | movTarget++; 96 | } 97 | beginMoveRows(parent, row, row, parent, movTarget); 98 | items.move(row, target); 99 | endMoveRows(); 100 | return true; 101 | } 102 | 103 | bool ConnectionTableModel::appendConnection(Connection *con, const QModelIndex &parent) 104 | { 105 | ConnectionItem* newItem = new ConnectionItem(con, this); 106 | connect(newItem, &ConnectionItem::message, this, &ConnectionTableModel::message); 107 | connect(newItem, &ConnectionItem::stateChanged, this, &ConnectionTableModel::onConnectionStateChanged); 108 | connect(newItem, &ConnectionItem::latencyChanged, this, &ConnectionTableModel::onConnectionLatencyChanged); 109 | connect(newItem, &ConnectionItem::dataUsageChanged, this, &ConnectionTableModel::onConnectionDataUsageChanged); 110 | beginInsertRows(parent, items.count(), items.count()); 111 | items.append(newItem); 112 | endInsertRows(); 113 | return true; 114 | } 115 | 116 | void ConnectionTableModel::disconnectConnectionsAt(const QString &addr, quint16 port) 117 | { 118 | bool anyAddr = (addr.compare("0.0.0.0") == 0); 119 | for (auto &i : items) { 120 | Connection *con = i->getConnection(); 121 | if (con->isRunning() && con->getProfile().localPort == port) { 122 | if ((con->getProfile().localAddress == addr) || 123 | (con->getProfile().localAddress.compare("0.0.0.0") == 0) || 124 | anyAddr) { 125 | con->stop(); 126 | } 127 | } 128 | } 129 | } 130 | 131 | void ConnectionTableModel::testAllLatency() 132 | { 133 | for (auto &i : items) { 134 | i->testLatency(); 135 | } 136 | } 137 | 138 | void ConnectionTableModel::onConnectionStateChanged(bool running) 139 | { 140 | ConnectionItem *item = qobject_cast(sender()); 141 | int row = items.indexOf(item); 142 | emit dataChanged(this->index(row, 0), 143 | this->index(row, ConnectionItem::columnCount() - 1)); 144 | emit rowStatusChanged(row, running); 145 | } 146 | 147 | void ConnectionTableModel::onConnectionLatencyChanged() 148 | { 149 | ConnectionItem *item = qobject_cast(sender()); 150 | int row = items.indexOf(item); 151 | emit dataChanged(this->index(row, 3), this->index(row, 3)); 152 | } 153 | 154 | void ConnectionTableModel::onConnectionDataUsageChanged() 155 | { 156 | ConnectionItem *item = qobject_cast(sender()); 157 | int row = items.indexOf(item); 158 | emit dataChanged(this->index(row, 5), this->index(row, 6)); 159 | } -------------------------------------------------------------------------------- /src/connectiontablemodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | */ 18 | 19 | #ifndef CONNECTIONTABLEMODEL_H 20 | #define CONNECTIONTABLEMODEL_H 21 | 22 | #include 23 | #include 24 | #include "connectionitem.h" 25 | 26 | class ConnectionTableModel : public QAbstractTableModel 27 | { 28 | Q_OBJECT 29 | 30 | public: 31 | explicit ConnectionTableModel(QObject *parent = nullptr); 32 | ~ConnectionTableModel(); 33 | 34 | ConnectionItem *getItem(const int &row) const; 35 | int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; 36 | int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; 37 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; 38 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; 39 | QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; 40 | bool removeRows(int row, int count, const QModelIndex & parent = QModelIndex()) Q_DECL_OVERRIDE; 41 | bool move(int row, int target, const QModelIndex &parent = QModelIndex()); 42 | 43 | bool appendConnection(Connection *con, const QModelIndex &parent = QModelIndex()); 44 | 45 | void disconnectConnectionsAt(const QString &addr, quint16 port); 46 | 47 | public slots: 48 | void testAllLatency(); 49 | 50 | signals: 51 | void message(const QString &); 52 | void rowStatusChanged(int row, bool running); 53 | 54 | private: 55 | QList items; 56 | static QString convertLatencyToString(const int latency); 57 | 58 | private slots: 59 | void onConnectionStateChanged(bool running); 60 | void onConnectionLatencyChanged(); 61 | void onConnectionDataUsageChanged(); 62 | }; 63 | 64 | #endif // CONNECTIONTABLEMODEL_H 65 | -------------------------------------------------------------------------------- /src/editdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "editdialog.h" 2 | #include "ui_editdialog.h" 3 | #include "ssvalidator.h" 4 | #include "ip4validator.h" 5 | #include "portvalidator.h" 6 | 7 | EditDialog::EditDialog(Connection *_connection, QWidget *parent) : 8 | QDialog(parent), 9 | ui(new Ui::EditDialog), 10 | connection(_connection) 11 | { 12 | ui->setupUi(this); 13 | 14 | /* initialisation and validator setup */ 15 | static const QStringList supportedMethodList = 16 | SSValidator::supportedMethodList(); 17 | ui->encryptComboBox->addItems(supportedMethodList); 18 | IP4Validator *addrValidator = new IP4Validator(this); 19 | PortValidator *portValidator = new PortValidator(this); 20 | ui->serverPortEdit->setValidator(portValidator); 21 | ui->localPortEdit->setValidator(portValidator); 22 | //Maybe we shouldn't validate local address using IPv4 format? 23 | ui->localAddrEdit->setValidator(addrValidator); 24 | 25 | ui->nameEdit->setText(connection->profile.name); 26 | ui->serverAddrEdit->setText(connection->profile.serverAddress); 27 | ui->serverPortEdit->setText(QString::number(connection->profile.serverPort)); 28 | ui->localAddrEdit->setText(connection->profile.localAddress); 29 | ui->localPortEdit->setText(QString::number(connection->profile.localPort)); 30 | ui->httpRadioButton->setChecked(connection->profile.httpMode); 31 | ui->pwdEdit->setText(connection->profile.password); 32 | ui->encryptComboBox->setCurrentText(connection->profile.method.toUpper()); 33 | ui->timeoutSpinBox->setValue(connection->profile.timeout); 34 | ui->resetDateEdit->setDate(connection->profile.nextResetDate); 35 | ui->resetDateEdit->setMinimumDate(QDate::currentDate()); 36 | ui->autoStartCheckBox->setChecked(connection->profile.autoStart); 37 | 38 | connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &EditDialog::save); 39 | 40 | this->adjustSize(); 41 | } 42 | 43 | EditDialog::~EditDialog() 44 | { 45 | delete ui; 46 | } 47 | 48 | void EditDialog::save() 49 | { 50 | connection->profile.name = ui->nameEdit->text(); 51 | connection->profile.serverAddress = ui->serverAddrEdit->text().trimmed(); 52 | connection->profile.serverPort = ui->serverPortEdit->text().toUShort(); 53 | connection->profile.localAddress = ui->localAddrEdit->text(); 54 | connection->profile.localPort = ui->localPortEdit->text().toUShort(); 55 | connection->profile.httpMode = ui->httpRadioButton->isChecked(); 56 | connection->profile.password = ui->pwdEdit->text(); 57 | connection->profile.method = ui->encryptComboBox->currentText(); 58 | connection->profile.timeout = ui->timeoutSpinBox->value(); 59 | connection->profile.nextResetDate = ui->resetDateEdit->date(); 60 | connection->profile.autoStart = ui->autoStartCheckBox->isChecked(); 61 | 62 | this->accept(); 63 | } 64 | -------------------------------------------------------------------------------- /src/editdialog.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | */ 18 | 19 | #ifndef EDITDIALOG_H 20 | #define EDITDIALOG_H 21 | 22 | #include 23 | #include "connection.h" 24 | 25 | namespace Ui { 26 | class EditDialog; 27 | } 28 | 29 | class EditDialog : public QDialog 30 | { 31 | Q_OBJECT 32 | 33 | public: 34 | explicit EditDialog(Connection *_connection, QWidget *parent = 0); 35 | ~EditDialog(); 36 | 37 | private: 38 | Ui::EditDialog *ui; 39 | Connection *connection; 40 | 41 | private slots: 42 | void save(); 43 | }; 44 | 45 | #endif // EDITDIALOG_H 46 | -------------------------------------------------------------------------------- /src/editdialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | EditDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 462 10 | 524 11 | 12 | 13 | 14 | Profile Editor 15 | 16 | 17 | 18 | 19 | 20 | QFrame::StyledPanel 21 | 22 | 23 | QFrame::Raised 24 | 25 | 26 | 27 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 28 | 29 | 30 | 31 | 32 | Profile Name 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | Server Address 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | Server Port 53 | 54 | 55 | 56 | 57 | 58 | 59 | Qt::ImhDigitsOnly|Qt::ImhPreferNumbers 60 | 61 | 62 | 63 | 64 | 65 | 66 | Password 67 | 68 | 69 | 70 | 71 | 72 | 73 | Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText|Qt::ImhSensitiveData 74 | 75 | 76 | QLineEdit::PasswordEchoOnEdit 77 | 78 | 79 | 80 | 81 | 82 | 83 | Local Address 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | Local Port 94 | 95 | 96 | 97 | 98 | 99 | 100 | Qt::ImhDigitsOnly|Qt::ImhPreferNumbers 101 | 102 | 103 | 104 | 105 | 106 | 107 | Local Server Type 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | SOCKS&5 117 | 118 | 119 | true 120 | 121 | 122 | buttonGroup 123 | 124 | 125 | 126 | 127 | 128 | 129 | HTTP(S) 130 | 131 | 132 | buttonGroup 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | Encryption Method 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 0 150 | 0 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | Timeout 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 0 167 | 0 168 | 169 | 170 | 171 | 10 172 | 173 | 174 | 3600 175 | 176 | 177 | 10 178 | 179 | 180 | 60 181 | 182 | 183 | 184 | 185 | 186 | 187 | Reset Data Usage after 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 0 196 | 0 197 | 198 | 199 | 200 | true 201 | 202 | 203 | 204 | 205 | 206 | 207 | Automation 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 0 216 | 0 217 | 218 | 219 | 220 | Auto connect on application start 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | Qt::Vertical 231 | 232 | 233 | 234 | 20 235 | 0 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | Qt::Horizontal 244 | 245 | 246 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 247 | 248 | 249 | 250 | 251 | 252 | 253 | nameEdit 254 | serverAddrEdit 255 | serverPortEdit 256 | pwdEdit 257 | localAddrEdit 258 | localPortEdit 259 | socks5RadioButton 260 | httpRadioButton 261 | encryptComboBox 262 | timeoutSpinBox 263 | resetDateEdit 264 | autoStartCheckBox 265 | 266 | 267 | 268 | 269 | buttonBox 270 | rejected() 271 | EditDialog 272 | reject() 273 | 274 | 275 | 316 276 | 260 277 | 278 | 279 | 286 280 | 274 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | -------------------------------------------------------------------------------- /src/i18n/ss-qt5_zh_CN.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/i18n/ss-qt5_zh_CN.qm -------------------------------------------------------------------------------- /src/i18n/ss-qt5_zh_CN.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ConnectionItem 6 | 7 | 8 | Connected 9 | 已连接 10 | 11 | 12 | 13 | Disconnected 14 | 未连接 15 | 16 | 17 | 18 | Timeout 19 | 超时 20 | 21 | 22 | 23 | Error 24 | 错误 25 | 26 | 27 | 28 | Unknown 29 | 未知 30 | 31 | 32 | 33 | s 34 | 35 | 36 | 37 | 38 | ms 39 | 毫秒 40 | 41 | 42 | 43 | connected 44 | 已连接 45 | 46 | 47 | 48 | disconnected 49 | 已断开 50 | 51 | 52 | 53 | timed out 54 | 超时 55 | 56 | 57 | 58 | latency test failed 59 | 延迟测试失败 60 | 61 | 62 | 63 | Failed to start 64 | 无法启动 65 | 66 | 67 | 68 | ConnectionTableModel 69 | 70 | 71 | Name 72 | 名称 73 | 74 | 75 | 76 | Server 77 | 服务器 78 | 79 | 80 | 81 | Status 82 | 状态 83 | 84 | 85 | 86 | Latency 87 | 延迟 88 | 89 | 90 | 91 | Local Port 92 | 本地端口 93 | 94 | 95 | 96 | Term Usage 97 | 本期用量 98 | 99 | 100 | 101 | Total Usage 102 | 累计用量 103 | 104 | 105 | 106 | Reset Date 107 | 重置日期 108 | 109 | 110 | 111 | Last Used 112 | 上次使用 113 | 114 | 115 | 116 | EditDialog 117 | 118 | 119 | Profile Editor 120 | 配置编辑器 121 | 122 | 123 | 124 | Local Server Type 125 | 本地服务器类型 126 | 127 | 128 | 129 | Timeout 130 | 超时 131 | 132 | 133 | 134 | [Deprecated] Turn on one-time authentication and header verification (need server support) 135 | [弃用]开启一次验证和消息头验证(需要服务器支持) 136 | 137 | 138 | Turn on one-time authentication and header verification (need server support) 139 | 开启一次验证和消息头验证(需要服务器支持) 140 | 141 | 142 | 143 | One-time authentication 144 | 一次验证 145 | 146 | 147 | 148 | Encryption Method 149 | 加密方式 150 | 151 | 152 | 153 | Local Port 154 | 本地端口 155 | 156 | 157 | 158 | Local Address 159 | 本地地址 160 | 161 | 162 | 163 | Password 164 | 密钥 165 | 166 | 167 | 168 | Server Port 169 | 服务器端口 170 | 171 | 172 | 173 | Server Address 174 | 服务器地址 175 | 176 | 177 | 178 | Profile Name 179 | 配置名称 180 | 181 | 182 | 183 | Auto connect on application start 184 | 程序启动时自动连接 185 | 186 | 187 | 188 | Debug 189 | 调试 190 | 191 | 192 | 193 | Log Level 194 | 日志级别 195 | 196 | 197 | 198 | Automation 199 | 自动化 200 | 201 | 202 | 203 | Reset Data Usage after 204 | 重置数据流量 205 | 206 | 207 | 208 | LogDialog 209 | 210 | 211 | Log Viewer 212 | 日志查看器 213 | 214 | 215 | 216 | Save log as a plain text file 217 | 保存日志为文本文件 218 | 219 | 220 | 221 | Save As... 222 | 保存为... 223 | 224 | 225 | 226 | Clear 227 | 清空 228 | 229 | 230 | 231 | Save Log As 232 | 保存日志为 233 | 234 | 235 | 236 | MainWindow 237 | 238 | 239 | About 240 | 关于 241 | 242 | 243 | 244 | Import Connections from gui-config.json 245 | 从gui-config.json导入连接 246 | 247 | 248 | 249 | Export Connections as gui-config.json 250 | 将所有连接信息导出为gui-config.json 251 | 252 | 253 | 254 | 255 | QR Code Not Found 256 | 未找到二维码 257 | 258 | 259 | 260 | 261 | Can't find any QR code image that contains valid URI on your screen(s). 262 | 无法在您的屏幕上找到任何包含有效URI的二维码图像。 263 | 264 | 265 | 266 | Open QR Code Image File 267 | 打开二维码图像文件 268 | 269 | 270 | 271 | Open config.json 272 | 打开 config.json 273 | 274 | 275 | 276 | 277 | Invalid 278 | 无效 279 | 280 | 281 | 282 | 283 | The connection's profile is invalid! 284 | 当前连接的配置无效! 285 | 286 | 287 | 288 | Connection Manager 289 | 连接编辑器 290 | 291 | 292 | 293 | &Connection 294 | 连接(&C) 295 | 296 | 297 | 298 | &Add 299 | 添加(&A) 300 | 301 | 302 | 303 | Fi&le 304 | 文件(&F) 305 | 306 | 307 | 308 | Settin&gs 309 | 设置(&S) 310 | 311 | 312 | Help 313 | 帮助(&H) 314 | 315 | 316 | 317 | &Manually 318 | 手动(&M) 319 | 320 | 321 | 322 | Add connection manually 323 | 手动添加连接 324 | 325 | 326 | 327 | &From QR Code Image File 328 | 自二维码图像文件(&Q) 329 | 330 | 331 | 332 | From QR code image file 333 | 自二维码图像文件 334 | 335 | 336 | 337 | View &Log 338 | 查看日志(&L) 339 | 340 | 341 | 342 | &Scan QR Code on Screen 343 | 扫描屏幕上的二维码(&S) 344 | 345 | 346 | 347 | Input to filter 348 | 输入以过滤 349 | 350 | 351 | 352 | Show Toolbar 353 | 显示工具栏 354 | 355 | 356 | 357 | &Help 358 | 帮助(&H) 359 | 360 | 361 | 362 | &URI 363 | &URI 364 | 365 | 366 | 367 | Add connection from URI 368 | 从 URI 添加连接 369 | 370 | 371 | 372 | &Delete 373 | 删除(&D) 374 | 375 | 376 | 377 | &Edit 378 | 编辑(&E) 379 | 380 | 381 | 382 | &Connect 383 | 连接(&C) 384 | 385 | 386 | 387 | D&isconnect 388 | 断开连接(&D) 389 | 390 | 391 | 392 | &Quit 393 | 退出(&Q) 394 | 395 | 396 | 397 | Ctrl+Q 398 | 399 | 400 | 401 | 402 | &About 403 | 关于(&A) 404 | 405 | 406 | 407 | About &Qt 408 | 关于 &Qt 409 | 410 | 411 | 412 | &General Settings 413 | 常规设置(&G) 414 | 415 | 416 | 417 | &Share 418 | 分享(&S) 419 | 420 | 421 | 422 | &Report Bug 423 | 报告错误(&R) 424 | 425 | 426 | 427 | Test the latency of selected connection 428 | 测试所选连接的延迟 429 | 430 | 431 | 432 | Test All C&onnections Latency 433 | 测试所有连接的延迟(&O) 434 | 435 | 436 | 437 | &Show Filter Bar 438 | 显示过滤栏(&S) 439 | 440 | 441 | 442 | Ctrl+F 443 | 444 | 445 | 446 | 447 | &Export as gui-config.json 448 | 导出为gui-config.json (&E) 449 | 450 | 451 | 452 | Scan &QR Code using Capturer 453 | 使用捕获器扫描二维码(&C) 454 | 455 | 456 | 457 | Scan QR Code using Capturer 458 | 使用捕获器扫描二维码 459 | 460 | 461 | 462 | &Force Connect 463 | 强制连接(&F) 464 | 465 | 466 | Force Connect 467 | 强制连接 468 | 469 | 470 | 471 | Connect to this connection and disconnect any connections currently using the same local port 472 | 连接到该连接并断开占用了相同本地端口的连接 473 | 474 | 475 | 476 | From &config.json 477 | 自 &config.json 478 | 479 | 480 | 481 | &Save Manually 482 | 手动保存(&S) 483 | 484 | 485 | 486 | Ctrl+Shift+S 487 | 488 | 489 | 490 | 491 | &Move Up 492 | 上移(&U) 493 | 494 | 495 | 496 | Mo&ve Down 497 | 下移(&V) 498 | 499 | 500 | 501 | &Import Connections from gui-config.json 502 | 从 gui-config.json导入连接(&I) 503 | 504 | 505 | 506 | Import connections from old version configuration file 507 | 从旧版配置文件导入连接 508 | 509 | 510 | 511 | &Test Latency 512 | 测试延迟(&T) 513 | 514 | 515 | 516 | QObject 517 | 518 | Failed to communicate with previously running instance of Shadowsocks-Qt5 (PID: %1). It might already crashed. 519 | 无法与先前运行的Shadowsocks-Qt5实例(PID: %1)通讯。可能已经崩溃了。 520 | 521 | 522 | Error 523 | 错误 524 | 525 | 526 | Another instance of Shadowsocks-Qt5 (PID: %1) is already running. 527 | 另一个 Shadowsocks-Qt5 (PID: %1) 的实例已经在运行了。 528 | 529 | 530 | 531 | Unnamed Profile 532 | 未命名配置 533 | 534 | 535 | 536 | QRCodeCapturer 537 | 538 | QR Code Capturer 539 | 二维码捕获器 540 | 541 | 542 | 543 | QR Capturer 544 | 二维码捕获器 545 | 546 | 547 | 548 | QRWidget 549 | 550 | 551 | Generating QR code failed. 552 | 二维码生成失败。 553 | 554 | 555 | 556 | SettingsDialog 557 | 558 | 559 | General Settings 560 | 常规设置 561 | 562 | 563 | 564 | Toolbar Style 565 | 工具栏风格 566 | 567 | 568 | 569 | Icons Only 570 | 仅图标 571 | 572 | 573 | 574 | Text Only 575 | 仅文本 576 | 577 | 578 | 579 | Text Alongside Icons 580 | 文本在图标侧 581 | 582 | 583 | 584 | Text Under Icons 585 | 文本在图标下 586 | 587 | 588 | 589 | System Style 590 | 系统风格 591 | 592 | 593 | 594 | Allow only one instance running 595 | 仅允许一个实例运行 596 | 597 | 598 | 599 | Hide window on startup 600 | 启动时隐藏窗口 601 | 602 | 603 | 604 | Need to restart the application for this change to take effect 605 | 需重启程序以生效 606 | 607 | 608 | 609 | Use native menu bar 610 | 使用原生菜单栏 611 | 612 | 613 | 614 | Start at login 615 | 登录时启动 616 | 617 | 618 | 619 | ShareDialog 620 | 621 | 622 | Share Profile 623 | 分享配置 624 | 625 | 626 | 627 | Save QR code as an Image file 628 | 保存二维码为图像文件 629 | 630 | 631 | 632 | Save QR Code 633 | 保存二维码 634 | 635 | 636 | 637 | StatusNotifier 638 | 639 | 640 | 641 | Minimise 642 | 最小化 643 | 644 | 645 | 646 | Quit 647 | 退出 648 | 649 | 650 | 651 | 652 | Restore 653 | 恢复 654 | 655 | 656 | 657 | URIInputDialog 658 | 659 | 660 | URI Input Dialog 661 | URI输入对话框 662 | 663 | 664 | 665 | Please input ss:// URI 666 | 请输入 ss:// URI 667 | 668 | 669 | 670 | -------------------------------------------------------------------------------- /src/i18n/ss-qt5_zh_TW.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/i18n/ss-qt5_zh_TW.qm -------------------------------------------------------------------------------- /src/i18n/ss-qt5_zh_TW.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ConnectionItem 6 | 7 | 8 | Connected 9 | 已連線 10 | 11 | 12 | 13 | Disconnected 14 | 已斷線 15 | 16 | 17 | 18 | Timeout 19 | 逾時 20 | 21 | 22 | 23 | Error 24 | 錯誤 25 | 26 | 27 | 28 | Unknown 29 | 不明 30 | 31 | 32 | 33 | s 34 | 35 | 36 | 37 | 38 | ms 39 | 毫秒 40 | 41 | 42 | 43 | connected 44 | 已連線 45 | 46 | 47 | 48 | disconnected 49 | 已斷線 50 | 51 | 52 | 53 | timed out 54 | 逾時 55 | 56 | 57 | 58 | latency test failed 59 | 延遲測試失敗 60 | 61 | 62 | 63 | Failed to start 64 | 啟動失敗 65 | 66 | 67 | 68 | ConnectionTableModel 69 | 70 | 71 | Name 72 | 名稱 73 | 74 | 75 | 76 | Server 77 | 伺服器 78 | 79 | 80 | 81 | Status 82 | 狀態 83 | 84 | 85 | 86 | Latency 87 | 延遲 88 | 89 | 90 | 91 | Local Port 92 | 本機埠 93 | 94 | 95 | 96 | Term Usage 97 | 本期使用量 98 | 99 | 100 | 101 | Total Usage 102 | 使用量總計 103 | 104 | 105 | 106 | Reset Date 107 | 重設日期 108 | 109 | 110 | 111 | Last Used 112 | 上次使用 113 | 114 | 115 | 116 | EditDialog 117 | 118 | 119 | Profile Editor 120 | 設定檔編輯器 121 | 122 | 123 | 124 | Local Server Type 125 | 本機伺服器型別 126 | 127 | 128 | 129 | Timeout 130 | 逾時 131 | 132 | 133 | 134 | [Deprecated] Turn on one-time authentication and header verification (need server support) 135 | [棄用]開啟單次驗證與標頭驗證(需要伺服器支援) 136 | 137 | 138 | Turn on one-time authentication and header verification (need server support) 139 | 開啟單次驗證與標頭驗證(需要伺服器支援) 140 | 141 | 142 | 143 | One-time authentication 144 | 單次驗證 145 | 146 | 147 | 148 | Encryption Method 149 | 加密方法 150 | 151 | 152 | 153 | Local Port 154 | 本機埠 155 | 156 | 157 | 158 | Local Address 159 | 本機位址 160 | 161 | 162 | 163 | Password 164 | 密碼 165 | 166 | 167 | 168 | Server Port 169 | 伺服器埠 170 | 171 | 172 | 173 | Server Address 174 | 伺服器位址 175 | 176 | 177 | 178 | Profile Name 179 | 設定檔名稱 180 | 181 | 182 | 183 | Auto connect on application start 184 | 程式啟動時自動連線 185 | 186 | 187 | 188 | Debug 189 | 偵錯 190 | 191 | 192 | 193 | Log Level 194 | 記錄檔等級 195 | 196 | 197 | 198 | Automation 199 | 自動化 200 | 201 | 202 | 203 | Reset Data Usage after 204 | 重設資料使用量之後 205 | 206 | 207 | 208 | LogDialog 209 | 210 | 211 | Log Viewer 212 | 記錄檔檢視器 213 | 214 | 215 | 216 | Save log as a plain text file 217 | 儲存記錄檔為文字檔 218 | 219 | 220 | 221 | Save As... 222 | 另存新檔... 223 | 224 | 225 | 226 | Clear 227 | 清除 228 | 229 | 230 | 231 | Save Log As 232 | 另存記錄檔 233 | 234 | 235 | 236 | MainWindow 237 | 238 | 239 | About 240 | 關於 241 | 242 | 243 | 244 | Import Connections from gui-config.json 245 | 自 gui-config.json 匯入連線 246 | 247 | 248 | 249 | Export Connections as gui-config.json 250 | 匯出連線為 gui-config.json 251 | 252 | 253 | 254 | 255 | QR Code Not Found 256 | 找不到 QR 碼 257 | 258 | 259 | 260 | 261 | Can't find any QR code image that contains valid URI on your screen(s). 262 | 在你的螢幕上無法找到任何包含有效 URI 的 QR 碼圖片。 263 | 264 | 265 | 266 | Open QR Code Image File 267 | 開啟 QR 碼圖檔 268 | 269 | 270 | 271 | Open config.json 272 | 開啟 config.json 273 | 274 | 275 | 276 | 277 | Invalid 278 | 無效 279 | 280 | 281 | 282 | 283 | The connection's profile is invalid! 284 | 此連線的設定檔無效! 285 | 286 | 287 | 288 | Connection Manager 289 | 連線管理員 290 | 291 | 292 | 293 | &Connection 294 | &連線 295 | 296 | 297 | 298 | &Add 299 | &新增 300 | 301 | 302 | 303 | Fi&le 304 | &檔案 305 | 306 | 307 | 308 | Settin&gs 309 | &設定 310 | 311 | 312 | Help 313 | 說明 314 | 315 | 316 | 317 | &Manually 318 | &手動 319 | 320 | 321 | 322 | Add connection manually 323 | 手動新增連線 324 | 325 | 326 | 327 | &From QR Code Image File 328 | &來自 QR 碼圖檔 329 | 330 | 331 | 332 | From QR code image file 333 | 來自 QR 碼圖檔 334 | 335 | 336 | 337 | View &Log 338 | &檢視記錄檔 339 | 340 | 341 | 342 | &Scan QR Code on Screen 343 | &掃描螢幕上的 QR 碼 344 | 345 | 346 | 347 | Input to filter 348 | 輸入以篩選 349 | 350 | 351 | 352 | Show Toolbar 353 | 顯示工具列 354 | 355 | 356 | 357 | &Help 358 | 說明(&H) 359 | 360 | 361 | 362 | &URI 363 | &URI 364 | 365 | 366 | 367 | Add connection from URI 368 | 自 URI 新增連線 369 | 370 | 371 | 372 | &Delete 373 | &刪除 374 | 375 | 376 | 377 | &Edit 378 | &編輯 379 | 380 | 381 | 382 | &Connect 383 | &連線 384 | 385 | 386 | 387 | D&isconnect 388 | &中斷連線 389 | 390 | 391 | 392 | &Quit 393 | &結束 394 | 395 | 396 | 397 | Ctrl+Q 398 | 399 | 400 | 401 | 402 | &About 403 | &關於 404 | 405 | 406 | 407 | About &Qt 408 | 關於 &Qt 409 | 410 | 411 | 412 | &General Settings 413 | &一般設定 414 | 415 | 416 | 417 | &Share 418 | &分享 419 | 420 | 421 | 422 | &Report Bug 423 | &回報 Bug 424 | 425 | 426 | 427 | Test the latency of selected connection 428 | 測試已選連線的延遲 429 | 430 | 431 | 432 | Test All C&onnections Latency 433 | 測試&所有連線的延遲 434 | 435 | 436 | 437 | &Show Filter Bar 438 | &顯示篩選列 439 | 440 | 441 | 442 | Ctrl+F 443 | 444 | 445 | 446 | 447 | &Export as gui-config.json 448 | &匯出為 gui-config.json 449 | 450 | 451 | 452 | Scan &QR Code using Capturer 453 | &使用捕捉器掃描 QR 碼 454 | 455 | 456 | 457 | Scan QR Code using Capturer 458 | 使用捕捉器掃描 QR 碼 459 | 460 | 461 | 462 | &Force Connect 463 | 強迫連線(&E) 464 | 465 | 466 | Force Connect 467 | 強迫連線 468 | 469 | 470 | 471 | Connect to this connection and disconnect any connections currently using the same local port 472 | 連線至此連線並且中斷使用了相同本機埠的連線 473 | 474 | 475 | 476 | From &config.json 477 | 自 &config.json 478 | 479 | 480 | 481 | &Save Manually 482 | &手動儲存 483 | 484 | 485 | 486 | Ctrl+Shift+S 487 | 488 | 489 | 490 | 491 | &Move Up 492 | &向上移動 493 | 494 | 495 | 496 | Mo&ve Down 497 | &向下移動 498 | 499 | 500 | 501 | &Import Connections from gui-config.json 502 | &自 gui-config.json 匯入連線 503 | 504 | 505 | 506 | Import connections from old version configuration file 507 | 自舊版設定檔匯入連線 508 | 509 | 510 | 511 | &Test Latency 512 | &測試延遲 513 | 514 | 515 | 516 | QObject 517 | 518 | Failed to communicate with previously running instance of Shadowsocks-Qt5 (PID: %1). It might already crashed. 519 | 與之前執行的 Shadowsocks-Qt5 執行個體(PID: %1)通訊失敗。或許已損毀。 520 | 521 | 522 | Error 523 | 錯誤 524 | 525 | 526 | Another instance of Shadowsocks-Qt5 (PID: %1) is already running. 527 | 另一個 Shadowsocks-Qt5 (PID: %1) 的執行個體已在執行。 528 | 529 | 530 | 531 | Unnamed Profile 532 | 未命名的設定檔 533 | 534 | 535 | 536 | QRCodeCapturer 537 | 538 | QR Code Capturer 539 | QR 碼捕捉器 540 | 541 | 542 | 543 | QR Capturer 544 | QR 碼捕捉器 545 | 546 | 547 | 548 | QRWidget 549 | 550 | 551 | Generating QR code failed. 552 | QR 碼產生失敗。 553 | 554 | 555 | 556 | SettingsDialog 557 | 558 | 559 | General Settings 560 | 一般設定 561 | 562 | 563 | 564 | Toolbar Style 565 | 工具列樣式 566 | 567 | 568 | 569 | Icons Only 570 | 僅圖示 571 | 572 | 573 | 574 | Text Only 575 | 僅文字 576 | 577 | 578 | 579 | Text Alongside Icons 580 | 文字在圖示邊 581 | 582 | 583 | 584 | Text Under Icons 585 | 文字在圖示下 586 | 587 | 588 | 589 | System Style 590 | 系統樣式 591 | 592 | 593 | 594 | Allow only one instance running 595 | 僅允許一個執行個體執行 596 | 597 | 598 | 599 | Hide window on startup 600 | 啟動時隱藏視窗 601 | 602 | 603 | 604 | Need to restart the application for this change to take effect 605 | 需要重新啟動程式方能使此變更生效 606 | 607 | 608 | 609 | Use native menu bar 610 | 使用原生選單列 611 | 612 | 613 | 614 | Start at login 615 | 616 | 617 | 618 | 619 | ShareDialog 620 | 621 | 622 | Share Profile 623 | 分享設定檔 624 | 625 | 626 | 627 | Save QR code as an Image file 628 | 儲存 QR 碼為圖檔 629 | 630 | 631 | 632 | Save QR Code 633 | 儲存 QR 碼 634 | 635 | 636 | 637 | StatusNotifier 638 | 639 | 640 | 641 | Minimise 642 | 最小化 643 | 644 | 645 | 646 | Quit 647 | 結束 648 | 649 | 650 | 651 | 652 | Restore 653 | 還原 654 | 655 | 656 | 657 | URIInputDialog 658 | 659 | 660 | URI Input Dialog 661 | URI 輸入對話方塊 662 | 663 | 664 | 665 | Please input ss:// URI 666 | 請輸入 ss:// URI 667 | 668 | 669 | 670 | -------------------------------------------------------------------------------- /src/icons.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/Breeze/actions/application-exit.png 4 | icons/Breeze/actions/configure.png 5 | icons/Breeze/actions/document-open.png 6 | icons/Breeze/actions/document-revert.png 7 | icons/Breeze/actions/document-edit.png 8 | icons/Breeze/actions/document-import.png 9 | icons/Breeze/actions/document-export.png 10 | icons/Breeze/actions/document-save.png 11 | icons/Breeze/actions/document-share.png 12 | icons/Breeze/actions/edit-guides.png 13 | icons/Breeze/actions/edit-image-face-recognize.png 14 | icons/Breeze/actions/flag.png 15 | icons/Breeze/actions/go-down.png 16 | icons/Breeze/actions/go-up.png 17 | icons/Breeze/actions/help-about.png 18 | icons/Breeze/actions/list-add.png 19 | icons/Breeze/actions/list-remove.png 20 | icons/Breeze/actions/network-connect.png 21 | icons/Breeze/actions/network-disconnect.png 22 | icons/Breeze/actions/text-field.png 23 | icons/Breeze/actions/tools-report-bug.png 24 | icons/Breeze/actions/view-list-text.png 25 | icons/Breeze/index.theme 26 | icons/shadowsocks-qt5.png 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/icons/Breeze/actions/application-exit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/application-exit.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/configure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/configure.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/document-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/document-edit.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/document-export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/document-export.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/document-import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/document-import.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/document-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/document-open.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/document-revert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/document-revert.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/document-save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/document-save.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/document-share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/document-share.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/edit-guides.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/edit-guides.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/edit-image-face-recognize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/edit-image-face-recognize.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/flag.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/go-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/go-down.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/go-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/go-up.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/help-about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/help-about.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/list-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/list-add.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/list-remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/list-remove.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/network-connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/network-connect.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/network-disconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/network-disconnect.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/text-field.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/text-field.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/tools-report-bug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/tools-report-bug.png -------------------------------------------------------------------------------- /src/icons/Breeze/actions/view-list-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/Breeze/actions/view-list-text.png -------------------------------------------------------------------------------- /src/icons/Breeze/index.theme: -------------------------------------------------------------------------------- 1 | [Icon Theme] 2 | Name=Breeze 3 | Directories=actions 4 | 5 | [actions] 6 | Size=22 7 | Context=Actions 8 | Type=Fixed 9 | -------------------------------------------------------------------------------- /src/icons/shadowsocks-qt5.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/shadowsocks-qt5.icns -------------------------------------------------------------------------------- /src/icons/shadowsocks-qt5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/icons/shadowsocks-qt5.png -------------------------------------------------------------------------------- /src/ip4validator.cpp: -------------------------------------------------------------------------------- 1 | #include "ip4validator.h" 2 | 3 | IP4Validator::IP4Validator(QObject *parent) 4 | : QValidator(parent) 5 | {} 6 | 7 | QValidator::State IP4Validator::validate(QString &input, int &) const 8 | { 9 | if (input.isEmpty()) { 10 | return Acceptable; 11 | } 12 | 13 | QStringList slist = input.split("."); 14 | int number = slist.size(); 15 | if (number > 4) { 16 | return Invalid; 17 | } 18 | 19 | bool emptyGroup = false; 20 | for (int i = 0; i < number; i++) { 21 | bool ok; 22 | if(slist[i].isEmpty()){ 23 | emptyGroup = true; 24 | continue; 25 | } 26 | ushort value = slist[i].toUShort(&ok); 27 | if(!ok || value > 255) { 28 | return Invalid; 29 | } 30 | } 31 | 32 | if(number < 4 || emptyGroup) { 33 | return Intermediate; 34 | } 35 | 36 | return Acceptable; 37 | } 38 | -------------------------------------------------------------------------------- /src/ip4validator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014-2016 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | * 18 | * This class is based on a piece of code posted on Qt Centre. 19 | * http://www.qtcentre.org/threads/6228-Ip-Address-Validation?p=32368#post32368 20 | */ 21 | 22 | #ifndef IP4VALIDATOR_H 23 | #define IP4VALIDATOR_H 24 | 25 | #include 26 | 27 | class IP4Validator : public QValidator 28 | { 29 | public: 30 | IP4Validator(QObject *parent = 0); 31 | State validate(QString &input, int &) const; 32 | }; 33 | 34 | #endif // IP4VALIDATOR_H 35 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "mainwindow.h" 11 | #include "confighelper.h" 12 | 13 | MainWindow *mainWindow = nullptr; 14 | 15 | static void onSignalRecv(int sig) 16 | { 17 | if (sig == SIGINT || sig == SIGTERM) { 18 | qApp->quit(); 19 | } else { 20 | qWarning("Unhandled signal %d", sig); 21 | } 22 | } 23 | 24 | void setupApplication(QApplication &a) 25 | { 26 | signal(SIGINT, onSignalRecv); 27 | signal(SIGTERM, onSignalRecv); 28 | 29 | QApplication::setFallbackSessionManagementEnabled(false); 30 | 31 | a.setApplicationName(QString("shadowsocks-qt5")); 32 | a.setApplicationDisplayName(QString("Shadowsocks-Qt5")); 33 | a.setApplicationVersion(APP_VERSION); 34 | 35 | #ifdef Q_OS_WIN 36 | if (QLocale::system().country() == QLocale::China) { 37 | a.setFont(QFont("Microsoft Yahei", 9, QFont::Normal, false)); 38 | } 39 | else { 40 | a.setFont(QFont("Segoe UI", 9, QFont::Normal, false)); 41 | } 42 | #endif 43 | #if defined(Q_OS_WIN) || defined(Q_OS_MAC) 44 | QIcon::setThemeName("Breeze"); 45 | #endif 46 | 47 | QTranslator *ssqt5t = new QTranslator(&a); 48 | ssqt5t->load(QLocale::system(), "ss-qt5", "_", ":/i18n"); 49 | a.installTranslator(ssqt5t); 50 | } 51 | 52 | int main(int argc, char *argv[]) 53 | { 54 | qRegisterMetaTypeStreamOperators("SQProfile"); 55 | 56 | QApplication a(argc, argv); 57 | setupApplication(a); 58 | 59 | QCommandLineParser parser; 60 | parser.addHelpOption(); 61 | parser.addVersionOption(); 62 | QCommandLineOption configFileOption("c", 63 | "specify configuration file.", 64 | "config.ini"); 65 | parser.addOption(configFileOption); 66 | parser.process(a); 67 | 68 | QString configFile = parser.value(configFileOption); 69 | if (configFile.isEmpty()) { 70 | #ifdef Q_OS_WIN 71 | configFile = a.applicationDirPath() + "/config.ini"; 72 | #else 73 | QDir configDir = QDir::homePath() + "/.config/shadowsocks-qt5"; 74 | configFile = configDir.absolutePath() + "/config.ini"; 75 | if (!configDir.exists()) { 76 | configDir.mkpath(configDir.absolutePath()); 77 | } 78 | #endif 79 | } 80 | ConfigHelper conf(configFile); 81 | 82 | MainWindow w(&conf); 83 | mainWindow = &w; 84 | 85 | if (conf.isOnlyOneInstance() && w.isInstanceRunning()) { 86 | return -1; 87 | } 88 | 89 | w.startAutoStartConnections(); 90 | 91 | if (!conf.isHideWindowOnStartup()) { 92 | w.show(); 93 | } 94 | 95 | return a.exec(); 96 | } 97 | -------------------------------------------------------------------------------- /src/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | 4 | #include "connection.h" 5 | #include "editdialog.h" 6 | #include "urihelper.h" 7 | #include "uriinputdialog.h" 8 | #include "sharedialog.h" 9 | #include "settingsdialog.h" 10 | #include "qrcodecapturer.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | MainWindow::MainWindow(ConfigHelper *confHelper, QWidget *parent) : 20 | QMainWindow(parent), 21 | ui(new Ui::MainWindow), 22 | configHelper(confHelper), 23 | instanceRunning(false) 24 | { 25 | Q_ASSERT(configHelper); 26 | 27 | initSingleInstance(); 28 | 29 | ui->setupUi(this); 30 | 31 | //setup Settings menu 32 | #ifndef Q_OS_DARWIN 33 | ui->menuSettings->addAction(ui->toolBar->toggleViewAction()); 34 | #endif 35 | 36 | //initialisation 37 | model = new ConnectionTableModel(this); 38 | configHelper->read(model); 39 | proxyModel = new QSortFilterProxyModel(this); 40 | proxyModel->setSourceModel(model); 41 | proxyModel->setSortRole(Qt::EditRole); 42 | proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); 43 | proxyModel->setFilterKeyColumn(-1);//read from all columns 44 | ui->connectionView->setModel(proxyModel); 45 | ui->toolBar->setToolButtonStyle(static_cast 46 | (configHelper->getToolbarStyle())); 47 | setupActionIcon(); 48 | 49 | notifier = new StatusNotifier(this, configHelper->isHideWindowOnStartup(), this); 50 | 51 | connect(configHelper, &ConfigHelper::toolbarStyleChanged, 52 | ui->toolBar, &QToolBar::setToolButtonStyle); 53 | connect(model, &ConnectionTableModel::message, 54 | notifier, &StatusNotifier::showNotification); 55 | connect(model, &ConnectionTableModel::rowStatusChanged, 56 | this, &MainWindow::onConnectionStatusChanged); 57 | connect(ui->actionSaveManually, &QAction::triggered, 58 | this, &MainWindow::onSaveManually); 59 | connect(ui->actionTestAllLatency, &QAction::triggered, 60 | model, &ConnectionTableModel::testAllLatency); 61 | 62 | //some UI changes accoding to config 63 | ui->toolBar->setVisible(configHelper->isShowToolbar()); 64 | ui->actionShowFilterBar->setChecked(configHelper->isShowFilterBar()); 65 | ui->menuBar->setNativeMenuBar(configHelper->isNativeMenuBar()); 66 | 67 | //Move to the center of the screen 68 | this->move(QApplication::desktop()->screen()->rect().center() - 69 | this->rect().center()); 70 | 71 | //UI signals 72 | connect(ui->actionImportGUIJson, &QAction::triggered, 73 | this, &MainWindow::onImportGuiJson); 74 | connect(ui->actionExportGUIJson, &QAction::triggered, 75 | this, &MainWindow::onExportGuiJson); 76 | connect(ui->actionQuit, &QAction::triggered, qApp, &QApplication::quit); 77 | connect(ui->actionManually, &QAction::triggered, 78 | this, &MainWindow::onAddManually); 79 | connect(ui->actionQRCode, &QAction::triggered, 80 | this, &MainWindow::onAddScreenQRCode); 81 | connect(ui->actionScanQRCodeCapturer, &QAction::triggered, 82 | this, &MainWindow::onAddScreenQRCodeCapturer); 83 | connect(ui->actionQRCodeFromFile, &QAction::triggered, 84 | this, &MainWindow::onAddQRCodeFile); 85 | connect(ui->actionURI, &QAction::triggered, 86 | this, &MainWindow::onAddFromURI); 87 | connect(ui->actionFromConfigJson, &QAction::triggered, 88 | this, &MainWindow::onAddFromConfigJSON); 89 | connect(ui->actionDelete, &QAction::triggered, this, &MainWindow::onDelete); 90 | connect(ui->actionEdit, &QAction::triggered, this, &MainWindow::onEdit); 91 | connect(ui->actionShare, &QAction::triggered, this, &MainWindow::onShare); 92 | connect(ui->actionConnect, &QAction::triggered, 93 | this, &MainWindow::onConnect); 94 | connect(ui->actionForceConnect, &QAction::triggered, 95 | this, &MainWindow::onForceConnect); 96 | connect(ui->actionDisconnect, &QAction::triggered, 97 | this, &MainWindow::onDisconnect); 98 | connect(ui->actionTestLatency, &QAction::triggered, 99 | this, &MainWindow::onLatencyTest); 100 | connect(ui->actionMoveUp, &QAction::triggered, this, &MainWindow::onMoveUp); 101 | connect(ui->actionMoveDown, &QAction::triggered, 102 | this, &MainWindow::onMoveDown); 103 | connect(ui->actionGeneralSettings, &QAction::triggered, 104 | this, &MainWindow::onGeneralSettings); 105 | connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::onAbout); 106 | connect(ui->actionAboutQt, &QAction::triggered, 107 | qApp, &QApplication::aboutQt); 108 | connect(ui->actionReportBug, &QAction::triggered, 109 | this, &MainWindow::onReportBug); 110 | connect(ui->actionShowFilterBar, &QAction::toggled, 111 | configHelper, &ConfigHelper::setShowFilterBar); 112 | connect(ui->actionShowFilterBar, &QAction::toggled, 113 | this, &MainWindow::onFilterToggled); 114 | connect(ui->toolBar, &QToolBar::visibilityChanged, 115 | configHelper, &ConfigHelper::setShowToolbar); 116 | connect(ui->filterLineEdit, &QLineEdit::textChanged, 117 | this, &MainWindow::onFilterTextChanged); 118 | 119 | connect(ui->connectionView, &QTableView::clicked, 120 | this, static_cast 121 | (&MainWindow::checkCurrentIndex)); 122 | connect(ui->connectionView, &QTableView::activated, 123 | this, static_cast 124 | (&MainWindow::checkCurrentIndex)); 125 | connect(ui->connectionView, &QTableView::doubleClicked, 126 | this, &MainWindow::onEdit); 127 | 128 | /* set custom context menu */ 129 | ui->connectionView->setContextMenuPolicy(Qt::CustomContextMenu); 130 | connect(ui->connectionView, &QTableView::customContextMenuRequested, 131 | this, &MainWindow::onCustomContextMenuRequested); 132 | 133 | checkCurrentIndex(); 134 | 135 | // Restore mainWindow's geometry and state 136 | restoreGeometry(configHelper->getMainWindowGeometry()); 137 | restoreState(configHelper->getMainWindowState()); 138 | ui->connectionView->horizontalHeader()->restoreGeometry(configHelper->getTableGeometry()); 139 | ui->connectionView->horizontalHeader()->restoreState(configHelper->getTableState()); 140 | } 141 | 142 | MainWindow::~MainWindow() 143 | { 144 | configHelper->save(*model); 145 | configHelper->setTableGeometry(ui->connectionView->horizontalHeader()->saveGeometry()); 146 | configHelper->setTableState(ui->connectionView->horizontalHeader()->saveState()); 147 | configHelper->setMainWindowGeometry(saveGeometry()); 148 | configHelper->setMainWindowState(saveState()); 149 | 150 | // delete ui after everything in case it's deleted while still needed for 151 | // the functions written above 152 | delete ui; 153 | } 154 | 155 | const QUrl MainWindow::issueUrl = 156 | QUrl("https://github.com/shadowsocks/shadowsocks-qt5/issues"); 157 | 158 | void MainWindow::startAutoStartConnections() 159 | { 160 | configHelper->startAllAutoStart(*model); 161 | } 162 | 163 | void MainWindow::onImportGuiJson() 164 | { 165 | QString file = QFileDialog::getOpenFileName( 166 | this, 167 | tr("Import Connections from gui-config.json"), 168 | QString(), 169 | "GUI Configuration (gui-config.json)"); 170 | if (!file.isNull()) { 171 | configHelper->importGuiConfigJson(model, file); 172 | } 173 | } 174 | 175 | void MainWindow::onExportGuiJson() 176 | { 177 | QString file = QFileDialog::getSaveFileName( 178 | this, 179 | tr("Export Connections as gui-config.json"), 180 | QString("gui-config.json"), 181 | "GUI Configuration (gui-config.json)"); 182 | if (!file.isNull()) { 183 | configHelper->exportGuiConfigJson(*model, file); 184 | } 185 | } 186 | 187 | void MainWindow::onSaveManually() 188 | { 189 | configHelper->save(*model); 190 | } 191 | 192 | void MainWindow::onAddManually() 193 | { 194 | Connection *newCon = new Connection; 195 | newProfile(newCon); 196 | } 197 | 198 | void MainWindow::onAddScreenQRCode() 199 | { 200 | QString uri = QRCodeCapturer::scanEntireScreen(); 201 | if (uri.isNull()) { 202 | QMessageBox::critical( 203 | this, 204 | tr("QR Code Not Found"), 205 | tr("Can't find any QR code image that contains " 206 | "valid URI on your screen(s).")); 207 | } else { 208 | Connection *newCon = new Connection(uri, this); 209 | newProfile(newCon); 210 | } 211 | } 212 | 213 | void MainWindow::onAddScreenQRCodeCapturer() 214 | { 215 | QRCodeCapturer *capturer = new QRCodeCapturer(this); 216 | connect(capturer, &QRCodeCapturer::closed, 217 | capturer, &QRCodeCapturer::deleteLater); 218 | connect(capturer, &QRCodeCapturer::qrCodeFound, 219 | this, &MainWindow::onQRCodeCapturerResultFound, 220 | Qt::DirectConnection); 221 | capturer->show(); 222 | } 223 | 224 | void MainWindow::onAddQRCodeFile() 225 | { 226 | QString qrFile = 227 | QFileDialog::getOpenFileName(this, 228 | tr("Open QR Code Image File"), 229 | QString(), 230 | "Images (*.png *jpg *jpeg *xpm)"); 231 | if (!qrFile.isNull()) { 232 | QImage img(qrFile); 233 | QString uri = URIHelper::decodeImage(img); 234 | if (uri.isNull()) { 235 | QMessageBox::critical(this, 236 | tr("QR Code Not Found"), 237 | tr("Can't find any QR code image that " 238 | "contains valid URI on your screen(s).")); 239 | } else { 240 | Connection *newCon = new Connection(uri, this); 241 | newProfile(newCon); 242 | } 243 | } 244 | } 245 | 246 | void MainWindow::onAddFromURI() 247 | { 248 | URIInputDialog *inputDlg = new URIInputDialog(this); 249 | connect(inputDlg, &URIInputDialog::finished, 250 | inputDlg, &URIInputDialog::deleteLater); 251 | connect(inputDlg, &URIInputDialog::acceptedURI, [&](const QString &uri){ 252 | Connection *newCon = new Connection(uri, this); 253 | newProfile(newCon); 254 | }); 255 | inputDlg->exec(); 256 | } 257 | 258 | void MainWindow::onAddFromConfigJSON() 259 | { 260 | QString file = QFileDialog::getOpenFileName(this, tr("Open config.json"), 261 | QString(), "JSON (*.json)"); 262 | if (!file.isNull()) { 263 | Connection *con = configHelper->configJsonToConnection(file); 264 | if (con) { 265 | newProfile(con); 266 | } 267 | } 268 | } 269 | 270 | void MainWindow::onDelete() 271 | { 272 | if (model->removeRow(proxyModel->mapToSource( 273 | ui->connectionView->currentIndex()).row())) { 274 | configHelper->save(*model); 275 | } 276 | checkCurrentIndex(); 277 | } 278 | 279 | void MainWindow::onEdit() 280 | { 281 | editRow(proxyModel->mapToSource(ui->connectionView->currentIndex()).row()); 282 | } 283 | 284 | void MainWindow::onShare() 285 | { 286 | QByteArray uri = model->getItem( 287 | proxyModel->mapToSource(ui->connectionView->currentIndex()). 288 | row())->getConnection()->getURI(); 289 | ShareDialog *shareDlg = new ShareDialog(uri, this); 290 | connect(shareDlg, &ShareDialog::finished, 291 | shareDlg, &ShareDialog::deleteLater); 292 | shareDlg->exec(); 293 | } 294 | 295 | void MainWindow::onConnect() 296 | { 297 | int row = proxyModel->mapToSource(ui->connectionView->currentIndex()).row(); 298 | Connection *con = model->getItem(row)->getConnection(); 299 | if (con->isValid()) { 300 | con->start(); 301 | } else { 302 | QMessageBox::critical(this, tr("Invalid"), 303 | tr("The connection's profile is invalid!")); 304 | } 305 | } 306 | 307 | void MainWindow::onForceConnect() 308 | { 309 | int row = proxyModel->mapToSource(ui->connectionView->currentIndex()).row(); 310 | Connection *con = model->getItem(row)->getConnection(); 311 | if (con->isValid()) { 312 | model->disconnectConnectionsAt(con->getProfile().localAddress, 313 | con->getProfile().localPort); 314 | con->start(); 315 | } else { 316 | QMessageBox::critical(this, tr("Invalid"), 317 | tr("The connection's profile is invalid!")); 318 | } 319 | } 320 | 321 | void MainWindow::onDisconnect() 322 | { 323 | int row = proxyModel->mapToSource(ui->connectionView->currentIndex()).row(); 324 | model->getItem(row)->getConnection()->stop(); 325 | } 326 | 327 | void MainWindow::onConnectionStatusChanged(const int row, const bool running) 328 | { 329 | if (proxyModel->mapToSource( 330 | ui->connectionView->currentIndex()).row() == row) { 331 | ui->actionConnect->setEnabled(!running); 332 | ui->actionDisconnect->setEnabled(running); 333 | } 334 | } 335 | 336 | void MainWindow::onLatencyTest() 337 | { 338 | model->getItem(proxyModel->mapToSource(ui->connectionView->currentIndex()). 339 | row())->testLatency(); 340 | } 341 | 342 | void MainWindow::onMoveUp() 343 | { 344 | QModelIndex proxyIndex = ui->connectionView->currentIndex(); 345 | int currentRow = proxyModel->mapToSource(proxyIndex).row(); 346 | int targetRow = proxyModel->mapToSource( 347 | proxyModel->index(proxyIndex.row() - 1, 348 | proxyIndex.column(), 349 | proxyIndex.parent()) 350 | ).row(); 351 | model->move(currentRow, targetRow); 352 | checkCurrentIndex(); 353 | } 354 | 355 | void MainWindow::onMoveDown() 356 | { 357 | QModelIndex proxyIndex = ui->connectionView->currentIndex(); 358 | int currentRow = proxyModel->mapToSource(proxyIndex).row(); 359 | int targetRow = proxyModel->mapToSource( 360 | proxyModel->index(proxyIndex.row() + 1, 361 | proxyIndex.column(), 362 | proxyIndex.parent()) 363 | ).row(); 364 | model->move(currentRow, targetRow); 365 | checkCurrentIndex(); 366 | } 367 | 368 | void MainWindow::onGeneralSettings() 369 | { 370 | SettingsDialog *sDlg = new SettingsDialog(configHelper, this); 371 | connect(sDlg, &SettingsDialog::finished, 372 | sDlg, &SettingsDialog::deleteLater); 373 | if (sDlg->exec()) { 374 | configHelper->save(*model); 375 | configHelper->setStartAtLogin(); 376 | } 377 | } 378 | 379 | void MainWindow::newProfile(Connection *newCon) 380 | { 381 | EditDialog *editDlg = new EditDialog(newCon, this); 382 | connect(editDlg, &EditDialog::finished, editDlg, &EditDialog::deleteLater); 383 | if (editDlg->exec()) {//accepted 384 | model->appendConnection(newCon); 385 | configHelper->save(*model); 386 | } else { 387 | newCon->deleteLater(); 388 | } 389 | } 390 | 391 | void MainWindow::editRow(int row) 392 | { 393 | Connection *con = model->getItem(row)->getConnection(); 394 | EditDialog *editDlg = new EditDialog(con, this); 395 | connect(editDlg, &EditDialog::finished, editDlg, &EditDialog::deleteLater); 396 | if (editDlg->exec()) { 397 | configHelper->save(*model); 398 | } 399 | } 400 | 401 | void MainWindow::checkCurrentIndex() 402 | { 403 | checkCurrentIndex(ui->connectionView->currentIndex()); 404 | } 405 | 406 | void MainWindow::checkCurrentIndex(const QModelIndex &_index) 407 | { 408 | QModelIndex index = proxyModel->mapToSource(_index); 409 | const bool valid = index.isValid(); 410 | ui->actionTestLatency->setEnabled(valid); 411 | ui->actionEdit->setEnabled(valid); 412 | ui->actionDelete->setEnabled(valid); 413 | ui->actionShare->setEnabled(valid); 414 | ui->actionMoveUp->setEnabled(valid ? _index.row() > 0 : false); 415 | ui->actionMoveDown->setEnabled(valid ? 416 | _index.row() < model->rowCount() - 1 : 417 | false); 418 | 419 | if (valid) { 420 | const bool &running = 421 | model->getItem(index.row())->getConnection()->isRunning(); 422 | ui->actionConnect->setEnabled(!running); 423 | ui->actionForceConnect->setEnabled(!running); 424 | ui->actionDisconnect->setEnabled(running); 425 | } else { 426 | ui->actionConnect->setEnabled(false); 427 | ui->actionForceConnect->setEnabled(false); 428 | ui->actionDisconnect->setEnabled(false); 429 | } 430 | } 431 | 432 | void MainWindow::onAbout() 433 | { 434 | QString text = QString("

Shadowsocks-Qt5

Version %1
" 435 | "Using libQtShadowsocks %2

" 436 | "

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

" 439 | "

License: " 440 | "GNU Lesser General Public License Version 3
" 441 | "Project Hosted at " 442 | "" 443 | "GitHub

") 444 | .arg(QStringLiteral(APP_VERSION)) 445 | .arg(QSS::Common::version()); 446 | QMessageBox::about(this, tr("About"), text); 447 | } 448 | 449 | void MainWindow::onReportBug() 450 | { 451 | QDesktopServices::openUrl(issueUrl); 452 | } 453 | 454 | void MainWindow::onCustomContextMenuRequested(const QPoint &pos) 455 | { 456 | this->checkCurrentIndex(ui->connectionView->indexAt(pos)); 457 | ui->menuConnection->popup(ui->connectionView->viewport()->mapToGlobal(pos)); 458 | } 459 | 460 | void MainWindow::onFilterToggled(bool show) 461 | { 462 | if (show) { 463 | ui->filterLineEdit->setFocus(); 464 | } 465 | } 466 | 467 | void MainWindow::onFilterTextChanged(const QString &text) 468 | { 469 | proxyModel->setFilterWildcard(text); 470 | } 471 | 472 | void MainWindow::onQRCodeCapturerResultFound(const QString &uri) 473 | { 474 | QRCodeCapturer* capturer = qobject_cast(sender()); 475 | // Disconnect immediately to avoid duplicate signals 476 | disconnect(capturer, &QRCodeCapturer::qrCodeFound, 477 | this, &MainWindow::onQRCodeCapturerResultFound); 478 | Connection *newCon = new Connection(uri, this); 479 | newProfile(newCon); 480 | } 481 | 482 | void MainWindow::hideEvent(QHideEvent *e) 483 | { 484 | QMainWindow::hideEvent(e); 485 | notifier->onWindowVisibleChanged(false); 486 | } 487 | 488 | void MainWindow::showEvent(QShowEvent *e) 489 | { 490 | QMainWindow::showEvent(e); 491 | notifier->onWindowVisibleChanged(true); 492 | this->setFocus(); 493 | } 494 | 495 | void MainWindow::closeEvent(QCloseEvent *e) 496 | { 497 | if (e->spontaneous()) { 498 | e->ignore(); 499 | hide(); 500 | } else { 501 | QMainWindow::closeEvent(e); 502 | } 503 | } 504 | 505 | void MainWindow::setupActionIcon() 506 | { 507 | ui->actionConnect->setIcon(QIcon::fromTheme("network-connect", 508 | QIcon::fromTheme("network-vpn"))); 509 | ui->actionDisconnect->setIcon(QIcon::fromTheme("network-disconnect", 510 | QIcon::fromTheme("network-offline"))); 511 | ui->actionEdit->setIcon(QIcon::fromTheme("document-edit", 512 | QIcon::fromTheme("accessories-text-editor"))); 513 | ui->actionShare->setIcon(QIcon::fromTheme("document-share", 514 | QIcon::fromTheme("preferences-system-sharing"))); 515 | ui->actionTestLatency->setIcon(QIcon::fromTheme("flag", 516 | QIcon::fromTheme("starred"))); 517 | ui->actionImportGUIJson->setIcon(QIcon::fromTheme("document-import", 518 | QIcon::fromTheme("insert-text"))); 519 | ui->actionExportGUIJson->setIcon(QIcon::fromTheme("document-export", 520 | QIcon::fromTheme("document-save-as"))); 521 | ui->actionManually->setIcon(QIcon::fromTheme("edit-guides", 522 | QIcon::fromTheme("accessories-text-editor"))); 523 | ui->actionURI->setIcon(QIcon::fromTheme("text-field", 524 | QIcon::fromTheme("insert-link"))); 525 | ui->actionQRCode->setIcon(QIcon::fromTheme("edit-image-face-recognize", 526 | QIcon::fromTheme("insert-image"))); 527 | ui->actionScanQRCodeCapturer->setIcon(ui->actionQRCode->icon()); 528 | ui->actionGeneralSettings->setIcon(QIcon::fromTheme("configure", 529 | QIcon::fromTheme("preferences-desktop"))); 530 | ui->actionReportBug->setIcon(QIcon::fromTheme("tools-report-bug", 531 | QIcon::fromTheme("help-faq"))); 532 | } 533 | 534 | bool MainWindow::isInstanceRunning() const 535 | { 536 | return instanceRunning; 537 | } 538 | 539 | void MainWindow::initSingleInstance() 540 | { 541 | const QString serverName = QCoreApplication::applicationName(); 542 | QLocalSocket socket; 543 | socket.connectToServer(serverName); 544 | if (socket.waitForConnected(500)) { 545 | instanceRunning = true; 546 | if (configHelper->isOnlyOneInstance()) { 547 | qWarning() << "An instance of ss-qt5 is already running"; 548 | } 549 | QByteArray username = qgetenv("USER"); 550 | if (username.isEmpty()) { 551 | username = qgetenv("USERNAME"); 552 | } 553 | socket.write(username); 554 | socket.waitForBytesWritten(); 555 | return; 556 | } 557 | 558 | /* Can't connect to server, indicating it's the first instance of the user */ 559 | instanceServer = new QLocalServer(this); 560 | instanceServer->setSocketOptions(QLocalServer::WorldAccessOption); 561 | connect(instanceServer, &QLocalServer::newConnection, 562 | this, &MainWindow::onSingleInstanceConnect); 563 | if (instanceServer->listen(serverName)) { 564 | /* Remove server in case of crashes */ 565 | if (instanceServer->serverError() == QAbstractSocket::AddressInUseError && 566 | QFile::exists(instanceServer->serverName())) { 567 | QFile::remove(instanceServer->serverName()); 568 | instanceServer->listen(serverName); 569 | } 570 | } 571 | } 572 | 573 | void MainWindow::onSingleInstanceConnect() 574 | { 575 | QLocalSocket *socket = instanceServer->nextPendingConnection(); 576 | if (!socket) { 577 | return; 578 | } 579 | 580 | if (socket->waitForReadyRead(1000)) { 581 | QByteArray username = qgetenv("USER"); 582 | if (username.isEmpty()) { 583 | username = qgetenv("USERNAME"); 584 | } 585 | 586 | QByteArray recvUsername = socket->readAll(); 587 | if (recvUsername == username) { 588 | // Only show the window if it's the same user 589 | show(); 590 | } else { 591 | qWarning("Another user is trying to run another instance of ss-qt5"); 592 | } 593 | } 594 | socket->deleteLater(); 595 | } 596 | -------------------------------------------------------------------------------- /src/mainwindow.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014-2016 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | */ 18 | 19 | #ifndef MAINWINDOW_H 20 | #define MAINWINDOW_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include "connectiontablemodel.h" 26 | #include "confighelper.h" 27 | #include "statusnotifier.h" 28 | 29 | namespace Ui { 30 | class MainWindow; 31 | } 32 | 33 | class MainWindow : public QMainWindow 34 | { 35 | Q_OBJECT 36 | 37 | public: 38 | explicit MainWindow(ConfigHelper *confHelper, QWidget *parent = 0); 39 | ~MainWindow(); 40 | 41 | void startAutoStartConnections(); 42 | bool isInstanceRunning() const; 43 | 44 | private: 45 | Ui::MainWindow *ui; 46 | 47 | ConnectionTableModel *model; 48 | QSortFilterProxyModel *proxyModel; 49 | ConfigHelper *configHelper; 50 | StatusNotifier *notifier; 51 | 52 | QLocalServer* instanceServer; 53 | bool instanceRunning; 54 | void initSingleInstance(); 55 | 56 | void newProfile(Connection *); 57 | void editRow(int row); 58 | void blockChildrenSignals(bool); 59 | void checkCurrentIndex(); 60 | void setupActionIcon(); 61 | 62 | static const QUrl issueUrl; 63 | 64 | private slots: 65 | void onImportGuiJson(); 66 | void onExportGuiJson(); 67 | void onSaveManually(); 68 | void onAddManually(); 69 | void onAddScreenQRCode(); 70 | void onAddScreenQRCodeCapturer(); 71 | void onAddQRCodeFile(); 72 | void onAddFromURI(); 73 | void onAddFromConfigJSON(); 74 | void onDelete(); 75 | void onEdit(); 76 | void onShare(); 77 | void onConnect(); 78 | void onForceConnect(); 79 | void onDisconnect(); 80 | void onConnectionStatusChanged(const int row, const bool running); 81 | void onLatencyTest(); 82 | void onMoveUp(); 83 | void onMoveDown(); 84 | void onGeneralSettings(); 85 | void checkCurrentIndex(const QModelIndex &index); 86 | void onAbout(); 87 | void onReportBug(); 88 | void onCustomContextMenuRequested(const QPoint &pos); 89 | void onFilterToggled(bool); 90 | void onFilterTextChanged(const QString &text); 91 | void onQRCodeCapturerResultFound(const QString &uri); 92 | void onSingleInstanceConnect(); 93 | 94 | protected slots: 95 | void hideEvent(QHideEvent *e); 96 | void showEvent(QShowEvent *e); 97 | void closeEvent(QCloseEvent *e); 98 | }; 99 | 100 | #endif // MAINWINDOW_H 101 | -------------------------------------------------------------------------------- /src/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 480 10 | 480 11 | 12 | 13 | 14 | 15 | 400 16 | 400 17 | 18 | 19 | 20 | Connection Manager 21 | 22 | 23 | 24 | :/icons/icons/shadowsocks-qt5.png:/icons/icons/shadowsocks-qt5.png 25 | 26 | 27 | Qt::ToolButtonFollowStyle 28 | 29 | 30 | 31 | 32 | 33 | 34 | Input to filter 35 | 36 | 37 | true 38 | 39 | 40 | 41 | 42 | 43 | 44 | QAbstractItemView::NoEditTriggers 45 | 46 | 47 | false 48 | 49 | 50 | QAbstractItemView::SingleSelection 51 | 52 | 53 | QAbstractItemView::SelectRows 54 | 55 | 56 | false 57 | 58 | 59 | true 60 | 61 | 62 | false 63 | 64 | 65 | false 66 | 67 | 68 | false 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | Show Toolbar 77 | 78 | 79 | false 80 | 81 | 82 | Qt::AllToolBarAreas 83 | 84 | 85 | Qt::ToolButtonFollowStyle 86 | 87 | 88 | false 89 | 90 | 91 | TopToolBarArea 92 | 93 | 94 | false 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 0 111 | 0 112 | 480 113 | 26 114 | 115 | 116 | 117 | 118 | &Connection 119 | 120 | 121 | 122 | &Add 123 | 124 | 125 | 126 | .. 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | Fi&le 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | Settin&gs 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | &Help 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | &Manually 185 | 186 | 187 | Add connection manually 188 | 189 | 190 | 191 | 192 | &Scan QR Code on Screen 193 | 194 | 195 | 196 | 197 | 198 | .. 199 | 200 | 201 | &From QR Code Image File 202 | 203 | 204 | From QR code image file 205 | 206 | 207 | 208 | 209 | &URI 210 | 211 | 212 | Add connection from URI 213 | 214 | 215 | 216 | 217 | 218 | .. 219 | 220 | 221 | &Delete 222 | 223 | 224 | 225 | 226 | &Edit 227 | 228 | 229 | 230 | 231 | &Connect 232 | 233 | 234 | 235 | 236 | D&isconnect 237 | 238 | 239 | 240 | 241 | 242 | .. 243 | 244 | 245 | &Quit 246 | 247 | 248 | Ctrl+Q 249 | 250 | 251 | 252 | 253 | 254 | .. 255 | 256 | 257 | &About 258 | 259 | 260 | 261 | 262 | About &Qt 263 | 264 | 265 | 266 | 267 | &General Settings 268 | 269 | 270 | 271 | 272 | &Share 273 | 274 | 275 | 276 | 277 | &Report Bug 278 | 279 | 280 | 281 | 282 | &Test Latency 283 | 284 | 285 | Test the latency of selected connection 286 | 287 | 288 | 289 | 290 | Test All C&onnections Latency 291 | 292 | 293 | 294 | 295 | &Import Connections from gui-config.json 296 | 297 | 298 | Import connections from old version configuration file 299 | 300 | 301 | 302 | 303 | 304 | .. 305 | 306 | 307 | From &config.json 308 | 309 | 310 | 311 | 312 | 313 | .. 314 | 315 | 316 | &Save Manually 317 | 318 | 319 | Ctrl+Shift+S 320 | 321 | 322 | 323 | 324 | 325 | .. 326 | 327 | 328 | &Move Up 329 | 330 | 331 | 332 | 333 | 334 | .. 335 | 336 | 337 | Mo&ve Down 338 | 339 | 340 | 341 | 342 | true 343 | 344 | 345 | true 346 | 347 | 348 | &Show Filter Bar 349 | 350 | 351 | Ctrl+F 352 | 353 | 354 | 355 | 356 | &Export as gui-config.json 357 | 358 | 359 | 360 | 361 | Scan &QR Code using Capturer 362 | 363 | 364 | Scan QR Code using Capturer 365 | 366 | 367 | 368 | 369 | &Force Connect 370 | 371 | 372 | Connect to this connection and disconnect any connections currently using the same local port 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | actionShowFilterBar 383 | toggled(bool) 384 | filterLineEdit 385 | setVisible(bool) 386 | 387 | 388 | -1 389 | -1 390 | 391 | 392 | 239 393 | 88 394 | 395 | 396 | 397 | 398 | 399 | -------------------------------------------------------------------------------- /src/portvalidator.cpp: -------------------------------------------------------------------------------- 1 | #include "portvalidator.h" 2 | #include "ssvalidator.h" 3 | 4 | PortValidator::PortValidator(QObject *parent) 5 | : QValidator(parent) 6 | {} 7 | 8 | QValidator::State PortValidator::validate(QString &input, int &) const 9 | { 10 | if (SSValidator::validatePort(input)) { 11 | return Acceptable; 12 | } 13 | else 14 | return Invalid; 15 | } 16 | -------------------------------------------------------------------------------- /src/portvalidator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014-2016 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | */ 18 | 19 | #ifndef PORTVALIDATOR_H 20 | #define PORTVALIDATOR_H 21 | 22 | #include 23 | 24 | class PortValidator : public QValidator 25 | { 26 | public: 27 | PortValidator(QObject *parent = 0); 28 | State validate(QString &input, int &) const; 29 | }; 30 | 31 | #endif // PORTVALIDATOR_H 32 | -------------------------------------------------------------------------------- /src/qrcodecapturer.cpp: -------------------------------------------------------------------------------- 1 | #include "qrcodecapturer.h" 2 | #include "urihelper.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | QRCodeCapturer::QRCodeCapturer(QWidget *parent) : 10 | QMainWindow(parent) 11 | { 12 | #ifdef Q_OS_WIN 13 | /* 14 | * On Windows, it requires Qt::FramelessWindowHint to be set to make 15 | * translucent background work, but we need a window with borders. 16 | * Therefore, we set the entire window semi-transparent so that 17 | * users are still able to see the region below while moving the 18 | * capturer above the QR code image. 19 | */ 20 | this->setWindowOpacity(0.75); 21 | #else 22 | this->setAttribute(Qt::WA_TranslucentBackground, true); 23 | #endif 24 | this->setWindowTitle(tr("QR Capturer")); 25 | this->setMinimumSize(400, 400); 26 | } 27 | 28 | QRCodeCapturer::~QRCodeCapturer() 29 | {} 30 | 31 | QString QRCodeCapturer::scanEntireScreen() 32 | { 33 | QString uri; 34 | QList screens = qApp->screens(); 35 | for (QList::iterator sc = screens.begin(); 36 | sc != screens.end(); 37 | ++sc) { 38 | QImage raw_sc = (*sc)->grabWindow(qApp->desktop()->winId()).toImage(); 39 | QString result = URIHelper::decodeImage(raw_sc); 40 | if (!result.isNull()) { 41 | uri = result; 42 | } 43 | } 44 | return uri; 45 | } 46 | 47 | void QRCodeCapturer::moveEvent(QMoveEvent *e) 48 | { 49 | QMainWindow::moveEvent(e); 50 | decodeCurrentRegion(); 51 | } 52 | 53 | void QRCodeCapturer::resizeEvent(QResizeEvent *e) 54 | { 55 | QMainWindow::resizeEvent(e); 56 | decodeCurrentRegion(); 57 | } 58 | 59 | void QRCodeCapturer::closeEvent(QCloseEvent *e) 60 | { 61 | QMainWindow::closeEvent(e); 62 | emit closed(); 63 | } 64 | 65 | void QRCodeCapturer::decodeCurrentRegion() 66 | { 67 | QScreen *sc = qApp->screens().at(qApp->desktop()->screenNumber(this)); 68 | QRect geometry = this->geometry(); 69 | QImage raw_sc = sc->grabWindow(qApp->desktop()->winId(), 70 | geometry.x(), 71 | geometry.y(), 72 | geometry.width(), 73 | geometry.height()).toImage(); 74 | QString result = URIHelper::decodeImage(raw_sc); 75 | if (!result.isNull()) { 76 | this->close(); 77 | // moveEvent and resizeEvent both happen quite frequent 78 | // it's very likely this signal would be emitted multiple times 79 | // the solution is to use Qt::DirectConnection signal-slot connection 80 | // and disconnect such a connection in the slot function 81 | emit qrCodeFound(result); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/qrcodecapturer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | */ 18 | 19 | #ifndef QRCODECAPTURER_H 20 | #define QRCODECAPTURER_H 21 | 22 | #include 23 | 24 | class QRCodeCapturer : public QMainWindow 25 | { 26 | Q_OBJECT 27 | 28 | public: 29 | explicit QRCodeCapturer(QWidget *parent = 0); 30 | ~QRCodeCapturer(); 31 | 32 | static QString scanEntireScreen(); 33 | 34 | signals: 35 | void qrCodeFound(const QString &result); 36 | void closed(); 37 | 38 | protected slots: 39 | void moveEvent(QMoveEvent *e); 40 | void resizeEvent(QResizeEvent *e); 41 | void closeEvent(QCloseEvent *e); 42 | 43 | private: 44 | void decodeCurrentRegion(); 45 | }; 46 | 47 | #endif // QRCODECAPTURER_H 48 | -------------------------------------------------------------------------------- /src/qrwidget.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "qrwidget.h" 6 | 7 | QRWidget::QRWidget(QWidget *parent) : 8 | QWidget(parent) 9 | {} 10 | 11 | void QRWidget::setQRData(const QByteArray &data) 12 | { 13 | qrImage = QImage(512, 512, QImage::Format_Mono); 14 | QPainter painter(&qrImage); 15 | QRcode *qrcode = QRcode_encodeString(data.constData(), 1, QR_ECLEVEL_L, QR_MODE_8, 1); 16 | if (qrcode) { 17 | QColor fg(Qt::black); 18 | QColor bg(Qt::white); 19 | painter.setBrush(bg); 20 | painter.setPen(Qt::NoPen); 21 | painter.drawRect(0, 0, 512, 512); 22 | painter.setBrush(fg); 23 | const int s = qrcode->width > 0 ? qrcode->width : 1; 24 | const qreal scale = 512.0 / s; 25 | for(int y = 0; y < s; y++){ 26 | for(int x = 0; x < s; x++){ 27 | if(qrcode->data[y * s + x] & 0x01){ 28 | const qreal rx1 = x * scale, ry1 = y * scale; 29 | QRectF r(rx1, ry1, scale, scale); 30 | painter.drawRects(&r,1); 31 | } 32 | } 33 | } 34 | QRcode_free(qrcode); 35 | } 36 | else { 37 | qWarning() << tr("Generating QR code failed."); 38 | } 39 | } 40 | 41 | void QRWidget::paintEvent(QPaintEvent *e) 42 | { 43 | QWidget::paintEvent(e); 44 | 45 | QStyleOption opt; 46 | opt.init(this); 47 | QPainter painter(this); 48 | style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); 49 | 50 | QSizeF nSize = qrImage.size().scaled(this->size(), Qt::KeepAspectRatio); 51 | painter.translate((this->width() - nSize.width()) / 2, (this->height() - nSize.height()) / 2); 52 | painter.scale(nSize.width() / qrImage.width(), nSize.height() / qrImage.height()); 53 | painter.drawImage(0, 0, qrImage); 54 | } 55 | 56 | const QImage& QRWidget::getQRImage() const 57 | { 58 | return qrImage; 59 | } 60 | -------------------------------------------------------------------------------- /src/qrwidget.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014-2016 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | */ 18 | 19 | #ifndef QRWIDGET_H 20 | #define QRWIDGET_H 21 | 22 | #include 23 | #include 24 | 25 | class QRWidget : public QWidget 26 | { 27 | Q_OBJECT 28 | public: 29 | explicit QRWidget(QWidget *parent = 0); 30 | void setQRData(const QByteArray &data); 31 | const QImage& getQRImage() const; 32 | 33 | private: 34 | QImage qrImage; 35 | 36 | protected: 37 | void paintEvent(QPaintEvent *); 38 | }; 39 | 40 | #endif // QRWIDGET_H 41 | -------------------------------------------------------------------------------- /src/settingsdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "settingsdialog.h" 2 | #include "ui_settingsdialog.h" 3 | #include 4 | 5 | SettingsDialog::SettingsDialog(ConfigHelper *ch, QWidget *parent) : 6 | QDialog(parent), 7 | ui(new Ui::SettingsDialog), 8 | helper(ch) 9 | { 10 | ui->setupUi(this); 11 | 12 | ui->toolbarStyleComboBox->setCurrentIndex(helper->getToolbarStyle()); 13 | ui->hideCheckBox->setChecked(helper->isHideWindowOnStartup()); 14 | ui->startAtLoginCheckbox->setChecked(helper->isStartAtLogin()); 15 | ui->oneInstanceCheckBox->setChecked(helper->isOnlyOneInstance()); 16 | ui->nativeMenuBarCheckBox->setChecked(helper->isNativeMenuBar()); 17 | 18 | connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::onAccepted); 19 | connect(ui->toolbarStyleComboBox, &QComboBox::currentTextChanged, this, &SettingsDialog::onChanged); 20 | connect(ui->hideCheckBox, &QCheckBox::stateChanged, this, &SettingsDialog::onChanged); 21 | connect(ui->startAtLoginCheckbox, &QCheckBox::stateChanged, this, &SettingsDialog::onChanged); 22 | connect(ui->oneInstanceCheckBox, &QCheckBox::stateChanged, this, &SettingsDialog::onChanged); 23 | connect(ui->nativeMenuBarCheckBox, &QCheckBox::stateChanged, this, &SettingsDialog::onChanged); 24 | 25 | ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); 26 | 27 | this->adjustSize(); 28 | } 29 | 30 | SettingsDialog::~SettingsDialog() 31 | { 32 | delete ui; 33 | } 34 | 35 | void SettingsDialog::onAccepted() 36 | { 37 | helper->setGeneralSettings(ui->toolbarStyleComboBox->currentIndex(), 38 | ui->hideCheckBox->isChecked(), 39 | ui->startAtLoginCheckbox->isChecked(), 40 | ui->oneInstanceCheckBox->isChecked(), 41 | ui->nativeMenuBarCheckBox->isChecked()); 42 | this->accept(); 43 | } 44 | 45 | void SettingsDialog::onChanged() 46 | { 47 | ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); 48 | } 49 | -------------------------------------------------------------------------------- /src/settingsdialog.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | */ 18 | 19 | #ifndef SETTINGSDIALOG_H 20 | #define SETTINGSDIALOG_H 21 | 22 | #include 23 | #include "confighelper.h" 24 | 25 | namespace Ui { 26 | class SettingsDialog; 27 | } 28 | 29 | class SettingsDialog : public QDialog 30 | { 31 | Q_OBJECT 32 | 33 | public: 34 | explicit SettingsDialog(ConfigHelper *ch, QWidget *parent = 0); 35 | ~SettingsDialog(); 36 | 37 | private: 38 | Ui::SettingsDialog *ui; 39 | ConfigHelper *helper; 40 | 41 | private slots: 42 | void onAccepted(); 43 | void onChanged(); 44 | }; 45 | 46 | #endif // SETTINGSDIALOG_H 47 | -------------------------------------------------------------------------------- /src/settingsdialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SettingsDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 340 10 | 217 11 | 12 | 13 | 14 | General Settings 15 | 16 | 17 | 18 | 19 | 20 | Qt::Horizontal 21 | 22 | 23 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 24 | 25 | 26 | 27 | 28 | 29 | 30 | Toolbar Style 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 0 39 | 0 40 | 41 | 42 | 43 | 44 | Icons Only 45 | 46 | 47 | 48 | 49 | Text Only 50 | 51 | 52 | 53 | 54 | Text Alongside Icons 55 | 56 | 57 | 58 | 59 | Text Under Icons 60 | 61 | 62 | 63 | 64 | System Style 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Allow only one instance running 73 | 74 | 75 | 76 | 77 | 78 | 79 | Hide window on startup 80 | 81 | 82 | 83 | 84 | 85 | 86 | Need to restart the application for this change to take effect 87 | 88 | 89 | Use native menu bar 90 | 91 | 92 | 93 | 94 | 95 | 96 | Start at login 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | buttonBox 106 | rejected() 107 | SettingsDialog 108 | reject() 109 | 110 | 111 | 316 112 | 260 113 | 114 | 115 | 286 116 | 274 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /src/shadowsocks-qt5.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Shadowsocks-Qt5 3 | GenericName=Shadowsocks-Qt5 4 | Comment=Shadowsocks GUI client 5 | Exec=ss-qt5 6 | Icon=shadowsocks-qt5 7 | Terminal=false 8 | Type=Application 9 | Categories=Network; 10 | StartupNotify=true 11 | -------------------------------------------------------------------------------- /src/sharedialog.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "qrwidget.h" 3 | #include "sharedialog.h" 4 | #include "ui_sharedialog.h" 5 | 6 | ShareDialog::ShareDialog(const QByteArray &ssUrl, QWidget *parent) : 7 | QDialog(parent), 8 | ui(new Ui::ShareDialog) 9 | { 10 | ui->setupUi(this); 11 | ui->qrWidget->setQRData(ssUrl); 12 | ui->ssUrlEdit->setText(QString(ssUrl)); 13 | ui->ssUrlEdit->setCursorPosition(0); 14 | 15 | connect(ui->saveButton, &QPushButton::clicked, this, &ShareDialog::onSaveButtonClicked); 16 | 17 | this->adjustSize(); 18 | } 19 | 20 | ShareDialog::~ShareDialog() 21 | { 22 | delete ui; 23 | } 24 | 25 | void ShareDialog::onSaveButtonClicked() 26 | { 27 | QString filename = QFileDialog::getSaveFileName(this, tr("Save QR Code"), QString(), "PNG (*.png)"); 28 | if (!filename.isEmpty()) { 29 | ui->qrWidget->getQRImage().save(filename); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/sharedialog.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014-2016 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | */ 18 | 19 | #ifndef SHAREDIALOG_H 20 | #define SHAREDIALOG_H 21 | 22 | #include 23 | 24 | namespace Ui { 25 | class ShareDialog; 26 | } 27 | 28 | class ShareDialog : public QDialog 29 | { 30 | Q_OBJECT 31 | 32 | public: 33 | explicit ShareDialog(const QByteArray &ssUrl, QWidget *parent = 0); 34 | ~ShareDialog(); 35 | 36 | private: 37 | Ui::ShareDialog *ui; 38 | 39 | private slots: 40 | void onSaveButtonClicked(); 41 | }; 42 | 43 | #endif // SHAREDIALOG_H 44 | -------------------------------------------------------------------------------- /src/sharedialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ShareDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 300 10 | 360 11 | 12 | 13 | 14 | 15 | 300 16 | 360 17 | 18 | 19 | 20 | Share Profile 21 | 22 | 23 | true 24 | 25 | 26 | 27 | 28 | 29 | 30 | 0 31 | 10 32 | 33 | 34 | 35 | 36 | 256 37 | 256 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | background-color: "transparent" 46 | 47 | 48 | false 49 | 50 | 51 | true 52 | 53 | 54 | 55 | 56 | 57 | 58 | Save QR code as an Image file 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | QRWidget 67 | QWidget 68 |
qrwidget.h
69 | 1 70 |
71 |
72 | 73 | 74 |
75 | -------------------------------------------------------------------------------- /src/sqprofile.cpp: -------------------------------------------------------------------------------- 1 | #include "sqprofile.h" 2 | 3 | SQProfile::SQProfile() 4 | { 5 | autoStart = false; 6 | debug = false; 7 | serverPort = 8388; 8 | localPort = 1080; 9 | name = QObject::tr("Unnamed Profile"); 10 | localAddress = QString("127.0.0.1"); 11 | method = QString("RC4-MD5"); 12 | timeout = 600; 13 | latency = LATENCY_UNKNOWN; 14 | currentUsage = 0; 15 | totalUsage = 0; 16 | QDate currentDate = QDate::currentDate(); 17 | nextResetDate = QDate(currentDate.year(), currentDate.month() + 1, 1); 18 | httpMode = false; 19 | } 20 | 21 | SQProfile::SQProfile(const QSS::Profile &profile) : SQProfile() 22 | { 23 | name = QString::fromStdString(profile.name()); 24 | localAddress = QString::fromStdString(profile.localAddress()); 25 | localPort = profile.localPort(); 26 | serverPort = profile.serverPort(); 27 | serverAddress = QString::fromStdString(profile.serverAddress()); 28 | method = QString::fromStdString(profile.method()).toUpper(); 29 | password = QString::fromStdString(profile.password()); 30 | timeout = profile.timeout(); 31 | httpMode = profile.httpProxy(); 32 | debug = profile.debug(); 33 | } 34 | 35 | SQProfile::SQProfile(const QString &uri) 36 | { 37 | *this = SQProfile(QSS::Profile::fromUri(uri.toStdString())); 38 | } 39 | 40 | QSS::Profile SQProfile::toProfile() const 41 | { 42 | QSS::Profile qssprofile; 43 | qssprofile.setName(name.toStdString()); 44 | qssprofile.setServerAddress(serverAddress.toStdString()); 45 | qssprofile.setServerPort(serverPort); 46 | qssprofile.setLocalAddress(localAddress.toStdString()); 47 | qssprofile.setLocalPort(localPort); 48 | qssprofile.setMethod(method.toLower().toStdString()); 49 | qssprofile.setPassword(password.toStdString()); 50 | qssprofile.setTimeout(timeout); 51 | qssprofile.setHttpProxy(httpMode); 52 | if (debug) { 53 | qssprofile.enableDebug(); 54 | } else { 55 | qssprofile.disableDebug(); 56 | } 57 | return qssprofile; 58 | } 59 | 60 | QDataStream& operator << (QDataStream &out, const SQProfile &p) 61 | { 62 | 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; 63 | return out; 64 | } 65 | 66 | QDataStream& operator >> (QDataStream &in, SQProfile &p) 67 | { 68 | 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; 69 | return in; 70 | } 71 | -------------------------------------------------------------------------------- /src/sqprofile.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014-2016 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | */ 18 | 19 | #ifndef SQPROFILE_H 20 | #define SQPROFILE_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | struct SQProfile 28 | { 29 | SQProfile(); 30 | SQProfile(const QSS::Profile& profile); // Copy values from QSS Profile 31 | SQProfile(const QString& uri); // Construct it using ss protocol 32 | 33 | QSS::Profile toProfile() const; // Convert it into a QSS Profile 34 | 35 | bool autoStart; 36 | bool debug; 37 | quint16 serverPort; 38 | quint16 localPort; 39 | QString name; 40 | QString serverAddress; 41 | QString localAddress; 42 | QString method; 43 | QString password; 44 | int timeout; 45 | int latency; 46 | quint64 currentUsage; 47 | quint64 totalUsage; 48 | QDateTime lastTime;//last time this connection is used 49 | QDate nextResetDate;//next scheduled date to reset data usage 50 | bool httpMode; 51 | 52 | static const int LATENCY_TIMEOUT = -1; 53 | static const int LATENCY_ERROR = -2; 54 | static const int LATENCY_UNKNOWN = -3; 55 | }; 56 | Q_DECLARE_METATYPE(SQProfile) 57 | 58 | QDataStream& operator << (QDataStream &out, const SQProfile &p); 59 | QDataStream& operator >> (QDataStream &in, SQProfile &p); 60 | 61 | #endif // SQPROFILE_H 62 | -------------------------------------------------------------------------------- /src/ss-qt5.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocks/shadowsocks-qt5/2692abea80435c16cb24f14cc3dd336b398a3c19/src/ss-qt5.ico -------------------------------------------------------------------------------- /src/ss-qt5.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "ss-qt5.ico" 2 | -------------------------------------------------------------------------------- /src/ssvalidator.cpp: -------------------------------------------------------------------------------- 1 | #include "ssvalidator.h" 2 | #include 3 | 4 | QStringList SSValidator::supportedMethodList() 5 | { 6 | std::vector methodBA = QSS::Cipher::supportedMethods(); 7 | std::sort(methodBA.begin(), methodBA.end()); 8 | QStringList methodList; 9 | for (const std::string& method : methodBA) { 10 | methodList.push_back(QString::fromStdString(method).toUpper()); 11 | } 12 | return methodList; 13 | } 14 | 15 | bool SSValidator::validate(const QString &input) 16 | { 17 | bool valid = true; 18 | try { 19 | QSS::Profile::fromUri(input.toStdString()); 20 | } catch(const std::exception&) { 21 | valid = false; 22 | } 23 | return valid; 24 | } 25 | 26 | bool SSValidator::validatePort(const QString &port) 27 | { 28 | bool ok; 29 | port.toUShort(&ok); 30 | return ok; 31 | } 32 | 33 | bool SSValidator::validateMethod(const QString &method) 34 | { 35 | static const QStringList validMethodList = supportedMethodList(); 36 | return validMethodList.contains(method, Qt::CaseInsensitive); 37 | } 38 | -------------------------------------------------------------------------------- /src/ssvalidator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014-2016 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | */ 18 | 19 | #ifndef SSVALIDATOR_H 20 | #define SSVALIDATOR_H 21 | #include 22 | #include 23 | 24 | class SSValidator 25 | { 26 | public: 27 | static bool validate(const QString &input); 28 | static bool validatePort(const QString &port); 29 | static bool validateMethod(const QString &method); 30 | 31 | /* 32 | * Return supported encryption method list at run-time 33 | * To avoid repetitive query, please store return result as static. 34 | */ 35 | static QStringList supportedMethodList(); 36 | }; 37 | 38 | #endif // SSVALIDATOR_H 39 | -------------------------------------------------------------------------------- /src/statusnotifier.cpp: -------------------------------------------------------------------------------- 1 | #include "statusnotifier.h" 2 | #include "mainwindow.h" 3 | #include 4 | #ifdef Q_OS_LINUX 5 | #include 6 | #include 7 | #include 8 | #endif 9 | 10 | StatusNotifier::StatusNotifier(MainWindow *w, bool startHiden, QObject *parent) : 11 | QObject(parent), 12 | window(w) 13 | { 14 | systray.setIcon(QIcon(":/icons/icons/shadowsocks-qt5.png")); 15 | systray.setToolTip(QString("Shadowsocks-Qt5")); 16 | connect(&systray, &QSystemTrayIcon::activated, [this](QSystemTrayIcon::ActivationReason r) { 17 | if (r != QSystemTrayIcon::Context) { 18 | this->activate(); 19 | } 20 | }); 21 | minimiseRestoreAction = new QAction(startHiden ? tr("Restore") : tr("Minimise"), this); 22 | connect(minimiseRestoreAction, &QAction::triggered, this, &StatusNotifier::activate); 23 | systrayMenu.addAction(minimiseRestoreAction); 24 | systrayMenu.addAction(QIcon::fromTheme("application-exit", QIcon::fromTheme("exit")), tr("Quit"), qApp, SLOT(quit())); 25 | systray.setContextMenu(&systrayMenu); 26 | systray.show(); 27 | } 28 | 29 | void StatusNotifier::activate() 30 | { 31 | if (!window->isVisible() || window->isMinimized()) { 32 | window->showNormal(); 33 | window->activateWindow(); 34 | window->raise(); 35 | } else { 36 | window->hide(); 37 | } 38 | } 39 | 40 | void StatusNotifier::showNotification(const QString &msg) 41 | { 42 | #ifdef Q_OS_LINUX 43 | //Using DBus to send message. 44 | QDBusMessage method = QDBusMessage::createMethodCall("org.freedesktop.Notifications","/org/freedesktop/Notifications", "org.freedesktop.Notifications", "Notify"); 45 | QVariantList args; 46 | args << QCoreApplication::applicationName() << quint32(0) << "shadowsocks-qt5" << "Shadowsocks-Qt5" << msg << QStringList () << QVariantMap() << qint32(-1); 47 | method.setArguments(args); 48 | QDBusConnection::sessionBus().asyncCall(method); 49 | #else 50 | systray.showMessage("Shadowsocks-Qt5", msg); 51 | #endif 52 | } 53 | 54 | void StatusNotifier::onWindowVisibleChanged(bool visible) 55 | { 56 | minimiseRestoreAction->setText(visible ? tr("Minimise") : tr("Restore")); 57 | } 58 | -------------------------------------------------------------------------------- /src/statusnotifier.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2017 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | */ 18 | 19 | #ifndef STATUSNOTIFIER_H 20 | #define STATUSNOTIFIER_H 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | class MainWindow; 27 | 28 | class StatusNotifier : public QObject 29 | { 30 | Q_OBJECT 31 | public: 32 | StatusNotifier(MainWindow *w, bool startHiden, QObject *parent = 0); 33 | 34 | public slots: 35 | void activate(); 36 | void showNotification(const QString &); 37 | void onWindowVisibleChanged(bool visible); 38 | 39 | private: 40 | QMenu systrayMenu; 41 | QAction *minimiseRestoreAction; 42 | QSystemTrayIcon systray; 43 | MainWindow *window; 44 | }; 45 | 46 | #endif // STATUSNOTIFIER_H 47 | -------------------------------------------------------------------------------- /src/translations.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | i18n/ss-qt5_zh_CN.qm 4 | i18n/ss-qt5_zh_TW.qm 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/urihelper.cpp: -------------------------------------------------------------------------------- 1 | #include "urihelper.h" 2 | #include 3 | 4 | QImage URIHelper::convertToGrey(const QImage &input) 5 | { 6 | if (input.isNull()) { 7 | return QImage(); 8 | } 9 | QImage ret(input.width(), input.height(), QImage::Format_Indexed8); 10 | QVector gtable(256); 11 | for (int i = 0; i < 256; ++i) { 12 | gtable[i] = qRgb(i, i, i); 13 | } 14 | ret.setColorTable(gtable); 15 | for (int i = 0; i < input.width(); ++i) { 16 | for (int j = 0; j < input.height(); ++j) { 17 | QRgb val = input.pixel(i, j); 18 | ret.setPixel(i, j, qGray(val)); 19 | } 20 | } 21 | return ret; 22 | } 23 | 24 | QString URIHelper::decodeImage(const QImage &img) 25 | { 26 | QString uri; 27 | QImage gimg = convertToGrey(img); 28 | 29 | //use zbar to decode the QR code 30 | zbar::ImageScanner scanner; 31 | zbar::Image image(gimg.bytesPerLine(), gimg.height(), "Y800", gimg.bits(), gimg.byteCount()); 32 | scanner.scan(image); 33 | zbar::SymbolSet res_set = scanner.get_results(); 34 | for (zbar::SymbolIterator it = res_set.symbol_begin(); it != res_set.symbol_end(); ++it) { 35 | if (it->get_type() == zbar::ZBAR_QRCODE) { 36 | /* 37 | * uri will be overwritten if the result is valid 38 | * this means always the last uri gets used 39 | * therefore, please only leave one QR code for the sake of accuracy 40 | */ 41 | QString result = QString::fromStdString(it->get_data()); 42 | if (result.left(5).compare("ss://", Qt::CaseInsensitive) == 0) { 43 | uri = result; 44 | } 45 | } 46 | } 47 | 48 | return uri; 49 | } 50 | -------------------------------------------------------------------------------- /src/urihelper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | */ 18 | 19 | #ifndef URIHELPER_H 20 | #define URIHELPER_H 21 | 22 | #include 23 | #include 24 | 25 | class URIHelper 26 | { 27 | public: 28 | virtual ~URIHelper() = 0; 29 | 30 | static QImage convertToGrey(const QImage &input); 31 | static QString decodeImage(const QImage &img); 32 | }; 33 | 34 | #endif // URIHELPER_H 35 | -------------------------------------------------------------------------------- /src/uriinputdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "uriinputdialog.h" 2 | #include "ui_uriinputdialog.h" 3 | #include "ssvalidator.h" 4 | #include 5 | 6 | URIInputDialog::URIInputDialog(QWidget *parent) : 7 | QDialog(parent), 8 | ui(new Ui::URIInputDialog) 9 | { 10 | ui->setupUi(this); 11 | ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); 12 | connect(ui->uriEdit, &QLineEdit::textChanged, this, &URIInputDialog::onURIChanged); 13 | connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &URIInputDialog::onAccepted); 14 | 15 | this->adjustSize(); 16 | } 17 | 18 | URIInputDialog::~URIInputDialog() 19 | { 20 | delete ui; 21 | } 22 | 23 | void URIInputDialog::onURIChanged(const QString &str) 24 | { 25 | if (!SSValidator::validate(str)) { 26 | ui->uriEdit->setStyleSheet("background: pink"); 27 | ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); 28 | } 29 | else { 30 | ui->uriEdit->setStyleSheet("background: #81F279"); 31 | ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); 32 | } 33 | } 34 | 35 | void URIInputDialog::onAccepted() 36 | { 37 | emit acceptedURI(ui->uriEdit->text()); 38 | this->accept(); 39 | } 40 | -------------------------------------------------------------------------------- /src/uriinputdialog.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015-2016 Symeon Huang 3 | * 4 | * shadowsocks-qt5 is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU Lesser General Public License as published 6 | * by the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * shadowsocks-qt5 is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with libQtShadowsocks; see the file LICENSE. If not, see 16 | * . 17 | */ 18 | 19 | #ifndef URIINPUTDIALOG_H 20 | #define URIINPUTDIALOG_H 21 | 22 | #include 23 | 24 | namespace Ui { 25 | class URIInputDialog; 26 | } 27 | 28 | class URIInputDialog : public QDialog 29 | { 30 | Q_OBJECT 31 | 32 | signals: 33 | void acceptedURI(const QString &uri); 34 | 35 | public: 36 | explicit URIInputDialog(QWidget *parent = 0); 37 | ~URIInputDialog(); 38 | 39 | private: 40 | Ui::URIInputDialog *ui; 41 | 42 | private slots: 43 | void onAccepted(); 44 | void onURIChanged(const QString &); 45 | }; 46 | 47 | #endif // URIINPUTDIALOG_H 48 | -------------------------------------------------------------------------------- /src/uriinputdialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | URIInputDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 159 11 | 12 | 13 | 14 | URI Input Dialog 15 | 16 | 17 | 18 | 19 | 20 | Please input ss:// URI 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 300 29 | 0 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | Qt::Vertical 38 | 39 | 40 | 41 | 20 42 | 0 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Qt::Horizontal 51 | 52 | 53 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | buttonBox 63 | rejected() 64 | URIInputDialog 65 | reject() 66 | 67 | 68 | 316 69 | 260 70 | 71 | 72 | 286 73 | 274 74 | 75 | 76 | 77 | 78 | 79 | --------------------------------------------------------------------------------