mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-08-31 13:32:10 +08:00
[feat] add native iOS client support
This commit is contained in:
@@ -7,20 +7,16 @@
|
||||
#ifndef _DEVICE_CONTROLLER_H_
|
||||
#define _DEVICE_CONTROLLER_H_
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
#include "display_info.h"
|
||||
#include "remote_cursor_shape.h"
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
typedef enum {
|
||||
enum ControlType {
|
||||
invalid = -1,
|
||||
mouse = 0,
|
||||
keyboard = 1,
|
||||
audio_capture = 2,
|
||||
@@ -30,8 +26,9 @@ typedef enum {
|
||||
service_command = 6,
|
||||
keyboard_state = 7,
|
||||
cursor_state = 8,
|
||||
} ControlType;
|
||||
typedef enum {
|
||||
};
|
||||
|
||||
enum MouseFlag {
|
||||
move = 0,
|
||||
left_down,
|
||||
left_up,
|
||||
@@ -40,66 +37,80 @@ typedef enum {
|
||||
middle_down,
|
||||
middle_up,
|
||||
wheel_vertical,
|
||||
wheel_horizontal
|
||||
} MouseFlag;
|
||||
typedef enum { key_down = 0, key_up } KeyFlag;
|
||||
typedef enum { send_sas = 0, lock_workstation } ServiceCommandFlag;
|
||||
typedef struct {
|
||||
wheel_horizontal,
|
||||
};
|
||||
|
||||
enum KeyFlag { key_down = 0, key_up };
|
||||
enum ServiceCommandFlag { send_sas = 0, lock_workstation };
|
||||
|
||||
struct Mouse {
|
||||
float x;
|
||||
float y;
|
||||
int s;
|
||||
MouseFlag flag;
|
||||
} Mouse;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
size_t key_value;
|
||||
struct Key {
|
||||
std::size_t key_value;
|
||||
uint32_t scan_code;
|
||||
bool extended;
|
||||
KeyFlag flag;
|
||||
} Key;
|
||||
};
|
||||
|
||||
inline constexpr size_t kMaxKeyboardStateKeys = 32;
|
||||
inline constexpr std::size_t kMaxKeyboardStateKeys = 32;
|
||||
|
||||
typedef struct {
|
||||
size_t key_value;
|
||||
struct KeyboardStateKey {
|
||||
std::size_t key_value;
|
||||
uint32_t scan_code;
|
||||
bool extended;
|
||||
} KeyboardStateKey;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
struct KeyboardState {
|
||||
uint32_t seq;
|
||||
size_t pressed_count;
|
||||
std::size_t pressed_count;
|
||||
KeyboardStateKey pressed_keys[kMaxKeyboardStateKeys];
|
||||
} KeyboardState;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
struct CursorState {
|
||||
uint32_t seq;
|
||||
bool visible;
|
||||
RemoteCursorShape shape;
|
||||
} CursorState;
|
||||
bool position_valid;
|
||||
float x;
|
||||
float y;
|
||||
// Normalized displacement from the input hotspot to the visible cursor
|
||||
// anchor. This is presentation metadata and must not affect input mapping.
|
||||
float visual_offset_x;
|
||||
float visual_offset_y;
|
||||
int display_id;
|
||||
// Whether receivers should apply the position fields in this message.
|
||||
// Shape-only updates set this to false so cursor appearance can remain
|
||||
// responsive while position feedback to the input source is suppressed.
|
||||
bool position_update;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
struct HostInfo {
|
||||
char host_name[64];
|
||||
size_t host_name_size;
|
||||
std::size_t host_name_size;
|
||||
char** display_list;
|
||||
size_t display_num;
|
||||
std::size_t display_num;
|
||||
int* left;
|
||||
int* top;
|
||||
int* right;
|
||||
int* bottom;
|
||||
} HostInfo;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
struct ServiceStatus {
|
||||
bool available;
|
||||
char interactive_stage[32];
|
||||
} ServiceStatus;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
struct ServiceCommand {
|
||||
ServiceCommandFlag flag;
|
||||
} ServiceCommand;
|
||||
};
|
||||
|
||||
struct RemoteAction {
|
||||
ControlType type;
|
||||
ControlType type = ControlType::invalid;
|
||||
union {
|
||||
Mouse m;
|
||||
Key k;
|
||||
@@ -112,210 +123,25 @@ struct RemoteAction {
|
||||
ServiceCommand c;
|
||||
};
|
||||
|
||||
// parse
|
||||
std::string to_json() const { return ToJson(*this); }
|
||||
std::string to_json() const;
|
||||
bool from_json(const std::string& json_string);
|
||||
|
||||
bool from_json(const std::string& json_str) {
|
||||
RemoteAction temp;
|
||||
if (!FromJson(json_str, temp)) return false;
|
||||
*this = temp;
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string ToJson(const RemoteAction& a) {
|
||||
json j;
|
||||
j["type"] = a.type;
|
||||
switch (a.type) {
|
||||
case ControlType::mouse:
|
||||
j["mouse"] = {
|
||||
{"x", a.m.x}, {"y", a.m.y}, {"s", a.m.s}, {"flag", a.m.flag}};
|
||||
break;
|
||||
case ControlType::keyboard:
|
||||
j["keyboard"] = {{"key_value", a.k.key_value},
|
||||
{"scan_code", a.k.scan_code},
|
||||
{"extended", a.k.extended},
|
||||
{"flag", a.k.flag}};
|
||||
break;
|
||||
case ControlType::keyboard_state: {
|
||||
json keys = json::array();
|
||||
const size_t pressed_count =
|
||||
a.ks.pressed_count < kMaxKeyboardStateKeys
|
||||
? a.ks.pressed_count
|
||||
: kMaxKeyboardStateKeys;
|
||||
for (size_t idx = 0; idx < pressed_count; ++idx) {
|
||||
keys.push_back({{"key_value", a.ks.pressed_keys[idx].key_value},
|
||||
{"scan_code", a.ks.pressed_keys[idx].scan_code},
|
||||
{"extended", a.ks.pressed_keys[idx].extended}});
|
||||
}
|
||||
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;
|
||||
case ControlType::display_id:
|
||||
j["display_id"] = a.d;
|
||||
break;
|
||||
case ControlType::service_status:
|
||||
j["service_status"] = {{"available", a.ss.available},
|
||||
{"interactive_stage", a.ss.interactive_stage}};
|
||||
break;
|
||||
case ControlType::service_command:
|
||||
j["service_command"] = {{"flag", a.c.flag}};
|
||||
break;
|
||||
case ControlType::host_infomation: {
|
||||
json displays = json::array();
|
||||
for (size_t idx = 0; idx < a.i.display_num; idx++) {
|
||||
displays.push_back(
|
||||
{{"name", a.i.display_list ? a.i.display_list[idx] : ""},
|
||||
{"left", a.i.left ? a.i.left[idx] : 0},
|
||||
{"top", a.i.top ? a.i.top[idx] : 0},
|
||||
{"right", a.i.right ? a.i.right[idx] : 0},
|
||||
{"bottom", a.i.bottom ? a.i.bottom[idx] : 0}});
|
||||
}
|
||||
|
||||
j["host_info"] = {{"host_name", a.i.host_name},
|
||||
{"display_num", a.i.display_num},
|
||||
{"displays", displays}};
|
||||
break;
|
||||
}
|
||||
}
|
||||
return j.dump();
|
||||
}
|
||||
|
||||
static bool FromJson(const std::string& json_str, RemoteAction& out) {
|
||||
try {
|
||||
json j = json::parse(json_str);
|
||||
out.type = (ControlType)j.at("type").get<int>();
|
||||
switch (out.type) {
|
||||
case ControlType::mouse:
|
||||
out.m.x = j.at("mouse").at("x").get<float>();
|
||||
out.m.y = j.at("mouse").at("y").get<float>();
|
||||
out.m.s = j.at("mouse").at("s").get<int>();
|
||||
out.m.flag = (MouseFlag)j.at("mouse").at("flag").get<int>();
|
||||
break;
|
||||
case ControlType::keyboard:
|
||||
out.k.key_value = j.at("keyboard").at("key_value").get<size_t>();
|
||||
out.k.scan_code =
|
||||
j.at("keyboard").value("scan_code", static_cast<uint32_t>(0));
|
||||
out.k.extended = j.at("keyboard").value("extended", false);
|
||||
out.k.flag = (KeyFlag)j.at("keyboard").at("flag").get<int>();
|
||||
break;
|
||||
case ControlType::keyboard_state: {
|
||||
const auto& keyboard_state_json = j.at("keyboard_state");
|
||||
out.ks.seq = keyboard_state_json.value("seq", 0u);
|
||||
out.ks.pressed_count = 0;
|
||||
|
||||
const auto keys_json =
|
||||
keyboard_state_json.value("pressed_keys", json::array());
|
||||
if (!keys_json.is_array()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const size_t count =
|
||||
keys_json.size() < kMaxKeyboardStateKeys
|
||||
? keys_json.size()
|
||||
: kMaxKeyboardStateKeys;
|
||||
for (size_t idx = 0; idx < count; ++idx) {
|
||||
const auto& key_json = keys_json[idx];
|
||||
out.ks.pressed_keys[idx].key_value =
|
||||
key_json.at("key_value").get<size_t>();
|
||||
out.ks.pressed_keys[idx].scan_code =
|
||||
key_json.value("scan_code", static_cast<uint32_t>(0));
|
||||
out.ks.pressed_keys[idx].extended =
|
||||
key_json.value("extended", false);
|
||||
}
|
||||
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>(RemoteCursorShape::default_cursor) ||
|
||||
shape > static_cast<int>(RemoteCursorShape::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<RemoteCursorShape>(shape);
|
||||
break;
|
||||
}
|
||||
case ControlType::audio_capture:
|
||||
out.a = j.at("audio_capture").get<bool>();
|
||||
break;
|
||||
case ControlType::display_id:
|
||||
out.d = j.at("display_id").get<int>();
|
||||
break;
|
||||
case ControlType::service_status: {
|
||||
const auto& service_status_json = j.at("service_status");
|
||||
out.ss.available = service_status_json.value("available", false);
|
||||
std::string interactive_stage =
|
||||
service_status_json.value("interactive_stage", std::string());
|
||||
std::strncpy(out.ss.interactive_stage, interactive_stage.c_str(),
|
||||
sizeof(out.ss.interactive_stage) - 1);
|
||||
out.ss.interactive_stage[sizeof(out.ss.interactive_stage) - 1] = '\0';
|
||||
break;
|
||||
}
|
||||
case ControlType::service_command:
|
||||
out.c.flag = static_cast<ServiceCommandFlag>(
|
||||
j.at("service_command").at("flag").get<int>());
|
||||
break;
|
||||
case ControlType::host_infomation: {
|
||||
std::string host_name =
|
||||
j.at("host_info").at("host_name").get<std::string>();
|
||||
strncpy(out.i.host_name, host_name.c_str(), sizeof(out.i.host_name));
|
||||
out.i.host_name[sizeof(out.i.host_name) - 1] = '\0';
|
||||
out.i.host_name_size = host_name.size();
|
||||
|
||||
out.i.display_num = j.at("host_info").at("display_num").get<size_t>();
|
||||
auto displays = j.at("host_info").at("displays");
|
||||
|
||||
out.i.display_list =
|
||||
(char**)malloc(out.i.display_num * sizeof(char*));
|
||||
out.i.left = (int*)malloc(out.i.display_num * sizeof(int));
|
||||
out.i.top = (int*)malloc(out.i.display_num * sizeof(int));
|
||||
out.i.right = (int*)malloc(out.i.display_num * sizeof(int));
|
||||
out.i.bottom = (int*)malloc(out.i.display_num * sizeof(int));
|
||||
|
||||
for (size_t idx = 0; idx < out.i.display_num; idx++) {
|
||||
std::string name = displays[idx].at("name").get<std::string>();
|
||||
out.i.display_list[idx] = (char*)malloc(name.size() + 1);
|
||||
strcpy(out.i.display_list[idx], name.c_str());
|
||||
out.i.left[idx] = displays[idx].at("left").get<int>();
|
||||
out.i.top[idx] = displays[idx].at("top").get<int>();
|
||||
out.i.right[idx] = displays[idx].at("right").get<int>();
|
||||
out.i.bottom[idx] = displays[idx].at("bottom").get<int>();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
printf("Failed to parse RemoteAction JSON: %s\n", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
static std::string ToJson(const RemoteAction& action);
|
||||
static bool FromJson(const std::string& json_string, RemoteAction& output);
|
||||
};
|
||||
|
||||
// Releases the dynamically allocated display arrays held by host information.
|
||||
// Other RemoteAction variants do not own memory and are left unchanged.
|
||||
void FreeRemoteAction(RemoteAction& action);
|
||||
|
||||
// int key_code, bool is_down, uint32_t scan_code, bool extended
|
||||
typedef void (*OnKeyAction)(int, bool, uint32_t, bool, void*);
|
||||
using OnKeyAction = void (*)(int, bool, uint32_t, bool, void*);
|
||||
|
||||
class DeviceController {
|
||||
public:
|
||||
virtual ~DeviceController() {}
|
||||
|
||||
public:
|
||||
// virtual int Init(int screen_width, int screen_height);
|
||||
// virtual int Destroy();
|
||||
// virtual int SendMouseCommand(RemoteAction remote_action);
|
||||
|
||||
// virtual int Hook();
|
||||
// virtual int Unhook();
|
||||
virtual ~DeviceController() = default;
|
||||
};
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "device_controller.h"
|
||||
#include "display_info.h"
|
||||
|
||||
struct DBusConnection;
|
||||
struct DBusMessageIter;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "rd_log.h"
|
||||
@@ -44,6 +45,13 @@ int MouseController::Init(std::vector<DisplayInfo> display_info_list) {
|
||||
|
||||
int MouseController::Destroy() { return 0; }
|
||||
|
||||
void MouseController::UpdateDisplayInfoList(
|
||||
const std::vector<DisplayInfo>& display_info_list) {
|
||||
if (!display_info_list.empty()) {
|
||||
display_info_list_ = display_info_list;
|
||||
}
|
||||
}
|
||||
|
||||
int MouseController::BeginClick(ClickTracker& tracker, int x, int y) {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const bool continues_previous_click =
|
||||
@@ -94,10 +102,26 @@ int MouseController::SendMouseCommand(RemoteAction remote_action,
|
||||
return -1;
|
||||
}
|
||||
|
||||
const float normalized_x = std::clamp(remote_action.m.x, 0.0f, 1.0f);
|
||||
const float normalized_y = std::clamp(remote_action.m.y, 0.0f, 1.0f);
|
||||
int mouse_pos_x = normalized_x * display_info.width + display_info.left;
|
||||
int mouse_pos_y = normalized_y * display_info.height + display_info.top;
|
||||
const double normalized_x =
|
||||
std::clamp(static_cast<double>(remote_action.m.x), 0.0, 1.0);
|
||||
const double normalized_y =
|
||||
std::clamp(static_cast<double>(remote_action.m.y), 0.0, 1.0);
|
||||
// Keep the coordinate continuous until it reaches Core Graphics. This
|
||||
// avoids turning a one-pixel rounding error into a visible offset when the
|
||||
// client zooms the remote image. The upper bound remains just inside the
|
||||
// display because CG display rectangles use an exclusive right/bottom edge.
|
||||
const double max_x = std::nextafter(static_cast<double>(display_info.right),
|
||||
static_cast<double>(display_info.left));
|
||||
const double max_y = std::nextafter(static_cast<double>(display_info.bottom),
|
||||
static_cast<double>(display_info.top));
|
||||
const double mouse_pos_x = std::clamp(
|
||||
display_info.left + normalized_x * display_info.width,
|
||||
static_cast<double>(display_info.left), max_x);
|
||||
const double mouse_pos_y = std::clamp(
|
||||
display_info.top + normalized_y * display_info.height,
|
||||
static_cast<double>(display_info.top), max_y);
|
||||
const int tracked_mouse_pos_x = static_cast<int>(std::lround(mouse_pos_x));
|
||||
const int tracked_mouse_pos_y = static_cast<int>(std::lround(mouse_pos_y));
|
||||
|
||||
CGEventRef mouse_event = nullptr;
|
||||
CGEventType mouse_type;
|
||||
@@ -109,7 +133,8 @@ int MouseController::SendMouseCommand(RemoteAction remote_action,
|
||||
case MouseFlag::left_down:
|
||||
mouse_type = kCGEventLeftMouseDown;
|
||||
left_dragging_ = true;
|
||||
click_state = BeginClick(left_click_tracker_, mouse_pos_x, mouse_pos_y);
|
||||
click_state = BeginClick(left_click_tracker_, tracked_mouse_pos_x,
|
||||
tracked_mouse_pos_y);
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
|
||||
kCGMouseButtonLeft);
|
||||
SetClickState(mouse_event, click_state);
|
||||
@@ -117,7 +142,8 @@ int MouseController::SendMouseCommand(RemoteAction remote_action,
|
||||
case MouseFlag::left_up:
|
||||
mouse_type = kCGEventLeftMouseUp;
|
||||
left_dragging_ = false;
|
||||
click_state = EndClick(left_click_tracker_, mouse_pos_x, mouse_pos_y);
|
||||
click_state = EndClick(left_click_tracker_, tracked_mouse_pos_x,
|
||||
tracked_mouse_pos_y);
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
|
||||
kCGMouseButtonLeft);
|
||||
SetClickState(mouse_event, click_state);
|
||||
@@ -125,7 +151,8 @@ int MouseController::SendMouseCommand(RemoteAction remote_action,
|
||||
case MouseFlag::right_down:
|
||||
mouse_type = kCGEventRightMouseDown;
|
||||
right_dragging_ = true;
|
||||
click_state = BeginClick(right_click_tracker_, mouse_pos_x, mouse_pos_y);
|
||||
click_state = BeginClick(right_click_tracker_, tracked_mouse_pos_x,
|
||||
tracked_mouse_pos_y);
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
|
||||
kCGMouseButtonRight);
|
||||
SetClickState(mouse_event, click_state);
|
||||
@@ -133,21 +160,24 @@ int MouseController::SendMouseCommand(RemoteAction remote_action,
|
||||
case MouseFlag::right_up:
|
||||
mouse_type = kCGEventRightMouseUp;
|
||||
right_dragging_ = false;
|
||||
click_state = EndClick(right_click_tracker_, mouse_pos_x, mouse_pos_y);
|
||||
click_state = EndClick(right_click_tracker_, tracked_mouse_pos_x,
|
||||
tracked_mouse_pos_y);
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
|
||||
kCGMouseButtonRight);
|
||||
SetClickState(mouse_event, click_state);
|
||||
break;
|
||||
case MouseFlag::middle_down:
|
||||
mouse_type = kCGEventOtherMouseDown;
|
||||
click_state = BeginClick(middle_click_tracker_, mouse_pos_x, mouse_pos_y);
|
||||
click_state = BeginClick(middle_click_tracker_, tracked_mouse_pos_x,
|
||||
tracked_mouse_pos_y);
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
|
||||
kCGMouseButtonCenter);
|
||||
SetClickState(mouse_event, click_state);
|
||||
break;
|
||||
case MouseFlag::middle_up:
|
||||
mouse_type = kCGEventOtherMouseUp;
|
||||
click_state = EndClick(middle_click_tracker_, mouse_pos_x, mouse_pos_y);
|
||||
click_state = EndClick(middle_click_tracker_, tracked_mouse_pos_x,
|
||||
tracked_mouse_pos_y);
|
||||
mouse_event = CGEventCreateMouseEvent(NULL, mouse_type, mouse_point,
|
||||
kCGMouseButtonCenter);
|
||||
SetClickState(mouse_event, click_state);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "device_controller.h"
|
||||
#include "display_info.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
@@ -23,6 +24,8 @@ class MouseController : public DeviceController {
|
||||
virtual int Init(std::vector<DisplayInfo> display_info_list);
|
||||
virtual int Destroy();
|
||||
virtual int SendMouseCommand(RemoteAction remote_action, int display_index);
|
||||
void UpdateDisplayInfoList(
|
||||
const std::vector<DisplayInfo>& display_info_list);
|
||||
|
||||
private:
|
||||
struct ClickTracker {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "device_controller.h"
|
||||
#include "display_info.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
@@ -27,4 +28,4 @@ class MouseController : public DeviceController {
|
||||
std::vector<DisplayInfo> display_info_list_;
|
||||
};
|
||||
} // namespace crossdesk
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
#include "device_controller.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace crossdesk {
|
||||
namespace {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
void ResetHostInfo(HostInfo& info) {
|
||||
info.host_name[0] = '\0';
|
||||
info.host_name_size = 0;
|
||||
info.display_list = nullptr;
|
||||
info.display_num = 0;
|
||||
info.left = nullptr;
|
||||
info.top = nullptr;
|
||||
info.right = nullptr;
|
||||
info.bottom = nullptr;
|
||||
}
|
||||
|
||||
bool AllocateHostDisplays(HostInfo& info, std::size_t count) {
|
||||
if (count == 0) return true;
|
||||
|
||||
info.display_list = static_cast<char**>(std::calloc(count, sizeof(char*)));
|
||||
info.left = static_cast<int*>(std::malloc(count * sizeof(int)));
|
||||
info.top = static_cast<int*>(std::malloc(count * sizeof(int)));
|
||||
info.right = static_cast<int*>(std::malloc(count * sizeof(int)));
|
||||
info.bottom = static_cast<int*>(std::malloc(count * sizeof(int)));
|
||||
return info.display_list && info.left && info.top && info.right &&
|
||||
info.bottom;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string RemoteAction::to_json() const { return ToJson(*this); }
|
||||
|
||||
bool RemoteAction::from_json(const std::string& json_string) {
|
||||
RemoteAction temporary{};
|
||||
if (!FromJson(json_string, temporary)) return false;
|
||||
*this = temporary;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string RemoteAction::ToJson(const RemoteAction& action) {
|
||||
if (action.type == ControlType::invalid) return {};
|
||||
|
||||
json object;
|
||||
object["type"] = action.type;
|
||||
switch (action.type) {
|
||||
case ControlType::mouse:
|
||||
object["mouse"] = {{"x", action.m.x},
|
||||
{"y", action.m.y},
|
||||
{"s", action.m.s},
|
||||
{"flag", action.m.flag}};
|
||||
break;
|
||||
case ControlType::keyboard:
|
||||
object["keyboard"] = {{"key_value", action.k.key_value},
|
||||
{"scan_code", action.k.scan_code},
|
||||
{"extended", action.k.extended},
|
||||
{"flag", action.k.flag}};
|
||||
break;
|
||||
case ControlType::keyboard_state: {
|
||||
json keys = json::array();
|
||||
const std::size_t pressed_count =
|
||||
std::min(action.ks.pressed_count, kMaxKeyboardStateKeys);
|
||||
for (std::size_t index = 0; index < pressed_count; ++index) {
|
||||
keys.push_back(
|
||||
{{"key_value", action.ks.pressed_keys[index].key_value},
|
||||
{"scan_code", action.ks.pressed_keys[index].scan_code},
|
||||
{"extended", action.ks.pressed_keys[index].extended}});
|
||||
}
|
||||
object["keyboard_state"] =
|
||||
{{"seq", action.ks.seq}, {"pressed_keys", keys}};
|
||||
break;
|
||||
}
|
||||
case ControlType::cursor_state:
|
||||
object["cursor_state"] =
|
||||
{{"seq", action.cs.seq},
|
||||
{"visible", action.cs.visible},
|
||||
{"shape", static_cast<int>(action.cs.shape)},
|
||||
{"position_valid", action.cs.position_valid},
|
||||
{"x", action.cs.x},
|
||||
{"y", action.cs.y},
|
||||
{"visual_offset_x", action.cs.visual_offset_x},
|
||||
{"visual_offset_y", action.cs.visual_offset_y},
|
||||
{"display_id", action.cs.display_id},
|
||||
{"position_update", action.cs.position_update}};
|
||||
break;
|
||||
case ControlType::audio_capture:
|
||||
object["audio_capture"] = action.a;
|
||||
break;
|
||||
case ControlType::display_id:
|
||||
object["display_id"] = action.d;
|
||||
break;
|
||||
case ControlType::service_status:
|
||||
object["service_status"] =
|
||||
{{"available", action.ss.available},
|
||||
{"interactive_stage", action.ss.interactive_stage}};
|
||||
break;
|
||||
case ControlType::service_command:
|
||||
object["service_command"] = {{"flag", action.c.flag}};
|
||||
break;
|
||||
case ControlType::host_infomation: {
|
||||
json displays = json::array();
|
||||
for (std::size_t index = 0; index < action.i.display_num; ++index) {
|
||||
displays.push_back(
|
||||
{{"name", action.i.display_list ? action.i.display_list[index]
|
||||
: ""},
|
||||
{"left", action.i.left ? action.i.left[index] : 0},
|
||||
{"top", action.i.top ? action.i.top[index] : 0},
|
||||
{"right", action.i.right ? action.i.right[index] : 0},
|
||||
{"bottom", action.i.bottom ? action.i.bottom[index] : 0}});
|
||||
}
|
||||
object["host_info"] = {{"host_name", action.i.host_name},
|
||||
{"display_num", action.i.display_num},
|
||||
{"displays", displays}};
|
||||
break;
|
||||
}
|
||||
case ControlType::invalid:
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
return object.dump();
|
||||
}
|
||||
|
||||
bool RemoteAction::FromJson(const std::string& json_string,
|
||||
RemoteAction& output) {
|
||||
bool owns_host_info = false;
|
||||
try {
|
||||
const json object = json::parse(json_string);
|
||||
output.type = static_cast<ControlType>(object.at("type").get<int>());
|
||||
switch (output.type) {
|
||||
case ControlType::mouse:
|
||||
output.m.x = object.at("mouse").at("x").get<float>();
|
||||
output.m.y = object.at("mouse").at("y").get<float>();
|
||||
output.m.s = object.at("mouse").at("s").get<int>();
|
||||
output.m.flag = static_cast<MouseFlag>(
|
||||
object.at("mouse").at("flag").get<int>());
|
||||
break;
|
||||
case ControlType::keyboard:
|
||||
output.k.key_value =
|
||||
object.at("keyboard").at("key_value").get<std::size_t>();
|
||||
output.k.scan_code = object.at("keyboard").value("scan_code", 0u);
|
||||
output.k.extended = object.at("keyboard").value("extended", false);
|
||||
output.k.flag = static_cast<KeyFlag>(
|
||||
object.at("keyboard").at("flag").get<int>());
|
||||
break;
|
||||
case ControlType::keyboard_state: {
|
||||
const auto& keyboard_state_object = object.at("keyboard_state");
|
||||
output.ks.seq = keyboard_state_object.value("seq", 0u);
|
||||
output.ks.pressed_count = 0;
|
||||
const auto keys =
|
||||
keyboard_state_object.value("pressed_keys", json::array());
|
||||
if (!keys.is_array()) break;
|
||||
const std::size_t count =
|
||||
std::min(keys.size(), kMaxKeyboardStateKeys);
|
||||
for (std::size_t index = 0; index < count; ++index) {
|
||||
output.ks.pressed_keys[index].key_value =
|
||||
keys[index].at("key_value").get<std::size_t>();
|
||||
output.ks.pressed_keys[index].scan_code =
|
||||
keys[index].value("scan_code", 0u);
|
||||
output.ks.pressed_keys[index].extended =
|
||||
keys[index].value("extended", false);
|
||||
}
|
||||
output.ks.pressed_count = count;
|
||||
break;
|
||||
}
|
||||
case ControlType::cursor_state: {
|
||||
const auto& cursor_state_object = object.at("cursor_state");
|
||||
const int shape = cursor_state_object.at("shape").get<int>();
|
||||
if (shape < static_cast<int>(RemoteCursorShape::default_cursor) ||
|
||||
shape > static_cast<int>(RemoteCursorShape::nwse_resize)) {
|
||||
return false;
|
||||
}
|
||||
output.cs.seq = cursor_state_object.at("seq").get<uint32_t>();
|
||||
output.cs.visible = cursor_state_object.at("visible").get<bool>();
|
||||
output.cs.shape = static_cast<RemoteCursorShape>(shape);
|
||||
output.cs.position_valid =
|
||||
cursor_state_object.value("position_valid", false);
|
||||
output.cs.x = cursor_state_object.value("x", 0.5f);
|
||||
output.cs.y = cursor_state_object.value("y", 0.5f);
|
||||
output.cs.visual_offset_x =
|
||||
cursor_state_object.value("visual_offset_x", 0.0f);
|
||||
output.cs.visual_offset_y =
|
||||
cursor_state_object.value("visual_offset_y", 0.0f);
|
||||
output.cs.display_id = cursor_state_object.value("display_id", -1);
|
||||
// Cursor state messages predating position-only echo suppression
|
||||
// always carried an authoritative position update.
|
||||
output.cs.position_update =
|
||||
cursor_state_object.value("position_update", true);
|
||||
if (!std::isfinite(output.cs.x) || !std::isfinite(output.cs.y) ||
|
||||
!std::isfinite(output.cs.visual_offset_x) ||
|
||||
!std::isfinite(output.cs.visual_offset_y)) {
|
||||
return false;
|
||||
}
|
||||
output.cs.x = std::clamp(output.cs.x, 0.0f, 1.0f);
|
||||
output.cs.y = std::clamp(output.cs.y, 0.0f, 1.0f);
|
||||
output.cs.visual_offset_x =
|
||||
std::clamp(output.cs.visual_offset_x, -1.0f, 1.0f);
|
||||
output.cs.visual_offset_y =
|
||||
std::clamp(output.cs.visual_offset_y, -1.0f, 1.0f);
|
||||
break;
|
||||
}
|
||||
case ControlType::audio_capture:
|
||||
output.a = object.at("audio_capture").get<bool>();
|
||||
break;
|
||||
case ControlType::display_id:
|
||||
output.d = object.at("display_id").get<int>();
|
||||
break;
|
||||
case ControlType::service_status: {
|
||||
const auto& service_status_object = object.at("service_status");
|
||||
output.ss.available = service_status_object.value("available", false);
|
||||
const std::string stage = service_status_object.value(
|
||||
"interactive_stage", std::string());
|
||||
std::strncpy(output.ss.interactive_stage, stage.c_str(),
|
||||
sizeof(output.ss.interactive_stage) - 1);
|
||||
output.ss.interactive_stage[sizeof(output.ss.interactive_stage) - 1] =
|
||||
'\0';
|
||||
break;
|
||||
}
|
||||
case ControlType::service_command:
|
||||
output.c.flag = static_cast<ServiceCommandFlag>(
|
||||
object.at("service_command").at("flag").get<int>());
|
||||
break;
|
||||
case ControlType::host_infomation: {
|
||||
ResetHostInfo(output.i);
|
||||
owns_host_info = true;
|
||||
const auto& host_info_object = object.at("host_info");
|
||||
const std::string host_name =
|
||||
host_info_object.at("host_name").get<std::string>();
|
||||
std::strncpy(output.i.host_name, host_name.c_str(),
|
||||
sizeof(output.i.host_name) - 1);
|
||||
output.i.host_name[sizeof(output.i.host_name) - 1] = '\0';
|
||||
output.i.host_name_size = std::strlen(output.i.host_name);
|
||||
|
||||
const auto& displays = host_info_object.at("displays");
|
||||
if (!displays.is_array()) return false;
|
||||
output.i.display_num =
|
||||
host_info_object.at("display_num").get<std::size_t>();
|
||||
if (output.i.display_num != displays.size()) return false;
|
||||
if (!AllocateHostDisplays(output.i, output.i.display_num)) {
|
||||
FreeRemoteAction(output);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < output.i.display_num; ++index) {
|
||||
const std::string name =
|
||||
displays[index].at("name").get<std::string>();
|
||||
output.i.display_list[index] =
|
||||
static_cast<char*>(std::malloc(name.size() + 1));
|
||||
if (!output.i.display_list[index]) {
|
||||
FreeRemoteAction(output);
|
||||
return false;
|
||||
}
|
||||
std::memcpy(output.i.display_list[index], name.c_str(),
|
||||
name.size() + 1);
|
||||
output.i.left[index] = displays[index].at("left").get<int>();
|
||||
output.i.top[index] = displays[index].at("top").get<int>();
|
||||
output.i.right[index] = displays[index].at("right").get<int>();
|
||||
output.i.bottom[index] = displays[index].at("bottom").get<int>();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (const std::exception& exception) {
|
||||
if (owns_host_info) {
|
||||
FreeRemoteAction(output);
|
||||
}
|
||||
std::fprintf(stderr, "Failed to parse RemoteAction JSON: %s\n",
|
||||
exception.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void FreeRemoteAction(RemoteAction& action) {
|
||||
if (action.type != ControlType::host_infomation) return;
|
||||
|
||||
if (action.i.display_list) {
|
||||
for (std::size_t index = 0; index < action.i.display_num; ++index) {
|
||||
std::free(action.i.display_list[index]);
|
||||
}
|
||||
}
|
||||
std::free(action.i.display_list);
|
||||
std::free(action.i.left);
|
||||
std::free(action.i.top);
|
||||
std::free(action.i.right);
|
||||
std::free(action.i.bottom);
|
||||
ResetHostInfo(action.i);
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
Reference in New Issue
Block a user