#include #include #include #include #include #include #include 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(std::calloc(count, sizeof(char*))); info.left = static_cast(std::malloc(count * sizeof(int))); info.top = static_cast(std::malloc(count * sizeof(int))); info.right = static_cast(std::malloc(count * sizeof(int))); info.bottom = static_cast(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(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::privacy_command: object["privacy_command"] = {{"flag", action.pc.flag}, {"block_local_input", action.pc.block_local_input}}; break; case ControlType::privacy_status: object["privacy_status"] = {{"state", action.ps.state}, {"supported", action.ps.supported}, {"input_block_supported", action.ps.input_block_supported}, {"overlay_active", action.ps.overlay_active}, {"input_blocked", action.ps.input_blocked}, {"remote_paused", action.ps.remote_paused}, {"revision", action.ps.revision}, {"reason", action.ps.reason}}; 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(object.at("type").get()); switch (output.type) { case ControlType::mouse: output.m.x = object.at("mouse").at("x").get(); output.m.y = object.at("mouse").at("y").get(); output.m.s = object.at("mouse").at("s").get(); output.m.flag = static_cast( object.at("mouse").at("flag").get()); break; case ControlType::keyboard: output.k.key_value = object.at("keyboard").at("key_value").get(); 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( object.at("keyboard").at("flag").get()); 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(); 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(); if (shape < static_cast(RemoteCursorShape::default_cursor) || shape > static_cast(RemoteCursorShape::nwse_resize)) { return false; } output.cs.seq = cursor_state_object.at("seq").get(); output.cs.visible = cursor_state_object.at("visible").get(); output.cs.shape = static_cast(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(); break; case ControlType::display_id: output.d = object.at("display_id").get(); 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( object.at("service_command").at("flag").get()); break; case ControlType::privacy_command: { const auto& command = object.at("privacy_command"); const int flag = command.at("flag").get(); if (flag < 0 || flag > 2) return false; output.pc.flag = static_cast(flag); output.pc.block_local_input = command.at("block_local_input").get(); break; } case ControlType::privacy_status: { const auto& status = object.at("privacy_status"); const int state = status.at("state").get(); const auto reason = status.at("reason").get(); if (state < 0 || state > 5 || reason.size() >= sizeof(output.ps.reason) || reason.find('\0') != std::string::npos) return false; output.ps.state = static_cast(state); output.ps.supported = status.at("supported").get(); output.ps.input_block_supported = status.at("input_block_supported").get(); output.ps.overlay_active = status.at("overlay_active").get(); output.ps.input_blocked = status.at("input_blocked").get(); output.ps.remote_paused = status.at("remote_paused").get(); output.ps.revision = status.at("revision").get(); std::memcpy(output.ps.reason, reason.c_str(), reason.size() + 1); // Never render a contradictory success from a malformed peer. if (output.ps.state == PrivacyState::on && (!output.ps.supported || !output.ps.overlay_active || output.ps.remote_paused)) return false; 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::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(); 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(); output.i.display_list[index] = static_cast(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(); output.i.top[index] = displays[index].at("top").get(); output.i.right[index] = displays[index].at("right").get(); output.i.bottom[index] = displays[index].at("bottom").get(); } 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