mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-08-26 05:15:45 +08:00
[feat] synchronize remote cursor state
This commit is contained in:
@@ -28,6 +28,7 @@ typedef enum {
|
||||
service_status = 5,
|
||||
service_command = 6,
|
||||
keyboard_state = 7,
|
||||
cursor_state = 8,
|
||||
} ControlType;
|
||||
typedef enum {
|
||||
move = 0,
|
||||
@@ -70,6 +71,47 @@ typedef struct {
|
||||
KeyboardStateKey pressed_keys[kMaxKeyboardStateKeys];
|
||||
} KeyboardState;
|
||||
|
||||
// Keep these values aligned with Slint's MouseCursor enum. The wire protocol
|
||||
// intentionally carries a semantic cursor instead of a platform handle so a
|
||||
// Windows, macOS or Linux host can control a different desktop platform.
|
||||
enum class CursorShape : uint8_t {
|
||||
default_cursor = 0,
|
||||
none,
|
||||
help,
|
||||
pointer,
|
||||
progress,
|
||||
wait,
|
||||
crosshair,
|
||||
text,
|
||||
alias,
|
||||
copy,
|
||||
move,
|
||||
no_drop,
|
||||
not_allowed,
|
||||
grab,
|
||||
grabbing,
|
||||
col_resize,
|
||||
row_resize,
|
||||
n_resize,
|
||||
e_resize,
|
||||
s_resize,
|
||||
w_resize,
|
||||
ne_resize,
|
||||
nw_resize,
|
||||
se_resize,
|
||||
sw_resize,
|
||||
ew_resize,
|
||||
ns_resize,
|
||||
nesw_resize,
|
||||
nwse_resize,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint32_t seq;
|
||||
bool visible;
|
||||
CursorShape shape;
|
||||
} CursorState;
|
||||
|
||||
typedef struct {
|
||||
char host_name[64];
|
||||
size_t host_name_size;
|
||||
@@ -96,6 +138,7 @@ struct RemoteAction {
|
||||
Mouse m;
|
||||
Key k;
|
||||
KeyboardState ks;
|
||||
CursorState cs;
|
||||
HostInfo i;
|
||||
bool a;
|
||||
int d;
|
||||
@@ -141,6 +184,11 @@ struct RemoteAction {
|
||||
j["keyboard_state"] = {{"seq", a.ks.seq}, {"pressed_keys", keys}};
|
||||
break;
|
||||
}
|
||||
case ControlType::cursor_state:
|
||||
j["cursor_state"] = {{"seq", a.cs.seq},
|
||||
{"visible", a.cs.visible},
|
||||
{"shape", static_cast<int>(a.cs.shape)}};
|
||||
break;
|
||||
case ControlType::audio_capture:
|
||||
j["audio_capture"] = a.a;
|
||||
break;
|
||||
@@ -219,6 +267,18 @@ struct RemoteAction {
|
||||
out.ks.pressed_count = count;
|
||||
break;
|
||||
}
|
||||
case ControlType::cursor_state: {
|
||||
const auto& cursor_state_json = j.at("cursor_state");
|
||||
const int shape = cursor_state_json.at("shape").get<int>();
|
||||
if (shape < static_cast<int>(CursorShape::default_cursor) ||
|
||||
shape > static_cast<int>(CursorShape::nwse_resize)) {
|
||||
return false;
|
||||
}
|
||||
out.cs.seq = cursor_state_json.at("seq").get<uint32_t>();
|
||||
out.cs.visible = cursor_state_json.at("visible").get<bool>();
|
||||
out.cs.shape = static_cast<CursorShape>(shape);
|
||||
break;
|
||||
}
|
||||
case ControlType::audio_capture:
|
||||
out.a = j.at("audio_capture").get<bool>();
|
||||
break;
|
||||
|
||||
@@ -1796,6 +1796,7 @@ void GuiApplication::Tick() {
|
||||
SyncStreamKeyboardFocus();
|
||||
#endif
|
||||
devices_.UpdateInteractions();
|
||||
ShareLocalCursorState();
|
||||
|
||||
UpdateLocalization();
|
||||
SyncMainWindow();
|
||||
@@ -1805,6 +1806,58 @@ void GuiApplication::Tick() {
|
||||
SyncServerWindow();
|
||||
}
|
||||
|
||||
void GuiApplication::ShareLocalCursorState() {
|
||||
constexpr auto kCursorStateHeartbeatInterval = 500ms;
|
||||
|
||||
bool has_connected_controller = false;
|
||||
{
|
||||
std::shared_lock lock(connection_status_mutex_);
|
||||
has_connected_controller =
|
||||
std::any_of(connection_status_.begin(), connection_status_.end(),
|
||||
[](const auto& entry) {
|
||||
return entry.second == ConnectionStatus::Connected;
|
||||
});
|
||||
}
|
||||
if (!is_server_mode_ || !peer_ || !has_connected_controller) {
|
||||
has_shared_cursor_state_ = false;
|
||||
last_cursor_state_share_time_ = {};
|
||||
return;
|
||||
}
|
||||
|
||||
CursorState sampled{};
|
||||
if (!cursor_state_provider_.Sample(&sampled)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const bool changed =
|
||||
!has_shared_cursor_state_ ||
|
||||
sampled.visible != last_shared_cursor_state_.visible ||
|
||||
sampled.shape != last_shared_cursor_state_.shape;
|
||||
const bool heartbeat_due =
|
||||
last_cursor_state_share_time_.time_since_epoch().count() == 0 ||
|
||||
now - last_cursor_state_share_time_ >= kCursorStateHeartbeatInterval;
|
||||
if (!changed && !heartbeat_due) {
|
||||
return;
|
||||
}
|
||||
|
||||
sampled.seq = ++cursor_state_sequence_;
|
||||
RemoteAction action{};
|
||||
action.type = ControlType::cursor_state;
|
||||
action.cs = sampled;
|
||||
const std::string message = action.to_json();
|
||||
const int result = SendDataFrame(peer_, message.c_str(), message.size(),
|
||||
mouse_label_.c_str());
|
||||
if (result != 0) {
|
||||
LOG_WARN("Send cursor state failed, ret={}", result);
|
||||
return;
|
||||
}
|
||||
|
||||
last_shared_cursor_state_ = sampled;
|
||||
has_shared_cursor_state_ = true;
|
||||
last_cursor_state_share_time_ = now;
|
||||
}
|
||||
|
||||
void GuiApplication::HandlePasswordChangeResult() {
|
||||
bool succeeded = false;
|
||||
bool uncertain = false;
|
||||
@@ -2364,6 +2417,23 @@ void GuiApplication::SyncStreamWindow() {
|
||||
? localization::receiving_screen[localization_language_index_]
|
||||
: std::string{}));
|
||||
(*ui_->stream)->set_mouse_control_enabled(props->control_mouse_);
|
||||
int remote_cursor_shape =
|
||||
static_cast<int>(CursorShape::default_cursor);
|
||||
bool remote_cursor_active = false;
|
||||
{
|
||||
std::lock_guard lock(props->remote_cursor_state_mutex_);
|
||||
remote_cursor_active =
|
||||
status == ConnectionStatus::Connected && props->control_mouse_ &&
|
||||
props->remote_cursor_state_received_;
|
||||
if (remote_cursor_active) {
|
||||
remote_cursor_shape = static_cast<int>(
|
||||
props->remote_cursor_state_.visible
|
||||
? props->remote_cursor_state_.shape
|
||||
: CursorShape::none);
|
||||
}
|
||||
}
|
||||
(*ui_->stream)->set_remote_cursor_active(remote_cursor_active);
|
||||
(*ui_->stream)->set_remote_cursor_shape(remote_cursor_shape);
|
||||
(*ui_->stream)->set_audio_enabled(props->audio_capture_button_pressed_);
|
||||
#if defined(__APPLE__)
|
||||
fullscreen_button_pressed_ = IsStreamWindowFullscreen();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "runtime/cursor_state_provider.h"
|
||||
#include "runtime/gui_runtime.h"
|
||||
|
||||
namespace crossdesk {
|
||||
@@ -32,6 +33,7 @@ private:
|
||||
void BindStreamCallbacks();
|
||||
void BindServerCallbacks();
|
||||
void Tick();
|
||||
void ShareLocalCursorState();
|
||||
void HandlePasswordChangeResult();
|
||||
void HandleCredentialRecovery();
|
||||
void SyncMainWindow();
|
||||
@@ -70,6 +72,11 @@ private:
|
||||
bool OpenUrl(const std::string &url);
|
||||
|
||||
std::unique_ptr<SlintUi> ui_;
|
||||
CursorStateProvider cursor_state_provider_;
|
||||
CursorState last_shared_cursor_state_{};
|
||||
bool has_shared_cursor_state_ = false;
|
||||
uint32_t cursor_state_sequence_ = 0;
|
||||
std::chrono::steady_clock::time_point last_cursor_state_share_time_{};
|
||||
std::chrono::steady_clock::time_point next_video_frame_time_{};
|
||||
#if defined(__linux__) && !defined(__APPLE__)
|
||||
bool use_xwayland_gui_ = false;
|
||||
|
||||
@@ -338,6 +338,11 @@ void GuiRuntime::ResetRemoteSessionResources(
|
||||
props->render_rect_dirty_ = true;
|
||||
props->stream_cleanup_pending_ = false;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(props->remote_cursor_state_mutex_);
|
||||
props->remote_cursor_state_ = {};
|
||||
props->remote_cursor_state_received_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<GuiRuntime::RemoteSession> GuiRuntime::FindRemoteSession(
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
#include "runtime/cursor_state_provider.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace crossdesk {
|
||||
namespace {
|
||||
|
||||
bool IsSystemCursor(HCURSOR cursor, LPCWSTR resource) {
|
||||
return cursor != nullptr && cursor == LoadCursorW(nullptr, resource);
|
||||
}
|
||||
|
||||
CursorShape ShapeFromWindowsCursor(HCURSOR cursor) {
|
||||
if (IsSystemCursor(cursor, IDC_HELP)) return CursorShape::help;
|
||||
if (IsSystemCursor(cursor, IDC_HAND)) return CursorShape::pointer;
|
||||
if (IsSystemCursor(cursor, IDC_APPSTARTING)) return CursorShape::progress;
|
||||
if (IsSystemCursor(cursor, IDC_WAIT)) return CursorShape::wait;
|
||||
if (IsSystemCursor(cursor, IDC_CROSS)) return CursorShape::crosshair;
|
||||
if (IsSystemCursor(cursor, IDC_IBEAM)) return CursorShape::text;
|
||||
if (IsSystemCursor(cursor, IDC_NO)) return CursorShape::not_allowed;
|
||||
if (IsSystemCursor(cursor, IDC_SIZEALL)) return CursorShape::move;
|
||||
if (IsSystemCursor(cursor, IDC_SIZEWE)) return CursorShape::ew_resize;
|
||||
if (IsSystemCursor(cursor, IDC_SIZENS)) return CursorShape::ns_resize;
|
||||
if (IsSystemCursor(cursor, IDC_SIZENESW)) return CursorShape::nesw_resize;
|
||||
if (IsSystemCursor(cursor, IDC_SIZENWSE)) return CursorShape::nwse_resize;
|
||||
if (IsSystemCursor(cursor, IDC_UPARROW)) return CursorShape::n_resize;
|
||||
return CursorShape::default_cursor;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct CursorStateProvider::Impl {};
|
||||
|
||||
CursorStateProvider::CursorStateProvider() : impl_(std::make_unique<Impl>()) {}
|
||||
CursorStateProvider::~CursorStateProvider() = default;
|
||||
|
||||
bool CursorStateProvider::Sample(CursorState* state) {
|
||||
if (!state) return false;
|
||||
|
||||
CURSORINFO info{};
|
||||
info.cbSize = sizeof(info);
|
||||
if (!GetCursorInfo(&info)) return false;
|
||||
|
||||
state->seq = 0;
|
||||
state->visible = (info.flags & CURSOR_SHOWING) != 0;
|
||||
state->shape = state->visible ? ShapeFromWindowsCursor(info.hCursor)
|
||||
: CursorShape::none;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#elif defined(__linux__) && !defined(__APPLE__)
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/extensions/Xfixes.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
|
||||
namespace crossdesk {
|
||||
namespace {
|
||||
|
||||
std::string Lowercase(std::string value) {
|
||||
std::transform(value.begin(), value.end(), value.begin(),
|
||||
[](unsigned char ch) { return std::tolower(ch); });
|
||||
return value;
|
||||
}
|
||||
|
||||
bool Contains(const std::string& value, const char* token) {
|
||||
return value.find(token) != std::string::npos;
|
||||
}
|
||||
|
||||
CursorShape ShapeFromXCursorName(const std::string& raw_name) {
|
||||
const std::string name = Lowercase(raw_name);
|
||||
if (Contains(name, "left_ptr_watch") || Contains(name, "progress"))
|
||||
return CursorShape::progress;
|
||||
if (Contains(name, "watch") || Contains(name, "wait"))
|
||||
return CursorShape::wait;
|
||||
if (Contains(name, "question") || Contains(name, "help"))
|
||||
return CursorShape::help;
|
||||
if (Contains(name, "xterm") || Contains(name, "vertical-text") ||
|
||||
name == "text")
|
||||
return CursorShape::text;
|
||||
if (Contains(name, "crosshair") || name == "cross" || name == "tcross")
|
||||
return CursorShape::crosshair;
|
||||
if (Contains(name, "closedhand") || Contains(name, "grabbing"))
|
||||
return CursorShape::grabbing;
|
||||
if (Contains(name, "openhand") || Contains(name, "grab"))
|
||||
return CursorShape::grab;
|
||||
if (Contains(name, "dnd-link") || name == "alias")
|
||||
return CursorShape::alias;
|
||||
if (Contains(name, "hand") || Contains(name, "pointer") ||
|
||||
Contains(name, "link"))
|
||||
return CursorShape::pointer;
|
||||
if (Contains(name, "dnd-copy") || name == "copy")
|
||||
return CursorShape::copy;
|
||||
if (Contains(name, "no-drop")) return CursorShape::no_drop;
|
||||
if (Contains(name, "not-allowed") || Contains(name, "crossed_circle"))
|
||||
return CursorShape::not_allowed;
|
||||
if (name == "fleur" || Contains(name, "size_all") || name == "move")
|
||||
return CursorShape::move;
|
||||
if (Contains(name, "top_left_corner") ||
|
||||
Contains(name, "bottom_right_corner") ||
|
||||
Contains(name, "nwse-resize") || Contains(name, "size_fdiag"))
|
||||
return CursorShape::nwse_resize;
|
||||
if (Contains(name, "top_right_corner") ||
|
||||
Contains(name, "bottom_left_corner") ||
|
||||
Contains(name, "nesw-resize") || Contains(name, "size_bdiag"))
|
||||
return CursorShape::nesw_resize;
|
||||
if (Contains(name, "sb_h_double_arrow") || Contains(name, "ew-resize") ||
|
||||
Contains(name, "size_hor"))
|
||||
return CursorShape::ew_resize;
|
||||
if (Contains(name, "sb_v_double_arrow") || Contains(name, "ns-resize") ||
|
||||
Contains(name, "size_ver"))
|
||||
return CursorShape::ns_resize;
|
||||
if (Contains(name, "col-resize")) return CursorShape::col_resize;
|
||||
if (Contains(name, "row-resize")) return CursorShape::row_resize;
|
||||
if (Contains(name, "ne-resize")) return CursorShape::ne_resize;
|
||||
if (Contains(name, "nw-resize")) return CursorShape::nw_resize;
|
||||
if (Contains(name, "se-resize")) return CursorShape::se_resize;
|
||||
if (Contains(name, "sw-resize")) return CursorShape::sw_resize;
|
||||
if (Contains(name, "top_side") || name == "n-resize")
|
||||
return CursorShape::n_resize;
|
||||
if (Contains(name, "right_side") || name == "e-resize")
|
||||
return CursorShape::e_resize;
|
||||
if (Contains(name, "bottom_side") || name == "s-resize")
|
||||
return CursorShape::s_resize;
|
||||
if (Contains(name, "left_side") || name == "w-resize")
|
||||
return CursorShape::w_resize;
|
||||
return CursorShape::default_cursor;
|
||||
}
|
||||
|
||||
bool CursorHasVisiblePixel(const XFixesCursorImage& image) {
|
||||
const size_t pixel_count =
|
||||
static_cast<size_t>(image.width) * static_cast<size_t>(image.height);
|
||||
for (size_t index = 0; index < pixel_count; ++index) {
|
||||
if (((image.pixels[index] >> 24U) & 0xffU) != 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct CursorStateProvider::Impl {
|
||||
Display* display = XOpenDisplay(nullptr);
|
||||
|
||||
~Impl() {
|
||||
if (display) XCloseDisplay(display);
|
||||
}
|
||||
};
|
||||
|
||||
CursorStateProvider::CursorStateProvider() : impl_(std::make_unique<Impl>()) {}
|
||||
CursorStateProvider::~CursorStateProvider() = default;
|
||||
|
||||
bool CursorStateProvider::Sample(CursorState* state) {
|
||||
if (!state || !impl_ || !impl_->display) return false;
|
||||
|
||||
XFixesCursorImage* image = XFixesGetCursorImageAndName(impl_->display);
|
||||
if (!image) return false;
|
||||
|
||||
std::string name;
|
||||
if (image->atom != None) {
|
||||
char* atom_name = XGetAtomName(impl_->display, image->atom);
|
||||
if (atom_name) {
|
||||
name = atom_name;
|
||||
XFree(atom_name);
|
||||
}
|
||||
}
|
||||
|
||||
state->seq = 0;
|
||||
state->visible = CursorHasVisiblePixel(*image);
|
||||
state->shape = state->visible ? ShapeFromXCursorName(name)
|
||||
: CursorShape::none;
|
||||
XFree(image);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef CROSSDESK_GUI_CURSOR_STATE_PROVIDER_H_
|
||||
#define CROSSDESK_GUI_CURSOR_STATE_PROVIDER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "device_controller.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
// Samples the cursor that is currently displayed by the controlled desktop
|
||||
// and converts platform-specific cursor handles to protocol cursor shapes.
|
||||
class CursorStateProvider {
|
||||
public:
|
||||
CursorStateProvider();
|
||||
~CursorStateProvider();
|
||||
|
||||
CursorStateProvider(const CursorStateProvider&) = delete;
|
||||
CursorStateProvider& operator=(const CursorStateProvider&) = delete;
|
||||
|
||||
bool Sample(CursorState* state);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif // CROSSDESK_GUI_CURSOR_STATE_PROVIDER_H_
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "runtime/cursor_state_provider.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <CoreGraphics/CGRemoteOperation.h>
|
||||
|
||||
namespace crossdesk {
|
||||
namespace {
|
||||
|
||||
bool SameCursor(NSCursor* left, NSCursor* right) {
|
||||
if (left == right || [left isEqual:right]) return true;
|
||||
return left && right && NSEqualPoints(left.hotSpot, right.hotSpot) &&
|
||||
[left.image isEqual:right.image];
|
||||
}
|
||||
|
||||
CursorShape ShapeFromMacCursor(NSCursor* cursor) {
|
||||
if (SameCursor(cursor, NSCursor.pointingHandCursor))
|
||||
return CursorShape::pointer;
|
||||
if (SameCursor(cursor, NSCursor.crosshairCursor))
|
||||
return CursorShape::crosshair;
|
||||
if (SameCursor(cursor, NSCursor.IBeamCursor) ||
|
||||
SameCursor(cursor, NSCursor.IBeamCursorForVerticalLayout))
|
||||
return CursorShape::text;
|
||||
if (SameCursor(cursor, NSCursor.operationNotAllowedCursor))
|
||||
return CursorShape::not_allowed;
|
||||
if (SameCursor(cursor, NSCursor.dragLinkCursor)) return CursorShape::alias;
|
||||
if (SameCursor(cursor, NSCursor.dragCopyCursor)) return CursorShape::copy;
|
||||
if (SameCursor(cursor, NSCursor.openHandCursor)) return CursorShape::grab;
|
||||
if (SameCursor(cursor, NSCursor.closedHandCursor))
|
||||
return CursorShape::grabbing;
|
||||
if (SameCursor(cursor, NSCursor.resizeLeftRightCursor))
|
||||
return CursorShape::ew_resize;
|
||||
if (SameCursor(cursor, NSCursor.resizeUpDownCursor))
|
||||
return CursorShape::ns_resize;
|
||||
if (SameCursor(cursor, NSCursor.resizeUpCursor))
|
||||
return CursorShape::n_resize;
|
||||
if (SameCursor(cursor, NSCursor.resizeRightCursor))
|
||||
return CursorShape::e_resize;
|
||||
if (SameCursor(cursor, NSCursor.resizeDownCursor))
|
||||
return CursorShape::s_resize;
|
||||
if (SameCursor(cursor, NSCursor.resizeLeftCursor))
|
||||
return CursorShape::w_resize;
|
||||
return CursorShape::default_cursor;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct CursorStateProvider::Impl {};
|
||||
|
||||
CursorStateProvider::CursorStateProvider() : impl_(std::make_unique<Impl>()) {}
|
||||
CursorStateProvider::~CursorStateProvider() = default;
|
||||
|
||||
bool CursorStateProvider::Sample(CursorState* state) {
|
||||
if (!state) return false;
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
NSCursor* cursor = NSCursor.currentSystemCursor;
|
||||
const bool visible = CGCursorIsVisible();
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
state->seq = 0;
|
||||
state->visible = visible && cursor != nil;
|
||||
state->shape = state->visible ? ShapeFromMacCursor(cursor)
|
||||
: CursorShape::none;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
@@ -164,6 +164,31 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
||||
return;
|
||||
}
|
||||
|
||||
if (remote_action.type == ControlType::cursor_state) {
|
||||
std::shared_ptr<GuiRuntime::RemoteSession> props;
|
||||
{
|
||||
std::shared_lock lock(runtime->remote_sessions_mutex_);
|
||||
auto props_it = runtime->remote_sessions_.find(remote_id);
|
||||
if (props_it != runtime->remote_sessions_.end()) {
|
||||
props = props_it->second;
|
||||
}
|
||||
}
|
||||
if (!props) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock(props->remote_cursor_state_mutex_);
|
||||
const uint32_t previous_seq = props->remote_cursor_state_.seq;
|
||||
const bool is_newer =
|
||||
!props->remote_cursor_state_received_ ||
|
||||
static_cast<int32_t>(remote_action.cs.seq - previous_seq) > 0;
|
||||
if (is_newer) {
|
||||
props->remote_cursor_state_ = remote_action.cs;
|
||||
props->remote_cursor_state_received_ = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (remote_action.type == ControlType::host_infomation) {
|
||||
bool is_client_mode = false;
|
||||
std::shared_ptr<GuiRuntime::RemoteSession> props;
|
||||
|
||||
@@ -249,6 +249,11 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
||||
switch (status) {
|
||||
case ConnectionStatus::Connected: {
|
||||
runtime->ResetRemoteServiceStatus(*props);
|
||||
{
|
||||
std::lock_guard lock(props->remote_cursor_state_mutex_);
|
||||
props->remote_cursor_state_ = {};
|
||||
props->remote_cursor_state_received_ = false;
|
||||
}
|
||||
{
|
||||
RemoteAction remote_action;
|
||||
remote_action.i.display_num =
|
||||
@@ -313,6 +318,11 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
||||
props->connection_established_ = false;
|
||||
props->enable_mouse_control_ = false;
|
||||
runtime->ResetRemoteServiceStatus(*props);
|
||||
{
|
||||
std::lock_guard lock(props->remote_cursor_state_mutex_);
|
||||
props->remote_cursor_state_ = {};
|
||||
props->remote_cursor_state_received_ = false;
|
||||
}
|
||||
|
||||
std::shared_ptr<std::vector<unsigned char>> native_snapshot;
|
||||
int native_snapshot_width = 0;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "device_controller.h"
|
||||
#include "display_info.h"
|
||||
#include "minirtc.h"
|
||||
|
||||
@@ -148,6 +149,12 @@ struct RemoteSession {
|
||||
bool remote_service_available_ = false;
|
||||
std::string remote_interactive_stage_;
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
// 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.
|
||||
std::mutex remote_cursor_state_mutex_;
|
||||
CursorState remote_cursor_state_{};
|
||||
bool remote_cursor_state_received_ = false;
|
||||
// Shared by minirtc callbacks, Slint rendering and SDL audio callbacks.
|
||||
std::atomic<ConnectionStatus> connection_status_ = ConnectionStatus::Closed;
|
||||
TraversalMode traversal_mode_ = TraversalMode::UnknownMode;
|
||||
|
||||
@@ -133,6 +133,9 @@ export component StreamWindow inherits Window {
|
||||
in property <[string]> displays;
|
||||
in-out property <int> selected-display: 0;
|
||||
in property <bool> mouse-control-enabled: true;
|
||||
// Numeric values mirror crossdesk::CursorShape and Slint's MouseCursor.
|
||||
in property <bool> remote-cursor-active: false;
|
||||
in property <int> remote-cursor-shape: 0;
|
||||
in property <bool> audio-enabled: true;
|
||||
in property <bool> srtp-enabled: false;
|
||||
in property <bool> fullscreen-enabled: false;
|
||||
@@ -551,6 +554,36 @@ export component StreamWindow inherits Window {
|
||||
accept
|
||||
}
|
||||
pointer := TouchArea {
|
||||
mouse-cursor: !root.remote-cursor-active ? MouseCursor.default
|
||||
: root.remote-cursor-shape == 1 ? MouseCursor.none
|
||||
: root.remote-cursor-shape == 2 ? MouseCursor.help
|
||||
: root.remote-cursor-shape == 3 ? MouseCursor.pointer
|
||||
: root.remote-cursor-shape == 4 ? MouseCursor.progress
|
||||
: root.remote-cursor-shape == 5 ? MouseCursor.wait
|
||||
: root.remote-cursor-shape == 6 ? MouseCursor.crosshair
|
||||
: root.remote-cursor-shape == 7 ? MouseCursor.text
|
||||
: root.remote-cursor-shape == 8 ? MouseCursor.alias
|
||||
: root.remote-cursor-shape == 9 ? MouseCursor.copy
|
||||
: root.remote-cursor-shape == 10 ? MouseCursor.move
|
||||
: root.remote-cursor-shape == 11 ? MouseCursor.no-drop
|
||||
: root.remote-cursor-shape == 12 ? MouseCursor.not-allowed
|
||||
: root.remote-cursor-shape == 13 ? MouseCursor.grab
|
||||
: root.remote-cursor-shape == 14 ? MouseCursor.grabbing
|
||||
: root.remote-cursor-shape == 15 ? MouseCursor.col-resize
|
||||
: root.remote-cursor-shape == 16 ? MouseCursor.row-resize
|
||||
: root.remote-cursor-shape == 17 ? MouseCursor.n-resize
|
||||
: root.remote-cursor-shape == 18 ? MouseCursor.e-resize
|
||||
: root.remote-cursor-shape == 19 ? MouseCursor.s-resize
|
||||
: root.remote-cursor-shape == 20 ? MouseCursor.w-resize
|
||||
: root.remote-cursor-shape == 21 ? MouseCursor.ne-resize
|
||||
: root.remote-cursor-shape == 22 ? MouseCursor.nw-resize
|
||||
: root.remote-cursor-shape == 23 ? MouseCursor.se-resize
|
||||
: root.remote-cursor-shape == 24 ? MouseCursor.sw-resize
|
||||
: root.remote-cursor-shape == 25 ? MouseCursor.ew-resize
|
||||
: root.remote-cursor-shape == 26 ? MouseCursor.ns-resize
|
||||
: root.remote-cursor-shape == 27 ? MouseCursor.nesw-resize
|
||||
: root.remote-cursor-shape == 28 ? MouseCursor.nwse-resize
|
||||
: MouseCursor.default;
|
||||
pointer-event(event) => {
|
||||
let over-control = self.mouse-x >= control.x
|
||||
&& self.mouse-x <= control.x + control.width
|
||||
|
||||
+2
-1
@@ -84,7 +84,8 @@ function setup_platform_settings()
|
||||
add_ldflags("-Wl,-ld_classic")
|
||||
end
|
||||
add_cxflags("-Wno-unused-variable")
|
||||
add_frameworks("Cocoa", "Metal", "QuartzCore", "IOSurface",
|
||||
add_frameworks("Cocoa", "CoreGraphics", "Metal", "QuartzCore",
|
||||
"IOSurface",
|
||||
"ScreenCaptureKit", "AVFoundation", "CoreMedia", "CoreVideo",
|
||||
"CoreAudio", "AudioToolbox")
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user