├── proto └── .placeholder ├── res ├── icon.rc ├── icon.ico ├── OpenDGLab-Desktop.png ├── OpenDGLab-Desktop.icns └── image.qrc ├── .gitignore ├── external └── OpenDGLab.desktop ├── src ├── bleConnectThread.h ├── deviceStateEnum.h ├── waveSenderTimerThread.h ├── autoWaveChangerThread.h ├── bleConnectThread.cpp ├── remoteControl.h ├── autoRemoteProtocolPinger.h ├── mainWindowController.h ├── deviceItem.h ├── waveSenderTimerThread.cpp ├── scanDeviceController.h ├── autoRemoteProtocolPinger.cpp ├── scanDevice.h ├── autoWaveChangerThread.cpp ├── mainWindow.h ├── mainWindowController.cpp ├── remoteClient.h ├── deviceOperator.h ├── remote.h ├── scanDevice.cpp ├── scanDevice.ui ├── global.h ├── deviceItem.cpp ├── main.cpp ├── scanDeviceController.cpp ├── deviceItem.ui ├── dglabDevice.h ├── remoteControl.cpp ├── mainWindow.ui ├── global.cpp ├── mainWindow.cpp ├── remoteControl.ui ├── deviceOperator.cpp ├── remote.cpp ├── deviceOperator.ui ├── remoteClient.cpp └── dglabDevice.cpp ├── README.md ├── CMakeLists.txt ├── .github └── workflows │ └── tags.yml └── LICENSE /proto/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/icon.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "icon.ico" -------------------------------------------------------------------------------- /res/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenDGLab/OpenDGLab-Desktop/HEAD/res/icon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | opendglab-core 3 | CMakeLists.txt.user 4 | build 5 | cmake-build-debug 6 | proto/*.proto -------------------------------------------------------------------------------- /res/OpenDGLab-Desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenDGLab/OpenDGLab-Desktop/HEAD/res/OpenDGLab-Desktop.png -------------------------------------------------------------------------------- /res/OpenDGLab-Desktop.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenDGLab/OpenDGLab-Desktop/HEAD/res/OpenDGLab-Desktop.icns -------------------------------------------------------------------------------- /res/image.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | OpenDGLab-Desktop.png 4 | 5 | 6 | -------------------------------------------------------------------------------- /external/OpenDGLab.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=OpenDGLab Desktop 4 | Exec=AppRun %F 5 | Icon=OpenDGLab 6 | Comment=Open source solution to control DG-Lab Device 7 | Categories=Utility;Application; -------------------------------------------------------------------------------- /src/bleConnectThread.h: -------------------------------------------------------------------------------- 1 | #ifndef BLECONNECTTHREAD_H 2 | #define BLECONNECTTHREAD_H 3 | 4 | #include 5 | #include 6 | 7 | class BLEConnectThread : public QThread 8 | { 9 | Q_OBJECT 10 | public: 11 | void run() override; 12 | 13 | signals: 14 | 15 | }; 16 | 17 | #endif // BLECONNECTTHREAD_H 18 | -------------------------------------------------------------------------------- /src/deviceStateEnum.h: -------------------------------------------------------------------------------- 1 | #ifndef DEVICESTATEENUM_H 2 | #define DEVICESTATEENUM_H 3 | 4 | namespace DeviceStateEnum { 5 | enum DeviceState { 6 | UNCONNECTED, CONNECTED, READY, READY_REMOTEMANAGED 7 | }; 8 | enum DeviceChannel { 9 | CHANNEL_A, CHANNEL_B 10 | }; 11 | } 12 | 13 | #endif // DEVICESTATEENUM_H 14 | -------------------------------------------------------------------------------- /src/waveSenderTimerThread.h: -------------------------------------------------------------------------------- 1 | #ifndef WAVESENDERTIMERTHREAD_H 2 | #define WAVESENDERTIMERTHREAD_H 3 | 4 | #include 5 | #include 6 | #include 7 | class WaveSenderTimerThread : public QThread 8 | { 9 | Q_OBJECT 10 | public: 11 | explicit WaveSenderTimerThread(QObject *parent = nullptr); 12 | ~WaveSenderTimerThread() override; 13 | void run() override; 14 | private: 15 | QTimer* timer{}; 16 | }; 17 | 18 | #endif // WAVESENDERTIMERTHREAD_H 19 | -------------------------------------------------------------------------------- /src/autoWaveChangerThread.h: -------------------------------------------------------------------------------- 1 | #ifndef AUTOWAVECHANGERTHREAD_H 2 | #define AUTOWAVECHANGERTHREAD_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class AutoWaveChangerThread : public QThread 9 | { 10 | Q_OBJECT 11 | public: 12 | explicit AutoWaveChangerThread(QObject *parent = nullptr); 13 | ~AutoWaveChangerThread() override; 14 | void run() override; 15 | private: 16 | QTimer* timer{}; 17 | }; 18 | 19 | #endif // AUTOWAVECHANGERTHREAD_H 20 | -------------------------------------------------------------------------------- /src/bleConnectThread.cpp: -------------------------------------------------------------------------------- 1 | #include "bleConnectThread.h" 2 | #include "global.h" 3 | 4 | void BLEConnectThread::run() { 5 | while (!this->isInterruptionRequested()) { 6 | for (auto device: Global::dglabList) { 7 | if (device->getBLEController()->state() == QLowEnergyController::ControllerState::UnconnectedState) { 8 | SKIPNEXTQWARNING(SKIPNEXTERROR(device->getBLEController()->connectToDevice())); 9 | } 10 | } 11 | sleep(3); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/remoteControl.h: -------------------------------------------------------------------------------- 1 | #ifndef REMOTECONTROL_H 2 | #define REMOTECONTROL_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class RemoteControl; 8 | } 9 | 10 | class RemoteControl : public QWidget 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit RemoteControl(QWidget *parent = nullptr); 16 | ~RemoteControl() override; 17 | 18 | private slots: 19 | void updateList(); 20 | 21 | private: 22 | Ui::RemoteControl *ui; 23 | void changeState(bool start); 24 | }; 25 | 26 | #endif // REMOTECONTROL_H 27 | -------------------------------------------------------------------------------- /src/autoRemoteProtocolPinger.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by max_3 on 2021/1/11. 3 | // 4 | 5 | #ifndef OPENDGLAB_DESKTOP_AUTOREMOTEPROTOCOLPINGER_H 6 | #define OPENDGLAB_DESKTOP_AUTOREMOTEPROTOCOLPINGER_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | class AutoRemoteProtocolPinger : public QThread 13 | { 14 | Q_OBJECT 15 | public: 16 | explicit AutoRemoteProtocolPinger(QObject *parent = nullptr); 17 | ~AutoRemoteProtocolPinger() override; 18 | void run() override; 19 | private: 20 | QTimer* timer{}; 21 | 22 | }; 23 | 24 | 25 | #endif //OPENDGLAB_DESKTOP_AUTOREMOTEPROTOCOLPINGER_H 26 | -------------------------------------------------------------------------------- /src/mainWindowController.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOWCONTROLLER_H 2 | #define MAINWINDOWCONTROLLER_H 3 | #include 4 | #include "bleConnectThread.h" 5 | #include "autoWaveChangerThread.h" 6 | 7 | class MainWindowController : public QObject 8 | { 9 | Q_OBJECT 10 | public: 11 | explicit MainWindowController(QObject *parent = nullptr); 12 | ~MainWindowController() override; 13 | void startAllBoost(int); 14 | void stopAllBoost(); 15 | void shutdown(); 16 | private: 17 | BLEConnectThread connectThread; 18 | AutoWaveChangerThread autoWaveChangerThread = AutoWaveChangerThread(this); 19 | signals: 20 | 21 | }; 22 | 23 | #endif // MAINWINDOWCONTROLLER_H 24 | -------------------------------------------------------------------------------- /src/deviceItem.h: -------------------------------------------------------------------------------- 1 | #ifndef DEVICEITEM_H 2 | #define DEVICEITEM_H 3 | 4 | #include 5 | #include "deviceStateEnum.h" 6 | 7 | namespace Ui { 8 | class DeviceItem; 9 | } 10 | 11 | class DeviceItem : public QWidget 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit DeviceItem(QString id, QString address, QString name, QWidget *parent = nullptr); 17 | ~DeviceItem() override; 18 | void changeState(DeviceStateEnum::DeviceState state); 19 | void changeBattery(int level); 20 | DeviceStateEnum::DeviceState getState(); 21 | 22 | private: 23 | Ui::DeviceItem *ui; 24 | DeviceStateEnum::DeviceState deviceState = DeviceStateEnum::DeviceState::UNCONNECTED; 25 | }; 26 | 27 | #endif // DEVICEITEM_H 28 | -------------------------------------------------------------------------------- /src/waveSenderTimerThread.cpp: -------------------------------------------------------------------------------- 1 | #include "waveSenderTimerThread.h" 2 | #include "dglabDevice.h" 3 | WaveSenderTimerThread::WaveSenderTimerThread(QObject *parent): 4 | QThread(parent) 5 | { 6 | connect(this, &QThread::finished, this, &QThread::deleteLater); 7 | } 8 | 9 | void WaveSenderTimerThread::run() 10 | { 11 | timer = new QTimer(); 12 | timer->setInterval(200); 13 | connect(timer, &QTimer::timeout, parent(), [this](){ 14 | reinterpret_cast(parent())->waveSender(); 15 | }); 16 | connect(this, &QThread::finished, timer, &QTimer::stop); 17 | timer->start(); 18 | this->exec(); 19 | } 20 | 21 | WaveSenderTimerThread::~WaveSenderTimerThread() { 22 | delete timer; 23 | } 24 | -------------------------------------------------------------------------------- /src/scanDeviceController.h: -------------------------------------------------------------------------------- 1 | #ifndef SCANDEVICECONTROLLER_H 2 | #define SCANDEVICECONTROLLER_H 3 | 4 | #include 5 | #include 6 | 7 | class ScanDevice; 8 | class ScanDeviceController : public QObject 9 | { 10 | Q_OBJECT 11 | public: 12 | explicit ScanDeviceController(QObject *parent = nullptr); 13 | ~ScanDeviceController() override; 14 | void startScan(); 15 | void stopScan(); 16 | 17 | private slots: 18 | void deviceScanFound(const QBluetoothDeviceInfo&); 19 | void deviceScanError(); 20 | void deviceScanFinished(); 21 | 22 | signals: 23 | void eventScanError(); 24 | void eventScanFinished(); 25 | void eventScanFound(); 26 | }; 27 | 28 | #endif // SCANDEVICECONTROLLER_H 29 | -------------------------------------------------------------------------------- /src/autoRemoteProtocolPinger.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by max_3 on 2021/1/11. 3 | // 4 | 5 | #include "autoRemoteProtocolPinger.h" 6 | #include 7 | #include "global.h" 8 | 9 | #include 10 | AutoRemoteProtocolPinger::AutoRemoteProtocolPinger(QObject* parent): QThread(parent) 11 | { 12 | timer = new QTimer(); 13 | timer->setInterval(1000); 14 | connect(timer, &QTimer::timeout, this, [](){ 15 | for(auto r: Global::remoteList) { 16 | r->sendPing(); 17 | } 18 | }); 19 | connect(this, &QThread::finished, timer, &QTimer::stop); 20 | timer->start(); 21 | } 22 | 23 | AutoRemoteProtocolPinger::~AutoRemoteProtocolPinger() 24 | { 25 | delete timer; 26 | } 27 | 28 | void AutoRemoteProtocolPinger::run() 29 | { 30 | this->exec(); 31 | } 32 | -------------------------------------------------------------------------------- /src/scanDevice.h: -------------------------------------------------------------------------------- 1 | #ifndef SCANDEVICE_H 2 | #define SCANDEVICE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "scanDeviceController.h" 8 | 9 | namespace Ui { 10 | class ScanDevice; 11 | } 12 | 13 | class ScanDevice : public QMainWindow 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | explicit ScanDevice(QWidget *parent = nullptr); 19 | ~ScanDevice() override; 20 | 21 | private: 22 | Ui::ScanDevice *ui; 23 | ScanDeviceController* mvcController; 24 | 25 | public slots: 26 | void setCancelled(); 27 | void setError(); 28 | void setFinished(); 29 | void setFound(); 30 | 31 | protected: 32 | void showEvent(QShowEvent*) override; 33 | void closeEvent(QCloseEvent*) override; 34 | 35 | signals: 36 | void needRefresh(); 37 | 38 | }; 39 | 40 | #endif // SCANDEVICE_H 41 | -------------------------------------------------------------------------------- /src/autoWaveChangerThread.cpp: -------------------------------------------------------------------------------- 1 | #include "autoWaveChangerThread.h" 2 | #include 3 | #include "global.h" 4 | AutoWaveChangerThread::AutoWaveChangerThread(QObject* parent): QThread(parent) 5 | { 6 | timer = new QTimer(); 7 | timer->setInterval(1000); 8 | connect(timer, &QTimer::timeout, this, [](){ 9 | for (auto device: Global::dglabList) { 10 | if (device->getState() == DeviceStateEnum::DeviceState::READY || device->getState() == DeviceStateEnum::DeviceState::READY_REMOTEMANAGED) { 11 | device->getUiDeviceOperator()->checkIfNeedChangeWave(); 12 | } 13 | } 14 | }); 15 | connect(this, &QThread::finished, timer, &QTimer::stop); 16 | timer->start(); 17 | } 18 | 19 | AutoWaveChangerThread::~AutoWaveChangerThread() 20 | { 21 | delete timer; 22 | } 23 | 24 | void AutoWaveChangerThread::run() 25 | { 26 | this->exec(); 27 | } 28 | -------------------------------------------------------------------------------- /src/mainWindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include "mainWindowController.h" 6 | 7 | QT_BEGIN_NAMESPACE 8 | namespace Ui { class MainWindow; } 9 | QT_END_NAMESPACE 10 | 11 | class MainWindow : public QMainWindow 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit MainWindow(QWidget *parent = nullptr); 17 | ~MainWindow() override; 18 | void saveDevice(); 19 | void loadDevice(); 20 | 21 | protected: 22 | void showEvent(QShowEvent*) override; 23 | void closeEvent(QCloseEvent*) override; 24 | 25 | private slots: 26 | void addDevice(); 27 | void refreshDeviceList(); 28 | void showContextMenu(const QPoint &); 29 | void removeDeviceItem(); 30 | void allBoostChanged(int); 31 | void startAllBoost(int); 32 | void stopAllBoost(); 33 | 34 | private: 35 | Ui::MainWindow *ui; 36 | MainWindowController *mvcController; 37 | }; 38 | #endif // MAINWINDOW_H 39 | -------------------------------------------------------------------------------- /src/mainWindowController.cpp: -------------------------------------------------------------------------------- 1 | #include "mainWindowController.h" 2 | #include "global.h" 3 | MainWindowController::MainWindowController(QObject *parent) : QObject(parent) 4 | { 5 | connectThread.start(); 6 | autoWaveChangerThread.start(); 7 | } 8 | 9 | MainWindowController::~MainWindowController() { 10 | autoWaveChangerThread.quit(); 11 | connectThread.requestInterruption(); 12 | connectThread.wait(); 13 | } 14 | 15 | void MainWindowController::startAllBoost(int boost) 16 | { 17 | for (auto device: Global::dglabList) { 18 | if (device->getState() == DeviceStateEnum::DeviceState::READY || device->getState() == DeviceStateEnum::DeviceState::READY_REMOTEMANAGED) 19 | device->startGlobalBoost(boost); 20 | } 21 | } 22 | 23 | void MainWindowController::stopAllBoost() 24 | { 25 | for (auto device: Global::dglabList) { 26 | if (device->getState() == DeviceStateEnum::DeviceState::READY || device->getState() == DeviceStateEnum::DeviceState::READY_REMOTEMANAGED) 27 | device->stopGlobalBoost(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/remoteClient.h: -------------------------------------------------------------------------------- 1 | #ifndef REMOTECLIENT_H 2 | #define REMOTECLIENT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #ifndef __linux__ 8 | #include 9 | #endif 10 | 11 | class RemoteClient : public QObject 12 | { 13 | Q_OBJECT 14 | public: 15 | explicit RemoteClient(QTcpSocket *tcpSocket, QObject *parent = nullptr); 16 | #ifndef __linux__ 17 | explicit RemoteClient(QWebSocket *webSocket, QObject *parent = nullptr); 18 | #endif 19 | ~RemoteClient() override ; 20 | void sendConnected(const QString& _uuid, const QString& token, bool isAuth); 21 | void sendDeviceReset(const QString& deviceId); 22 | void sendPowerUpdate(const QString &deviceId, int channel_a, int channel_b); 23 | void sendPing(); 24 | void close(); 25 | QString getUuid(); 26 | 27 | private: 28 | bool isWS = false; 29 | QTcpSocket *tcpSocket = nullptr; 30 | #ifndef __linux__ 31 | QWebSocket *wsSocket = nullptr; 32 | #endif 33 | void readyRead(QByteArray data); 34 | bool isAuthed = false; 35 | QString uuid; 36 | QList locked; 37 | 38 | signals: 39 | void doAuth(RemoteClient *client, QString appName, QString appId, QString token); 40 | }; 41 | 42 | #endif // REMOTECLIENT_H 43 | -------------------------------------------------------------------------------- /src/deviceOperator.h: -------------------------------------------------------------------------------- 1 | #ifndef DEVICEOPERATOR_H 2 | #define DEVICEOPERATOR_H 3 | 4 | #include "deviceStateEnum.h" 5 | #include 6 | #include 7 | namespace Ui { 8 | class DeviceOperator; 9 | } 10 | 11 | class DeviceOperator : public QWidget 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit DeviceOperator(QString id, QWidget *parent = nullptr); 17 | ~DeviceOperator() override; 18 | QString getWaveA(); 19 | QString getWaveB(); 20 | void setWaveA(QString); 21 | void setWaveB(QString); 22 | void setAutoChange(DeviceStateEnum::DeviceChannel, bool); 23 | bool getAutoChange(DeviceStateEnum::DeviceChannel); 24 | void checkIfNeedChangeWave(); 25 | 26 | public slots: 27 | void setPower(DeviceStateEnum::DeviceChannel channel, int level); 28 | 29 | private slots: 30 | 31 | 32 | private: 33 | Ui::DeviceOperator *ui; 34 | int autoATimer = 0; 35 | int autoBTimer = 0; 36 | QRandomGenerator random; 37 | 38 | signals: 39 | void startBoost(DeviceStateEnum::DeviceChannel, int); 40 | void stopBoost(DeviceStateEnum::DeviceChannel, int); 41 | void changePower(int, int); 42 | void changeWave(DeviceStateEnum::DeviceChannel, QString); 43 | }; 44 | 45 | #endif // DEVICEOPERATOR_H 46 | -------------------------------------------------------------------------------- /src/remote.h: -------------------------------------------------------------------------------- 1 | #ifndef REMOTE_H 2 | #define REMOTE_H 3 | 4 | #include 5 | #include 6 | #ifndef __linux__ 7 | #include 8 | #endif 9 | #include 10 | #include "remoteClient.h" 11 | #include "autoRemoteProtocolPinger.h" 12 | class Remote : public QObject 13 | { 14 | Q_OBJECT 15 | public: 16 | explicit Remote(QObject *parent = nullptr); 17 | ~Remote() override; 18 | bool isStarted(); 19 | bool start(); 20 | void stop(); 21 | void setWSPort(int port); 22 | void setTcpPort(int port); 23 | void load(); 24 | void save(); 25 | QMap preAuth; //UUID, Token 26 | QMap preAuthName; //UUID, Token 27 | 28 | private: 29 | int tcpPort; 30 | int wsPort; 31 | QRandomGenerator qrng; 32 | QList clientList; 33 | QTcpServer *tcpServer; 34 | #ifndef __linux__ 35 | QWebSocketServer *wsServer; 36 | #endif 37 | bool isStart = false; 38 | QString getRandomString(); 39 | AutoRemoteProtocolPinger* autoRemoteProtocolPinger = nullptr; 40 | 41 | private slots: 42 | void tcpNewConnection(); 43 | void wsNewConnection(); 44 | void doAuth(RemoteClient* client, QString appName, QString appId, QString token); 45 | 46 | signals: 47 | void stateChange(); 48 | 49 | }; 50 | 51 | #endif // REMOTE_H 52 | -------------------------------------------------------------------------------- /src/scanDevice.cpp: -------------------------------------------------------------------------------- 1 | #include "scanDevice.h" 2 | #include "ui_scanDevice.h" 3 | #include 4 | 5 | ScanDevice::ScanDevice(QWidget *parent) : 6 | QMainWindow(parent), 7 | ui(new Ui::ScanDevice), 8 | mvcController(new ScanDeviceController(this)) 9 | { 10 | ui->setupUi(this); 11 | this->setAttribute(Qt::WidgetAttribute::WA_DeleteOnClose); 12 | this->setFixedSize(QSize(300, 100)); 13 | connect(mvcController, &ScanDeviceController::eventScanError, this, &ScanDevice::setError); 14 | connect(mvcController, &ScanDeviceController::eventScanFinished, this, &ScanDevice::setFinished); 15 | connect(mvcController, &ScanDeviceController::eventScanFound, this, &ScanDevice::setFound); 16 | connect(ui->btnAddCancel, &QPushButton::clicked, this, &ScanDevice::setCancelled); 17 | } 18 | 19 | ScanDevice::~ScanDevice() 20 | { 21 | delete ui; 22 | } 23 | void ScanDevice::showEvent(QShowEvent*) { 24 | mvcController->startScan(); 25 | } 26 | void ScanDevice::closeEvent(QCloseEvent*) { 27 | 28 | } 29 | void ScanDevice::setError() { 30 | ui->lbl_status->setText("错误:无法扫描"); 31 | } 32 | void ScanDevice::setFinished() { 33 | ui->lbl_status->setText("扫描完成 未发现设备"); 34 | } 35 | void ScanDevice::setFound() { 36 | emit needRefresh(); 37 | this->close(); 38 | } 39 | void ScanDevice::setCancelled() { 40 | this->close(); 41 | } 42 | -------------------------------------------------------------------------------- /src/scanDevice.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ScanDevice 4 | 5 | 6 | Qt::ApplicationModal 7 | 8 | 9 | 10 | 0 11 | 0 12 | 300 13 | 100 14 | 15 | 16 | 17 | 18 | 0 19 | 0 20 | 21 | 22 | 23 | 添加设备 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 正在发现设备... 34 | 35 | 36 | Qt::AlignCenter 37 | 38 | 39 | 40 | 41 | 42 | 43 | 取消 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/global.h: -------------------------------------------------------------------------------- 1 | #ifndef GLOBAL_H 2 | #define GLOBAL_H 3 | #define SKIPNEXTERROR(N) \ 4 | ( \ 5 | (Global::skipError = true),\ 6 | (N), \ 7 | (Global::skipError = false),\ 8 | (void)0 \ 9 | ) 10 | #define SKIPNEXTQWARNING(N) \ 11 | ( \ 12 | (Global::skipWarning = true),\ 13 | (N), \ 14 | (Global::skipWarning = false),\ 15 | (void)0 \ 16 | ) 17 | #include 18 | #include 19 | #include 20 | #include "dglabDevice.h" 21 | #include "remote.h" 22 | 23 | namespace Global { 24 | extern QList basicWaveNameList; 25 | extern QList touchWaveNameList; 26 | extern QString deviceName; 27 | namespace DGLabServices { 28 | namespace DeviceStatus { 29 | extern QString service; 30 | namespace Characteristic { 31 | extern QString electric; 32 | } 33 | } 34 | namespace EStimStatus { 35 | extern QString service; 36 | namespace Characteristic { 37 | extern QString setup; 38 | extern QString abpower; 39 | extern QString waveA; 40 | extern QString waveB; 41 | } 42 | } 43 | } 44 | extern bool skipError; 45 | extern bool skipWarning; 46 | extern Remote* remote; 47 | extern QList remoteList; 48 | extern QList dglabList; 49 | extern QBluetoothDeviceDiscoveryAgent *bleScanAgent; 50 | extern QMutex mutex; 51 | //extern Remote *remote; 52 | void initGlobal(); 53 | QList getWaveList(); 54 | } 55 | 56 | #endif // GLOBAL_H 57 | -------------------------------------------------------------------------------- /src/deviceItem.cpp: -------------------------------------------------------------------------------- 1 | #include "deviceItem.h" 2 | #include "ui_deviceItem.h" 3 | 4 | DeviceItem::DeviceItem(QString id, QString address, QString name, QWidget *parent) : 5 | QWidget(parent), 6 | ui(new Ui::DeviceItem) 7 | { 8 | ui->setupUi(this); 9 | ui->lbl_ID->setText(id); 10 | ui->lbl_Mac->setText(address); 11 | ui->lbl_Name->setText(name); 12 | ui->lbl_Status->setText("未连接"); 13 | } 14 | 15 | DeviceItem::~DeviceItem() 16 | { 17 | delete ui; 18 | } 19 | 20 | void DeviceItem::changeBattery(int level) 21 | { 22 | if (level < 0) { 23 | ui->pb_battery->setFormat(" 正在配置.."); 24 | ui->pb_battery->setMaximum(0); 25 | ui->pb_battery->setMinimum(0); 26 | ui->pb_battery->setValue(0); 27 | } else { 28 | ui->pb_battery->setFormat(" 电池:%p%"); 29 | ui->pb_battery->setMaximum(100); 30 | ui->pb_battery->setMinimum(0); 31 | ui->pb_battery->setValue(level); 32 | } 33 | } 34 | 35 | DeviceStateEnum::DeviceState DeviceItem::getState() 36 | { 37 | return deviceState; 38 | } 39 | 40 | void DeviceItem::changeState(DeviceStateEnum::DeviceState state) 41 | { 42 | switch (state) { 43 | case DeviceStateEnum::DeviceState::CONNECTED: 44 | ui->lbl_Status->setText("已连接 - 正在配置"); 45 | break; 46 | case DeviceStateEnum::DeviceState::READY: 47 | ui->lbl_Status->setText("已连接 - 已就绪"); 48 | break; 49 | case DeviceStateEnum::DeviceState::UNCONNECTED: 50 | ui->lbl_Status->setText("未连接"); 51 | break; 52 | case DeviceStateEnum::DeviceState::READY_REMOTEMANAGED: 53 | ui->lbl_Status->setText("已连接 - 已就绪(正在被远程管理)"); 54 | break; 55 | } 56 | deviceState = state; 57 | } 58 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainWindow.h" 2 | #include "global.h" 3 | #include 4 | void crashingMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) 5 | { 6 | QByteArray localMsg(msg.toLocal8Bit()); 7 | switch (type) { 8 | case QtDebugMsg: 9 | fprintf(stderr, "Debug: %s\n", localMsg.constData()); 10 | break; 11 | case QtInfoMsg: 12 | fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); 13 | break; 14 | case QtWarningMsg: 15 | fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); 16 | if(!Global::skipWarning) fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); 17 | if(!Global::skipError) abort(); //Index out of range is just a Warning! 18 | break; 19 | case QtCriticalMsg: 20 | fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); 21 | if(!Global::skipError) abort(); 22 | break; 23 | case QtFatalMsg: 24 | fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); 25 | if(!Global::skipError) abort(); 26 | break; 27 | } 28 | } 29 | 30 | 31 | int main(int argc, char *argv[]) 32 | { 33 | QApplication a(argc, argv); 34 | qInstallMessageHandler(crashingMessageHandler); 35 | Global::initGlobal(); 36 | Global::remote->load(); 37 | MainWindow w; 38 | w.show(); 39 | return a.exec(); 40 | } 41 | -------------------------------------------------------------------------------- /src/scanDeviceController.cpp: -------------------------------------------------------------------------------- 1 | #include "scanDeviceController.h" 2 | #include "global.h" 3 | #include 4 | #include 5 | ScanDeviceController::ScanDeviceController(QObject *parent) : QObject(parent) 6 | { 7 | connect(Global::bleScanAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &ScanDeviceController::deviceScanFound); 8 | connect(Global::bleScanAgent, QOverload::of(&QBluetoothDeviceDiscoveryAgent::error), this, &ScanDeviceController::deviceScanError); 9 | connect(Global::bleScanAgent, &QBluetoothDeviceDiscoveryAgent::finished, this, &ScanDeviceController::deviceScanFinished); 10 | } 11 | 12 | ScanDeviceController::~ScanDeviceController() { 13 | stopScan(); 14 | } 15 | void ScanDeviceController::startScan() { 16 | Global::bleScanAgent->start(); 17 | } 18 | 19 | void ScanDeviceController::stopScan() { 20 | Global::bleScanAgent->stop(); 21 | } 22 | 23 | void ScanDeviceController::deviceScanFound(const QBluetoothDeviceInfo& info) { 24 | QMutexLocker locker(&Global::mutex); 25 | bool isSaved = false; 26 | for (auto savedDevice: (Global::dglabList)) { 27 | if(savedDevice->getDeviceAddressOrUuid() == 28 | #ifdef Q_OS_MACOS 29 | info.deviceUuid() 30 | #else 31 | info.address() 32 | #endif 33 | .toString() 34 | ) { 35 | isSaved = true; 36 | } 37 | } 38 | if (info.name() == Global::deviceName && !isSaved) { 39 | stopScan(); 40 | DGLabDevice *device = new DGLabDevice(info); 41 | Global::dglabList.append(device); 42 | emit eventScanFound(); 43 | } 44 | } 45 | 46 | void ScanDeviceController::deviceScanError() { 47 | emit eventScanError(); 48 | } 49 | 50 | void ScanDeviceController::deviceScanFinished() { 51 | emit eventScanFinished(); 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenDGLab Desktop 2 | OpenDGLab Desktop 是一个使用 OpenDGLab Core 和 Qt5 编写的桌面客户端。 3 | OpenDGLab Desktop 的目标是在 Windows、Linux、MacOS 上提供对多 DG-Lab 设备的统一化支持。 4 | 使用 OpenDGLab Desktop 您可以在支持蓝牙 BLE 的电脑上同时管理多个 DG-Lab 设备。给您更加优质的使用体验。 5 | 支持 OpenDGLab OpenProtocol 协议,允许第三方程序接入。提供更多可能。 6 | 7 | ## 编译 8 | ### 获取 OpenDGLab 必要文件 9 | 下载并编译 OpenDGLab Core 获取您对应架构的 Core 文件。 10 | 11 | 从 OpenDGLab OpenProtocol 获取 app.proto 文件放入 proto 文件夹中。 12 | 13 | 依赖 libprotobuf 请事先安装。 14 | 15 | ### 编译 OpenDGLab Desktop (Windows) 16 | Windows 编译请使用 `lib /def:libopendglab.def /out:libopendglab.lib /machine:x64` 或 `lib /def:libopendglab.def /out:libopendglab.lib /machine:x86` 获取对应的 .lib 文件。 17 | 将 lib dll 文件放入 opendglab-core/bin 文件夹,将 h 文件放入 opendglab-core/header 文件夹。 18 | 19 | ```shell 20 | mkdir build 21 | cd build 22 | cmake -G "Ninja" -DCMAKE_BUILD_TYPE=MinSizeRel .. 23 | cmake --build . --target all 24 | ``` 25 | 26 | 在 Build 文件夹下可以找到编译好的对应版本的 zip 压缩文件。 27 | 28 | ### 编译 OpenDGLab Desktop (Linux) 29 | 将 OpenDGLab-Core 编译出的 a 文件放入 opendglab-core/bin 文件夹,将 h 文件放入 opendglab-core/header 文件夹。 30 | 如果需要打包未 AppImage 请安装 linuxdeployqt 31 | ```shell 32 | mkdir build 33 | cd build 34 | cmake -DCMAKE_BUILD_TYPE=MinSizeRel .. 35 | cmake --build . --target all 36 | ``` 37 | 编译后会生成 OpenDGLab-Desktop 可执行文件,您需要使用 `sudo setcap CAP_NET_ADMIN+ep OpenDGLab-Desktop` 和 `chmod +x OpenDGLab-Desktop` 来对其添加权限。 38 | 39 | 如果需要生成 AppImage 则运行 40 | ```shell 41 | mkdir appImage 42 | cp ../external/OpenDGLab.desktop appImage/ 43 | cp ../res/OpenDGLab-Desktop.png appImage/OpenDGLab.png 44 | cp OpenDGLab-Desktop appImage/ 45 | cd appImage 46 | ../linuxdeployqt OpenDGLab-Desktop -appimage -unsupported-bundle-everything 47 | ``` 48 | 即可完成打包操作,请注意 AppImage 不能设置 Cap,如需运行请使用 `gksu` 或 `kdesu` 来运行。 49 | 50 | ### 编译 OpenDGLab Desktop (MacOS) 51 | 将 OpenDGLab-Core 编译出的 a 文件放入 opendglab-core/bin 文件夹,将 h 文件放入 opendglab-core/header 文件夹。 52 | ```shell 53 | mkdir build 54 | cd build 55 | cmake -DCMAKE_BUILD_TYPE=MinSizeRel .. 56 | cmake --build . --target all 57 | macdeployqt OpenDGLab-Desktop.app -dmg 58 | ``` 59 | 即可生成 OpenDGLab-Desktop.dmg 文件。 60 | 61 | ## 注意 62 | 经过一些测试开发组发现如下问题。 63 | * CSR 高通芯片方案的蓝牙模块似乎不支持 BLE,您可能遇到无法搜索到 DG-Lab 设备的问题。 64 | * ASUS PCE-AC58BT 因为未知原因工作不良,目前无法确定是硬件问题还是网卡固件问题。您可能会遇到长时间无法搜索到 DG-Lab 设备的问题,或者搜索到后需要很长时间才能连接和完成配置。甚至遭遇 OpenDGLab Desktop 应用程序崩溃(这是由于您的网卡蓝牙没有正常返回探索服务指令导致的)。 65 | 66 | ## 声明 67 | 使用本程序请遵循 DG-Lab 官方安全声明使用。使用 OpenDGLab 项目,视同于您自己承担相关非官方实现风险。OpenDGLab 开源项目组不为您使用 DG-Lab 设备出现的任何问题负责。 68 | 69 | ## 协议 70 | 使用 AGPLv3 协议授权。 -------------------------------------------------------------------------------- /src/deviceItem.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | DeviceItem 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 96 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 0 22 | 0 23 | 24 | 25 | 26 | 蓝牙设备 27 | 28 | 29 | 30 | 31 | 32 | font-weight: bold; 33 | 34 | 35 | TextLabel 36 | 37 | 38 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | TextLabel 48 | 49 | 50 | 51 | 52 | 53 | 54 | Qt::LeftToRight 55 | 56 | 57 | TextLabel 58 | 59 | 60 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 状态 72 | 73 | 74 | 75 | 76 | 77 | 78 | TextLabel 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 0 88 | 89 | 90 | true 91 | 92 | 93 | false 94 | 95 | 96 | 电池:%p% 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/dglabDevice.h: -------------------------------------------------------------------------------- 1 | #ifndef DGLABDEVICE_H 2 | #define DGLABDEVICE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "deviceItem.h" 14 | #include "deviceOperator.h" 15 | #include "waveSenderTimerThread.h" 16 | #include 17 | class DGLabDevice : public QObject 18 | { 19 | Q_OBJECT 20 | public: 21 | explicit DGLabDevice(const QBluetoothDeviceInfo &_deviceInfo, QObject *parent = nullptr); 22 | ~DGLabDevice() override; 23 | QString getDeviceAddressOrUuid(); 24 | QString getID(); 25 | bool isShownOnUi(); 26 | void setShownOnUi(); 27 | DeviceItem* getUiDeviceItem(); 28 | DeviceOperator* getUiDeviceOperator(); 29 | QLowEnergyController* getBLEController(); 30 | void startGlobalBoost(int); 31 | void stopGlobalBoost(); 32 | DeviceStateEnum::DeviceState getState(); 33 | 34 | void clearCustomWaveAPI(DeviceStateEnum::DeviceChannel); 35 | void addCustomWaveAPI(DeviceStateEnum::DeviceChannel, QByteArray); 36 | QString getWaveAPI(DeviceStateEnum::DeviceChannel); 37 | void setWaveAPI(DeviceStateEnum::DeviceChannel, QString); 38 | int getStrengthAPI(DeviceStateEnum::DeviceChannel); 39 | void setStrengthAPI(int, int); 40 | void setDeviceRemoteLocked(bool); 41 | bool getDeviceRemoteLocked(); 42 | 43 | private: 44 | QBluetoothDeviceInfo deviceInfo; 45 | QString id; 46 | QString addressOrUuid; 47 | QLowEnergyController* controller; 48 | QLowEnergyService* batteryService; 49 | QLowEnergyService* eStimService; 50 | libopendglab_kref_OpenDGLab openDgLab; 51 | DeviceItem* uiDeviceItem; 52 | DeviceOperator* uiDeviceOperator; 53 | QRecursiveMutex customWaveAMutex; 54 | QRecursiveMutex customWaveBMutex; 55 | QList customWaveA; 56 | QList customWaveB; 57 | bool shownOnUi = false; 58 | WaveSenderTimerThread* waveSenderTimerThread; 59 | int powerA = 0; 60 | int powerB = 0; 61 | int boostA = 0; 62 | int boostB = 0; 63 | int globalBoost = 0; 64 | bool remoteLocked = false; 65 | 66 | public slots: 67 | void waveSender(); 68 | void setWave(DeviceStateEnum::DeviceChannel, QString); 69 | void startChannelBoost(DeviceStateEnum::DeviceChannel, int boost); 70 | void stopChannelBoost(DeviceStateEnum::DeviceChannel, int boost); 71 | 72 | private slots: 73 | void changePower(int, int); 74 | void deviceConnected(); 75 | void deviceDisconnected(); 76 | void deviceServiceDiscoverFinished(); 77 | void deviceBatteryServiceStateChanged(QLowEnergyService::ServiceState); 78 | void deviceEStimServiceStateChanged(QLowEnergyService::ServiceState); 79 | void deviceBatteryCharacteristicArrived(const QLowEnergyCharacteristic&, const QByteArray &); 80 | void deviceEStimCharacteristicArrived(const QLowEnergyCharacteristic&, const QByteArray &); 81 | 82 | signals: 83 | void powerUpdate(DeviceStateEnum::DeviceChannel, int); 84 | }; 85 | 86 | #endif // DGLABDEVICE_H 87 | -------------------------------------------------------------------------------- /src/remoteControl.cpp: -------------------------------------------------------------------------------- 1 | #include "remoteControl.h" 2 | #include "ui_remoteControl.h" 3 | #include "global.h" 4 | #include 5 | 6 | RemoteControl::RemoteControl(QWidget *parent) : 7 | QWidget(parent), 8 | ui(new Ui::RemoteControl) 9 | { 10 | ui->setupUi(this); 11 | #ifdef __linux__ 12 | ui->sb_ws_port->setVisible(false); 13 | #endif 14 | ui->btn_remote_remove->setEnabled(false); 15 | changeState(false); 16 | connect(ui->chk_remote, &QCheckBox::stateChanged, this,[this](int state){ 17 | changeState(state); 18 | }); 19 | connect(ui->sb_tcp_port, qOverload(&QSpinBox::valueChanged), this, [this](int value) { 20 | Global::remote->setTcpPort(value); 21 | }); 22 | connect(ui->sb_ws_port, qOverload(&QSpinBox::valueChanged), this, [this](int value) { 23 | Global::remote->setWSPort(value); 24 | }); 25 | connect(ui->listWidget, &QListWidget::currentTextChanged, this, [this](const QString ¤tText){ 26 | if (currentText.isEmpty()) { 27 | ui->lbl_title->setText(""); 28 | ui->lbl_remote_id->setText(""); 29 | ui->btn_remote_remove->setEnabled(false); 30 | } else { 31 | QString uuid; 32 | uuid = currentText.split("(")[1]; 33 | uuid = uuid.split(")")[0]; 34 | auto name = Global::remote->preAuthName[uuid]; 35 | ui->lbl_title->setText(name); 36 | ui->lbl_remote_id->setText(uuid); 37 | ui->btn_remote_remove->setEnabled(true); 38 | } 39 | }); 40 | connect(ui->btn_remote_remove, &QPushButton::clicked, this, [this](){ 41 | RemoteClient* client = nullptr; 42 | Global::remote->preAuth.remove(ui->lbl_remote_id->text()); 43 | Global::remote->preAuthName.remove(ui->lbl_remote_id->text()); 44 | for(auto r: Global::remoteList) { 45 | if (r->getUuid() == ui->lbl_remote_id->text()) { 46 | client = r; 47 | break; 48 | } 49 | } 50 | if (client != nullptr){ 51 | client->close(); 52 | Global::remote->save(); 53 | } 54 | updateList(); 55 | }); 56 | connect(Global::remote, &Remote::stateChange, this, &RemoteControl::updateList); 57 | updateList(); 58 | } 59 | 60 | void RemoteControl::changeState(bool start) { 61 | ui->sb_tcp_port->setEnabled(!start); 62 | ui->sb_ws_port->setEnabled(!start); 63 | ui->listWidget->setEnabled(start); 64 | ui->btn_remote_remove->setEnabled(start); 65 | if (start) { 66 | if(!Global::remote->start()) { 67 | QMessageBox::warning(this, "警告", "无法启动服务器。"); 68 | changeState(false); 69 | } 70 | } else { 71 | Global::remote->stop(); 72 | ui->listWidget->clearSelection(); 73 | ui->btn_remote_remove->setEnabled(false); 74 | } 75 | } 76 | 77 | RemoteControl::~RemoteControl() 78 | { 79 | delete ui; 80 | } 81 | 82 | void RemoteControl::updateList() { 83 | ui->listWidget->clearSelection(); 84 | ui->listWidget->clear(); 85 | for (const auto& pre: Global::remote->preAuthName.keys()){ 86 | auto k = Global::remote->preAuthName[pre]; 87 | ui->listWidget->addItem(QString("%1 (%2)").arg(k, pre)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/mainWindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 600 11 | 12 | 13 | 14 | OpenDGLab Desktop 15 | 16 | 17 | 18 | :/icon/OpenDGLab-Desktop.png:/icon/OpenDGLab-Desktop.png 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 0 27 | 0 28 | 29 | 30 | 31 | 0 32 | 33 | 34 | 35 | 连接 36 | 37 | 38 | 39 | 40 | 41 | 添加设备 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 操作 53 | 54 | 55 | 56 | 57 | 58 | Qt::Horizontal 59 | 60 | 61 | 62 | 63 | 64 | 65 | 1 66 | 67 | 68 | 274 69 | 70 | 71 | Qt::Horizontal 72 | 73 | 74 | 75 | 76 | 77 | 78 | 按住执行 79 | 80 | 81 | 82 | 83 | 84 | 85 | QListWidget::item { border-bottom: 1px solid black; } 86 | 87 | 88 | 89 | 90 | 91 | 92 | <html><head/><body><p>突增</p></body></html> 93 | 94 | 95 | 96 | 97 | 98 | 99 | 1 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 应用控制 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | RemoteControl 123 | QWidget 124 |
remoteControl.h
125 | 1 126 |
127 |
128 | 129 | 130 | 131 | 132 |
133 | -------------------------------------------------------------------------------- /src/global.cpp: -------------------------------------------------------------------------------- 1 | #include "global.h" 2 | #include 3 | 4 | namespace Global { 5 | QList basicWaveNameList; 6 | QList touchWaveNameList; 7 | QString deviceName; 8 | namespace DGLabServices { 9 | namespace DeviceStatus { 10 | QString service; 11 | namespace Characteristic { 12 | QString electric; 13 | } 14 | } 15 | namespace EStimStatus { 16 | QString service; 17 | namespace Characteristic { 18 | QString setup; 19 | QString abpower; 20 | QString waveA; 21 | QString waveB; 22 | } 23 | } 24 | } 25 | bool skipError = false; 26 | bool skipWarning = false; 27 | Remote *remote = new Remote(); 28 | QMutex mutex; 29 | QList dglabList; 30 | QList remoteList; 31 | QBluetoothDeviceDiscoveryAgent *bleScanAgent; 32 | void initGlobal() { 33 | bleScanAgent = new QBluetoothDeviceDiscoveryAgent(); 34 | bleScanAgent->setLowEnergyDiscoveryTimeout(5000); 35 | libopendglab_ExportedSymbols* libopendglab = libopendglab_symbols(); 36 | auto basicWave = libopendglab->kotlin.root.WaveCenter.Companion.getBasicWaveList(libopendglab->kotlin.root.WaveCenter.Companion._instance()); 37 | auto basicWaveSize = libopendglab->kotlin.root.getStringArraySize(basicWave); 38 | for(int i = 0; i < basicWaveSize; i++) { 39 | auto item = libopendglab->kotlin.root.getStringArrayItemAt(basicWave, i); 40 | basicWaveNameList.append(QString::fromUtf8(item)); 41 | libopendglab->DisposeString(item); 42 | } 43 | auto touchWave = libopendglab->kotlin.root.WaveCenter.Companion.getTouchWaveList(libopendglab->kotlin.root.WaveCenter.Companion._instance()); 44 | auto touchWaveSize = libopendglab->kotlin.root.getStringArraySize(touchWave); 45 | for(int i = 0; i < touchWaveSize; i++) { 46 | auto item = libopendglab->kotlin.root.getStringArrayItemAt(touchWave, i); 47 | touchWaveNameList.append(QString::fromUtf8(item)); 48 | libopendglab->DisposeString(item); 49 | } 50 | auto dlabName = libopendglab->kotlin.root.OpenDGLab.Device.Companion.getName(libopendglab->kotlin.root.OpenDGLab.Device.Companion._instance()); 51 | deviceName = QString::fromUtf8(dlabName); 52 | libopendglab->DisposeString(dlabName); 53 | //DeviceStatus 54 | //Service 55 | auto batteryServiceUuid = libopendglab->kotlin.root.OpenDGLab.DeviceStatus.Companion.getUUID(libopendglab->kotlin.root.OpenDGLab.DeviceStatus.Companion._instance()); 56 | Global::DGLabServices::DeviceStatus::service = QString::fromUtf8(batteryServiceUuid); 57 | libopendglab->DisposeString(batteryServiceUuid); 58 | //Characterisitc 59 | auto batteryCharacteristic = libopendglab->kotlin.root.OpenDGLab.DeviceStatus.Electric.Companion.getUUID( 60 | libopendglab->kotlin.root.OpenDGLab.DeviceStatus.Electric.Companion._instance() 61 | ); 62 | Global::DGLabServices::DeviceStatus::Characteristic::electric = QString::fromUtf8(batteryCharacteristic); 63 | libopendglab->DisposeString(batteryCharacteristic); 64 | //EStimStatus 65 | //Service 66 | auto estimServiceUuid = libopendglab->kotlin.root.OpenDGLab.EStimStatus.Companion.getUUID(libopendglab->kotlin.root.OpenDGLab.EStimStatus.Companion._instance()); 67 | Global::DGLabServices::EStimStatus::service = QString::fromUtf8(estimServiceUuid); 68 | libopendglab->DisposeString(estimServiceUuid); 69 | //Characterisitc 70 | auto setupCharacterisitc = libopendglab->kotlin.root.OpenDGLab.EStimStatus.Setup.Companion.getUUID( 71 | libopendglab->kotlin.root.OpenDGLab.EStimStatus.Setup.Companion._instance() 72 | ); 73 | Global::DGLabServices::EStimStatus::Characteristic::setup = QString::fromUtf8(setupCharacterisitc); 74 | libopendglab->DisposeString(setupCharacterisitc); 75 | auto abpowerCharacterisitc = libopendglab->kotlin.root.OpenDGLab.EStimStatus.ABPower.Companion.getUUID( 76 | libopendglab->kotlin.root.OpenDGLab.EStimStatus.ABPower.Companion._instance() 77 | ); 78 | Global::DGLabServices::EStimStatus::Characteristic::abpower = QString::fromUtf8(abpowerCharacterisitc); 79 | libopendglab->DisposeString(abpowerCharacterisitc); 80 | auto waveACharacterisitc = libopendglab->kotlin.root.OpenDGLab.EStimStatus.Wave.Companion.getUUIDA( 81 | libopendglab->kotlin.root.OpenDGLab.EStimStatus.Wave.Companion._instance() 82 | ); 83 | Global::DGLabServices::EStimStatus::Characteristic::waveA = QString::fromUtf8(waveACharacterisitc); 84 | libopendglab->DisposeString(waveACharacterisitc); 85 | auto waveBCharacterisitc = libopendglab->kotlin.root.OpenDGLab.EStimStatus.Wave.Companion.getUUIDB( 86 | libopendglab->kotlin.root.OpenDGLab.EStimStatus.Wave.Companion._instance() 87 | ); 88 | Global::DGLabServices::EStimStatus::Characteristic::waveB = QString::fromUtf8(waveBCharacterisitc); 89 | libopendglab->DisposeString(waveBCharacterisitc); 90 | 91 | } 92 | 93 | QList getWaveList() 94 | { 95 | QList list; 96 | list.append(basicWaveNameList); 97 | list.append(touchWaveNameList); 98 | return list; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/mainWindow.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "mainWindow.h" 3 | #include "global.h" 4 | #include "scanDevice.h" 5 | #include "ui_mainWindow.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | MainWindow::MainWindow(QWidget *parent) 13 | : QMainWindow(parent) 14 | , ui(new Ui::MainWindow) 15 | , mvcController(new MainWindowController(this)) 16 | { 17 | ui->setupUi(this); 18 | connect(ui->btnAddDevice, &QPushButton::clicked, this, &MainWindow::addDevice); 19 | ui->listDevice->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); 20 | connect(ui->listDevice, &QWidget::customContextMenuRequested, this, &MainWindow::showContextMenu); 21 | connect(ui->allBoost, &QSlider::valueChanged, this, &MainWindow::allBoostChanged); 22 | connect(ui->btn_boost, &QPushButton::pressed, this, [this](){ 23 | startAllBoost(ui->allBoost->value()); 24 | }); 25 | connect(ui->btn_boost, &QPushButton::released, this, [this](){ 26 | stopAllBoost(); 27 | }); 28 | loadDevice(); 29 | } 30 | 31 | MainWindow::~MainWindow() 32 | { 33 | delete ui; 34 | } 35 | 36 | void MainWindow::addDevice() { 37 | auto scanDeviceDialog = new ScanDevice(this); 38 | connect(scanDeviceDialog, &ScanDevice::needRefresh, this, &MainWindow::refreshDeviceList); 39 | scanDeviceDialog->show(); 40 | } 41 | 42 | void MainWindow::showContextMenu(const QPoint &pos) { 43 | QPoint globalPos = ui->listDevice->mapToGlobal(pos); 44 | QMenu myMenu; 45 | myMenu.addAction("删除", this, &MainWindow::removeDeviceItem); 46 | myMenu.exec(globalPos); 47 | } 48 | void MainWindow::removeDeviceItem() { 49 | QMutexLocker locker(&Global::mutex); 50 | int index = ui->listDevice->currentRow(); 51 | if (index < 0) { 52 | return; 53 | } 54 | auto device = Global::dglabList[index]; 55 | if(device->getBLEController()->state() != QLowEnergyController::ControllerState::UnconnectedState) { 56 | QMessageBox::warning(this, "通知", "您不能移除一个已经连接的设备,请关闭设备后再试。"); 57 | return; 58 | } 59 | QListWidgetItem *item = ui->listDevice->takeItem(index); 60 | QListWidgetItem *itemOperator = ui->listOperator->takeItem(index); 61 | Global::dglabList.removeAt(index); 62 | delete item; 63 | delete itemOperator; 64 | delete device; 65 | ui->listDevice->update(); 66 | saveDevice(); 67 | } 68 | void MainWindow::showEvent(QShowEvent*) { 69 | // 70 | } 71 | void MainWindow::closeEvent(QCloseEvent*) { 72 | 73 | } 74 | void MainWindow::saveDevice() { 75 | QFile saveFile(QStringLiteral("save.json")); 76 | if (!saveFile.open(QIODevice::WriteOnly)) { 77 | return; 78 | } 79 | QJsonObject savedObject; 80 | savedObject["platform"] = 81 | #ifdef Q_OS_MACOS 82 | "macos" 83 | #else 84 | "compatible" 85 | #endif 86 | ; 87 | QJsonArray deviceArray; 88 | for (auto device: (Global::dglabList)) { 89 | QJsonObject bleObject; 90 | bleObject["address"] = device->getDeviceAddressOrUuid(); 91 | bleObject["id"] = device->getID(); 92 | deviceArray.append(bleObject); 93 | } 94 | savedObject["devices"] = deviceArray; 95 | QJsonDocument saveDoc(savedObject); 96 | saveFile.write(saveDoc.toJson()); 97 | } 98 | void MainWindow::loadDevice() { 99 | libopendglab_ExportedSymbols *core = libopendglab_symbols(); 100 | auto instance = core->kotlin.root.OpenDGLab.Device.Companion._instance(); 101 | auto name = QString::fromUtf8( 102 | core->kotlin.root.OpenDGLab.Device.Companion.getName( 103 | instance 104 | ) 105 | ); 106 | QFile loadFile(QStringLiteral("save.json")); 107 | if (!loadFile.open(QIODevice::ReadOnly)) { 108 | return; 109 | } 110 | QByteArray savedDevice = loadFile.readAll(); 111 | QJsonDocument loadDoc(QJsonDocument::fromJson(savedDevice)); 112 | QJsonObject json = loadDoc.object(); 113 | QString platform = json["platform"].toString(); 114 | #ifdef Q_OS_MACOS 115 | if (platform == "compatible") 116 | #else 117 | if (platform == "macos") 118 | #endif 119 | { 120 | return; 121 | } 122 | QJsonArray deviceArray = json["devices"].toArray(); 123 | for (auto array: deviceArray) { 124 | QJsonObject obj = array.toObject(); 125 | QString address = obj["address"].toString(); 126 | QString id = obj["id"].toString(); 127 | QBluetoothDeviceInfo info = QBluetoothDeviceInfo( 128 | #ifdef Q_OS_MACOS 129 | QBluetoothUuid(address) 130 | #else 131 | QBluetoothAddress(address) 132 | #endif 133 | , name, 0); 134 | DGLabDevice *device = new DGLabDevice(info); 135 | Global::dglabList.append(device); 136 | } 137 | this->refreshDeviceList(); 138 | } 139 | void MainWindow::refreshDeviceList() { 140 | for (auto device: Global::dglabList) { 141 | if (!device->isShownOnUi()) { 142 | device->setShownOnUi(); 143 | auto listDevice = new QListWidgetItem(ui->listDevice); 144 | listDevice->setSizeHint(device->getUiDeviceItem()->sizeHint()); 145 | ui->listDevice->addItem(listDevice); 146 | ui->listDevice->setItemWidget(listDevice, device->getUiDeviceItem()); 147 | auto listOperator = new QListWidgetItem(ui->listOperator); 148 | listOperator->setSizeHint(device->getUiDeviceOperator()->sizeHint()); 149 | ui->listOperator->addItem(listOperator); 150 | ui->listOperator->setItemWidget(listOperator, device->getUiDeviceOperator()); 151 | } 152 | } 153 | saveDevice(); 154 | } 155 | void MainWindow::allBoostChanged(int value) { 156 | ui->lbl_boost->setText(QString::number(value)); 157 | } 158 | 159 | void MainWindow::startAllBoost(int boost) 160 | { 161 | ui->allBoost->setEnabled(false); 162 | mvcController->startAllBoost(boost); 163 | } 164 | 165 | void MainWindow::stopAllBoost() 166 | { 167 | ui->allBoost->setEnabled(true); 168 | mvcController->stopAllBoost(); 169 | } 170 | -------------------------------------------------------------------------------- /src/remoteControl.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | RemoteControl 4 | 5 | 6 | 7 | 0 8 | 0 9 | 621 10 | 398 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 启动远程服务器 21 | 22 | 23 | 24 | 25 | 26 | 27 | WS 端口号 28 | 29 | 30 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 31 | 32 | 33 | 34 | 35 | 36 | 37 | 1024 38 | 39 | 40 | 65535 41 | 42 | 43 | 11240 44 | 45 | 46 | 47 | 48 | 49 | 50 | 1024 51 | 52 | 53 | 65535 54 | 55 | 56 | 11241 57 | 58 | 59 | 60 | 61 | 62 | 63 | TCP 端口号 64 | 65 | 66 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | true 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 0 86 | 0 87 | 88 | 89 | 90 | 91 | 300 92 | 0 93 | 94 | 95 | 96 | 97 | 98 | 99 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 100 | 101 | 102 | 103 | 104 | 105 | 106 | 解除授权 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 0 115 | 0 116 | 117 | 118 | 119 | 120 | 300 121 | 0 122 | 123 | 124 | 125 | 126 | 127 | 128 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 0 140 | 0 141 | 142 | 143 | 144 | 连入名 145 | 146 | 147 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 0 156 | 0 157 | 158 | 159 | 160 | 连入 ID 161 | 162 | 163 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 0 172 | 0 173 | 174 | 175 | 176 | 无状态 177 | 178 | 179 | Qt::AlignCenter 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /src/deviceOperator.cpp: -------------------------------------------------------------------------------- 1 | #include "deviceOperator.h" 2 | #include "ui_deviceOperator.h" 3 | #include "global.h" 4 | 5 | DeviceOperator::DeviceOperator(QString id, QWidget *parent) : 6 | QWidget(parent), 7 | ui(new Ui::DeviceOperator) 8 | { 9 | ui->setupUi(this); 10 | ui->lbl_id->setText(id); 11 | for (auto wave: Global::basicWaveNameList) { 12 | ui->cb_a_wave->addItem(wave); 13 | ui->cb_b_wave->addItem(wave); 14 | } 15 | ui->cb_a_wave->insertSeparator(ui->cb_a_wave->count()); 16 | ui->cb_b_wave->insertSeparator(ui->cb_b_wave->count()); 17 | for (auto wave: Global::touchWaveNameList) { 18 | ui->cb_a_wave->addItem(wave); 19 | ui->cb_b_wave->addItem(wave); 20 | } 21 | ui->cb_a_wave->insertSeparator(ui->cb_a_wave->count()); 22 | ui->cb_b_wave->insertSeparator(ui->cb_b_wave->count()); 23 | ui->cb_a_wave->addItem("外部输入"); 24 | ui->cb_b_wave->addItem("外部输入"); 25 | connect(ui->chk_a_auto, &QCheckBox::stateChanged, [this](int state){ 26 | ui->cb_a_wave->setEnabled(!state); 27 | ui->sp_a_auto_timer->setEnabled(!state); 28 | if (!state) { 29 | autoATimer = 0; 30 | } 31 | }); 32 | connect(ui->chk_b_auto, &QCheckBox::stateChanged, [this](int state){ 33 | ui->cb_b_wave->setEnabled(!state); 34 | ui->sp_b_auto_timer->setEnabled(!state); 35 | if (!state) { 36 | autoBTimer = 0; 37 | } 38 | }); 39 | connect(ui->silde_a_step, &QSlider::valueChanged, [this](int value){ 40 | ui->lbl_a_step->setText(QString::number(value)); 41 | }); 42 | connect(ui->silde_b_step, &QSlider::valueChanged, [this](int value){ 43 | ui->lbl_b_step->setText(QString::number(value)); 44 | }); 45 | connect(ui->sb_a_boost, &QSlider::valueChanged, [this](int value){ 46 | ui->lbl_a_boost->setText(QString::number(value)); 47 | }); 48 | connect(ui->sb_b_boost, &QSlider::valueChanged, [this](int value){ 49 | ui->lbl_b_boost->setText(QString::number(value)); 50 | }); 51 | 52 | connect(ui->btn_a_increase, &QPushButton::clicked, [this](bool){ 53 | emit changePower(ui->lcd_a_strength->intValue() + ui->silde_a_step->value(), ui->lcd_b_strength->intValue()); 54 | }); 55 | connect(ui->btn_a_decrease, &QPushButton::clicked, [this](bool){ 56 | emit changePower(ui->lcd_a_strength->intValue() - ui->silde_a_step->value(), ui->lcd_b_strength->intValue()); 57 | }); 58 | connect(ui->btn_a_stop, &QPushButton::clicked, [this](bool){ 59 | emit changePower(0, ui->lcd_b_strength->intValue()); 60 | }); 61 | connect(ui->btn_b_increase, &QPushButton::clicked, [this](bool){ 62 | emit changePower(ui->lcd_a_strength->intValue(), ui->lcd_b_strength->intValue() + ui->silde_b_step->value()); 63 | }); 64 | connect(ui->btn_b_decrease, &QPushButton::clicked, [this](bool){ 65 | emit changePower(ui->lcd_a_strength->intValue(), ui->lcd_b_strength->intValue() - ui->silde_b_step->value()); 66 | }); 67 | connect(ui->btn_b_stop, &QPushButton::clicked, [this](bool){ 68 | emit changePower(ui->lcd_a_strength->intValue(), 0); 69 | }); 70 | 71 | connect(ui->cb_a_wave, &QComboBox::currentTextChanged, [this](const QString & value){ 72 | emit changeWave(DeviceStateEnum::DeviceChannel::CHANNEL_A, value); 73 | }); 74 | connect(ui->cb_b_wave, &QComboBox::currentTextChanged, [this](const QString & value){ 75 | emit changeWave(DeviceStateEnum::DeviceChannel::CHANNEL_B, value); 76 | }); 77 | connect(ui->btn_a_boost, &QPushButton::pressed, [this](){ 78 | ui->sb_a_boost->setEnabled(false); 79 | emit startBoost(DeviceStateEnum::DeviceChannel::CHANNEL_A, ui->sb_a_boost->value()); 80 | }); 81 | connect(ui->btn_b_boost, &QPushButton::pressed, [this](){ 82 | ui->sb_b_boost->setEnabled(false); 83 | emit startBoost(DeviceStateEnum::DeviceChannel::CHANNEL_B, ui->sb_b_boost->value()); 84 | }); 85 | connect(ui->btn_a_boost, &QPushButton::released, [this](){ 86 | ui->sb_a_boost->setEnabled(true); 87 | emit stopBoost(DeviceStateEnum::DeviceChannel::CHANNEL_A, ui->sb_a_boost->value()); 88 | }); 89 | connect(ui->btn_b_boost, &QPushButton::released, [this](){ 90 | ui->sb_b_boost->setEnabled(true); 91 | emit stopBoost(DeviceStateEnum::DeviceChannel::CHANNEL_B, ui->sb_b_boost->value()); 92 | }); 93 | } 94 | 95 | DeviceOperator::~DeviceOperator() 96 | { 97 | delete ui; 98 | } 99 | 100 | QString DeviceOperator::getWaveA() 101 | { 102 | return ui->cb_a_wave->currentText(); 103 | } 104 | QString DeviceOperator::getWaveB() 105 | { 106 | return ui->cb_b_wave->currentText(); 107 | } 108 | 109 | void DeviceOperator::setWaveA(QString str) { 110 | if (ui->cb_a_wave->findText(str) >= 0) { 111 | ui->cb_a_wave->setCurrentText(str); 112 | } 113 | } 114 | void DeviceOperator::setWaveB(QString str) { 115 | if (ui->cb_b_wave->findText(str) >= 0) { 116 | ui->cb_b_wave->setCurrentText(str); 117 | } 118 | } 119 | 120 | void DeviceOperator::checkIfNeedChangeWave() 121 | { 122 | if (ui->chk_a_auto->isChecked()) { 123 | autoATimer++; 124 | if (autoATimer > ui->sp_a_auto_timer->value()) { 125 | auto next = random.bounded(0, ui->cb_a_wave->count()); 126 | ui->cb_a_wave->setCurrentIndex(next); 127 | autoATimer = 0; 128 | } 129 | } 130 | if (ui->chk_b_auto->isChecked()) { 131 | autoBTimer++; 132 | if (autoBTimer > ui->sp_b_auto_timer->value()) { 133 | auto next = random.bounded(0, ui->cb_b_wave->count()); 134 | ui->cb_b_wave->setCurrentIndex(next); 135 | autoBTimer = 0; 136 | } 137 | } 138 | } 139 | 140 | void DeviceOperator::setPower(DeviceStateEnum::DeviceChannel channel, int level) { 141 | switch (channel) { 142 | case DeviceStateEnum::DeviceChannel::CHANNEL_A: 143 | ui->lcd_a_strength->display(level); 144 | break; 145 | case DeviceStateEnum::DeviceChannel::CHANNEL_B: 146 | ui->lcd_b_strength->display(level); 147 | break; 148 | } 149 | } 150 | void DeviceOperator::setAutoChange(DeviceStateEnum::DeviceChannel channel, bool autoChange) { 151 | switch (channel) { 152 | case DeviceStateEnum::DeviceChannel::CHANNEL_A: 153 | ui->chk_a_auto->setChecked(autoChange); 154 | break; 155 | case DeviceStateEnum::DeviceChannel::CHANNEL_B: 156 | ui->chk_b_auto->setChecked(autoChange); 157 | break; 158 | } 159 | } 160 | bool DeviceOperator::getAutoChange(DeviceStateEnum::DeviceChannel channel) { 161 | switch (channel) { 162 | case DeviceStateEnum::DeviceChannel::CHANNEL_A: 163 | return ui->chk_a_auto->isChecked(); 164 | case DeviceStateEnum::DeviceChannel::CHANNEL_B: 165 | return ui->chk_b_auto->isChecked(); 166 | } 167 | return false; 168 | } 169 | -------------------------------------------------------------------------------- /src/remote.cpp: -------------------------------------------------------------------------------- 1 | #include "remote.h" 2 | #include "global.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #ifndef __linux__ 10 | #include 11 | #endif 12 | Remote::Remote(QObject *parent) : QObject(parent), tcpPort(11240), wsPort(11241) 13 | { 14 | load(); 15 | tcpServer = new QTcpServer(this); 16 | #ifndef __linux__ 17 | wsServer = new QWebSocketServer("127.0.0.1", QWebSocketServer::SslMode::NonSecureMode, this); 18 | auto *cors = new QWebSocketCorsAuthenticator("127.0.0.1"); 19 | cors->setAllowed(true); 20 | wsServer->originAuthenticationRequired(cors); 21 | connect(wsServer, &QWebSocketServer::originAuthenticationRequired, [](QWebSocketCorsAuthenticator *){ 22 | return true; 23 | }); 24 | connect(wsServer, &QWebSocketServer::newConnection, this, &Remote::wsNewConnection); 25 | #endif 26 | connect(tcpServer, &QTcpServer::newConnection, this, &Remote::tcpNewConnection); 27 | } 28 | 29 | Remote::~Remote() 30 | { 31 | stop(); 32 | } 33 | 34 | bool Remote::isStarted() { 35 | return isStart; 36 | } 37 | 38 | bool Remote::start() { 39 | bool tcpStart = false; 40 | bool wsStart = false; 41 | if (!isStart) { 42 | if(tcpServer->listen(QHostAddress::LocalHost, tcpPort)){ 43 | tcpStart = true; 44 | } 45 | #ifndef __linux__ 46 | if (wsServer->listen(QHostAddress::LocalHost, wsPort)) { 47 | wsStart = true; 48 | } 49 | #else 50 | wsStart = true; 51 | #endif 52 | if (tcpStart && wsStart){ 53 | isStart = true; 54 | if (autoRemoteProtocolPinger == nullptr) { 55 | autoRemoteProtocolPinger = new AutoRemoteProtocolPinger(); 56 | } 57 | if (!autoRemoteProtocolPinger->isRunning()) { 58 | autoRemoteProtocolPinger->start(); 59 | } 60 | return true; 61 | } 62 | if (tcpStart) tcpServer->close(); 63 | #ifndef __linux__ 64 | if (wsStart) wsServer->close(); 65 | #endif 66 | return false; 67 | } 68 | return true; 69 | } 70 | 71 | void Remote::stop() { 72 | if (isStart) { 73 | if (autoRemoteProtocolPinger != nullptr) { 74 | if (autoRemoteProtocolPinger->isRunning()) { 75 | autoRemoteProtocolPinger->quit(); 76 | delete autoRemoteProtocolPinger; 77 | autoRemoteProtocolPinger = nullptr; 78 | } 79 | } 80 | tcpServer->close(); 81 | #ifndef __linux__ 82 | wsServer->close(); 83 | #endif 84 | for(auto r: Global::remoteList) { 85 | r->close(); 86 | } 87 | isStart = false; 88 | } 89 | } 90 | 91 | void Remote::setWSPort(int port) { 92 | if (!isStart) wsPort = port; 93 | } 94 | 95 | void Remote::setTcpPort(int port) { 96 | if (!isStart) tcpPort = port; 97 | } 98 | 99 | void Remote::tcpNewConnection() { 100 | auto socket = tcpServer->nextPendingConnection(); 101 | auto* client = new RemoteClient(socket); 102 | connect(client, &RemoteClient::doAuth, this, &Remote::doAuth); 103 | Global::remoteList.push_back(client); 104 | } 105 | void Remote::wsNewConnection() { 106 | #ifndef __linux__ 107 | auto socket = wsServer->nextPendingConnection(); 108 | auto* client = new RemoteClient(socket); 109 | connect(client, &RemoteClient::doAuth, this, &Remote::doAuth); 110 | Global::remoteList.push_back(client); 111 | #endif 112 | } 113 | 114 | void Remote::doAuth(RemoteClient* client, QString appName, QString appId, QString token) { 115 | if (appName.isEmpty() || appId.isEmpty()) client->sendConnected("", "", false); 116 | if (preAuth.contains(appId)) { 117 | auto pA = preAuth[appId]; 118 | if (pA == token) { 119 | client->sendConnected(appId, token, true); 120 | } else { 121 | QMessageBox::StandardButton reply; 122 | reply = QMessageBox::question(nullptr, "应用连接", QString("%1 正在请求连接 OpenDGLab Desktop 以控制您的设备。").arg(appName), QMessageBox::Yes|QMessageBox::No); 123 | if (reply == QMessageBox::Yes) { 124 | auto nToken = getRandomString(); 125 | client->sendConnected(appId, nToken, true); 126 | Global::remote->preAuth[appId] = nToken; 127 | Global::remote->preAuthName[appId] = appName; 128 | } else { 129 | client->sendConnected("", "", false); 130 | } 131 | } 132 | } else { 133 | QMessageBox::StandardButton reply; 134 | reply = QMessageBox::question(nullptr, "应用连接", QString("%1 正在请求连接 OpenDGLab Desktop 以控制您的设备。").arg(appName), QMessageBox::Yes|QMessageBox::No); 135 | if (reply == QMessageBox::Yes) { 136 | auto nToken = getRandomString(); 137 | client->sendConnected(appId, nToken, true); 138 | Global::remote->preAuth[appId] = nToken; 139 | Global::remote->preAuthName[appId] = appName; 140 | } else { 141 | client->sendConnected("", "", false); 142 | } 143 | } 144 | save(); 145 | emit stateChange(); 146 | } 147 | 148 | QString Remote::getRandomString() { 149 | const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); 150 | const int randomStringLength = 16; 151 | 152 | QString randomString; 153 | for(int i=0; i:/utf-8>") 48 | add_compile_options("$<$:/utf-8>") 49 | 50 | set(CMAKE_INCLUDE_CURRENT_DIR TRUE) 51 | find_package(Protobuf REQUIRED) 52 | set_property(SOURCE proto/app.pb.h PROPERTY SKIP_AUTOGEN ON) 53 | set_property(SOURCE proto/app.pb.cc PROPERTY SKIP_AUTOGEN ON) 54 | set_target_properties(protobuf::libprotobuf PROPERTIES 55 | INTERFACE_COMPILE_DEFINITIONS "PROTOBUF_USE_DLLS" 56 | ) 57 | 58 | if(Qt5_FOUND AND WIN32 AND TARGET Qt5::qmake AND NOT TARGET Qt5::windeployqt) 59 | get_target_property(_qt5_qmake_location Qt5::qmake IMPORTED_LOCATION) 60 | 61 | execute_process( 62 | COMMAND "${_qt5_qmake_location}" -query QT_INSTALL_PREFIX 63 | RESULT_VARIABLE return_code 64 | OUTPUT_VARIABLE qt5_install_prefix 65 | OUTPUT_STRIP_TRAILING_WHITESPACE 66 | ) 67 | set(imported_location "${qt5_install_prefix}/bin/windeployqt.exe") 68 | 69 | if(EXISTS ${imported_location}) 70 | add_executable(Qt5::windeployqt IMPORTED) 71 | 72 | set_target_properties(Qt5::windeployqt PROPERTIES 73 | IMPORTED_LOCATION ${imported_location} 74 | ) 75 | endif() 76 | endif() 77 | file(GLOB_RECURSE OpenDGLabDesktop_SRC 78 | "src/*.h" 79 | "src/*.cpp" 80 | "src/*.ui" 81 | "proto/*.proto" 82 | ) 83 | add_library(libopendglab STATIC IMPORTED) 84 | set_property(TARGET libopendglab PROPERTY IMPORTED_LOCATION ${OPENDGLABCORE_LIB}) 85 | if (WIN32) 86 | set(APP_ICON_RESOURCE_WINDOWS "${CMAKE_CURRENT_SOURCE_DIR}/res/icon.rc") 87 | add_executable(OpenDGLab-Desktop WIN32 ${OpenDGLabDesktop_SRC} ${appProto_PROTO_SRCS} res/image.qrc ${APP_ICON_RESOURCE_WINDOWS}) 88 | target_link_libraries(OpenDGLab-Desktop PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Bluetooth Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::WebSockets libopendglab protobuf::libprotobuf) 89 | elseif(LINUX) 90 | add_executable(OpenDGLab-Desktop ${OpenDGLabDesktop_SRC} ${appProto_PROTO_SRCS} res/image.qrc) 91 | target_link_libraries(OpenDGLab-Desktop PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Bluetooth Qt${QT_VERSION_MAJOR}::Network libopendglab protobuf::libprotobuf Threads::Threads ${CMAKE_DL_LIBS}) 92 | elseif(APPLE) 93 | add_executable(OpenDGLab-Desktop MACOSX_BUNDLE ${OpenDGLabDesktop_SRC} ${appProto_PROTO_SRCS} res/image.qrc ${APP_ICON_MACOSX}) 94 | target_link_libraries(OpenDGLab-Desktop PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Bluetooth Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::WebSockets libopendglab protobuf::libprotobuf ${CMAKE_DL_LIBS} "-framework AppKit" "-framework CoreData" "-framework Foundation") 95 | endif() 96 | target_include_directories(OpenDGLab-Desktop PUBLIC "src") 97 | 98 | protobuf_generate(TARGET OpenDGLab-Desktop) 99 | 100 | if(TARGET Qt5::windeployqt) 101 | if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") 102 | set(WIN_ARCH "x86") 103 | else() 104 | set(WIN_ARCH "x64") 105 | endif() 106 | add_custom_command(TARGET OpenDGLab-Desktop 107 | POST_BUILD 108 | COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_CURRENT_BINARY_DIR}/OpenDGLab-Desktop-${WIN_ARCH}" 109 | COMMAND set PATH=%PATH%$${qt5_install_prefix}/bin 110 | COMMAND Qt5::windeployqt --dir "${CMAKE_CURRENT_BINARY_DIR}/OpenDGLab-Desktop-${WIN_ARCH}" "$/$" 111 | ) 112 | install( 113 | TARGETS OpenDGLab-Desktop 114 | ARCHIVE DESTINATION ${CMAKE_SOURCE_DIR}/target/bin 115 | LIBRARY DESTINATION ${CMAKE_SOURCE_DIR}/target/bin 116 | RUNTIME DESTINATION ${CMAKE_SOURCE_DIR}/target/bin 117 | PUBLIC_HEADER DESTINATION ${CMAKE_SOURCE_DIR}/target/bin 118 | ) 119 | 120 | # copy deployment directory during installation 121 | install( 122 | DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/OpenDGLab-Desktop-${WIN_ARCH}/" 123 | DESTINATION ${OpenDGLab-Desktop_BINARY_DIR} 124 | ) 125 | 126 | set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE) 127 | 128 | include(InstallRequiredSystemLibraries) 129 | 130 | install( 131 | PROGRAMS ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS} 132 | DESTINATION ${OpenDGLab-Desktop_BINARY_DIR} 133 | ) 134 | add_custom_command(TARGET OpenDGLab-Desktop POST_BUILD 135 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 136 | ${OPENDGLABCORE_BIN} 137 | "$/OpenDGLab-Desktop-${WIN_ARCH}/") 138 | add_custom_command(TARGET OpenDGLab-Desktop POST_BUILD 139 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 140 | $ 141 | "$/OpenDGLab-Desktop-${WIN_ARCH}/") 142 | if(CMAKE_BUILD_TYPE MATCHES DEBUG OR CMAKE_BUILD_TYPE MATCHES Debug) 143 | add_custom_command(TARGET OpenDGLab-Desktop POST_BUILD 144 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 145 | ${CMAKE_CURRENT_BINARY_DIR}/libprotobufd.dll 146 | "$/OpenDGLab-Desktop-${WIN_ARCH}/") 147 | else() 148 | add_custom_command(TARGET OpenDGLab-Desktop POST_BUILD 149 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 150 | ${CMAKE_CURRENT_BINARY_DIR}/libprotobuf.dll 151 | "$/OpenDGLab-Desktop-${WIN_ARCH}/") 152 | endif() 153 | add_custom_command(TARGET OpenDGLab-Desktop POST_BUILD 154 | COMMAND ${CMAKE_COMMAND} -E tar "cfv" "OpenDGLab-Desktop-${WIN_ARCH}.zip" --format=zip 155 | "$/OpenDGLab-Desktop-${WIN_ARCH}") 156 | endif() 157 | -------------------------------------------------------------------------------- /.github/workflows/tags.yml: -------------------------------------------------------------------------------- 1 | name: Build Release 2 | on: 3 | create: 4 | tags: 5 | - v* 6 | jobs: 7 | create_release: 8 | name: Create release 9 | runs-on: ubuntu-latest 10 | outputs: 11 | upload_url: ${{ steps.create_release.outputs.upload_url }} 12 | version: ${{ steps.get_version.outputs.version }} 13 | steps: 14 | - name: Get latest release version number 15 | id: get_version 16 | uses: battila7/get-version-action@v2 17 | - name: Create Release 18 | id: create_release 19 | uses: actions/create-release@v1 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | with: 23 | tag_name: ${{ github.ref }} 24 | release_name: OpenDGLab Desktop ${{ steps.get_version.outputs.version }} 25 | body: | 26 | OpenDGLab Desktop ${{ steps.get_version.outputs.version }} 27 | draft: false 28 | prerelease: false 29 | build_windows: 30 | strategy: 31 | matrix: 32 | qt_ver: [5.15.1] 33 | os: [windows-latest] 34 | qt_arch: [win64_msvc2019_64, win32_msvc2019] 35 | qt_target: ['desktop'] 36 | include: 37 | - qt_arch: win64_msvc2019_64 38 | msvc_arch: x64 39 | qt_arch_install: msvc2019_64 40 | - qt_arch: win32_msvc2019 41 | msvc_arch: x86 42 | qt_arch_install: msvc2019 43 | needs: [create_release] 44 | runs-on: ${{ matrix.os }} 45 | steps: 46 | - name: Cache Qt 47 | id: windows-cache-qt 48 | uses: actions/cache@v1 49 | with: 50 | path: ../Qt/${{matrix.qt_ver}}/${{matrix.qt_arch_install}} 51 | key: ${{ runner.os }}-Qt/${{matrix.qt_ver}}/${{matrix.qt_arch}} 52 | - name: Install Qt 53 | uses: jurplel/install-qt-action@v2.11.1 54 | with: 55 | version: ${{ matrix.qt_ver }} 56 | target: ${{ matrix.qt_target }} 57 | arch: ${{ matrix.qt_arch }} 58 | cached: ${{steps.windows-cache-qt.outputs.cache-hit }} 59 | - name: Checkout 60 | uses: actions/checkout@v1 61 | - name: Get OpenDGLab Core Windows x64 62 | if: matrix.msvc_arch == 'x64' 63 | shell: powershell 64 | run: | 65 | Invoke-WebRequest https://github.com/OpenDGLab/OpenDGLab-Core/releases/latest/download/OpenDGLab-Core-Windows-X64.zip -OutFile OpenDGLab-Core-Windows-X64.zip 66 | Expand-Archive OpenDGLab-Core-Windows-X64.zip -DestinationPath OpenDGLab-Core-Unzipped 67 | New-Item opendglab-core/header -ItemType Directory -ea 0 68 | New-Item opendglab-core/bin -ItemType Directory -ea 0 69 | Copy-Item -Path "OpenDGLab-Core-Unzipped/releaseShared/libopendglab_api.h" -Destination "opendglab-core/header/" 70 | Copy-Item -Path "OpenDGLab-Core-Unzipped/releaseShared/libopendglab.dll" -Destination "opendglab-core/bin/" 71 | Copy-Item -Path "OpenDGLab-Core-Unzipped/releaseShared/libopendglab.def" -Destination "opendglab-core/bin/" 72 | - name: Get OpenDGLab Core Windows x86 73 | if: matrix.msvc_arch == 'x86' 74 | shell: powershell 75 | run: | 76 | Invoke-WebRequest https://github.com/OpenDGLab/OpenDGLab-Core/releases/latest/download/OpenDGLab-Core-Windows-X86.zip -OutFile OpenDGLab-Core-Windows-X86.zip 77 | Expand-Archive OpenDGLab-Core-Windows-X86.zip -DestinationPath OpenDGLab-Core-Unzipped 78 | New-Item opendglab-core/header -ItemType Directory -ea 0 79 | New-Item opendglab-core/bin -ItemType Directory -ea 0 80 | Copy-Item -Path "OpenDGLab-Core-Unzipped/releaseShared/libopendglab_api.h" -Destination "opendglab-core/header/" 81 | Copy-Item -Path "OpenDGLab-Core-Unzipped/releaseShared/libopendglab.dll" -Destination "opendglab-core/bin/" 82 | Copy-Item -Path "OpenDGLab-Core-Unzipped/releaseShared/libopendglab.def" -Destination "opendglab-core/bin/" 83 | - name: Get OpenDGLab OpenProtocol file 84 | shell: powershell 85 | run: | 86 | Invoke-WebRequest https://raw.githubusercontent.com/OpenDGLab/OpenDGLab-OpenProtocol/master/app.proto -OutFile proto/app.proto 87 | - name: Get latest CMake and ninja 88 | uses: lukka/get-cmake@latest 89 | - name: Get vcpkg 90 | uses: lukka/run-vcpkg@v6 91 | with: 92 | setupOnly: true 93 | vcpkgDirectory: '${{ github.workspace }}/vcpkg' 94 | vcpkgGitCommitId: 'c54abfafbe0051075952c507da1f1ec234875e05' 95 | - name: Install protobuf 96 | shell: cmd 97 | env: 98 | vc_arch: ${{ matrix.msvc_arch }} 99 | run: | 100 | %VCPKG_ROOT%/vcpkg install protobuf:%vc_arch%-windows 101 | - name: Windows Build 102 | shell: cmd 103 | env: 104 | vc_arch: ${{ matrix.msvc_arch }} 105 | run: | 106 | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" %vc_arch% 107 | cd opendglab-core/bin 108 | lib /def:libopendglab.def /out:libopendglab.lib /machine:%vc_arch% 109 | cd ../.. 110 | mkdir build 111 | cd build 112 | cmake -G "Ninja" -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%\scripts\buildsystems\vcpkg.cmake .. 113 | cmake --build . --target all 114 | cd .. 115 | - name: Upload Release Asset Windows x86 116 | uses: actions/upload-release-asset@v1 117 | if: matrix.msvc_arch == 'x86' 118 | env: 119 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 120 | with: 121 | upload_url: ${{ needs.create_release.outputs.upload_url }} 122 | asset_path: ./build/OpenDGLab-Desktop-x86.zip 123 | asset_name: OpenDGLab-Desktop-Windows-x86.zip 124 | asset_content_type: application/zip 125 | - name: Upload Release Asset Windows x64 126 | uses: actions/upload-release-asset@v1 127 | if: matrix.msvc_arch == 'x64' 128 | env: 129 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 130 | with: 131 | upload_url: ${{ needs.create_release.outputs.upload_url }} 132 | asset_path: ./build/OpenDGLab-Desktop-x64.zip 133 | asset_name: OpenDGLab-Desktop-Windows-x64.zip 134 | asset_content_type: application/zip 135 | build_linux: 136 | strategy: 137 | matrix: 138 | qt_ver: [5.15.1] 139 | os: [ubuntu-latest] 140 | needs: [create_release] 141 | runs-on: ${{ matrix.os }} 142 | steps: 143 | - name: Install Qt 144 | uses: jurplel/install-qt-action@v2.11.1 145 | with: 146 | version: ${{ matrix.qt_ver }} 147 | - name: Install Package... 148 | run: | 149 | sudo apt install -y unzip libxcb-* libgl1-mesa-dev 150 | - name: Checkout 151 | uses: actions/checkout@v1 152 | - name: Get OpenDGLab Core Linux x64 153 | shell: bash 154 | run: | 155 | wget https://github.com/OpenDGLab/OpenDGLab-Core/releases/latest/download/OpenDGLab-Core-Linux-X64.zip 156 | unzip OpenDGLab-Core-Linux-X64.zip -d OpenDGLab-Core-Unzipped 157 | mkdir -p opendglab-core/header 158 | mkdir -p opendglab-core/bin 159 | cp OpenDGLab-Core-Unzipped/releaseStatic/libopendglab_api.h opendglab-core/header/ 160 | cp OpenDGLab-Core-Unzipped/releaseStatic/libopendglab.a opendglab-core/bin/ 161 | - name: Get OpenDGLab OpenProtocol file 162 | shell: bash 163 | run: | 164 | wget https://raw.githubusercontent.com/OpenDGLab/OpenDGLab-OpenProtocol/master/app.proto 165 | mv app.proto proto/ 166 | - name: Get latest CMake and ninja 167 | uses: lukka/get-cmake@latest 168 | - name: Get vcpkg 169 | uses: lukka/run-vcpkg@v6 170 | with: 171 | setupOnly: true 172 | vcpkgDirectory: '${{ github.workspace }}/vcpkg' 173 | vcpkgGitCommitId: 'c54abfafbe0051075952c507da1f1ec234875e05' 174 | - name: Install protobuf 175 | run: | 176 | $VCPKG_ROOT/vcpkg install protobuf 177 | - name: Linux Build 178 | shell: bash 179 | run: | 180 | mkdir build 181 | cd build 182 | cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake .. 183 | cmake --build . --target all 184 | cd .. 185 | - name: Packaging... 186 | run: | 187 | cd build 188 | wget -O linuxdeployqt https://github.com/probonopd/linuxdeployqt/releases/latest/download/linuxdeployqt-7-x86_64.AppImage 189 | chmod +x linuxdeployqt 190 | mkdir appImage 191 | cp ../external/OpenDGLab.desktop appImage/ 192 | cp ../res/OpenDGLab-Desktop.png appImage/OpenDGLab.png 193 | cp OpenDGLab-Desktop appImage/ 194 | cd appImage 195 | ../linuxdeployqt OpenDGLab-Desktop -appimage -unsupported-bundle-everything 196 | mv *.AppImage OpenDGLab-Desktop-Linux-x86_64.AppImage 197 | cd ../.. 198 | - name: Upload Release Asset Linux x64 199 | uses: actions/upload-release-asset@v1 200 | env: 201 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 202 | with: 203 | upload_url: ${{ needs.create_release.outputs.upload_url }} 204 | asset_path: ./build/appImage/OpenDGLab-Desktop-Linux-x86_64.AppImage 205 | asset_name: OpenDGLab-Desktop-Linux-x86_64.AppImage 206 | asset_content_type: application/x-elf 207 | build_macos: 208 | strategy: 209 | matrix: 210 | qt_ver: [5.15.1] 211 | os: [macos-latest] 212 | needs: [create_release] 213 | runs-on: ${{ matrix.os }} 214 | steps: 215 | - name: Install Qt 216 | uses: jurplel/install-qt-action@v2.11.1 217 | with: 218 | version: ${{ matrix.qt_ver }} 219 | - name: Install Package... 220 | run: | 221 | brew install unzip 222 | - name: Checkout 223 | uses: actions/checkout@v1 224 | - name: Get OpenDGLab Core MacOS x64 225 | shell: bash 226 | run: | 227 | wget https://github.com/OpenDGLab/OpenDGLab-Core/releases/latest/download/OpenDGLab-Core-MacOS-X64.zip 228 | unzip OpenDGLab-Core-MacOS-X64.zip -d OpenDGLab-Core-Unzipped 229 | mkdir -p opendglab-core/header 230 | mkdir -p opendglab-core/bin 231 | cp OpenDGLab-Core-Unzipped/releaseStatic/libopendglab_api.h opendglab-core/header/ 232 | cp OpenDGLab-Core-Unzipped/releaseStatic/libopendglab.a opendglab-core/bin/ 233 | - name: Get OpenDGLab OpenProtocol file 234 | shell: bash 235 | run: | 236 | wget https://raw.githubusercontent.com/OpenDGLab/OpenDGLab-OpenProtocol/master/app.proto 237 | mv app.proto proto/ 238 | - name: Get latest CMake and ninja 239 | uses: lukka/get-cmake@latest 240 | - name: Get vcpkg 241 | uses: lukka/run-vcpkg@v6 242 | with: 243 | setupOnly: true 244 | vcpkgDirectory: '${{ github.workspace }}/vcpkg' 245 | vcpkgGitCommitId: 'c54abfafbe0051075952c507da1f1ec234875e05' 246 | - name: Install protobuf 247 | shell: bash 248 | run: | 249 | $VCPKG_ROOT/vcpkg install protobuf 250 | - name: MacOS Build 251 | shell: bash 252 | run: | 253 | mkdir build 254 | cd build 255 | cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake .. 256 | cmake --build . --target all 257 | cd .. 258 | - name: Packaging... 259 | run: | 260 | cd build 261 | macdeployqt OpenDGLab-Desktop.app -dmg 262 | cd .. 263 | - name: Upload Release Asset MacOS x64 264 | uses: actions/upload-release-asset@v1 265 | env: 266 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 267 | with: 268 | upload_url: ${{ needs.create_release.outputs.upload_url }} 269 | asset_path: ./build/OpenDGLab-Desktop.dmg 270 | asset_name: OpenDGLab-Desktop-MacOS-x64.dmg 271 | asset_content_type: application/octet-stream -------------------------------------------------------------------------------- /src/deviceOperator.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | DeviceOperator 4 | 5 | 6 | 7 | 0 8 | 0 9 | 692 10 | 231 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 0 27 | 0 28 | 29 | 30 | 31 | 32 | 16777215 33 | 20 34 | 35 | 36 | 37 | 38 | 75 39 | true 40 | 41 | 42 | 43 | TextLabel 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 1 59 | 60 | 61 | 62 | 63 | 64 | 65 | 1 66 | 67 | 68 | Qt::AlignCenter 69 | 70 | 71 | 72 | 73 | 74 | 75 | 自动切换 76 | 77 | 78 | 79 | 80 | 81 | 82 | 升高 83 | 84 | 85 | 86 | 87 | 88 | 89 | QFrame::NoFrame 90 | 91 | 92 | 3 93 | 94 | 95 | QLCDNumber::Flat 96 | 97 | 98 | 99 | 100 | 101 | 102 | 按住执行 103 | 104 | 105 | 106 | 107 | 108 | 109 | 1 110 | 111 | 112 | 40 113 | 114 | 115 | Qt::Horizontal 116 | 117 | 118 | 119 | 120 | 121 | 122 | 突增 123 | 124 | 125 | 126 | 127 | 128 | 129 | 调节步进 130 | 131 | 132 | 133 | 134 | 135 | 136 | A 通道 137 | 138 | 139 | 140 | 141 | 142 | 143 | 降低 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 停止 154 | 155 | 156 | 157 | 158 | 159 | 160 | Qt::Horizontal 161 | 162 | 163 | 164 | 165 | 166 | 167 | 1 168 | 169 | 170 | 274 171 | 172 | 173 | Qt::Horizontal 174 | 175 | 176 | 177 | 178 | 179 | 180 | Qt::Horizontal 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 10 191 | 192 | 193 | 600 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | <html><head/><body><p>调节步进</p></body></html> 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | B 通道 220 | 221 | 222 | 223 | 224 | 225 | 226 | 升高 227 | 228 | 229 | 230 | 231 | 232 | 233 | 1 234 | 235 | 236 | 40 237 | 238 | 239 | Qt::Horizontal 240 | 241 | 242 | 243 | 244 | 245 | 246 | 1 247 | 248 | 249 | Qt::AlignCenter 250 | 251 | 252 | 253 | 254 | 255 | 256 | 按住执行 257 | 258 | 259 | 260 | 261 | 262 | 263 | 停止 264 | 265 | 266 | 267 | 268 | 269 | 270 | Qt::Horizontal 271 | 272 | 273 | 274 | 275 | 276 | 277 | 自动切换 278 | 279 | 280 | 281 | 282 | 283 | 284 | 1 285 | 286 | 287 | 288 | 289 | 290 | 291 | QFrame::NoFrame 292 | 293 | 294 | false 295 | 296 | 297 | 3 298 | 299 | 300 | QLCDNumber::Flat 301 | 302 | 303 | 304 | 305 | 306 | 307 | 突增 308 | 309 | 310 | 311 | 312 | 313 | 314 | 1 315 | 316 | 317 | 274 318 | 319 | 320 | Qt::Horizontal 321 | 322 | 323 | 324 | 325 | 326 | 327 | 降低 328 | 329 | 330 | 331 | 332 | 333 | 334 | Qt::Horizontal 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 10 345 | 346 | 347 | 600 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | -------------------------------------------------------------------------------- /src/remoteClient.cpp: -------------------------------------------------------------------------------- 1 | #include "remoteClient.h" 2 | #include "global.h" 3 | #include 4 | #include 5 | RemoteClient::RemoteClient(QTcpSocket *tcpSocket, QObject *parent) : QObject(parent), tcpSocket(tcpSocket), isWS(false) 6 | { 7 | connect(tcpSocket, &QTcpSocket::disconnected, this, [this](){ 8 | for (auto r: Global::dglabList) { 9 | if (locked.contains(r->getID())) { 10 | r->setDeviceRemoteLocked(false); 11 | r->clearCustomWaveAPI(DeviceStateEnum::DeviceChannel::CHANNEL_A); 12 | r->clearCustomWaveAPI(DeviceStateEnum::DeviceChannel::CHANNEL_B); 13 | } 14 | } 15 | delete this; 16 | }); 17 | connect(tcpSocket, &QTcpSocket::readyRead, this, [this](){ 18 | auto bytes = this->tcpSocket->readAll(); 19 | readyRead(bytes); 20 | }); 21 | } 22 | #ifndef __linux__ 23 | RemoteClient::RemoteClient(QWebSocket *wsSocket, QObject *parent) : QObject(parent), wsSocket(wsSocket), isWS(true) 24 | { 25 | connect(wsSocket, &QWebSocket::disconnected, this, [this](){ 26 | delete this; 27 | }); 28 | connect(wsSocket, &QWebSocket::binaryMessageReceived, this, [this](const QByteArray &message){ 29 | readyRead(message); 30 | }); 31 | } 32 | #endif 33 | 34 | RemoteClient::~RemoteClient() { 35 | isAuthed = false; 36 | if (isWS) { 37 | #ifndef __linux__ 38 | wsSocket->close(); 39 | #endif 40 | } else { 41 | tcpSocket->close(); 42 | } 43 | Global::remoteList.removeOne(this); 44 | } 45 | 46 | void RemoteClient::readyRead(QByteArray data) { 47 | auto dgRequest = com::github::opendglab::DGRequest(); 48 | dgRequest.ParseFromArray(data.data(), data.size()); 49 | auto response = com::github::opendglab::DGResponse(); 50 | response.set_version(1); 51 | if (dgRequest.event() == com::github::opendglab::DGEvent::PING) { 52 | return; 53 | } 54 | if (!isAuthed && dgRequest.event() != com::github::opendglab::DGEvent::CONNECT) { 55 | response.set_event(com::github::opendglab::DGEvent::CANTDOTHIS); 56 | response.set_error(com::github::opendglab::DGError::UNAUTHED); 57 | } else { 58 | switch (dgRequest.event()) { 59 | case com::github::opendglab::DGEvent::CONNECT: 60 | { 61 | if (!isAuthed) { 62 | const auto& conn = dgRequest.connect(); 63 | emit doAuth(this, QString::fromStdString(conn.appname()), QString::fromStdString(conn.uuid()), QString::fromStdString(conn.token())); 64 | } 65 | return; 66 | } 67 | case com::github::opendglab::GETDEVICE: 68 | { 69 | response.set_event(com::github::opendglab::DGEvent::GETDEVICE); 70 | auto deviceList = new com::github::opendglab::DGResponse_DGDeviceList(); 71 | for (auto r: Global::dglabList) { 72 | if(r->getState() == DeviceStateEnum::DeviceState::READY || r->getState() == DeviceStateEnum::DeviceState::READY_REMOTEMANAGED) { 73 | auto device = deviceList->add_devices(); 74 | device->set_id(r->getID().toStdString()); 75 | bool isLocked = r->getState() == DeviceStateEnum::DeviceState::READY_REMOTEMANAGED; 76 | device->set_islockedbyremote(isLocked); 77 | bool isLockedByMe = this->locked.contains(r->getID()); 78 | device->set_islockedbyme(isLockedByMe); 79 | } 80 | } 81 | response.set_allocated_devicelist(deviceList); 82 | break; 83 | } 84 | case com::github::opendglab::LOCKDEVICE: 85 | { 86 | const auto& device = dgRequest.device(); 87 | auto deviceId = QString::fromStdString(device.deviceid()); 88 | if (deviceId.isEmpty()) { 89 | response.set_event(com::github::opendglab::DGEvent::CANTDOTHIS); 90 | response.set_error(com::github::opendglab::DGError::UNKNOWN); 91 | } else { 92 | for (auto r: Global::dglabList) { 93 | if (r->getID() == deviceId) { 94 | switch (r->getState()) { 95 | case DeviceStateEnum::DeviceState::UNCONNECTED: 96 | case DeviceStateEnum::CONNECTED: 97 | response.set_event(com::github::opendglab::DGEvent::CANTDOTHIS); 98 | response.set_error(com::github::opendglab::DGError::DEVICEOFFLINE); 99 | break; 100 | case DeviceStateEnum::READY: 101 | { 102 | r->setDeviceRemoteLocked(true); 103 | locked.append(deviceId); 104 | response.set_event(com::github::opendglab::DGEvent::LOCKDEVICE); 105 | auto mDevice = new com::github::opendglab::DGResponse_DGDevice(); 106 | mDevice->set_id(deviceId.toStdString()); 107 | mDevice->set_islockedbyme(true); 108 | mDevice->set_islockedbyremote(true); 109 | response.set_allocated_device(mDevice); 110 | break; 111 | } 112 | case DeviceStateEnum::READY_REMOTEMANAGED: 113 | if (locked.contains(deviceId)) { 114 | response.set_event(com::github::opendglab::DGEvent::LOCKDEVICE); 115 | auto mDevice = new com::github::opendglab::DGResponse_DGDevice(); 116 | mDevice->set_id(deviceId.toStdString()); 117 | mDevice->set_islockedbyme(true); 118 | mDevice->set_islockedbyremote(true); 119 | response.set_allocated_device(mDevice); 120 | } else { 121 | response.set_event(com::github::opendglab::DGEvent::CANTDOTHIS); 122 | response.set_error(com::github::opendglab::DGError::DEVICENOTLOCKBYYOU); 123 | } 124 | break; 125 | } 126 | goto escape_route_lock; 127 | } 128 | } 129 | escape_route_lock: 130 | break; 131 | } 132 | } 133 | case com::github::opendglab::UNLOCKDEVICE: 134 | { 135 | const auto& device = dgRequest.device(); 136 | auto deviceId = QString::fromStdString(device.deviceid()); 137 | if (deviceId.isEmpty()) { 138 | response.set_event(com::github::opendglab::DGEvent::CANTDOTHIS); 139 | response.set_error(com::github::opendglab::DGError::UNKNOWN); 140 | } else { 141 | for (auto r: Global::dglabList) { 142 | if (r->getID() == deviceId) { 143 | switch (r->getState()) { 144 | case DeviceStateEnum::DeviceState::UNCONNECTED: 145 | case DeviceStateEnum::CONNECTED: 146 | response.set_event(com::github::opendglab::DGEvent::CANTDOTHIS); 147 | response.set_error(com::github::opendglab::DGError::DEVICEOFFLINE); 148 | break; 149 | case DeviceStateEnum::READY: 150 | { 151 | response.set_event(com::github::opendglab::DGEvent::CANTDOTHIS); 152 | response.set_error(com::github::opendglab::DGError::DEVICENOTLOCK); 153 | break; 154 | } 155 | case DeviceStateEnum::READY_REMOTEMANAGED: 156 | if (locked.contains(deviceId)) { 157 | r->setDeviceRemoteLocked(false); 158 | r->clearCustomWaveAPI(DeviceStateEnum::DeviceChannel::CHANNEL_A); 159 | r->clearCustomWaveAPI(DeviceStateEnum::DeviceChannel::CHANNEL_B); 160 | response.set_event(com::github::opendglab::DGEvent::UNLOCKDEVICE); 161 | auto mDevice = new com::github::opendglab::DGResponse_DGDevice(); 162 | mDevice->set_id(deviceId.toStdString()); 163 | mDevice->set_islockedbyme(false); 164 | mDevice->set_islockedbyremote(false); 165 | response.set_allocated_device(mDevice); 166 | locked.removeOne(deviceId); 167 | } else { 168 | response.set_event(com::github::opendglab::DGEvent::CANTDOTHIS); 169 | response.set_error(com::github::opendglab::DGError::DEVICENOTLOCKBYYOU); 170 | } 171 | break; 172 | } 173 | goto escape_route_unlock; 174 | } 175 | } 176 | escape_route_unlock: 177 | break; 178 | } 179 | } 180 | case com::github::opendglab::GETSTRENGTH: 181 | { 182 | const auto& device = dgRequest.device(); 183 | auto deviceId = QString::fromStdString(device.deviceid()); 184 | if (locked.contains(deviceId)) { 185 | for (auto r: Global::dglabList) { 186 | if (r->getID() == deviceId) { 187 | auto strength = new com::github::opendglab::DGResponse_DGDeviceStrength(); 188 | response.set_event(com::github::opendglab::DGEvent::GETSTRENGTH); 189 | strength->set_strengtha(r->getStrengthAPI(DeviceStateEnum::DeviceChannel::CHANNEL_A)); 190 | strength->set_strengthb(r->getStrengthAPI(DeviceStateEnum::DeviceChannel::CHANNEL_B)); 191 | response.set_allocated_strength(strength); 192 | break; 193 | } 194 | } 195 | } else { 196 | response.set_event(com::github::opendglab::DGEvent::CANTDOTHIS); 197 | response.set_error(com::github::opendglab::DGError::DEVICENOTLOCKBYYOU); 198 | } 199 | break; 200 | } 201 | case com::github::opendglab::SETSTRENGTH: 202 | { 203 | const auto& device = dgRequest.device(); 204 | auto deviceId = QString::fromStdString(device.deviceid()); 205 | const auto& strength = dgRequest.strength(); 206 | if (locked.contains(deviceId)) { 207 | for (auto r: Global::dglabList) { 208 | if (r->getID() == deviceId) { 209 | r->setStrengthAPI(strength.strengtha(), strength.strengthb()); 210 | return; 211 | } 212 | } 213 | } else { 214 | response.set_event(com::github::opendglab::DGEvent::CANTDOTHIS); 215 | response.set_error(com::github::opendglab::DGError::DEVICENOTLOCKBYYOU); 216 | } 217 | return; 218 | } 219 | case com::github::opendglab::GETWAVELIST: 220 | { 221 | response.set_event(com::github::opendglab::DGEvent::GETWAVELIST); 222 | auto* waveList = new com::github::opendglab::DGResponse_DGWaveList(); 223 | for (const auto& w: Global::basicWaveNameList) { 224 | waveList->add_wave(w.toStdString()); 225 | } 226 | for (const auto& w: Global::touchWaveNameList) { 227 | waveList->add_wave(w.toStdString()); 228 | } 229 | response.set_allocated_wavelist(waveList); 230 | break; 231 | } 232 | case com::github::opendglab::GETWAVE: 233 | { 234 | const auto& device = dgRequest.device(); 235 | auto deviceId = QString::fromStdString(device.deviceid()); 236 | const auto& strength = dgRequest.strength(); 237 | if (locked.contains(deviceId)) { 238 | for (auto r: Global::dglabList) { 239 | if (r->getID() == deviceId) { 240 | auto oDevice = new com::github::opendglab::DGResponse_DGDeviceID(); 241 | auto wave = new com::github::opendglab::DGResponse_DGWave(); 242 | response.set_event(com::github::opendglab::DGEvent::GETWAVE); 243 | switch (device.devicechannel()) { 244 | case com::github::opendglab::CHANNEL_A: 245 | { 246 | auto w = r->getWaveAPI(DeviceStateEnum::DeviceChannel::CHANNEL_A); 247 | wave->set_wave(w.toStdString()); 248 | oDevice->set_deviceid(deviceId.toStdString()); 249 | oDevice->set_devicechannel(com::github::opendglab::CHANNEL_A); 250 | break; 251 | } 252 | case com::github::opendglab::CHANNEL_B: 253 | { 254 | auto w = r->getWaveAPI(DeviceStateEnum::DeviceChannel::CHANNEL_B); 255 | wave->set_wave(w.toStdString()); 256 | oDevice->set_deviceid(deviceId.toStdString()); 257 | oDevice->set_devicechannel(com::github::opendglab::CHANNEL_B); 258 | break; 259 | } 260 | default: 261 | break; 262 | } 263 | break; 264 | } 265 | } 266 | } else { 267 | response.set_event(com::github::opendglab::DGEvent::CANTDOTHIS); 268 | response.set_error(com::github::opendglab::DGError::DEVICENOTLOCKBYYOU); 269 | } 270 | break; 271 | } 272 | case com::github::opendglab::SETWAVE: 273 | { 274 | const auto& device = dgRequest.device(); 275 | auto deviceId = QString::fromStdString(device.deviceid()); 276 | const auto& strength = dgRequest.strength(); 277 | auto waveName = QString::fromStdString(dgRequest.wave().wavename()); 278 | if (locked.contains(deviceId)) { 279 | for (auto r: Global::dglabList) { 280 | if (r->getID() == deviceId) { 281 | response.set_event(com::github::opendglab::DGEvent::SETWAVE); 282 | auto oDevice = new com::github::opendglab::DGResponse_DGDeviceID(); 283 | auto wave = new com::github::opendglab::DGResponse_DGWave(); 284 | switch (device.devicechannel()) { 285 | case com::github::opendglab::CHANNEL_A: 286 | { 287 | r->setWaveAPI(DeviceStateEnum::DeviceChannel::CHANNEL_A, waveName); 288 | auto w = r->getWaveAPI(DeviceStateEnum::DeviceChannel::CHANNEL_A); 289 | wave->set_wave(w.toStdString()); 290 | oDevice->set_deviceid(deviceId.toStdString()); 291 | oDevice->set_devicechannel(com::github::opendglab::CHANNEL_A); 292 | response.set_allocated_deviceid(oDevice); 293 | response.set_allocated_wavename(wave); 294 | break; 295 | } 296 | case com::github::opendglab::CHANNEL_B: 297 | { 298 | r->setWaveAPI(DeviceStateEnum::DeviceChannel::CHANNEL_B, waveName); 299 | auto w = r->getWaveAPI(DeviceStateEnum::DeviceChannel::CHANNEL_B); 300 | wave->set_wave(w.toStdString()); 301 | oDevice->set_deviceid(deviceId.toStdString()); 302 | oDevice->set_devicechannel(com::github::opendglab::CHANNEL_B); 303 | response.set_allocated_deviceid(oDevice); 304 | response.set_allocated_wavename(wave); 305 | break; 306 | } 307 | default: 308 | break; 309 | } 310 | break; 311 | } 312 | } 313 | } else { 314 | response.set_event(com::github::opendglab::DGEvent::CANTDOTHIS); 315 | response.set_error(com::github::opendglab::DGError::DEVICENOTLOCKBYYOU); 316 | } 317 | break; 318 | } 319 | case com::github::opendglab::CUSTOMWAVE: 320 | { 321 | const auto& device = dgRequest.device(); 322 | auto deviceId = QString::fromStdString(device.deviceid()); 323 | const auto& customWave = dgRequest.customwave(); 324 | if (locked.contains(deviceId)) { 325 | for (auto r: Global::dglabList) { 326 | if (r->getID() == deviceId) { 327 | switch (device.devicechannel()) { 328 | case com::github::opendglab::CHANNEL_A: 329 | { 330 | for (const auto& c: customWave) { 331 | if(c.bytes().size() == 3) { 332 | auto array = new QByteArray(3, Qt::Initialization::Uninitialized); 333 | const auto& cb = c.bytes(); 334 | array->data()[0] = cb[0]; 335 | array->data()[1] = cb[1]; 336 | array->data()[2] = cb[2]; 337 | r->addCustomWaveAPI(DeviceStateEnum::DeviceChannel::CHANNEL_A, *array); 338 | } 339 | } 340 | break; 341 | } 342 | case com::github::opendglab::CHANNEL_B: 343 | { 344 | for (const auto& c: customWave) { 345 | if(c.bytes().size() == 3) { 346 | auto array = new QByteArray(3, Qt::Initialization::Uninitialized); 347 | const auto& cb = c.bytes(); 348 | array->data()[0] = cb[0]; 349 | array->data()[1] = cb[1]; 350 | array->data()[2] = cb[2]; 351 | r->addCustomWaveAPI(DeviceStateEnum::DeviceChannel::CHANNEL_B, *array); 352 | } 353 | } 354 | break; 355 | } 356 | default: 357 | break; 358 | } 359 | return; 360 | } 361 | } 362 | } else { 363 | response.set_event(com::github::opendglab::DGEvent::CANTDOTHIS); 364 | response.set_error(com::github::opendglab::DGError::DEVICENOTLOCKBYYOU); 365 | } 366 | break; 367 | } 368 | case com::github::opendglab::CLEARCUSTOM: 369 | { 370 | const auto& device = dgRequest.device(); 371 | auto deviceId = QString::fromStdString(device.deviceid()); 372 | if (locked.contains(deviceId)) { 373 | for (auto r: Global::dglabList) { 374 | if (r->getID() == deviceId) { 375 | switch (device.devicechannel()) { 376 | case com::github::opendglab::CHANNEL_A: 377 | r->clearCustomWaveAPI(DeviceStateEnum::DeviceChannel::CHANNEL_A); 378 | break; 379 | case com::github::opendglab::CHANNEL_B: 380 | r->clearCustomWaveAPI(DeviceStateEnum::DeviceChannel::CHANNEL_B); 381 | break; 382 | default: 383 | break; 384 | } 385 | return; 386 | } 387 | } 388 | } else { 389 | response.set_event(com::github::opendglab::DGEvent::CANTDOTHIS); 390 | response.set_error(com::github::opendglab::DGError::DEVICENOTLOCKBYYOU); 391 | } 392 | break; 393 | } 394 | default: 395 | return; 396 | } 397 | } 398 | QByteArray byteArray(response.SerializeAsString().c_str(), response.ByteSizeLong()); 399 | if(isWS) { 400 | #ifndef __linux__ 401 | wsSocket->sendBinaryMessage(byteArray); 402 | #endif 403 | } else { 404 | tcpSocket->write(byteArray); 405 | } 406 | } 407 | 408 | void RemoteClient::sendConnected(const QString& _uuid, const QString& token, bool isAuth) { 409 | auto response = com::github::opendglab::DGResponse(); 410 | response.set_version(1); 411 | response.set_event(com::github::opendglab::DGEvent::CONNECT); 412 | auto *conn = new com::github::opendglab::DGResponse_DGConnect(); 413 | conn->set_token(token.toStdString()); 414 | response.set_allocated_connect(conn); 415 | QByteArray byteArray(response.SerializeAsString().c_str(), response.ByteSizeLong()); 416 | this->isAuthed = isAuth; 417 | this->uuid = _uuid; 418 | if(isWS) { 419 | #ifndef __linux__ 420 | wsSocket->sendBinaryMessage(byteArray); 421 | #endif 422 | } else { 423 | tcpSocket->write(byteArray); 424 | } 425 | if (!isAuth) { 426 | close(); 427 | } 428 | } 429 | 430 | void RemoteClient::close() { 431 | if (isWS) { 432 | #ifndef __linux__ 433 | wsSocket->close(); 434 | #endif 435 | } else { 436 | tcpSocket->close(); 437 | } 438 | } 439 | 440 | QString RemoteClient::getUuid() { 441 | return uuid; 442 | } 443 | 444 | void RemoteClient::sendDeviceReset(const QString &deviceId) { 445 | if (locked.contains(deviceId)) { 446 | auto response = com::github::opendglab::DGResponse(); 447 | response.set_version(1); 448 | response.set_event(com::github::opendglab::DGEvent::DEVICERESET); 449 | auto sDeviceId = new com::github::opendglab::DGResponse_DGDeviceID(); 450 | sDeviceId->set_deviceid(deviceId.toStdString()); 451 | response.set_allocated_deviceid(sDeviceId); 452 | QByteArray byteArray(response.SerializeAsString().c_str(), response.ByteSizeLong()); 453 | if(isWS) { 454 | #ifndef __linux__ 455 | wsSocket->sendBinaryMessage(byteArray); 456 | #endif 457 | } else { 458 | tcpSocket->write(byteArray); 459 | } 460 | locked.removeOne(deviceId); 461 | } 462 | } 463 | 464 | void RemoteClient::sendPowerUpdate(const QString &deviceId, int channel_a, int channel_b) { 465 | if (locked.contains(deviceId)) { 466 | auto response = com::github::opendglab::DGResponse(); 467 | response.set_version(1); 468 | response.set_event(com::github::opendglab::DGEvent::GETSTRENGTH); 469 | auto sDeviceId = new com::github::opendglab::DGResponse_DGDeviceID(); 470 | sDeviceId->set_deviceid(deviceId.toStdString()); 471 | response.set_allocated_deviceid(sDeviceId); 472 | auto strength = new com::github::opendglab::DGResponse_DGDeviceStrength(); 473 | strength->set_strengtha(channel_a); 474 | strength->set_strengthb(channel_b); 475 | response.set_allocated_strength(strength); 476 | QByteArray byteArray(response.SerializeAsString().c_str(), response.ByteSizeLong()); 477 | if(isWS) { 478 | #ifndef __linux__ 479 | wsSocket->sendBinaryMessage(byteArray); 480 | #endif 481 | } else { 482 | tcpSocket->write(byteArray); 483 | } 484 | } 485 | } 486 | 487 | void RemoteClient::sendPing() { 488 | if (isAuthed) { 489 | auto response = com::github::opendglab::DGResponse(); 490 | response.set_version(1); 491 | response.set_event(com::github::opendglab::DGEvent::PING); 492 | QByteArray byteArray(response.SerializeAsString().c_str(), response.ByteSizeLong()); 493 | if(isWS) { 494 | #ifndef __linux__ 495 | wsSocket->sendBinaryMessage(byteArray); 496 | #endif 497 | } else { 498 | tcpSocket->write(byteArray); 499 | } 500 | } 501 | } 502 | -------------------------------------------------------------------------------- /src/dglabDevice.cpp: -------------------------------------------------------------------------------- 1 | #include "dglabDevice.h" 2 | #include "global.h" 3 | #include 4 | DGLabDevice::DGLabDevice(const QBluetoothDeviceInfo &_deviceInfo, QObject *parent) : 5 | QObject(parent), 6 | deviceInfo(_deviceInfo), 7 | openDgLab(libopendglab_symbols()->kotlin.root.OpenDGLab.OpenDGLab()) 8 | { 9 | controller = QLowEnergyController::createCentral(deviceInfo, this); 10 | controller->setRemoteAddressType(QLowEnergyController::PublicAddress); 11 | id = QString(QCryptographicHash::hash(getDeviceAddressOrUuid().toUtf8(),QCryptographicHash::Sha256).toHex()).right(6); 12 | uiDeviceItem = new DeviceItem(getID(), getDeviceAddressOrUuid(), Global::deviceName); 13 | uiDeviceOperator = new DeviceOperator(getID()); 14 | uiDeviceOperator->setEnabled(false); 15 | connect(controller, &QLowEnergyController::connected, this, &DGLabDevice::deviceConnected); 16 | connect(controller, &QLowEnergyController::disconnected, this, &DGLabDevice::deviceDisconnected); 17 | connect(this, &DGLabDevice::powerUpdate, uiDeviceOperator, &DeviceOperator::setPower); 18 | connect(uiDeviceOperator, &DeviceOperator::changeWave, this, &DGLabDevice::setWave); 19 | connect(uiDeviceOperator, &DeviceOperator::startBoost, this, &DGLabDevice::startChannelBoost); 20 | connect(uiDeviceOperator, &DeviceOperator::stopBoost, this, &DGLabDevice::stopChannelBoost); 21 | } 22 | 23 | DGLabDevice::~DGLabDevice() 24 | { 25 | if (waveSenderTimerThread) { 26 | waveSenderTimerThread->quit(); 27 | //waveSenderTimerThread->wait(); 28 | waveSenderTimerThread = nullptr; 29 | } 30 | SKIPNEXTQWARNING(SKIPNEXTERROR(controller->disconnectFromDevice())); 31 | } 32 | 33 | QString DGLabDevice::getID() 34 | { 35 | return id; 36 | } 37 | 38 | QString DGLabDevice::getDeviceAddressOrUuid() 39 | { 40 | QString address = 41 | #ifdef Q_OS_MACOS 42 | deviceInfo.deviceUuid() 43 | #else 44 | deviceInfo.address() 45 | #endif 46 | .toString(); 47 | return address; 48 | } 49 | 50 | QLowEnergyController* DGLabDevice::getBLEController() 51 | { 52 | return controller; 53 | } 54 | 55 | void DGLabDevice::startGlobalBoost(int boost) 56 | { 57 | if (globalBoost != 0) return; 58 | globalBoost = boost; 59 | const QLowEnergyCharacteristic abpower = eStimService->characteristic(QBluetoothUuid(QUuid(Global::DGLabServices::EStimStatus::Characteristic::abpower))); 60 | libopendglab_ExportedSymbols* libopendglab = libopendglab_symbols(); 61 | auto eStim = libopendglab->kotlin.root.OpenDGLab.get_eStimStatus(openDgLab); 62 | auto power = libopendglab->kotlin.root.OpenDGLab.EStimStatus.get_abPower(eStim); 63 | libopendglab_kref_OpenDGLab_WriteBLE dataKt; 64 | auto afboostA = powerA + boost; 65 | auto afboostB = powerB + boost; 66 | if (afboostA > 274) afboostA = 274; 67 | if (afboostB > 274) afboostB = 274; 68 | dataKt = libopendglab->kotlin.root.OpenDGLab.EStimStatus.ABPower.setABPower(power, afboostA, afboostB); 69 | auto dataBytes = libopendglab->kotlin.root.OpenDGLab.WriteBLE.get_data(dataKt); 70 | auto dataSize = libopendglab->kotlin.root.getByteArraySize(dataBytes); 71 | auto data = QByteArray(dataSize, Qt::Initialization::Uninitialized); 72 | libopendglab->kotlin.root.toNativeByteArray(data.data(), dataBytes); 73 | eStimService->writeCharacteristic(abpower, data, QLowEnergyService::WriteWithoutResponse); 74 | } 75 | 76 | void DGLabDevice::stopGlobalBoost() 77 | { 78 | const QLowEnergyCharacteristic abpower = eStimService->characteristic(QBluetoothUuid(QUuid(Global::DGLabServices::EStimStatus::Characteristic::abpower))); 79 | libopendglab_ExportedSymbols* libopendglab = libopendglab_symbols(); 80 | auto eStim = libopendglab->kotlin.root.OpenDGLab.get_eStimStatus(openDgLab); 81 | auto power = libopendglab->kotlin.root.OpenDGLab.EStimStatus.get_abPower(eStim); 82 | libopendglab_kref_OpenDGLab_WriteBLE dataKt; 83 | auto afboostA = powerA - globalBoost; 84 | auto afboostB = powerB - globalBoost; 85 | if (afboostA < 0) afboostA = 0; 86 | if (afboostB < 0) afboostB = 0; 87 | dataKt = libopendglab->kotlin.root.OpenDGLab.EStimStatus.ABPower.setABPower(power, afboostA, afboostB); 88 | auto dataBytes = libopendglab->kotlin.root.OpenDGLab.WriteBLE.get_data(dataKt); 89 | auto dataSize = libopendglab->kotlin.root.getByteArraySize(dataBytes); 90 | auto data = QByteArray(dataSize, Qt::Initialization::Uninitialized); 91 | libopendglab->kotlin.root.toNativeByteArray(data.data(), dataBytes); 92 | eStimService->writeCharacteristic(abpower, data, QLowEnergyService::WriteWithoutResponse); 93 | globalBoost = 0; 94 | } 95 | 96 | DeviceStateEnum::DeviceState DGLabDevice::getState() 97 | { 98 | return uiDeviceItem->getState(); 99 | } 100 | 101 | void DGLabDevice::waveSender() 102 | { 103 | if (uiDeviceItem->getState() == DeviceStateEnum::DeviceState::READY || uiDeviceItem->getState() == DeviceStateEnum::DeviceState::READY_REMOTEMANAGED) { 104 | libopendglab_ExportedSymbols* libopendglab = libopendglab_symbols(); 105 | auto eStimStatus = libopendglab->kotlin.root.OpenDGLab.get_eStimStatus(openDgLab); 106 | auto wave = libopendglab->kotlin.root.OpenDGLab.EStimStatus.get_wave(eStimStatus); 107 | if (powerA > 0) { 108 | //WaveA 109 | auto waveCenterA = libopendglab->kotlin.root.OpenDGLab.EStimStatus.Wave.getWaveCenterA(wave); 110 | auto hasWave = libopendglab->kotlin.root.WaveCenter.toDGWaveGen(waveCenterA); 111 | if (QString::fromUtf8(hasWave) == "") { 112 | //Custom Send 113 | QMutexLocker locker(&customWaveAMutex); 114 | if (!customWaveA.isEmpty()) { 115 | auto qBytes = customWaveA.takeFirst(); 116 | const QLowEnergyCharacteristic wave = eStimService->characteristic(QBluetoothUuid(QUuid(Global::DGLabServices::EStimStatus::Characteristic::waveA))); 117 | eStimService->writeCharacteristic(wave, qBytes, QLowEnergyService::WriteWithoutResponse); 118 | } 119 | } else { 120 | auto waveBytes = libopendglab->kotlin.root.WaveCenter.waveTick(waveCenterA); 121 | auto waveBytesSize = libopendglab->kotlin.root.getByteArraySize(waveBytes); 122 | auto qBytes = QByteArray(waveBytesSize, Qt::Initialization::Uninitialized); 123 | libopendglab->kotlin.root.toNativeByteArray(qBytes.data(), waveBytes); 124 | const QLowEnergyCharacteristic wave = eStimService->characteristic(QBluetoothUuid(QUuid(Global::DGLabServices::EStimStatus::Characteristic::waveA))); 125 | eStimService->writeCharacteristic(wave, qBytes, QLowEnergyService::WriteWithoutResponse); 126 | } 127 | libopendglab->DisposeString(hasWave); 128 | } else { 129 | auto waveCenterA = libopendglab->kotlin.root.OpenDGLab.EStimStatus.Wave.getWaveCenterA(wave); 130 | auto hasWave = libopendglab->kotlin.root.WaveCenter.toDGWaveGen(waveCenterA); 131 | if (QString::fromUtf8(hasWave) == "") { 132 | QMutexLocker locker(&customWaveAMutex); 133 | if (!customWaveA.isEmpty()) customWaveA.empty(); 134 | } 135 | } 136 | if (powerB > 0){ 137 | //WaveB 138 | auto waveCenterB = libopendglab->kotlin.root.OpenDGLab.EStimStatus.Wave.getWaveCenterB(wave); 139 | auto hasWave = libopendglab->kotlin.root.WaveCenter.toDGWaveGen(waveCenterB); 140 | if (QString::fromUtf8(hasWave) == "") { 141 | QMutexLocker locker(&customWaveBMutex); 142 | if (!customWaveB.isEmpty()) { 143 | auto qBytes = customWaveB.takeFirst(); 144 | const QLowEnergyCharacteristic wave = eStimService->characteristic(QBluetoothUuid(QUuid(Global::DGLabServices::EStimStatus::Characteristic::waveB))); 145 | eStimService->writeCharacteristic(wave, qBytes, QLowEnergyService::WriteWithoutResponse); 146 | } 147 | } else { 148 | auto waveBytes = libopendglab->kotlin.root.WaveCenter.waveTick(waveCenterB); 149 | auto waveBytesSize = libopendglab->kotlin.root.getByteArraySize(waveBytes); 150 | auto qBytes = QByteArray(waveBytesSize, Qt::Initialization::Uninitialized); 151 | libopendglab->kotlin.root.toNativeByteArray(qBytes.data(), waveBytes); 152 | const QLowEnergyCharacteristic wave = eStimService->characteristic(QBluetoothUuid(QUuid(Global::DGLabServices::EStimStatus::Characteristic::waveB))); 153 | eStimService->writeCharacteristic(wave, qBytes, QLowEnergyService::WriteWithoutResponse); 154 | } 155 | libopendglab->DisposeString(hasWave); 156 | } else { 157 | auto waveCenterB = libopendglab->kotlin.root.OpenDGLab.EStimStatus.Wave.getWaveCenterB(wave); 158 | auto hasWave = libopendglab->kotlin.root.WaveCenter.toDGWaveGen(waveCenterB); 159 | if (QString::fromUtf8(hasWave) == "") { 160 | QMutexLocker locker(&customWaveBMutex); 161 | if (!customWaveB.isEmpty()) customWaveB.empty(); 162 | } 163 | } 164 | } 165 | } 166 | 167 | void DGLabDevice::setWave(DeviceStateEnum::DeviceChannel channel, QString value) 168 | { 169 | libopendglab_ExportedSymbols* libopendglab = libopendglab_symbols(); 170 | auto eStimState = libopendglab->kotlin.root.OpenDGLab.get_eStimStatus(openDgLab); 171 | auto wave = libopendglab->kotlin.root.OpenDGLab.EStimStatus.get_wave(eStimState); 172 | switch (channel) { 173 | case DeviceStateEnum::DeviceChannel::CHANNEL_A: 174 | { 175 | auto waveCenter = libopendglab->kotlin.root.OpenDGLab.EStimStatus.Wave.getWaveCenterA(wave); 176 | auto cWave = Global::basicWaveNameList.contains(value); 177 | if (cWave) { 178 | auto waveA = libopendglab->kotlin.root.WaveCenter.Companion.getBasicWave(libopendglab->kotlin.root.WaveCenter.Companion._instance(), value.toUtf8().data()); 179 | auto convertWaveA = libopendglab->kotlin.root.convertBasicWaveDataToBasicWave(waveA); 180 | libopendglab->kotlin.root.WaveCenter.selectWave(waveCenter, convertWaveA); 181 | } else { 182 | cWave = Global::touchWaveNameList.contains(value); 183 | if (cWave) { 184 | auto waveA = libopendglab->kotlin.root.WaveCenter.Companion.getTouchWave(libopendglab->kotlin.root.WaveCenter.Companion._instance(), value.toUtf8().data()); 185 | auto convertWaveA = libopendglab->kotlin.root.convertTouchWaveDataToBasicWave(waveA); 186 | libopendglab->kotlin.root.WaveCenter.selectWave(waveCenter, convertWaveA); 187 | } else { 188 | libopendglab->kotlin.root.WaveCenter.selectWave(waveCenter, libopendglab->kotlin.root.createNullBasicWave()); 189 | } 190 | } 191 | } 192 | break; 193 | case DeviceStateEnum::DeviceChannel::CHANNEL_B: 194 | { 195 | auto waveCenter = libopendglab->kotlin.root.OpenDGLab.EStimStatus.Wave.getWaveCenterB(wave); 196 | auto cWave = Global::basicWaveNameList.contains(value); 197 | if (cWave) { 198 | auto waveB = libopendglab->kotlin.root.WaveCenter.Companion.getBasicWave(libopendglab->kotlin.root.WaveCenter.Companion._instance(), value.toUtf8().data()); 199 | auto convertWaveB = libopendglab->kotlin.root.convertBasicWaveDataToBasicWave(waveB); 200 | libopendglab->kotlin.root.WaveCenter.selectWave(waveCenter, convertWaveB); 201 | } else { 202 | cWave = Global::touchWaveNameList.contains(value); 203 | if (cWave) { 204 | auto waveB = libopendglab->kotlin.root.WaveCenter.Companion.getTouchWave(libopendglab->kotlin.root.WaveCenter.Companion._instance(), value.toUtf8().data()); 205 | auto convertWaveB = libopendglab->kotlin.root.convertTouchWaveDataToBasicWave(waveB); 206 | libopendglab->kotlin.root.WaveCenter.selectWave(waveCenter, convertWaveB); 207 | } else { 208 | libopendglab->kotlin.root.WaveCenter.selectWave(waveCenter, libopendglab->kotlin.root.createNullBasicWave()); 209 | } 210 | } 211 | } 212 | break; 213 | } 214 | } 215 | 216 | void DGLabDevice::startChannelBoost(DeviceStateEnum::DeviceChannel channel, int boost) 217 | { 218 | const QLowEnergyCharacteristic abpower = eStimService->characteristic(QBluetoothUuid(QUuid(Global::DGLabServices::EStimStatus::Characteristic::abpower))); 219 | libopendglab_ExportedSymbols* libopendglab = libopendglab_symbols(); 220 | auto eStim = libopendglab->kotlin.root.OpenDGLab.get_eStimStatus(openDgLab); 221 | auto power = libopendglab->kotlin.root.OpenDGLab.EStimStatus.get_abPower(eStim); 222 | libopendglab_kref_OpenDGLab_WriteBLE dataKt; 223 | int afboost; 224 | switch (channel) { 225 | case DeviceStateEnum::DeviceChannel::CHANNEL_A: 226 | boostA = boost; 227 | afboost = powerA + boost; 228 | if (afboost > 274) afboost = 274; 229 | dataKt = libopendglab->kotlin.root.OpenDGLab.EStimStatus.ABPower.setABPower(power, afboost, powerB); 230 | break; 231 | case DeviceStateEnum::DeviceChannel::CHANNEL_B: 232 | boostB = boost; 233 | afboost = powerB + boost; 234 | if (afboost > 274) afboost = 274; 235 | dataKt = libopendglab->kotlin.root.OpenDGLab.EStimStatus.ABPower.setABPower(power, powerA, afboost); 236 | break; 237 | } 238 | auto dataBytes = libopendglab->kotlin.root.OpenDGLab.WriteBLE.get_data(dataKt); 239 | auto dataSize = libopendglab->kotlin.root.getByteArraySize(dataBytes); 240 | auto data = QByteArray(dataSize, Qt::Initialization::Uninitialized); 241 | libopendglab->kotlin.root.toNativeByteArray(data.data(), dataBytes); 242 | eStimService->writeCharacteristic(abpower, data, QLowEnergyService::WriteWithoutResponse); 243 | } 244 | 245 | void DGLabDevice::stopChannelBoost(DeviceStateEnum::DeviceChannel channel, int boost) 246 | { 247 | const QLowEnergyCharacteristic abpower = eStimService->characteristic(QBluetoothUuid(QUuid(Global::DGLabServices::EStimStatus::Characteristic::abpower))); 248 | libopendglab_ExportedSymbols* libopendglab = libopendglab_symbols(); 249 | auto eStim = libopendglab->kotlin.root.OpenDGLab.get_eStimStatus(openDgLab); 250 | auto power = libopendglab->kotlin.root.OpenDGLab.EStimStatus.get_abPower(eStim); 251 | libopendglab_kref_OpenDGLab_WriteBLE dataKt; 252 | int afboost; 253 | switch (channel) { 254 | case DeviceStateEnum::DeviceChannel::CHANNEL_A: 255 | boostA = 0; 256 | afboost = powerA - boost; 257 | if (afboost < 0) afboost = 0; 258 | dataKt = libopendglab->kotlin.root.OpenDGLab.EStimStatus.ABPower.setABPower(power, afboost, powerB); 259 | break; 260 | case DeviceStateEnum::DeviceChannel::CHANNEL_B: 261 | boostB = 0; 262 | afboost = powerB - boost; 263 | if (afboost < 0) afboost = 0; 264 | dataKt = libopendglab->kotlin.root.OpenDGLab.EStimStatus.ABPower.setABPower(power, powerA, afboost); 265 | break; 266 | } 267 | auto dataBytes = libopendglab->kotlin.root.OpenDGLab.WriteBLE.get_data(dataKt); 268 | auto dataSize = libopendglab->kotlin.root.getByteArraySize(dataBytes); 269 | auto data = QByteArray(dataSize, Qt::Initialization::Uninitialized); 270 | libopendglab->kotlin.root.toNativeByteArray(data.data(), dataBytes); 271 | eStimService->writeCharacteristic(abpower, data, QLowEnergyService::WriteWithoutResponse); 272 | } 273 | 274 | bool DGLabDevice::isShownOnUi() 275 | { 276 | return shownOnUi; 277 | } 278 | void DGLabDevice::setShownOnUi() 279 | { 280 | shownOnUi = true; 281 | } 282 | DeviceItem* DGLabDevice::getUiDeviceItem() { 283 | return uiDeviceItem; 284 | } 285 | DeviceOperator* DGLabDevice::getUiDeviceOperator() { 286 | return uiDeviceOperator; 287 | } 288 | 289 | void DGLabDevice::deviceConnected() 290 | { 291 | uiDeviceItem->changeState(DeviceStateEnum::DeviceState::CONNECTED); 292 | uiDeviceItem->changeBattery(-1); 293 | uiDeviceItem->update(); 294 | waveSenderTimerThread = new WaveSenderTimerThread(this); 295 | connect(controller, &QLowEnergyController::discoveryFinished, this, &DGLabDevice::deviceServiceDiscoverFinished); 296 | this->controller->discoverServices(); 297 | } 298 | void DGLabDevice::deviceDisconnected() 299 | { 300 | if (waveSenderTimerThread) { 301 | waveSenderTimerThread->quit(); 302 | //waveSenderTimerThread->wait(); 303 | waveSenderTimerThread = nullptr; 304 | } 305 | if (uiDeviceItem) { 306 | uiDeviceItem->changeState(DeviceStateEnum::DeviceState::UNCONNECTED); 307 | uiDeviceItem->changeBattery(0); 308 | uiDeviceItem->update(); 309 | } 310 | if (remoteLocked) { 311 | // 通知锁定客户端设备已经释放 312 | for (auto r: Global::remoteList) { 313 | r->sendDeviceReset(getID()); 314 | } 315 | } 316 | remoteLocked = false; 317 | uiDeviceOperator->setAutoChange(DeviceStateEnum::DeviceChannel::CHANNEL_A, false); 318 | uiDeviceOperator->setAutoChange(DeviceStateEnum::DeviceChannel::CHANNEL_B, false); 319 | } 320 | void DGLabDevice::deviceServiceDiscoverFinished() 321 | { 322 | batteryService = controller->createServiceObject(QBluetoothUuid(QUuid(Global::DGLabServices::DeviceStatus::service)),this); 323 | if (!batteryService) { 324 | qDebug() << "Not Found Battery Service"; 325 | return; 326 | } 327 | connect(batteryService, qOverload(&QLowEnergyService::stateChanged), this, &DGLabDevice::deviceBatteryServiceStateChanged); 328 | connect(batteryService, qOverload(&QLowEnergyService::characteristicChanged), this, &DGLabDevice::deviceBatteryCharacteristicArrived); 329 | connect(batteryService, qOverload(&QLowEnergyService::characteristicRead), this, &DGLabDevice::deviceBatteryCharacteristicArrived); 330 | eStimService = controller->createServiceObject(QBluetoothUuid(QUuid(Global::DGLabServices::EStimStatus::service)),this); 331 | if (!eStimService) { 332 | qDebug() << "Not Found EStim Service"; 333 | return; 334 | } 335 | connect(eStimService, qOverload(&QLowEnergyService::stateChanged), this, &DGLabDevice::deviceEStimServiceStateChanged); 336 | connect(eStimService, qOverload(&QLowEnergyService::characteristicChanged), this, &DGLabDevice::deviceEStimCharacteristicArrived); 337 | connect(eStimService, qOverload(&QLowEnergyService::characteristicRead), this, &DGLabDevice::deviceEStimCharacteristicArrived); 338 | 339 | //Qt BLE Bug Workaround https://bugreports.qt.io/browse/QTBUG-78488 340 | QTimer::singleShot(0, [this] () { 341 | batteryService->discoverDetails(); 342 | eStimService->discoverDetails(); 343 | }); 344 | 345 | //Connect UI Event 346 | connect(uiDeviceOperator, &DeviceOperator::changePower, this, &DGLabDevice::changePower); 347 | } 348 | void DGLabDevice::changePower(int levelA, int levelB) { 349 | if (levelA < 0) levelA = 0; 350 | if (levelB < 0) levelB = 0; 351 | if (levelA > 274) levelA = 274; 352 | if (levelB > 274) levelB = 274; 353 | const QLowEnergyCharacteristic abpower = eStimService->characteristic(QBluetoothUuid(QUuid(Global::DGLabServices::EStimStatus::Characteristic::abpower))); 354 | libopendglab_ExportedSymbols* libopendglab = libopendglab_symbols(); 355 | auto eStim = libopendglab->kotlin.root.OpenDGLab.get_eStimStatus(openDgLab); 356 | auto power = libopendglab->kotlin.root.OpenDGLab.EStimStatus.get_abPower(eStim); 357 | auto dataKt = libopendglab->kotlin.root.OpenDGLab.EStimStatus.ABPower.setABPower(power, levelA, levelB); 358 | auto dataBytes = libopendglab->kotlin.root.OpenDGLab.WriteBLE.get_data(dataKt); 359 | auto dataSize = libopendglab->kotlin.root.getByteArraySize(dataBytes); 360 | auto data = QByteArray(dataSize, Qt::Initialization::Uninitialized); 361 | libopendglab->kotlin.root.toNativeByteArray(data.data(), dataBytes); 362 | eStimService->writeCharacteristic(abpower, data, QLowEnergyService::WriteWithoutResponse); 363 | } 364 | void DGLabDevice::deviceBatteryServiceStateChanged(QLowEnergyService::ServiceState state){ 365 | if (!batteryService) return; 366 | if (state == QLowEnergyService::ServiceDiscovered) { 367 | auto uuid = QBluetoothUuid( 368 | QUuid( 369 | Global::DGLabServices::DeviceStatus::Characteristic::electric 370 | ) 371 | ); 372 | const QLowEnergyCharacteristic battery = batteryService->characteristic(uuid); 373 | if (!battery.isValid()){ 374 | qDebug() << "Battery Not Found"; 375 | return; 376 | } 377 | const QLowEnergyDescriptor m_notificationDescBattery = battery.descriptor( 378 | QBluetoothUuid::ClientCharacteristicConfiguration); 379 | if (m_notificationDescBattery.isValid()) { 380 | // enable notification 381 | batteryService->writeDescriptor(m_notificationDescBattery, QByteArray::fromHex("0100")); 382 | } 383 | batteryService->readCharacteristic(battery); 384 | } 385 | } 386 | void DGLabDevice::deviceEStimServiceStateChanged(QLowEnergyService::ServiceState state){ 387 | if (!eStimService) return; 388 | if (state == QLowEnergyService::ServiceDiscovered) { 389 | auto uuidSetup = QBluetoothUuid( 390 | QUuid( 391 | Global::DGLabServices::EStimStatus::Characteristic::setup 392 | ) 393 | ); 394 | const QLowEnergyCharacteristic setup = eStimService->characteristic(uuidSetup); 395 | if (!setup.isValid()){ 396 | qDebug() << "Setup Not Found"; 397 | return; 398 | } 399 | eStimService->readCharacteristic(setup); 400 | 401 | auto uuidABPower = QBluetoothUuid( 402 | QUuid( 403 | Global::DGLabServices::EStimStatus::Characteristic::abpower 404 | ) 405 | ); 406 | const QLowEnergyCharacteristic abpower = eStimService->characteristic(uuidABPower); 407 | if (!abpower.isValid()){ 408 | qDebug() << "ABPower Not Found"; 409 | return; 410 | } 411 | const QLowEnergyDescriptor abPowerDescriptor = abpower.descriptor( 412 | QBluetoothUuid::ClientCharacteristicConfiguration); 413 | if (abPowerDescriptor.isValid()) { 414 | eStimService->writeDescriptor(abPowerDescriptor, QByteArray::fromHex("0100")); 415 | } 416 | } 417 | } 418 | void DGLabDevice::deviceBatteryCharacteristicArrived(const QLowEnergyCharacteristic&, const QByteArray & array) { 419 | libopendglab_ExportedSymbols* libopendglab = libopendglab_symbols(); 420 | auto batteryValue = array.data(); 421 | auto deviceState = libopendglab->kotlin.root.OpenDGLab.get_deviceStatus(openDgLab); 422 | auto electric = libopendglab->kotlin.root.OpenDGLab.DeviceStatus.get_electric(deviceState); 423 | auto barray = libopendglab->kotlin.root.createByteArray((void *)batteryValue, array.size()); 424 | auto level = libopendglab->kotlin.root.OpenDGLab.DeviceStatus.Electric.onChange(electric, barray); 425 | uiDeviceItem->changeBattery(level); 426 | uiDeviceItem->update(); 427 | } 428 | void DGLabDevice::deviceEStimCharacteristicArrived(const QLowEnergyCharacteristic& characteristic, const QByteArray & array) { 429 | libopendglab_ExportedSymbols* libopendglab = libopendglab_symbols(); 430 | if (characteristic.uuid() == QBluetoothUuid(QUuid(Global::DGLabServices::EStimStatus::Characteristic::setup))) { 431 | auto setupValue = array.data(); 432 | auto eStimState = libopendglab->kotlin.root.OpenDGLab.get_eStimStatus(openDgLab); 433 | auto setup = libopendglab->kotlin.root.OpenDGLab.EStimStatus.get_setup(eStimState); 434 | auto barray = libopendglab->kotlin.root.createByteArray((void *)setupValue, array.size()); 435 | libopendglab->kotlin.root.OpenDGLab.EStimStatus.Setup.read(setup, barray); 436 | uiDeviceOperator->setEnabled(true); 437 | uiDeviceOperator->update(); 438 | uiDeviceItem->changeState(DeviceStateEnum::DeviceState::READY); 439 | uiDeviceItem->update(); 440 | auto wave = libopendglab->kotlin.root.OpenDGLab.EStimStatus.get_wave(eStimState); 441 | auto waveCenterA = libopendglab->kotlin.root.OpenDGLab.EStimStatus.Wave.getWaveCenterA(wave); 442 | auto waveCenterB = libopendglab->kotlin.root.OpenDGLab.EStimStatus.Wave.getWaveCenterB(wave); 443 | auto sWaveA = uiDeviceOperator->getWaveA(); 444 | auto sWaveB = uiDeviceOperator->getWaveB(); 445 | auto cWave = Global::basicWaveNameList.contains(sWaveA); 446 | if (cWave) { 447 | auto waveA = libopendglab->kotlin.root.WaveCenter.Companion.getBasicWave(libopendglab->kotlin.root.WaveCenter.Companion._instance(), sWaveA.toUtf8().data()); 448 | auto convertWaveA = libopendglab->kotlin.root.convertBasicWaveDataToBasicWave(waveA); 449 | libopendglab->kotlin.root.WaveCenter.selectWave(waveCenterA, convertWaveA); 450 | } else { 451 | cWave = Global::touchWaveNameList.contains(sWaveA); 452 | if (cWave) { 453 | auto waveA = libopendglab->kotlin.root.WaveCenter.Companion.getTouchWave(libopendglab->kotlin.root.WaveCenter.Companion._instance(), sWaveA.toUtf8().data()); 454 | auto convertWaveA = libopendglab->kotlin.root.convertTouchWaveDataToBasicWave(waveA); 455 | libopendglab->kotlin.root.WaveCenter.selectWave(waveCenterA, convertWaveA); 456 | } 457 | } 458 | cWave = Global::basicWaveNameList.contains(sWaveB); 459 | if (cWave) { 460 | auto waveB = libopendglab->kotlin.root.WaveCenter.Companion.getBasicWave(libopendglab->kotlin.root.WaveCenter.Companion._instance(), sWaveB.toUtf8().data()); 461 | auto convertWaveB = libopendglab->kotlin.root.convertBasicWaveDataToBasicWave(waveB); 462 | libopendglab->kotlin.root.WaveCenter.selectWave(waveCenterB, convertWaveB); 463 | } else { 464 | cWave = Global::touchWaveNameList.contains(sWaveA); 465 | if (cWave) { 466 | auto waveB = libopendglab->kotlin.root.WaveCenter.Companion.getTouchWave(libopendglab->kotlin.root.WaveCenter.Companion._instance(), sWaveB.toUtf8().data()); 467 | auto convertWaveB = libopendglab->kotlin.root.convertTouchWaveDataToBasicWave(waveB); 468 | libopendglab->kotlin.root.WaveCenter.selectWave(waveCenterB, convertWaveB); 469 | } 470 | } 471 | waveSenderTimerThread->start(); 472 | } else if (characteristic.uuid() == QBluetoothUuid(QUuid(Global::DGLabServices::EStimStatus::Characteristic::abpower))) { 473 | auto value = array.data(); 474 | auto eStimState = libopendglab->kotlin.root.OpenDGLab.get_eStimStatus(openDgLab); 475 | auto abpower = libopendglab->kotlin.root.OpenDGLab.EStimStatus.get_abPower(eStimState); 476 | auto barray = libopendglab->kotlin.root.createByteArray((void *)value, array.size()); 477 | libopendglab->kotlin.root.OpenDGLab.EStimStatus.ABPower.onChange(abpower, barray); 478 | auto aPower = libopendglab->kotlin.root.OpenDGLab.EStimStatus.ABPower.getAPower(abpower); 479 | auto bPower = libopendglab->kotlin.root.OpenDGLab.EStimStatus.ABPower.getBPower(abpower); 480 | powerA = aPower; 481 | powerB = bPower; 482 | emit powerUpdate(DeviceStateEnum::DeviceChannel::CHANNEL_A, powerA); 483 | emit powerUpdate(DeviceStateEnum::DeviceChannel::CHANNEL_B, powerB); 484 | if (remoteLocked) { 485 | // 通知锁定客户端设备更新 486 | for (auto r: Global::remoteList) { 487 | r->sendPowerUpdate(getID(), powerA, powerB); 488 | } 489 | } 490 | } 491 | } 492 | 493 | 494 | void DGLabDevice::clearCustomWaveAPI(DeviceStateEnum::DeviceChannel channel) { 495 | switch (channel) { 496 | case DeviceStateEnum::DeviceChannel::CHANNEL_A: 497 | { 498 | QMutexLocker locker(&customWaveAMutex); 499 | customWaveA.empty(); 500 | } 501 | break; 502 | case DeviceStateEnum::DeviceChannel::CHANNEL_B: 503 | { 504 | QMutexLocker locker(&customWaveBMutex); 505 | customWaveB.empty(); 506 | } 507 | break; 508 | } 509 | } 510 | void DGLabDevice::addCustomWaveAPI(DeviceStateEnum::DeviceChannel channel, QByteArray qbytes) { 511 | switch (channel) { 512 | case DeviceStateEnum::DeviceChannel::CHANNEL_A: 513 | { 514 | QMutexLocker locker(&customWaveAMutex); 515 | customWaveA.push_back(qbytes); 516 | } 517 | break; 518 | case DeviceStateEnum::DeviceChannel::CHANNEL_B: 519 | { 520 | QMutexLocker locker(&customWaveBMutex); 521 | customWaveB.push_back(qbytes); 522 | } 523 | break; 524 | } 525 | } 526 | QString DGLabDevice::getWaveAPI(DeviceStateEnum::DeviceChannel channel) { 527 | switch (channel) { 528 | case DeviceStateEnum::DeviceChannel::CHANNEL_A: 529 | return uiDeviceOperator->getWaveA(); 530 | case DeviceStateEnum::DeviceChannel::CHANNEL_B: 531 | return uiDeviceOperator->getWaveB(); 532 | } 533 | return ""; 534 | } 535 | void DGLabDevice::setWaveAPI(DeviceStateEnum::DeviceChannel channel, QString str) { 536 | QString wave; 537 | if (Global::basicWaveNameList.indexOf(str) >= 0 || Global::touchWaveNameList.indexOf(str) >= 0) { 538 | wave = str; 539 | } else { 540 | wave = "外部输入"; 541 | } 542 | switch (channel) { 543 | case DeviceStateEnum::DeviceChannel::CHANNEL_A: 544 | { 545 | uiDeviceOperator->setWaveA(wave); 546 | } 547 | break; 548 | case DeviceStateEnum::DeviceChannel::CHANNEL_B: 549 | { 550 | 551 | uiDeviceOperator->setWaveB(wave); 552 | } 553 | break; 554 | } 555 | } 556 | int DGLabDevice::getStrengthAPI(DeviceStateEnum::DeviceChannel channel) { 557 | switch (channel) { 558 | case DeviceStateEnum::DeviceChannel::CHANNEL_A: 559 | return powerA; 560 | case DeviceStateEnum::DeviceChannel::CHANNEL_B: 561 | return powerB; 562 | } 563 | return false; 564 | } 565 | void DGLabDevice::setStrengthAPI(int level_A, int level_B) { 566 | changePower(level_A, level_B); 567 | } 568 | void DGLabDevice::setDeviceRemoteLocked(bool lock) { 569 | remoteLocked = lock; 570 | if (remoteLocked) { 571 | uiDeviceOperator->setAutoChange(DeviceStateEnum::DeviceChannel::CHANNEL_A, false); 572 | uiDeviceOperator->setAutoChange(DeviceStateEnum::DeviceChannel::CHANNEL_B, false); 573 | uiDeviceItem->changeState(DeviceStateEnum::DeviceState::READY_REMOTEMANAGED); 574 | uiDeviceOperator->setEnabled(false); 575 | } else { 576 | if (controller->state() == QLowEnergyController::UnconnectedState) { 577 | uiDeviceItem->changeState(DeviceStateEnum::DeviceState::UNCONNECTED); 578 | uiDeviceOperator->setEnabled(false); 579 | } else { 580 | uiDeviceItem->changeState(DeviceStateEnum::DeviceState::READY); 581 | uiDeviceOperator->setEnabled(true); 582 | } 583 | } 584 | } 585 | bool DGLabDevice::getDeviceRemoteLocked() 586 | { 587 | return remoteLocked; 588 | } 589 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 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 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | OpenDGLab Desktop 633 | Copyright (C) 2020/9/28 Sound Reload 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . --------------------------------------------------------------------------------