Compare commits

...

21 Commits

Author SHA1 Message Date
dijunkun
adb6cee326 [fix] fix version check issue 2025-11-12 00:21:18 +08:00
dijunkun
941b5e5cdc [ci] update version.json when new tag pushed 2025-11-12 00:11:32 +08:00
dijunkun
c602dea58f [chore] update README 2025-11-11 22:30:17 +08:00
dijunkun
e9ced9fa4f [ci] switch macos-13 to macos-15-intel due to the macOS-13 based runner images are being deprecated 2025-11-11 22:28:35 +08:00
dijunkun
6ab5e7487f [chore] update README 2025-11-11 21:59:50 +08:00
dijunkun
e3143f3e7a [feat] add automatic version check 2025-11-11 15:38:31 +08:00
dijunkun
538c17d182 [feat] disable AV1 encoding when connected to a Web client 2025-11-11 10:52:28 +08:00
dijunkun
2ad32ec2b4 [feat] add VideoQuality configuration option to limit maximum video resolution 2025-11-11 10:07:35 +08:00
dijunkun
b28f1dca81 [feat] update MiniRTC module 2025-11-11 01:32:40 +08:00
dijunkun
6947f7e1c3 Merge branch 'self-hosted-server' into libdatachannel 2025-11-11 01:18:24 +08:00
dijunkun
b1df10c0de [fix] fix frame rate and video quality settings not being applied correctly, fixes #24 2025-11-11 01:11:33 +08:00
dijunkun
3817b222fd Merge branch 'libdatachannel' of https://github.com/kunkundi/crossdesk into libdatachannel 2025-11-11 01:00:19 +08:00
dijunkun
910cc9b587 [fix] stop devices control and capture when client peer destroyed 2025-11-11 00:59:54 +08:00
dijunkun
2ee3e93afe [feat] do not control mouse in debug mode 2025-11-11 00:56:23 +08:00
dijunkun
8875c6a6a1 [feat] update MiniRTC module 2025-11-11 00:53:10 +08:00
dijunkun
28062f5574 [config] use 60 fps streaming by default 2025-11-10 17:03:14 +08:00
dijunkun
017af3eea4 [feat] support multiple web clients connecting simultaneously 2025-11-10 16:54:56 +08:00
dijunkun
78eb069cc8 [feat] enable speaker capturer by default 2025-11-10 15:02:21 +08:00
dijunkun
0d591f769d [feat] send control commands via JSON 2025-11-06 17:31:06 +08:00
dijunkun
d4726355a7 [feat] update minirtc: support web streaming by using libdatachannel 2025-11-06 02:54:03 +08:00
dijunkun
b78c9cf7d1 [feat] optimize Windows display device names 2025-11-05 20:36:04 +08:00
24 changed files with 2750 additions and 2063 deletions

View File

@@ -144,7 +144,7 @@ jobs:
matrix:
include:
- arch: x64
runner: macos-13
runner: macos-15-intel
cache-key: intel
out-dir: ./build/macosx/x86_64/release/crossdesk
package_script: ./scripts/macosx/pkg_x64.sh
@@ -385,3 +385,19 @@ jobs:
remote_host: ${{ secrets.SERVER_HOST }}
remote_user: ${{ secrets.SERVER_USER }}
remote_key: ${{ secrets.SERVER_KEY }}
- name: Generate version.json
run: |
echo "{" > version.json
echo " \"latest_version\": \"${{ steps.version.outputs.VERSION_NUM }}\"" >> version.json
echo "}" >> version.json cat version.json
- name: Upload version.json to server
uses: burnett01/rsync-deployments@5.2
with:
switches: -avzr --delete
path: version.json
remote_path: /var/www/html/version/
remote_host: ${{ secrets.SERVER_HOST }}
remote_user: ${{ secrets.SERVER_USER }}
remote_key: ${{ secrets.SERVER_KEY }}

View File

