[feat] add configurable all-interface IPv4 listening, refs #129

This commit is contained in:
dijunkun
2026-10-29 18:23:34 +08:00
parent dd774de4f8
commit f09a4c50d1
15 changed files with 266 additions and 50 deletions
@@ -1529,6 +1529,24 @@ void GuiApplication::ResetSettingsUi(bool gallery_window) {
main->set_codec_index(video_encode_format_button_value_);
main->set_ip_direct_enabled_draft(config_center_->IsIpDirectEnabled());
main->set_settings_save_error("");
try {
bool listen_all = true;
if (lan_) {
listen_all = lan_->identity().listen_all();
} else {
const auto directory = path_manager_->GetConfigPath() / "lan";
if (std::filesystem::exists(directory / "identity.json")) {
lan::Identity identity(directory);
identity.Load();
listen_all = identity.listen_all();
}
}
main->set_lan_listen_all(listen_all);
main->set_lan_listen_all_draft(listen_all);
} catch (const std::exception& error) {
main->set_settings_save_error(
main->global<ui::LanStrings>().invoke_error_message(UiText(error.what())));
}
main->set_force_relay_enabled(config_center_->IsForceRelay());
main->set_self_hosted_enabled(enable_self_hosted_);
main->set_autostart_enabled(enable_autostart_);
@@ -1767,7 +1785,7 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) {
try {
lan_->identity().SelectAddress(std::string(address));
const auto port =
UiText(std::to_string(lan_->identity().PortFor(std::string(address))));
UiText(std::to_string(lan_->PortFor(std::string(address))));
main->set_lan_listener_ip(address);
main->set_lan_listener_port(port);
main->set_lan_local_port(port);
@@ -2452,6 +2470,7 @@ void GuiApplication::Tick() {
TickLan();
if (LanAvailable()) {
const auto address = lan_->identity().settings().value("address", "");
ui_->main->set_lan_listen_all(lan_->identity().listen_all());
ui_->main->set_lan_password_pending(lan_->password_pending());
ui_->main->set_lan_listening(lan_->listening());
if (lan_connecting_ && lan_connecting_->authorized) {
@@ -2504,7 +2523,7 @@ void GuiApplication::Tick() {
? available_interfaces.front().address : "";
}
const auto port =
UiText(std::to_string(lan_->identity().PortFor(display_address)));
UiText(std::to_string(lan_->PortFor(display_address)));
if (previous_address != display_address) {
// A disappearing address must not transfer its unfinished port edit to
// another interface. Explicit selection commits the old edit first.
@@ -2523,7 +2542,8 @@ void GuiApplication::Tick() {
if (!ui_->main->get_lan_port_editing() && !ui_->main->get_lan_port_dirty())
ui_->main->set_lan_local_port(port);
ui_->main->set_lan_listener_error(UiText(
display_address.empty() ? "Selected LAN interface is unavailable"
display_address.empty() && !lan_->identity().listen_all()
? "Selected LAN interface is unavailable"
: lan_->ListenerError(display_address)));
// Show failures one address at a time, without interrupting edits or another
// dialog. A working listener must not clear another address's error.
@@ -4570,6 +4590,8 @@ bool GuiApplication::ValidateSelfHostedSettingsFromUi(bool gallery_window) {
bool GuiApplication::SaveSettingsFromUi(bool gallery_window) {
const auto previous_server = GetSignalServerSettings(*config_center_);
const bool previous_ip_enabled = config_center_->IsIpDirectEnabled();
const auto previous_connection_mode = config_center_->GetConnectionMode();
auto& main = ui_->Window(gallery_window);
main->set_settings_save_error("");
const bool has_endpoint = main->get_self_hosted_enabled() ||
@@ -4630,6 +4652,32 @@ bool GuiApplication::SaveSettingsFromUi(bool gallery_window) {
localization::Literal("无法保存隐私屏解锁设置,请重试。", localization_language_index_)));
return false;
}
try {
const bool listen_all = main->get_lan_listen_all_draft();
if (LanAvailable() && main->get_ip_direct_enabled_draft()) {
lan_->SetListenAll(listen_all);
} else if (lan_) {
// Persist the preference without rebinding a disabled/stopping listener.
if (listen_all != lan_->identity().listen_all())
lan_->identity().SaveListenerMode(listen_all, {});
} else if (listen_all != main->get_lan_listen_all()) {
// Do not open/create an inactive identity when this preference is unchanged.
lan::Identity identity(path_manager_->GetConfigPath() / "lan");
identity.Load();
if (listen_all != identity.listen_all())
identity.SaveListenerMode(listen_all, {});
}
} catch (const std::exception& error) {
if (config_center_->SetIpDirectEnabled(previous_ip_enabled) != 0 ||
config_center_->SetConnectionMode(previous_connection_mode) != 0)
LOG_ERROR("Failed to restore IP availability after listener settings failure");
if (wall_changed && !ui_->gallery.Save(ui_->gallery_path))
LOG_ERROR("Failed to restore DeskHub settings after listener settings failure");
main->set_settings_tab(0);
main->set_settings_save_error(
main->global<ui::LanStrings>().invoke_error_message(UiText(error.what())));
return false;
}
language_button_value_ =
std::clamp(main->get_language_index(), 0, localization::kSystemLanguage);
video_encode_format_button_value_ = std::clamp(main->get_codec_index(), 0, 1);
@@ -492,6 +492,8 @@ inline constexpr TranslationRow kTranslationRows[] = {
{"高", {"高", "High", "Высокое", "高", "高", "높음", "Hoch", "Élevée", "Alta", "Alta"}},
{"(仅在交互终端显示)", {"(仅在交互终端显示)", "(shown only in an interactive terminal)", "(виден только в интерактивном терминале)", "(僅在互動終端顯示)", "(対話型端末でのみ表示)", "(대화형 터미널에만 표시됨)", "(wird nur in einem interaktiven Terminal angezeigt)", "(affiché uniquement dans un terminal interactif)", "(se muestra solo en una terminal interactiva)", "(mostrado apenas em um terminal interativo)"}},
{":同步键鼠到所有已连接设备", {":同步键鼠到所有已连接设备", ": mirror input to all connected devices", ": ввод на всех подключённых устройствах", ":同步鍵鼠到所有已連線裝置", ": 接続されているすべてのデバイスへのミラー入力", ": 연결된 모든 장치에 대한 미러 입력", ": Eingang auf alle angeschlossenen Geräte spiegeln", ": entrée miroir sur tous les appareils connectés", ": entrada espejo para todos los dispositivos conectados", ": entrada espelhada para todos os dispositivos conectados"}},
{"监听所有 IPv4 接口", {"监听所有 IPv4 接口", "Listen on all IPv4 interfaces", "Все интерфейсы IPv4", "監聽所有 IPv4 介面", "すべての IPv4 で待ち受け", "모든 IPv4 인터페이스 수신", "Alle IPv4-Schnittstellen", "Toutes les interfaces IPv4", "Todas las interfaces IPv4", "Todas as interfaces IPv4"}},
{"所有接口共用一个端口,包括 VPN。分享时请选择实际 IP;关闭后恢复各 IP 的独立端口。", {"所有接口共用一个端口,包括 VPN。分享时请选择实际 IP;关闭后恢复各 IP 的独立端口。", "Use one port on all interfaces, including VPNs. Select an actual IP to share. Turn off to restore separate ports per IP.", "Один порт для всех интерфейсов, включая VPN. Для подключения выберите реальный IP. Отключение вернёт отдельные порты для каждого IP.", "所有介面共用一個連接埠,包括 VPN。分享時請選擇實際 IP;關閉後恢復各 IP 的獨立連接埠。", "VPN を含む全インターフェースで同じポートを使います。共有には実際の IP を選択してください。オフにすると IP ごとのポートに戻ります。", "VPN을 포함한 모든 인터페이스가 하나의 포트를 사용합니다. 공유할 실제 IP를 선택하세요. 끄면 IP별 포트가 복원됩니다.", "Ein Port für alle Schnittstellen, einschließlich VPN. Zum Teilen eine tatsächliche IP wählen. Ausschalten stellt die Ports pro IP wieder her.", "Un port commun à toutes les interfaces, VPN inclus. Choisissez une IP réelle à partager. Désactivez pour rétablir les ports propres à chaque IP.", "Un puerto para todas las interfaces, incluidas las VPN. Seleccione una IP real para compartir. Desactive para restaurar los puertos de cada IP.", "Uma porta para todas as interfaces, incluindo VPNs. Selecione um IP real para compartilhar. Desative para restaurar as portas de cada IP."}},
};
struct NamedTranslation { const char* name; const char* literal; };
@@ -986,6 +988,8 @@ inline constexpr const char* kSlintLiterals[] = {
"未连接",
"确认断开",
"确认执行",
"监听所有 IPv4 接口",
"所有接口共用一个端口,包括 VPN。分享时请选择实际 IP;关闭后恢复各 IP 的独立端口。",
};
} // namespace detail
@@ -9,5 +9,6 @@ export { FileTransferNotification } from "file_transfer_notification.slint";
export { GalleryTile, GalleryRecentDevice, GalleryActionDevice } from "gallery_view.slint";
export { GalleryStrings } from "device_strings.slint";
export { LanStrings } from "lan_strings.slint";
export { GalleryNetworkDevice } from "gallery_network_dialog.slint";
export { DeviceGroupOption } from "device_group_dialog.slint";
+10 -3
View File
@@ -465,6 +465,7 @@ export component MainWindow inherits Window {
}
}
in-out property <bool> lan-listening;
in-out property <bool> lan-listen-all: true;
in-out property <string> lan-listener-ip;
in-out property <string> lan-listener-port: "52888";
out property <string> lan-listener-address: root.lan-listener-ip == "" ? "" : root.lan-listener-ip + ":" + root.lan-listener-port;
@@ -634,6 +635,7 @@ export component MainWindow inherits Window {
in-out property <int> capture-method-index: 0;
in-out property <int> codec-index: 0;
in-out property <bool> ip-direct-enabled-draft: true;
in-out property <bool> lan-listen-all-draft: true;
in property <string> settings-save-error;
in-out property <bool> force-relay-enabled: false;
in-out property <bool> self-hosted-enabled: false;
@@ -1282,7 +1284,7 @@ export component MainWindow inherits Window {
width: lan-edit-port-metrics.preferred-width + 20px; height: 22px;
quiet: true; text: lan-edit-port-metrics.text;
text-size: 12px; text-color: WorkbenchStyle.accent;
enabled: root.lan-mode && !root.lan-password-pending && root.lan-interfaces.length > 0;
enabled: root.lan-mode && !root.lan-password-pending && (root.lan-listen-all || root.lan-interfaces.length > 0);
clicked => {
// Moving from the editor to this action is still editing.
root.lan-apply-pending = false;
@@ -2720,11 +2722,16 @@ export component MainWindow inherits Window {
}
}
SettingsGroup {
title: GalleryStrings.texts[116]; rows: 3;
title: GalleryStrings.texts[116]; rows: 4;
SettingsToggleRow {
label: GalleryStrings.texts[117];
checked <=> root.ip-direct-enabled-draft;
}
SettingsToggleRow {
label: GalleryStrings.texts[206];
checked <=> root.lan-listen-all-draft;
enabled: root.ip-direct-enabled-draft;
}
SettingsToggleRow { label: UiStrings.force-relay; checked <=> root.force-relay-enabled; enabled: !root.settings-session-active; }
SettingsToggleRow {
label: UiStrings.self-hosted;
@@ -2744,7 +2751,7 @@ export component MainWindow inherits Window {
}
}
Text {
text: GalleryStrings.texts[119];
text: GalleryStrings.texts[119] + "\n" + GalleryStrings.texts[207];
font-size: 11px; color: WorkbenchStyle.muted; wrap: word-wrap;
}
SettingsGroup {