mirror of
https://github.com/kunkundi/crossdesk.git
synced 2025-12-20 06:29:08 +08:00
Compare commits
14 Commits
v1.1.13-20
...
file-trans
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d337971de0 | ||
|
|
a967dc72d7 | ||
|
|
5066fcda48 | ||
|
|
e7bdf42694 | ||
|
|
875fea88ee | ||
|
|
b2654ea9db | ||
|
|
8f8e415262 | ||
|
|
5ff624f7b2 | ||
|
|
e09243f1ec | ||
|
|
f5941c7eda | ||
|
|
3c2ebe602e | ||
|
|
2f64172ead | ||
|
|
a83206a346 | ||
|
|
46e769976f |
2
.github/ISSUE_TEMPLATE/问题反馈.md
vendored
2
.github/ISSUE_TEMPLATE/问题反馈.md
vendored
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: 问题反馈
|
name: 问题反馈
|
||||||
about: Create a report to help us improve
|
about: 请在此提交问题报告,以便持续优化产品。
|
||||||
title: ''
|
title: ''
|
||||||
labels: bug
|
labels: bug
|
||||||
assignees: kunkundi
|
assignees: kunkundi
|
||||||
|
|||||||
28
.github/ISSUE_TEMPLATE/需求建议.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE/需求建议.md
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
name: 需求建议
|
||||||
|
about: 请在此提交功能需求或改进建议,以便后续迭代参考。
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: kunkundi
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**功能/改进建议描述**
|
||||||
|
清晰简洁地描述希望新增的功能或改进的内容。
|
||||||
|
|
||||||
|
**使用场景 / 背景**
|
||||||
|
说明该功能或改进的使用场景,以及解决后带来的价值。
|
||||||
|
|
||||||
|
**预期效果**
|
||||||
|
描述你认为最理想的功能表现或改进效果。
|
||||||
|
|
||||||
|
**参考示例(可选)**
|
||||||
|
提供类似功能截图、参考链接或其他说明,帮助更好理解需求。
|
||||||
|
|
||||||
|
**优先级(可选)**
|
||||||
|
- [ ] 高
|
||||||
|
- [ ] 中
|
||||||
|
- [ ] 低
|
||||||
|
|
||||||
|
**补充信息(可选)**
|
||||||
|
其他相关信息或特殊要求。
|
||||||
40
README.md
40
README.md
@@ -169,7 +169,7 @@ xmake r -d crossdesk
|
|||||||
|
|
||||||
## 自托管服务器
|
## 自托管服务器
|
||||||
推荐使用Docker部署CrossDesk Server。
|
推荐使用Docker部署CrossDesk Server。
|
||||||
```
|
```bash
|
||||||
sudo docker run -d \
|
sudo docker run -d \
|
||||||
--name crossdesk_server \
|
--name crossdesk_server \
|
||||||
--network host \
|
--network host \
|
||||||
@@ -181,7 +181,7 @@ sudo docker run -d \
|
|||||||
-e MAX_PORT=xxxxx \
|
-e MAX_PORT=xxxxx \
|
||||||
-v /var/lib/crossdesk:/var/lib/crossdesk \
|
-v /var/lib/crossdesk:/var/lib/crossdesk \
|
||||||
-v /var/log/crossdesk:/var/log/crossdesk \
|
-v /var/log/crossdesk:/var/log/crossdesk \
|
||||||
crossdesk/crossdesk-server:v1.1.2
|
crossdesk/crossdesk-server:v1.1.3
|
||||||
```
|
```
|
||||||
|
|
||||||
上述命令中,用户需注意的参数如下:
|
上述命令中,用户需注意的参数如下:
|
||||||
@@ -194,9 +194,25 @@ sudo docker run -d \
|
|||||||
- MIN_PORT/MAX_PORT:COTURN 服务使用的端口范围,例如:MIN_PORT=50000, MAX_PORT=60000,范围可根据客户端数量调整。
|
- MIN_PORT/MAX_PORT:COTURN 服务使用的端口范围,例如:MIN_PORT=50000, MAX_PORT=60000,范围可根据客户端数量调整。
|
||||||
- `-v /var/lib/crossdesk:/var/lib/crossdesk`:持久化数据库和证书文件到宿主机
|
- `-v /var/lib/crossdesk:/var/lib/crossdesk`:持久化数据库和证书文件到宿主机
|
||||||
- `-v /var/log/crossdesk:/var/log/crossdesk`:持久化日志文件到宿主机
|
- `-v /var/log/crossdesk:/var/log/crossdesk`:持久化日志文件到宿主机
|
||||||
-
|
|
||||||
|
**示例**:
|
||||||
|
```bash
|
||||||
|
sudo docker run -d \
|
||||||
|
--name crossdesk_server \
|
||||||
|
--network host \
|
||||||
|
-e EXTERNAL_IP=114.114.114.114 \
|
||||||
|
-e INTERNAL_IP=10.0.0.1 \
|
||||||
|
-e CROSSDESK_SERVER_PORT=9099 \
|
||||||
|
-e COTURN_PORT=3478 \
|
||||||
|
-e MIN_PORT=50000 \
|
||||||
|
-e MAX_PORT=60000 \
|
||||||
|
-v /var/lib/crossdesk:/var/lib/crossdesk \
|
||||||
|
-v /var/log/crossdesk:/var/log/crossdesk \
|
||||||
|
crossdesk/crossdesk-server:v1.1.3
|
||||||
|
```
|
||||||
|
|
||||||
**注意**:
|
**注意**:
|
||||||
- **服务器需开放端口:3478/udp,3478/tcp,MIN_PORT-MAX_PORT/udp,CROSSDESK_SERVER_PORT/tcp。**
|
- **服务器需开放端口:COTURN_PORT/udp,COTURN_PORT/tcp,MIN_PORT-MAX_PORT/udp,CROSSDESK_SERVER_PORT/tcp。**
|
||||||
- 如果不挂载 volume,容器删除后数据会丢失
|
- 如果不挂载 volume,容器删除后数据会丢失
|
||||||
- 证书文件会在首次启动时自动生成并持久化到宿主机的 `/var/lib/crossdesk/certs` 路径下
|
- 证书文件会在首次启动时自动生成并持久化到宿主机的 `/var/lib/crossdesk/certs` 路径下
|
||||||
- 数据库文件会自动创建并持久化到宿主机的 `/var/lib/crossdesk/db/crossdesk-server.db` 路径下
|
- 数据库文件会自动创建并持久化到宿主机的 `/var/lib/crossdesk/db/crossdesk-server.db` 路径下
|
||||||
@@ -213,21 +229,19 @@ sudo mkdir -p /var/lib/crossdesk /var/log/crossdesk
|
|||||||
sudo chown -R $(id -u):$(id -g) /var/lib/crossdesk /var/log/crossdesk
|
sudo chown -R $(id -u):$(id -g) /var/lib/crossdesk /var/log/crossdesk
|
||||||
```
|
```
|
||||||
|
|
||||||
## 证书文件
|
|
||||||
在宿主机的 `/var/lib/crossdesk/certs` 路径下可找到证书文件 `crossdesk.cn_root.crt`,下载到你的客户端主机,并在客户端的**自托管服务器设置**中选择相应的**证书文件路径**。
|
|
||||||
|
|
||||||
### 客户端
|
### 客户端
|
||||||
1. 点击右上角设置进入设置页面。<br>
|
1. 点击右上角设置进入设置页面。<br><br>
|
||||||
<img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br>
|
<img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br>
|
||||||
|
|
||||||
3. 点击点击**自托管服务器配置**。<br><br>
|
2. 点击点击`自托管服务器配置`按钮。<br><br>
|
||||||
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br><br>
|
<img width="600" height="140" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br><br>
|
||||||
|
|
||||||
5. 在**证书文件路径**选择框中找到 **crossdesk.cn_root.crt** 的存放路径,选中 **crossdesk.cn_root.crt**,点击确认。<br><br>
|
3. 输入`服务器地址`(**EXTERNAL_IP**)、`信令服务端口`(**CROSSDESK_SERVER_PORT**)、`中继服务端口`(**COTURN_PORT**)。<br><br>
|
||||||
<img width="600" height="220" alt="image" src="https://github.com/user-attachments/assets/4af7cd3a-c72e-44fb-b032-30e050019c2a" /><br><br>
|
<img width="600" height="200" alt="image" src="https://github.com/user-attachments/assets/9a32ddd5-37f8-4bee-9a51-eae295820f9a" /><br><br>
|
||||||
|
|
||||||
7. 勾选使用**自托管服务器配置**,点击确认配置生效。<br><br>
|
4. 后续如果自托管服务器被重置或因其他原因导致证书更换,可以点击`重置证书指纹`按钮重置客户端保存的证书指纹。<br><br>
|
||||||
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
|
<img width="600" height="200" alt="image" src="https://github.com/user-attachments/assets/d9e423ab-0c2b-4fab-b132-4dc27462d704" /><br><br>
|
||||||
|
|
||||||
### Web 客户端
|
### Web 客户端
|
||||||
详情见项目 [CrossDesk Web Client](https://github.com/kunkundi/crossdesk-web-client)。
|
详情见项目 [CrossDesk Web Client](https://github.com/kunkundi/crossdesk-web-client)。
|
||||||
|
|||||||
36
README_EN.md
36
README_EN.md
@@ -189,7 +189,7 @@ sudo docker run -d \
|
|||||||
-e MAX_PORT=xxxxx \
|
-e MAX_PORT=xxxxx \
|
||||||
-v /var/lib/crossdesk:/var/lib/crossdesk \
|
-v /var/lib/crossdesk:/var/lib/crossdesk \
|
||||||
-v /var/log/crossdesk:/var/log/crossdesk \
|
-v /var/log/crossdesk:/var/log/crossdesk \
|
||||||
crossdesk/crossdesk-server:v1.1.2
|
crossdesk/crossdesk-server:v1.1.3
|
||||||
```
|
```
|
||||||
|
|
||||||
The parameters you need to pay attention to are as follows:
|
The parameters you need to pay attention to are as follows:
|
||||||
@@ -203,8 +203,24 @@ The parameters you need to pay attention to are as follows:
|
|||||||
- `-v /var/lib/crossdesk:/var/lib/crossdesk`: Persists database and certificate files on the host machine.
|
- `-v /var/lib/crossdesk:/var/lib/crossdesk`: Persists database and certificate files on the host machine.
|
||||||
- `-v /var/log/crossdesk:/var/log/crossdesk`: Persists log files on the host machine.
|
- `-v /var/log/crossdesk:/var/log/crossdesk`: Persists log files on the host machine.
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```bash
|
||||||
|
sudo docker run -d \
|
||||||
|
--name crossdesk_server \
|
||||||
|
--network host \
|
||||||
|
-e EXTERNAL_IP=114.114.114.114 \
|
||||||
|
-e INTERNAL_IP=10.0.0.1 \
|
||||||
|
-e CROSSDESK_SERVER_PORT=9099 \
|
||||||
|
-e COTURN_PORT=3478 \
|
||||||
|
-e MIN_PORT=50000 \
|
||||||
|
-e MAX_PORT=60000 \
|
||||||
|
-v /var/lib/crossdesk:/var/lib/crossdesk \
|
||||||
|
-v /var/log/crossdesk:/var/log/crossdesk \
|
||||||
|
crossdesk/crossdesk-server:v1.1.3
|
||||||
|
```
|
||||||
|
|
||||||
**Notes**
|
**Notes**
|
||||||
- **The server must open the following ports: 3478/udp, 3478/tcp, MIN_PORT–MAX_PORT/udp, and CROSSDESK_SERVER_PORT/tcp.**
|
- **The server must open the following ports: COTURN_PORT/udp, COTURN_PORT/tcp, MIN_PORT–MAX_PORT/udp, and CROSSDESK_SERVER_PORT/tcp.**
|
||||||
- If you don’t mount volumes, all data will be lost when the container is removed.
|
- If you don’t mount volumes, all data will be lost when the container is removed.
|
||||||
- Certificate files will be automatically generated on first startup and persisted to the host at `/var/lib/crossdesk/certs`.
|
- Certificate files will be automatically generated on first startup and persisted to the host at `/var/lib/crossdesk/certs`.
|
||||||
- The database file will be automatically created and stored at `/var/lib/crossdesk/db/crossdesk-server.db`.
|
- The database file will be automatically created and stored at `/var/lib/crossdesk/db/crossdesk-server.db`.
|
||||||
@@ -222,25 +238,21 @@ sudo mkdir -p /var/lib/crossdesk /var/log/crossdesk
|
|||||||
sudo chown -R $(id -u):$(id -g) /var/lib/crossdesk /var/log/crossdesk
|
sudo chown -R $(id -u):$(id -g) /var/lib/crossdesk /var/log/crossdesk
|
||||||
```
|
```
|
||||||
|
|
||||||
### Certificate Files
|
|
||||||
You can find the certificate file `crossdesk.cn_root.crt` at `/var/lib/crossdesk/certs` on the host machine.
|
|
||||||
Download it to your client device and select it in the **Certificate File Path** field under the CrossDesk client’s **Self-Hosted Server Settings**.
|
|
||||||
|
|
||||||
### Server Side
|
### Server Side
|
||||||
Place **crossdesk.cn.key** and **crossdesk.cn_bundle.crt** into the **/path/to/your/certs** directory.
|
Place **crossdesk.cn.key** and **crossdesk.cn_bundle.crt** into the **/path/to/your/certs** directory.
|
||||||
|
|
||||||
### Client Side
|
### Client Side
|
||||||
1. Click the settings icon in the top-right corner to enter the settings page.<br>
|
1. Click the settings icon in the top-right corner to enter the settings page.<br><br>
|
||||||
<img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br>
|
<img width="600" height="210" alt="image" src="https://github.com/user-attachments/assets/6431131d-b32a-4726-8783-6788f47baa3b" /><br><br>
|
||||||
|
|
||||||
2. Click **Self-Hosted Server Configuration**.<br><br>
|
2. Click `Self-Hosted Server Configuration` button.<br><br>
|
||||||
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br><br>
|
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/24c761a3-1985-4d7e-84be-787383c2afb8" /><br><br>
|
||||||
|
|
||||||
3. In the **Certificate File Path** selection, locate and select the **crossdesk.cn_root.crt** file.<br><br>
|
3. Enter the `Server Address` (**EXTERNAL_IP**), `Signaling Service Port` (**CROSSDESK_SERVER_PORT**), and `Relay Service Port` (**COTURN_PORT**).<br><br>
|
||||||
<img width="600" height="220" alt="image" src="https://github.com/user-attachments/assets/4af7cd3a-c72e-44fb-b032-30e050019c2a" /><br><br>
|
<img width="600" height="200" alt="image" src="https://github.com/user-attachments/assets/9a32ddd5-37f8-4bee-9a51-eae295820f9a" /><br><br>
|
||||||
|
|
||||||
4. Check the option to use **Self-Hosted Server Configuration**.<br><br>
|
4. If the self-hosted server is later reset or the certificate is replaced for any reason, you can click the `Reset Certificate Fingerprint` button to clear the certificate fingerprint saved on the client.<br><br>
|
||||||
<img width="600" height="160" alt="image" src="https://github.com/user-attachments/assets/1e455dc3-4087-4f37-a544-1ff9f8789383" /><br><br>
|
<img width="600" height="200" alt="image" src="https://github.com/user-attachments/assets/d9e423ab-0c2b-4fab-b132-4dc27462d704" /><br><br>
|
||||||
|
|
||||||
### Web Client
|
### Web Client
|
||||||
See [CrossDesk Web Client](https://github.com/kunkundi/crossdesk-web-client)。
|
See [CrossDesk Web Client](https://github.com/kunkundi/crossdesk-web-client)。
|
||||||
|
|||||||
@@ -77,6 +77,64 @@ int ConfigCenter::Load() {
|
|||||||
} else {
|
} else {
|
||||||
cert_file_path_ = "";
|
cert_file_path_ = "";
|
||||||
}
|
}
|
||||||
|
const char* cert_fingerprint_value =
|
||||||
|
ini_.GetValue(section_, "cert_fingerprint", nullptr);
|
||||||
|
if (cert_fingerprint_value != nullptr && strlen(cert_fingerprint_value) > 0) {
|
||||||
|
cert_fingerprint_ = cert_fingerprint_value;
|
||||||
|
} else {
|
||||||
|
cert_fingerprint_ = "";
|
||||||
|
}
|
||||||
|
const char* cert_fingerprint_server_host_value =
|
||||||
|
ini_.GetValue(section_, "cert_fingerprint_server_host", nullptr);
|
||||||
|
if (cert_fingerprint_server_host_value != nullptr &&
|
||||||
|
strlen(cert_fingerprint_server_host_value) > 0) {
|
||||||
|
cert_fingerprint_server_host_ = cert_fingerprint_server_host_value;
|
||||||
|
} else {
|
||||||
|
cert_fingerprint_server_host_ = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* default_cert_fingerprint_value =
|
||||||
|
ini_.GetValue(section_, "default_cert_fingerprint", nullptr);
|
||||||
|
if (default_cert_fingerprint_value != nullptr &&
|
||||||
|
strlen(default_cert_fingerprint_value) > 0) {
|
||||||
|
default_cert_fingerprint_ = default_cert_fingerprint_value;
|
||||||
|
} else {
|
||||||
|
default_cert_fingerprint_ = "";
|
||||||
|
}
|
||||||
|
const char* default_cert_fingerprint_server_host_value =
|
||||||
|
ini_.GetValue(section_, "default_cert_fingerprint_server_host", nullptr);
|
||||||
|
if (default_cert_fingerprint_server_host_value != nullptr &&
|
||||||
|
strlen(default_cert_fingerprint_server_host_value) > 0) {
|
||||||
|
default_cert_fingerprint_server_host_ =
|
||||||
|
default_cert_fingerprint_server_host_value;
|
||||||
|
} else {
|
||||||
|
default_cert_fingerprint_server_host_ = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enable_self_hosted_ && !cert_fingerprint_.empty() &&
|
||||||
|
!cert_fingerprint_server_host_.empty() &&
|
||||||
|
signal_server_host_ != cert_fingerprint_server_host_) {
|
||||||
|
LOG_INFO("Server IP changed from {} to {}, clearing old fingerprint",
|
||||||
|
cert_fingerprint_server_host_, signal_server_host_);
|
||||||
|
cert_fingerprint_.clear();
|
||||||
|
cert_fingerprint_server_host_.clear();
|
||||||
|
ini_.Delete(section_, "cert_fingerprint", false);
|
||||||
|
ini_.Delete(section_, "cert_fingerprint_server_host", false);
|
||||||
|
ini_.SaveFile(config_path_.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enable_self_hosted_ && !default_cert_fingerprint_.empty() &&
|
||||||
|
!default_cert_fingerprint_server_host_.empty() &&
|
||||||
|
signal_server_host_default_ != default_cert_fingerprint_server_host_) {
|
||||||
|
LOG_INFO(
|
||||||
|
"Default server IP changed from {} to {}, clearing old fingerprint",
|
||||||
|
default_cert_fingerprint_server_host_, signal_server_host_default_);
|
||||||
|
default_cert_fingerprint_.clear();
|
||||||
|
default_cert_fingerprint_server_host_.clear();
|
||||||
|
ini_.Delete(section_, "default_cert_fingerprint", false);
|
||||||
|
ini_.Delete(section_, "default_cert_fingerprint_server_host", false);
|
||||||
|
ini_.SaveFile(config_path_.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
enable_autostart_ =
|
enable_autostart_ =
|
||||||
ini_.GetBoolValue(section_, "enable_autostart", enable_autostart_);
|
ini_.GetBoolValue(section_, "enable_autostart", enable_autostart_);
|
||||||
@@ -108,6 +166,18 @@ int ConfigCenter::Save() {
|
|||||||
ini_.SetLongValue(section_, "coturn_server_port",
|
ini_.SetLongValue(section_, "coturn_server_port",
|
||||||
static_cast<long>(coturn_server_port_));
|
static_cast<long>(coturn_server_port_));
|
||||||
ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str());
|
ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str());
|
||||||
|
if (!cert_fingerprint_.empty()) {
|
||||||
|
ini_.SetValue(section_, "cert_fingerprint", cert_fingerprint_.c_str());
|
||||||
|
ini_.SetValue(section_, "cert_fingerprint_server_host",
|
||||||
|
cert_fingerprint_server_host_.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!default_cert_fingerprint_.empty()) {
|
||||||
|
ini_.SetValue(section_, "default_cert_fingerprint",
|
||||||
|
default_cert_fingerprint_.c_str());
|
||||||
|
ini_.SetValue(section_, "default_cert_fingerprint_server_host",
|
||||||
|
default_cert_fingerprint_server_host_.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
ini_.SetBoolValue(section_, "enable_autostart", enable_autostart_);
|
ini_.SetBoolValue(section_, "enable_autostart", enable_autostart_);
|
||||||
@@ -200,6 +270,15 @@ int ConfigCenter::SetSrtp(bool enable_srtp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int ConfigCenter::SetServerHost(const std::string& signal_server_host) {
|
int ConfigCenter::SetServerHost(const std::string& signal_server_host) {
|
||||||
|
if (enable_self_hosted_ && !cert_fingerprint_.empty() &&
|
||||||
|
signal_server_host != signal_server_host_) {
|
||||||
|
LOG_INFO("Server IP changed from {} to {}, clearing old fingerprint",
|
||||||
|
signal_server_host_, signal_server_host);
|
||||||
|
cert_fingerprint_.clear();
|
||||||
|
cert_fingerprint_server_host_.clear();
|
||||||
|
ini_.Delete(section_, "cert_fingerprint", false);
|
||||||
|
ini_.Delete(section_, "cert_fingerprint_server_host", false);
|
||||||
|
}
|
||||||
signal_server_host_ = signal_server_host;
|
signal_server_host_ = signal_server_host;
|
||||||
ini_.SetValue(section_, "signal_server_host", signal_server_host_.c_str());
|
ini_.SetValue(section_, "signal_server_host", signal_server_host_.c_str());
|
||||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||||
@@ -241,6 +320,57 @@ int ConfigCenter::SetCertFilePath(const std::string& cert_file_path) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ConfigCenter::SetCertFingerprint(const std::string& fingerprint) {
|
||||||
|
cert_fingerprint_ = fingerprint;
|
||||||
|
cert_fingerprint_server_host_ = signal_server_host_;
|
||||||
|
ini_.SetValue(section_, "cert_fingerprint", cert_fingerprint_.c_str());
|
||||||
|
ini_.SetValue(section_, "cert_fingerprint_server_host",
|
||||||
|
cert_fingerprint_server_host_.c_str());
|
||||||
|
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||||
|
if (rc < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ConfigCenter::SetDefaultCertFingerprint(const std::string& fingerprint) {
|
||||||
|
default_cert_fingerprint_ = fingerprint;
|
||||||
|
default_cert_fingerprint_server_host_ = signal_server_host_default_;
|
||||||
|
ini_.SetValue(section_, "default_cert_fingerprint",
|
||||||
|
default_cert_fingerprint_.c_str());
|
||||||
|
ini_.SetValue(section_, "default_cert_fingerprint_server_host",
|
||||||
|
default_cert_fingerprint_server_host_.c_str());
|
||||||
|
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||||
|
if (rc < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ConfigCenter::ClearCertFingerprint() {
|
||||||
|
cert_fingerprint_.clear();
|
||||||
|
cert_fingerprint_server_host_.clear();
|
||||||
|
ini_.Delete(section_, "cert_fingerprint", false);
|
||||||
|
ini_.Delete(section_, "cert_fingerprint_server_host", false);
|
||||||
|
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||||
|
if (rc < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ConfigCenter::ClearDefaultCertFingerprint() {
|
||||||
|
default_cert_fingerprint_.clear();
|
||||||
|
default_cert_fingerprint_server_host_.clear();
|
||||||
|
ini_.Delete(section_, "default_cert_fingerprint", false);
|
||||||
|
ini_.Delete(section_, "default_cert_fingerprint_server_host", false);
|
||||||
|
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||||
|
if (rc < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int ConfigCenter::SetSelfHosted(bool enable_self_hosted) {
|
int ConfigCenter::SetSelfHosted(bool enable_self_hosted) {
|
||||||
enable_self_hosted_ = enable_self_hosted;
|
enable_self_hosted_ = enable_self_hosted;
|
||||||
ini_.SetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
|
ini_.SetBoolValue(section_, "enable_self_hosted", enable_self_hosted_);
|
||||||
@@ -272,6 +402,28 @@ int ConfigCenter::SetSelfHosted(bool enable_self_hosted) {
|
|||||||
if (cert_file_path_value != nullptr && strlen(cert_file_path_value) > 0) {
|
if (cert_file_path_value != nullptr && strlen(cert_file_path_value) > 0) {
|
||||||
cert_file_path_ = cert_file_path_value;
|
cert_file_path_ = cert_file_path_value;
|
||||||
}
|
}
|
||||||
|
const char* cert_fingerprint_value =
|
||||||
|
ini_.GetValue(section_, "cert_fingerprint", nullptr);
|
||||||
|
if (cert_fingerprint_value != nullptr &&
|
||||||
|
strlen(cert_fingerprint_value) > 0) {
|
||||||
|
cert_fingerprint_ = cert_fingerprint_value;
|
||||||
|
}
|
||||||
|
const char* cert_fingerprint_server_host_value =
|
||||||
|
ini_.GetValue(section_, "cert_fingerprint_server_host", nullptr);
|
||||||
|
if (cert_fingerprint_server_host_value != nullptr &&
|
||||||
|
strlen(cert_fingerprint_server_host_value) > 0) {
|
||||||
|
cert_fingerprint_server_host_ = cert_fingerprint_server_host_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cert_fingerprint_.empty() && !cert_fingerprint_server_host_.empty() &&
|
||||||
|
signal_server_host_ != cert_fingerprint_server_host_) {
|
||||||
|
LOG_INFO("Server IP changed from {} to {}, clearing old fingerprint",
|
||||||
|
cert_fingerprint_server_host_, signal_server_host_);
|
||||||
|
cert_fingerprint_.clear();
|
||||||
|
cert_fingerprint_server_host_.clear();
|
||||||
|
ini_.Delete(section_, "cert_fingerprint", false);
|
||||||
|
ini_.Delete(section_, "cert_fingerprint_server_host", false);
|
||||||
|
}
|
||||||
|
|
||||||
ini_.SetValue(section_, "signal_server_host", signal_server_host_.c_str());
|
ini_.SetValue(section_, "signal_server_host", signal_server_host_.c_str());
|
||||||
ini_.SetLongValue(section_, "signal_server_port",
|
ini_.SetLongValue(section_, "signal_server_port",
|
||||||
@@ -279,6 +431,11 @@ int ConfigCenter::SetSelfHosted(bool enable_self_hosted) {
|
|||||||
ini_.SetLongValue(section_, "coturn_server_port",
|
ini_.SetLongValue(section_, "coturn_server_port",
|
||||||
static_cast<long>(coturn_server_port_));
|
static_cast<long>(coturn_server_port_));
|
||||||
ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str());
|
ini_.SetValue(section_, "cert_file_path", cert_file_path_.c_str());
|
||||||
|
if (!cert_fingerprint_.empty()) {
|
||||||
|
ini_.SetValue(section_, "cert_fingerprint", cert_fingerprint_.c_str());
|
||||||
|
ini_.SetValue(section_, "cert_fingerprint_server_host",
|
||||||
|
cert_fingerprint_server_host_.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
SI_Error rc = ini_.SaveFile(config_path_.c_str());
|
||||||
@@ -368,6 +525,14 @@ int ConfigCenter::GetCoturnServerPort() const { return coturn_server_port_; }
|
|||||||
|
|
||||||
std::string ConfigCenter::GetCertFilePath() const { return cert_file_path_; }
|
std::string ConfigCenter::GetCertFilePath() const { return cert_file_path_; }
|
||||||
|
|
||||||
|
std::string ConfigCenter::GetCertFingerprint() const {
|
||||||
|
return cert_fingerprint_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ConfigCenter::GetDefaultCertFingerprint() const {
|
||||||
|
return default_cert_fingerprint_;
|
||||||
|
}
|
||||||
|
|
||||||
std::string ConfigCenter::GetDefaultServerHost() const {
|
std::string ConfigCenter::GetDefaultServerHost() const {
|
||||||
return signal_server_host_default_;
|
return signal_server_host_default_;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ class ConfigCenter {
|
|||||||
int SetServerPort(int signal_server_port);
|
int SetServerPort(int signal_server_port);
|
||||||
int SetCoturnServerPort(int coturn_server_port);
|
int SetCoturnServerPort(int coturn_server_port);
|
||||||
int SetCertFilePath(const std::string& cert_file_path);
|
int SetCertFilePath(const std::string& cert_file_path);
|
||||||
|
int SetCertFingerprint(const std::string& fingerprint);
|
||||||
|
int SetDefaultCertFingerprint(const std::string& fingerprint);
|
||||||
|
int ClearCertFingerprint();
|
||||||
|
int ClearDefaultCertFingerprint();
|
||||||
int SetSelfHosted(bool enable_self_hosted);
|
int SetSelfHosted(bool enable_self_hosted);
|
||||||
int SetMinimizeToTray(bool enable_minimize_to_tray);
|
int SetMinimizeToTray(bool enable_minimize_to_tray);
|
||||||
int SetAutostart(bool enable_autostart);
|
int SetAutostart(bool enable_autostart);
|
||||||
@@ -56,6 +60,8 @@ class ConfigCenter {
|
|||||||
int GetSignalServerPort() const;
|
int GetSignalServerPort() const;
|
||||||
int GetCoturnServerPort() const;
|
int GetCoturnServerPort() const;
|
||||||
std::string GetCertFilePath() const;
|
std::string GetCertFilePath() const;
|
||||||
|
std::string GetCertFingerprint() const;
|
||||||
|
std::string GetDefaultCertFingerprint() const;
|
||||||
std::string GetDefaultServerHost() const;
|
std::string GetDefaultServerHost() const;
|
||||||
int GetDefaultSignalServerPort() const;
|
int GetDefaultSignalServerPort() const;
|
||||||
int GetDefaultCoturnServerPort() const;
|
int GetDefaultCoturnServerPort() const;
|
||||||
@@ -88,6 +94,10 @@ class ConfigCenter {
|
|||||||
int coturn_server_port_default_ = 3478;
|
int coturn_server_port_default_ = 3478;
|
||||||
std::string cert_file_path_ = "";
|
std::string cert_file_path_ = "";
|
||||||
std::string cert_file_path_default_ = "";
|
std::string cert_file_path_default_ = "";
|
||||||
|
std::string cert_fingerprint_ = "";
|
||||||
|
std::string cert_fingerprint_server_host_ = "";
|
||||||
|
std::string default_cert_fingerprint_ = "";
|
||||||
|
std::string default_cert_fingerprint_server_host_ = "";
|
||||||
bool enable_self_hosted_ = false;
|
bool enable_self_hosted_ = false;
|
||||||
bool enable_minimize_to_tray_ = false;
|
bool enable_minimize_to_tray_ = false;
|
||||||
bool enable_autostart_ = false;
|
bool enable_autostart_ = false;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -116,6 +116,9 @@ static std::vector<std::string> self_hosted_server_certificate_path = {
|
|||||||
reinterpret_cast<const char*>(u8"证书文件路径:"), "Certificate File Path:"};
|
reinterpret_cast<const char*>(u8"证书文件路径:"), "Certificate File Path:"};
|
||||||
static std::vector<std::string> select_a_file = {
|
static std::vector<std::string> select_a_file = {
|
||||||
reinterpret_cast<const char*>(u8"请选择文件"), "Please select a file"};
|
reinterpret_cast<const char*>(u8"请选择文件"), "Please select a file"};
|
||||||
|
static std::vector<std::string> reset_cert_fingerprint = {
|
||||||
|
reinterpret_cast<const char*>(u8"重置证书指纹"),
|
||||||
|
"Reset Certificate Fingerprint"};
|
||||||
static std::vector<std::string> ok = {reinterpret_cast<const char*>(u8"确认"),
|
static std::vector<std::string> ok = {reinterpret_cast<const char*>(u8"确认"),
|
||||||
"OK"};
|
"OK"};
|
||||||
static std::vector<std::string> cancel = {
|
static std::vector<std::string> cancel = {
|
||||||
@@ -182,6 +185,8 @@ static std::vector<std::string> enable_daemon = {
|
|||||||
static std::vector<std::string> takes_effect_after_restart = {
|
static std::vector<std::string> takes_effect_after_restart = {
|
||||||
reinterpret_cast<const char*>(u8"重启后生效"),
|
reinterpret_cast<const char*>(u8"重启后生效"),
|
||||||
"Takes effect after restart"};
|
"Takes effect after restart"};
|
||||||
|
static std::vector<std::string> select_file = {
|
||||||
|
reinterpret_cast<const char*>(u8"选择文件"), "Select File"};
|
||||||
#if _WIN32
|
#if _WIN32
|
||||||
static std::vector<std::string> minimize_to_tray = {
|
static std::vector<std::string> minimize_to_tray = {
|
||||||
reinterpret_cast<const char*>(u8"退出时最小化到系统托盘:"),
|
reinterpret_cast<const char*>(u8"退出时最小化到系统托盘:"),
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ int Render::LocalWindow() {
|
|||||||
ImGui::SetCursorPos(
|
ImGui::SetCursorPos(
|
||||||
ImVec2(io.DisplaySize.x * 0.045f, io.DisplaySize.y * 0.02f));
|
ImVec2(io.DisplaySize.x * 0.045f, io.DisplaySize.y * 0.02f));
|
||||||
|
|
||||||
|
ImGui::SetWindowFontScale(0.9f);
|
||||||
ImGui::TextColored(
|
ImGui::TextColored(
|
||||||
ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s",
|
ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s",
|
||||||
localization::local_desktop[localization_language_index_].c_str());
|
localization::local_desktop[localization_language_index_].c_str());
|
||||||
@@ -233,15 +234,41 @@ int Render::LocalWindow() {
|
|||||||
sizeof(password_saved_) - 1);
|
sizeof(password_saved_) - 1);
|
||||||
password_saved_[sizeof(password_saved_) - 1] = '\0';
|
password_saved_[sizeof(password_saved_) - 1] = '\0';
|
||||||
|
|
||||||
std::string client_id_with_password =
|
// if self hosted
|
||||||
std::string(client_id_) + "@" + password_saved_;
|
if (config_center_->IsSelfHosted()) {
|
||||||
strncpy(client_id_with_password_, client_id_with_password.c_str(),
|
std::string self_hosted_id_str;
|
||||||
sizeof(client_id_with_password_) - 1);
|
if (strlen(self_hosted_id_) > 0) {
|
||||||
client_id_with_password_[sizeof(client_id_with_password_) - 1] =
|
const char* at_pos = strchr(self_hosted_id_, '@');
|
||||||
'\0';
|
if (at_pos != nullptr) {
|
||||||
|
self_hosted_id_str =
|
||||||
|
std::string(self_hosted_id_, at_pos - self_hosted_id_);
|
||||||
|
} else {
|
||||||
|
self_hosted_id_str = self_hosted_id_;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self_hosted_id_str = client_id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string new_self_hosted_id =
|
||||||
|
self_hosted_id_str + "@" + password_saved_;
|
||||||
|
memset(&self_hosted_id_, 0, sizeof(self_hosted_id_));
|
||||||
|
strncpy(self_hosted_id_, new_self_hosted_id.c_str(),
|
||||||
|
sizeof(self_hosted_id_) - 1);
|
||||||
|
self_hosted_id_[sizeof(self_hosted_id_) - 1] = '\0';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
std::string client_id_with_password =
|
||||||
|
std::string(client_id_) + "@" + password_saved_;
|
||||||
|
strncpy(client_id_with_password_, client_id_with_password.c_str(),
|
||||||
|
sizeof(client_id_with_password_) - 1);
|
||||||
|
client_id_with_password_[sizeof(client_id_with_password_) - 1] =
|
||||||
|
'\0';
|
||||||
|
}
|
||||||
|
|
||||||
SaveSettingsIntoCacheFile();
|
SaveSettingsIntoCacheFile();
|
||||||
|
|
||||||
|
memset(new_password_, 0, sizeof(new_password_));
|
||||||
|
|
||||||
LeaveConnection(peer_, client_id_);
|
LeaveConnection(peer_, client_id_);
|
||||||
DestroyPeer(&peer_);
|
DestroyPeer(&peer_);
|
||||||
focus_on_input_widget_ = true;
|
focus_on_input_widget_ = true;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ int Render::RecentConnectionsWindow() {
|
|||||||
ImGui::SetCursorPos(
|
ImGui::SetCursorPos(
|
||||||
ImVec2(io.DisplaySize.x * 0.045f, io.DisplaySize.y * 0.02f));
|
ImVec2(io.DisplaySize.x * 0.045f, io.DisplaySize.y * 0.02f));
|
||||||
|
|
||||||
|
ImGui::SetWindowFontScale(0.9f);
|
||||||
ImGui::TextColored(
|
ImGui::TextColored(
|
||||||
ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s",
|
ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s",
|
||||||
localization::recent_connections[localization_language_index_].c_str());
|
localization::recent_connections[localization_language_index_].c_str());
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ int Render::RemoteWindow() {
|
|||||||
ImGui::SetCursorPos(
|
ImGui::SetCursorPos(
|
||||||
ImVec2(io.DisplaySize.x * 0.057f, io.DisplaySize.y * 0.02f));
|
ImVec2(io.DisplaySize.x * 0.057f, io.DisplaySize.y * 0.02f));
|
||||||
|
|
||||||
|
ImGui::SetWindowFontScale(0.9f);
|
||||||
ImGui::TextColored(
|
ImGui::TextColored(
|
||||||
ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s",
|
ImVec4(0.0f, 0.0f, 0.0f, 0.5f), "%s",
|
||||||
localization::remote_desktop[localization_language_index_].c_str());
|
localization::remote_desktop[localization_language_index_].c_str());
|
||||||
@@ -189,11 +190,11 @@ int Render::ConnectTo(const std::string& remote_id, const char* password,
|
|||||||
props->params_.user_id = props->local_id_.c_str();
|
props->params_.user_id = props->local_id_.c_str();
|
||||||
props->peer_ = CreatePeer(&props->params_);
|
props->peer_ = CreatePeer(&props->params_);
|
||||||
|
|
||||||
props->control_window_width_ = title_bar_height_ * 8.0f;
|
props->control_window_width_ = title_bar_height_ * 9.0f;
|
||||||
props->control_window_height_ = title_bar_height_ * 1.3f;
|
props->control_window_height_ = title_bar_height_ * 1.3f;
|
||||||
props->control_window_min_width_ = title_bar_height_ * 0.65f;
|
props->control_window_min_width_ = title_bar_height_ * 0.65f;
|
||||||
props->control_window_min_height_ = title_bar_height_ * 1.3f;
|
props->control_window_min_height_ = title_bar_height_ * 1.3f;
|
||||||
props->control_window_max_width_ = title_bar_height_ * 8.0f;
|
props->control_window_max_width_ = title_bar_height_ * 9.0f;
|
||||||
props->control_window_max_height_ = title_bar_height_ * 6.0f;
|
props->control_window_max_height_ = title_bar_height_ * 6.0f;
|
||||||
|
|
||||||
if (!props->peer_) {
|
if (!props->peer_) {
|
||||||
@@ -205,7 +206,8 @@ int Render::ConnectTo(const std::string& remote_id, const char* password,
|
|||||||
AddVideoStream(props->peer_, display_info.name.c_str());
|
AddVideoStream(props->peer_, display_info.name.c_str());
|
||||||
}
|
}
|
||||||
AddAudioStream(props->peer_, props->audio_label_.c_str());
|
AddAudioStream(props->peer_, props->audio_label_.c_str());
|
||||||
AddDataStream(props->peer_, props->data_label_.c_str());
|
AddDataStream(props->peer_, props->data_label_.c_str(), false);
|
||||||
|
AddDataStream(props->peer_, props->file_label_.c_str(), true);
|
||||||
|
|
||||||
props->connection_status_ = ConnectionStatus::Connecting;
|
props->connection_status_ = ConnectionStatus::Connecting;
|
||||||
|
|
||||||
|
|||||||
@@ -203,22 +203,41 @@ Render::~Render() {}
|
|||||||
|
|
||||||
int Render::SaveSettingsIntoCacheFile() {
|
int Render::SaveSettingsIntoCacheFile() {
|
||||||
cd_cache_mutex_.lock();
|
cd_cache_mutex_.lock();
|
||||||
std::ofstream cd_cache_file(cache_path_ + "/secure_cache.enc",
|
|
||||||
std::ios::binary);
|
std::ofstream cd_cache_v2_file(cache_path_ + "/secure_cache_v2.enc",
|
||||||
if (!cd_cache_file) {
|
std::ios::binary);
|
||||||
|
if (!cd_cache_v2_file) {
|
||||||
cd_cache_mutex_.unlock();
|
cd_cache_mutex_.unlock();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(&cd_cache_.client_id_with_password, 0,
|
memset(&cd_cache_v2_.client_id_with_password, 0,
|
||||||
sizeof(cd_cache_.client_id_with_password));
|
sizeof(cd_cache_v2_.client_id_with_password));
|
||||||
memcpy(cd_cache_.client_id_with_password, client_id_with_password_,
|
memcpy(cd_cache_v2_.client_id_with_password, client_id_with_password_,
|
||||||
sizeof(client_id_with_password_));
|
sizeof(client_id_with_password_));
|
||||||
memcpy(&cd_cache_.key, &aes128_key_, sizeof(aes128_key_));
|
memcpy(&cd_cache_v2_.key, &aes128_key_, sizeof(aes128_key_));
|
||||||
memcpy(&cd_cache_.iv, &aes128_iv_, sizeof(aes128_iv_));
|
memcpy(&cd_cache_v2_.iv, &aes128_iv_, sizeof(aes128_iv_));
|
||||||
|
|
||||||
|
memset(&cd_cache_v2_.self_hosted_id, 0, sizeof(cd_cache_v2_.self_hosted_id));
|
||||||
|
memcpy(cd_cache_v2_.self_hosted_id, self_hosted_id_, sizeof(self_hosted_id_));
|
||||||
|
|
||||||
|
cd_cache_v2_file.write(reinterpret_cast<char*>(&cd_cache_v2_),
|
||||||
|
sizeof(CDCacheV2));
|
||||||
|
cd_cache_v2_file.close();
|
||||||
|
|
||||||
|
std::ofstream cd_cache_file(cache_path_ + "/secure_cache.enc",
|
||||||
|
std::ios::binary);
|
||||||
|
if (cd_cache_file) {
|
||||||
|
memset(&cd_cache_.client_id_with_password, 0,
|
||||||
|
sizeof(cd_cache_.client_id_with_password));
|
||||||
|
memcpy(cd_cache_.client_id_with_password, client_id_with_password_,
|
||||||
|
sizeof(client_id_with_password_));
|
||||||
|
memcpy(&cd_cache_.key, &aes128_key_, sizeof(aes128_key_));
|
||||||
|
memcpy(&cd_cache_.iv, &aes128_iv_, sizeof(aes128_iv_));
|
||||||
|
cd_cache_file.write(reinterpret_cast<char*>(&cd_cache_), sizeof(CDCache));
|
||||||
|
cd_cache_file.close();
|
||||||
|
}
|
||||||
|
|
||||||
cd_cache_file.write(reinterpret_cast<char*>(&cd_cache_), sizeof(CDCache));
|
|
||||||
cd_cache_file.close();
|
|
||||||
cd_cache_mutex_.unlock();
|
cd_cache_mutex_.unlock();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -226,33 +245,81 @@ int Render::SaveSettingsIntoCacheFile() {
|
|||||||
|
|
||||||
int Render::LoadSettingsFromCacheFile() {
|
int Render::LoadSettingsFromCacheFile() {
|
||||||
cd_cache_mutex_.lock();
|
cd_cache_mutex_.lock();
|
||||||
std::ifstream cd_cache_file(cache_path_ + "/secure_cache.enc",
|
|
||||||
std::ios::binary);
|
std::ifstream cd_cache_v2_file(cache_path_ + "/secure_cache_v2.enc",
|
||||||
if (!cd_cache_file) {
|
std::ios::binary);
|
||||||
|
bool v2_file_exists = cd_cache_v2_file.good();
|
||||||
|
|
||||||
|
if (v2_file_exists) {
|
||||||
|
cd_cache_v2_file.read(reinterpret_cast<char*>(&cd_cache_v2_),
|
||||||
|
sizeof(CDCacheV2));
|
||||||
|
cd_cache_v2_file.close();
|
||||||
|
|
||||||
|
memset(&client_id_with_password_, 0, sizeof(client_id_with_password_));
|
||||||
|
memcpy(client_id_with_password_, cd_cache_v2_.client_id_with_password,
|
||||||
|
sizeof(client_id_with_password_));
|
||||||
|
|
||||||
|
memset(&self_hosted_id_, 0, sizeof(self_hosted_id_));
|
||||||
|
memcpy(self_hosted_id_, cd_cache_v2_.self_hosted_id,
|
||||||
|
sizeof(self_hosted_id_));
|
||||||
|
|
||||||
|
memcpy(aes128_key_, cd_cache_v2_.key, sizeof(cd_cache_v2_.key));
|
||||||
|
memcpy(aes128_iv_, cd_cache_v2_.iv, sizeof(cd_cache_v2_.iv));
|
||||||
|
|
||||||
|
LOG_INFO("Load settings from v2 cache file");
|
||||||
|
} else {
|
||||||
|
std::ifstream cd_cache_file(cache_path_ + "/secure_cache.enc",
|
||||||
|
std::ios::binary);
|
||||||
|
if (!cd_cache_file) {
|
||||||
|
cd_cache_mutex_.unlock();
|
||||||
|
|
||||||
|
memset(password_saved_, 0, sizeof(password_saved_));
|
||||||
|
memset(aes128_key_, 0, sizeof(aes128_key_));
|
||||||
|
memset(aes128_iv_, 0, sizeof(aes128_iv_));
|
||||||
|
memset(self_hosted_id_, 0, sizeof(self_hosted_id_));
|
||||||
|
|
||||||
|
thumbnail_.reset();
|
||||||
|
thumbnail_ = std::make_shared<Thumbnail>(cache_path_ + "/thumbnails/");
|
||||||
|
thumbnail_->GetKeyAndIv(aes128_key_, aes128_iv_);
|
||||||
|
thumbnail_->DeleteAllFilesInDirectory();
|
||||||
|
|
||||||
|
SaveSettingsIntoCacheFile();
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cd_cache_file.read(reinterpret_cast<char*>(&cd_cache_), sizeof(CDCache));
|
||||||
|
cd_cache_file.close();
|
||||||
|
|
||||||
|
memset(&cd_cache_v2_.client_id_with_password, 0,
|
||||||
|
sizeof(cd_cache_v2_.client_id_with_password));
|
||||||
|
memcpy(cd_cache_v2_.client_id_with_password,
|
||||||
|
cd_cache_.client_id_with_password,
|
||||||
|
sizeof(cd_cache_.client_id_with_password));
|
||||||
|
memcpy(&cd_cache_v2_.key, &cd_cache_.key, sizeof(cd_cache_.key));
|
||||||
|
memcpy(&cd_cache_v2_.iv, &cd_cache_.iv, sizeof(cd_cache_.iv));
|
||||||
|
|
||||||
|
memset(&cd_cache_v2_.self_hosted_id, 0,
|
||||||
|
sizeof(cd_cache_v2_.self_hosted_id));
|
||||||
|
|
||||||
|
memset(&client_id_with_password_, 0, sizeof(client_id_with_password_));
|
||||||
|
memcpy(client_id_with_password_, cd_cache_.client_id_with_password,
|
||||||
|
sizeof(client_id_with_password_));
|
||||||
|
|
||||||
|
memset(&self_hosted_id_, 0, sizeof(self_hosted_id_));
|
||||||
|
|
||||||
|
memcpy(aes128_key_, cd_cache_.key, sizeof(cd_cache_.key));
|
||||||
|
memcpy(aes128_iv_, cd_cache_.iv, sizeof(cd_cache_.iv));
|
||||||
|
|
||||||
cd_cache_mutex_.unlock();
|
cd_cache_mutex_.unlock();
|
||||||
|
|
||||||
memset(password_saved_, 0, sizeof(password_saved_));
|
|
||||||
memset(aes128_key_, 0, sizeof(aes128_key_));
|
|
||||||
memset(aes128_iv_, 0, sizeof(aes128_iv_));
|
|
||||||
|
|
||||||
thumbnail_.reset();
|
|
||||||
thumbnail_ = std::make_shared<Thumbnail>(cache_path_ + "/thumbnails/");
|
|
||||||
thumbnail_->GetKeyAndIv(aes128_key_, aes128_iv_);
|
|
||||||
thumbnail_->DeleteAllFilesInDirectory();
|
|
||||||
|
|
||||||
SaveSettingsIntoCacheFile();
|
SaveSettingsIntoCacheFile();
|
||||||
|
cd_cache_mutex_.lock();
|
||||||
|
|
||||||
return -1;
|
LOG_INFO("Migrated settings from v1 to v2 cache file");
|
||||||
}
|
}
|
||||||
|
|
||||||
cd_cache_file.read(reinterpret_cast<char*>(&cd_cache_), sizeof(CDCache));
|
|
||||||
cd_cache_file.close();
|
|
||||||
cd_cache_mutex_.unlock();
|
cd_cache_mutex_.unlock();
|
||||||
|
|
||||||
memset(&client_id_with_password_, 0, sizeof(client_id_with_password_));
|
|
||||||
memcpy(client_id_with_password_, cd_cache_.client_id_with_password,
|
|
||||||
sizeof(client_id_with_password_));
|
|
||||||
|
|
||||||
if (strchr(client_id_with_password_, '@') != nullptr) {
|
if (strchr(client_id_with_password_, '@') != nullptr) {
|
||||||
std::string id, password;
|
std::string id, password;
|
||||||
const char* at_pos = strchr(client_id_with_password_, '@');
|
const char* at_pos = strchr(client_id_with_password_, '@');
|
||||||
@@ -273,9 +340,6 @@ int Render::LoadSettingsFromCacheFile() {
|
|||||||
password_saved_[sizeof(password_saved_) - 1] = '\0';
|
password_saved_[sizeof(password_saved_) - 1] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(aes128_key_, cd_cache_.key, sizeof(cd_cache_.key));
|
|
||||||
memcpy(aes128_iv_, cd_cache_.iv, sizeof(cd_cache_.iv));
|
|
||||||
|
|
||||||
thumbnail_.reset();
|
thumbnail_.reset();
|
||||||
thumbnail_ = std::make_shared<Thumbnail>(cache_path_ + "/thumbnails/",
|
thumbnail_ = std::make_shared<Thumbnail>(cache_path_ + "/thumbnails/",
|
||||||
aes128_key_, aes128_iv_);
|
aes128_key_, aes128_iv_);
|
||||||
@@ -473,18 +537,75 @@ int Render::CreateConnectionPeer() {
|
|||||||
std::string signal_server_ip;
|
std::string signal_server_ip;
|
||||||
int signal_server_port;
|
int signal_server_port;
|
||||||
int coturn_server_port;
|
int coturn_server_port;
|
||||||
std::string tls_cert_path;
|
std::string tls_cert_fingerprint;
|
||||||
|
|
||||||
if (config_center_->IsSelfHosted()) {
|
if (config_center_->IsSelfHosted()) {
|
||||||
signal_server_ip = config_center_->GetSignalServerHost();
|
signal_server_ip = config_center_->GetSignalServerHost();
|
||||||
signal_server_port = config_center_->GetSignalServerPort();
|
signal_server_port = config_center_->GetSignalServerPort();
|
||||||
coturn_server_port = config_center_->GetCoturnServerPort();
|
coturn_server_port = config_center_->GetCoturnServerPort();
|
||||||
tls_cert_path = config_center_->GetCertFilePath();
|
tls_cert_fingerprint = config_center_->GetCertFingerprint();
|
||||||
|
|
||||||
|
std::string current_self_hosted_ip = config_center_->GetSignalServerHost();
|
||||||
|
bool use_cached_id = false;
|
||||||
|
|
||||||
|
// Check secure_cache_v2.enc exists or not
|
||||||
|
std::ifstream v2_file(cache_path_ + "/secure_cache_v2.enc",
|
||||||
|
std::ios::binary);
|
||||||
|
if (v2_file.good()) {
|
||||||
|
CDCacheV2 temp_cache;
|
||||||
|
v2_file.read(reinterpret_cast<char*>(&temp_cache), sizeof(CDCacheV2));
|
||||||
|
v2_file.close();
|
||||||
|
|
||||||
|
if (strlen(temp_cache.self_hosted_id) > 0) {
|
||||||
|
memset(&self_hosted_id_, 0, sizeof(self_hosted_id_));
|
||||||
|
strncpy(self_hosted_id_, temp_cache.self_hosted_id,
|
||||||
|
sizeof(self_hosted_id_) - 1);
|
||||||
|
self_hosted_id_[sizeof(self_hosted_id_) - 1] = '\0';
|
||||||
|
use_cached_id = true;
|
||||||
|
|
||||||
|
std::string id, password;
|
||||||
|
const char* at_pos = strchr(self_hosted_id_, '@');
|
||||||
|
if (at_pos == nullptr) {
|
||||||
|
id = self_hosted_id_;
|
||||||
|
password.clear();
|
||||||
|
} else {
|
||||||
|
id.assign(self_hosted_id_, at_pos - self_hosted_id_);
|
||||||
|
password = at_pos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&client_id_, 0, sizeof(client_id_));
|
||||||
|
strncpy(client_id_, id.c_str(), sizeof(client_id_) - 1);
|
||||||
|
client_id_[sizeof(client_id_) - 1] = '\0';
|
||||||
|
|
||||||
|
memset(&password_saved_, 0, sizeof(password_saved_));
|
||||||
|
strncpy(password_saved_, password.c_str(), sizeof(password_saved_) - 1);
|
||||||
|
password_saved_[sizeof(password_saved_) - 1] = '\0';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
memset(&self_hosted_id_, 0, sizeof(self_hosted_id_));
|
||||||
|
LOG_INFO(
|
||||||
|
"secure_cache_v2.enc not found, will use empty id to get new id from "
|
||||||
|
"server");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (use_cached_id && strlen(self_hosted_id_) > 0) {
|
||||||
|
memset(&self_hosted_user_id_, 0, sizeof(self_hosted_user_id_));
|
||||||
|
strncpy(self_hosted_user_id_, self_hosted_id_,
|
||||||
|
sizeof(self_hosted_user_id_) - 1);
|
||||||
|
self_hosted_user_id_[sizeof(self_hosted_user_id_) - 1] = '\0';
|
||||||
|
params_.user_id = self_hosted_user_id_;
|
||||||
|
} else {
|
||||||
|
memset(&self_hosted_user_id_, 0, sizeof(self_hosted_user_id_));
|
||||||
|
params_.user_id = self_hosted_user_id_;
|
||||||
|
LOG_INFO(
|
||||||
|
"Using empty id for self-hosted server, server will assign new id");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
signal_server_ip = config_center_->GetDefaultServerHost();
|
signal_server_ip = config_center_->GetDefaultServerHost();
|
||||||
signal_server_port = config_center_->GetDefaultSignalServerPort();
|
signal_server_port = config_center_->GetDefaultSignalServerPort();
|
||||||
coturn_server_port = config_center_->GetDefaultCoturnServerPort();
|
coturn_server_port = config_center_->GetDefaultCoturnServerPort();
|
||||||
tls_cert_path = config_center_->GetDefaultCertFilePath();
|
tls_cert_fingerprint = config_center_->GetDefaultCertFingerprint();
|
||||||
|
params_.user_id = client_id_with_password_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// self hosted server config
|
// self hosted server config
|
||||||
@@ -528,9 +649,30 @@ int Render::CreateConnectionPeer() {
|
|||||||
strncpy((char*)params_.turn_server_password, "crossdeskpw",
|
strncpy((char*)params_.turn_server_password, "crossdeskpw",
|
||||||
sizeof(params_.turn_server_password) - 1);
|
sizeof(params_.turn_server_password) - 1);
|
||||||
params_.turn_server_password[sizeof(params_.turn_server_password) - 1] = '\0';
|
params_.turn_server_password[sizeof(params_.turn_server_password) - 1] = '\0';
|
||||||
strncpy(params_.tls_cert_path, tls_cert_path.c_str(),
|
strncpy(params_.tls_cert_fingerprint, tls_cert_fingerprint.c_str(),
|
||||||
sizeof(params_.tls_cert_path) - 1);
|
sizeof(params_.tls_cert_fingerprint) - 1);
|
||||||
params_.tls_cert_path[sizeof(params_.tls_cert_path) - 1] = '\0';
|
params_.tls_cert_fingerprint[sizeof(params_.tls_cert_fingerprint) - 1] = '\0';
|
||||||
|
|
||||||
|
if (config_center_->IsSelfHosted()) {
|
||||||
|
params_.on_cert_fingerprint = [](const char* fingerprint, void* user_data) {
|
||||||
|
Render* render = static_cast<Render*>(user_data);
|
||||||
|
if (render && render->config_center_) {
|
||||||
|
render->config_center_->SetCertFingerprint(fingerprint);
|
||||||
|
LOG_INFO("Saved self-hosted certificate fingerprint: {}", fingerprint);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
params_.fingerprint_user_data = this;
|
||||||
|
} else {
|
||||||
|
params_.on_cert_fingerprint = [](const char* fingerprint, void* user_data) {
|
||||||
|
Render* render = static_cast<Render*>(user_data);
|
||||||
|
if (render && render->config_center_) {
|
||||||
|
render->config_center_->SetDefaultCertFingerprint(fingerprint);
|
||||||
|
LOG_INFO("Saved default server certificate fingerprint: {}",
|
||||||
|
fingerprint);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
params_.fingerprint_user_data = this;
|
||||||
|
}
|
||||||
|
|
||||||
strncpy(params_.log_path, dll_log_path_.c_str(),
|
strncpy(params_.log_path, dll_log_path_.c_str(),
|
||||||
sizeof(params_.log_path) - 1);
|
sizeof(params_.log_path) - 1);
|
||||||
@@ -554,7 +696,6 @@ int Render::CreateConnectionPeer() {
|
|||||||
params_.on_connection_status = OnConnectionStatusCb;
|
params_.on_connection_status = OnConnectionStatusCb;
|
||||||
params_.net_status_report = NetStatusReport;
|
params_.net_status_report = NetStatusReport;
|
||||||
|
|
||||||
params_.user_id = client_id_with_password_;
|
|
||||||
params_.user_data = this;
|
params_.user_data = this;
|
||||||
|
|
||||||
peer_ = CreatePeer(¶ms_);
|
peer_ = CreatePeer(¶ms_);
|
||||||
@@ -572,7 +713,8 @@ int Render::CreateConnectionPeer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AddAudioStream(peer_, audio_label_.c_str());
|
AddAudioStream(peer_, audio_label_.c_str());
|
||||||
AddDataStream(peer_, data_label_.c_str());
|
AddDataStream(peer_, data_label_.c_str(), false);
|
||||||
|
AddDataStream(peer_, file_label_.c_str(), true);
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class Render {
|
|||||||
PeerPtr* peer_ = nullptr;
|
PeerPtr* peer_ = nullptr;
|
||||||
std::string audio_label_ = "control_audio";
|
std::string audio_label_ = "control_audio";
|
||||||
std::string data_label_ = "control_data";
|
std::string data_label_ = "control_data";
|
||||||
|
std::string file_label_ = "file";
|
||||||
std::string local_id_ = "";
|
std::string local_id_ = "";
|
||||||
std::string remote_id_ = "";
|
std::string remote_id_ = "";
|
||||||
bool exit_ = false;
|
bool exit_ = false;
|
||||||
@@ -277,8 +278,25 @@ class Render {
|
|||||||
unsigned char iv[16];
|
unsigned char iv[16];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CDCacheV2 {
|
||||||
|
char client_id_with_password[17];
|
||||||
|
int language;
|
||||||
|
int video_quality;
|
||||||
|
int video_frame_rate;
|
||||||
|
int video_encode_format;
|
||||||
|
bool enable_hardware_video_codec;
|
||||||
|
bool enable_turn;
|
||||||
|
bool enable_srtp;
|
||||||
|
|
||||||
|
unsigned char key[16];
|
||||||
|
unsigned char iv[16];
|
||||||
|
|
||||||
|
char self_hosted_id[17];
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CDCache cd_cache_;
|
CDCache cd_cache_;
|
||||||
|
CDCacheV2 cd_cache_v2_;
|
||||||
std::mutex cd_cache_mutex_;
|
std::mutex cd_cache_mutex_;
|
||||||
std::unique_ptr<ConfigCenter> config_center_;
|
std::unique_ptr<ConfigCenter> config_center_;
|
||||||
ConfigCenter::LANGUAGE localization_language_ =
|
ConfigCenter::LANGUAGE localization_language_ =
|
||||||
@@ -447,6 +465,7 @@ class Render {
|
|||||||
std::string video_secondary_label_ = "secondary_display";
|
std::string video_secondary_label_ = "secondary_display";
|
||||||
std::string audio_label_ = "audio";
|
std::string audio_label_ = "audio";
|
||||||
std::string data_label_ = "data";
|
std::string data_label_ = "data";
|
||||||
|
std::string file_label_ = "file";
|
||||||
Params params_;
|
Params params_;
|
||||||
SDL_AudioDeviceID input_dev_;
|
SDL_AudioDeviceID input_dev_;
|
||||||
SDL_AudioDeviceID output_dev_;
|
SDL_AudioDeviceID output_dev_;
|
||||||
@@ -470,6 +489,8 @@ class Render {
|
|||||||
char client_id_display_[12] = "";
|
char client_id_display_[12] = "";
|
||||||
char client_id_with_password_[17] = "";
|
char client_id_with_password_[17] = "";
|
||||||
char password_saved_[7] = "";
|
char password_saved_[7] = "";
|
||||||
|
char self_hosted_id_[17] = "";
|
||||||
|
char self_hosted_user_id_[17] = "";
|
||||||
int language_button_value_ = 0;
|
int language_button_value_ = 0;
|
||||||
int video_quality_button_value_ = 0;
|
int video_quality_button_value_ = 0;
|
||||||
int video_frame_rate_button_value_ = 1;
|
int video_frame_rate_button_value_ = 1;
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
|
#include <chrono>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
#include "device_controller.h"
|
#include "device_controller.h"
|
||||||
|
#include "file_transfer.h"
|
||||||
#include "localization.h"
|
#include "localization.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
@@ -303,6 +308,12 @@ void Render::OnReceiveDataBufferCb(const char* data, size_t size,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// try to parse as file-transfer chunk first
|
||||||
|
static FileReceiver receiver;
|
||||||
|
if (receiver.OnData(data, size)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::string json_str(data, size);
|
std::string json_str(data, size);
|
||||||
RemoteAction remote_action;
|
RemoteAction remote_action;
|
||||||
|
|
||||||
@@ -556,24 +567,93 @@ void Render::NetStatusReport(const char* client_id, size_t client_id_size,
|
|||||||
password = at_pos + 1;
|
password = at_pos + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(&render->client_id_, 0, sizeof(render->client_id_));
|
bool is_self_hosted = render->config_center_->IsSelfHosted();
|
||||||
strncpy(render->client_id_, id.c_str(), sizeof(render->client_id_) - 1);
|
|
||||||
render->client_id_[sizeof(render->client_id_) - 1] = '\0';
|
|
||||||
|
|
||||||
memset(&render->password_saved_, 0, sizeof(render->password_saved_));
|
if (is_self_hosted) {
|
||||||
strncpy(render->password_saved_, password.c_str(),
|
memset(&render->client_id_, 0, sizeof(render->client_id_));
|
||||||
sizeof(render->password_saved_) - 1);
|
strncpy(render->client_id_, id.c_str(), sizeof(render->client_id_) - 1);
|
||||||
render->password_saved_[sizeof(render->password_saved_) - 1] = '\0';
|
render->client_id_[sizeof(render->client_id_) - 1] = '\0';
|
||||||
|
|
||||||
memset(&render->client_id_with_password_, 0,
|
memset(&render->password_saved_, 0, sizeof(render->password_saved_));
|
||||||
sizeof(render->client_id_with_password_));
|
strncpy(render->password_saved_, password.c_str(),
|
||||||
strncpy(render->client_id_with_password_, client_id,
|
sizeof(render->password_saved_) - 1);
|
||||||
sizeof(render->client_id_with_password_) - 1);
|
render->password_saved_[sizeof(render->password_saved_) - 1] = '\0';
|
||||||
render->client_id_with_password_[sizeof(render->client_id_with_password_) -
|
|
||||||
|
memset(&render->self_hosted_id_, 0, sizeof(render->self_hosted_id_));
|
||||||
|
strncpy(render->self_hosted_id_, client_id,
|
||||||
|
sizeof(render->self_hosted_id_) - 1);
|
||||||
|
render->self_hosted_id_[sizeof(render->self_hosted_id_) - 1] = '\0';
|
||||||
|
|
||||||
|
LOG_INFO("Use self-hosted client id [{}] and save to cache file", id);
|
||||||
|
|
||||||
|
render->cd_cache_mutex_.lock();
|
||||||
|
|
||||||
|
std::ifstream v2_file_read(render->cache_path_ + "/secure_cache_v2.enc",
|
||||||
|
std::ios::binary);
|
||||||
|
if (v2_file_read.good()) {
|
||||||
|
v2_file_read.read(reinterpret_cast<char*>(&render->cd_cache_v2_),
|
||||||
|
sizeof(CDCacheV2));
|
||||||
|
v2_file_read.close();
|
||||||
|
} else {
|
||||||
|
memset(&render->cd_cache_v2_, 0, sizeof(CDCacheV2));
|
||||||
|
memset(&render->cd_cache_v2_.client_id_with_password, 0,
|
||||||
|
sizeof(render->cd_cache_v2_.client_id_with_password));
|
||||||
|
strncpy(render->cd_cache_v2_.client_id_with_password,
|
||||||
|
render->client_id_with_password_,
|
||||||
|
sizeof(render->cd_cache_v2_.client_id_with_password));
|
||||||
|
memcpy(&render->cd_cache_v2_.key, &render->aes128_key_,
|
||||||
|
sizeof(render->cd_cache_v2_.key));
|
||||||
|
memcpy(&render->cd_cache_v2_.iv, &render->aes128_iv_,
|
||||||
|
sizeof(render->cd_cache_v2_.iv));
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&render->cd_cache_v2_.self_hosted_id, 0,
|
||||||
|
sizeof(render->cd_cache_v2_.self_hosted_id));
|
||||||
|
strncpy(render->cd_cache_v2_.self_hosted_id, client_id,
|
||||||
|
sizeof(render->cd_cache_v2_.self_hosted_id) - 1);
|
||||||
|
render->cd_cache_v2_
|
||||||
|
.self_hosted_id[sizeof(render->cd_cache_v2_.self_hosted_id) - 1] =
|
||||||
|
'\0';
|
||||||
|
|
||||||
|
memset(&render->cd_cache_v2_.client_id_with_password, 0,
|
||||||
|
sizeof(render->cd_cache_v2_.client_id_with_password));
|
||||||
|
strncpy(render->cd_cache_v2_.client_id_with_password,
|
||||||
|
render->client_id_with_password_,
|
||||||
|
sizeof(render->cd_cache_v2_.client_id_with_password));
|
||||||
|
memcpy(&render->cd_cache_v2_.key, &render->aes128_key_,
|
||||||
|
sizeof(render->cd_cache_v2_.key));
|
||||||
|
memcpy(&render->cd_cache_v2_.iv, &render->aes128_iv_,
|
||||||
|
sizeof(render->cd_cache_v2_.iv));
|
||||||
|
std::ofstream cd_cache_v2_file(
|
||||||
|
render->cache_path_ + "/secure_cache_v2.enc", std::ios::binary);
|
||||||
|
if (cd_cache_v2_file) {
|
||||||
|
cd_cache_v2_file.write(reinterpret_cast<char*>(&render->cd_cache_v2_),
|
||||||
|
sizeof(CDCacheV2));
|
||||||
|
cd_cache_v2_file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
render->cd_cache_mutex_.unlock();
|
||||||
|
} else {
|
||||||
|
memset(&render->client_id_, 0, sizeof(render->client_id_));
|
||||||
|
strncpy(render->client_id_, id.c_str(), sizeof(render->client_id_) - 1);
|
||||||
|
render->client_id_[sizeof(render->client_id_) - 1] = '\0';
|
||||||
|
|
||||||
|
memset(&render->password_saved_, 0, sizeof(render->password_saved_));
|
||||||
|
strncpy(render->password_saved_, password.c_str(),
|
||||||
|
sizeof(render->password_saved_) - 1);
|
||||||
|
render->password_saved_[sizeof(render->password_saved_) - 1] = '\0';
|
||||||
|
|
||||||
|
memset(&render->client_id_with_password_, 0,
|
||||||
|
sizeof(render->client_id_with_password_));
|
||||||
|
strncpy(render->client_id_with_password_, client_id,
|
||||||
|
sizeof(render->client_id_with_password_) - 1);
|
||||||
|
render
|
||||||
|
->client_id_with_password_[sizeof(render->client_id_with_password_) -
|
||||||
1] = '\0';
|
1] = '\0';
|
||||||
|
|
||||||
LOG_INFO("Use client id [{}] and save id into cache file", id);
|
LOG_INFO("Use client id [{}] and save id into cache file", id);
|
||||||
render->SaveSettingsIntoCacheFile();
|
render->SaveSettingsIntoCacheFile();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string remote_id(user_id, user_id_size);
|
std::string remote_id(user_id, user_id_size);
|
||||||
|
|||||||
@@ -1,10 +1,29 @@
|
|||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "file_transfer.h"
|
||||||
#include "layout.h"
|
#include "layout.h"
|
||||||
#include "localization.h"
|
#include "localization.h"
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
#include "render.h"
|
#include "render.h"
|
||||||
|
#include "tinyfiledialogs.h"
|
||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
|
std::string OpenFileDialog(std::string title) {
|
||||||
|
const char* path = tinyfd_openFileDialog(title.c_str(),
|
||||||
|
"", // default path
|
||||||
|
0, // number of filters
|
||||||
|
nullptr, // filters
|
||||||
|
nullptr, // filter description
|
||||||
|
0 // no multiple selection
|
||||||
|
);
|
||||||
|
|
||||||
|
return path ? path : "";
|
||||||
|
}
|
||||||
|
|
||||||
int CountDigits(int number) {
|
int CountDigits(int number) {
|
||||||
if (number == 0) return 1;
|
if (number == 0) return 1;
|
||||||
return (int)std::floor(std::log10(std::abs(number))) + 1;
|
return (int)std::floor(std::log10(std::abs(number))) + 1;
|
||||||
@@ -41,14 +60,14 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
|
|||||||
if (props->control_bar_expand_) {
|
if (props->control_bar_expand_) {
|
||||||
ImGui::SetCursorPosX(props->is_control_bar_in_left_
|
ImGui::SetCursorPosX(props->is_control_bar_in_left_
|
||||||
? props->control_window_width_ * 1.03f
|
? props->control_window_width_ * 1.03f
|
||||||
: props->control_window_width_ * 0.2f);
|
: props->control_window_width_ * 0.17f);
|
||||||
|
|
||||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||||
if (!props->is_control_bar_in_left_) {
|
if (!props->is_control_bar_in_left_) {
|
||||||
draw_list->AddLine(
|
draw_list->AddLine(
|
||||||
ImVec2(ImGui::GetCursorScreenPos().x - button_height * 0.56f,
|
ImVec2(ImGui::GetCursorScreenPos().x - button_height * 0.5f,
|
||||||
ImGui::GetCursorScreenPos().y + button_height * 0.2f),
|
ImGui::GetCursorScreenPos().y + button_height * 0.2f),
|
||||||
ImVec2(ImGui::GetCursorScreenPos().x - button_height * 0.56f,
|
ImVec2(ImGui::GetCursorScreenPos().x - button_height * 0.5f,
|
||||||
ImGui::GetCursorScreenPos().y + button_height * 0.8f),
|
ImGui::GetCursorScreenPos().y + button_height * 0.8f),
|
||||||
IM_COL32(178, 178, 178, 255), 2.0f);
|
IM_COL32(178, 178, 178, 255), 2.0f);
|
||||||
}
|
}
|
||||||
@@ -175,6 +194,39 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
|
|||||||
line_thickness);
|
line_thickness);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
std::string open_folder = ICON_FA_FOLDER_OPEN;
|
||||||
|
if (ImGui::Button(open_folder.c_str(),
|
||||||
|
ImVec2(button_width, button_height))) {
|
||||||
|
std::string title =
|
||||||
|
localization::select_file[localization_language_index_];
|
||||||
|
std::string path = OpenFileDialog(title);
|
||||||
|
if (!path.empty()) {
|
||||||
|
LOG_INFO("Selected file: {}", path.c_str());
|
||||||
|
|
||||||
|
// Send selected file over file data channel in a background thread.
|
||||||
|
auto peer = props->peer_;
|
||||||
|
std::filesystem::path file_path = std::filesystem::path(path);
|
||||||
|
std::string file_label = file_label_;
|
||||||
|
|
||||||
|
std::thread([peer, file_path, file_label]() {
|
||||||
|
FileSender sender;
|
||||||
|
int ret = sender.SendFile(
|
||||||
|
file_path, file_path.filename().string(),
|
||||||
|
[peer, file_label](const char* buf, size_t sz) -> int {
|
||||||
|
return SendReliableDataFrame(peer, buf, sz, file_label.c_str());
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERROR("FileSender::SendFile failed for [{}], ret={}",
|
||||||
|
file_path.string().c_str(), ret);
|
||||||
|
} else {
|
||||||
|
LOG_INFO("File send finished: {}", file_path.string().c_str());
|
||||||
|
}
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
// net traffic stats button
|
// net traffic stats button
|
||||||
bool button_color_style_pushed = false;
|
bool button_color_style_pushed = false;
|
||||||
@@ -238,9 +290,9 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
|
|||||||
|
|
||||||
if (props->is_control_bar_in_left_) {
|
if (props->is_control_bar_in_left_) {
|
||||||
draw_list->AddLine(
|
draw_list->AddLine(
|
||||||
ImVec2(ImGui::GetCursorScreenPos().x + button_height * 0.2f,
|
ImVec2(ImGui::GetCursorScreenPos().x + button_height * 0.13f,
|
||||||
ImGui::GetCursorScreenPos().y + button_height * 0.2f),
|
ImGui::GetCursorScreenPos().y + button_height * 0.2f),
|
||||||
ImVec2(ImGui::GetCursorScreenPos().x + button_height * 0.2f,
|
ImVec2(ImGui::GetCursorScreenPos().x + button_height * 0.13f,
|
||||||
ImGui::GetCursorScreenPos().y + button_height * 0.8f),
|
ImGui::GetCursorScreenPos().y + button_height * 0.8f),
|
||||||
IM_COL32(178, 178, 178, 255), 2.0f);
|
IM_COL32(178, 178, 178, 255), 2.0f);
|
||||||
}
|
}
|
||||||
@@ -250,7 +302,7 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
|
|||||||
|
|
||||||
float expand_button_pos_x =
|
float expand_button_pos_x =
|
||||||
props->control_bar_expand_ ? (props->is_control_bar_in_left_
|
props->control_bar_expand_ ? (props->is_control_bar_in_left_
|
||||||
? props->control_window_width_ * 1.91f
|
? props->control_window_width_ * 1.917f
|
||||||
: props->control_window_width_ * 0.03f)
|
: props->control_window_width_ * 0.03f)
|
||||||
: (props->is_control_bar_in_left_
|
: (props->is_control_bar_in_left_
|
||||||
? props->control_window_width_ * 1.02f
|
? props->control_window_width_ * 1.02f
|
||||||
|
|||||||
@@ -214,20 +214,31 @@ int Render::SelfHostedServerWindow() {
|
|||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
|
// {
|
||||||
|
// ImGui::AlignTextToFramePadding();
|
||||||
|
// ImGui::Text(
|
||||||
|
// "%s",
|
||||||
|
// localization::reset_cert_fingerprint[localization_language_index_]
|
||||||
|
// .c_str());
|
||||||
|
// ImGui::SameLine();
|
||||||
|
// if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
||||||
|
// ImGui::SetCursorPosX(title_bar_button_width_ * 2.5f);
|
||||||
|
// } else {
|
||||||
|
// ImGui::SetCursorPosX(title_bar_button_width_ * 3.43f);
|
||||||
|
// }
|
||||||
|
// ImGui::SetNextItemWidth(title_bar_button_width_ * 3.8f);
|
||||||
|
|
||||||
|
// ShowSimpleFileBrowser();
|
||||||
|
// }
|
||||||
|
|
||||||
{
|
{
|
||||||
ImGui::AlignTextToFramePadding();
|
ImGui::AlignTextToFramePadding();
|
||||||
ImGui::Text("%s", localization::self_hosted_server_certificate_path
|
if (ImGui::Button(localization::reset_cert_fingerprint
|
||||||
[localization_language_index_]
|
[localization_language_index_]
|
||||||
.c_str());
|
.c_str())) {
|
||||||
ImGui::SameLine();
|
config_center_->ClearCertFingerprint();
|
||||||
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
|
LOG_INFO("Certificate fingerprint cleared by user");
|
||||||
ImGui::SetCursorPosX(title_bar_button_width_ * 2.5f);
|
|
||||||
} else {
|
|
||||||
ImGui::SetCursorPosX(title_bar_button_width_ * 3.43f);
|
|
||||||
}
|
}
|
||||||
ImGui::SetNextItemWidth(title_bar_button_width_ * 3.8f);
|
|
||||||
|
|
||||||
ShowSimpleFileBrowser();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream_window_inited_) {
|
if (stream_window_inited_) {
|
||||||
|
|||||||
286
src/tools/file_transfer.cpp
Normal file
286
src/tools/file_transfer.cpp
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
#include "file_transfer.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "rd_log.h"
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::atomic<uint32_t> g_next_file_id{1};
|
||||||
|
constexpr uint32_t kFileChunkMagic = 0x4A4E544D; // 'JNTM'
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
uint32_t FileSender::NextFileId() { return g_next_file_id.fetch_add(1); }
|
||||||
|
|
||||||
|
int FileSender::SendFile(const std::filesystem::path& path,
|
||||||
|
const std::string& label, const SendFunc& send,
|
||||||
|
std::size_t chunk_size) {
|
||||||
|
if (!send) {
|
||||||
|
LOG_ERROR("FileSender::SendFile: send function is empty");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
if (!std::filesystem::exists(path, ec) ||
|
||||||
|
!std::filesystem::is_regular_file(path, ec)) {
|
||||||
|
LOG_ERROR("FileSender::SendFile: file [{}] not found or not regular",
|
||||||
|
path.string().c_str());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t total_size = std::filesystem::file_size(path, ec);
|
||||||
|
if (ec) {
|
||||||
|
LOG_ERROR("FileSender::SendFile: failed to get size of [{}]: {}",
|
||||||
|
path.string().c_str(), ec.message().c_str());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream ifs(path, std::ios::binary);
|
||||||
|
if (!ifs.is_open()) {
|
||||||
|
LOG_ERROR("FileSender::SendFile: failed to open [{}]",
|
||||||
|
path.string().c_str());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t file_id = NextFileId();
|
||||||
|
uint64_t offset = 0;
|
||||||
|
bool is_first = true;
|
||||||
|
std::string file_name = label.empty() ? path.filename().string() : label;
|
||||||
|
|
||||||
|
std::vector<char> buffer;
|
||||||
|
buffer.resize(chunk_size);
|
||||||
|
|
||||||
|
while (ifs && offset < total_size) {
|
||||||
|
uint64_t remaining = total_size - offset;
|
||||||
|
uint32_t to_read =
|
||||||
|
static_cast<uint32_t>(std::min<uint64_t>(remaining, chunk_size));
|
||||||
|
|
||||||
|
ifs.read(buffer.data(), static_cast<std::streamsize>(to_read));
|
||||||
|
std::streamsize bytes_read = ifs.gcount();
|
||||||
|
if (bytes_read <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_last = (offset + static_cast<uint64_t>(bytes_read) >= total_size);
|
||||||
|
const std::string* name_ptr = is_first ? &file_name : nullptr;
|
||||||
|
|
||||||
|
std::vector<char> chunk = BuildChunk(
|
||||||
|
file_id, offset, total_size, buffer.data(),
|
||||||
|
static_cast<uint32_t>(bytes_read), name_ptr, is_first, is_last);
|
||||||
|
|
||||||
|
int ret = send(chunk.data(), chunk.size());
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERROR("FileSender::SendFile: send failed for [{}], ret={}",
|
||||||
|
path.string().c_str(), ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += static_cast<uint64_t>(bytes_read);
|
||||||
|
is_first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> FileSender::BuildChunk(uint32_t file_id, uint64_t offset,
|
||||||
|
uint64_t total_size, const char* data,
|
||||||
|
uint32_t data_size,
|
||||||
|
const std::string* file_name,
|
||||||
|
bool is_first, bool is_last) {
|
||||||
|
FileChunkHeader header{};
|
||||||
|
header.magic = kFileChunkMagic;
|
||||||
|
header.file_id = file_id;
|
||||||
|
header.offset = offset;
|
||||||
|
header.total_size = total_size;
|
||||||
|
header.chunk_size = data_size;
|
||||||
|
header.name_len =
|
||||||
|
(file_name && is_first) ? static_cast<uint16_t>(file_name->size()) : 0;
|
||||||
|
header.flags = 0;
|
||||||
|
if (is_first) header.flags |= 0x01;
|
||||||
|
if (is_last) header.flags |= 0x02;
|
||||||
|
|
||||||
|
std::size_t total_size_bytes =
|
||||||
|
sizeof(FileChunkHeader) + header.name_len + header.chunk_size;
|
||||||
|
|
||||||
|
std::vector<char> buffer;
|
||||||
|
buffer.resize(total_size_bytes);
|
||||||
|
|
||||||
|
std::size_t offset_bytes = 0;
|
||||||
|
memcpy(buffer.data() + offset_bytes, &header, sizeof(FileChunkHeader));
|
||||||
|
offset_bytes += sizeof(FileChunkHeader);
|
||||||
|
|
||||||
|
if (header.name_len > 0 && file_name) {
|
||||||
|
memcpy(buffer.data() + offset_bytes, file_name->data(), header.name_len);
|
||||||
|
offset_bytes += header.name_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.chunk_size > 0 && data) {
|
||||||
|
memcpy(buffer.data() + offset_bytes, data, header.chunk_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- FileReceiver ----------
|
||||||
|
|
||||||
|
FileReceiver::FileReceiver() : output_dir_(GetDefaultDesktopPath()) {}
|
||||||
|
|
||||||
|
FileReceiver::FileReceiver(const std::filesystem::path& output_dir)
|
||||||
|
: output_dir_(output_dir) {
|
||||||
|
std::error_code ec;
|
||||||
|
if (!output_dir_.empty()) {
|
||||||
|
std::filesystem::create_directories(output_dir_, ec);
|
||||||
|
if (ec) {
|
||||||
|
LOG_ERROR("FileReceiver: failed to create output dir [{}]: {}",
|
||||||
|
output_dir_.string().c_str(), ec.message().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path FileReceiver::GetDefaultDesktopPath() {
|
||||||
|
#ifdef _WIN32
|
||||||
|
const char* home_env = std::getenv("USERPROFILE");
|
||||||
|
#else
|
||||||
|
const char* home_env = std::getenv("HOME");
|
||||||
|
#endif
|
||||||
|
if (!home_env) {
|
||||||
|
return std::filesystem::path{};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path desktop_path =
|
||||||
|
std::filesystem::path(home_env) / "Desktop";
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::create_directories(desktop_path, ec);
|
||||||
|
if (ec) {
|
||||||
|
LOG_ERROR("FileReceiver: failed to create desktop directory [{}]: {}",
|
||||||
|
desktop_path.string().c_str(), ec.message().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return desktop_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileReceiver::OnData(const char* data, size_t size) {
|
||||||
|
if (!data || size < sizeof(FileChunkHeader)) {
|
||||||
|
LOG_ERROR("FileReceiver::OnData: invalid buffer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileChunkHeader header{};
|
||||||
|
memcpy(&header, data, sizeof(FileChunkHeader));
|
||||||
|
|
||||||
|
if (header.magic != kFileChunkMagic) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t header_and_name =
|
||||||
|
sizeof(FileChunkHeader) + static_cast<std::size_t>(header.name_len);
|
||||||
|
if (size < header_and_name ||
|
||||||
|
size < header_and_name + static_cast<std::size_t>(header.chunk_size)) {
|
||||||
|
LOG_ERROR("FileReceiver::OnData: buffer too small for header + payload");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* name_ptr = data + sizeof(FileChunkHeader);
|
||||||
|
std::string file_name;
|
||||||
|
const std::string* file_name_ptr = nullptr;
|
||||||
|
if (header.name_len > 0) {
|
||||||
|
file_name.assign(name_ptr,
|
||||||
|
name_ptr + static_cast<std::size_t>(header.name_len));
|
||||||
|
file_name_ptr = &file_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* payload = data + header_and_name;
|
||||||
|
std::size_t payload_size =
|
||||||
|
static_cast<std::size_t>(header.chunk_size); // may be 0
|
||||||
|
|
||||||
|
return HandleChunk(header, payload, payload_size, file_name_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileReceiver::HandleChunk(const FileChunkHeader& header,
|
||||||
|
const char* payload, size_t payload_size,
|
||||||
|
const std::string* file_name) {
|
||||||
|
auto it = contexts_.find(header.file_id);
|
||||||
|
if (it == contexts_.end()) {
|
||||||
|
// new file context must start with first chunk.
|
||||||
|
if ((header.flags & 0x01) == 0) {
|
||||||
|
LOG_ERROR("FileReceiver: received non-first chunk for unknown file_id={}",
|
||||||
|
header.file_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileContext ctx;
|
||||||
|
ctx.total_size = header.total_size;
|
||||||
|
|
||||||
|
std::string filename;
|
||||||
|
if (file_name && !file_name->empty()) {
|
||||||
|
filename = *file_name;
|
||||||
|
} else {
|
||||||
|
filename = "received_" + std::to_string(header.file_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.file_name = filename;
|
||||||
|
|
||||||
|
std::filesystem::path save_path = output_dir_.empty()
|
||||||
|
? std::filesystem::path(filename)
|
||||||
|
: output_dir_ / filename;
|
||||||
|
|
||||||
|
// if file exists, append timestamp.
|
||||||
|
std::error_code ec;
|
||||||
|
if (std::filesystem::exists(save_path, ec)) {
|
||||||
|
auto now = std::chrono::system_clock::now();
|
||||||
|
auto ts = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
now.time_since_epoch())
|
||||||
|
.count();
|
||||||
|
save_path = save_path.parent_path() /
|
||||||
|
(save_path.stem().string() + "_" + std::to_string(ts) +
|
||||||
|
save_path.extension().string());
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.ofs.open(save_path, std::ios::binary | std::ios::trunc);
|
||||||
|
if (!ctx.ofs.is_open()) {
|
||||||
|
LOG_ERROR("FileReceiver: failed to open [{}] for writing",
|
||||||
|
save_path.string().c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
contexts_.emplace(header.file_id, std::move(ctx));
|
||||||
|
it = contexts_.find(header.file_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileContext& ctx = it->second;
|
||||||
|
|
||||||
|
if (payload_size > 0 && payload) {
|
||||||
|
ctx.ofs.seekp(static_cast<std::streamoff>(header.offset), std::ios::beg);
|
||||||
|
ctx.ofs.write(payload, static_cast<std::streamsize>(payload_size));
|
||||||
|
if (!ctx.ofs.good()) {
|
||||||
|
LOG_ERROR("FileReceiver: write failed for file_id={}", header.file_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ctx.received += static_cast<uint64_t>(payload_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_last = (header.flags & 0x02) != 0;
|
||||||
|
if (is_last || ctx.received >= ctx.total_size) {
|
||||||
|
ctx.ofs.close();
|
||||||
|
|
||||||
|
std::filesystem::path saved_path =
|
||||||
|
output_dir_.empty() ? std::filesystem::path(ctx.file_name)
|
||||||
|
: output_dir_ / ctx.file_name;
|
||||||
|
|
||||||
|
LOG_INFO("FileReceiver: file received complete, file_id={}, size={}",
|
||||||
|
header.file_id, ctx.received);
|
||||||
|
|
||||||
|
if (on_file_complete_) {
|
||||||
|
on_file_complete_(saved_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
contexts_.erase(header.file_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crossdesk
|
||||||
99
src/tools/file_transfer.h
Normal file
99
src/tools/file_transfer.h
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2025-12-18
|
||||||
|
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _FILE_TRANSFER_H_
|
||||||
|
#define _FILE_TRANSFER_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct FileChunkHeader {
|
||||||
|
uint32_t magic; // magic to identify file-transfer chunks
|
||||||
|
uint32_t file_id; // unique id per file transfer
|
||||||
|
uint64_t offset; // offset in file
|
||||||
|
uint64_t total_size; // total file size
|
||||||
|
uint32_t chunk_size; // payload size in this chunk
|
||||||
|
uint16_t name_len; // filename length (bytes), only set on first chunk
|
||||||
|
uint8_t flags; // bit0: is_first, bit1: is_last, others reserved
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
class FileSender {
|
||||||
|
public:
|
||||||
|
using SendFunc = std::function<int(const char* data, size_t size)>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FileSender() = default;
|
||||||
|
|
||||||
|
// generate a new file id
|
||||||
|
static uint32_t NextFileId();
|
||||||
|
|
||||||
|
// synchronously send a file using the provided send function.
|
||||||
|
// `path` : full path to the local file.
|
||||||
|
// `label` : logical filename to send (usually path.filename()).
|
||||||
|
// `send` : callback that pushes one encoded chunk into the data channel.
|
||||||
|
// Return 0 on success, <0 on error.
|
||||||
|
int SendFile(const std::filesystem::path& path, const std::string& label,
|
||||||
|
const SendFunc& send, std::size_t chunk_size = 64 * 1024);
|
||||||
|
|
||||||
|
// build a single encoded chunk buffer according to FileChunkHeader protocol.
|
||||||
|
static std::vector<char> BuildChunk(uint32_t file_id, uint64_t offset,
|
||||||
|
uint64_t total_size, const char* data,
|
||||||
|
uint32_t data_size,
|
||||||
|
const std::string* file_name,
|
||||||
|
bool is_first, bool is_last);
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileReceiver {
|
||||||
|
public:
|
||||||
|
struct FileContext {
|
||||||
|
std::string file_name;
|
||||||
|
uint64_t total_size = 0;
|
||||||
|
uint64_t received = 0;
|
||||||
|
std::ofstream ofs;
|
||||||
|
};
|
||||||
|
|
||||||
|
using OnFileComplete =
|
||||||
|
std::function<void(const std::filesystem::path& saved_path)>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// save to default desktop directory.
|
||||||
|
FileReceiver();
|
||||||
|
|
||||||
|
// save to a specified directory.
|
||||||
|
explicit FileReceiver(const std::filesystem::path& output_dir);
|
||||||
|
|
||||||
|
// process one received data buffer (one chunk).
|
||||||
|
// return true if parsed and processed successfully, false otherwise.
|
||||||
|
bool OnData(const char* data, size_t size);
|
||||||
|
|
||||||
|
void SetOnFileComplete(OnFileComplete cb) { on_file_complete_ = cb; }
|
||||||
|
|
||||||
|
const std::filesystem::path& OutputDir() const { return output_dir_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::filesystem::path GetDefaultDesktopPath();
|
||||||
|
|
||||||
|
bool HandleChunk(const FileChunkHeader& header, const char* payload,
|
||||||
|
size_t payload_size, const std::string* file_name);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::filesystem::path output_dir_;
|
||||||
|
std::unordered_map<uint32_t, FileContext> contexts_;
|
||||||
|
OnFileComplete on_file_complete_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace crossdesk
|
||||||
|
|
||||||
|
#endif
|
||||||
Submodule submodules/minirtc updated: 0008123221...05ea5f9b2f
11
xmake.lua
11
xmake.lua
@@ -33,6 +33,7 @@ add_requires("imgui v1.92.1-docking", {configs = {sdl3 = true, sdl3_renderer = t
|
|||||||
add_requires("openssl3 3.3.2", {system = false})
|
add_requires("openssl3 3.3.2", {system = false})
|
||||||
add_requires("nlohmann_json 3.11.3")
|
add_requires("nlohmann_json 3.11.3")
|
||||||
add_requires("cpp-httplib v0.26.0", {configs = {ssl = true}})
|
add_requires("cpp-httplib v0.26.0", {configs = {ssl = true}})
|
||||||
|
add_requires("tinyfiledialogs 3.15.1")
|
||||||
|
|
||||||
if is_os("windows") then
|
if is_os("windows") then
|
||||||
add_requires("libyuv", "miniaudio 0.11.21")
|
add_requires("libyuv", "miniaudio 0.11.21")
|
||||||
@@ -168,13 +169,19 @@ target("version_checker")
|
|||||||
add_files("src/version_checker/*.cpp")
|
add_files("src/version_checker/*.cpp")
|
||||||
add_includedirs("src/version_checker", {public = true})
|
add_includedirs("src/version_checker", {public = true})
|
||||||
|
|
||||||
|
target("tools")
|
||||||
|
set_kind("object")
|
||||||
|
add_deps("rd_log")
|
||||||
|
add_files("src/tools/*.cpp")
|
||||||
|
add_includedirs("src/tools", {public = true})
|
||||||
|
|
||||||
target("gui")
|
target("gui")
|
||||||
set_kind("object")
|
set_kind("object")
|
||||||
add_packages("libyuv")
|
add_packages("libyuv", "tinyfiledialogs")
|
||||||
add_defines("CROSSDESK_VERSION=\"" .. (get_config("CROSSDESK_VERSION") or "Unknown") .. "\"")
|
add_defines("CROSSDESK_VERSION=\"" .. (get_config("CROSSDESK_VERSION") or "Unknown") .. "\"")
|
||||||
add_deps("rd_log", "common", "assets", "config_center", "minirtc",
|
add_deps("rd_log", "common", "assets", "config_center", "minirtc",
|
||||||
"path_manager", "screen_capturer", "speaker_capturer",
|
"path_manager", "screen_capturer", "speaker_capturer",
|
||||||
"device_controller", "thumbnail", "version_checker")
|
"device_controller", "thumbnail", "version_checker", "tools")
|
||||||
add_files("src/gui/*.cpp", "src/gui/panels/*.cpp", "src/gui/toolbars/*.cpp",
|
add_files("src/gui/*.cpp", "src/gui/panels/*.cpp", "src/gui/toolbars/*.cpp",
|
||||||
"src/gui/windows/*.cpp")
|
"src/gui/windows/*.cpp")
|
||||||
add_includedirs("src/gui", "src/gui/panels", "src/gui/toolbars",
|
add_includedirs("src/gui", "src/gui/panels", "src/gui/toolbars",
|
||||||
|
|||||||
Reference in New Issue
Block a user