/**
 * @file src/platform/linux/x11grab.cpp
 * @brief Definitions for x11 capture.
 */
// standard includes
#include <fstream>
#include <ranges>
#include <thread>

// plaform includes
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/Xfixes.h>
#include <X11/extensions/Xrandr.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <xcb/shm.h>
#include <xcb/xfixes.h>

// local includes
#include "cuda.h"
#include "graphics.h"
#include "misc.h"
#include "src/config.h"
#include "src/globals.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/task_pool.h"
#include "src/video.h"
#include "vaapi.h"
#include "x11grab.h"

using namespace std::literals;

namespace platf {
  /**
   * @brief Load XCB entry points used by the X11 capture backend.
   *
   * @return 0 when required XCB symbols are loaded; nonzero otherwise.
   */
  int load_xcb();
  /**
   * @brief Load X11 entry points used by the X11 capture backend.
   *
   * @return 0 when required X11 symbols are loaded; nonzero otherwise.
   */
  int load_x11();

  namespace x11 {
/**
 * @def _FN(x, ret, args)
 * @brief Macro for FN.
 */
#define _FN(x, ret, args) \
  /** \
   * @brief Function pointer type for the dynamically loaded X11 entry point. \
   */ \
  typedef ret(*x##_fn) args; \
  /** \
   * @brief Loaded X11 entry point pointer. \
   */ \
  static x##_fn x

    _FN(GetImage, XImage *, (Display * display, Drawable d, int x, int y, unsigned int width, unsigned int height, unsigned long plane_mask, int format));

    _FN(OpenDisplay, Display *, (_Xconst char *display_name));
    _FN(GetWindowAttributes, Status, (Display * display, Window w, XWindowAttributes *window_attributes_return));

    _FN(CloseDisplay, int, (Display * display));
    _FN(Free, int, (void *data));
    _FN(InitThreads, Status, (void) );

    namespace rr {
      _FN(GetScreenResources, XRRScreenResources *, (Display * dpy, Window window));
      _FN(GetOutputInfo, XRROutputInfo *, (Display * dpy, XRRScreenResources *resources, RROutput output));
      _FN(GetCrtcInfo, XRRCrtcInfo *, (Display * dpy, XRRScreenResources *resources, RRCrtc crtc));
      _FN(FreeScreenResources, void, (XRRScreenResources * resources));
      _FN(FreeOutputInfo, void, (XRROutputInfo * outputInfo));
      _FN(FreeCrtcInfo, void, (XRRCrtcInfo * crtcInfo));

      static int init() {
        static void *handle {nullptr};
        static bool funcs_loaded = false;

        if (funcs_loaded) {
          return 0;
        }

        if (!handle) {
          handle = dyn::handle({"libXrandr.so.2", "libXrandr.so"});
          if (!handle) {
            return -1;
          }
        }

        std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
          {(dyn::apiproc *) &GetScreenResources, "XRRGetScreenResources"},
          {(dyn::apiproc *) &GetOutputInfo, "XRRGetOutputInfo"},
          {(dyn::apiproc *) &GetCrtcInfo, "XRRGetCrtcInfo"},
          {(dyn::apiproc *) &FreeScreenResources, "XRRFreeScreenResources"},
          {(dyn::apiproc *) &FreeOutputInfo, "XRRFreeOutputInfo"},
          {(dyn::apiproc *) &FreeCrtcInfo, "XRRFreeCrtcInfo"},
        };

        if (dyn::load(handle, funcs)) {
          return -1;
        }

        funcs_loaded = true;
        return 0;
      }

    }  // namespace rr

    namespace fix {
      _FN(GetCursorImage, XFixesCursorImage *, (Display * dpy));

      static int init() {
        static void *handle {nullptr};
        static bool funcs_loaded = false;

        if (funcs_loaded) {
          return 0;
        }

        if (!handle) {
          handle = dyn::handle({"libXfixes.so.3", "libXfixes.so"});
          if (!handle) {
            return -1;
          }
        }

        std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
          {(dyn::apiproc *) &GetCursorImage, "XFixesGetCursorImage"},
        };

        if (dyn::load(handle, funcs)) {
          return -1;
        }

        funcs_loaded = true;
        return 0;
      }
    }  // namespace fix

