[feat] synchronize remote cursor state

This commit is contained in:
dijunkun
2026-08-25 22:25:18 +08:00
parent b854512c24
commit bd73ef55f1
12 changed files with 503 additions and 1 deletions
+60
View File
@@ -28,6 +28,7 @@ typedef enum {
service_status = 5, service_status = 5,
service_command = 6, service_command = 6,
keyboard_state = 7, keyboard_state = 7,
cursor_state = 8,
} ControlType; } ControlType;
typedef enum { typedef enum {
move = 0, move = 0,
@@ -70,6 +71,47 @@ typedef struct {
KeyboardStateKey pressed_keys[kMaxKeyboardStateKeys]; KeyboardStateKey pressed_keys[kMaxKeyboardStateKeys];
} KeyboardState; } 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 { typedef struct {
char host_name[64]; char host_name[64];
size_t host_name_size; size_t host_name_size;
@@ -96,6 +138,7 @@ struct RemoteAction {
Mouse m; Mouse m;
Key k; Key k;
KeyboardState ks; KeyboardState ks;
CursorState cs;
HostInfo i; HostInfo i;
bool a; bool a;
int d; int d;
@@ -141,6 +184,11 @@ struct RemoteAction {
j["keyboard_state"] = {{"seq", a.ks.seq}, {"pressed_keys", keys}}; j["keyboard_state"] = {{"seq", a.ks.seq}, {"pressed_keys", keys}};
break; 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: case ControlType::audio_capture:
j["audio_capture"] = a.a; j["audio_capture"] = a.a;
break; break;
@@ -219,6 +267,18 @@ struct RemoteAction {
out.ks.pressed_count = count; out.ks.pressed_count = count;
break; 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: case ControlType::audio_capture:
out.a = j.at("audio_capture").get<bool>(); out.a = j.at("audio_capture").get<bool>();
break; break;
+70
View File
@@ -1796,6 +1796,7 @@ void GuiApplication::Tick() {
SyncStreamKeyboardFocus(); SyncStreamKeyboardFocus();
#endif #endif
devices_.UpdateInteractions(); devices_.UpdateInteractions();
ShareLocalCursorState();
UpdateLocalization(); UpdateLocalization();
SyncMainWindow(); SyncMainWindow();
@@ -1805,6 +1806,58 @@ void GuiApplication::Tick() {
SyncServerWindow(); 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() { void GuiApplication::HandlePasswordChangeResult() {
bool succeeded = false; bool succeeded = false;
bool uncertain = false; bool uncertain = false;
@@ -2364,6 +2417,23 @@ void GuiApplication::SyncStreamWindow() {
? localization::receiving_screen[localization_language_index_] ? localization::receiving_screen[localization_language_index_]
: std::string{})); : std::string{}));
(*ui_->stream)->set_mouse_control_enabled(props->control_mouse_); (*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_); (*ui_->stream)->set_audio_enabled(props->audio_capture_button_pressed_);
#if defined(__APPLE__) #if defined(__APPLE__)
fullscreen_button_pressed_ = IsStreamWindowFullscreen(); fullscreen_button_pressed_ = IsStreamWindowFullscreen();
+7
View File
@@ -5,6 +5,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include "runtime/cursor_state_provider.h"
#include "runtime/gui_runtime.h" #include "runtime/gui_runtime.h"
namespace crossdesk { namespace crossdesk {
@@ -32,6 +33,7 @@ private:
void BindStreamCallbacks(); void BindStreamCallbacks();
void BindServerCallbacks(); void BindServerCallbacks();
void Tick(); void Tick();
void ShareLocalCursorState();
void HandlePasswordChangeResult(); void HandlePasswordChangeResult();
void HandleCredentialRecovery(); void HandleCredentialRecovery();
void SyncMainWindow(); void SyncMainWindow();
@@ -70,6 +72,11 @@ private:
bool OpenUrl(const std::string &url); bool OpenUrl(const std::string &url);
std::unique_ptr<SlintUi> ui_; 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_{}; std::chrono::steady_clock::time_point next_video_frame_time_{};
#if defined(__linux__) && !defined(__APPLE__) #if defined(__linux__) && !defined(__APPLE__)
bool use_xwayland_gui_ = false; bool use_xwayland_gui_ = false;
+5
View File
@@ -338,6 +338,11 @@ void GuiRuntime::ResetRemoteSessionResources(
props->render_rect_dirty_ = true; props->render_rect_dirty_ = true;
props->stream_cleanup_pending_ = false; 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( std::shared_ptr<GuiRuntime::RemoteSession> GuiRuntime::FindRemoteSession(
+183
View File
@@ -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
+29
View File
@@ -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
+25
View File
@@ -164,6 +164,31 @@ void PeerEventHandler::OnReceiveDataBuffer(
return; 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) { if (remote_action.type == ControlType::host_infomation) {
bool is_client_mode = false; bool is_client_mode = false;
std::shared_ptr<GuiRuntime::RemoteSession> props; std::shared_ptr<GuiRuntime::RemoteSession> props;
+10
View File
@@ -249,6 +249,11 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
switch (status) { switch (status) {
case ConnectionStatus::Connected: { case ConnectionStatus::Connected: {
runtime->ResetRemoteServiceStatus(*props); 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; RemoteAction remote_action;
remote_action.i.display_num = remote_action.i.display_num =
@@ -313,6 +318,11 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
props->connection_established_ = false; props->connection_established_ = false;
props->enable_mouse_control_ = false; props->enable_mouse_control_ = false;
runtime->ResetRemoteServiceStatus(*props); 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; std::shared_ptr<std::vector<unsigned char>> native_snapshot;
int native_snapshot_width = 0; int native_snapshot_width = 0;
+7
View File
@@ -13,6 +13,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "device_controller.h"
#include "display_info.h" #include "display_info.h"
#include "minirtc.h" #include "minirtc.h"
@@ -148,6 +149,12 @@ struct RemoteSession {
bool remote_service_available_ = false; bool remote_service_available_ = false;
std::string remote_interactive_stage_; std::string remote_interactive_stage_;
std::vector<DisplayInfo> display_info_list_; 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. // Shared by minirtc callbacks, Slint rendering and SDL audio callbacks.
std::atomic<ConnectionStatus> connection_status_ = ConnectionStatus::Closed; std::atomic<ConnectionStatus> connection_status_ = ConnectionStatus::Closed;
TraversalMode traversal_mode_ = TraversalMode::UnknownMode; TraversalMode traversal_mode_ = TraversalMode::UnknownMode;
+33
View File
@@ -133,6 +133,9 @@ export component StreamWindow inherits Window {
in property <[string]> displays; in property <[string]> displays;
in-out property <int> selected-display: 0; in-out property <int> selected-display: 0;
in property <bool> mouse-control-enabled: true; 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> audio-enabled: true;
in property <bool> srtp-enabled: false; in property <bool> srtp-enabled: false;
in property <bool> fullscreen-enabled: false; in property <bool> fullscreen-enabled: false;
@@ -551,6 +554,36 @@ export component StreamWindow inherits Window {
accept accept
} }
pointer := TouchArea { 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) => { pointer-event(event) => {
let over-control = self.mouse-x >= control.x let over-control = self.mouse-x >= control.x
&& self.mouse-x <= control.x + control.width && self.mouse-x <= control.x + control.width
+2 -1
View File
@@ -84,7 +84,8 @@ function setup_platform_settings()
add_ldflags("-Wl,-ld_classic") add_ldflags("-Wl,-ld_classic")
end end
add_cxflags("-Wno-unused-variable") add_cxflags("-Wno-unused-variable")
add_frameworks("Cocoa", "Metal", "QuartzCore", "IOSurface", add_frameworks("Cocoa", "CoreGraphics", "Metal", "QuartzCore",
"IOSurface",
"ScreenCaptureKit", "AVFoundation", "CoreMedia", "CoreVideo", "ScreenCaptureKit", "AVFoundation", "CoreMedia", "CoreVideo",
"CoreAudio", "AudioToolbox") "CoreAudio", "AudioToolbox")
end end