mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-13 20:25:53 +08:00
[feat] add remote display resolution selection to video settings
This commit is contained in:
1 parent
14fa95beeb
commit
a3159b198e
15 files changed
+740
-43
No files matched your search
@@ -0,0 +1,89 @@
|
||||
#include "runtime/gui_runtime.h"
|
||||
|
||||
#include <SDL3/SDL_timer.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "interactive_desktop.h"
|
||||
#include "platform/windows/screen_capturer/display_resolution_win.h"
|
||||
#endif
|
||||
|
||||
namespace crossdesk {
|
||||
namespace {
|
||||
// EnumDisplaySettings caches its enumeration. Serialize queries on transport
|
||||
// threads with mode changes on the application thread.
|
||||
std::mutex native_display_resolution_mutex;
|
||||
} // namespace
|
||||
|
||||
std::vector<DisplayResolutionInfo> GuiRuntime::QueryHostDisplayResolutions() {
|
||||
#ifdef _WIN32
|
||||
std::lock_guard lock(native_display_resolution_mutex);
|
||||
return QueryWindowsDisplayResolutions(devices_.display_info_list());
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
void GuiRuntime::QueueDisplayResolutionChange(
|
||||
const std::string &id, PeerEventHandler *source,
|
||||
const DisplayResolutionChange &change) {
|
||||
const auto events = ControllerEvents(id);
|
||||
if (events.get() != source || !IsAuthorizedController(id))
|
||||
return;
|
||||
std::lock_guard lock(display_resolution_commands_mutex_);
|
||||
if (display_resolution_commands_.size() >= 32)
|
||||
return;
|
||||
display_resolution_commands_.push_back({id, events, change});
|
||||
}
|
||||
|
||||
void GuiRuntime::HandleDisplayResolutionChanges() {
|
||||
std::deque<PendingDisplayResolution> commands;
|
||||
{
|
||||
std::lock_guard lock(display_resolution_commands_mutex_);
|
||||
commands.swap(display_resolution_commands_);
|
||||
}
|
||||
for (const auto &command : commands) {
|
||||
const auto source = command.source.lock();
|
||||
if (!source || !source->IsActive() ||
|
||||
source != ControllerEvents(command.remote_id) ||
|
||||
!IsAuthorizedController(command.remote_id))
|
||||
continue;
|
||||
RemoteAction reply;
|
||||
reply.type = ControlType::display_resolution_status;
|
||||
reply.resolution_change = command.change;
|
||||
reply.resolution_change.accepted = false;
|
||||
#ifdef _WIN32
|
||||
{
|
||||
std::lock_guard lock(native_display_resolution_mutex);
|
||||
if (IsCurrentSessionUserDesktopActive())
|
||||
reply.resolution_change.accepted = ApplyWindowsDisplayResolution(
|
||||
devices_.display_info_list(), command.change);
|
||||
}
|
||||
#endif
|
||||
reply.display_resolutions = QueryHostDisplayResolutions();
|
||||
const auto message = reply.to_json();
|
||||
SendControllerData(command.remote_id, message.data(), message.size(),
|
||||
control_data_label_.c_str());
|
||||
next_display_resolution_poll_ = 0;
|
||||
}
|
||||
const auto now = SDL_GetTicks();
|
||||
if (now < next_display_resolution_poll_)
|
||||
return;
|
||||
next_display_resolution_poll_ = now + 2000;
|
||||
const auto controllers = ConnectedControllers();
|
||||
if (controllers.empty())
|
||||
return;
|
||||
auto current = QueryHostDisplayResolutions();
|
||||
if (current == last_display_resolutions_)
|
||||
return;
|
||||
last_display_resolutions_ = std::move(current);
|
||||
// Re-advertise after local changes/hotplug as well as remote requests. The
|
||||
// optional host-info extension is harmless to pre-feature controllers.
|
||||
const auto message = BuildHostInformation();
|
||||
for (const auto &id : controllers) {
|
||||
if (IsAuthorizedController(id))
|
||||
SendControllerData(id, message.data(), message.size(),
|
||||
control_data_label_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-11-11
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _DISPLAY_RESOLUTION_STATE_H_
|
||||
#define _DISPLAY_RESOLUTION_STATE_H_
|
||||
|
||||
#include <display_resolution.h>
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
// Guarded by RemoteSession::display_resolution_mutex_. The current selection
|
||||
// always comes from the host, never from an unacknowledged request.
|
||||
struct DisplayResolutionState {
|
||||
std::vector<DisplayResolutionInfo> displays;
|
||||
DisplayResolutionChange requested;
|
||||
bool pending = false;
|
||||
bool failed = false;
|
||||
uint64_t request_tick = 0;
|
||||
|
||||
bool Begin(const DisplayResolutionChange &selection, uint64_t now) {
|
||||
if (pending)
|
||||
return false;
|
||||
for (const auto &display : displays) {
|
||||
if (!SupportsDisplayResolution(display, selection))
|
||||
continue;
|
||||
if (display.current == selection.resolution)
|
||||
return false;
|
||||
uint32_t next = requested.request_id + 1;
|
||||
if (next == 0)
|
||||
next = 1;
|
||||
requested = {display.display_id, display.display_key,
|
||||
selection.resolution, next, false};
|
||||
pending = true;
|
||||
failed = false;
|
||||
request_tick = now;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Complete(const DisplayResolutionChange &response,
|
||||
std::vector<DisplayResolutionInfo> actual) {
|
||||
if (response.request_id != requested.request_id ||
|
||||
response.display_id != requested.display_id ||
|
||||
response.display_key != requested.display_key ||
|
||||
!(response.resolution == requested.resolution))
|
||||
return;
|
||||
displays = std::move(actual);
|
||||
pending = false;
|
||||
failed = !response.accepted;
|
||||
if (response.accepted) {
|
||||
const auto found = std::find_if(
|
||||
displays.begin(), displays.end(), [&](const auto &display) {
|
||||
return display.display_id == response.display_id &&
|
||||
display.display_key == response.display_key &&
|
||||
display.current == response.resolution;
|
||||
});
|
||||
failed = found == displays.end();
|
||||
}
|
||||
}
|
||||
|
||||
void Expire(uint64_t now) {
|
||||
if (pending && now - request_tick >= 10000) {
|
||||
pending = false;
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
@@ -152,6 +152,11 @@ class GuiRuntime : protected gui_detail::GuiState {
|
||||
// any connected controller lacks support, or on for the first automatic one.
|
||||
void ApplyPrivacyAdmission(bool automatic_enable);
|
||||
void QueuePrivacyCommand(const std::string& remote_id, const PrivacyCommand& command);
|
||||
std::vector<DisplayResolutionInfo> QueryHostDisplayResolutions();
|
||||
void QueueDisplayResolutionChange(const std::string& id,
|
||||
PeerEventHandler* source,
|
||||
const DisplayResolutionChange& change);
|
||||
void HandleDisplayResolutionChanges();
|
||||
|
||||
void CloseRemoteSession(std::shared_ptr<RemoteSession> props);
|
||||
void CloseAllRemoteSessions();
|
||||
@@ -247,6 +252,15 @@ class GuiRuntime : protected gui_detail::GuiState {
|
||||
DisconnectLockPolicy disconnect_lock_;
|
||||
std::mutex privacy_commands_mutex_;
|
||||
std::deque<std::pair<std::string, PrivacyCommand>> privacy_commands_;
|
||||
struct PendingDisplayResolution {
|
||||
std::string remote_id;
|
||||
std::weak_ptr<PeerEventHandler> source;
|
||||
DisplayResolutionChange change;
|
||||
};
|
||||
std::mutex display_resolution_commands_mutex_;
|
||||
std::deque<PendingDisplayResolution> display_resolution_commands_;
|
||||
std::vector<DisplayResolutionInfo> last_display_resolutions_;
|
||||
uint64_t next_display_resolution_poll_ = 0;
|
||||
bool privacy_settings_changed_ = false;
|
||||
uint32_t last_privacy_revision_ = 0;
|
||||
uint64_t last_privacy_status_tick_ = 0;
|
||||
|
||||
@@ -280,6 +280,21 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
||||
return;
|
||||
}
|
||||
|
||||
if (remote_action.type == ControlType::display_resolution_status) {
|
||||
if (auto props = runtime->FindRemoteSession(remote_id);
|
||||
props && source_id == props->control_data_label_ && !props->closing_ &&
|
||||
props->connection_status_ == MiniRtcConnectionStatus::MiniRtcConnectionConnected) {
|
||||
std::lock_guard lock(props->display_resolution_mutex_);
|
||||
props->display_resolution_.Complete(remote_action.resolution_change,
|
||||
std::move(remote_action.display_resolutions));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (remote_action.type == ControlType::display_resolution) {
|
||||
if (source_id == runtime->control_data_label_ || source_id == kWebControlStream)
|
||||
runtime->QueueDisplayResolutionChange(remote_id, handler, remote_action.resolution_change);
|
||||
return;
|
||||
}
|
||||
if (remote_action.type == ControlType::video_codec_status) {
|
||||
if (auto props = runtime->FindRemoteSession(remote_id);
|
||||
props && source_id == props->control_data_label_ &&
|
||||
@@ -460,6 +475,10 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
||||
}
|
||||
|
||||
if (is_client_mode) {
|
||||
if (props && source_id == props->control_data_label_) {
|
||||
std::lock_guard lock(props->display_resolution_mutex_);
|
||||
props->display_resolution_.displays = std::move(remote_action.display_resolutions);
|
||||
}
|
||||
if (props && source_id == props->control_data_label_) {
|
||||
std::lock_guard lock(props->remote_version_mutex_);
|
||||
props->remote_host_info_received_ = true;
|
||||
@@ -502,7 +521,11 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
||||
props->remote_host_name_ = std::string(remote_action.i.host_name,
|
||||
remote_action.i.host_name_size);
|
||||
LOG_DEBUG("Remote hostname: [{}]", props->remote_host_name_);
|
||||
|
||||
}
|
||||
// Preserve legacy hosts' initial display list on their original data
|
||||
// channel; live topology updates use the reliable control channel.
|
||||
if (source_id == props->control_data_label_ || props->display_info_list_.empty()) {
|
||||
props->display_info_list_.clear();
|
||||
for (int i = 0; i < remote_action.i.display_num; i++) {
|
||||
props->display_info_list_.push_back(
|
||||
DisplayInfo(remote_action.i.display_list[i],
|
||||
|
||||
@@ -333,6 +333,10 @@ void PeerEventHandler::DispatchConnectionStatus(MiniRtcConnectionStatus status,
|
||||
if (!runtime->BeginControllerTrust(remote_id, handler)) return;
|
||||
} else {
|
||||
runtime->ForgetControllerTrust(remote_id, handler);
|
||||
std::lock_guard lock(runtime->display_resolution_commands_mutex_);
|
||||
std::erase_if(runtime->display_resolution_commands_, [&](const auto& request) {
|
||||
return request.remote_id == remote_id;
|
||||
});
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (runtime->unattended_host_) {
|
||||
@@ -422,6 +426,10 @@ void PeerEventHandler::DispatchConnectionStatus(MiniRtcConnectionStatus status,
|
||||
props->video_settings_pending_ = false;
|
||||
props->video_settings_failed_ = false;
|
||||
}
|
||||
{
|
||||
std::lock_guard lock(props->display_resolution_mutex_);
|
||||
props->display_resolution_ = {};
|
||||
}
|
||||
{
|
||||
std::lock_guard lock(props->remote_cursor_state_mutex_);
|
||||
props->remote_cursor_state_ = {};
|
||||
@@ -648,6 +656,7 @@ void PeerEventHandler::OnNetStatusReport(
|
||||
std::string GuiRuntime::BuildHostInformation(bool advertise_trust) {
|
||||
auto action = MakeHostInformation(GetHostName(), devices_.host_display_list(),
|
||||
UserSessionFeaturesAvailable(), CROSSDESK_VERSION, kClientPlatform);
|
||||
action.display_resolutions = QueryHostDisplayResolutions();
|
||||
action.i.supports_video_settings = true;
|
||||
action.i.supports_video_codec_status = true;
|
||||
action.i.supports_text_input = devices_.SupportsTextInput();
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <stream_names.h>
|
||||
#include "display_info.h"
|
||||
#include "features/gallery/preview_resolution.h"
|
||||
#include "runtime/display_resolution_state.h"
|
||||
#include "cursor_presentation.h"
|
||||
#include "minirtc.h"
|
||||
#include "rendering/video_latency.h"
|
||||
@@ -203,6 +204,8 @@ struct RemoteSession {
|
||||
bool remote_service_available_ = false;
|
||||
std::string remote_interactive_stage_;
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
std::mutex display_resolution_mutex_;
|
||||
DisplayResolutionState display_resolution_;
|
||||
// Cursor snapshots arrive on the transport callback thread and are applied
|
||||
// by the Slint UI thread. Keep the snapshot atomic as a unit so visibility
|
||||
// and shape cannot briefly come from different protocol messages.
|
||||
|
||||
Reference in new issue
Block a user