mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-15 16:30:50 +08:00
[fix] support self-hosted server URLs with paths
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
#include "SimpleIni.h"
|
||||
#include "autostart.h"
|
||||
#include "rd_log.h"
|
||||
#include "signal_server_address.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
@@ -290,10 +291,23 @@ int ConfigCenter::SetTurn(bool enable_turn, bool force_relay) {
|
||||
}
|
||||
|
||||
int ConfigCenter::SetServerHost(const std::string& host) {
|
||||
if (host.empty() || host.size() > 253 || host.find_first_of("/\\@?#") != std::string::npos ||
|
||||
std::any_of(host.begin(), host.end(), [](unsigned char c) { return c <= 32 || c == 127; })) return -1;
|
||||
if (StoreValues({{"signal_server_host", host}}) != 0) return -1;
|
||||
signal_server_host_ = host;
|
||||
const auto address = minirtc::ParseSignalServerAddress(host);
|
||||
if (!address) return -1;
|
||||
if (address->port) return SetServerEndpoint(host, *address->port);
|
||||
if (StoreValues({{"signal_server_host", address->Address()}}) != 0) return -1;
|
||||
signal_server_host_ = address->Address();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ConfigCenter::SetServerEndpoint(const std::string& host, int port) {
|
||||
const auto address = minirtc::ParseSignalServerAddress(host);
|
||||
if (!address) return -1;
|
||||
port = address->port.value_or(port);
|
||||
if (port < 1 || port > 65535) return -1;
|
||||
if (StoreValues({{"signal_server_host", address->Address()},
|
||||
{"signal_server_port", std::to_string(port)}}) != 0) return -1;
|
||||
signal_server_host_ = address->Address();
|
||||
signal_server_port_ = port;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ class ConfigCenter {
|
||||
int SetTurn(bool enable_turn, bool force_relay);
|
||||
int SetServerHost(const std::string& signal_server_host);
|
||||
int SetServerPort(int signal_server_port);
|
||||
int SetServerEndpoint(const std::string& address, int port);
|
||||
int SetSelfHosted(bool enable_self_hosted);
|
||||
int SetAutostart(bool enable_autostart);
|
||||
int SetDaemon(bool enable_daemon);
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
#include "rd_log.h"
|
||||
#include "server_window_state.h"
|
||||
#include "signal_server_settings.h"
|
||||
#include "signal_server_address.h"
|
||||
#include "ui/announcement_text.h"
|
||||
#include "ui/ui_localization.h"
|
||||
#include "update_checker.h"
|
||||
@@ -1285,6 +1286,7 @@ void GuiApplication::ResetSettingsUi() {
|
||||
ui_->main->set_file_save_path(file_transfer_save_path_buf_);
|
||||
ui_->main->set_server_host(signal_server_ip_self_);
|
||||
ui_->main->set_server_port(signal_server_port_self_);
|
||||
ui_->main->set_self_host_settings_error("");
|
||||
}
|
||||
|
||||
void GuiApplication::BindMainCallbacks() {
|
||||
@@ -1419,29 +1421,14 @@ void GuiApplication::BindMainCallbacks() {
|
||||
ui_->main->set_file_save_path(folder);
|
||||
}
|
||||
});
|
||||
main->on_save_settings([this] { SaveSettingsFromUi(); });
|
||||
main->on_save_settings([this] { return SaveSettingsFromUi(); });
|
||||
main->on_cancel_settings([this] { ResetSettingsUi(); });
|
||||
main->on_save_self_hosted_settings([this] {
|
||||
const std::string host(ui_->main->get_server_host());
|
||||
const auto signal_port = ParsePort(ui_->main->get_server_port());
|
||||
if (!host.empty()) {
|
||||
config_center_->SetServerHost(host);
|
||||
std::memset(signal_server_ip_self_, 0, sizeof(signal_server_ip_self_));
|
||||
std::strncpy(signal_server_ip_self_, host.c_str(),
|
||||
sizeof(signal_server_ip_self_) - 1);
|
||||
std::memset(signal_server_ip_, 0, sizeof(signal_server_ip_));
|
||||
std::strncpy(signal_server_ip_, host.c_str(),
|
||||
sizeof(signal_server_ip_) - 1);
|
||||
}
|
||||
if (signal_port) {
|
||||
config_center_->SetServerPort(*signal_port);
|
||||
std::snprintf(signal_server_port_self_, sizeof(signal_server_port_self_),
|
||||
"%d", *signal_port);
|
||||
std::snprintf(signal_server_port_, sizeof(signal_server_port_), "%d",
|
||||
*signal_port);
|
||||
}
|
||||
// Stage the endpoint until the surrounding settings dialog is confirmed.
|
||||
return ValidateSelfHostedSettingsFromUi();
|
||||
});
|
||||
main->on_cancel_self_hosted_settings([this] {
|
||||
ui_->main->set_self_host_settings_error("");
|
||||
const std::string host = config_center_->GetSignalServerHost();
|
||||
ui_->main->set_server_host(UiText(host));
|
||||
const int signal_port = config_center_->GetSignalServerPort();
|
||||
@@ -3496,9 +3483,45 @@ void GuiApplication::SyncServerWindow() {
|
||||
PositionServerWindows();
|
||||
}
|
||||
|
||||
void GuiApplication::SaveSettingsFromUi() {
|
||||
bool GuiApplication::ValidateSelfHostedSettingsFromUi() {
|
||||
auto& main = ui_->main;
|
||||
const auto address = minirtc::ParseSignalServerAddress(
|
||||
std::string(main->get_server_host()));
|
||||
if (!address) {
|
||||
main->set_self_host_settings_error(UiText(
|
||||
localization::self_hosted_address_invalid[localization_language_index_]));
|
||||
return false;
|
||||
}
|
||||
const auto port = address->port ? address->port : ParsePort(main->get_server_port());
|
||||
if (!port) {
|
||||
main->set_self_host_settings_error(UiText(
|
||||
localization::self_hosted_port_invalid[localization_language_index_]));
|
||||
return false;
|
||||
}
|
||||
main->set_server_host(UiText(address->Address()));
|
||||
main->set_server_port(UiText(std::to_string(*port)));
|
||||
main->set_self_host_settings_error("");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GuiApplication::SaveSettingsFromUi() {
|
||||
const auto previous_server = GetSignalServerSettings(*config_center_);
|
||||
auto& main = ui_->main;
|
||||
const bool has_endpoint = main->get_self_hosted_enabled() ||
|
||||
!std::string(main->get_server_host()).empty() ||
|
||||
!std::string(main->get_server_port()).empty();
|
||||
if (has_endpoint && !ValidateSelfHostedSettingsFromUi()) {
|
||||
main->set_self_host_settings_open(true);
|
||||
return false;
|
||||
}
|
||||
if ((has_endpoint && config_center_->SetServerEndpoint(
|
||||
std::string(main->get_server_host()), *ParsePort(main->get_server_port())) != 0) ||
|
||||
config_center_->SetSelfHosted(main->get_self_hosted_enabled()) != 0) {
|
||||
main->set_self_host_settings_error(UiText(
|
||||
localization::self_hosted_save_failed[localization_language_index_]));
|
||||
main->set_self_host_settings_open(true);
|
||||
return false;
|
||||
}
|
||||
language_button_value_ =
|
||||
localization::detail::ClampLanguageIndex(main->get_language_index());
|
||||
video_encode_format_button_value_ = std::clamp(main->get_codec_index(), 0, 1);
|
||||
@@ -3564,17 +3587,9 @@ void GuiApplication::SaveSettingsFromUi() {
|
||||
sizeof(file_transfer_save_path_buf_) - 1);
|
||||
config_center_->SetFileTransferSavePath(path);
|
||||
|
||||
const std::string host(main->get_server_host());
|
||||
if (!host.empty()) {
|
||||
config_center_->SetServerHost(host);
|
||||
}
|
||||
if (const auto port = ParsePort(main->get_server_port())) {
|
||||
config_center_->SetServerPort(*port);
|
||||
}
|
||||
|
||||
config_center_->SetSelfHosted(enable_self_hosted_);
|
||||
ApplySettingsFromConfig(previous_server !=
|
||||
GetSignalServerSettings(*config_center_));
|
||||
return true;
|
||||
}
|
||||
|
||||
void GuiApplication::ApplySettingsFromConfig(bool reconnect) {
|
||||
|
||||
@@ -76,7 +76,8 @@ private:
|
||||
#endif
|
||||
void UpdateLocalization();
|
||||
void ResetSettingsUi();
|
||||
void SaveSettingsFromUi();
|
||||
bool ValidateSelfHostedSettingsFromUi();
|
||||
bool SaveSettingsFromUi();
|
||||
void ApplySettingsFromConfig(bool reconnect);
|
||||
#if _WIN32 && CROSSDESK_PORTABLE
|
||||
void CheckPortableWindowsService();
|
||||
|
||||
@@ -240,6 +240,9 @@ struct TranslationRow {
|
||||
"Server Address:", u8"Адрес сервера:") \
|
||||
X(self_hosted_server_port, u8"信令服务端口:", \
|
||||
"Signal Service Port:", u8"Порт сигнального сервиса:") \
|
||||
X(self_hosted_address_invalid, u8"请输入有效的主机名、IP 或 HTTPS/WSS 信令地址,可包含路径。", "Enter a valid host, IP, or HTTPS/WSS signaling URL. Paths are supported.", u8"Введите имя хоста, IP или HTTPS/WSS-адрес сигнального сервиса. Путь допустим.") \
|
||||
X(self_hosted_port_invalid, u8"请输入 1–65535 范围内的信令端口。", "Enter a signaling port between 1 and 65535.", u8"Введите порт сигнального сервиса от 1 до 65535.") \
|
||||
X(self_hosted_save_failed, u8"配置保存失败,请检查配置目录是否可写后重试。", "Could not save settings. Check that the configuration directory is writable and retry.", u8"Не удалось сохранить настройки. Проверьте доступ на запись в каталог конфигурации.") \
|
||||
X(ok, u8"确认", "OK", u8"ОК") \
|
||||
X(cancel, u8"取消", "Cancel", u8"Отмена") \
|
||||
X(new_password, u8"请输入六位密码:", \
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <set>
|
||||
#include <system_error>
|
||||
|
||||
#include "signal_server_address.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
@@ -51,9 +53,12 @@ void AnnouncementInbox::Configure(std::string host, int port,
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (directory_.empty() || host.empty() || port <= 0 || device_id.empty())
|
||||
return;
|
||||
for (auto& ch : host)
|
||||
auto address = minirtc::ParseSignalServerAddress(host);
|
||||
if (!address) return;
|
||||
for (auto& ch : address->host)
|
||||
if (ch >= 'A' && ch <= 'Z') ch += 'a' - 'A';
|
||||
if (host.back() == '.') host.pop_back();
|
||||
if (address->host.back() == '.') address->host.pop_back();
|
||||
host = address->Address();
|
||||
const auto scope = json::array({host, port, device_id}).dump();
|
||||
if (scope_ == scope) return;
|
||||
scope_ = scope;
|
||||
|
||||
@@ -379,6 +379,7 @@ export component MainWindow inherits Window {
|
||||
in-out property <string> file-save-path: "";
|
||||
in-out property <string> server-host: "";
|
||||
in-out property <string> server-port: "";
|
||||
in-out property <string> self-host-settings-error: "";
|
||||
in property <bool> settings-session-active: false;
|
||||
// Keep the setting visible on every platform. Unsupported builds expose it
|
||||
// as disabled so the fixed settings layout never turns into a blank row.
|
||||
@@ -393,9 +394,9 @@ export component MainWindow inherits Window {
|
||||
callback recent-connect(string);
|
||||
callback recent-edit-alias(string, string);
|
||||
callback recent-delete(string);
|
||||
callback save-settings;
|
||||
callback save-settings() -> bool;
|
||||
callback cancel-settings;
|
||||
callback save-self-hosted-settings;
|
||||
callback save-self-hosted-settings() -> bool;
|
||||
callback cancel-self-hosted-settings;
|
||||
callback browse-save-path;
|
||||
callback open-download;
|
||||
@@ -418,6 +419,9 @@ export component MainWindow inherits Window {
|
||||
in-out property <bool> settings-open: false;
|
||||
private property <length> settings-scroll-y: 0px;
|
||||
in-out property <bool> self-host-settings-open: false;
|
||||
changed self-host-settings-open => {
|
||||
if !root.self-host-settings-open { root.self-host-settings-error = ""; }
|
||||
}
|
||||
in-out property <bool> about-open: false;
|
||||
in-out property <bool> update-open: false;
|
||||
in-out property <bool> reset-password-open: false;
|
||||
@@ -1809,7 +1813,7 @@ export component MainWindow inherits Window {
|
||||
}
|
||||
|
||||
Rectangle { x: 7px; y: 286px; width: parent.width - 14px; height: 1px; background: ImGuiLineStyle.separator; }
|
||||
CompactButton { x: root.compact-language ? 63px : 89px; y: 297px; primary: true; text: UiStrings.ok; clicked => { root.save-settings(); root.settings-open = false; root.self-host-settings-open = false; } }
|
||||
CompactButton { x: root.compact-language ? 63px : 89px; y: 297px; primary: true; text: UiStrings.ok; clicked => { if root.save-settings() { root.settings-open = false; root.self-host-settings-open = false; } } }
|
||||
CompactButton { x: root.compact-language ? 104px : 130px; y: 297px; width: root.compact-language ? 34px : 56px; text: UiStrings.cancel; clicked => { root.cancel-settings(); root.settings-open = false; root.self-host-settings-open = false; } }
|
||||
}
|
||||
}
|
||||
@@ -1822,7 +1826,7 @@ export component MainWindow inherits Window {
|
||||
x: root.compact-language ? 190px : 140px;
|
||||
y: 105px;
|
||||
width: root.compact-language ? 260px : 360px;
|
||||
height: 138px;
|
||||
height: root.self-host-settings-error == "" ? 138px : 202px;
|
||||
border-radius: 7px;
|
||||
|
||||
Rectangle {
|
||||
@@ -1846,7 +1850,7 @@ export component MainWindow inherits Window {
|
||||
border-width: 1px;
|
||||
border-color: ImGuiLineStyle.border;
|
||||
border-radius: 4px;
|
||||
LineEdit { x: 1px; y: 1.5px; width: parent.width - 2px; height: parent.height - 2px; text <=> root.server-host; font-size: ImGuiFontStyle.body; }
|
||||
LineEdit { x: 1px; y: 1.5px; width: parent.width - 2px; height: parent.height - 2px; text <=> root.server-host; input-type: InputType.text; font-size: ImGuiFontStyle.body; }
|
||||
LineEditFocusUnderlineMask { x: 5px; y: parent.height - 3px; width: parent.width - 10px; }
|
||||
}
|
||||
Text { x: 8px; y: 61px; width: root.compact-language ? 88px : 165px; height: 24px; text: UiStrings.server-port; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
|
||||
@@ -1862,8 +1866,13 @@ export component MainWindow inherits Window {
|
||||
LineEdit { x: 1px; y: 1.5px; width: parent.width - 2px; height: parent.height - 2px; text <=> root.server-port; input-type: InputType.number; font-size: ImGuiFontStyle.body; }
|
||||
LineEditFocusUnderlineMask { x: 5px; y: parent.height - 3px; width: parent.width - 10px; }
|
||||
}
|
||||
CompactButton { x: root.compact-language ? 93px : 135px; y: 100px; primary: true; text: UiStrings.ok; clicked => { root.save-self-hosted-settings(); root.self-host-settings-open = false; } }
|
||||
CompactButton { x: root.compact-language ? 134px : 176px; y: 100px; width: root.compact-language ? 34px : 56px; text: UiStrings.cancel; clicked => { root.cancel-self-hosted-settings(); root.self-host-settings-open = false; } }
|
||||
if root.self-host-settings-error != "": Text {
|
||||
x: 8px; y: 89px; width: parent.width - 16px; height: 64px;
|
||||
text: root.self-host-settings-error; color: #b3261e;
|
||||
font-size: ImGuiFontStyle.body; wrap: word-wrap;
|
||||
}
|
||||
CompactButton { x: root.compact-language ? 93px : 135px; y: parent.height - 38px; primary: true; text: UiStrings.ok; clicked => { if root.save-self-hosted-settings() { root.self-host-settings-open = false; } } }
|
||||
CompactButton { x: root.compact-language ? 134px : 176px; y: parent.height - 38px; width: root.compact-language ? 34px : 56px; text: UiStrings.cancel; clicked => { root.cancel-self-hosted-settings(); root.self-host-settings-open = false; } }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <optional>
|
||||
|
||||
#include "config_center.h"
|
||||
#include "signal_server_address.h"
|
||||
#include "signal_server_settings.h"
|
||||
|
||||
namespace crossdesk {
|
||||
@@ -83,9 +84,7 @@ SettingResult ApplyConsoleSetting(ConfigCenter& config, const std::string& key,
|
||||
canonical = value;
|
||||
save = [&config, mode] { return config.SetTurnMode(static_cast<ConfigCenter::TURN_MODE>(mode)); };
|
||||
} else if (key == "server_host") {
|
||||
if (input.empty() || input.size() > 253 ||
|
||||
input.find_first_of("/\\@?#") != std::string::npos ||
|
||||
std::any_of(input.begin(), input.end(), [](unsigned char c) { return c <= 32 || c == 127; })) return result;
|
||||
if (!minirtc::ParseSignalServerAddress(input)) return result;
|
||||
save = [&] { return config.SetServerHost(input); };
|
||||
} else if (key == "server_port") {
|
||||
int port = 0;
|
||||
|
||||
@@ -453,6 +453,7 @@ function setup_targets()
|
||||
add_deps("rd_log", "autostart")
|
||||
add_files("apps/desktop/src/config_center/*.cpp")
|
||||
add_includedirs("apps/desktop/src", "apps/desktop/src/config_center",
|
||||
"deps/submodules/minirtc/src/api",
|
||||
{public = true})
|
||||
|
||||
target("assets")
|
||||
|
||||
Vendored
+1
-1
Submodule deps/submodules/minirtc updated: 3f6f7f2725...d2f42cb02c
+1
-1
@@ -61,7 +61,7 @@ xmake r crossdesk --headless
|
||||
| `codec` | `h264` / `av1` | 保存后重新连接 |
|
||||
| `hardware` | `on` / `off` | 保存后重新连接;构建不支持时拒绝开启 |
|
||||
| `turn` | `off` / `auto` / `udp` / `tcp` | 保存后重新连接;`udp` / `tcp` 表示强制中继 |
|
||||
| `server_host` | 主机名或 IP,不含协议与路径 | 自托管未启用时保存备用,已启用时重连 |
|
||||
| `server_host` | 主机名、IP 或 HTTPS/WSS 信令地址,支持路径和显式端口 | 自托管未启用时保存备用,已启用时重连 |
|
||||
| `server_port` | `1`–`65535` | 同上 |
|
||||
| `self_hosted` | `on` / `off` | 先配置地址、端口,再开启;保存后重连 |
|
||||
| `privacy` | `on` / `off` | 后续远程连接的自动隐私屏行为 |
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ The menu shares the GUI's `config.ini`. Each valid change is saved immediately;
|
||||
| `codec` | `h264` / `av1` | Reconnect after saving |
|
||||
| `hardware` | `on` / `off` | Reconnect; enabling requires a build with hardware codec support |
|
||||
| `turn` | `off` / `auto` / `udp` / `tcp` | Reconnect; UDP/TCP force relay use |
|
||||
| `server_host` | Hostname or IP without URL scheme/path | Reconnect if self-hosting is enabled; otherwise saved for later |
|
||||
| `server_host` | Hostname, IP, or HTTPS/WSS signaling URL, with optional path and explicit port | Reconnect if self-hosting is enabled; otherwise saved for later |
|
||||
| `server_port` | `1`–`65535` | Same as server host |
|
||||
| `self_hosted` | `on` / `off` | Configure host/port first, then enable; reconnect |
|
||||
| `privacy` | `on` / `off` | Automatic privacy screen on subsequent connections |
|
||||
|
||||
@@ -44,10 +44,12 @@ sudo docker compose ps
|
||||
|
||||
1. 断开现有会话,点击主窗口右上角 **☰ → 设置**。
|
||||
2. 在设置列表中向下滚动,点击 **自托管配置**。
|
||||
3. 填写 **服务器地址**、**信令服务端口**,点击“确认”。地址只填主机名或 IP,不加 `https://`、路径或端口。
|
||||
3. 填写 **服务器地址**、**信令服务端口**,点击“确认”。地址支持主机名或 IP(如 `my-server.example.com`),也支持带路径的信令地址(如 `my-server.example.com/signal-path`)或 `https://` / `wss://` URL。URL 内明确填写的端口优先;否则使用单独填写的信令服务端口。所有连接均使用 WSS,带路径的地址需要反向代理将该路径转发到信令 WebSocket 服务,普通网页地址不能直接用于信令连接。
|
||||
4. 回到设置窗口,勾选 **自托管配置** 右侧复选框,再点击“确认”保存。
|
||||
5. 在控制端和被控端执行相同配置。等待两端都显示“已连接服务器”,再使用被控端当前显示的 ID 发起连接。
|
||||
|
||||
地址与端口在最后确认设置时一起保存;格式无效或保存失败时会保留输入并显示错误。
|
||||
|
||||
<p align="center">
|
||||
<img src="images/desktop-self-hosted-zh.png" width="720" alt="当前 Slint 客户端自托管设置:服务器地址与信令端口" />
|
||||
</p>
|
||||
|
||||
@@ -44,7 +44,7 @@ For a self-signed deployment, complete the [certificate trust steps](#3-trust-th
|
||||
|
||||
1. Disconnect existing sessions and open **☰ → Settings**.
|
||||
2. Scroll down and click **Self-Hosted Config**.
|
||||
3. Enter **Server Address** and **Signal Service Port**, then confirm. Enter only the hostname or IP in the address field, without a URL scheme, path, or port.
|
||||
3. Enter **Server Address** and **Signal Service Port**, then confirm. The address accepts a hostname or IP (such as `my-server.example.com`), a signaling path (such as `my-server.example.com/signal-path`), or an `https://` / `wss://` URL. An explicit URL port takes precedence; otherwise the separate signaling port is used. Connections always use WSS. Paths must be routed to the signaling WebSocket service by your reverse proxy; an ordinary web page is not a signaling endpoint.
|
||||
4. Enable the checkbox beside **Self-Hosted Config**, then click **OK** in the parent settings window.
|
||||
5. Configure both the controller and host this way. Once both report a server connection, connect using the host's currently displayed ID.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user