我正在尝试使用
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;
您必须调用
mmap
才能访问共享内存。来自 xcb_shm_create_segment_unchecked
的文档:
如何获得访问权限?要求服务器分配共享内存段。服务器的回复将包含一个文件描述符,供客户端传递给 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,这样你就可以这样查看。