mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-05 21:52:46 +08:00
[feat] negotiate per-device preview and audio subscriptions
This commit is contained in:
@@ -49,7 +49,14 @@ SessionDeviceManager::SessionDeviceManager(GuiRuntime &owner)
|
|||||||
frame.data = reinterpret_cast<const char *>(data);
|
frame.data = reinterpret_cast<const char *>(data);
|
||||||
frame.size = size;
|
frame.size = size;
|
||||||
frame.captured_timestamp = GetSystemTimeMicros(owner_.peer_);
|
frame.captured_timestamp = GetSystemTimeMicros(owner_.peer_);
|
||||||
SendAudioFrame(owner_.peer_, &frame, owner_.audio_label_.c_str());
|
std::shared_lock lock(owner_.connection_status_mutex_);
|
||||||
|
for (const auto& [id, status] : owner_.connection_status_) {
|
||||||
|
if (status == ConnectionStatus::Connected &&
|
||||||
|
owner_.preview_subscriptions_.ShouldSendAudio(id))
|
||||||
|
SendAudioFrameToPeer(owner_.peer_, &frame,
|
||||||
|
owner_.audio_label_.c_str(), id.data(),
|
||||||
|
id.size());
|
||||||
|
}
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
SessionDeviceManager::~SessionDeviceManager() { speaker_capture_.Shutdown(); }
|
SessionDeviceManager::~SessionDeviceManager() { speaker_capture_.Shutdown(); }
|
||||||
@@ -245,6 +252,8 @@ int SessionDeviceManager::InitializeScreenCapturer() {
|
|||||||
const auto send_started = std::chrono::steady_clock::now();
|
const auto send_started = std::chrono::steady_clock::now();
|
||||||
uint64_t send_errors = 0;
|
uint64_t send_errors = 0;
|
||||||
for (const std::string &remote_id : connected_remote_ids) {
|
for (const std::string &remote_id : connected_remote_ids) {
|
||||||
|
if (!owner_.preview_subscriptions_.ShouldSend(remote_id, now_time,
|
||||||
|
stream_id)) continue;
|
||||||
if (SendVideoFrameToPeer(owner_.peer_, &frame, stream_id.c_str(),
|
if (SendVideoFrameToPeer(owner_.peer_, &frame, stream_id.c_str(),
|
||||||
remote_id.data(), remote_id.size()) != 0)
|
remote_id.data(), remote_id.size()) != 0)
|
||||||
++send_errors;
|
++send_errors;
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ void GuiRuntime::HandlePresenceProbeTimeout() {
|
|||||||
|
|
||||||
void GuiRuntime::HandleServerControllerDisconnected(
|
void GuiRuntime::HandleServerControllerDisconnected(
|
||||||
const std::string& remote_id, const char* reason) {
|
const std::string& remote_id, const char* reason) {
|
||||||
|
preview_subscriptions_.Remove(remote_id);
|
||||||
keyboard_.ReleaseRemotePressedKeys(remote_id, reason);
|
keyboard_.ReleaseRemotePressedKeys(remote_id, reason);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
devices_.ReleaseRemoteMouseButtons();
|
devices_.ReleaseRemoteMouseButtons();
|
||||||
|
|||||||
@@ -151,14 +151,67 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string remote_id(user_id, user_id_size);
|
||||||
std::string json_str(data, size);
|
std::string json_str(data, size);
|
||||||
|
const auto preview_json =
|
||||||
|
json_str.find("\"deskhub_preview") != std::string::npos
|
||||||
|
? nlohmann::json::parse(json_str, nullptr, false)
|
||||||
|
: nlohmann::json{};
|
||||||
|
if (preview_json.is_object() &&
|
||||||
|
preview_json.value("type", nlohmann::json{}) == "deskhub_preview") {
|
||||||
|
if (source_id != runtime->control_data_label_ ||
|
||||||
|
!runtime->IsAuthorizedController(remote_id))
|
||||||
|
return;
|
||||||
|
const auto subscription = PreviewSubscription::Parse(preview_json);
|
||||||
|
if (!subscription) return;
|
||||||
|
bool accepted = true;
|
||||||
|
// Apply encoder quality to this controller only; other controllers retain
|
||||||
|
// their own demand. Frame gating happens before encoding and transmission.
|
||||||
|
if (subscription->fps > 0 && runtime->preview_subscriptions_.IsNewer(
|
||||||
|
remote_id, subscription->request_id))
|
||||||
|
accepted = UpdateVideoSettings(
|
||||||
|
runtime->peer_, remote_id.data(), remote_id.size(),
|
||||||
|
static_cast<VideoQuality>(subscription->quality),
|
||||||
|
subscription->frame_rate,
|
||||||
|
static_cast<VideoDegradationPreference>(
|
||||||
|
subscription->EncoderPreference())) == 0;
|
||||||
|
if (accepted && runtime->preview_subscriptions_.Set(remote_id, *subscription) &&
|
||||||
|
subscription->fps > 0)
|
||||||
|
RequestAllVideoKeyFrames(runtime->peer_);
|
||||||
|
const auto reply = nlohmann::json{
|
||||||
|
{"type", "deskhub_preview_status"},
|
||||||
|
{"version", 1},
|
||||||
|
{"request_id", subscription->request_id},
|
||||||
|
{"accepted",
|
||||||
|
accepted}}.dump();
|
||||||
|
SendReliableDataFrameToPeer(runtime->peer_, reply.data(), reply.size(),
|
||||||
|
runtime->control_data_label_.c_str(),
|
||||||
|
remote_id.data(), remote_id.size());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (preview_json.is_object() &&
|
||||||
|
preview_json.value("type", nlohmann::json{}) ==
|
||||||
|
"deskhub_preview_status") {
|
||||||
|
if (auto props = runtime->FindRemoteSession(remote_id);
|
||||||
|
props && source_id == props->control_data_label_) {
|
||||||
|
try {
|
||||||
|
if (preview_json.at("version") == 1 &&
|
||||||
|
preview_json.at("request_id").is_number_unsigned() &&
|
||||||
|
preview_json.at("accepted").is_boolean()) {
|
||||||
|
props->preview_accepted_ = preview_json.at("accepted").get<bool>();
|
||||||
|
props->preview_ack_ = preview_json.at("request_id").get<uint32_t>();
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
RemoteAction remote_action{};
|
RemoteAction remote_action{};
|
||||||
if (!remote_action.from_json(json_str)) {
|
if (!remote_action.from_json(json_str)) {
|
||||||
LOG_ERROR("Failed to parse RemoteAction JSON payload");
|
LOG_ERROR("Failed to parse RemoteAction JSON payload");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string remote_id(user_id, user_id_size);
|
|
||||||
if (remote_action.type == ControlType::video_settings_status) {
|
if (remote_action.type == ControlType::video_settings_status) {
|
||||||
if (auto props = runtime->FindRemoteSession(remote_id)) {
|
if (auto props = runtime->FindRemoteSession(remote_id)) {
|
||||||
if (source_id != props->control_data_label_) return;
|
if (source_id != props->control_data_label_) return;
|
||||||
@@ -342,6 +395,8 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
|||||||
if (props && source_id == props->control_data_label_) {
|
if (props && source_id == props->control_data_label_) {
|
||||||
std::lock_guard lock(props->video_settings_mutex_);
|
std::lock_guard lock(props->video_settings_mutex_);
|
||||||
const bool first_advertisement = !props->video_settings_supported_;
|
const bool first_advertisement = !props->video_settings_supported_;
|
||||||
|
props->preview_subscription_supported_ =
|
||||||
|
remote_action.i.supports_deskhub_preview;
|
||||||
props->video_settings_supported_ =
|
props->video_settings_supported_ =
|
||||||
remote_action.i.supports_video_settings;
|
remote_action.i.supports_video_settings;
|
||||||
if (first_advertisement && props->video_settings_supported_) {
|
if (first_advertisement && props->video_settings_supported_) {
|
||||||
|
|||||||
@@ -288,6 +288,7 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
|||||||
/*supports_privacy_screen=*/!runtime->unattended_host_,
|
/*supports_privacy_screen=*/!runtime->unattended_host_,
|
||||||
CROSSDESK_VERSION, kClientPlatform);
|
CROSSDESK_VERSION, kClientPlatform);
|
||||||
remote_action.i.supports_video_settings = true;
|
remote_action.i.supports_video_settings = true;
|
||||||
|
remote_action.i.supports_deskhub_preview = true;
|
||||||
for (std::size_t i = 0; i < remote_action.i.display_num; i++) {
|
for (std::size_t i = 0; i < remote_action.i.display_num; i++) {
|
||||||
LOG_INFO("Local display [{}:{}]", i + 1,
|
LOG_INFO("Local display [{}:{}]", i + 1,
|
||||||
remote_action.i.display_list[i]);
|
remote_action.i.display_list[i]);
|
||||||
@@ -327,6 +328,7 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
|||||||
}
|
}
|
||||||
props->net_traffic_stats_.Reset();
|
props->net_traffic_stats_.Reset();
|
||||||
props->enable_mouse_control_ = false;
|
props->enable_mouse_control_ = false;
|
||||||
|
props->preview_subscription_supported_ = false;
|
||||||
runtime->ResetRemoteServiceStatus(*props);
|
runtime->ResetRemoteServiceStatus(*props);
|
||||||
{
|
{
|
||||||
std::lock_guard lock(props->video_settings_mutex_);
|
std::lock_guard lock(props->video_settings_mutex_);
|
||||||
@@ -414,6 +416,7 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
|||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case ConnectionStatus::Connected: {
|
case ConnectionStatus::Connected: {
|
||||||
|
runtime->preview_subscriptions_.Remove(remote_id);
|
||||||
#if _WIN32
|
#if _WIN32
|
||||||
runtime->last_windows_service_status_tick_ = 0;
|
runtime->last_windows_service_status_tick_ = 0;
|
||||||
#endif
|
#endif
|
||||||
@@ -423,6 +426,7 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
|||||||
/*supports_privacy_screen=*/!runtime->unattended_host_,
|
/*supports_privacy_screen=*/!runtime->unattended_host_,
|
||||||
CROSSDESK_VERSION, kClientPlatform);
|
CROSSDESK_VERSION, kClientPlatform);
|
||||||
remote_action.i.supports_video_settings = true;
|
remote_action.i.supports_video_settings = true;
|
||||||
|
remote_action.i.supports_deskhub_preview = true;
|
||||||
for (std::size_t i = 0; i < remote_action.i.display_num; i++) {
|
for (std::size_t i = 0; i < remote_action.i.display_num; i++) {
|
||||||
LOG_INFO("Local display [{}:{}]", i + 1,
|
LOG_INFO("Local display [{}:{}]", i + 1,
|
||||||
remote_action.i.display_list[i]);
|
remote_action.i.display_list[i]);
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2026-10-12
|
||||||
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREVIEW_SUBSCRIPTIONS_H_
|
||||||
|
#define _PREVIEW_SUBSCRIPTIONS_H_
|
||||||
|
|
||||||
|
#include <preview_subscription.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
// Accessed by the network callback and screen-capture callback. Legacy
|
||||||
|
// controllers have no entry and continue receiving their normal stream.
|
||||||
|
class PreviewSubscriptions {
|
||||||
|
public:
|
||||||
|
using Clock = std::chrono::steady_clock;
|
||||||
|
bool IsNewer(const std::string &id, uint32_t request_id) {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
const auto found = states_.find(id);
|
||||||
|
return found == states_.end() ||
|
||||||
|
request_id > found->second.request.request_id;
|
||||||
|
}
|
||||||
|
bool Set(const std::string &id, const PreviewSubscription &request) {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
auto &state = states_[id];
|
||||||
|
if (request.request_id <= state.request.request_id)
|
||||||
|
return false;
|
||||||
|
state.request = request;
|
||||||
|
state.next.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool ShouldSend(const std::string &id, Clock::time_point now = Clock::now(),
|
||||||
|
const std::string &stream = {}) {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
const auto found = states_.find(id);
|
||||||
|
if (found == states_.end())
|
||||||
|
return true;
|
||||||
|
auto &state = found->second;
|
||||||
|
if (!state.request.fps)
|
||||||
|
return false;
|
||||||
|
// Interactive streams use the encoder's own 30/60 FPS cadence. A second
|
||||||
|
// time gate here can drop valid frames because capture timing varies.
|
||||||
|
if (state.request.fps == 60)
|
||||||
|
return true;
|
||||||
|
auto &next = state.next[stream];
|
||||||
|
if (now < next)
|
||||||
|
return false;
|
||||||
|
next = now + std::chrono::milliseconds(1000 / state.request.fps);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool ShouldSendAudio(const std::string &id) {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
const auto found = states_.find(id);
|
||||||
|
return found == states_.end() || found->second.request.audio;
|
||||||
|
}
|
||||||
|
void Remove(const std::string &id) {
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
states_.erase(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct State {
|
||||||
|
PreviewSubscription request;
|
||||||
|
// One display must not consume another display's preview frame budget.
|
||||||
|
std::unordered_map<std::string, Clock::time_point> next;
|
||||||
|
};
|
||||||
|
std::mutex mutex_;
|
||||||
|
std::unordered_map<std::string, State> states_;
|
||||||
|
};
|
||||||
|
} // namespace crossdesk
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <remote_action.h>
|
#include <remote_action.h>
|
||||||
|
#include <preview_subscription.h>
|
||||||
#include <stream_names.h>
|
#include <stream_names.h>
|
||||||
#include "display_info.h"
|
#include "display_info.h"
|
||||||
#include "cursor_presentation.h"
|
#include "cursor_presentation.h"
|
||||||
@@ -70,6 +71,12 @@ struct FileTransferState {
|
|||||||
// Runtime state for one connected or connecting remote endpoint. It groups the
|
// Runtime state for one connected or connecting remote endpoint. It groups the
|
||||||
// connection, media, input, window and transfer data that share one lifetime.
|
// connection, media, input, window and transfer data that share one lifetime.
|
||||||
struct RemoteSession {
|
struct RemoteSession {
|
||||||
|
std::atomic<bool> preview_subscription_supported_{false};
|
||||||
|
std::atomic<uint32_t> preview_ack_{0};
|
||||||
|
std::atomic<bool> preview_accepted_{false};
|
||||||
|
uint32_t preview_request_ = 0;
|
||||||
|
PreviewSubscription preview_requested_;
|
||||||
|
std::chrono::steady_clock::time_point preview_request_at_{};
|
||||||
Params params_;
|
Params params_;
|
||||||
PeerPtr* peer_ = nullptr;
|
PeerPtr* peer_ = nullptr;
|
||||||
std::shared_ptr<PeerEventHandler> peer_events_;
|
std::shared_ptr<PeerEventHandler> peer_events_;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "common/task_queue_lock_free.h"
|
#include "common/task_queue_lock_free.h"
|
||||||
#include "path_manager.h"
|
#include "path_manager.h"
|
||||||
#include "runtime/device_presence_cache.h"
|
#include "runtime/device_presence_cache.h"
|
||||||
|
#include "runtime/preview_subscriptions.h"
|
||||||
#include "runtime/remote_session.h"
|
#include "runtime/remote_session.h"
|
||||||
#include "runtime/session_lifecycle.h"
|
#include "runtime/session_lifecycle.h"
|
||||||
#include "thumbnail.h"
|
#include "thumbnail.h"
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
namespace crossdesk::gui_detail {
|
namespace crossdesk::gui_detail {
|
||||||
|
|
||||||
struct InfrastructureState {
|
struct InfrastructureState {
|
||||||
|
PreviewSubscriptions preview_subscriptions_;
|
||||||
std::unique_ptr<ConfigCenter> config_center_;
|
std::unique_ptr<ConfigCenter> config_center_;
|
||||||
ConfigCenter::LANGUAGE localization_language_ =
|
ConfigCenter::LANGUAGE localization_language_ =
|
||||||
ConfigCenter::LANGUAGE::CHINESE;
|
ConfigCenter::LANGUAGE::CHINESE;
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2026-10-12
|
||||||
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PREVIEW_SUBSCRIPTION_H_
|
||||||
|
#define _PREVIEW_SUBSCRIPTION_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
// Versioned independently from the interactive 30/60 FPS video settings.
|
||||||
|
struct PreviewSubscription {
|
||||||
|
uint32_t request_id = 0;
|
||||||
|
int fps =
|
||||||
|
0; // 0 pauses encoding/delivery, 1 overview, 8 focus, 60 interactive.
|
||||||
|
int quality = 0;
|
||||||
|
int frame_rate = 30;
|
||||||
|
int preference = 0;
|
||||||
|
bool audio = false;
|
||||||
|
|
||||||
|
static PreviewSubscription Interactive(int quality, int frame_rate,
|
||||||
|
int preference, bool audio = false) {
|
||||||
|
return {0, 60, quality, frame_rate, preference, audio};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SameSettings(const PreviewSubscription &other) const {
|
||||||
|
return fps == other.fps && quality == other.quality &&
|
||||||
|
frame_rate == other.frame_rate &&
|
||||||
|
EncoderPreference() == other.EncoderPreference() &&
|
||||||
|
audio == other.audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
int EncoderPreference() const {
|
||||||
|
// Sparse preview input is intentional. Preserve spatial detail instead of
|
||||||
|
// applying the interactive frame-rate/keyframe latency budget to it.
|
||||||
|
return fps == 1 || fps == 8 ? 1 : preference;
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json ToJson() const {
|
||||||
|
return {{"type", "deskhub_preview"}, {"version", 1},
|
||||||
|
{"request_id", request_id}, {"fps", fps},
|
||||||
|
{"quality", quality}, {"frame_rate", frame_rate},
|
||||||
|
{"preference", EncoderPreference()}, {"audio", audio}};
|
||||||
|
}
|
||||||
|
static std::optional<PreviewSubscription> Parse(const nlohmann::json &json) {
|
||||||
|
try {
|
||||||
|
if (json.at("type") != "deskhub_preview" || json.at("version") != 1)
|
||||||
|
return {};
|
||||||
|
for (const char *key :
|
||||||
|
{"request_id", "fps", "quality", "frame_rate", "preference"})
|
||||||
|
if (!json.at(key).is_number_integer())
|
||||||
|
return {};
|
||||||
|
const auto id = json.at("request_id").get<int64_t>();
|
||||||
|
if (id <= 0 || id > UINT32_MAX)
|
||||||
|
return {};
|
||||||
|
for (const char *key : {"fps", "quality", "frame_rate", "preference"}) {
|
||||||
|
const auto value = json.at(key).get<int64_t>();
|
||||||
|
if (value < 0 || value > 60)
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
PreviewSubscription result{static_cast<uint32_t>(id), json.at("fps"),
|
||||||
|
json.at("quality"), json.at("frame_rate"),
|
||||||
|
json.at("preference")};
|
||||||
|
if ((result.fps != 0 && result.fps != 1 && result.fps != 8 &&
|
||||||
|
result.fps != 60) ||
|
||||||
|
result.quality < 0 || result.quality > 2 ||
|
||||||
|
(result.frame_rate != 30 && result.frame_rate != 60) ||
|
||||||
|
result.preference < 0 || result.preference > 2)
|
||||||
|
return {};
|
||||||
|
if (json.contains("audio")) {
|
||||||
|
if (!json.at("audio").is_boolean())
|
||||||
|
return {};
|
||||||
|
result.audio = json.at("audio").get<bool>();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (...) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace crossdesk
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -137,6 +137,7 @@ struct HostInfo {
|
|||||||
// Optional capability; absent in legacy desktop and web host information.
|
// Optional capability; absent in legacy desktop and web host information.
|
||||||
bool supports_privacy_screen;
|
bool supports_privacy_screen;
|
||||||
bool supports_video_settings;
|
bool supports_video_settings;
|
||||||
|
bool supports_deskhub_preview;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ServiceStatus {
|
struct ServiceStatus {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ void ResetHostInfo(HostInfo& info) {
|
|||||||
info.bottom = nullptr;
|
info.bottom = nullptr;
|
||||||
info.supports_privacy_screen = false;
|
info.supports_privacy_screen = false;
|
||||||
info.supports_video_settings = false;
|
info.supports_video_settings = false;
|
||||||
|
info.supports_deskhub_preview = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AllocateHostDisplays(HostInfo& info, std::size_t count) {
|
bool AllocateHostDisplays(HostInfo& info, std::size_t count) {
|
||||||
@@ -219,7 +220,8 @@ std::string RemoteAction::ToJson(const RemoteAction& action) {
|
|||||||
{"display_num", action.i.display_num},
|
{"display_num", action.i.display_num},
|
||||||
{"displays", displays},
|
{"displays", displays},
|
||||||
{"supports_privacy_screen", action.i.supports_privacy_screen},
|
{"supports_privacy_screen", action.i.supports_privacy_screen},
|
||||||
{"supports_video_settings", action.i.supports_video_settings}};
|
{"supports_video_settings", action.i.supports_video_settings},
|
||||||
|
{"supports_deskhub_preview", action.i.supports_deskhub_preview}};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ControlType::invalid:
|
case ControlType::invalid:
|
||||||
@@ -412,6 +414,8 @@ bool RemoteAction::FromJson(const std::string& json_string,
|
|||||||
privacy_support->is_boolean() && privacy_support->get<bool>();
|
privacy_support->is_boolean() && privacy_support->get<bool>();
|
||||||
output.i.supports_video_settings =
|
output.i.supports_video_settings =
|
||||||
host_info_object.value("supports_video_settings", false);
|
host_info_object.value("supports_video_settings", false);
|
||||||
|
output.i.supports_deskhub_preview =
|
||||||
|
host_info_object.value("supports_deskhub_preview", false);
|
||||||
const std::string host_name =
|
const std::string host_name =
|
||||||
host_info_object.at("host_name").get<std::string>();
|
host_info_object.at("host_name").get<std::string>();
|
||||||
std::strncpy(output.i.host_name, host_name.c_str(),
|
std::strncpy(output.i.host_name, host_name.c_str(),
|
||||||
|
|||||||
Reference in New Issue
Block a user