diff --git a/cmake/compile_definitions/linux.cmake b/cmake/compile_definitions/linux.cmake index 3758884fdce..bd4a328137d 100644 --- a/cmake/compile_definitions/linux.cmake +++ b/cmake/compile_definitions/linux.cmake @@ -136,8 +136,11 @@ if(WAYLAND_FOUND) pkg_check_modules(WAYLAND_PROTOCOLS wayland-protocols REQUIRED) endif() + GEN_WAYLAND("${WAYLAND_PROTOCOLS_DIR}" "stable/linux-dmabuf" linux-dmabuf-v1) + GEN_WAYLAND("${WAYLAND_PROTOCOLS_DIR}" "staging/ext-image-copy-capture" ext-image-copy-capture-v1) + GEN_WAYLAND("${WAYLAND_PROTOCOLS_DIR}" "staging/ext-image-capture-source" ext-image-capture-source-v1) + GEN_WAYLAND("${WAYLAND_PROTOCOLS_DIR}" "staging/ext-foreign-toplevel-list" ext-foreign-toplevel-list-v1) GEN_WAYLAND("${WAYLAND_PROTOCOLS_DIR}" "unstable/xdg-output" xdg-output-unstable-v1) - GEN_WAYLAND("${WAYLAND_PROTOCOLS_DIR}" "unstable/linux-dmabuf" linux-dmabuf-unstable-v1) GEN_WAYLAND("${CMAKE_SOURCE_DIR}/third-party/wlr-protocols" "unstable" wlr-screencopy-unstable-v1) include_directories( diff --git a/src/platform/linux/wayland.cpp b/src/platform/linux/wayland.cpp index c41e660857b..948e424f113 100644 --- a/src/platform/linux/wayland.cpp +++ b/src/platform/linux/wayland.cpp @@ -44,8 +44,19 @@ namespace wl { // Define buffer params listener static const struct zwp_linux_buffer_params_v1_listener params_listener = { - .created = dmabuf_t::buffer_params_created, - .failed = dmabuf_t::buffer_params_failed + .created = CLASS_CALL(dmabuf_t, buffer_params_created), + .failed = CLASS_CALL(dmabuf_t, buffer_params_failed), + }; + + // Define buffer feedback listener + static const struct zwp_linux_dmabuf_feedback_v1_listener feedback_listener = { + .done = CLASS_CALL(dmabuf_t, buffer_feedback_done), + .format_table = CLASS_CALL(dmabuf_t, buffer_feedback_format_table), + .main_device = CLASS_CALL(dmabuf_t, buffer_feedback_main_device), + .tranche_done = CLASS_CALL(dmabuf_t, buffer_feedback_tranche_done), + .tranche_target_device = CLASS_CALL(dmabuf_t, buffer_feedback_tranche_target_device), + .tranche_formats = CLASS_CALL(dmabuf_t, buffer_feedback_tranche_formats), + .tranche_flags = CLASS_CALL(dmabuf_t, buffer_feedback_tranche_flags), }; int display_t::init(const char *display_name) { @@ -173,6 +184,8 @@ namespace wl { screencopy_manager {nullptr}, dmabuf_interface {nullptr}, output_manager {nullptr}, + icc_manager {nullptr}, + icc_source_manager {nullptr}, listener { &CLASS_CALL(interface_t, add_interface), &CLASS_CALL(interface_t, del_interface) @@ -191,6 +204,8 @@ namespace wl { ) { BOOST_LOG(debug) << "Available interface: "sv << interface << '(' << id << ") version "sv << version; + const auto *wayland_backend = std::getenv("SUNSHINE_WAYLAND_BACKEND"); + if (!std::strcmp(interface, wl_output_interface.name)) { BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version; monitors.emplace_back( @@ -203,16 +218,26 @@ namespace wl { output_manager = (zxdg_output_manager_v1 *) wl_registry_bind(registry, id, &zxdg_output_manager_v1_interface, version); this->interface[XDG_OUTPUT] = true; - } else if (!std::strcmp(interface, zwlr_screencopy_manager_v1_interface.name)) { + } else if (!std::strcmp(interface, zwlr_screencopy_manager_v1_interface.name) && (!wayland_backend || !strcmp(wayland_backend, "screencopy"))) { BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version; screencopy_manager = (zwlr_screencopy_manager_v1 *) wl_registry_bind(registry, id, &zwlr_screencopy_manager_v1_interface, version); - this->interface[WLR_EXPORT_DMABUF] = true; + this->interface[WLR_SCREENCOPY] = true; } else if (!std::strcmp(interface, zwp_linux_dmabuf_v1_interface.name)) { BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version; dmabuf_interface = (zwp_linux_dmabuf_v1 *) wl_registry_bind(registry, id, &zwp_linux_dmabuf_v1_interface, version); this->interface[LINUX_DMABUF] = true; + } else if (!std::strcmp(interface, ext_image_copy_capture_manager_v1_interface.name) && (!wayland_backend || !strcmp(wayland_backend, "icc"))) { + BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version; + icc_manager = (ext_image_copy_capture_manager_v1 *) wl_registry_bind(registry, id, &ext_image_copy_capture_manager_v1_interface, version); + + this->interface[EXT_IMAGE_COPY_CAPTURE] = true; + } else if (!std::strcmp(interface, ext_output_image_capture_source_manager_v1_interface.name) && (!wayland_backend || !strcmp(wayland_backend, "icc"))) { + BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version; + icc_source_manager = (ext_output_image_capture_source_manager_v1 *) wl_registry_bind(registry, id, &ext_output_image_capture_source_manager_v1_interface, version); + + this->interface[EXT_IMAGE_CAPTURE_SOURCE] = true; } } @@ -226,28 +251,43 @@ namespace wl { return true; } - // Find render node - drmDevice *devices[16]; - int n = drmGetDevices2(0, devices, 16); - if (n <= 0) { - BOOST_LOG(error) << "No DRM devices found"sv; - return false; - } + // We don't have a drm device from the protocol for some reason + // This should never happen, but this code was already here so it's a fallback now + auto node = config::video.adapter_name; int drm_fd = -1; - for (int i = 0; i < n; i++) { - if (devices[i]->available_nodes & (1 << DRM_NODE_RENDER)) { - drm_fd = open(devices[i]->nodes[DRM_NODE_RENDER], O_RDWR); - if (drm_fd >= 0) { - break; + if (!node.empty()) { + // Prefer adapter_name from config + drm_fd = open(node.c_str(), O_RDWR); + } + + if (drm_fd < 0) { + // Find any render node and hope that works + drmDevice *devices[16]; + int n = drmGetDevices2(0, devices, 16); + if (n <= 0) { + BOOST_LOG(error) << "No DRM devices found"sv; + return false; + } + + int drm_fd = -1; + for (int i = 0; i < n; i++) { + if (devices[i]->available_nodes & (1 << DRM_NODE_RENDER)) { + drm_fd = open(devices[i]->nodes[DRM_NODE_RENDER], O_RDWR); + if (drm_fd >= 0) { + node = std::string {devices[i]->nodes[DRM_NODE_RENDER]}; + break; + } } } + drmFreeDevices(devices, n); } - drmFreeDevices(devices, n); if (drm_fd < 0) { - BOOST_LOG(error) << "Failed to open DRM render node"sv; + BOOST_LOG(error) << "Failed to open DRM render node: " << node; return false; + } else { + BOOST_LOG(info) << "Using DRM render node: " << node; } gbm_device = gbm_create_device(drm_fd); @@ -289,31 +329,48 @@ namespace wl { } // Start capture - void dmabuf_t::listen( + void dmabuf_t::screencopy_create( zwlr_screencopy_manager_v1 *screencopy_manager, zwp_linux_dmabuf_v1 *dmabuf_interface, - wl_output *output, - bool blend_cursor + wl_output *output ) { this->dmabuf_interface = dmabuf_interface; // Reset state shm_info.supported = false; dmabuf_info.supported = false; + screencopy_session = {}; + + screencopy_session.manager = screencopy_manager; + screencopy_session.output = output; + + // Get feedback for correct drm device to use + auto feedback = zwp_linux_dmabuf_v1_get_default_feedback(dmabuf_interface); + zwp_linux_dmabuf_feedback_v1_set_user_data(feedback, this); + zwp_linux_dmabuf_feedback_v1_add_listener(feedback, &feedback_listener, this); + } - // Create new frame - auto frame = zwlr_screencopy_manager_v1_capture_output( - screencopy_manager, - blend_cursor ? 1 : 0, - output - ); + void dmabuf_t::screencopy_capture(bool blend_cursor) { + if (!screencopy_session.done) { + status = REINIT; + return; + } else if (screencopy_session.manager && screencopy_session.output && screencopy_session.done && !screencopy_session.frame) { + // Create new frame + auto frame = zwlr_screencopy_manager_v1_capture_output( + screencopy_session.manager, + blend_cursor ? 1 : 0, + screencopy_session.output + ); - // Store frame data pointer for callbacks - zwlr_screencopy_frame_v1_set_user_data(frame, this); + // Store frame data pointer for callbacks + zwlr_screencopy_frame_v1_set_user_data(frame, this); - // Add listener - zwlr_screencopy_frame_v1_add_listener(frame, &listener, this); + // Add listener + zwlr_screencopy_frame_v1_add_listener(frame, &listener, this); - status = WAITING; + screencopy_session.frame = frame; + + status = WAITING; + } } dmabuf_t::~dmabuf_t() { @@ -410,9 +467,8 @@ namespace wl { // Create linux-dmabuf buffer auto params = zwp_linux_dmabuf_v1_create_params(dmabuf_interface); zwp_linux_buffer_params_v1_add(params, fd, 0, 0, stride, modifier >> 32, modifier & 0xffffffff); - - // Add listener for buffer creation - zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, frame); + zwp_linux_buffer_params_v1_set_user_data(params, this); + zwp_linux_buffer_params_v1_add_listener(params, ¶ms_listener, this); // Create Wayland buffer (async - callback will handle copy) zwp_linux_buffer_params_v1_create(params, dmabuf_info.width, dmabuf_info.height, dmabuf_info.format, 0); @@ -445,37 +501,87 @@ namespace wl { // Buffer params created callback void dmabuf_t::buffer_params_created( - void *data, - struct zwp_linux_buffer_params_v1 *params, + zwp_linux_buffer_params_v1 *params, struct wl_buffer *buffer ) { - auto frame = static_cast(data); - auto self = static_cast(zwlr_screencopy_frame_v1_get_user_data(frame)); - // Store for cleanup - self->current_wl_buffer = buffer; + current_wl_buffer = buffer; // Start the actual copy zwp_linux_buffer_params_v1_destroy(params); - zwlr_screencopy_frame_v1_copy(frame, buffer); + zwlr_screencopy_frame_v1_copy(screencopy_session.frame, buffer); } // Buffer params failed callback void dmabuf_t::buffer_params_failed( - void *data, - struct zwp_linux_buffer_params_v1 *params + zwp_linux_buffer_params_v1 *params ) { - auto frame = static_cast(data); - auto self = static_cast(zwlr_screencopy_frame_v1_get_user_data(frame)); - BOOST_LOG(error) << "Failed to create buffer from params"sv; - self->cleanup_gbm(); + cleanup_gbm(); zwp_linux_buffer_params_v1_destroy(params); - zwlr_screencopy_frame_v1_destroy(frame); - self->status = REINIT; + zwlr_screencopy_frame_v1_destroy(screencopy_session.frame); + status = REINIT; + } + + void dmabuf_t::buffer_feedback_done(zwp_linux_dmabuf_feedback_v1 *feedback) { + screencopy_session.done = true; + status = WAITING; } + void dmabuf_t::buffer_feedback_format_table(zwp_linux_dmabuf_feedback_v1 *feedback, int fd, int size) {}; + + void dmabuf_t::buffer_feedback_main_device(zwp_linux_dmabuf_feedback_v1 *feedback, struct wl_array *device) { + if (device->size != sizeof(dev_t)) { + BOOST_LOG(error) << "Screencopy DMA-BUF device size mismatch: " << device->size << " != " << sizeof(dev_t); + status = REINIT; + return; + } + + dev_t device_id; + memcpy(&device_id, device->data, sizeof(dev_t)); + + drmDevice *drm_device {nullptr}; + if (drmGetDeviceFromDevId(device_id, 0, &drm_device) != 0) { + BOOST_LOG(error) << "Screencopy failed to open DRM device"; + status = REINIT; + return; + } + + char *node {nullptr}; + if (drm_device->available_nodes & (1 << DRM_NODE_RENDER)) { + node = drm_device->nodes[DRM_NODE_RENDER]; + } else if (drm_device->available_nodes & (1 << DRM_NODE_PRIMARY)) { + node = drm_device->nodes[DRM_NODE_PRIMARY]; + } else { + BOOST_LOG(error) << "Screencopy failed to find DRM node"; + status = REINIT; + return; + } + + int drm_fd = open(node, O_RDWR); + if (drm_fd < 0) { + BOOST_LOG(error) << "Screencopy failed to open DRM render node"; + status = REINIT; + return; + } + + gbm_device = gbm_create_device(drm_fd); + if (!gbm_device) { + close(drm_fd); + BOOST_LOG(error) << "Screencopy failed to create GBM device"; + status = REINIT; + return; + } + + BOOST_LOG(info) << "Screencopy DMA-BUF device: " << node; + } + + void dmabuf_t::buffer_feedback_tranche_done(zwp_linux_dmabuf_feedback_v1 *feedback) {}; + void dmabuf_t::buffer_feedback_tranche_target_device(zwp_linux_dmabuf_feedback_v1 *feedback, struct wl_array *device) {}; + void dmabuf_t::buffer_feedback_tranche_formats(zwp_linux_dmabuf_feedback_v1 *feedback, struct wl_array *formats) {}; + void dmabuf_t::buffer_feedback_tranche_flags(zwp_linux_dmabuf_feedback_v1 *feedback, int flags) {}; + // Ready callback void dmabuf_t::ready( zwlr_screencopy_frame_v1 *frame, @@ -498,6 +604,7 @@ namespace wl { cleanup_gbm(); zwlr_screencopy_frame_v1_destroy(frame); + screencopy_session.frame = nullptr; status = READY; } @@ -575,6 +682,319 @@ namespace wl { return !validated; } + // ext-image-copy-capture implementation starts here + + static const struct zwp_linux_buffer_params_v1_listener icc_params_listener = { + .created = CLASS_CALL(dmabuf_t, icc_buffer_params_created), + .failed = CLASS_CALL(dmabuf_t, icc_buffer_params_failed), + }; + + static const struct ext_image_copy_capture_session_v1_listener icc_session_listener = { + .buffer_size = CLASS_CALL(dmabuf_t, icc_session_buffer_size), + .shm_format = CLASS_CALL(dmabuf_t, icc_session_shm_format), + .dmabuf_device = CLASS_CALL(dmabuf_t, icc_session_dmabuf_device), + .dmabuf_format = CLASS_CALL(dmabuf_t, icc_session_dmabuf_format), + .done = CLASS_CALL(dmabuf_t, icc_session_done), + .stopped = CLASS_CALL(dmabuf_t, icc_session_stopped), + }; + + static const struct ext_image_copy_capture_frame_v1_listener icc_frame_listener = { + .transform = CLASS_CALL(dmabuf_t, icc_frame_transform), + .damage = CLASS_CALL(dmabuf_t, icc_frame_damage), + .presentation_time = CLASS_CALL(dmabuf_t, icc_frame_presentation_time), + .ready = CLASS_CALL(dmabuf_t, icc_frame_ready), + .failed = CLASS_CALL(dmabuf_t, icc_frame_failed), + }; + + void dmabuf_t::icc_create( + ext_image_copy_capture_manager_v1 *manager, + ext_output_image_capture_source_manager_v1 *source_manager, + zwp_linux_dmabuf_v1 *dmabuf_interface, + wl_output *output, + bool blend_cursor + ) { + this->dmabuf_interface = dmabuf_interface; + icc_cleanup(); + icc_session.cursor = blend_cursor; + + auto icc_source = ext_output_image_capture_source_manager_v1_create_source(source_manager, output); + auto session = ext_image_copy_capture_manager_v1_create_session(manager, icc_source, blend_cursor ? EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_OPTIONS_PAINT_CURSORS : 0); + ext_image_copy_capture_session_v1_set_user_data(session, this); + ext_image_copy_capture_session_v1_add_listener(session, &icc_session_listener, this); + icc_session.session = session; + status = WAITING; + } + + void dmabuf_t::icc_capture(bool blend_cursor) { + if (!icc_session.session || blend_cursor != icc_session.cursor) { + status = REINIT; + return; + } else if (icc_session.done && !icc_session.frame) { + auto frame = ext_image_copy_capture_session_v1_create_frame(icc_session.session); + icc_session.frame = frame; + ext_image_copy_capture_frame_v1_set_user_data(frame, this); + ext_image_copy_capture_frame_v1_add_listener(frame, &icc_frame_listener, this); + icc_create_and_copy_dmabuf(frame); + status = WAITING; + } + } + + void dmabuf_t::icc_create_and_copy_dmabuf(ext_image_copy_capture_frame_v1 *frame) { + if (!init_gbm()) { + BOOST_LOG(error) << "Failed to initialize GBM"sv; + ext_image_copy_capture_frame_v1_destroy(frame); + status = REINIT; + return; + } + + if (icc_session.width == 0 || icc_session.height == 0 || icc_session.format == 0) { + BOOST_LOG(error) << "Invalid capture session parameters"sv; + ext_image_copy_capture_frame_v1_destroy(frame); + status = REINIT; + return; + } + + current_bo = gbm_bo_create(gbm_device, icc_session.width, icc_session.height, icc_session.format, GBM_BO_USE_RENDERING); + if (!current_bo) { + BOOST_LOG(error) << "Failed to create GBM buffer"sv; + ext_image_copy_capture_frame_v1_destroy(frame); + status = REINIT; + return; + } + + int fd = gbm_bo_get_fd(current_bo); + if (fd < 0) { + BOOST_LOG(error) << "Failed to get buffer FD"sv; + gbm_bo_destroy(current_bo); + current_bo = nullptr; + ext_image_copy_capture_frame_v1_destroy(frame); + status = REINIT; + return; + } + + uint32_t stride = gbm_bo_get_stride(current_bo); + auto next_frame = get_next_frame(); + next_frame->sd.fds[0] = fd; + next_frame->sd.pitches[0] = stride; + next_frame->sd.offsets[0] = 0; + next_frame->sd.modifier = icc_session.num_modifiers > 0 ? icc_session.modifiers[icc_session.modifier] : DRM_FORMAT_MOD_LINEAR; + next_frame->sd.fourcc = icc_session.format; + next_frame->sd.width = icc_session.width; + next_frame->sd.height = icc_session.height; + + auto params = zwp_linux_dmabuf_v1_create_params(dmabuf_interface); + zwp_linux_buffer_params_v1_add(params, fd, 0, 0, stride, next_frame->sd.modifier >> 32, next_frame->sd.modifier & 0xffffffff); + zwp_linux_buffer_params_v1_set_user_data(params, this); + zwp_linux_buffer_params_v1_add_listener(params, &icc_params_listener, this); + zwp_linux_buffer_params_v1_create(params, icc_session.width, icc_session.height, icc_session.format, 0); + } + + void dmabuf_t::icc_buffer_params_created( + zwp_linux_buffer_params_v1 *params, + struct wl_buffer *buffer + ) { + current_wl_buffer = buffer; + + zwp_linux_buffer_params_v1_destroy(params); + if (icc_session.frame) { + ext_image_copy_capture_frame_v1_attach_buffer(icc_session.frame, buffer); + ext_image_copy_capture_frame_v1_damage_buffer(icc_session.frame, 0, 0, icc_session.width, icc_session.height); + ext_image_copy_capture_frame_v1_capture(icc_session.frame); + } + } + + void dmabuf_t::icc_buffer_params_failed( + zwp_linux_buffer_params_v1 *params + ) { + BOOST_LOG(error) << "Failed to create buffer for ICC"sv; + + cleanup_gbm(); + status = REINIT; + + zwp_linux_buffer_params_v1_destroy(params); + if (icc_session.frame) { + ext_image_copy_capture_frame_v1_destroy(icc_session.frame); + icc_session.frame = nullptr; + } + } + + void dmabuf_t::icc_session_buffer_size(ext_image_copy_capture_session_v1 *session, std::uint32_t width, std::uint32_t height) { + icc_session.width = width; + icc_session.height = height; + BOOST_LOG(info) << "ICC session buffer size: " << width << 'x' << height; + } + + void dmabuf_t::icc_session_shm_format(ext_image_copy_capture_session_v1 *session, std::uint32_t format) { + shm_info.supported = true; + shm_info.format = format; + BOOST_LOG(debug) << "ICC session SHM format: " << format; + } + + void dmabuf_t::icc_session_dmabuf_device(ext_image_copy_capture_session_v1 *session, struct wl_array *device) { + if (device->size != sizeof(dev_t)) { + BOOST_LOG(error) << "ICC session DMA-BUF device size mismatch: " << device->size << " != " << sizeof(dev_t); + status = REINIT; + return; + } + + dev_t device_id; + memcpy(&device_id, device->data, sizeof(dev_t)); + + drmDevice *drm_device {nullptr}; + if (drmGetDeviceFromDevId(device_id, 0, &drm_device) != 0) { + BOOST_LOG(info) << "ICC session failed to open DRM device"; + status = REINIT; + return; + } + + char *node {nullptr}; + if (drm_device->available_nodes & (1 << DRM_NODE_RENDER)) { + node = drm_device->nodes[DRM_NODE_RENDER]; + } else if (drm_device->available_nodes & (1 << DRM_NODE_PRIMARY)) { + node = drm_device->nodes[DRM_NODE_PRIMARY]; + } else { + BOOST_LOG(info) << "ICC session failed to find DRM node"; + status = REINIT; + return; + } + + int drm_fd = open(node, O_RDWR); + if (drm_fd < 0) { + BOOST_LOG(error) << "ICC session failed to open DRM render node"; + status = REINIT; + return; + } + + gbm_device = gbm_create_device(drm_fd); + if (!gbm_device) { + close(drm_fd); + BOOST_LOG(error) << "ICC session failed to create GBM device"; + status = REINIT; + return; + } + + BOOST_LOG(info) << "ICC session DMA-BUF device: " << node; + } + + void dmabuf_t::icc_session_dmabuf_format(ext_image_copy_capture_session_v1 *session, std::uint32_t format, struct wl_array *modifiers) { + char fourcc[5] {0}; + memcpy(&fourcc, &format, 4); + BOOST_LOG(info) << "ICC session DMA-BUF format: " << std::hex << format << " = " << fourcc; + if (fourcc[0] != 'X') { + return; + } + icc_session.dmabuf_supported = true; + icc_session.format = format; + if (icc_session.modifiers != nullptr) { + free(icc_session.modifiers); + icc_session.modifiers = nullptr; + } + icc_session.num_modifiers = modifiers->size / sizeof(uint64_t); + icc_session.modifiers = (uint64_t *) calloc(icc_session.num_modifiers, sizeof(uint64_t)); + icc_session.modifier = 0; + if (icc_session.modifiers != nullptr) { + memcpy(icc_session.modifiers, modifiers->data, modifiers->size); + for (size_t i = 0; i < icc_session.num_modifiers; ++i) { + BOOST_LOG(info) << " Modifier " << std::setw(2) << i << ": " << std::hex << icc_session.modifiers[i]; + if (icc_session.modifiers[icc_session.modifier] == DRM_FORMAT_MOD_INVALID) { + icc_session.modifier = i; + } + } + } + BOOST_LOG(info) << "Selected modifier " << icc_session.modifier; + } + + void dmabuf_t::icc_session_done(ext_image_copy_capture_session_v1 *session) { + BOOST_LOG(debug) << "ICC session constraints done"; + + if (!icc_session.session) { + BOOST_LOG(error) << "No ICC session"sv; + status = REINIT; + return; + } + + if (icc_session.width == 0 || icc_session.height == 0) { + BOOST_LOG(error) << "Invalid ICC buffer size"sv; + status = REINIT; + return; + } + + if (!icc_session.dmabuf_supported) { + BOOST_LOG(error) << "ICC session does not support dmabuf"sv; + status = REINIT; + return; + } + + icc_session.done = true; + } + + void dmabuf_t::icc_cleanup() { + if (icc_session.modifiers) { + free(icc_session.modifiers); + } + icc_session = {}; + } + + void dmabuf_t::icc_session_stopped(ext_image_copy_capture_session_v1 *session) { + BOOST_LOG(warning) << "ICC session stopped"sv; + ext_image_copy_capture_session_v1_destroy(session); + icc_cleanup(); + status = REINIT; + } + + void dmabuf_t::icc_frame_transform(ext_image_copy_capture_frame_v1 *frame, std::uint32_t transform) { + // TODO: this could probably do something + BOOST_LOG(debug) << "ICC transform: " << transform; + } + + void dmabuf_t::icc_frame_damage(ext_image_copy_capture_frame_v1 *frame, std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) { + BOOST_LOG(debug) << "ICC damage: " << x << ',' << y << ' ' << width << 'x' << height; + } + + void dmabuf_t::icc_frame_presentation_time(ext_image_copy_capture_frame_v1 *frame, std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec) { + uint64_t timestamp = ((uint64_t) tv_sec_hi << 32) | tv_sec_lo; + BOOST_LOG(debug) << "ICC presentation time: " << timestamp << '.' << tv_nsec; + } + + void dmabuf_t::icc_frame_ready(ext_image_copy_capture_frame_v1 *frame) { + BOOST_LOG(debug) << "ICC frame ready"sv; + current_frame->destroy(); + current_frame = get_next_frame(); + if (current_wl_buffer) { + wl_buffer_destroy(current_wl_buffer); + current_wl_buffer = nullptr; + } + cleanup_gbm(); + ext_image_copy_capture_frame_v1_destroy(frame); + icc_session.frame = nullptr; + status = READY; + } + + void dmabuf_t::icc_frame_failed(ext_image_copy_capture_frame_v1 *frame, std::uint32_t reason) { + if (reason == EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED || icc_session.fail_count > icc_session.num_modifiers) { + BOOST_LOG(error) << "ICC frame failed: Session has stopped or too many failures, restarting"; + cleanup_gbm(); + auto next_frame = get_next_frame(); + next_frame->destroy(); + ext_image_copy_capture_frame_v1_destroy(frame); + icc_cleanup(); + status = REINIT; + return; + } else if (reason == EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS) { + BOOST_LOG(error) << "ICC frame failed: Buffer constraints"; + } else { + BOOST_LOG(error) << "ICC frame failed: Unknown reason"; + } + ++icc_session.fail_count; + if (icc_session.num_modifiers > 1) { + icc_session.modifier = (icc_session.modifier + 1) % icc_session.num_modifiers; + BOOST_LOG(info) << "Retrying with modifier " << icc_session.modifier; + } + ext_image_copy_capture_frame_v1_destroy(frame); + icc_session.frame = nullptr; + icc_capture(icc_session.cursor); + } + } // namespace wl #pragma GCC diagnostic pop diff --git a/src/platform/linux/wayland.h b/src/platform/linux/wayland.h index 08d5acbd543..3baa70ac1aa 100644 --- a/src/platform/linux/wayland.h +++ b/src/platform/linux/wayland.h @@ -8,7 +8,9 @@ #include #ifdef SUNSHINE_BUILD_WAYLAND - #include + #include + #include + #include #include #include #endif @@ -49,9 +51,18 @@ namespace wl { dmabuf_t &operator=(const dmabuf_t &) = delete; dmabuf_t &operator=(dmabuf_t &&) = delete; - void listen(zwlr_screencopy_manager_v1 *screencopy_manager, zwp_linux_dmabuf_v1 *dmabuf_interface, wl_output *output, bool blend_cursor = false); - static void buffer_params_created(void *data, struct zwp_linux_buffer_params_v1 *params, struct wl_buffer *wl_buffer); - static void buffer_params_failed(void *data, struct zwp_linux_buffer_params_v1 *params); + // wlr-screencopy based capture + void screencopy_create(zwlr_screencopy_manager_v1 *screencopy_manager, zwp_linux_dmabuf_v1 *dmabuf_interface, wl_output *output); + void screencopy_capture(bool blend_cursor = false); + void buffer_params_created(zwp_linux_buffer_params_v1 *params, struct wl_buffer *wl_buffer); + void buffer_params_failed(zwp_linux_buffer_params_v1 *params); + void buffer_feedback_done(zwp_linux_dmabuf_feedback_v1 *feedback); + void buffer_feedback_format_table(zwp_linux_dmabuf_feedback_v1 *feedback, int fd, int size); + void buffer_feedback_main_device(zwp_linux_dmabuf_feedback_v1 *feedback, struct wl_array *device); + void buffer_feedback_tranche_done(zwp_linux_dmabuf_feedback_v1 *feedback); + void buffer_feedback_tranche_target_device(zwp_linux_dmabuf_feedback_v1 *feedback, struct wl_array *device); + void buffer_feedback_tranche_formats(zwp_linux_dmabuf_feedback_v1 *feedback, struct wl_array *formats); + void buffer_feedback_tranche_flags(zwp_linux_dmabuf_feedback_v1 *feedback, int flags); void buffer(zwlr_screencopy_frame_v1 *frame, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t stride); void linux_dmabuf(zwlr_screencopy_frame_v1 *frame, std::uint32_t format, std::uint32_t width, std::uint32_t height); void buffer_done(zwlr_screencopy_frame_v1 *frame); @@ -60,6 +71,24 @@ namespace wl { void ready(zwlr_screencopy_frame_v1 *frame, std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec); void failed(zwlr_screencopy_frame_v1 *frame); + // ext-image-copy-capture based capture + void icc_create(ext_image_copy_capture_manager_v1 *manager, ext_output_image_capture_source_manager_v1 *source_manager, zwp_linux_dmabuf_v1 *dmabuf_interface, wl_output *output, bool blend_cursor = false); + void icc_capture(bool blend_cursor = false); + void icc_cleanup(); + void icc_buffer_params_created(zwp_linux_buffer_params_v1 *params, struct wl_buffer *buffer); + void icc_buffer_params_failed(zwp_linux_buffer_params_v1 *params); + void icc_session_buffer_size(ext_image_copy_capture_session_v1 *session, std::uint32_t width, std::uint32_t height); + void icc_session_shm_format(ext_image_copy_capture_session_v1 *session, std::uint32_t format); + void icc_session_dmabuf_device(ext_image_copy_capture_session_v1 *session, struct wl_array *device); + void icc_session_dmabuf_format(ext_image_copy_capture_session_v1 *session, std::uint32_t format, struct wl_array *modifiers); + void icc_session_done(ext_image_copy_capture_session_v1 *session); + void icc_session_stopped(ext_image_copy_capture_session_v1 *session); + void icc_frame_transform(ext_image_copy_capture_frame_v1 *frame, std::uint32_t transform); + void icc_frame_damage(ext_image_copy_capture_frame_v1 *frame, std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height); + void icc_frame_presentation_time(ext_image_copy_capture_frame_v1 *frame, std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec); + void icc_frame_ready(ext_image_copy_capture_frame_v1 *frame); + void icc_frame_failed(ext_image_copy_capture_frame_v1 *frame, std::uint32_t reason); + frame_t *get_next_frame() { return current_frame == &frames[0] ? &frames[1] : &frames[0]; } @@ -73,6 +102,7 @@ namespace wl { bool init_gbm(); void cleanup_gbm(); void create_and_copy_dmabuf(zwlr_screencopy_frame_v1 *frame); + void icc_create_and_copy_dmabuf(ext_image_copy_capture_frame_v1 *frame); zwp_linux_dmabuf_v1 *dmabuf_interface {nullptr}; @@ -91,6 +121,28 @@ namespace wl { std::uint32_t height; } dmabuf_info; + struct { + zwlr_screencopy_manager_v1 *manager {nullptr}; + zwlr_screencopy_frame_v1 *frame {nullptr}; + wl_output *output {nullptr}; + bool done {false}; + } screencopy_session; + + struct { + ext_image_copy_capture_session_v1 *session {nullptr}; + ext_image_copy_capture_frame_v1 *frame {nullptr}; + std::uint32_t width {0}; + std::uint32_t height {0}; + std::uint32_t format {0}; + std::uint64_t modifier {0}; + std::uint64_t num_modifiers {0}; + std::uint64_t *modifiers; + bool dmabuf_supported {false}; + bool cursor {false}; + bool done {false}; + std::uint32_t fail_count; + } icc_session; + struct gbm_device *gbm_device {nullptr}; struct gbm_bo *current_bo {nullptr}; struct wl_buffer *current_wl_buffer {nullptr}; @@ -139,8 +191,10 @@ namespace wl { public: enum interface_e { XDG_OUTPUT, ///< xdg-output - WLR_EXPORT_DMABUF, ///< screencopy manager + WLR_SCREENCOPY, ///< wlr-screencopy manager LINUX_DMABUF, ///< linux-dmabuf protocol + EXT_IMAGE_COPY_CAPTURE, ///< ext-image-copy-capture manager + EXT_IMAGE_CAPTURE_SOURCE, ///< ext-image-capture-source manager MAX_INTERFACES, ///< Maximum number of interfaces }; @@ -161,6 +215,8 @@ namespace wl { zwlr_screencopy_manager_v1 *screencopy_manager {nullptr}; zwp_linux_dmabuf_v1 *dmabuf_interface {nullptr}; zxdg_output_manager_v1 *output_manager {nullptr}; + ext_image_copy_capture_manager_v1 *icc_manager {nullptr}; + ext_output_image_capture_source_manager_v1 *icc_source_manager {nullptr}; private: void add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version); diff --git a/src/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp index a8ec4a2fe15..bab164667de 100644 --- a/src/platform/linux/wlgrab.cpp +++ b/src/platform/linux/wlgrab.cpp @@ -45,18 +45,34 @@ namespace wl { return -1; } - if (!interface[wl::interface_t::WLR_EXPORT_DMABUF]) { - BOOST_LOG(error) << "Missing Wayland wire for wlr-export-dmabuf"sv; + if (!interface[wl::interface_t::WLR_SCREENCOPY] && !interface[wl::interface_t::EXT_IMAGE_COPY_CAPTURE]) { + BOOST_LOG(warning) << "Missing Wayland wire for wlr-screencopy AND ext-image-copy-capture"sv; return -1; } + // find all wayland outputs again if a new one was just created + for (auto &monitor : interface.monitors) { + monitor->listen(interface.output_manager); + } + + display.roundtrip(); + auto monitor = interface.monitors[0].get(); if (!display_name.empty()) { - auto streamedMonitor = util::from_view(display_name); - - if (streamedMonitor >= 0 && streamedMonitor < interface.monitors.size()) { - monitor = interface.monitors[streamedMonitor].get(); + // support both display index and connector name + if (std::all_of(display_name.begin(), display_name.end(), ::isdigit)) { + auto streamedMonitor = util::from_view(display_name); + if (streamedMonitor >= 0 && streamedMonitor < interface.monitors.size()) { + monitor = interface.monitors[streamedMonitor].get(); + } + } else { + auto it = std::find_if(interface.monitors.begin(), interface.monitors.end(), [&display_name](std::unique_ptr &m) { + return m.get()->name == display_name; + }); + if (it != interface.monitors.end()) { + monitor = (*it).get(); + } } } @@ -105,8 +121,20 @@ namespace wl { inline platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) { auto to = std::chrono::steady_clock::now() + timeout; - // Dispatch events until we get a new frame or the timeout expires - dmabuf.listen(interface.screencopy_manager, interface.dmabuf_interface, output, cursor); + // Prefer ext-image-copy-capture over wlr-screencopy + if (interface.icc_manager && interface.icc_source_manager) { + if (dmabuf.status == dmabuf_t::REINIT) { + BOOST_LOG(info) << "Creating ext-image-copy-capture session"; + dmabuf.icc_create(interface.icc_manager, interface.icc_source_manager, interface.dmabuf_interface, output, cursor); + } + dmabuf.icc_capture(cursor); + } else { + if (dmabuf.status == dmabuf_t::REINIT) { + BOOST_LOG(info) << "Creating wlr-screencopy session"; + dmabuf.screencopy_create(interface.screencopy_manager, interface.dmabuf_interface, output); + } + dmabuf.screencopy_capture(cursor); + } do { auto remaining_time_ms = std::chrono::duration_cast(to - std::chrono::steady_clock::now()); if (remaining_time_ms.count() < 0 || !display.dispatch(remaining_time_ms)) { @@ -424,8 +452,8 @@ namespace platf { return {}; } - if (!interface[wl::interface_t::WLR_EXPORT_DMABUF]) { - BOOST_LOG(warning) << "Missing Wayland wire for wlr-export-dmabuf"sv; + if (!interface[wl::interface_t::WLR_SCREENCOPY] && !interface[wl::interface_t::EXT_IMAGE_COPY_CAPTURE]) { + BOOST_LOG(warning) << "Missing Wayland wire for wlr-screencopy AND ext-image-copy-capture"sv; return {}; } diff --git a/src/video.cpp b/src/video.cpp index 7487e1278e6..c99486a0159 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -1171,6 +1171,14 @@ namespace video { return; } } + + // If output_name is a connector name (e.g. "DP-2") rather than a numeric + // index, it won't match the numeric display_names list. Add it directly + // so display_t::init() can resolve the connector name. + if (!output_name.empty()) { + display_names.emplace_back(output_name); + current_display_index = display_names.size() - 1; + } } } diff --git a/src_assets/common/assets/web/configs/tabs/Advanced.vue b/src_assets/common/assets/web/configs/tabs/Advanced.vue index d63d095f2d9..cfa8ba27914 100644 --- a/src_assets/common/assets/web/configs/tabs/Advanced.vue +++ b/src_assets/common/assets/web/configs/tabs/Advanced.vue @@ -65,13 +65,13 @@ const config = ref(props.config)