(&MainWindow::checkCurrentIndex));
connect(ui->connectionView, &QTableView::doubleClicked,
this, &MainWindow::onEdit);
/* set custom context menu */
ui->connectionView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->connectionView, &QTableView::customContextMenuRequested,
this, &MainWindow::onCustomContextMenuRequested);
checkCurrentIndex();
// Restore mainWindow's geometry and state
restoreGeometry(configHelper->getMainWindowGeometry());
restoreState(configHelper->getMainWindowState());
ui->connectionView->horizontalHeader()->restoreGeometry(configHelper->getTableGeometry());
ui->connectionView->horizontalHeader()->restoreState(configHelper->getTableState());
}
MainWindow::~MainWindow()
{
configHelper->save(*model);
configHelper->setTableGeometry(ui->connectionView->horizontalHeader()->saveGeometry());
configHelper->setTableState(ui->connectionView->horizontalHeader()->saveState());
configHelper->setMainWindowGeometry(saveGeometry());
configHelper->setMainWindowState(saveState());
// delete ui after everything in case it's deleted while still needed for
// the functions written above
delete ui;
}
const QUrl MainWindow::issueUrl =
QUrl("https://github.com/shadowsocks/shadowsocks-qt5/issues");
void MainWindow::startAutoStartConnections()
{
configHelper->startAllAutoStart(*model);
}
void MainWindow::onImportGuiJson()
{
QString file = QFileDialog::getOpenFileName(
this,
tr("Import Connections from gui-config.json"),
QString(),
"GUI Configuration (gui-config.json)");
if (!file.isNull()) {
configHelper->importGuiConfigJson(model, file);
}
}
void MainWindow::onExportGuiJson()
{
QString file = QFileDialog::getSaveFileName(
this,
tr("Export Connections as gui-config.json"),
QString("gui-config.json"),
"GUI Configuration (gui-config.json)");
if (!file.isNull()) {
configHelper->exportGuiConfigJson(*model, file);
}
}
void MainWindow::onSaveManually()
{
configHelper->save(*model);
}
void MainWindow::onAddManually()
{
Connection *newCon = new Connection;
newProfile(newCon);
}
void MainWindow::onAddScreenQRCode()
{
QString uri = QRCodeCapturer::scanEntireScreen();
if (uri.isNull()) {
QMessageBox::critical(
this,
tr("QR Code Not Found"),
tr("Can't find any QR code image that contains "
"valid URI on your screen(s)."));
} else {
Connection *newCon = new Connection(uri, this);
newProfile(newCon);
}
}
void MainWindow::onAddScreenQRCodeCapturer()
{
QRCodeCapturer *capturer = new QRCodeCapturer(this);
connect(capturer, &QRCodeCapturer::closed,
capturer, &QRCodeCapturer::deleteLater);
connect(capturer, &QRCodeCapturer::qrCodeFound,
this, &MainWindow::onQRCodeCapturerResultFound,
Qt::DirectConnection);
capturer->show();
}
void MainWindow::onAddQRCodeFile()
{
QString qrFile =
QFileDialog::getOpenFileName(this,
tr("Open QR Code Image File"),
QString(),
"Images (*.png *jpg *jpeg *xpm)");
if (!qrFile.isNull()) {
QImage img(qrFile);
QString uri = URIHelper::decodeImage(img);
if (uri.isNull()) {
QMessageBox::critical(this,
tr("QR Code Not Found"),
tr("Can't find any QR code image that "
"contains valid URI on your screen(s)."));
} else {
Connection *newCon = new Connection(uri, this);
newProfile(newCon);
}
}
}
void MainWindow::onAddFromURI()
{
URIInputDialog *inputDlg = new URIInputDialog(this);
connect(inputDlg, &URIInputDialog::finished,
inputDlg, &URIInputDialog::deleteLater);
connect(inputDlg, &URIInputDialog::acceptedURI, [&](const QString &uri){
Connection *newCon = new Connection(uri, this);
newProfile(newCon);
});
inputDlg->exec();
}
void MainWindow::onAddFromConfigJSON()
{
QString file = QFileDialog::getOpenFileName(this, tr("Open config.json"),
QString(), "JSON (*.json)");
if (!file.isNull()) {
Connection *con = configHelper->configJsonToConnection(file);
if (con) {
newProfile(con);
}
}
}
void MainWindow::onDelete()
{
if (model->removeRow(proxyModel->mapToSource(
ui->connectionView->currentIndex()).row())) {
configHelper->save(*model);
}
checkCurrentIndex();
}
void MainWindow::onEdit()
{
editRow(proxyModel->mapToSource(ui->connectionView->currentIndex()).row());
}
void MainWindow::onShare()
{
QByteArray uri = model->getItem(
proxyModel->mapToSource(ui->connectionView->currentIndex()).
row())->getConnection()->getURI();
ShareDialog *shareDlg = new ShareDialog(uri, this);
connect(shareDlg, &ShareDialog::finished,
shareDlg, &ShareDialog::deleteLater);
shareDlg->exec();
}
void MainWindow::onConnect()
{
int row = proxyModel->mapToSource(ui->connectionView->currentIndex()).row();
Connection *con = model->getItem(row)->getConnection();
if (con->isValid()) {
con->start();
} else {
QMessageBox::critical(this, tr("Invalid"),
tr("The connection's profile is invalid!"));
}
}
void MainWindow::onForceConnect()
{
int row = proxyModel->mapToSource(ui->connectionView->currentIndex()).row();
Connection *con = model->getItem(row)->getConnection();
if (con->isValid()) {
model->disconnectConnectionsAt(con->getProfile().localAddress,
con->getProfile().localPort);
con->start();
} else {
QMessageBox::critical(this, tr("Invalid"),
tr("The connection's profile is invalid!"));
}
}
void MainWindow::onDisconnect()
{
int row = proxyModel->mapToSource(ui->connectionView->currentIndex()).row();
model->getItem(row)->getConnection()->stop();
}
void MainWindow::onConnectionStatusChanged(const int row, const bool running)
{
if (proxyModel->mapToSource(
ui->connectionView->currentIndex()).row() == row) {
ui->actionConnect->setEnabled(!running);
ui->actionDisconnect->setEnabled(running);
}
}
void MainWindow::onLatencyTest()
{
model->getItem(proxyModel->mapToSource(ui->connectionView->currentIndex()).
row())->testLatency();
}
void MainWindow::onMoveUp()
{
QModelIndex proxyIndex = ui->connectionView->currentIndex();
int currentRow = proxyModel->mapToSource(proxyIndex).row();
int targetRow = proxyModel->mapToSource(
proxyModel->index(proxyIndex.row() - 1,
proxyIndex.column(),
proxyIndex.parent())
).row();
model->move(currentRow, targetRow);
checkCurrentIndex();
}
void MainWindow::onMoveDown()
{
QModelIndex proxyIndex = ui->connectionView->currentIndex();
int currentRow = proxyModel->mapToSource(proxyIndex).row();
int targetRow = proxyModel->mapToSource(
proxyModel->index(proxyIndex.row() + 1,
proxyIndex.column(),
proxyIndex.parent())
).row();
model->move(currentRow, targetRow);
checkCurrentIndex();
}
void MainWindow::onGeneralSettings()
{
SettingsDialog *sDlg = new SettingsDialog(configHelper, this);
connect(sDlg, &SettingsDialog::finished,
sDlg, &SettingsDialog::deleteLater);
if (sDlg->exec()) {
configHelper->save(*model);
configHelper->setStartAtLogin();
}
}
void MainWindow::newProfile(Connection *newCon)
{
EditDialog *editDlg = new EditDialog(newCon, this);
connect(editDlg, &EditDialog::finished, editDlg, &EditDialog::deleteLater);
if (editDlg->exec()) {//accepted
model->appendConnection(newCon);
configHelper->save(*model);
} else {
newCon->deleteLater();
}
}
void MainWindow::editRow(int row)
{
Connection *con = model->getItem(row)->getConnection();
EditDialog *editDlg = new EditDialog(con, this);
connect(editDlg, &EditDialog::finished, editDlg, &EditDialog::deleteLater);
if (editDlg->exec()) {
configHelper->save(*model);
}
}
void MainWindow::checkCurrentIndex()
{
checkCurrentIndex(ui->connectionView->currentIndex());
}
void MainWindow::checkCurrentIndex(const QModelIndex &_index)
{
QModelIndex index = proxyModel->mapToSource(_index);
const bool valid = index.isValid();
ui->actionTestLatency->setEnabled(valid);
ui->actionEdit->setEnabled(valid);
ui->actionDelete->setEnabled(valid);
ui->actionShare->setEnabled(valid);
ui->actionMoveUp->setEnabled(valid ? _index.row() > 0 : false);
ui->actionMoveDown->setEnabled(valid ?
_index.row() < model->rowCount() - 1 :
false);
if (valid) {
const bool &running =
model->getItem(index.row())->getConnection()->isRunning();
ui->actionConnect->setEnabled(!running);
ui->actionForceConnect->setEnabled(!running);
ui->actionDisconnect->setEnabled(running);
} else {
ui->actionConnect->setEnabled(false);
ui->actionForceConnect->setEnabled(false);
ui->actionDisconnect->setEnabled(false);
}
}
void MainWindow::onAbout()
{
QString text = QString("Shadowsocks-Qt5
Version %1
"
"Using libQtShadowsocks %2
"
"Copyright © 2014-2018 Symeon Huang "
"("
"@librehat)
"
"License: "
"GNU Lesser General Public License Version 3
"
"Project Hosted at "
""
"GitHub
")
.arg(QStringLiteral(APP_VERSION))
.arg(QSS::Common::version());
QMessageBox::about(this, tr("About"), text);
}
void MainWindow::onReportBug()
{
QDesktopServices::openUrl(issueUrl);
}
void MainWindow::onCustomContextMenuRequested(const QPoint &pos)
{
this->checkCurrentIndex(ui->connectionView->indexAt(pos));
ui->menuConnection->popup(ui->connectionView->viewport()->mapToGlobal(pos));
}
void MainWindow::onFilterToggled(bool show)
{
if (show) {
ui->filterLineEdit->setFocus();
}
}
void MainWindow::onFilterTextChanged(const QString &text)
{
proxyModel->setFilterWildcard(text);
}
void MainWindow::onQRCodeCapturerResultFound(const QString &uri)
{
QRCodeCapturer* capturer = qobject_cast(sender());
// Disconnect immediately to avoid duplicate signals
disconnect(capturer, &QRCodeCapturer::qrCodeFound,
this, &MainWindow::onQRCodeCapturerResultFound);
Connection *newCon = new Connection(uri, this);
newProfile(newCon);
}
void MainWindow::hideEvent(QHideEvent *e)
{
QMainWindow::hideEvent(e);
notifier->onWindowVisibleChanged(false);
}
void MainWindow::showEvent(QShowEvent *e)
{
QMainWindow::showEvent(e);
notifier->onWindowVisibleChanged(true);
this->setFocus();
}
void MainWindow::closeEvent(QCloseEvent *e)
{
if (e->spontaneous()) {
e->ignore();
hide();
} else {
QMainWindow::closeEvent(e);
}
}
void MainWindow::setupActionIcon()
{
ui->actionConnect->setIcon(QIcon::fromTheme("network-connect",
QIcon::fromTheme("network-vpn")));
ui->actionDisconnect->setIcon(QIcon::fromTheme("network-disconnect",
QIcon::fromTheme("network-offline")));
ui->actionEdit->setIcon(QIcon::fromTheme("document-edit",
QIcon::fromTheme("accessories-text-editor")));
ui->actionShare->setIcon(QIcon::fromTheme("document-share",
QIcon::fromTheme("preferences-system-sharing")));
ui->actionTestLatency->setIcon(QIcon::fromTheme("flag",
QIcon::fromTheme("starred")));
ui->actionImportGUIJson->setIcon(QIcon::fromTheme("document-import",
QIcon::fromTheme("insert-text")));
ui->actionExportGUIJson->setIcon(QIcon::fromTheme("document-export",
QIcon::fromTheme("document-save-as")));
ui->actionManually->setIcon(QIcon::fromTheme("edit-guides",
QIcon::fromTheme("accessories-text-editor")));
ui->actionURI->setIcon(QIcon::fromTheme("text-field",
QIcon::fromTheme("insert-link")));
ui->actionQRCode->setIcon(QIcon::fromTheme("edit-image-face-recognize",
QIcon::fromTheme("insert-image")));
ui->actionScanQRCodeCapturer->setIcon(ui->actionQRCode->icon());
ui->actionGeneralSettings->setIcon(QIcon::fromTheme("configure",
QIcon::fromTheme("preferences-desktop")));
ui->actionReportBug->setIcon(QIcon::fromTheme("tools-report-bug",
QIcon::fromTheme("help-faq")));
}
bool MainWindow::isInstanceRunning() const
{
return instanceRunning;
}
void MainWindow::initSingleInstance()
{
const QString serverName = QCoreApplication::applicationName();
QLocalSocket socket;
socket.connectToServer(serverName);
if (socket.waitForConnected(500)) {
instanceRunning = true;
if (configHelper->isOnlyOneInstance()) {
qWarning() << "An instance of ss-qt5 is already running";
}
QByteArray username = qgetenv("USER");
if (username.isEmpty()) {
username = qgetenv("USERNAME");
}
socket.write(username);
socket.waitForBytesWritten();
return;
}
/* Can't connect to server, indicating it's the first instance of the user */
instanceServer = new QLocalServer(this);
instanceServer->setSocketOptions(QLocalServer::WorldAccessOption);
connect(instanceServer, &QLocalServer::newConnection,
this, &MainWindow::onSingleInstanceConnect);
if (instanceServer->listen(serverName)) {
/* Remove server in case of crashes */
if (instanceServer->serverError() == QAbstractSocket::AddressInUseError &&
QFile::exists(instanceServer->serverName())) {
QFile::remove(instanceServer->serverName());
instanceServer->listen(serverName);
}
}
}
void MainWindow::onSingleInstanceConnect()
{
QLocalSocket *socket = instanceServer->nextPendingConnection();
if (!socket) {
return;
}
if (socket->waitForReadyRead(1000)) {
QByteArray username = qgetenv("USER");
if (username.isEmpty()) {
username = qgetenv("USERNAME");
}
QByteArray recvUsername = socket->readAll();
if (recvUsername == username) {
// Only show the window if it's the same user
show();
} else {
qWarning("Another user is trying to run another instance of ss-qt5");
}
}
socket->deleteLater();
}
================================================
FILE: src/mainwindow.h
================================================
/*
* Copyright (C) 2014-2016 Symeon Huang
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* .
*/
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
#include
#include "connectiontablemodel.h"
#include "confighelper.h"
#include "statusnotifier.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(ConfigHelper *confHelper, QWidget *parent = 0);
~MainWindow();
void startAutoStartConnections();
bool isInstanceRunning() const;
private:
Ui::MainWindow *ui;
ConnectionTableModel *model;
QSortFilterProxyModel *proxyModel;
ConfigHelper *configHelper;
StatusNotifier *notifier;
QLocalServer* instanceServer;
bool instanceRunning;
void initSingleInstance();
void newProfile(Connection *);
void editRow(int row);
void blockChildrenSignals(bool);
void checkCurrentIndex();
void setupActionIcon();
static const QUrl issueUrl;
private slots:
void onImportGuiJson();
void onExportGuiJson();
void onSaveManually();
void onAddManually();
void onAddScreenQRCode();
void onAddScreenQRCodeCapturer();
void onAddQRCodeFile();
void onAddFromURI();
void onAddFromConfigJSON();
void onDelete();
void onEdit();
void onShare();
void onConnect();
void onForceConnect();
void onDisconnect();
void onConnectionStatusChanged(const int row, const bool running);
void onLatencyTest();
void onMoveUp();
void onMoveDown();
void onGeneralSettings();
void checkCurrentIndex(const QModelIndex &index);
void onAbout();
void onReportBug();
void onCustomContextMenuRequested(const QPoint &pos);
void onFilterToggled(bool);
void onFilterTextChanged(const QString &text);
void onQRCodeCapturerResultFound(const QString &uri);
void onSingleInstanceConnect();
protected slots:
void hideEvent(QHideEvent *e);
void showEvent(QShowEvent *e);
void closeEvent(QCloseEvent *e);
};
#endif // MAINWINDOW_H
================================================
FILE: src/mainwindow.ui
================================================
MainWindow
0
0
480
480
400
400
Connection Manager
:/icons/icons/shadowsocks-qt5.png:/icons/icons/shadowsocks-qt5.png
Qt::ToolButtonFollowStyle
-
Input to filter
true
-
QAbstractItemView::NoEditTriggers
false
QAbstractItemView::SingleSelection
QAbstractItemView::SelectRows
false
true
false
false
false
Show Toolbar
false
Qt::AllToolBarAreas
Qt::ToolButtonFollowStyle
false
TopToolBarArea
false
&Manually
Add connection manually
&Scan QR Code on Screen
..
&From QR Code Image File
From QR code image file
&URI
Add connection from URI
..
&Delete
&Edit
&Connect
D&isconnect
..
&Quit
Ctrl+Q
..
&About
About &Qt
&General Settings
&Share
&Report Bug
&Test Latency
Test the latency of selected connection
Test All C&onnections Latency
&Import Connections from gui-config.json
Import connections from old version configuration file
..
From &config.json
..
&Save Manually
Ctrl+Shift+S
..
&Move Up
..
Mo&ve Down
true
true
&Show Filter Bar
Ctrl+F
&Export as gui-config.json
Scan &QR Code using Capturer
Scan QR Code using Capturer
&Force Connect
Connect to this connection and disconnect any connections currently using the same local port
actionShowFilterBar
toggled(bool)
filterLineEdit
setVisible(bool)
-1
-1
239
88
================================================
FILE: src/portvalidator.cpp
================================================
#include "portvalidator.h"
#include "ssvalidator.h"
PortValidator::PortValidator(QObject *parent)
: QValidator(parent)
{}
QValidator::State PortValidator::validate(QString &input, int &) const
{
if (SSValidator::validatePort(input)) {
return Acceptable;
}
else
return Invalid;
}
================================================
FILE: src/portvalidator.h
================================================
/*
* Copyright (C) 2014-2016 Symeon Huang
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* .
*/
#ifndef PORTVALIDATOR_H
#define PORTVALIDATOR_H
#include
class PortValidator : public QValidator
{
public:
PortValidator(QObject *parent = 0);
State validate(QString &input, int &) const;
};
#endif // PORTVALIDATOR_H
================================================
FILE: src/qrcodecapturer.cpp
================================================
#include "qrcodecapturer.h"
#include "urihelper.h"
#include
#include
#include
#include
#include
QRCodeCapturer::QRCodeCapturer(QWidget *parent) :
QMainWindow(parent)
{
#ifdef Q_OS_WIN
/*
* On Windows, it requires Qt::FramelessWindowHint to be set to make
* translucent background work, but we need a window with borders.
* Therefore, we set the entire window semi-transparent so that
* users are still able to see the region below while moving the
* capturer above the QR code image.
*/
this->setWindowOpacity(0.75);
#else
this->setAttribute(Qt::WA_TranslucentBackground, true);
#endif
this->setWindowTitle(tr("QR Capturer"));
this->setMinimumSize(400, 400);
}
QRCodeCapturer::~QRCodeCapturer()
{}
QString QRCodeCapturer::scanEntireScreen()
{
QString uri;
QList screens = qApp->screens();
for (QList::iterator sc = screens.begin();
sc != screens.end();
++sc) {
QImage raw_sc = (*sc)->grabWindow(qApp->desktop()->winId()).toImage();
QString result = URIHelper::decodeImage(raw_sc);
if (!result.isNull()) {
uri = result;
}
}
return uri;
}
void QRCodeCapturer::moveEvent(QMoveEvent *e)
{
QMainWindow::moveEvent(e);
decodeCurrentRegion();
}
void QRCodeCapturer::resizeEvent(QResizeEvent *e)
{
QMainWindow::resizeEvent(e);
decodeCurrentRegion();
}
void QRCodeCapturer::closeEvent(QCloseEvent *e)
{
QMainWindow::closeEvent(e);
emit closed();
}
void QRCodeCapturer::decodeCurrentRegion()
{
QScreen *sc = qApp->screens().at(qApp->desktop()->screenNumber(this));
QRect geometry = this->geometry();
QImage raw_sc = sc->grabWindow(qApp->desktop()->winId(),
geometry.x(),
geometry.y(),
geometry.width(),
geometry.height()).toImage();
QString result = URIHelper::decodeImage(raw_sc);
if (!result.isNull()) {
this->close();
// moveEvent and resizeEvent both happen quite frequent
// it's very likely this signal would be emitted multiple times
// the solution is to use Qt::DirectConnection signal-slot connection
// and disconnect such a connection in the slot function
emit qrCodeFound(result);
}
}
================================================
FILE: src/qrcodecapturer.h
================================================
/*
* Copyright (C) 2015-2016 Symeon Huang
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* .
*/
#ifndef QRCODECAPTURER_H
#define QRCODECAPTURER_H
#include
class QRCodeCapturer : public QMainWindow
{
Q_OBJECT
public:
explicit QRCodeCapturer(QWidget *parent = 0);
~QRCodeCapturer();
static QString scanEntireScreen();
signals:
void qrCodeFound(const QString &result);
void closed();
protected slots:
void moveEvent(QMoveEvent *e);
void resizeEvent(QResizeEvent *e);
void closeEvent(QCloseEvent *e);
private:
void decodeCurrentRegion();
};
#endif // QRCODECAPTURER_H
================================================
FILE: src/qrwidget.cpp
================================================
#include
#include
#include
#include
#include "qrwidget.h"
QRWidget::QRWidget(QWidget *parent) :
QWidget(parent)
{}
void QRWidget::setQRData(const QByteArray &data)
{
qrImage = QImage(512, 512, QImage::Format_Mono);
QPainter painter(&qrImage);
QRcode *qrcode = QRcode_encodeString(data.constData(), 1, QR_ECLEVEL_L, QR_MODE_8, 1);
if (qrcode) {
QColor fg(Qt::black);
QColor bg(Qt::white);
painter.setBrush(bg);
painter.setPen(Qt::NoPen);
painter.drawRect(0, 0, 512, 512);
painter.setBrush(fg);
const int s = qrcode->width > 0 ? qrcode->width : 1;
const qreal scale = 512.0 / s;
for(int y = 0; y < s; y++){
for(int x = 0; x < s; x++){
if(qrcode->data[y * s + x] & 0x01){
const qreal rx1 = x * scale, ry1 = y * scale;
QRectF r(rx1, ry1, scale, scale);
painter.drawRects(&r,1);
}
}
}
QRcode_free(qrcode);
}
else {
qWarning() << tr("Generating QR code failed.");
}
}
void QRWidget::paintEvent(QPaintEvent *e)
{
QWidget::paintEvent(e);
QStyleOption opt;
opt.init(this);
QPainter painter(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
QSizeF nSize = qrImage.size().scaled(this->size(), Qt::KeepAspectRatio);
painter.translate((this->width() - nSize.width()) / 2, (this->height() - nSize.height()) / 2);
painter.scale(nSize.width() / qrImage.width(), nSize.height() / qrImage.height());
painter.drawImage(0, 0, qrImage);
}
const QImage& QRWidget::getQRImage() const
{
return qrImage;
}
================================================
FILE: src/qrwidget.h
================================================
/*
* Copyright (C) 2014-2016 Symeon Huang
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* .
*/
#ifndef QRWIDGET_H
#define QRWIDGET_H
#include
#include
class QRWidget : public QWidget
{
Q_OBJECT
public:
explicit QRWidget(QWidget *parent = 0);
void setQRData(const QByteArray &data);
const QImage& getQRImage() const;
private:
QImage qrImage;
protected:
void paintEvent(QPaintEvent *);
};
#endif // QRWIDGET_H
================================================
FILE: src/settingsdialog.cpp
================================================
#include "settingsdialog.h"
#include "ui_settingsdialog.h"
#include
SettingsDialog::SettingsDialog(ConfigHelper *ch, QWidget *parent) :
QDialog(parent),
ui(new Ui::SettingsDialog),
helper(ch)
{
ui->setupUi(this);
ui->toolbarStyleComboBox->setCurrentIndex(helper->getToolbarStyle());
ui->hideCheckBox->setChecked(helper->isHideWindowOnStartup());
ui->startAtLoginCheckbox->setChecked(helper->isStartAtLogin());
ui->oneInstanceCheckBox->setChecked(helper->isOnlyOneInstance());
ui->nativeMenuBarCheckBox->setChecked(helper->isNativeMenuBar());
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::onAccepted);
connect(ui->toolbarStyleComboBox, &QComboBox::currentTextChanged, this, &SettingsDialog::onChanged);
connect(ui->hideCheckBox, &QCheckBox::stateChanged, this, &SettingsDialog::onChanged);
connect(ui->startAtLoginCheckbox, &QCheckBox::stateChanged, this, &SettingsDialog::onChanged);
connect(ui->oneInstanceCheckBox, &QCheckBox::stateChanged, this, &SettingsDialog::onChanged);
connect(ui->nativeMenuBarCheckBox, &QCheckBox::stateChanged, this, &SettingsDialog::onChanged);
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
this->adjustSize();
}
SettingsDialog::~SettingsDialog()
{
delete ui;
}
void SettingsDialog::onAccepted()
{
helper->setGeneralSettings(ui->toolbarStyleComboBox->currentIndex(),
ui->hideCheckBox->isChecked(),
ui->startAtLoginCheckbox->isChecked(),
ui->oneInstanceCheckBox->isChecked(),
ui->nativeMenuBarCheckBox->isChecked());
this->accept();
}
void SettingsDialog::onChanged()
{
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
}
================================================
FILE: src/settingsdialog.h
================================================
/*
* Copyright (C) 2015-2016 Symeon Huang
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* .
*/
#ifndef SETTINGSDIALOG_H
#define SETTINGSDIALOG_H
#include
#include "confighelper.h"
namespace Ui {
class SettingsDialog;
}
class SettingsDialog : public QDialog
{
Q_OBJECT
public:
explicit SettingsDialog(ConfigHelper *ch, QWidget *parent = 0);
~SettingsDialog();
private:
Ui::SettingsDialog *ui;
ConfigHelper *helper;
private slots:
void onAccepted();
void onChanged();
};
#endif // SETTINGSDIALOG_H
================================================
FILE: src/settingsdialog.ui
================================================
SettingsDialog
0
0
340
217
General Settings
-
Qt::Horizontal
QDialogButtonBox::Cancel|QDialogButtonBox::Ok
-
Toolbar Style
-
0
0
-
Icons Only
-
Text Only
-
Text Alongside Icons
-
Text Under Icons
-
System Style
-
Allow only one instance running
-
Hide window on startup
-
Need to restart the application for this change to take effect
Use native menu bar
-
Start at login
buttonBox
rejected()
SettingsDialog
reject()
316
260
286
274
================================================
FILE: src/shadowsocks-qt5.desktop
================================================
[Desktop Entry]
Name=Shadowsocks-Qt5
GenericName=Shadowsocks-Qt5
Comment=Shadowsocks GUI client
Exec=ss-qt5
Icon=shadowsocks-qt5
Terminal=false
Type=Application
Categories=Network;
StartupNotify=true
================================================
FILE: src/sharedialog.cpp
================================================
#include
#include "qrwidget.h"
#include "sharedialog.h"
#include "ui_sharedialog.h"
ShareDialog::ShareDialog(const QByteArray &ssUrl, QWidget *parent) :
QDialog(parent),
ui(new Ui::ShareDialog)
{
ui->setupUi(this);
ui->qrWidget->setQRData(ssUrl);
ui->ssUrlEdit->setText(QString(ssUrl));
ui->ssUrlEdit->setCursorPosition(0);
connect(ui->saveButton, &QPushButton::clicked, this, &ShareDialog::onSaveButtonClicked);
this->adjustSize();
}
ShareDialog::~ShareDialog()
{
delete ui;
}
void ShareDialog::onSaveButtonClicked()
{
QString filename = QFileDialog::getSaveFileName(this, tr("Save QR Code"), QString(), "PNG (*.png)");
if (!filename.isEmpty()) {
ui->qrWidget->getQRImage().save(filename);
}
}
================================================
FILE: src/sharedialog.h
================================================
/*
* Copyright (C) 2014-2016 Symeon Huang
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* .
*/
#ifndef SHAREDIALOG_H
#define SHAREDIALOG_H
#include
namespace Ui {
class ShareDialog;
}
class ShareDialog : public QDialog
{
Q_OBJECT
public:
explicit ShareDialog(const QByteArray &ssUrl, QWidget *parent = 0);
~ShareDialog();
private:
Ui::ShareDialog *ui;
private slots:
void onSaveButtonClicked();
};
#endif // SHAREDIALOG_H
================================================
FILE: src/sharedialog.ui
================================================
ShareDialog
0
0
300
360
300
360
Share Profile
true
-
0
10
256
256
-
background-color: "transparent"
false
true
-
Save QR code as an Image file
QRWidget
QWidget
1
================================================
FILE: src/sqprofile.cpp
================================================
#include "sqprofile.h"
SQProfile::SQProfile()
{
autoStart = false;
debug = false;
serverPort = 8388;
localPort = 1080;
name = QObject::tr("Unnamed Profile");
localAddress = QString("127.0.0.1");
method = QString("RC4-MD5");
timeout = 600;
latency = LATENCY_UNKNOWN;
currentUsage = 0;
totalUsage = 0;
QDate currentDate = QDate::currentDate();
nextResetDate = QDate(currentDate.year(), currentDate.month() + 1, 1);
httpMode = false;
}
SQProfile::SQProfile(const QSS::Profile &profile) : SQProfile()
{
name = QString::fromStdString(profile.name());
localAddress = QString::fromStdString(profile.localAddress());
localPort = profile.localPort();
serverPort = profile.serverPort();
serverAddress = QString::fromStdString(profile.serverAddress());
method = QString::fromStdString(profile.method()).toUpper();
password = QString::fromStdString(profile.password());
timeout = profile.timeout();
httpMode = profile.httpProxy();
debug = profile.debug();
}
SQProfile::SQProfile(const QString &uri)
{
*this = SQProfile(QSS::Profile::fromUri(uri.toStdString()));
}
QSS::Profile SQProfile::toProfile() const
{
QSS::Profile qssprofile;
qssprofile.setName(name.toStdString());
qssprofile.setServerAddress(serverAddress.toStdString());
qssprofile.setServerPort(serverPort);
qssprofile.setLocalAddress(localAddress.toStdString());
qssprofile.setLocalPort(localPort);
qssprofile.setMethod(method.toLower().toStdString());
qssprofile.setPassword(password.toStdString());
qssprofile.setTimeout(timeout);
qssprofile.setHttpProxy(httpMode);
if (debug) {
qssprofile.enableDebug();
} else {
qssprofile.disableDebug();
}
return qssprofile;
}
QDataStream& operator << (QDataStream &out, const SQProfile &p)
{
out << p.autoStart << p.debug << p.serverPort << p.localPort << p.name << p.serverAddress << p.localAddress << p.method << p.password << p.timeout << p.latency << p.currentUsage << p.totalUsage << p.lastTime << p.nextResetDate << p.httpMode;
return out;
}
QDataStream& operator >> (QDataStream &in, SQProfile &p)
{
in >> p.autoStart >> p.debug >> p.serverPort >> p.localPort >> p.name >> p.serverAddress >> p.localAddress >> p.method >> p.password >> p.timeout >> p.latency >> p.currentUsage >> p.totalUsage >> p.lastTime >> p.nextResetDate >> p.httpMode;
return in;
}
================================================
FILE: src/sqprofile.h
================================================
/*
* Copyright (C) 2014-2016 Symeon Huang
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* .
*/
#ifndef SQPROFILE_H
#define SQPROFILE_H
#include
#include
#include
#include
struct SQProfile
{
SQProfile();
SQProfile(const QSS::Profile& profile); // Copy values from QSS Profile
SQProfile(const QString& uri); // Construct it using ss protocol
QSS::Profile toProfile() const; // Convert it into a QSS Profile
bool autoStart;
bool debug;
quint16 serverPort;
quint16 localPort;
QString name;
QString serverAddress;
QString localAddress;
QString method;
QString password;
int timeout;
int latency;
quint64 currentUsage;
quint64 totalUsage;
QDateTime lastTime;//last time this connection is used
QDate nextResetDate;//next scheduled date to reset data usage
bool httpMode;
static const int LATENCY_TIMEOUT = -1;
static const int LATENCY_ERROR = -2;
static const int LATENCY_UNKNOWN = -3;
};
Q_DECLARE_METATYPE(SQProfile)
QDataStream& operator << (QDataStream &out, const SQProfile &p);
QDataStream& operator >> (QDataStream &in, SQProfile &p);
#endif // SQPROFILE_H
================================================
FILE: src/ss-qt5.rc
================================================
IDI_ICON1 ICON DISCARDABLE "ss-qt5.ico"
================================================
FILE: src/ssvalidator.cpp
================================================
#include "ssvalidator.h"
#include
QStringList SSValidator::supportedMethodList()
{
std::vector methodBA = QSS::Cipher::supportedMethods();
std::sort(methodBA.begin(), methodBA.end());
QStringList methodList;
for (const std::string& method : methodBA) {
methodList.push_back(QString::fromStdString(method).toUpper());
}
return methodList;
}
bool SSValidator::validate(const QString &input)
{
bool valid = true;
try {
QSS::Profile::fromUri(input.toStdString());
} catch(const std::exception&) {
valid = false;
}
return valid;
}
bool SSValidator::validatePort(const QString &port)
{
bool ok;
port.toUShort(&ok);
return ok;
}
bool SSValidator::validateMethod(const QString &method)
{
static const QStringList validMethodList = supportedMethodList();
return validMethodList.contains(method, Qt::CaseInsensitive);
}
================================================
FILE: src/ssvalidator.h
================================================
/*
* Copyright (C) 2014-2016 Symeon Huang
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* .
*/
#ifndef SSVALIDATOR_H
#define SSVALIDATOR_H
#include
#include
class SSValidator
{
public:
static bool validate(const QString &input);
static bool validatePort(const QString &port);
static bool validateMethod(const QString &method);
/*
* Return supported encryption method list at run-time
* To avoid repetitive query, please store return result as static.
*/
static QStringList supportedMethodList();
};
#endif // SSVALIDATOR_H
================================================
FILE: src/statusnotifier.cpp
================================================
#include "statusnotifier.h"
#include "mainwindow.h"
#include
#ifdef Q_OS_LINUX
#include
#include
#include
#endif
StatusNotifier::StatusNotifier(MainWindow *w, bool startHiden, QObject *parent) :
QObject(parent),
window(w)
{
systray.setIcon(QIcon(":/icons/icons/shadowsocks-qt5.png"));
systray.setToolTip(QString("Shadowsocks-Qt5"));
connect(&systray, &QSystemTrayIcon::activated, [this](QSystemTrayIcon::ActivationReason r) {
if (r != QSystemTrayIcon::Context) {
this->activate();
}
});
minimiseRestoreAction = new QAction(startHiden ? tr("Restore") : tr("Minimise"), this);
connect(minimiseRestoreAction, &QAction::triggered, this, &StatusNotifier::activate);
systrayMenu.addAction(minimiseRestoreAction);
systrayMenu.addAction(QIcon::fromTheme("application-exit", QIcon::fromTheme("exit")), tr("Quit"), qApp, SLOT(quit()));
systray.setContextMenu(&systrayMenu);
systray.show();
}
void StatusNotifier::activate()
{
if (!window->isVisible() || window->isMinimized()) {
window->showNormal();
window->activateWindow();
window->raise();
} else {
window->hide();
}
}
void StatusNotifier::showNotification(const QString &msg)
{
#ifdef Q_OS_LINUX
//Using DBus to send message.
QDBusMessage method = QDBusMessage::createMethodCall("org.freedesktop.Notifications","/org/freedesktop/Notifications", "org.freedesktop.Notifications", "Notify");
QVariantList args;
args << QCoreApplication::applicationName() << quint32(0) << "shadowsocks-qt5" << "Shadowsocks-Qt5" << msg << QStringList () << QVariantMap() << qint32(-1);
method.setArguments(args);
QDBusConnection::sessionBus().asyncCall(method);
#else
systray.showMessage("Shadowsocks-Qt5", msg);
#endif
}
void StatusNotifier::onWindowVisibleChanged(bool visible)
{
minimiseRestoreAction->setText(visible ? tr("Minimise") : tr("Restore"));
}
================================================
FILE: src/statusnotifier.h
================================================
/*
* Copyright (C) 2015-2017 Symeon Huang
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* .
*/
#ifndef STATUSNOTIFIER_H
#define STATUSNOTIFIER_H
#include
#include
#include
class MainWindow;
class StatusNotifier : public QObject
{
Q_OBJECT
public:
StatusNotifier(MainWindow *w, bool startHiden, QObject *parent = 0);
public slots:
void activate();
void showNotification(const QString &);
void onWindowVisibleChanged(bool visible);
private:
QMenu systrayMenu;
QAction *minimiseRestoreAction;
QSystemTrayIcon systray;
MainWindow *window;
};
#endif // STATUSNOTIFIER_H
================================================
FILE: src/translations.qrc
================================================
i18n/ss-qt5_zh_CN.qm
i18n/ss-qt5_zh_TW.qm
================================================
FILE: src/urihelper.cpp
================================================
#include "urihelper.h"
#include
QImage URIHelper::convertToGrey(const QImage &input)
{
if (input.isNull()) {
return QImage();
}
QImage ret(input.width(), input.height(), QImage::Format_Indexed8);
QVector gtable(256);
for (int i = 0; i < 256; ++i) {
gtable[i] = qRgb(i, i, i);
}
ret.setColorTable(gtable);
for (int i = 0; i < input.width(); ++i) {
for (int j = 0; j < input.height(); ++j) {
QRgb val = input.pixel(i, j);
ret.setPixel(i, j, qGray(val));
}
}
return ret;
}
QString URIHelper::decodeImage(const QImage &img)
{
QString uri;
QImage gimg = convertToGrey(img);
//use zbar to decode the QR code
zbar::ImageScanner scanner;
zbar::Image image(gimg.bytesPerLine(), gimg.height(), "Y800", gimg.bits(), gimg.byteCount());
scanner.scan(image);
zbar::SymbolSet res_set = scanner.get_results();
for (zbar::SymbolIterator it = res_set.symbol_begin(); it != res_set.symbol_end(); ++it) {
if (it->get_type() == zbar::ZBAR_QRCODE) {
/*
* uri will be overwritten if the result is valid
* this means always the last uri gets used
* therefore, please only leave one QR code for the sake of accuracy
*/
QString result = QString::fromStdString(it->get_data());
if (result.left(5).compare("ss://", Qt::CaseInsensitive) == 0) {
uri = result;
}
}
}
return uri;
}
================================================
FILE: src/urihelper.h
================================================
/*
* Copyright (C) 2015-2016 Symeon Huang
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* .
*/
#ifndef URIHELPER_H
#define URIHELPER_H
#include
#include
class URIHelper
{
public:
virtual ~URIHelper() = 0;
static QImage convertToGrey(const QImage &input);
static QString decodeImage(const QImage &img);
};
#endif // URIHELPER_H
================================================
FILE: src/uriinputdialog.cpp
================================================
#include "uriinputdialog.h"
#include "ui_uriinputdialog.h"
#include "ssvalidator.h"
#include
URIInputDialog::URIInputDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::URIInputDialog)
{
ui->setupUi(this);
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
connect(ui->uriEdit, &QLineEdit::textChanged, this, &URIInputDialog::onURIChanged);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &URIInputDialog::onAccepted);
this->adjustSize();
}
URIInputDialog::~URIInputDialog()
{
delete ui;
}
void URIInputDialog::onURIChanged(const QString &str)
{
if (!SSValidator::validate(str)) {
ui->uriEdit->setStyleSheet("background: pink");
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
}
else {
ui->uriEdit->setStyleSheet("background: #81F279");
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
}
}
void URIInputDialog::onAccepted()
{
emit acceptedURI(ui->uriEdit->text());
this->accept();
}
================================================
FILE: src/uriinputdialog.h
================================================
/*
* Copyright (C) 2015-2016 Symeon Huang
*
* shadowsocks-qt5 is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-qt5 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libQtShadowsocks; see the file LICENSE. If not, see
* .
*/
#ifndef URIINPUTDIALOG_H
#define URIINPUTDIALOG_H
#include
namespace Ui {
class URIInputDialog;
}
class URIInputDialog : public QDialog
{
Q_OBJECT
signals:
void acceptedURI(const QString &uri);
public:
explicit URIInputDialog(QWidget *parent = 0);
~URIInputDialog();
private:
Ui::URIInputDialog *ui;
private slots:
void onAccepted();
void onURIChanged(const QString &);
};
#endif // URIINPUTDIALOG_H
================================================
FILE: src/uriinputdialog.ui
================================================
URIInputDialog
0
0
400
159
URI Input Dialog
-
Please input ss:// URI
-
300
0
-
Qt::Vertical
20
0
-
Qt::Horizontal
QDialogButtonBox::Cancel|QDialogButtonBox::Ok
buttonBox
rejected()
URIInputDialog
reject()
316
260
286
274