    static int init() {
      static void *handle {nullptr};
      static bool funcs_loaded = false;

      if (funcs_loaded) {
        return 0;
      }

      if (!handle) {
        handle = dyn::handle({"libX11.so.6", "libX11.so"});
        if (!handle) {
          return -1;
        }
      }

      std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
        {(dyn::apiproc *) &GetImage, "XGetImage"},
        {(dyn::apiproc *) &OpenDisplay, "XOpenDisplay"},
        {(dyn::apiproc *) &GetWindowAttributes, "XGetWindowAttributes"},
        {(dyn::apiproc *) &Free, "XFree"},
        {(dyn::apiproc *) &CloseDisplay, "XCloseDisplay"},
        {(dyn::apiproc *) &InitThreads, "XInitThreads"},
      };

      if (dyn::load(handle, funcs)) {
        return -1;
      }

      funcs_loaded = true;
      return 0;
    }
  }  // namespace x11

  namespace xcb {
    static xcb_extension_t *shm_id;

    _FN(shm_get_image_reply, xcb_shm_get_image_reply_t *, (xcb_connection_t * c, xcb_shm_get_image_cookie_t cookie, xcb_generic_error_t **e));

    _FN(shm_get_image_unchecked, xcb_shm_get_image_cookie_t, (xcb_connection_t * c, xcb_drawable_t drawable, int16_t x, int16_t y, uint16_t width, uint16_t height, uint32_t plane_mask, uint8_t format, xcb_shm_seg_t shmseg, uint32_t offset));

    _FN(shm_attach, xcb_void_cookie_t, (xcb_connection_t * c, xcb_shm_seg_t shmseg, uint32_t shmid, uint8_t read_only));

    _FN(get_extension_data, xcb_query_extension_reply_t *, (xcb_connection_t * c, xcb_extension_t *ext));

    _FN(get_setup, xcb_setup_t *, (xcb_connection_t * c));
    _FN(disconnect, void, (xcb_connection_t * c));
    _FN(connection_has_error, int, (xcb_connection_t * c));
    _FN(connect, xcb_connection_t *, (const char *displayname, int *screenp));
    _FN(setup_roots_iterator, xcb_screen_iterator_t, (const xcb_setup_t *R));
    _FN(generate_id, std::uint32_t, (xcb_connection_t * c));

    /**
     * @brief Initialize shared-memory support for X11 capture.
     *
     * @return 0 when XCB shared-memory functions are loaded; nonzero otherwise.
     */
    int init_shm() {
      static void *handle {nullptr};
      static bool funcs_loaded = false;

      if (funcs_loaded) {
        return 0;
      }

      if (!handle) {
        handle = dyn::handle({"libxcb-shm.so.0", "libxcb-shm.so"});
        if (!handle) {
          return -1;
        }
      }

      std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
        {(dyn::apiproc *) &shm_id, "xcb_shm_id"},
        {(dyn::apiproc *) &shm_get_image_reply, "xcb_shm_get_image_reply"},
        {(dyn::apiproc *) &shm_get_image_unchecked, "xcb_shm_get_image_unchecked"},
        {(dyn::apiproc *) &shm_attach, "xcb_shm_attach"},
      };

      if (dyn::load(handle, funcs)) {
        return -1;
      }

      funcs_loaded = true;
      return 0;
    }

    /**
     * @brief Initialize XFixes cursor tracking for an X11 display.
     *
     * @return 0 on success; nonzero or negative platform status on failure.
     */
    int init() {
      static void *handle {nullptr};
      static bool funcs_loaded = false;

      if (funcs_loaded) {
        return 0;
      }

      if (!handle) {
        handle = dyn::handle({"libxcb.so.1", "libxcb.so"});
        if (!handle) {
          return -1;
        }
      }

      std::vector<std::tuple<dyn::apiproc *, const char *>> funcs {
        {(dyn::apiproc *) &get_extension_data, "xcb_get_extension_data"},
        {(dyn::apiproc *) &get_setup, "xcb_get_setup"},
        {(dyn::apiproc *) &disconnect, "xcb_disconnect"},
        {(dyn::apiproc *) &connection_has_error, "xcb_connection_has_error"},
        {(dyn::apiproc *) &connect, "xcb_connect"},
        {(dyn::apiproc *) &setup_roots_iterator, "xcb_setup_roots_iterator"},
        {(dyn::apiproc *) &generate_id, "xcb_generate_id"},
      };

      if (dyn::load(handle, funcs)) {
        return -1;
      }

      funcs_loaded = true;
      return 0;
    }

