Я пытаюсь сделать снимок экрана части окна, используя 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;
}
Первый вопрос: правильно ли я сделал с точки зрения освобождения ресурсов?
2-й: Как мне получить доступ к сегменту общей памяти?
Наконец, как мне сделать снимок экрана корневого окна размером 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, включая дескриптор файла, чтобы сообщить 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
. Вы можете видеть, что это похоже на то, как cairo
это делает , а также на то, как obs
это делает.
Чтобы фактически преобразовать байты в изображение, вы можете преобразовать его в формат изображения с наименьшим общим знаменателем, например PPM. Вот как может выглядеть этот код:
if (auto ppmFile = std::ofstream("/path/to/screenshot.ppm", std::ios::binary); ppmFile) {
ppmFile << "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);
}
ppmFile << r << g << b;
}
}
Конечным результатом должно быть изображение в формате ppm
, расположенное по адресу /path/to/screenshot.ppm
. Если у вас нет возможности просмотреть его, imagemagick может конвертировать ppm в png, и вы можете просмотреть его таким образом.
Перекрестная ссылка, которая может быть полезной, а может и нет (похоже, речь идет о тесно связанной проблеме с похожим кодом и даже может быть написана тем же автором): gitlab.freedesktop.org/xorg/lib/libxcb/-/issues/ 80