#include #include #include #include #include #include #include #include "sunshine/main.h" #include "sunshine/platform/common.h" #include "sunshine/round_robin.h" #include "sunshine/utility.h" // Cursor rendering support through x11 #include "graphics.h" #include "vaapi.h" #include "wayland.h" #include "x11grab.h" using namespace std::literals; namespace fs = std::filesystem; namespace platf { namespace kms { class wrapper_fb { public: wrapper_fb(drmModeFB *fb) : fb { fb }, fb_id { fb->fb_id }, width { fb->width }, height { fb->height } { pixel_format = DRM_FORMAT_XRGB8888; modifier = DRM_FORMAT_MOD_INVALID; std::fill_n(handles, 4, 0); std::fill_n(pitches, 4, 0); std::fill_n(offsets, 4, 0); handles[0] = fb->handle; pitches[0] = fb->pitch; } wrapper_fb(drmModeFB2 *fb2) : fb2 { fb2 }, fb_id { fb2->fb_id }, width { fb2->width }, height { fb2->height } { pixel_format = fb2->pixel_format; modifier = (fb2->flags & DRM_MODE_FB_MODIFIERS) ? fb2->modifier : DRM_FORMAT_MOD_INVALID; memcpy(handles, fb2->handles, sizeof(handles)); memcpy(pitches, fb2->pitches, sizeof(pitches)); memcpy(offsets, fb2->offsets, sizeof(offsets)); } ~wrapper_fb() { if(fb) { drmModeFreeFB(fb); } else if(fb2) { drmModeFreeFB2(fb2); } } drmModeFB *fb = nullptr; drmModeFB2 *fb2 = nullptr; uint32_t fb_id; uint32_t width; uint32_t height; uint32_t pixel_format; uint64_t modifier; uint32_t handles[4]; uint32_t pitches[4]; uint32_t offsets[4]; }; using plane_res_t = util::safe_ptr; using encoder_t = util::safe_ptr; using res_t = util::safe_ptr; using plane_t = util::safe_ptr; using fb_t = std::unique_ptr; using crtc_t = util::safe_ptr; using obj_prop_t = util::safe_ptr; using prop_t = util::safe_ptr; using conn_type_count_t = std::map; static int env_width; static int env_height; std::string_view plane_type(std::uint64_t val) { switch(val) { case DRM_PLANE_TYPE_OVERLAY: return "DRM_PLANE_TYPE_OVERLAY"sv; case DRM_PLANE_TYPE_PRIMARY: return "DRM_PLANE_TYPE_PRIMARY"sv; case DRM_PLANE_TYPE_CURSOR: return "DRM_PLANE_TYPE_CURSOR"sv; } return "UNKNOWN"sv; } struct connector_t { // For example: HDMI-A or HDMI std::uint32_t type; // Equals zero if not applicable std::uint32_t crtc_id; // For example HDMI-A-{index} or HDMI-{index} std::uint32_t index; bool connected; }; struct monitor_t { std::uint32_t type; std::uint32_t index; platf::touch_port_t viewport; }; struct card_descriptor_t { std::string path; std::map crtc_to_monitor; }; static std::vector card_descriptors; static std::uint32_t from_view(const std::string_view &string) { #define _CONVERT(x, y) \ if(string == x) return DRM_MODE_CONNECTOR_##y _CONVERT("VGA"sv, VGA); _CONVERT("DVI-I"sv, DVII); _CONVERT("DVI-D"sv, DVID); _CONVERT("DVI-A"sv, DVIA); _CONVERT("S-Video"sv, SVIDEO); _CONVERT("LVDS"sv, LVDS); _CONVERT("DIN"sv, 9PinDIN); _CONVERT("DisplayPort"sv, DisplayPort); _CONVERT("DP"sv, DisplayPort); _CONVERT("HDMI-A"sv, HDMIA); _CONVERT("HDMI"sv, HDMIA); _CONVERT("HDMI-B"sv, HDMIB); _CONVERT("eDP"sv, eDP); _CONVERT("DSI"sv, DSI); BOOST_LOG(error) << "Unknown Monitor connector type ["sv << string << "]: Please report this to the Github issue tracker"sv; return DRM_MODE_CONNECTOR_Unknown; } class plane_it_t : public util::it_wrap_t { public: plane_it_t(int fd, std::uint32_t *plane_p, std::uint32_t *end) : fd { fd }, plane_p { plane_p }, end { end } { inc(); } plane_it_t(int fd, std::uint32_t *end) : fd { fd }, plane_p { end }, end { end } {} void inc() { this->plane.reset(); for(; plane_p != end; ++plane_p) { plane_t plane = drmModeGetPlane(fd, *plane_p); if(!plane) { BOOST_LOG(error) << "Couldn't get drm plane ["sv << (end - plane_p) << "]: "sv << strerror(errno); continue; } // If this plane is unused if(plane->fb_id) { this->plane = util::make_shared(plane.release()); // One last increment ++plane_p; break; } } } bool eq(const plane_it_t &other) const { return plane_p == other.plane_p; } plane_t::pointer get() { return plane.get(); } int fd; std::uint32_t *plane_p; std::uint32_t *end; util::shared_t plane; }; class card_t { public: using connector_interal_t = util::safe_ptr; int init(const char *path) { fd.el = open(path, O_RDWR); if(fd.el < 0) { BOOST_LOG(error) << "Couldn't open: "sv << path << ": "sv << strerror(errno); return -1; } if(drmSetClientCap(fd.el, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { BOOST_LOG(error) << "Couldn't expose some/all drm planes for card: "sv << path; return -1; } if(drmSetClientCap(fd.el, DRM_CLIENT_CAP_ATOMIC, 1)) { BOOST_LOG(warning) << "Couldn't expose some properties for card: "sv << path; } plane_res.reset(drmModeGetPlaneResources(fd.el)); if(!plane_res) { BOOST_LOG(error) << "Couldn't get drm plane resources"sv; return -1; } return 0; } fb_t fb(plane_t::pointer plane) { auto fb = drmModeGetFB2(fd.el, plane->fb_id); if(fb) { return std::make_unique(fb); } return std::make_unique(drmModeGetFB(fd.el, plane->fb_id)); } crtc_t crtc(std::uint32_t id) { return drmModeGetCrtc(fd.el, id); } encoder_t encoder(std::uint32_t id) { return drmModeGetEncoder(fd.el, id); } res_t res() { return drmModeGetResources(fd.el); } bool is_cursor(std::uint32_t plane_id) { auto props = plane_props(plane_id); for(auto &[prop, val] : props) { if(prop->name == "type"sv) { if(val == DRM_PLANE_TYPE_CURSOR) { return true; } else { return false; } } } return false; } connector_interal_t connector(std::uint32_t id) { return drmModeGetConnector(fd.el, id); } std::vector monitors(conn_type_count_t &conn_type_count) { auto resources = res(); if(!resources) { BOOST_LOG(error) << "Couldn't get connector resources"sv; return {}; } std::vector monitors; std::for_each_n(resources->connectors, resources->count_connectors, [this, &conn_type_count, &monitors](std::uint32_t id) { auto conn = connector(id); std::uint32_t crtc_id = 0; if(conn->encoder_id) { auto enc = encoder(conn->encoder_id); if(enc) { crtc_id = enc->crtc_id; } } auto index = ++conn_type_count[conn->connector_type]; monitors.emplace_back(connector_t { conn->connector_type, crtc_id, index, conn->connection == DRM_MODE_CONNECTED, }); }); return monitors; } file_t handleFD(std::uint32_t handle) { file_t fb_fd; auto status = drmPrimeHandleToFD(fd.el, handle, 0 /* flags */, &fb_fd.el); if(status) { return {}; } return fb_fd; } std::vector> props(std::uint32_t id, std::uint32_t type) { obj_prop_t obj_prop = drmModeObjectGetProperties(fd.el, id, type); std::vector> props; props.reserve(obj_prop->count_props); for(auto x = 0; x < obj_prop->count_props; ++x) { props.emplace_back(drmModeGetProperty(fd.el, obj_prop->props[x]), obj_prop->prop_values[x]); } return props; } std::vector> plane_props(std::uint32_t id) { return props(id, DRM_MODE_OBJECT_PLANE); } std::vector> crtc_props(std::uint32_t id) { return props(id, DRM_MODE_OBJECT_CRTC); } std::vector> connector_props(std::uint32_t id) { return props(id, DRM_MODE_OBJECT_CONNECTOR); } plane_t operator[](std::uint32_t index) { return drmModeGetPlane(fd.el, plane_res->planes[index]); } std::uint32_t count() { return plane_res->count_planes; } plane_it_t begin() const { return plane_it_t { fd.el, plane_res->planes, plane_res->planes + plane_res->count_planes }; } plane_it_t end() const { return plane_it_t { fd.el, plane_res->planes + plane_res->count_planes }; } file_t fd; plane_res_t plane_res; }; std::map map_crtc_to_monitor(const std::vector &connectors) { std::map result; for(auto &connector : connectors) { result.emplace(connector.crtc_id, monitor_t { connector.type, connector.index, }); } return result; } struct kms_img_t : public img_t { ~kms_img_t() override { delete[] data; data = nullptr; } }; void print(plane_t::pointer plane, fb_t::pointer fb, crtc_t::pointer crtc) { if(crtc) { BOOST_LOG(debug) << "crtc("sv << crtc->x << ", "sv << crtc->y << ')'; BOOST_LOG(debug) << "crtc("sv << crtc->width << ", "sv << crtc->height << ')'; BOOST_LOG(debug) << "plane->possible_crtcs == "sv << plane->possible_crtcs; } BOOST_LOG(debug) << "x("sv << plane->x << ") y("sv << plane->y << ") crtc_x("sv << plane->crtc_x << ") crtc_y("sv << plane->crtc_y << ") crtc_id("sv << plane->crtc_id << ')'; BOOST_LOG(debug) << "Resolution: "sv << fb->width << 'x' << fb->height << ": Pitch: "sv << fb->pitches[0] << ": Offset: "sv << fb->offsets[0]; std::stringstream ss; ss << "Format ["sv; std::for_each_n(plane->formats, plane->count_formats - 1, [&ss](auto format) { ss << util::view(format) << ", "sv; }); ss << util::view(plane->formats[plane->count_formats - 1]) << ']'; BOOST_LOG(debug) << ss.str(); } class display_t : public platf::display_t { public: display_t(mem_type_e mem_type) : platf::display_t(), mem_type { mem_type } {} int init(const std::string &display_name, int framerate) { delay = std::chrono::nanoseconds { 1s } / framerate; int monitor_index = util::from_view(display_name); int monitor = 0; fs::path card_dir { "/dev/dri"sv }; for(auto &entry : fs::directory_iterator { card_dir }) { auto file = entry.path().filename(); auto filestring = file.generic_u8string(); if(filestring.size() < 4 || std::string_view { filestring }.substr(0, 4) != "card"sv) { continue; } kms::card_t card; if(card.init(entry.path().c_str())) { return {}; } auto end = std::end(card); for(auto plane = std::begin(card); plane != end; ++plane) { if(card.is_cursor(plane->plane_id)) { continue; } if(monitor != monitor_index) { ++monitor; continue; } auto fb = card.fb(plane.get()); if(!fb) { BOOST_LOG(error) << "Couldn't get drm fb for plane ["sv << plane->fb_id << "]: "sv << strerror(errno); return -1; } if(!fb->handles[0]) { BOOST_LOG(error) << "Couldn't get handle for DRM Framebuffer ["sv << plane->fb_id << "]: Possibly not permitted: do [sudo setcap cap_sys_admin+ep sunshine]"sv; return -1; } for(int i = 0; i < 4; ++i) { if(!fb->handles[i]) { break; } auto fb_fd = card.handleFD(fb->handles[i]); if(fb_fd.el < 0) { BOOST_LOG(error) << "Couldn't get primary file descriptor for Framebuffer ["sv << fb->fb_id << "]: "sv << strerror(errno); continue; } } BOOST_LOG(info) << "Found monitor for DRM screencasting"sv; // We need to find the correct /dev/dri/card{nr} to correlate the crtc_id with the monitor descriptor auto pos = std::find_if(std::begin(card_descriptors), std::end(card_descriptors), [&](card_descriptor_t &cd) { return cd.path == filestring; }); if(pos == std::end(card_descriptors)) { // This code path shouldn't happend, but it's there just in case. // card_descriptors is part of the guesswork after all. BOOST_LOG(error) << "Couldn't find ["sv << entry.path() << "]: This shouldn't have happened :/"sv; return -1; } //TODO: surf_sd = fb->to_sd(); auto crct = card.crtc(plane->crtc_id); kms::print(plane.get(), fb.get(), crct.get()); img_width = fb->width; img_height = fb->height; img_offset_x = crct->x; img_offset_y = crct->y; this->env_width = ::platf::kms::env_width; this->env_height = ::platf::kms::env_height; auto monitor = pos->crtc_to_monitor.find(plane->crtc_id); if(monitor != std::end(pos->crtc_to_monitor)) { auto &viewport = monitor->second.viewport; width = viewport.width; height = viewport.height; offset_x = viewport.offset_x; offset_y = viewport.offset_y; } // This code path shouldn't happend, but it's there just in case. // crtc_to_monitor is part of the guesswork after all. else { BOOST_LOG(warning) << "Couldn't find crtc_id, this shouldn't have happened :\\"sv; width = crct->width; height = crct->height; offset_x = crct->x; offset_y = crct->y; } this->card = std::move(card); plane_id = plane->plane_id; goto break_loop; } } // Neatly break from nested for loop break_loop: if(monitor != monitor_index) { BOOST_LOG(error) << "Couldn't find monitor ["sv << monitor_index << ']'; return -1; } cursor_opt = x11::cursor_t::make(); return 0; } inline capture_e refresh(file_t *file, egl::surface_descriptor_t *sd) { plane_t plane = drmModeGetPlane(card.fd.el, plane_id); auto fb = card.fb(plane.get()); if(!fb) { BOOST_LOG(error) << "Couldn't get drm fb for plane ["sv << plane->fb_id << "]: "sv << strerror(errno); return capture_e::error; } if(!fb->handles[0]) { BOOST_LOG(error) << "Couldn't get handle for DRM Framebuffer ["sv << plane->fb_id << "]: Possibly not permitted: do [sudo setcap cap_sys_admin+ep sunshine]"sv; return capture_e::error; } auto obj_count = 4; int x = 0; for(int y = 0; y < 4; ++y) { if(!fb->handles[y]) { // It's not clear wheter there could still be valid handles left. // So, continue anyway. // TODO: Is this redundent? --obj_count; continue; } file[x] = card.handleFD(fb->handles[x]); if(file[x].el < 0) { BOOST_LOG(error) << "Couldn't get primary file descriptor for Framebuffer ["sv << fb->fb_id << "]: "sv << strerror(errno); return capture_e::error; } sd->fds[x] = file[x].el; sd->offsets[x] = fb->offsets[y]; sd->pitches[x] = fb->pitches[y]; sd->plane_indices[x] = y; ++x; } sd->width = fb->width; sd->height = fb->height; sd->modifier = fb->modifier; sd->fourcc = fb->pixel_format; sd->obj_count = obj_count; if( fb->width != img_width || fb->height != img_height) { return capture_e::reinit; } return capture_e::ok; } mem_type_e mem_type; std::chrono::nanoseconds delay; int img_width, img_height; int img_offset_x, img_offset_y; int plane_id; card_t card; std::optional cursor_opt; }; class display_ram_t : public display_t { public: display_ram_t(mem_type_e mem_type) : display_t(mem_type) {} int init(const std::string &display_name, int framerate) { if(!gbm::create_device) { BOOST_LOG(warning) << "libgbm not initialized"sv; return -1; } if(display_t::init(display_name, framerate)) { return -1; } gbm.reset(gbm::create_device(card.fd.el)); if(!gbm) { BOOST_LOG(error) << "Couldn't create GBM device: ["sv << util::hex(eglGetError()).to_string_view() << ']'; return -1; } display = egl::make_display(gbm.get()); if(!display) { return -1; } auto ctx_opt = egl::make_ctx(display.get()); if(!ctx_opt) { return -1; } ctx = std::move(*ctx_opt); return 0; } capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override { auto next_frame = std::chrono::steady_clock::now(); while(img) { auto now = std::chrono::steady_clock::now(); if(next_frame > now) { std::this_thread::sleep_for((next_frame - now) / 3 * 2); } while(next_frame > now) { std::this_thread::sleep_for(1ns); now = std::chrono::steady_clock::now(); } next_frame = now + delay; auto status = snapshot(img.get(), 1000ms, *cursor); switch(status) { case platf::capture_e::reinit: case platf::capture_e::error: return status; case platf::capture_e::timeout: std::this_thread::sleep_for(1ms); continue; case platf::capture_e::ok: img = snapshot_cb(img); break; default: BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; return status; } } return capture_e::ok; } std::shared_ptr make_hwdevice(pix_fmt_e pix_fmt) override { if(mem_type == mem_type_e::vaapi) { return va::make_hwdevice(width, height, false); } return std::make_shared(); } capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) { file_t fb_fd[4]; egl::surface_descriptor_t sd; auto status = refresh(fb_fd, &sd); if(status != capture_e::ok) { return status; } auto rgb_opt = egl::import_source(display.get(), sd); if(!rgb_opt) { return capture_e::error; } auto &rgb = *rgb_opt; gl::ctx.BindTexture(GL_TEXTURE_2D, rgb->tex[0]); gl::ctx.GetTextureSubImage(rgb->tex[0], 0, img_offset_x, img_offset_y, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out_base->height * img_out_base->row_pitch, img_out_base->data); if(cursor_opt && cursor) { cursor_opt->blend(*img_out_base, img_offset_x, img_offset_y); } return capture_e::ok; } std::shared_ptr alloc_img() override { auto img = std::make_shared(); img->width = width; img->height = height; img->pixel_pitch = 4; img->row_pitch = img->pixel_pitch * width; img->data = new std::uint8_t[height * img->row_pitch]; return img; } int dummy_img(platf::img_t *img) override { return 0; } gbm::gbm_t gbm; egl::display_t display; egl::ctx_t ctx; }; class display_vram_t : public display_t { public: display_vram_t(mem_type_e mem_type) : display_t(mem_type) {} std::shared_ptr make_hwdevice(pix_fmt_e pix_fmt) override { if(mem_type == mem_type_e::vaapi) { return va::make_hwdevice(width, height, dup(card.fd.el), img_offset_x, img_offset_y, true); } BOOST_LOG(error) << "Unsupported pixel format for egl::display_vram_t: "sv << platf::from_pix_fmt(pix_fmt); return nullptr; } std::shared_ptr alloc_img() override { auto img = std::make_shared(); img->serial = std::numeric_limitsserial)>::max(); img->data = nullptr; img->pixel_pitch = 4; img->sequence = 0; img->sd.obj_count = 0; return img; } int dummy_img(platf::img_t *img) override { return snapshot(img, 1s, false) != capture_e::ok; } capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) { auto next_frame = std::chrono::steady_clock::now(); while(img) { auto now = std::chrono::steady_clock::now(); if(next_frame > now) { std::this_thread::sleep_for((next_frame - now) / 3 * 2); } while(next_frame > now) { std::this_thread::sleep_for(1ns); now = std::chrono::steady_clock::now(); } next_frame = now + delay; auto status = snapshot(img.get(), 1000ms, *cursor); switch(status) { case platf::capture_e::reinit: case platf::capture_e::error: return status; case platf::capture_e::timeout: std::this_thread::sleep_for(1ms); continue; case platf::capture_e::ok: img = snapshot_cb(img); break; default: BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; return status; } } return capture_e::ok; } capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds /* timeout */, bool cursor) { file_t fb_fd[4]; auto img = (egl::img_descriptor_t *)img_out_base; img->reset(); auto status = refresh(fb_fd, &img->sd); if(status != capture_e::ok) { return status; } img->sequence = ++sequence; if(!cursor || !cursor_opt) { img_out_base->data = nullptr; for(auto x = 0; x < img->sd.obj_count; ++x) { fb_fd[x].release(); } return capture_e::ok; } cursor_opt->capture(*img); img->x -= offset_x; img->y -= offset_y; for(auto x = 0; x < img->sd.obj_count; ++x) { fb_fd[x].release(); } return capture_e::ok; } int init(const std::string &display_name, int framerate) { if(display_t::init(display_name, framerate)) { return -1; } if(!va::validate(card.fd.el)) { BOOST_LOG(warning) << "Monitor "sv << display_name << " doesn't support hardware encoding. Reverting back to GPU -> RAM -> GPU"sv; return -1; } sequence = 0; return 0; } std::uint64_t sequence; }; } // namespace kms std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) { if(hwdevice_type == mem_type_e::vaapi) { auto disp = std::make_shared(hwdevice_type); if(!disp->init(display_name, framerate)) { return disp; } // In the case of failure, attempt the old method for VAAPI } auto disp = std::make_shared(hwdevice_type); if(disp->init(display_name, framerate)) { return nullptr; } return disp; } /** * On Wayland, it's not possible to determine the position of the monitor on the desktop with KMS. * Wayland does allow applications to query attached monitors on the desktop, * however, the naming scheme is not standardized across implementations. * * As a result, correlating the KMS output to the wayland outputs is guess work at best. * But, it's necessary for absolute mouse coordinates to work. * * This is an ugly hack :( */ void correlate_to_wayland(std::vector &cds) { auto monitors = wl::monitors(); for(auto &monitor : monitors) { std::string_view name = monitor->name; BOOST_LOG(info) << name << ": "sv << monitor->description; // Try to convert names in the format: // {type}-{index} // {index} is n'th occurence of {type} auto index_begin = name.find_last_of('-'); std::uint32_t index; if(index_begin == std::string_view::npos) { index = 1; } else { index = std::max(1, util::from_view(name.substr(index_begin + 1))); } auto type = kms::from_view(name.substr(0, index_begin)); for(auto &card_descriptor : cds) { for(auto &[_, monitor_descriptor] : card_descriptor.crtc_to_monitor) { if(monitor_descriptor.index == index && monitor_descriptor.type == type) { monitor_descriptor.viewport.offset_x = monitor->viewport.offset_x; monitor_descriptor.viewport.offset_y = monitor->viewport.offset_y; // A sanity check, it's guesswork after all. if( monitor_descriptor.viewport.width != monitor->viewport.width || monitor_descriptor.viewport.height != monitor->viewport.height) { BOOST_LOG(warning) << "Mismatch on expected Resolution compared to actual resolution: "sv << monitor_descriptor.viewport.width << 'x' << monitor_descriptor.viewport.height << " vs "sv << monitor->viewport.width << 'x' << monitor->viewport.height; } goto break_for_loop; } } } break_for_loop: BOOST_LOG(verbose) << "Reduced to name: "sv << name << ": "sv << index; } } // A list of names of displays accepted as display_name std::vector kms_display_names() { int count = 0; if(!gbm::create_device) { BOOST_LOG(warning) << "libgbm not initialized"sv; return {}; } kms::conn_type_count_t conn_type_count; std::vector cds; std::vector display_names; fs::path card_dir { "/dev/dri"sv }; for(auto &entry : fs::directory_iterator { card_dir }) { auto file = entry.path().filename(); auto filestring = file.generic_u8string(); if(std::string_view { filestring }.substr(0, 4) != "card"sv) { continue; } kms::card_t card; if(card.init(entry.path().c_str())) { return {}; } auto crtc_to_monitor = kms::map_crtc_to_monitor(card.monitors(conn_type_count)); auto end = std::end(card); for(auto plane = std::begin(card); plane != end; ++plane) { auto fb = card.fb(plane.get()); if(!fb) { BOOST_LOG(error) << "Couldn't get drm fb for plane ["sv << plane->fb_id << "]: "sv << strerror(errno); continue; } if(!fb->handles[0]) { BOOST_LOG(error) << "Couldn't get handle for DRM Framebuffer ["sv << plane->fb_id << "]: Possibly not permitted: do [sudo setcap cap_sys_admin+ep sunshine]"sv; break; } if(card.is_cursor(plane->plane_id)) { continue; } // This appears to return the offset of the monitor auto crtc = card.crtc(plane->crtc_id); if(!crtc) { BOOST_LOG(error) << "Couldn't get crtc info: "sv << strerror(errno); return {}; } auto it = crtc_to_monitor.find(plane->crtc_id); if(it != std::end(crtc_to_monitor)) { it->second.viewport = platf::touch_port_t { (int)crtc->x, (int)crtc->y, (int)crtc->width, (int)crtc->height, }; } kms::env_width = std::max(kms::env_width, (int)(crtc->x + crtc->width)); kms::env_height = std::max(kms::env_height, (int)(crtc->y + crtc->height)); kms::print(plane.get(), fb.get(), crtc.get()); display_names.emplace_back(std::to_string(count++)); } cds.emplace_back(kms::card_descriptor_t { std::move(file), std::move(crtc_to_monitor), }); } if(!wl::init()) { correlate_to_wayland(cds); } // Deduce the full virtual desktop size kms::env_width = 0; kms::env_height = 0; for(auto &card_descriptor : cds) { for(auto &[_, monitor_descriptor] : card_descriptor.crtc_to_monitor) { BOOST_LOG(debug) << "Monitor description"sv; BOOST_LOG(debug) << "Resolution: "sv << monitor_descriptor.viewport.width << 'x' << monitor_descriptor.viewport.height; BOOST_LOG(debug) << "Offset: "sv << monitor_descriptor.viewport.offset_x << 'x' << monitor_descriptor.viewport.offset_y; kms::env_width = std::max(kms::env_width, (int)(monitor_descriptor.viewport.offset_x + monitor_descriptor.viewport.width)); kms::env_height = std::max(kms::env_height, (int)(monitor_descriptor.viewport.offset_y + monitor_descriptor.viewport.height)); } } BOOST_LOG(debug) << "Desktop resolution: "sv << kms::env_width << 'x' << kms::env_height; kms::card_descriptors = std::move(cds); return display_names; } } // namespace platf