#undef _FN
  }  // namespace xcb

  /**
   * @brief Release image resources.
   *
   * @param p Pointer passed to the deleter or conversion helper.
   */
  void freeImage(XImage *);
  /**
   * @brief Release x resources.
   *
   * @param p Pointer passed to the deleter or conversion helper.
   */
  void freeX(XFixesCursorImage *);

  /**
   * @brief XCB connection pointer released with `xcb_disconnect`.
   */
  using xcb_connect_t = util::dyn_safe_ptr<xcb_connection_t, &xcb::disconnect>;
  /**
   * @brief XCB image pointer released with `xcb_image_destroy`.
   */
  using xcb_img_t = util::c_ptr<xcb_shm_get_image_reply_t>;

  /**
   * @brief XImage pointer released with `XDestroyImage`.
   */
  using ximg_t = util::safe_ptr<XImage, freeImage>;
  /**
   * @brief XFixes cursor image pointer released with `XFree`.
   */
  using xcursor_t = util::safe_ptr<XFixesCursorImage, freeX>;

  /**
   * @brief XRandR CRTC info pointer released with `XRRFreeCrtcInfo`.
   */
  using crtc_info_t = util::dyn_safe_ptr<_XRRCrtcInfo, &x11::rr::FreeCrtcInfo>;
  /**
   * @brief XRandR output info pointer released with `XRRFreeOutputInfo`.
   */
  using output_info_t = util::dyn_safe_ptr<_XRROutputInfo, &x11::rr::FreeOutputInfo>;
  /**
   * @brief XRandR screen resources pointer released with `XRRFreeScreenResources`.
   */
  using screen_res_t = util::dyn_safe_ptr<_XRRScreenResources, &x11::rr::FreeScreenResources>;

  /**
   * @brief RAII wrapper that removes a SysV shared-memory segment.
   */
  class shm_id_t {
  public:
    shm_id_t():
        id {-1} {
    }

    /**
     * @brief Take ownership of a SysV shared-memory segment ID.
     *
     * @param id SysV shared-memory segment ID.
     */
    shm_id_t(int id):
        id {id} {
    }

    /**
     * @brief Move ownership of a SysV shared-memory segment ID.
     *
     * @param other Shared-memory ID wrapper whose segment ownership is moved.
     */
    shm_id_t(shm_id_t &&other) noexcept:
        id(other.id) {
      other.id = -1;
    }

    ~shm_id_t() {
      if (id != -1) {
        shmctl(id, IPC_RMID, nullptr);
        id = -1;
      }
    }

    int id;  ///< SysV shared-memory segment identifier returned by shmget.
  };

  /**
   * @brief RAII wrapper that detaches mapped SysV shared memory.
   */
  class shm_data_t {
  public:
    shm_data_t():
        data {(void *) -1} {
    }

    /**
     * @brief Take ownership of an attached shared-memory mapping.
     *
     * @param data Pointer returned by shmat.
     */
    shm_data_t(void *data):
        data {data} {
    }

    /**
     * @brief Move ownership of an attached shared-memory mapping.
     *
     * @param other Shared-memory mapping wrapper whose attachment is moved.
     */
    shm_data_t(shm_data_t &&other) noexcept:
        data(other.data) {
      other.data = (void *) -1;
    }

    ~shm_data_t() {
      if ((std::uintptr_t) data != -1) {
        shmdt(data);
      }
    }

    void *data;  ///< Address returned by shmat for the shared-memory segment.
  };

  /**
   * @brief X11 image wrapper used by the software capture path.
   */
  struct x11_img_t: public img_t {
    ximg_t img;  ///< XImage backing the current software-captured frame.
  };

  /**
   * @brief X11 shared-memory image and segment ownership.
   */
  struct shm_img_t: public img_t {
    ~shm_img_t() override {
      delete[] data;
      data = nullptr;
    }
  };

  static void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) {
    xcursor_t overlay {x11::fix::GetCursorImage(display)};

    if (!overlay) {
      BOOST_LOG(error) << "Couldn't get cursor from XFixesGetCursorImage"sv;
      return;
    }

    overlay->x -= overlay->xhot;
    overlay->y -= overlay->yhot;

    overlay->x -= offsetX;
    overlay->y -= offsetY;

    overlay->x = std::max((short) 0, overlay->x);
    overlay->y = std::max((short) 0, overlay->y);

    auto pixels = (int *) img.data;

    auto screen_height = img.height;
    auto screen_width = img.width;

    auto delta_height = std::min<uint16_t>(overlay->height, std::max(0, screen_height - overlay->y));
    auto delta_width = std::min<uint16_t>(overlay->width, std::max(0, screen_width - overlay->x));
    for (auto y = 0; y < delta_height; ++y) {
      auto overlay_begin = &overlay->pixels[y * overlay->width];
      auto overlay_end = &overlay->pixels[y * overlay->width + delta_width];

      auto pixels_begin = &pixels[(y + overlay->y) * (img.row_pitch / img.pixel_pitch) + overlay->x];

      std::for_each(overlay_begin, overlay_end, [&](long pixel) {
        int *pixel_p = (int *) &pixel;

        auto colors_in = (uint8_t *) pixels_begin;

        auto alpha = (*(uint *) pixel_p) >> 24u;
        if (alpha == 255) {
          *pixels_begin = *pixel_p;
        } else {
          auto colors_out = (uint8_t *) pixel_p;
          colors_in[0] = colors_out[0] + (colors_in[0] * (255 - alpha) + 255 / 2) / 255;
          colors_in[1] = colors_out[1] + (colors_in[1] * (255 - alpha) + 255 / 2) / 255;
          colors_in[2] = colors_out[2] + (colors_in[2] * (255 - alpha) + 255 / 2) / 255;
        }
        ++pixels_begin;
      });
    }
  }

  /**
   * @brief X11 display, window, and attribute handles for capture.
   */
  struct x11_attr_t: public display_t {
    std::chrono::nanoseconds delay;  ///< Delay before the timer task becomes eligible to run.

    x11::xdisplay_t xdisplay;  ///< X11 display connection used for capture.
    Window xwindow;  ///< Root window being captured.
    XWindowAttributes xattr;  ///< Cached X11 window attributes used to detect size changes.

    mem_type_e mem_type;  ///< Mem type.

    /**
     * Last X (NOT the streamed monitor!) size.
     * This way we can trigger reinitialization if the dimensions changed while streaming
     */
    // int env_width, env_height;

    /**
     * @brief Open the X11 display and initialize capture attributes.
     *
     * @param mem_type Requested memory path for the capture backend.
     */
    x11_attr_t(mem_type_e mem_type):
        xdisplay {x11::OpenDisplay(nullptr)},
        xwindow {},
        xattr {},
        mem_type {mem_type} {
      x11::InitThreads();
    }

    /**
     * @brief Open the X11 display and cache capture window attributes.
     *
     * @param display_name Display name.
     * @param config Configuration values to apply.
     * @return 0 on success; nonzero or negative platform status on failure.
     */
    int init(const std::string &display_name, const ::video::config_t &config) {
      if (!xdisplay) {
        BOOST_LOG(error) << "Could not open X11 display"sv;
        return -1;
      }

      delay = ::video::capture_frame_interval(config);

      xwindow = DefaultRootWindow(xdisplay.get());

      refresh();

      int streamedMonitor = -1;
      if (!display_name.empty() && std::ranges::all_of(display_name, ::isdigit)) {
        // Resolve (legacy) monitor index from display_name
        streamedMonitor = (int) util::from_view(display_name);
      }

      screen_res_t screenr {x11::rr::GetScreenResources(xdisplay.get(), xwindow)};
      int output = screenr->noutput;

      output_info_t result;
      bool result_found = false;
      int monitor = 0;
      for (int x = 0; x < output; ++x) {
        output_info_t out_info {x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x])};
        if (out_info) {
          // Match monitor by index if a valid one is present, otherwise try to resolve by matching output name to display_name
          if ((streamedMonitor >= 0 && monitor == streamedMonitor) || (streamedMonitor < 0 && out_info->name == display_name)) {
            result = std::move(out_info);
            result_found = true;
            break;
          }
          monitor++;
        }
      }

      if (result_found && result->crtc) {
        crtc_info_t crt_info {x11::rr::GetCrtcInfo(xdisplay.get(), screenr.get(), result->crtc)};
        BOOST_LOG(info)
          << "Streaming display: "sv << result->name << " with res "sv << crt_info->width << 'x' << crt_info->height << " offset by "sv << crt_info->x << 'x' << crt_info->y;

        width = crt_info->width;
        height = crt_info->height;
        offset_x = crt_info->x;
        offset_y = crt_info->y;
      } else {
        BOOST_LOG(warning) << "Couldn't get info for requested display ["sv << display_name << "], defaulting to recording entire virtual desktop"sv;
        width = xattr.width;
        height = xattr.height;
      }

      env_width = xattr.width;
      env_height = xattr.height;

      return 0;
    }

    /**
     * Called when the display attributes should change.
     */
    void refresh() {
      x11::GetWindowAttributes(xdisplay.get(), xwindow, &xattr);  // Update xattr's
    }

    capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
      auto next_frame = std::chrono::steady_clock::now();

      sleep_overshoot_logger.reset();

      while (true) {
        auto now = std::chrono::steady_clock::now();

        if (next_frame > now) {
          std::this_thread::sleep_for(next_frame - now);
          sleep_overshoot_logger.first_point(next_frame);
          sleep_overshoot_logger.second_point_now_and_log();
        }

        next_frame += delay;
        if (next_frame < now) {  // some major slowdown happened; we couldn't keep up
          next_frame = now + delay;
        }

        std::shared_ptr<platf::img_t> img_out;
        auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
        switch (status) {
          case platf::capture_e::reinit:
          case platf::capture_e::error:
          case platf::capture_e::interrupted:
            return status;
          case platf::capture_e::timeout:
            if (!push_captured_image_cb(std::move(img_out), false)) {
              return platf::capture_e::ok;
            }
            break;
          case platf::capture_e::ok:
            if (!push_captured_image_cb(std::move(img_out), true)) {
              return platf::capture_e::ok;
            }
            break;
          default:
            BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
            return status;
        }
      }

      return capture_e::ok;
    }

    /**
     * @brief Capture a display frame into the provided image object.
     *
     * @param pull_free_image_cb Callback that provides an available image buffer.
     * @param img_out XImage-backed captured frame returned to the streaming pipeline.
     * @param timeout Maximum time to wait for the operation.
     * @param cursor Cursor image or visibility state to composite.
     * @return Capture status reported to the streaming pipeline.
     */
    capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
      refresh();

      // The whole X server changed, so we must reinit everything
      if (xattr.width != env_width || xattr.height != env_height) {
        BOOST_LOG(warning) << "X dimensions changed in non-SHM mode, request reinit"sv;
        return capture_e::reinit;
      }

      if (!pull_free_image_cb(img_out)) {
        return platf::capture_e::interrupted;
      }
      auto img = (x11_img_t *) img_out.get();

      XImage *x_img {x11::GetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap)};
      img->frame_timestamp = std::chrono::steady_clock::now();

      img->width = x_img->width;
      img->height = x_img->height;
      img->data = (uint8_t *) x_img->data;
      img->row_pitch = x_img->bytes_per_line;
      img->pixel_pitch = x_img->bits_per_pixel / 8;
      img->img.reset(x_img);

      if (cursor) {
        blend_cursor(xdisplay.get(), *img, offset_x, offset_y);
      }

      return capture_e::ok;
    }

    /**
     * @brief Allocate an image buffer compatible with this display backend.
     *
     * @return Allocated img object, or null when unavailable.
     */
    std::shared_ptr<img_t> alloc_img() override {
      return std::make_shared<x11_img_t>();
    }

    /**
     * @brief Create AVCodec encode device.
     *
     * @param pix_fmt Sunshine pixel format to convert or allocate for.
     * @return Constructed AVCodec encode device object.
     */
    std::unique_ptr<avcodec_encode_device_t> make_avcodec_encode_device(pix_fmt_e pix_fmt) override {
#ifdef SUNSHINE_BUILD_VAAPI
      if (mem_type == mem_type_e::vaapi) {
        return va::make_avcodec_encode_device(width, height, false);
      }
#endif

#ifdef SUNSHINE_BUILD_CUDA
      if (mem_type == mem_type_e::cuda) {
        return cuda::make_avcodec_encode_device(width, height, false);
      }
#endif

      return std::make_unique<avcodec_encode_device_t>();
    }

    /**
     * @brief Populate a fallback image when real capture data is unavailable.
     *
     * @param img Image or frame object to read from or populate.
     * @return Capture status reported to the streaming pipeline.
     */
    int dummy_img(img_t *img) override {
      // TODO: stop cheating and give black image
      if (!img) {
        return -1;
      };
      auto pull_dummy_img_callback = [&img](std::shared_ptr<platf::img_t> &img_out) -> bool {
        img_out = img->shared_from_this();
        return true;
      };
      std::shared_ptr<platf::img_t> img_out;
      snapshot(pull_dummy_img_callback, img_out, 0s, true);
      return 0;
    }
  };

  /**
   * @brief X11 shared-memory image dimensions and identifiers.
   */
  struct shm_attr_t: public x11_attr_t {
    x11::xdisplay_t shm_xdisplay;  ///< X11 display held separately to prevent races with x11_attr_t::xdisplay.
    xcb_connect_t xcb;  ///< XCB connection used by the shared-memory capture path.
    xcb_screen_t *display;  ///< XCB screen containing the captured root window.
    std::uint32_t seg;  ///< XCB shared-memory segment ID attached to the image.

    shm_id_t shm_id;  ///< Shm ID.

    shm_data_t data;  ///< Attached SysV shared-memory data used by XShm.

    task_pool_util::TaskPool::task_id_t refresh_task_id;  ///< Refresh task ID.

    /**
     * @brief Refresh X11 shared-memory capture after a scheduled delay.
     */
    void delayed_refresh() {
      refresh();

      refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
    }

    /**
     * @brief Open an X11 shared-memory capture backend.
     *
     * @param mem_type Requested memory path for the capture backend.
     */
    shm_attr_t(mem_type_e mem_type):
        x11_attr_t(mem_type),
        shm_xdisplay {x11::OpenDisplay(nullptr)} {
      refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id;
    }

    ~shm_attr_t() override {
      while (!task_pool.cancel(refresh_task_id));
    }

    capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
      auto next_frame = std::chrono::steady_clock::now();

      sleep_overshoot_logger.reset();

      while (true) {
        auto now = std::chrono::steady_clock::now();

        if (next_frame > now) {
          std::this_thread::sleep_for(next_frame - now);
          sleep_overshoot_logger.first_point(next_frame);
          sleep_overshoot_logger.second_point_now_and_log();
        }

        next_frame += delay;
        if (next_frame < now) {  // some major slowdown happened; we couldn't keep up
          next_frame = now + delay;
        }

        std::shared_ptr<platf::img_t> img_out;
        auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor);
        switch (status) {
          case platf::capture_e::reinit:
          case platf::capture_e::error:
          case platf::capture_e::interrupted:
            return status;
          case platf::capture_e::timeout:
            if (!push_captured_image_cb(std::move(img_out), false)) {
              return platf::capture_e::ok;
            }
            break;
          case platf::capture_e::ok:
            if (!push_captured_image_cb(std::move(img_out), true)) {
              return platf::capture_e::ok;
            }
            break;
          default:
            BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']';
            return status;
        }
      }

      return capture_e::ok;
    }

    /**
     * @brief Capture a display frame into the provided image object.
     *
     * @param pull_free_image_cb Callback that provides an available image buffer.
     * @param img_out Shared-memory captured frame returned to the streaming pipeline.
     * @param timeout Maximum time to wait for the operation.
     * @param cursor Cursor image or visibility state to composite.
     * @return Capture status reported to the streaming pipeline.
     */
    capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool cursor) {
      // The whole X server changed, so we must reinit everything
      if (xattr.width != env_width || xattr.height != env_height) {
        BOOST_LOG(warning) << "X dimensions changed in SHM mode, request reinit"sv;
        return capture_e::reinit;
      } else {
        auto img_cookie = xcb::shm_get_image_unchecked(xcb.get(), display->root, offset_x, offset_y, width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, seg, 0);
        auto frame_timestamp = std::chrono::steady_clock::now();

        xcb_img_t img_reply {xcb::shm_get_image_reply(xcb.get(), img_cookie, nullptr)};
        if (!img_reply) {
          BOOST_LOG(error) << "Could not get image reply"sv;
          return capture_e::reinit;
        }

        if (!pull_free_image_cb(img_out)) {
          return platf::capture_e::interrupted;
        }

        std::copy_n((std::uint8_t *) data.data, frame_size(), img_out->data);
        img_out->frame_timestamp = frame_timestamp;

        if (cursor) {
          blend_cursor(shm_xdisplay.get(), *img_out, offset_x, offset_y);
        }

        return capture_e::ok;
      }
    }

    /**
     * @brief Allocate an image buffer compatible with this display backend.
     *
     * @return Allocated img object, or null when unavailable.
     */
    std::shared_ptr<img_t> alloc_img() override {
      auto img = std::make_shared<shm_img_t>();
      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;
    }

    /**
     * @brief Populate a fallback image when real capture data is unavailable.
     *
     * @param img Image or frame object to read from or populate.
     * @return Capture status reported to the streaming pipeline.
     */
    int dummy_img(platf::img_t *img) override {
      return 0;
    }

    /**
     * @brief Initialize X11 shared-memory capture for the selected display.
     *
     * @param display_name Display name.
     * @param config Configuration values to apply.
     * @return 0 on success; nonzero or negative platform status on failure.
     */
    int init(const std::string &display_name, const ::video::config_t &config) {
      if (x11_attr_t::init(display_name, config)) {
        return 1;
      }

      shm_xdisplay.reset(x11::OpenDisplay(nullptr));
      xcb.reset(xcb::connect(nullptr, nullptr));
      if (xcb::connection_has_error(xcb.get())) {
        return -1;
      }

      if (!xcb::get_extension_data(xcb.get(), xcb::shm_id)->present) {
        BOOST_LOG(error) << "Missing SHM extension"sv;

        return -1;
      }

      auto iter = xcb::setup_roots_iterator(xcb::get_setup(xcb.get()));
      display = iter.data;
      seg = xcb::generate_id(xcb.get());

      shm_id.id = shmget(IPC_PRIVATE, frame_size(), IPC_CREAT | 0777);
      if (shm_id.id == -1) {
        BOOST_LOG(error) << "shmget failed"sv;
        return -1;
      }

      xcb::shm_attach(xcb.get(), seg, shm_id.id, false);
      data.data = shmat(shm_id.id, nullptr, 0);

      if ((uintptr_t) data.data == -1) {
        BOOST_LOG(error) << "shmat failed"sv;

        return -1;
      }

      return 0;
    }

    /**
     * @brief Calculate the XCB shared-memory frame size.
     *
     * @return Frame size in bytes for BGRA pixels.
     */
    std::uint32_t frame_size() {
      return width * height * 4;
    }
  };

  /**
   * @brief Create an X11 display capture backend.
   *
   * @param hwdevice_type Hardware device type requested for capture or encode.
   * @param display_name Display name.
   * @param config Configuration values to apply.
   * @return X11 display backend, or nullptr when initialization fails.
   */
  std::shared_ptr<display_t> x11_display(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
    if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) {
      BOOST_LOG(error) << "Could not initialize x11 display with the given hw device type"sv;
      return nullptr;
    }

    if (xcb::init_shm() || xcb::init() || x11::init() || x11::rr::init() || x11::fix::init()) {
      BOOST_LOG(error) << "Couldn't init x11 libraries"sv;

      return nullptr;
    }

    // Attempt to use shared memory X11 to avoid copying the frame
    auto shm_disp = std::make_shared<shm_attr_t>(hwdevice_type);

    auto status = shm_disp->init(display_name, config);
    if (status > 0) {
      // x11_attr_t::init() failed, don't bother trying again.
      return nullptr;
    }

    if (status == 0) {
      return shm_disp;
    }

    // Fallback
    auto x11_disp = std::make_shared<x11_attr_t>(hwdevice_type);
    if (x11_disp->init(display_name, config)) {
      return nullptr;
    }

    return x11_disp;
  }

  /**
   * @brief Enumerate display names accepted by the X11 backend.
   *
   * @return X11 display names, or an empty list when X11 probing fails.
   */
  std::vector<std::string> x11_display_names() {
    if (load_x11() || load_xcb()) {
      BOOST_LOG(error) << "Couldn't init x11 libraries"sv;

      return {};
    }

    BOOST_LOG(info) << "Detecting displays"sv;

    x11::xdisplay_t xdisplay {x11::OpenDisplay(nullptr)};
    if (!xdisplay) {
      return {};
    }

    auto xwindow = DefaultRootWindow(xdisplay.get());
    screen_res_t screenr {x11::rr::GetScreenResources(xdisplay.get(), xwindow)};
    int output = screenr->noutput;

    std::vector<std::string> names;
    int monitor = 0;
    for (int x = 0; x < output; ++x) {
      output_info_t out_info {x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x])};
      if (out_info) {
        BOOST_LOG(info) << "Detected display: "sv << out_info->name << " (id: "sv << monitor++ << ")"sv << out_info->name << " connected: "sv << (out_info->connection == RR_Connected);
        names.emplace_back(out_info->name);
      }
    }

    return names;
  }

  /**
   * @brief Release image resources.
   */
  void freeImage(XImage *p) {
    XDestroyImage(p);
  }

  /**
   * @brief Release x resources.
   */
  void freeX(XFixesCursorImage *p) {
    x11::Free(p);
  }

  /**
   * @brief Load XCB entry points used by the X11 capture backend.
   */
  int load_xcb() {
    // This will be called once only
    static int xcb_status = xcb::init_shm() || xcb::init();

    return xcb_status;
  }

  /**
   * @brief Load X11 entry points used by the X11 capture backend.
   */
  int load_x11() {
    // This will be called once only
    static int x11_status =
      window_system == window_system_e::NONE ||
      x11::init() || x11::rr::init() || x11::fix::init();

    return x11_status;
  }

  namespace x11 {
    std::optional<cursor_t> cursor_t::make() {
      if (load_x11()) {
        return std::nullopt;
      }

      cursor_t cursor;

      cursor.ctx.reset((cursor_ctx_t::pointer) x11::OpenDisplay(nullptr));

      return cursor;
    }

    void cursor_t::capture(egl::cursor_t &img) {
      auto display = (xdisplay_t::pointer) ctx.get();

      xcursor_t xcursor = fix::GetCursorImage(display);

      if (img.serial != xcursor->cursor_serial) {
        auto buf_size = xcursor->width * xcursor->height * sizeof(int);

        if (img.buffer.size() < buf_size) {
          img.buffer.resize(buf_size);
        }

        std::transform(xcursor->pixels, xcursor->pixels + buf_size / 4, (int *) img.buffer.data(), [](long pixel) -> int {
          return pixel;
        });
      }

      img.data = img.buffer.data();
      img.width = img.src_w = xcursor->width;
      img.height = img.src_h = xcursor->height;
      img.x = xcursor->x - xcursor->xhot;
      img.y = xcursor->y - xcursor->yhot;
      img.pixel_pitch = 4;
      img.row_pitch = img.pixel_pitch * img.width;
      img.serial = xcursor->cursor_serial;
    }

    void cursor_t::blend(img_t &img, int offsetX, int offsetY) {
      blend_cursor((xdisplay_t::pointer) ctx.get(), img, offsetX, offsetY);
    }

    /**
     * @brief Open and initialize the display connection used for capture.
     */
    xdisplay_t make_display() {
      return OpenDisplay(nullptr);
    }

    /**
     * @brief Release display resources.
     */
    void freeDisplay(_XDisplay *xdisplay) {
      CloseDisplay(xdisplay);
    }

    /**
     * @brief Release cursor context resources.
     *
     * @param ctx Native context object used by the operation or callback.
     */
    void freeCursorCtx(cursor_ctx_t::pointer ctx) {
      CloseDisplay((xdisplay_t::pointer) ctx);
    }
  }  // namespace x11
}  // namespace platf
