New paste Repaste Download
#include <algorithm>
#include <cctype>
#include <cerrno>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <fcntl.h>
#include <filesystem>
#include <fstream>
#include <gbm.h>
#include <iostream>
#include <print>
#include <ranges>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <system_error>
#include <type_traits>
#include <unistd.h>
#include <unordered_map>
#include <utility>
#include <vector>
#include <wayland-client-core.h>
#include <wayland-client-protocol.h>
#include <wayland-util.h>
#ifdef __linux__
#include <linux/openat2.h>
#endif
#include "ext-image-capture-source-v1-client-protocol.h"
#include "ext-image-copy-capture-v1-client-protocol.h"
#include "linux-dmabuf-v1-client-protocol.h"
namespace {
constexpr bool iequals(std::string_view a, std::string_view b) {
    return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [](char ac, char bc) {
               return std::tolower(static_cast<unsigned char>(ac)) ==
                      std::tolower(static_cast<unsigned char>(bc));
           });
}
constexpr auto CAPTURE_FPS = 60;
constexpr auto CAPTURE_SECONDS = 5;
} // namespace
namespace wayland {
struct State {
    struct wl_display *display{};
    struct wl_registry *registry{};
    struct ext_image_copy_capture_manager_v1 *copy_capture_manager{};
    struct ext_image_copy_capture_session_v1 *copy_capture_session{};
    struct ext_image_capture_source_v1 *capture_source{};
    struct ext_output_image_capture_source_manager_v1 *capture_source_manager{};
    struct zwp_linux_dmabuf_v1 *linux_dmabuf{};
    struct OutputInfo {
        std::string name;
        std::string description;
    };
    std::unordered_map<struct wl_output *, OutputInfo> outputs;
    // We do not have owership over _dmabuf_devices.
    struct wl_array *_dmabuf_devices{};
    std::span<dev_t> dmabuf_devices;
    struct DmaBufFormat {
        uint32_t format{0};
        // We do not have owership over _modifiers.
        struct wl_array *_modifiers{};
        std::span<uint64_t> modifiers;
    } dmabuf_format;
    uint32_t width{0};
    uint32_t height{0};
    uint32_t stride{0};
    bool session_ready{false};
    bool frame_ready{false};
    std::vector<uint8_t> frame_data;
    std::error_code error;
    struct DmabufState {
        struct zwp_linux_buffer_params_v1 *params{};
        struct wl_buffer *buffer{};
        int fd{-1};
        void *data{};
        size_t size{0};
        uint32_t stride{0};
        bool buffer_done{false};
        bool buffer_failed{false};
        void *map_data{};
    } dmabuf_state;
    int drm_fd{-1};
    struct gbm_device *gbm_device{};
    struct gbm_bo *gbm_bufferobject{};
    explicit State() = default;
    State(const State &) = delete;
    State &operator=(const State &) = delete;
    State(State &&) = delete;
    State &operator=(State &&) = delete;
    ~State() {
        if (copy_capture_manager != nullptr) {
            ext_image_copy_capture_manager_v1_destroy(copy_capture_manager);
        }
        if (copy_capture_session != nullptr) {
            ext_image_copy_capture_session_v1_destroy(copy_capture_session);
        }
        if (capture_source != nullptr) {
            ext_image_capture_source_v1_destroy(capture_source);
        }
        if (capture_source_manager != nullptr) {
            ext_output_image_capture_source_manager_v1_destroy(capture_source_manager);
        }
        if (linux_dmabuf != nullptr) {
            zwp_linux_dmabuf_v1_destroy(linux_dmabuf);
        }
        for (const auto &[output, _] : outputs) {
            if (output != nullptr) {
                wl_output_destroy(output);
            }
        }
        if (registry != nullptr) {
            wl_registry_destroy(registry);
        }
        if (display != nullptr) {
            wl_display_disconnect(display);
        }
        if (gbm_bufferobject != nullptr) {
            gbm_bo_destroy(gbm_bufferobject);
        }
        if (gbm_device != nullptr) {
            gbm_device_destroy(gbm_device);
        }
    }
};
namespace error {
enum class Errc : uint8_t {
    ListenerAddFailed = 1,
    DmabufBufferFailed,
};
class ErrorCategory : public std::error_category {
public:
    [[nodiscard]] const char *name() const noexcept override { return "Wayland"; }
    [[nodiscard]] std::string message(int ev) const override {
        switch (static_cast<Errc>(ev)) {
        case Errc::ListenerAddFailed:
            return "failed to add listener to Wayland global";
        case Errc::DmabufBufferFailed:
            return "failed to create wl_buffer from dmabuf";
        default:
            // unreachable
            std::terminate();
        }
    }
};
namespace {
inline std::error_code make_error_code(Errc e) { return {static_cast<int>(e), ErrorCategory()}; }
} // namespace
} // namespace error
namespace output {
constexpr struct wl_output_listener listener = {
    .geometry = [](void * /*data*/, struct wl_output * /*output*/, int32_t /*x*/, int32_t /*y*/,
                   int32_t /*physical_width*/, int32_t /*physical_height*/, int32_t /*subpixel*/,
                   const char * /*make*/, const char * /*model*/, int32_t /*transform*/) {},
    .mode = [](void * /*data*/, struct wl_output * /*output*/, uint32_t /*flags*/, int32_t /*width*/,
               int32_t /*height*/, int32_t /*refresh*/) {},
    .done = [](void * /*data*/, struct wl_output * /*output*/) {},
    .scale = [](void * /*data*/, struct wl_output * /*output*/, int32_t /*factor*/) {},
    .name =
        [](void *data, struct wl_output *output, const char *name) {
            auto *state = static_cast<State *>(data);
            try {
                state->outputs.at(output).name = name;
            } catch (const std::out_of_range &) {
                state->error = std::make_error_code(std::errc::result_out_of_range);
            }
        },
    .description =
        [](void *data, struct wl_output *output, const char *description) {
            auto *state = static_cast<State *>(data);
            try {
                state->outputs.at(output).description = description;
            } catch (const std::out_of_range &err) {
                state->error = std::make_error_code(std::errc::result_out_of_range);
            }
        },
};
} // namespace output
namespace registry {
constexpr struct wl_registry_listener listener = {
    .global =
        [](void *data, struct wl_registry *registry, uint32_t name, const char *interface,
           uint32_t /*version*/) {
            using error::Errc;
            auto *state = static_cast<State *>(data);
            if (strcmp(interface, "wl_output") == 0) {
                auto global = static_cast<struct wl_output *>(
                    wl_registry_bind(registry, name, &wl_output_interface, 4));
                state->outputs.insert({global, State::OutputInfo{}});
                const int r = wl_output_add_listener(global, &output::listener, state);
                if (r < 0) {
                    state->error = error::make_error_code(Errc::ListenerAddFailed);
                }
            } else if (strcmp(interface, "ext_image_copy_capture_manager_v1") == 0) {
                state->copy_capture_manager = static_cast<struct ext_image_copy_capture_manager_v1 *>(
                    wl_registry_bind(registry, name, &ext_image_copy_capture_manager_v1_interface, 1));
            } else if (strcmp(interface, "ext_output_image_capture_source_manager_v1") == 0) {
                state->capture_source_manager =
                    static_cast<struct ext_output_image_capture_source_manager_v1 *>(wl_registry_bind(
                        registry, name, &ext_output_image_capture_source_manager_v1_interface, 1));
            } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) {
                state->linux_dmabuf = static_cast<struct zwp_linux_dmabuf_v1 *>(
                    wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, 1));
            }
        },
    .global_remove =
        [](void * /*data*/, struct wl_registry * /*registry*/, uint32_t /*name*/) {
            // If we're already bound to a global that has been removed, the
            // object will remain valid until we destroy it, meaning that any
            // actions we attempt to take will effectively fail, and hence be
            // caught by other error checking logic.
        },
};
} // namespace registry
namespace session {
constexpr struct ext_image_copy_capture_session_v1_listener listener = {
    .buffer_size =
        // NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
    [](void *data, struct ext_image_copy_capture_session_v1 * /*session*/, uint32_t width, uint32_t height) {
        auto *state = static_cast<State *>(data);
        state->width = width;
        state->height = height;
        state->stride = width * 4; // RGBA8888
    },
    .shm_format = [](void * /*data*/, struct ext_image_copy_capture_session_v1 * /*session*/,
                     uint32_t /*format*/) {},
    .dmabuf_device =
        [](void *data, struct ext_image_copy_capture_session_v1 * /*session*/, struct wl_array *device) {
            auto *state = static_cast<State *>(data);
            state->_dmabuf_devices = device;
            state->dmabuf_devices =
                std::span(static_cast<dev_t *>(device->data), device->size / sizeof(dev_t));
        },
    .dmabuf_format =
        [](void *data, struct ext_image_copy_capture_session_v1 * /*session*/, uint32_t format,
           struct wl_array *modifiers) {
            auto *state = static_cast<State *>(data);
            state->dmabuf_format = State::DmaBufFormat{
                .format = format,
                ._modifiers = modifiers,
                .modifiers =
                    std::span(static_cast<uint64_t *>(modifiers->data), modifiers->size / sizeof(uint64_t)),
            };
        },
    .done =
        [](void *data, struct ext_image_copy_capture_session_v1 * /*session*/) {
            auto *state = static_cast<State *>(data);
            state->session_ready = true;
        },
    .stopped = [](void * /*data*/,
                  struct ext_image_copy_capture_session_v1 * /*session*/) { /* TODO: destroy session.  */ },
};
} // namespace session
namespace linux_dmabuf {
constexpr struct zwp_linux_buffer_params_v1_listener params_listener = {
    .created =
        [](void *data, struct zwp_linux_buffer_params_v1 *params, struct wl_buffer *buffer) {
            auto *state = static_cast<wayland::State *>(data);
            state->dmabuf_state.buffer = buffer;
            state->dmabuf_state.buffer_done = true;
            // TODO: Do we need to actually destroy the params here?
            zwp_linux_buffer_params_v1_destroy(params);
        },
    .failed =
        [](void *data, struct zwp_linux_buffer_params_v1 *params) {
            auto *state = static_cast<wayland::State *>(data);
            state->dmabuf_state.buffer_failed = true;
            state->error = error::make_error_code(error::Errc::DmabufBufferFailed);
            // TODO: Do we need to actually destroy the params here?
            zwp_linux_buffer_params_v1_destroy(params);
        }};
} // namespace linux_dmabuf
namespace frame {
constexpr struct ext_image_copy_capture_frame_v1_listener listener = {
    .transform = [](void *, struct ext_image_copy_capture_frame_v1 *, uint32_t) {},
    .damage = [](void *, struct ext_image_copy_capture_frame_v1 *, int32_t, int32_t, int32_t, int32_t) {},
    .presentation_time = [](void *, struct ext_image_copy_capture_frame_v1 *, uint32_t, uint32_t,
                            uint32_t) {},
    .ready =
        [](void *data, struct ext_image_copy_capture_frame_v1 * /*frame*/) {
            auto state = static_cast<wayland::State *>(data);
            state->frame_ready = true;
        },
    .failed =
        [](void *data, struct ext_image_copy_capture_frame_v1 * /*frame*/, uint32_t /*reason*/) {
            // TODO: actually handle failure.
            std::println(std::cerr, "Debug: frame listener failed");
            auto state = static_cast<wayland::State *>(data);
            state->frame_ready = true;
        }};
} // namespace frame
} // namespace wayland
namespace std {
template <> struct is_error_code_enum<wayland::error::Errc> : true_type {};
} // namespace std
int main() {
    using std::cerr, std::print, std::println;
    using namespace wayland;
    namespace fs = std::filesystem;
    namespace ranges = std::ranges;
    int r{0};
    State state;
    state.display = wl_display_connect(nullptr);
    if (state.display == nullptr) {
        println(cerr, "Failed to connect to Wayland display: {}.", strerror(errno));
        return EXIT_FAILURE;
    }
    state.registry = wl_display_get_registry(state.display);
    if (state.registry == nullptr) {
        println(cerr, "Failed to get Wayland registry: {}.", strerror(errno));
        return EXIT_FAILURE;
    }
    r = wl_registry_add_listener(state.registry, &registry::listener, &state);
    if (r < 0) {
        println(cerr, "Failed to add listener to registry.");
        return EXIT_FAILURE;
    }
    r = wl_display_roundtrip(state.display);
    if (r < 0) {
        println(cerr, "Failed to roundtrip Wayland display.");
        return EXIT_FAILURE;
    }
    // Registry has been processed by now.
    if (state.error) {
        println(cerr, "Error: {}.", state.error.message());
        return EXIT_FAILURE;
    }
    if (state.outputs.empty()) {
        println(cerr, "Failed to find any outputs.");
        return EXIT_FAILURE;
    }
    r = wl_display_dispatch(state.display);
    if (r < 0) {
        println(cerr, "Failed to dispatch Wayland display.");
        return EXIT_FAILURE;
    }
    // All callbacks for globals have been processed by now.
    if (state.error) {
        println(cerr, "Error: {}.", state.error.message());
        return EXIT_FAILURE;
    }
    if (state.copy_capture_manager == nullptr) {
        println(cerr, "Compositor doesn't support ext-image-copy-capture-v1.");
        return EXIT_FAILURE;
    }
    if (state.capture_source_manager == nullptr) {
        println(cerr, "Compositor doesn't support ext-image-capture-source-v1.");
        return EXIT_FAILURE;
    }
    if (state.linux_dmabuf == nullptr) {
        println(cerr, "Compositor doesn't support linux-dmabuf-v1.");
        return EXIT_FAILURE;
    }
    println("Outputs:");
    for (const auto &[_, info] : state.outputs) {
        println("  {}: {}", info.name, info.description);
    }
    print("Enter output to capture: ");
    std::string input;
    std::getline(std::cin, input);
    const auto it = ranges::find_if(state.outputs.begin(), state.outputs.end(), [input](const auto &pair) {
        return iequals(input, pair.second.name) || iequals(input, pair.second.description);
    });
    if (it == state.outputs.end()) {
        println(cerr, "Output `{}` does not exist.", input);
        return EXIT_FAILURE;
    }
    auto output = it->first;
    state.capture_source =
        ext_output_image_capture_source_manager_v1_create_source(state.capture_source_manager, output);
    state.copy_capture_session =
        ext_image_copy_capture_manager_v1_create_session(state.copy_capture_manager, state.capture_source, 0);
    r = ext_image_copy_capture_session_v1_add_listener(state.copy_capture_session, &session::listener,
                                                       &state);
    if (r < 0) {
        println(cerr, "Failed to add listener to capture session.");
        return EXIT_FAILURE;
    }
    while (!state.session_ready) {
        r = wl_display_dispatch(state.display);
        if (r < 0) {
            println(cerr, "Failed to dispatch Wayland display.");
            return EXIT_FAILURE;
        }
    }
    println("Width: {}", state.width);
    println("Height: {}", state.height);
    println("Stride: {}", state.stride);
    println("dmabuf devices:");
    for (const auto &device : state.dmabuf_devices) {
        println("  {}", device);
    }
    println("dmabuf format: {}", state.dmabuf_format.format);
    println("dmabuf modifiers:");
    for (const auto &modifier : state.dmabuf_format.modifiers) {
        println("  {}", modifier);
    }
    constexpr uint64_t LAYOUT_MODIFIER = 0; // linear
    if (ranges::find(state.dmabuf_format.modifiers, LAYOUT_MODIFIER) == state.dmabuf_format.modifiers.end()) {
        println(cerr, "Error: Compositor doesn't support linear layout modifier.");
        return EXIT_FAILURE;
    }
    const dev_t drm_dev = state.dmabuf_devices.front();
    const fs::path dri_dir{"/dev/dri"};
    std::string render_node;
    if (!fs::exists(dri_dir) || !fs::is_directory(dri_dir)) {
        std::println(std::cerr, "Directory {} does not exist", dri_dir.string());
        return EXIT_FAILURE;
    }
    bool found = false;
    for (const auto &entry : fs::directory_iterator(dri_dir)) {
        if (!entry.is_character_file()) {
            continue;
        }
        const auto &path = entry.path();
        if (!path.filename().string().starts_with("renderD")) {
            continue;
        }
        struct stat st{};
        if (::stat(path.string().c_str(), &st) == 0 && st.st_rdev == drm_dev) {
            render_node = path.string();
            found = true;
            break;
        }
    }
    if (!found) {
        std::println(std::cerr, "Error: No matching DRM render node found for dev_t `{}`.", drm_dev);
        return EXIT_FAILURE;
    }
    constexpr struct open_how how{.flags = O_RDWR | O_CLOEXEC, .mode = 0, .resolve = 0};
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
    state.drm_fd = static_cast<int>(syscall(SYS_openat2, AT_FDCWD, render_node.c_str(), &how, sizeof(how)));
    if (state.drm_fd < 0) {
        println(cerr, "Error: failed to open DRI render node: {}.", strerror(errno));
        return EXIT_FAILURE;
    }
    state.gbm_device = gbm_create_device(state.drm_fd);
    if (state.gbm_device == nullptr) {
        println(cerr, "Error: failed to create GBM device: {}.", strerror(errno));
        return EXIT_FAILURE;
    }
    const uint64_t *modifiers = state.dmabuf_format.modifiers.data();
    size_t modifier_count = state.dmabuf_format.modifiers.size();
    println("modifier count: {}", modifier_count);
    println("modifiers:");
    for (const auto &modifier : state.dmabuf_format.modifiers) {
        println("  {}", modifier);
    }
    state.gbm_bufferobject = gbm_bo_create_with_modifiers(
        state.gbm_device, state.width, state.height, state.dmabuf_format.format, modifiers, modifier_count);
    if (state.gbm_bufferobject == nullptr) {
        println(cerr, "Error: failed to create GBM buffer {}.", strerror(errno));
        return EXIT_FAILURE;
    }
    state.dmabuf_state.fd = gbm_bo_get_fd(state.gbm_bufferobject);
    if (state.dmabuf_state.fd < 0) {
        println(cerr, "Error: failed to export GBM buffer as DMA-BUF: {}.", strerror(errno));
        gbm_bo_destroy(state.gbm_bufferobject);
        return EXIT_FAILURE;
    }
    state.dmabuf_state.data =
        gbm_bo_map(state.gbm_bufferobject, 0, 0, state.width, state.height, GBM_BO_TRANSFER_READ_WRITE,
                   &state.dmabuf_state.stride, &state.dmabuf_state.map_data);
    if (state.dmabuf_state.data == nullptr) {
        println(cerr, "Error: failed to map GBM buffer object: {}.", strerror(errno));
        return EXIT_FAILURE;
    }
    state.dmabuf_state.params = zwp_linux_dmabuf_v1_create_params(state.linux_dmabuf);
    if (state.dmabuf_state.params == nullptr) {
        println(cerr, "Error: failed to create linux-dmabuf-v1 parameters.");
        return EXIT_FAILURE;
    }
    zwp_linux_buffer_params_v1_add(state.dmabuf_state.params, state.dmabuf_state.fd, 0, 0,
                                   state.dmabuf_state.stride, LAYOUT_MODIFIER, 0);
    r = zwp_linux_buffer_params_v1_add_listener(state.dmabuf_state.params, &linux_dmabuf::params_listener,
                                                &state);
    if (r < 0) {
        println(cerr, "Failed to add listener buffer parameters.");
        return EXIT_FAILURE;
    }
    // TODO: why do we pass a signed integer here?
    zwp_linux_buffer_params_v1_create(state.dmabuf_state.params, static_cast<int32_t>(state.width),
                                      static_cast<int32_t>(state.height), state.dmabuf_format.format, 0);
    while (!state.dmabuf_state.buffer_done && !state.dmabuf_state.buffer_failed) {
        r = wl_display_roundtrip(state.display);
        if (r < 0) {
            println(cerr, "Failed to roundtrip Wayland display.");
            return EXIT_FAILURE;
        }
    }
    if (state.dmabuf_state.buffer == nullptr || state.dmabuf_state.buffer_failed) {
        println(cerr, "Error: {}.", state.error.message());
        return EXIT_FAILURE;
    }
    std::ofstream capture("capture.raw", std::ios::binary);
    if (!capture) {
        println(cerr, "Error: could not open `capture.raw` for writing: {}.", strerror(errno));
        return EXIT_FAILURE;
    }
    for (auto _ : std::views::iota(0, CAPTURE_FPS * CAPTURE_SECONDS)) {
        state.frame_ready = false;
        auto frame = ext_image_copy_capture_session_v1_create_frame(state.copy_capture_session);
        ext_image_copy_capture_frame_v1_add_listener(frame, &frame::listener, &state);
        ext_image_copy_capture_frame_v1_attach_buffer(frame, state.dmabuf_state.buffer);
        // I'm not sure why this takes a signed integer considering that all the other APIs take a
        // signed integer, but casting from an uint32_t to an int32_t is the best we can do.
        ext_image_copy_capture_frame_v1_damage_buffer(frame, 0, 0, static_cast<int32_t>(state.width),
                                                      static_cast<int32_t>(state.height));
        ext_image_copy_capture_frame_v1_capture(frame);
        while (!state.frame_ready) {
            r = wl_display_dispatch(state.display);
            if (r < 0) {
                println(cerr, "Failed to dispatch Wayland display.");
                return EXIT_FAILURE;
            }
        }
        capture.write(static_cast<const char *>(state.dmabuf_state.data),
                      state.dmabuf_state.stride * state.height);
        // TODO: are we responsible for destroying the frame?
        ext_image_copy_capture_frame_v1_destroy(frame);
    }
    return EXIT_SUCCESS;
}
Filename: src/main.cc. Size: 22kb. View raw, , hex, or download this file.

This paste expires on 2025-05-19 22:00:52.249710. Pasted through v1-api.