@@ -15,10 +15,17 @@
## 简介
CrossDesk 是一个轻量级的跨平台远程桌面软件。
CrossDesk 是一个轻量级的跨平台远程桌面软件,支持 Web 端控制远程设备
CrossDesk 是 [MiniRTC](https://github.com/kunkundi/minirtc.git) 实时音视频传输库的实验性应用。MiniRTC 是一个轻量级的跨平台实时音视频传输库。它具有网络透传([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)视频软硬编解码H264/AV1音频编解码[Opus](https://github.com/xiph/opus)),信令交互,网络拥塞控制,传输加密([SRTP](https://tools.ietf.org/html/rfc3711))等基础能力。
## 系统要求
| 平台 | 最低版本 |
|----------------|---------------------------|
| **Windows** | Windows 10 及以上 (64 位) |
| **macOS** | macOS Intel 15.0 及以上 ( 大于 14.0 小于 15.0 的版本可自行编译实现兼容 )<br> macOS Apple Silicon 14.0 及以上 |
| **Linux** | Ubuntu 22.04 及以上 ( 低版本可自行编译实现兼容 ) |
## 使用
@@ -33,6 +40,10 @@ CrossDesk 是 [MiniRTC](https://github.com/kunkundi/minirtc.git) 实时音视频
发起连接前,可在设置中自定义配置项,如语言、视频编码格式等。
![settings](https://github.com/user-attachments/assets/8bc5468d-7bbb-4e30-95bd-da1f352ac08c)
### Web 客户端
浏览器访问 [CrossDesk Web Client](https://web.crossdesk.cn/)。
输入 **远程设备 ID****密码**,点击连接即可接入远程设备。
## 如何编译
依赖:
@@ -153,7 +164,7 @@ sudo docker run -d \
-v /path/to/your/certs:/crossdesk-server/certs \
-v /path/to/your/db:/crossdesk-server/db \
-v /path/to/your/logs:/crossdesk-server/logs \
crossdesk/crossdesk-server:v1.0.0
crossdesk/crossdesk-server:latest
```
上述命令中,用户需注意的参数如下:

View File

@@ -19,6 +19,15 @@ CrossDesk is a lightweight cross-platform remote desktop software.
CrossDesk is an experimental application of [MiniRTC](https://github.com/kunkundi/minirtc.git), a lightweight cross-platform real-time audio and video transmission library. MiniRTC provides fundamental capabilities including network traversal ([RFC5245](https://datatracker.ietf.org/doc/html/rfc5245)), video software/hardware encoding and decoding (H264/AV1), audio encoding/decoding ([Opus](https://github.com/xiph/opus)), signaling interaction, network congestion control, and transmission encryption ([SRTP](https://tools.ietf.org/html/rfc3711)).
## System Requirements
| Platform | Minimum Version |
|-----------|-----------------|
| **Windows** | Windows 10 or later (64-bit) |
| **macOS** | macOS Intel 15.0 or later *(versions between 14.0 and 15.0 can be built manually for compatibility)*<br>macOS Apple Silicon 14.0 or later |
| **Linux** | Ubuntu 22.04 or later *(older versions can be built manually for compatibility)* |
## Usage
Enter the remote desktop ID in the menu bars “Remote ID” field and click “→” to initiate a remote connection.
@@ -33,6 +42,11 @@ Before connecting, you can customize configuration options in the settings, such
![settings](https://github.com/user-attachments/assets/8bc5468d-7bbb-4e30-95bd-da1f352ac08c)
### Web Client
Visit [CrossDesk Web Client](https://web.crossdesk.cn/).
Enter the **Remote Device ID** and **Password**, then click Connect to access the remote device.
## How to build
Requirements:
@@ -158,7 +172,7 @@ sudo docker run -d \
-v /path/to/your/certs:/crossdesk-server/certs \
-v /path/to/your/db:/crossdesk-server/db \
-v /path/to/your/logs:/crossdesk-server/logs \
crossdesk/crossdesk-server:v1.0.0
crossdesk/crossdesk-server:v1.1.0
```
The parameters you need to pay attention to are as follows:

View File

@@ -72,7 +72,7 @@ class ConfigCenter {
LANGUAGE language_ = LANGUAGE::CHINESE;
VIDEO_QUALITY video_quality_ = VIDEO_QUALITY::MEDIUM;
VIDEO_FRAME_RATE video_frame_rate_ = VIDEO_FRAME_RATE::FPS_30;
VIDEO_FRAME_RATE video_frame_rate_ = VIDEO_FRAME_RATE::FPS_60;
VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::H264;
bool hardware_video_codec_ = false;
bool enable_turn_ = false;

View File

@@ -9,7 +9,11 @@
#include <stdio.h>
#include <nlohmann/json.hpp>
#include <string>
#include "display_info.h"
using json = nlohmann::json;
namespace crossdesk {
@@ -55,7 +59,7 @@ typedef struct {
int* bottom;
} HostInfo;
typedef struct {
struct RemoteAction {
ControlType type;
union {
Mouse m;
@@ -64,7 +68,111 @@ typedef struct {
bool a;
int d;
};
} RemoteAction;
// parse
std::string to_json() const { return ToJson(*this); }
bool from_json(const std::string& json_str) {
RemoteAction temp;
if (!FromJson(json_str, temp)) return false;
*this = temp;
return true;
}
static std::string ToJson(const RemoteAction& a) {
json j;
j["type"] = a.type;
switch (a.type) {
case ControlType::mouse:
j["mouse"] = {
{"x", a.m.x}, {"y", a.m.y}, {"s", a.m.s}, {"flag", a.m.flag}};
break;
case ControlType::keyboard:
j["keyboard"] = {{"key_value", a.k.key_value}, {"flag", a.k.flag}};
break;
case ControlType::audio_capture:
j["audio_capture"] = a.a;
break;
case ControlType::display_id:
j["display_id"] = a.d;
break;
case ControlType::host_infomation: {
json displays = json::array();
for (size_t idx = 0; idx < a.i.display_num; idx++) {
displays.push_back(
{{"name", a.i.display_list ? a.i.display_list[idx] : ""},
{"left", a.i.left ? a.i.left[idx] : 0},
{"top", a.i.top ? a.i.top[idx] : 0},
{"right", a.i.right ? a.i.right[idx] : 0},
{"bottom", a.i.bottom ? a.i.bottom[idx] : 0}});
}
j["host_info"] = {{"host_name", a.i.host_name},
{"display_num", a.i.display_num},
{"displays", displays}};
break;
}
}
return j.dump();
}
static bool FromJson(const std::string& json_str, RemoteAction& out) {
try {
json j = json::parse(json_str);
out.type = (ControlType)j.at("type").get<int>();
switch (out.type) {
case ControlType::mouse:
out.m.x = j.at("mouse").at("x").get<float>();
out.m.y = j.at("mouse").at("y").get<float>();
out.m.s = j.at("mouse").at("s").get<int>();
out.m.flag = (MouseFlag)j.at("mouse").at("flag").get<int>();
break;
case ControlType::keyboard:
out.k.key_value = j.at("keyboard").at("key_value").get<size_t>();
out.k.flag = (KeyFlag)j.at("keyboard").at("flag").get<int>();
break;
case ControlType::audio_capture:
out.a = j.at("audio_capture").get<bool>();
break;
case ControlType::display_id:
out.d = j.at("display_id").get<int>();
break;
case ControlType::host_infomation: {
std::string host_name =
j.at("host_info").at("host_name").get<std::string>();
strncpy(out.i.host_name, host_name.c_str(), sizeof(out.i.host_name));
out.i.host_name[sizeof(out.i.host_name) - 1] = '\0';
out.i.host_name_size = host_name.size();
out.i.display_num = j.at("host_info").at("display_num").get<size_t>();
auto displays = j.at("host_info").at("displays");
out.i.display_list =
(char**)malloc(out.i.display_num * sizeof(char*));
out.i.left = (int*)malloc(out.i.display_num * sizeof(int));
out.i.top = (int*)malloc(out.i.display_num * sizeof(int));
out.i.right = (int*)malloc(out.i.display_num * sizeof(int));
out.i.bottom = (int*)malloc(out.i.display_num * sizeof(int));
for (size_t idx = 0; idx < out.i.display_num; idx++) {
std::string name = displays[idx].at("name").get<std::string>();
out.i.display_list[idx] = (char*)malloc(name.size() + 1);
strcpy(out.i.display_list[idx], name.c_str());
out.i.left[idx] = displays[idx].at("left").get<int>();
out.i.top[idx] = displays[idx].at("top").get<int>();
out.i.right[idx] = displays[idx].at("right").get<int>();
out.i.bottom[idx] = displays[idx].at("bottom").get<int>();
}
break;
}
}
return true;
} catch (const std::exception& e) {
printf("Failed to parse RemoteAction JSON: %s\n", e.what());
return false;
}
}
};
// int key_code, bool is_down
typedef void (*OnKeyAction)(int, bool, void*);

File diff suppressed because it is too large Load Diff

View File

@@ -158,8 +158,12 @@ static std::vector<std::string> no_such_id = {
static std::vector<std::string> about = {
reinterpret_cast<const char*>(u8"关于"), "About"};
static std::vector<std::string> new_version_available = {
reinterpret_cast<const char*>(u8"新版本可用"), "New Version Available"};
static std::vector<std::string> version = {
reinterpret_cast<const char*>(u8"版本"), "Version"};
static std::vector<std::string> access_website = {
reinterpret_cast<const char*>(u8"访问官网: "), "Access Website: "};
static std::vector<std::string> confirm_delete_connection = {
reinterpret_cast<const char*>(u8"确认删除此连接"),

View File

@@ -16,6 +16,7 @@
#include "platform.h"
#include "rd_log.h"
#include "screen_capturer_factory.h"
#include "version_checker.h"
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
@@ -495,6 +496,8 @@ int Render::CreateConnectionPeer() {
: false;
params_.enable_turn = config_center_->IsEnableTurn();
params_.enable_srtp = config_center_->IsEnableSrtp();
params_.video_quality =
static_cast<VideoQuality>(config_center_->GetVideoQuality());
params_.on_receive_video_buffer = nullptr;
params_.on_receive_audio_buffer = OnReceiveAudioBufferCb;
params_.on_receive_data_buffer = OnReceiveDataBufferCb;
@@ -566,6 +569,14 @@ void Render::UpdateInteractions() {
screen_capturer_is_started_ = false;
}
if (start_speaker_capturer_ && !speaker_capturer_is_started_) {
StartSpeakerCapturer();
speaker_capturer_is_started_ = true;
} else if (!start_speaker_capturer_ && speaker_capturer_is_started_) {
StopSpeakerCapturer();
speaker_capturer_is_started_ = false;
}
if (start_mouse_controller_ && !mouse_controller_is_started_) {
StartMouseController();
mouse_controller_is_started_ = true;
@@ -896,6 +907,9 @@ int Render::DrawStreamWindow() {
}
int Render::Run() {
latest_version_ = CheckUpdate();
update_available_ = IsNewerVersion(CROSSDESK_VERSION, latest_version_);
path_manager_ = std::make_unique<PathManager>("CrossDesk");
if (path_manager_) {
cert_path_ =
@@ -1055,9 +1069,9 @@ void Render::MainLoop() {
remote_action.i.host_name[host_name.size()] = '\0';
remote_action.i.host_name_size = host_name.size();
std::vector<char> serialized = SerializeRemoteAction(remote_action);
int ret = SendDataFrame(peer_, serialized.data(), serialized.size(),
data_label_.c_str());
std::string msg = remote_action.to_json();
int ret =
SendDataFrame(peer_, msg.data(), msg.size(), data_label_.c_str());
FreeRemoteAction(remote_action);
if (0 == ret) {
need_to_send_host_info_ = false;
@@ -1183,6 +1197,10 @@ void Render::CleanupPeers() {
LOG_INFO("[{}] Leave connection [{}]", client_id_, client_id_);
LeaveConnection(peer_, client_id_);
is_client_mode_ = false;
StopScreenCapturer();
StopSpeakerCapturer();
StopMouseController();
StopKeyboardCapturer();
LOG_INFO("Destroy peer [{}]", client_id_);
DestroyPeer(&peer_);
}

View File

@@ -49,10 +49,10 @@ class Render {
bool connection_established_ = false;
bool rejoin_ = false;
bool net_traffic_stats_button_pressed_ = false;
bool mouse_control_button_pressed_ = false;
bool mouse_control_button_pressed_ = true;
bool mouse_controller_is_started_ = false;
bool audio_capture_button_pressed_ = false;
bool control_mouse_ = false;
bool audio_capture_button_pressed_ = true;
bool control_mouse_ = true;
bool streaming_ = false;
bool is_control_bar_in_left_ = true;
bool control_bar_hovered_ = false;
@@ -307,11 +307,15 @@ class Render {
#endif
// main window properties
std::string latest_version_ = "";
bool update_available_ = false;
bool start_mouse_controller_ = false;
bool mouse_controller_is_started_ = false;
bool start_screen_capturer_ = false;
bool screen_capturer_is_started_ = false;
bool start_keyboard_capturer_ = false;
bool start_speaker_capturer_ = false;
bool speaker_capturer_is_started_ = false;
bool start_keyboard_capturer_ = true;
bool keyboard_capturer_is_started_ = false;
bool foucs_on_main_window_ = false;
bool foucs_on_stream_window_ = false;
@@ -430,13 +434,17 @@ class Render {
KeyboardCapturer* keyboard_capturer_ = nullptr;
std::vector<DisplayInfo> display_info_list_;
uint64_t last_frame_time_;
bool show_new_version_icon_ = false;
bool show_new_version_icon_in_menu_ = true;
uint64_t new_version_icon_last_trigger_time_ = 0;
uint64_t new_version_icon_render_start_time_ = 0;
char client_id_[10] = "";
char client_id_display_[12] = "";
char client_id_with_password_[17] = "";
char password_saved_[7] = "";
int language_button_value_ = 0;
int video_quality_button_value_ = 0;
int video_frame_rate_button_value_ = 0;
int video_frame_rate_button_value_ = 1;
int video_encode_format_button_value_ = 0;
bool enable_hardware_video_codec_ = false;
bool enable_turn_ = false;
@@ -448,6 +456,7 @@ class Render {
bool enable_self_hosted_ = false;
int language_button_value_last_ = 0;
int video_quality_button_value_last_ = 0;
int video_frame_rate_button_value_last_ = 0;
int video_encode_format_button_value_last_ = 0;
bool enable_hardware_video_codec_last_ = false;
bool enable_turn_last_ = false;
@@ -470,6 +479,9 @@ class Render {
client_properties_;
void CloseTab(decltype(client_properties_)::iterator& it);
/* ------ stream window property end ------ */
/* ------ server mode ------ */
std::unordered_map<std::string, ConnectionStatus> connection_status_;
};
} // namespace crossdesk
#endif

View File

@@ -6,11 +6,6 @@
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
#ifdef CROSSDESK_DEBUG
#else
#define MOUSE_CONTROL 1
#endif
namespace crossdesk {
int Render::SendKeyCommand(int key_code, bool is_down) {
@@ -28,8 +23,9 @@ int Render::SendKeyCommand(int key_code, bool is_down) {
client_properties_.end()) {
auto props = client_properties_[controlled_remote_id_];
if (props->connection_status_ == ConnectionStatus::Connected) {
SendDataFrame(props->peer_, (const char*)&remote_action,
sizeof(remote_action), props->data_label_.c_str());
std::string msg = remote_action.to_json();
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
}
}
}
@@ -94,8 +90,10 @@ int Render::ProcessMouseEvent(const SDL_Event& event) {
if (props->control_bar_hovered_ || props->display_selectable_hovered_) {
remote_action.m.flag = MouseFlag::move;
}
SendDataFrame(props->peer_, (const char*)&remote_action,
sizeof(remote_action), props->data_label_.c_str());
std::string msg = remote_action.to_json();
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
} else if (SDL_EVENT_MOUSE_WHEEL == event.type &&
last_mouse_event.button.x >= props->stream_render_rect_.x &&
last_mouse_event.button.x <= props->stream_render_rect_.x +
@@ -127,8 +125,9 @@ int Render::ProcessMouseEvent(const SDL_Event& event) {
(float)(event.button.y - props->stream_render_rect_.y) /
render_height;
SendDataFrame(props->peer_, (const char*)&remote_action,
sizeof(remote_action), props->data_label_.c_str());
std::string msg = remote_action.to_json();
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
}
}
@@ -226,6 +225,8 @@ void Render::OnReceiveVideoBufferCb(const XVideoFrame* video_frame,
props->video_height_ = video_frame->height;
props->video_size_ = video_frame->size;
LOG_ERROR("receive: {}x{}", props->video_width_, props->video_height_);
if (need_to_update_render_rect) {
render->UpdateRenderRect();
}
@@ -279,64 +280,54 @@ void Render::OnReceiveDataBufferCb(const char* data, size_t size,
return;
}
std::string json_str(data, size);
RemoteAction remote_action;
memcpy(&remote_action, data, size);
try {
remote_action.from_json(json_str);
} catch (const std::exception& e) {
LOG_ERROR("Failed to parse RemoteAction JSON: {}", e.what());
return;
}
std::string remote_id(user_id, user_id_size);
if (render->client_properties_.find(remote_id) !=
render->client_properties_.end()) {
// local
auto props = render->client_properties_.find(remote_id)->second;
RemoteAction host_info;
if (DeserializeRemoteAction(data, size, host_info)) {
if (ControlType::host_infomation == host_info.type &&
props->remote_host_name_.empty()) {
props->remote_host_name_ =
std::string(host_info.i.host_name, host_info.i.host_name_size);
LOG_INFO("Remote hostname: [{}]", props->remote_host_name_);
for (int i = 0; i < host_info.i.display_num; i++) {
props->display_info_list_.push_back(DisplayInfo(
std::string(host_info.i.display_list[i]), host_info.i.left[i],
host_info.i.top[i], host_info.i.right[i], host_info.i.bottom[i]));
LOG_INFO("Remote display [{}:{}], bound [({}, {}) ({}, {})]", i + 1,
props->display_info_list_[i].name,
props->display_info_list_[i].left,
props->display_info_list_[i].top,
props->display_info_list_[i].right,
props->display_info_list_[i].bottom);
}
}
} else {
if (remote_action.type == ControlType::host_infomation &&
props->remote_host_name_.empty()) {
props->remote_host_name_ = std::string(remote_action.i.host_name,
remote_action.i.host_name_size);
LOG_INFO("Remote hostname: [{}]", props->remote_host_name_);
LOG_ERROR("No remote display detected");
for (int i = 0; i < remote_action.i.display_num; i++) {
props->display_info_list_.push_back(
DisplayInfo(remote_action.i.display_list[i],
remote_action.i.left[i], remote_action.i.top[i],
remote_action.i.right[i], remote_action.i.bottom[i]));
}
}
FreeRemoteAction(host_info);
FreeRemoteAction(remote_action);
} else {
// remote
if (ControlType::mouse == remote_action.type && render->mouse_controller_) {
if (remote_action.type == ControlType::mouse && render->mouse_controller_) {
render->mouse_controller_->SendMouseCommand(remote_action,
render->selected_display_);
} else if (ControlType::audio_capture == remote_action.type) {
if (remote_action.a) {
} else if (remote_action.type == ControlType::audio_capture) {
if (remote_action.a && !render->start_speaker_capturer_)
render->StartSpeakerCapturer();
render->audio_capture_ = true;
} else {
else if (!remote_action.a && render->start_speaker_capturer_)
render->StopSpeakerCapturer();
render->audio_capture_ = false;
}
} else if (ControlType::keyboard == remote_action.type &&
} else if (remote_action.type == ControlType::keyboard &&
render->keyboard_capturer_) {
render->keyboard_capturer_->SendKeyboardCommand(
(int)remote_action.k.key_value,
remote_action.k.flag == KeyFlag::key_down);
} else if (ControlType::display_id == remote_action.type) {
if (render->screen_capturer_) {
render->selected_display_ = remote_action.d;
render->screen_capturer_->SwitchTo(remote_action.d);
}
} else if (remote_action.type == ControlType::display_id &&
render->screen_capturer_) {
render->selected_display_ = remote_action.d;
render->screen_capturer_->SwitchTo(remote_action.d);
}
}
}
@@ -409,7 +400,7 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
props->connection_status_ = status;
switch (status) {
case ConnectionStatus::Connected:
case ConnectionStatus::Connected: {
if (!render->need_to_create_stream_window_ &&
!render->client_properties_.empty()) {
render->need_to_create_stream_window_ = true;
@@ -420,14 +411,10 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
(int)render->stream_window_width_,
(int)(render->stream_window_height_ - render->title_bar_height_)};
break;
}
case ConnectionStatus::Disconnected:
case ConnectionStatus::Failed:
case ConnectionStatus::Closed:
render->password_validating_time_ = 0;
render->start_screen_capturer_ = false;
render->start_mouse_controller_ = false;
render->start_keyboard_capturer_ = false;
render->control_mouse_ = false;
case ConnectionStatus::Closed: {
props->connection_established_ = false;
props->mouse_control_button_pressed_ = false;
if (props->dst_buffer_) {
@@ -436,8 +423,10 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
props->texture_width_);
}
render->CleanSubStreamWindowProperties(props);
break;
case ConnectionStatus::IncorrectPassword:
}
case ConnectionStatus::IncorrectPassword: {
render->password_validating_ = false;
render->password_validating_time_++;
if (render->connect_button_pressed_) {
@@ -447,37 +436,58 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
localization::connect[render->localization_language_index_];
}
break;
case ConnectionStatus::NoSuchTransmissionId:
}
case ConnectionStatus::NoSuchTransmissionId: {
if (render->connect_button_pressed_) {
props->connection_established_ = false;
render->connect_button_label_ =
localization::connect[render->localization_language_index_];
}
break;
}
default:
break;
}
} else {
render->is_client_mode_ = false;
render->show_connection_status_window_ = true;
render->connection_status_[remote_id] = status;
switch (status) {
case ConnectionStatus::Connected:
case ConnectionStatus::Connected: {
render->need_to_send_host_info_ = true;
render->start_screen_capturer_ = true;
render->start_mouse_controller_ = true;
break;
case ConnectionStatus::Closed:
render->start_screen_capturer_ = false;
render->start_speaker_capturer_ = true;
#ifdef CROSSDESK_DEBUG
render->start_mouse_controller_ = false;
render->start_keyboard_capturer_ = false;
render->need_to_send_host_info_ = false;
if (props) props->connection_established_ = false;
if (render->audio_capture_) {
render->StopSpeakerCapturer();
render->audio_capture_ = false;
#else
render->start_mouse_controller_ = true;
#endif
break;
}
case ConnectionStatus::Closed: {
if (std::all_of(render->connection_status_.begin(),
render->connection_status_.end(), [](const auto& kv) {
return kv.second == ConnectionStatus::Closed ||
kv.second == ConnectionStatus::Failed ||
kv.second == ConnectionStatus::Disconnected;
})) {
render->start_screen_capturer_ = false;
render->start_speaker_capturer_ = false;
render->start_mouse_controller_ = false;
render->start_keyboard_capturer_ = false;
render->need_to_send_host_info_ = false;
if (props) props->connection_established_ = false;
if (render->audio_capture_) {
render->StopSpeakerCapturer();
render->audio_capture_ = false;
}
render->connection_status_.erase(remote_id);
}
break;
}
default:
break;
}

View File

@@ -68,8 +68,9 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
remote_action.type = ControlType::display_id;
remote_action.d = i;
if (props->connection_status_ == ConnectionStatus::Connected) {
SendDataFrame(props->peer_, (const char*)&remote_action,
sizeof(remote_action), props->data_label_.c_str());
std::string msg = remote_action.to_json();
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
}
}
props->display_selectable_hovered_ = ImGui::IsWindowHovered();
@@ -142,8 +143,9 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
RemoteAction remote_action;
remote_action.type = ControlType::audio_capture;
remote_action.a = props->audio_capture_button_pressed_;
SendDataFrame(props->peer_, (const char*)&remote_action,
sizeof(remote_action), props->data_label_.c_str());
std::string msg = remote_action.to_json();
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
}
}
if (!props->audio_capture_button_pressed_) {

View File

@@ -3,6 +3,7 @@
#include "render.h"
#define BUTTON_PADDING 36.0f
#define NEW_VERSION_ICON_RENDER_TIME_INTERVAL 2000
namespace crossdesk {
@@ -39,13 +40,59 @@ int Render::TitleBar(bool main_window) {
localization::settings[localization_language_index_].c_str())) {
show_settings_window_ = true;
}
if (ImGui::MenuItem(
localization::about[localization_language_index_].c_str())) {
show_new_version_icon_in_menu_ = false;
std::string about_str =
localization::about[localization_language_index_];
if (update_available_) {
auto now_time =
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
// every 2 seconds
if (now_time - new_version_icon_last_trigger_time_ >=
NEW_VERSION_ICON_RENDER_TIME_INTERVAL) {
show_new_version_icon_ = true;
new_version_icon_render_start_time_ = now_time;
new_version_icon_last_trigger_time_ = now_time;
}
// render for 1 second
if (show_new_version_icon_) {
about_str = about_str + " " + ICON_FA_TRIANGLE_EXCLAMATION;
if (now_time - new_version_icon_render_start_time_ >=
NEW_VERSION_ICON_RENDER_TIME_INTERVAL / 2) {
show_new_version_icon_ = false;
}
} else {
about_str = about_str + " ";
}
}
if (ImGui::MenuItem(about_str.c_str())) {
show_about_window_ = true;
}
if (update_available_ && ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::SetWindowFontScale(0.5f);
std::string new_version_available_str =
localization::new_version_available
[localization_language_index_] +
": " + latest_version_;
ImGui::Text("%s", new_version_available_str.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::EndTooltip();
}
ImGui::SetWindowFontScale(1.0f);
ImGui::EndMenu();
} else {
show_new_version_icon_in_menu_ = true;
}
float menu_bar_line_size = 15.0f;
draw_list->AddLine(ImVec2(bar_pos_x, bar_pos_y - 6),
ImVec2(bar_pos_x + menu_bar_line_size, bar_pos_y - 6),
@@ -57,6 +104,33 @@ int Render::TitleBar(bool main_window) {
ImVec2(bar_pos_x + menu_bar_line_size, bar_pos_y + 6),
IM_COL32(0, 0, 0, 255));
if (update_available_ && show_new_version_icon_in_menu_) {
auto now_time = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
// every 2 seconds
if (now_time - new_version_icon_last_trigger_time_ >=
NEW_VERSION_ICON_RENDER_TIME_INTERVAL) {
show_new_version_icon_ = true;
new_version_icon_render_start_time_ = now_time;
new_version_icon_last_trigger_time_ = now_time;
}
// render for 1 second
if (show_new_version_icon_) {
ImGui::SetWindowFontScale(0.6f);
ImGui::SetCursorPos(ImVec2(bar_pos_x + 10, bar_pos_y - 17));
ImGui::Text(ICON_FA_TRIANGLE_EXCLAMATION);
ImGui::SetWindowFontScale(1.0f);
if (now_time - new_version_icon_render_start_time_ >=
NEW_VERSION_ICON_RENDER_TIME_INTERVAL / 2) {
show_new_version_icon_ = false;
}
}
}
{
SettingWindow();
SelfHostedServerWindow();

View File

@@ -1,3 +1,6 @@
#include <cstdlib>
#include <string>
#include "layout.h"
#include "localization.h"
#include "rd_log.h"
@@ -5,16 +8,44 @@
namespace crossdesk {
void Hyperlink(const std::string& label, const std::string& url) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 255, 255));
ImGui::Text("%s", label.c_str());
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImGui::BeginTooltip();
ImGui::SetWindowFontScale(0.6f);
ImGui::TextUnformatted(url.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::EndTooltip();
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
#if defined(_WIN32)
std::string cmd = "start " + url;
#elif defined(__APPLE__)
std::string cmd = "open " + url;
#else
std::string cmd = "xdg-open " + url;
#endif
system(cmd.c_str()); // open browser
}
}
}
int Render::AboutWindow() {
if (show_about_window_) {
const ImGuiViewport* viewport = ImGui::GetMainViewport();
float about_window_height = latest_version_.empty()
? about_window_height_
: about_window_height_ + 20.0f;
ImGui::SetNextWindowPos(ImVec2(
(viewport->WorkSize.x - viewport->WorkPos.x - about_window_width_) / 2,
(viewport->WorkSize.y - viewport->WorkPos.y - about_window_height_) /
(viewport->WorkSize.y - viewport->WorkPos.y - about_window_height) /
2));
ImGui::SetNextWindowSize(ImVec2(about_window_width_, about_window_height_));
ImGui::SetNextWindowSize(ImVec2(about_window_width_, about_window_height));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
@@ -38,6 +69,16 @@ int Render::AboutWindow() {
std::string text = localization::version[localization_language_index_] +
": CrossDesk v" + version;
ImGui::Text("%s", text.c_str());
if (update_available_) {
std::string latest_version =
localization::new_version_available[localization_language_index_] +
": " + latest_version_;
std::string access_website =
localization::access_website[localization_language_index_];
Hyperlink(latest_version, "https://crossdesk.cn");
}
ImGui::Text("");
std::string copyright_text = "© 2025 by JUNKUN DI. All rights reserved.";
@@ -46,7 +87,7 @@ int Render::AboutWindow() {
ImGui::Text("%s", license_text.c_str());
ImGui::SetCursorPosX(about_window_width_ * 0.42f);
ImGui::SetCursorPosY(about_window_height_ * 0.75f);
ImGui::SetCursorPosY(about_window_height * 0.75f);
// OK
if (ImGui::Button(localization::ok[localization_language_index_].c_str())) {
show_about_window_ = false;

View File

@@ -82,11 +82,11 @@ int Render::SettingWindow() {
{
const char* video_quality_items[] = {
localization::video_quality_high[localization_language_index_]
localization::video_quality_low[localization_language_index_]
.c_str(),
localization::video_quality_medium[localization_language_index_]
.c_str(),
localization::video_quality_low[localization_language_index_]
localization::video_quality_high[localization_language_index_]
.c_str()};
settings_items_offset += settings_items_padding;
@@ -288,14 +288,23 @@ int Render::SettingWindow() {
// Video quality
if (video_quality_button_value_ == 0) {
config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::HIGH);
config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::LOW);
} else if (video_quality_button_value_ == 1) {
config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::MEDIUM);
} else {
config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::LOW);
config_center_->SetVideoQuality(ConfigCenter::VIDEO_QUALITY::HIGH);
}
video_quality_button_value_last_ = video_quality_button_value_;
if (video_frame_rate_button_value_ == 0) {
config_center_->SetVideoFrameRate(
ConfigCenter::VIDEO_FRAME_RATE::FPS_30);
} else if (video_frame_rate_button_value_ == 1) {
config_center_->SetVideoFrameRate(
ConfigCenter::VIDEO_FRAME_RATE::FPS_60);
}
video_frame_rate_button_value_last_ = video_frame_rate_button_value_;
// Video encode format
if (video_encode_format_button_value_ == 0) {
config_center_->SetVideoEncodeFormat(
@@ -366,6 +375,11 @@ int Render::SettingWindow() {
video_quality_button_value_ = video_quality_button_value_last_;
}
if (video_frame_rate_button_value_ !=
video_frame_rate_button_value_last_) {
video_frame_rate_button_value_ = video_frame_rate_button_value_last_;
}
if (video_encode_format_button_value_ !=
video_encode_format_button_value_last_) {
video_encode_format_button_value_ =

View File

@@ -76,22 +76,34 @@ std::filesystem::path PathManager::GetKnownFolder(REFKNOWNFOLDERID id) {
#endif
std::string PathManager::GetHome() {
if (const char* home = getenv("HOME")) {
return std::string(home);
}
#ifdef _WIN32
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, path)))
return std::string(path);
#else
if (const char* home = getenv("HOME")) {
return std::string(home);
}
#endif
return {};
}
std::filesystem::path PathManager::GetEnvOrDefault(const char* env_var,
const std::string& def) {
#ifdef _WIN32
char* buffer = nullptr;
size_t size = 0;
if (_dupenv_s(&buffer, &size, env_var) == 0 && buffer != nullptr) {
std::filesystem::path result(buffer);
free(buffer);
return result;
}
#else
if (const char* val = getenv(env_var)) {
return std::filesystem::path(val);
}
#endif
return std::filesystem::path(def);
}

View File

@@ -14,33 +14,42 @@ namespace crossdesk {
static std::vector<DisplayInfo> gs_display_list;
std::string WideToUtf8(const wchar_t* wideStr) {
int size_needed = WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, nullptr, 0,
nullptr, nullptr);
std::string WideToUtf8(const std::wstring& wstr) {
if (wstr.empty()) return {};
int size_needed = WideCharToMultiByte(
CP_UTF8, 0, wstr.data(), (int)wstr.size(), nullptr, 0, nullptr, nullptr);
std::string result(size_needed, 0);
WideCharToMultiByte(CP_UTF8, 0, wideStr, -1, &result[0], size_needed, nullptr,
nullptr);
result.pop_back();
WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), result.data(),
size_needed, nullptr, nullptr);
return result;
}
std::string CleanDisplayName(const std::wstring& wide_name) {
std::string name = WideToUtf8(wide_name);
name.erase(std::remove_if(name.begin(), name.end(),
[](unsigned char c) { return !std::isalnum(c); }),
name.end());
return name;
}
BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, [[maybe_unused]] HDC hdc,
[[maybe_unused]] LPRECT lprc, LPARAM data) {
MONITORINFOEX monitor_info_;
monitor_info_.cbSize = sizeof(MONITORINFOEX);
if (GetMonitorInfo(hmonitor, &monitor_info_)) {
std::string display_name = CleanDisplayName(monitor_info_.szDevice);
if (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) {
gs_display_list.insert(
gs_display_list.begin(),
{(void*)hmonitor, WideToUtf8(monitor_info_.szDevice),
{(void*)hmonitor, display_name,
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom});
*(HMONITOR*)data = hmonitor;
} else {
gs_display_list.push_back(DisplayInfo(
(void*)hmonitor, WideToUtf8(monitor_info_.szDevice),
(void*)hmonitor, display_name,
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom));

View File

@@ -118,13 +118,13 @@ void ScaleNv12ToABGR(char* src, int src_w, int src_h, int dst_w, int dst_h,
memset(dst_rgba, 0, dst_w * dst_h * 4);
for (int i = 0; i < dst_w * dst_h; ++i) {
dst_rgba[i * 4 + 3] = 0xFF;
dst_rgba[i * 4 + 3] = static_cast<char>(0xFF);
}
for (int y = 0; y < fit_h; ++y) {
for (int row = 0; row < fit_h; ++row) {
int dst_offset =
((y + (dst_h - fit_h) / 2) * dst_w + (dst_w - fit_w) / 2) * 4;
memcpy(dst_rgba + dst_offset, abgr.data() + y * fit_w * 4, fit_w * 4);
((row + (dst_h - fit_h) / 2) * dst_w + (dst_w - fit_w) / 2) * 4;
memcpy(dst_rgba + dst_offset, abgr.data() + row * fit_w * 4, fit_w * 4);
}
}
@@ -172,7 +172,7 @@ int Thumbnail::SaveToThumbnail(const char* yuv420p, int width, int height,
memset(rgba_buffer_, 0x00, thumbnail_width_ * thumbnail_height_ * 4);
for (int i = 0; i < thumbnail_width_ * thumbnail_height_; ++i) {
// Set alpha channel to opaque
rgba_buffer_[i * 4 + 3] = 0xFF;
rgba_buffer_[i * 4 + 3] = static_cast<char>(0xFF);
}
}
@@ -214,7 +214,7 @@ int Thumbnail::LoadThumbnail(
return -1;
} else {
for (int i = 0; i < image_paths.size(); i++) {
size_t pos1 = image_paths[i].string().find('/') + 1;
// size_t pos1 = image_paths[i].string().find('/') + 1;
std::string cipher_image_name = image_paths[i].filename().string();
std::string remote_id;
std::string cipher_password;
@@ -241,7 +241,7 @@ int Thumbnail::LoadThumbnail(
AES_decrypt(cipher_password, aes128_key_, aes128_iv_);
} else {
size_t pos_n = cipher_image_name.find('N');
size_t pos_at = cipher_image_name.find('@');
// size_t pos_at = cipher_image_name.find('@');
if (pos_n == std::string::npos) {
LOG_ERROR("Invalid filename");

View File

@@ -0,0 +1,80 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-11-11
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _VERSION_CHECKER_H_
#define _VERSION_CHECKER_H_
#include <httplib.h>
#include <iostream>
#include <nlohmann/json.hpp>
#include <sstream>
using json = nlohmann::json;
namespace crossdesk {
std::string ExtractNumericPart(const std::string& ver) {
size_t start = 0;
while (start < ver.size() && !std::isdigit(ver[start])) start++;
size_t end = start;
while (end < ver.size() && (std::isdigit(ver[end]) || ver[end] == '.')) end++;
return ver.substr(start, end - start);
}
std::vector<int> SplitVersion(const std::string& ver) {
std::vector<int> nums;
std::istringstream ss(ver);
std::string token;
while (std::getline(ss, token, '.')) {
try {
nums.push_back(std::stoi(token));
} catch (...) {
nums.push_back(0);
}
}
return nums;
}
bool IsNewerVersion(const std::string& current, const std::string& latest) {
auto v1 = SplitVersion(ExtractNumericPart(current));
auto v2 = SplitVersion(ExtractNumericPart(latest));
size_t len = std::max(v1.size(), v2.size());
v1.resize(len, 0);
v2.resize(len, 0);
for (size_t i = 0; i < len; ++i) {
if (v2[i] > v1[i]) return true;
if (v2[i] < v1[i]) return false;
}
return false;
}
std::string CheckUpdate() {
httplib::Client cli("https://version.crossdesk.cn");
cli.set_connection_timeout(5);
cli.set_read_timeout(5);
if (auto res = cli.Get("/version.json")) {
if (res->status == 200) {
try {
auto j = json::parse(res->body);
std::string latest = j["latest_version"];
return latest;
} catch (std::exception&) {
return "";
}
} else {
return "";
}
} else {
return "";
}
}
} // namespace crossdesk
#endif

View File

@@ -0,0 +1,20 @@
/*
* @Author: DI JUNKUN
* @Date: 2025-11-11
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _VERSION_CHECKER_H_
#define _VERSION_CHECKER_H_
#include <string>
namespace crossdesk {
std::string CheckUpdate();
bool IsNewerVersion(const std::string& current, const std::string& latest);
} // namespace crossdesk
#endif

View File

@@ -0,0 +1,39 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d39958a..ba21f3c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -117,8 +117,14 @@ if(BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()
-if(CMAKE_SYSTEM_NAME MATCHES "Windows" AND ${CMAKE_SYSTEM_VERSION} VERSION_LESS "10.0.0")
- message(SEND_ERROR "Windows ${CMAKE_SYSTEM_VERSION} or lower is not supported. Please use Windows 10 or later.")
+if(CMAKE_SYSTEM_NAME MATCHES "Windows")
+ if(CMAKE_SYSTEM_VERSION)
+ if(${CMAKE_SYSTEM_VERSION} VERSION_LESS "10.0.0")
+ message(SEND_ERROR "Windows ${CMAKE_SYSTEM_VERSION} or lower is not supported. Please use Windows 10 or later.")
+ endif()
+ else()
+ message(WARNING "Cross-compiling for Windows, but CMAKE_SYSTEM_VERSION is not specified. Assuming target is Windows 10 or later.")
+ endif()
endif()
if(CMAKE_SIZEOF_VOID_P LESS 8)
message(WARNING "Pointer size ${CMAKE_SIZEOF_VOID_P} is not supported. Please use a 64-bit compiler.")
diff --git a/httplib.h b/httplib.h
index e15ba44..728504a 100644
--- a/httplib.h
+++ b/httplib.h
@@ -3083,8 +3083,13 @@ inline bool mmap::open(const char *path) {
auto wpath = u8string_to_wstring(path);
if (wpath.empty()) { return false; }
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM | WINAPI_PARTITION_GAMES) && (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ,
OPEN_EXISTING, NULL);
+#else
+ hFile_ = ::CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+#endif
if (hFile_ == INVALID_HANDLE_VALUE) { return false; }

117
thirdparty/cpp-httplib/xmake.lua vendored Normal file
View File

@@ -0,0 +1,117 @@
package("cpp-httplib")
set_kind("library", {headeronly = true})
set_homepage("https://github.com/yhirose/cpp-httplib")
set_description("A C++11 single-file header-only cross platform HTTP/HTTPS library.")
set_license("MIT")
set_urls("https://github.com/yhirose/cpp-httplib/archive/refs/tags/$(version).tar.gz",
"https://github.com/yhirose/cpp-httplib.git")
add_versions("v0.26.0", "a66f908f50ccb119769adce44fe1eac75f81b6ffab7c4ac0211bb663ffeb2688")
add_versions("v0.23.1", "410a1347ed6bcbcc4a19af8ed8ad3873fe9fa97731d52db845c4c78f3f9c31e6")
add_versions("v0.22.0", "fcfea48c8f2c386e7085ef8545c8a4875efa30fa6d5cf9dd31f03c6ad038da9d")
add_versions("v0.21.0", "99cfbce46981994d8f34ed21836510d7d408ffac91315bb22c9584a83e220e60")
add_versions("v0.20.1", "b74b1c2c150be2841eba80192f64d93e9a6711985b3ae8aaa1a9cec4863d1dd9")
add_versions("v0.20.0", "18064587e0cc6a0d5d56d619f4cbbcaba47aa5d84d86013abbd45d95c6653866")
add_versions("v0.19.0", "c9b9e0524666e1cd088f0874c57c1ce7c0eaa8552f9f4e15c755d5201fc8c608")
add_versions("v0.18.7", "b7b1e9e4e77565a5a9bc95e761d5df3e7c0e8ca37c90fd78b1b031bc6cb90fc1")
add_versions("v0.18.6", "8900747bba3dda8007f1876175be699036e09e4a25ceeab51196d9365bf1993a")
add_versions("v0.18.5", "731190e97acd63edce57cc3dacd496f57e7743bfc7933da7137cb3e93ec6c9a0")
add_versions("v0.18.3", "a0567bcd6c3fe5cef1b329b96245119047f876b49e06cc129a36a7a8dffe173e")
add_versions("v0.18.1", "405abd8170f2a446fc8612ac635d0db5947c0d2e156e32603403a4496255ff00")
add_versions("v0.17.3", "95bd6dba4241656c59d6f0854d408d14c220f7c71e673319ee27d30aee741aaa")
add_versions("v0.17.0", "dd3ba355b6aa74b7a0bff982ad0de7af6d9672fd83af30aa84eb707974d2a903")
add_versions("v0.16.3", "c1742fc7179aaae2a67ad9bba0740b7e9ffaf4f5e62feef53101ecdef1478716")
add_versions("v0.16.2", "75565bcdf12522929a26fb57a2c7f8cc0e175e27a9ecf51616075f3ea960da44")
add_versions("v0.15.3", "2121bbf38871bb2aafb5f7f2b9b94705366170909f434428352187cb0216124e")
add_versions("v0.15.2", "4afbcf4203249d2cbcb698e46e1f6fb61b479013a84844d6bb1c044e233cab6a")
add_versions("v0.15.1", "8d6a4a40ee8fd3f553b7e895882e60e674bd910883fc1857587dbbabee3cdb91")
add_versions("v0.15.0", "b658e625e283e2c81437a485a95f3acf8b1d32c53d8147b1ccecc8f630e1f7bb")
add_versions("v0.14.3", "dcf6486d9030937636d8a4f820ca9531808fd7edb283893dddbaa05f99357e63")
add_versions("v0.14.2", "dbcf5590e8ed35c6745c2ad659a5ebec92f05187d1506eec24449d6db95e5084")
add_versions("v0.14.1", "2d4fb5544da643e5d0a82585555d8b7502b4137eb321a4abbb075e21d2f00e96")
add_versions("v0.14.0", "3a92248ef8cf2c32ad07f910b8e3052ff2427022b2adb871cf326fb620d2438e")
add_versions("v0.12.6", "24bc594a9efcc08a5a6f3928e848d046d411a88b07bcd6f7f3851227a1f0133e")
add_versions("v0.12.1", "0e56c25c63e730ebd42e2beda6e7cb1b950131d8fc00d3158b1443a8d76f41ca")
add_versions("v0.9.2", "bfef2587a2aa31c85fb361df71c720be97076f8083e4f3881da8572f6a58054f")
add_versions("v0.8.5", "b353f3e7c124a08940d9425aeb7206183fa29857a8f720c162f8fd820cc18f0e")
add_patches("v0.26.0", path.join(os.scriptdir(), "patches/v0.26.0/fix-mingw.diff"), "f7b704e86abd8fd04217056e3ffb01427185e0bae72999246a3b8d13ba23c56a")
add_patches("v0.23.1", path.join(os.scriptdir(), "patches/v0.23.1/fix-mingw.diff"), "d2d8a4c16de3a00d9872526a187257c7ad344eba2a9f109d10b58eadce1c4059")
add_configs("ssl", { description = "Requires OpenSSL", default = false, type = "boolean"})
add_configs("zlib", { description = "Requires Zlib", default = false, type = "boolean"})
add_configs("brotli", { description = "Requires Brotli", default = false, type = "boolean"})
add_configs("exceptions", {description = "Enable the use of C++ exceptions", default = true, type = "boolean"})
add_deps("cmake")
if on_check then
on_check(function (package)
local pkg_ver = package:version()
if package:is_plat("windows") then
local winver = winos.version()
local vs = package:toolchain("msvc"):config("vs")
assert(winver and winver:gt("win8"), "package(httplib): Windows 8 or lower is neither supported nor tested.")
if pkg_ver and pkg_ver:ge("0.19.0") then
assert(vs and tonumber(vs) > 2015, "package(httplib >= 0.19.0): VS 2015 or lower is neither supported nor tested.")
elseif pkg_ver and pkg_ver:ge("0.11.0") then
assert(vs and tonumber(vs) > 2013, "package(httplib >= 0.11.0): VS 2013 or lower is neither supported nor tested.")
end
end
if pkg_ver and pkg_ver:ge("0.15.0") and package:is_plat("msys") then
wprint("package(httplib): MSYS2 (including MinGW) is not officially supported or tested by httplib.")
end
if package:is_plat("android") then
local ndk = package:toolchain("ndk")
local ndk_sdkver = ndk:config("ndk_sdkver")
assert(ndk_sdkver and tonumber(ndk_sdkver) >= 24, "package(httplib): need ndk api level >= 24 for android")
end
if pkg_ver and pkg_ver:ge("0.23.0") then
if package:check_sizeof("void*") == "4" then
raise("package(cpp-httplib >=0.23.0) does not support 32-bit")
end
end
end)
end
on_load(function (package)
if package:config("ssl") then
package:add("deps", "openssl3 3.3.2")
package:add("defines", "CPPHTTPLIB_OPENSSL_SUPPORT")
end
if package:config("zlib") then
package:add("deps", "zlib")
package:add("defines", "CPPHTTPLIB_ZLIB_SUPPORT")
end
if package:config("brotli") then
package:add("deps", "brotli")
package:add("defines", "CPPHTTPLIB_BROTLI_SUPPORT")
end
end)
on_install("!cygwin", function (package)
local configs = {"-DHTTPLIB_COMPILE=OFF"}
table.insert(configs, "-DCMAKE_BUILD_TYPE=" .. (package:is_debug() and "Debug" or "Release"))
table.insert(configs, "-DBUILD_SHARED_LIBS=" .. (package:config("shared") and "ON" or "OFF"))
table.insert(configs, "-DHTTPLIB_REQUIRE_OPENSSL=" .. (package:config("ssl") and "ON" or "OFF"))
table.insert(configs, "-DHTTPLIB_REQUIRE_ZLIB=" .. (package:config("zlib") and "ON" or "OFF"))
table.insert(configs, "-DHTTPLIB_REQUIRE_BROTLI=" .. (package:config("brotli") and "ON" or "OFF"))
table.insert(configs, "-DHTTPLIB_NO_EXCEPTIONS=" .. (package:config("exceptions") and "OFF" or "ON"))
if package:config("ssl") then
local openssl = package:dep("openssl" .. (package:version():ge("0.15.0") and "3" or ""))
if not openssl:is_system() then
table.insert(configs, "-DOPENSSL_ROOT_DIR=" .. openssl:installdir())
end
end
import("package.tools.cmake").install(package, configs)
end)
on_test(function (package)
assert(package:check_cxxsnippets({test = [[
void test() {
httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co");
}
]]}, {configs = {languages = "c++11"}, includes = "httplib.h"}))
end)

1
thirdparty/xmake.lua vendored Normal file
View File

@@ -0,0 +1 @@
includes("cpp-httplib")

View File

@@ -13,6 +13,7 @@ set_encodings("utf-8")
-- set_policy("build.warning", true)
-- set_warnings("all", "extra")
-- add_cxxflags("/W4", "/WX")
add_defines("UNICODE")
if is_mode("debug") then
@@ -22,6 +23,8 @@ end
add_requires("spdlog 1.14.1", {system = false})
add_requires("imgui v1.91.5-docking", {configs = {sdl3 = true, sdl3_renderer = true}})
add_requires("openssl3 3.3.2", {system = false})
add_requires("nlohmann_json 3.11.3")
add_requires("cpp-httplib v0.26.0", {configs = {ssl = true}})
if is_os("windows") then
add_requires("libyuv", "miniaudio 0.11.21")
@@ -44,9 +47,9 @@ elseif is_os("macosx") then
"CoreMedia", "CoreVideo", "CoreAudio", "AudioToolbox")
end
add_packages("spdlog", "imgui")
add_packages("spdlog", "imgui", "nlohmann_json")
includes("submodules")
includes("submodules", "thirdparty")
target("rd_log")
set_kind("object")
@@ -143,13 +146,21 @@ target("assets")
"src/gui/assets/icons",
"src/gui/assets/layouts", {public = true})
target("version_checker")
set_kind("object")
add_packages("cpp-httplib")
add_defines("CROSSDESK_VERSION=\"" .. (get_config("CROSSDESK_VERSION") or "Unknown") .. "\"")
add_deps("rd_log")
add_files("src/version_checker/*.cpp")
add_includedirs("src/version_checker", {public = true})
target("gui")
set_kind("object")
add_packages("libyuv")
add_defines("CROSSDESK_VERSION=\"" .. (get_config("CROSSDESK_VERSION") or "Unknown") .. "\"")
add_deps("rd_log", "common", "assets", "config_center", "minirtc",
"path_manager", "screen_capturer", "speaker_capturer",
"device_controller", "thumbnail")
"device_controller", "thumbnail", "version_checker")
add_files("src/gui/*.cpp", "src/gui/panels/*.cpp", "src/gui/toolbars/*.cpp",
"src/gui/windows/*.cpp")
add_includedirs("src/gui", "src/gui/panels", "src/gui/toolbars",