#include "screen_capturer_drm.h" #if defined(CROSSDESK_HAS_DRM) && CROSSDESK_HAS_DRM && \ defined(__has_include) && __has_include() && \ __has_include() #define CROSSDESK_DRM_BUILD_ENABLED 1 #include #include #elif defined(CROSSDESK_HAS_DRM) && CROSSDESK_HAS_DRM && \ defined(__has_include) && __has_include() && \ __has_include() #define CROSSDESK_DRM_BUILD_ENABLED 1 #include #include #else #define CROSSDESK_DRM_BUILD_ENABLED 0 #endif #if CROSSDESK_DRM_BUILD_ENABLED #include #include #include #include #include #include #include "libyuv.h" #include "rd_log.h" namespace crossdesk { namespace { constexpr int kMaxDrmCards = 16; const char* ConnectorTypeName(uint32_t type) { switch (type) { case DRM_MODE_CONNECTOR_VGA: return "VGA"; case DRM_MODE_CONNECTOR_DVII: return "DVI-I"; case DRM_MODE_CONNECTOR_DVID: return "DVI-D"; case DRM_MODE_CONNECTOR_DVIA: return "DVI-A"; case DRM_MODE_CONNECTOR_HDMIA: return "HDMI-A"; case DRM_MODE_CONNECTOR_HDMIB: return "HDMI-B"; case DRM_MODE_CONNECTOR_DisplayPort: return "DP"; case DRM_MODE_CONNECTOR_eDP: return "eDP"; case DRM_MODE_CONNECTOR_LVDS: return "LVDS"; #ifdef DRM_MODE_CONNECTOR_VIRTUAL case DRM_MODE_CONNECTOR_VIRTUAL: return "Virtual"; #endif default: return "Display"; } } } // namespace ScreenCapturerDrm::ScreenCapturerDrm() {} ScreenCapturerDrm::~ScreenCapturerDrm() { Destroy(); } int ScreenCapturerDrm::Init(const int fps, cb_desktop_data cb) { Destroy(); if (!cb) { LOG_ERROR("DRM screen capturer callback is null"); return -1; } fps_ = std::max(1, fps); callback_ = cb; monitor_index_ = 0; initial_monitor_index_ = 0; consecutive_failures_ = 0; display_info_list_.clear(); outputs_.clear(); y_plane_.clear(); uv_plane_.clear(); if (!DiscoverOutputs()) { LOG_ERROR("DRM screen capturer could not find active outputs"); callback_ = nullptr; CloseDevices(); return -1; } return 0; } int ScreenCapturerDrm::Destroy() { Stop(); callback_ = nullptr; display_info_list_.clear(); outputs_.clear(); y_plane_.clear(); uv_plane_.clear(); CloseDevices(); return 0; } int ScreenCapturerDrm::Start(bool show_cursor) { if (running_) { return 0; } if (outputs_.empty()) { LOG_ERROR("DRM screen capturer has no output to capture"); return -1; } show_cursor_ = show_cursor; paused_ = false; int probe_index = monitor_index_.load(); if (probe_index < 0 || probe_index >= static_cast(outputs_.size())) { probe_index = 0; } if (!CaptureOutputFrame(outputs_[probe_index], false)) { LOG_ERROR("DRM start probe failed on output {}", outputs_[probe_index].name); return -1; } running_ = true; thread_ = std::thread([this]() { CaptureLoop(); }); return 0; } int ScreenCapturerDrm::Stop() { if (!running_) { return 0; } running_ = false; if (thread_.joinable()) { thread_.join(); } return 0; } int ScreenCapturerDrm::Pause([[maybe_unused]] int monitor_index) { paused_ = true; return 0; } int ScreenCapturerDrm::Resume([[maybe_unused]] int monitor_index) { paused_ = false; return 0; } int ScreenCapturerDrm::SwitchTo(int monitor_index) { if (monitor_index < 0 || monitor_index >= static_cast(display_info_list_.size())) { LOG_ERROR("Invalid DRM monitor index: {}", monitor_index); return -1; } monitor_index_ = monitor_index; return 0; } int ScreenCapturerDrm::ResetToInitialMonitor() { monitor_index_ = initial_monitor_index_; return 0; } std::vector ScreenCapturerDrm::GetDisplayInfoList() { return display_info_list_; } bool ScreenCapturerDrm::DiscoverOutputs() { for (int card_index = 0; card_index < kMaxDrmCards; ++card_index) { const std::string card_path = "/dev/dri/card" + std::to_string(card_index); const int fd = open(card_path.c_str(), O_RDWR | O_CLOEXEC); if (fd < 0) { continue; } drmModeRes* resources = drmModeGetResources(fd); if (!resources) { close(fd); continue; } DrmDevice device; device.fd = fd; device.path = card_path; devices_.push_back(device); const int device_slot = static_cast(devices_.size()) - 1; const size_t output_count_before = outputs_.size(); for (int i = 0; i < resources->count_connectors; ++i) { drmModeConnector* connector = drmModeGetConnector(fd, resources->connectors[i]); if (!connector) { continue; } if (connector->connection != DRM_MODE_CONNECTED || connector->count_modes <= 0) { drmModeFreeConnector(connector); continue; } uint32_t crtc_id = 0; if (connector->encoder_id != 0) { drmModeEncoder* encoder = drmModeGetEncoder(fd, connector->encoder_id); if (encoder) { crtc_id = encoder->crtc_id; drmModeFreeEncoder(encoder); } } if (crtc_id == 0) { for (int enc_idx = 0; enc_idx < connector->count_encoders; ++enc_idx) { drmModeEncoder* encoder = drmModeGetEncoder(fd, connector->encoders[enc_idx]); if (!encoder) { continue; } if (encoder->crtc_id != 0) { crtc_id = encoder->crtc_id; drmModeFreeEncoder(encoder); break; } drmModeFreeEncoder(encoder); } } if (crtc_id == 0) { drmModeFreeConnector(connector); continue; } drmModeCrtc* crtc = drmModeGetCrtc(fd, crtc_id); if (!crtc || !crtc->mode_valid || crtc->width <= 0 || crtc->height <= 0) { if (crtc) { drmModeFreeCrtc(crtc); } drmModeFreeConnector(connector); continue; } DrmOutput output; output.device_index = device_slot; output.connector_id = connector->connector_id; output.crtc_id = crtc_id; output.left = crtc->x; output.top = crtc->y; output.width = static_cast(crtc->width); output.height = static_cast(crtc->height); output.name = std::string(ConnectorTypeName(connector->connector_type)) + std::to_string(connector->connector_type_id); outputs_.push_back(output); display_info_list_.push_back( DisplayInfo(output.name, output.left, output.top, output.left + output.width, output.top + output.height)); LOG_INFO("DRM output found: {} on {}, {}x{} @ ({}, {})", output.name, card_path, output.width, output.height, output.left, output.top); drmModeFreeCrtc(crtc); drmModeFreeConnector(connector); } drmModeFreeResources(resources); if (outputs_.size() == output_count_before) { close(fd); devices_.pop_back(); } } if (outputs_.empty()) { return false; } LOG_INFO("DRM screen capturer discovered {} output(s)", outputs_.size()); return true; } void ScreenCapturerDrm::CloseDevices() { for (auto& device : devices_) { if (device.fd >= 0) { close(device.fd); device.fd = -1; } } devices_.clear(); } void ScreenCapturerDrm::CaptureLoop() { using clock = std::chrono::steady_clock; const auto frame_interval = std::chrono::milliseconds(std::max(1, 1000 / std::max(1, fps_))); while (running_) { const auto frame_start = clock::now(); if (!paused_) { int index = monitor_index_.load(); if (index >= 0 && index < static_cast(outputs_.size())) { const bool ok = CaptureOutputFrame(outputs_[index], true); if (!ok) { ++consecutive_failures_; if (consecutive_failures_ == 1 || consecutive_failures_ % 60 == 0) { LOG_WARN("DRM capture failed (consecutive={})", consecutive_failures_); } } else { consecutive_failures_ = 0; } } } const auto elapsed = std::chrono::duration_cast( clock::now() - frame_start); if (elapsed < frame_interval) { std::this_thread::sleep_for(frame_interval - elapsed); } } } bool ScreenCapturerDrm::CaptureOutputFrame(const DrmOutput& output, bool emit_callback) { if (output.device_index < 0 || output.device_index >= static_cast(devices_.size())) { return false; } const int fd = devices_[output.device_index].fd; if (fd < 0) { return false; } drmModeCrtc* crtc = drmModeGetCrtc(fd, output.crtc_id); if (!crtc) { return false; } const uint32_t fb_id = crtc->buffer_id; drmModeFreeCrtc(crtc); if (fb_id == 0) { return false; } drmModeFB* fb = drmModeGetFB(fd, fb_id); if (!fb) { return false; } const uint32_t handle = fb->handle; const uint32_t pitch = fb->pitch; const int src_width = static_cast(fb->width); const int src_height = static_cast(fb->height); const int bpp = static_cast(fb->bpp); drmModeFreeFB(fb); if (handle == 0 || pitch == 0 || src_width <= 1 || src_height <= 1) { return false; } if (bpp != 32) { LOG_WARN("DRM capture unsupported bpp: {}", bpp); return false; } const size_t map_size = static_cast(pitch) * static_cast(src_height); uint8_t* mapped_ptr = nullptr; size_t mapped_size = 0; int prime_fd = -1; if (!MapFramebuffer(fd, handle, map_size, &mapped_ptr, &mapped_size, &prime_fd)) { return false; } int capture_width = std::min(src_width, output.width); int capture_height = std::min(src_height, output.height); if (capture_width <= 0 || capture_height <= 0) { capture_width = src_width; capture_height = src_height; } capture_width &= ~1; capture_height &= ~1; if (capture_width <= 1 || capture_height <= 1) { UnmapFramebuffer(mapped_ptr, mapped_size, prime_fd); return false; } const size_t y_size = static_cast(capture_width) * static_cast(capture_height); const size_t uv_size = y_size / 2; if (y_plane_.size() != y_size) { y_plane_.resize(y_size); } if (uv_plane_.size() != uv_size) { uv_plane_.resize(uv_size); } const int convert_ret = libyuv::ARGBToNV12(mapped_ptr, static_cast(pitch), y_plane_.data(), capture_width, uv_plane_.data(), capture_width, capture_width, capture_height); if (convert_ret != 0) { UnmapFramebuffer(mapped_ptr, mapped_size, prime_fd); return false; } std::vector nv12; nv12.reserve(y_plane_.size() + uv_plane_.size()); nv12.insert(nv12.end(), y_plane_.begin(), y_plane_.end()); nv12.insert(nv12.end(), uv_plane_.begin(), uv_plane_.end()); if (emit_callback && callback_) { callback_(nv12.data(), static_cast(nv12.size()), capture_width, capture_height, output.name.c_str()); } UnmapFramebuffer(mapped_ptr, mapped_size, prime_fd); return true; } bool ScreenCapturerDrm::MapFramebuffer(int fd, uint32_t handle, size_t map_size, uint8_t** mapped_ptr, size_t* mapped_size, int* prime_fd) const { if (!mapped_ptr || !mapped_size || !prime_fd || map_size == 0) { return false; } *mapped_ptr = nullptr; *mapped_size = 0; *prime_fd = -1; drm_mode_map_dumb map_arg{}; map_arg.handle = handle; if (drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_arg) == 0) { void* mapped = mmap(nullptr, map_size, PROT_READ, MAP_SHARED, fd, static_cast(map_arg.offset)); if (mapped != MAP_FAILED) { *mapped_ptr = static_cast(mapped); *mapped_size = map_size; return true; } } int dma_fd = -1; if (drmPrimeHandleToFD(fd, handle, DRM_CLOEXEC, &dma_fd) == 0) { size_t dma_map_size = map_size; const off_t fd_size = lseek(dma_fd, 0, SEEK_END); if (fd_size > 0) { dma_map_size = std::min(map_size, static_cast(fd_size)); } void* mapped = mmap(nullptr, dma_map_size, PROT_READ, MAP_SHARED, dma_fd, 0); if (mapped != MAP_FAILED) { *mapped_ptr = static_cast(mapped); *mapped_size = dma_map_size; *prime_fd = dma_fd; return true; } close(dma_fd); } return false; } void ScreenCapturerDrm::UnmapFramebuffer(uint8_t* mapped_ptr, size_t mapped_size, int prime_fd) const { if (mapped_ptr && mapped_size > 0) { munmap(mapped_ptr, mapped_size); } if (prime_fd >= 0) { close(prime_fd); } } } // namespace crossdesk #else #include "rd_log.h" namespace crossdesk { ScreenCapturerDrm::ScreenCapturerDrm() {} ScreenCapturerDrm::~ScreenCapturerDrm() { Destroy(); } int ScreenCapturerDrm::Init([[maybe_unused]] const int fps, cb_desktop_data cb) { Destroy(); callback_ = cb; LOG_WARN("DRM screen capturer disabled: libdrm headers not available"); return -1; } int ScreenCapturerDrm::Destroy() { Stop(); callback_ = nullptr; display_info_list_.clear(); outputs_.clear(); return 0; } int ScreenCapturerDrm::Start([[maybe_unused]] bool show_cursor) { return -1; } int ScreenCapturerDrm::Stop() { running_ = false; if (thread_.joinable()) { thread_.join(); } return 0; } int ScreenCapturerDrm::Pause([[maybe_unused]] int monitor_index) { return 0; } int ScreenCapturerDrm::Resume([[maybe_unused]] int monitor_index) { return 0; } int ScreenCapturerDrm::SwitchTo([[maybe_unused]] int monitor_index) { return -1; } int ScreenCapturerDrm::ResetToInitialMonitor() { return 0; } std::vector ScreenCapturerDrm::GetDisplayInfoList() { return display_info_list_; } bool ScreenCapturerDrm::DiscoverOutputs() { return false; } void ScreenCapturerDrm::CloseDevices() {} void ScreenCapturerDrm::CaptureLoop() {} bool ScreenCapturerDrm::CaptureOutputFrame( [[maybe_unused]] const DrmOutput& output, [[maybe_unused]] bool emit_callback) { return false; } bool ScreenCapturerDrm::MapFramebuffer([[maybe_unused]] int fd, [[maybe_unused]] uint32_t handle, [[maybe_unused]] size_t map_size, [[maybe_unused]] uint8_t** mapped_ptr, [[maybe_unused]] size_t* mapped_size, [[maybe_unused]] int* prime_fd) const { return false; } void ScreenCapturerDrm::UnmapFramebuffer([[maybe_unused]] uint8_t* mapped_ptr, [[maybe_unused]] size_t mapped_size, [[maybe_unused]] int prime_fd) const {} } // namespace crossdesk #endif