如何使用 xcb 访问共享内存

问题描述 投票:0回答:1

我正在尝试使用

xcb_shm_get_image_unchecked
抓取窗口部分的屏幕截图。我使用以下代码创建了共享内存:

#include <cstdlib>
#include <memory>

#include <sys/shm.h>
#include <xcb/shm.h>
#include <xcb/xcb.h>
#include <xcb/xcb_image.h>
#include <xcb/xcb_pixel.h>
#include <xcb/xproto.h>

#include <spdlog/spdlog.h>

const auto IMAGE_WIDTH  = 640;
const auto IMAGE_HEIGHT = 480;
const auto SHM_SIZE     = 4 * 1024 * 1024;

auto main() -> int
{
  std::unique_ptr<xcb_connection_t, decltype(&xcb_disconnect)> c(
    xcb_connect(nullptr, nullptr), &xcb_disconnect);

  if (!c) {
    spdlog::error("failed to connect to X server");
    return EXIT_FAILURE;
  }

  auto roots_iter = xcb_setup_roots_iterator(xcb_get_setup(c.get()));
  if (roots_iter.rem == 0) {
    spdlog::error("no screen found");
    return EXIT_FAILURE;
  }

  auto          root   = roots_iter.data->root;
  xcb_shm_seg_t shmseg = xcb_generate_id(c.get());
  // xcb_shm_get_im
  auto shm_reply = xcb_shm_create_segment_reply(
    c.get(), xcb_shm_create_segment(c.get(), shmseg, SHM_SIZE, 0), nullptr);

  if (!shm_reply) {
    spdlog::error("failed to create shared memory segment");
    return EXIT_FAILURE;
  }

  auto fds = xcb_shm_create_segment_reply_fds(c.get(), shm_reply);
  spdlog::info("found {} fds", shm_reply->nfd);
  for (int i = 0; i < shm_reply->nfd; i++) {
    auto err = xcb_request_check(
      c.get(), xcb_shm_attach_fd(c.get(), shmseg, fds[i], true));
    if (err) {
      spdlog::error("failed to attach fd: {}", fds[i]);
      delete err;
    }
  }

  for (int i = 0; i < shm_reply->nfd; i++) {
      close(fds[i]);
  }
  xcb_shm_detach(c.get(), shmseg);
  return 0;
}

第一个问题:在释放资源方面我做得对吗?

第二:如何访问共享内存段?

最后,我如何抓取根窗口的 640x480 屏幕截图?我是否走在正确的道路上:

auto image = xcb_shm_get_image_reply(
  c.get(),
  xcb_shm_get_image_unchecked(c.get(),
                              root,
                              0,
                              0,
                              IMAGE_WIDTH,
                              IMAGE_HEIGHT,
                              XCB_GC_PLANE_MASK,
                              XCB_IMAGE_FORMAT_Z_PIXMAP,
                              shmseg,
                              0),
  nullptr);

if (!image) {
  spdlog::error("failed to get image");
  xcb_shm_detach(c.get(), shmseg);
  return EXIT_FAILURE;
}

spdlog::info("image: size {}", image->size);
delete image;
c++ x11 shared-memory xcb
1个回答
0
投票

您必须调用

mmap
才能访问共享内存。来自
xcb_shm_create_segment_unchecked
的文档:

要求服务器分配共享内存段。服务器的回复将包含一个文件描述符,供客户端传递给 mmap()。

如何获得访问权限?

您必须调用

mmap

(包括文件描述符)来告诉 mmap 在哪里找到由 X
 服务器分配的内存区域。

你可以使用这样的东西来做到这一点:

auto *shmem = mmap(nullptr, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fds[0], 0); if (shmem == MAP_FAILED) { ... // cleanup } close(fds[0]);
一旦您阅读了 

mmap

 的文档,这些论点应该是不言自明的。最后一个 0 只是意味着我们希望指针指向内存区域的开头。

当然,完成后别忘了

munmap

munmap(p, SHM_SIZE);
最后,不需要

xcb_shm_attach_fd(c.get(), shmseg, fds[i], true));

。仅当您自己创建 mmap 文件并将其传递到 X 服务器以便它可以访问时才需要这样做。

第一个问题:在释放资源方面我做得对吗?

是的,你有

如何抓取根窗口的 640x480 屏幕截图?

您用于抓取图像的代码几乎是正确的。只需将

plane_mask

 参数更改为 
~0
 即可。要实际将字节转换为图像,您可以选择将其转换为最小公分母图像格式,例如 
PPM
。该代码如下所示:

if (auto f = std::ofstream("/path/to/screenshot.ppm", std::ios::binary); f) { f << "P6\n" << IMAGE_WIDTH << " " << IMAGE_HEIGHT << "\n255\n"; const auto *const imageData = shmem; for (uint32_t rgb = 0; rgb + 4 <= image->size; rgb += 4) { auto b = imageData[rgb]; auto g = imageData[rgb + 1]; auto r = imageData[rgb + 2]; if (setup->image_byte_order == XCB_IMAGE_ORDER_MSB_FIRST) { std::swap(b, r); } f << r << g << b; } }
最终结果应该是位于 

ppm

/path/to/screenshot.ppm
 格式的图像。如果你没有办法查看它,
imagemagick能够将ppm转换为png,这样你就可以这样查看。

© www.soinside.com 2019 - 2024. All rights reserved.