blob: f41cebd9fb0281ed1c8be403cbf6024dab5c0d95 [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdint.h>
#include <sys/mman.h>
#include <algorithm>
#include <memory>
#include <tuple>
#include <utility>
#include <vector>
#include <gbm.h>
#include <png.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include "base/files/file.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/macros.h"
namespace {
constexpr const char DRM_DEVICE_DIR[] = "/dev/dri";
constexpr const char DRM_DEVICE_GLOB[] = "card?";
struct DrmModeResDeleter {
void operator()(drmModeRes* resources) {
drmModeFreeResources(resources);
}
};
using ScopedDrmModeResPtr = std::unique_ptr<drmModeRes, DrmModeResDeleter>;
struct DrmModeCrtcDeleter {
void operator()(drmModeCrtc* crtc) {
drmModeFreeCrtc(crtc);
}
};
using ScopedDrmModeCrtcPtr = std::unique_ptr<drmModeCrtc, DrmModeCrtcDeleter>;
struct DrmModeFBDeleter {
void operator()(drmModeFB* fb) {
drmModeFreeFB(fb);
}
};
using ScopedDrmModeFBPtr = std::unique_ptr<drmModeFB, DrmModeFBDeleter>;
struct GbmDeviceDeleter {
void operator()(gbm_device* device) {
gbm_device_destroy(device);
}
};
using ScopedGbmDevicePtr = std::unique_ptr<gbm_device, GbmDeviceDeleter>;
struct GbmBoDeleter {
void operator()(gbm_bo* bo) {
gbm_bo_destroy(bo);
}
};
using ScopedGbmBoPtr = std::unique_ptr<gbm_bo, GbmBoDeleter>;
// Utility class to map/unmap GBM BO with RAII.
class GbmBoMap {
public:
GbmBoMap(gbm_bo* bo, uint32_t x, uint32_t y, uint32_t width, uint32_t height,
size_t plane)
: bo_(bo) {
buffer_ = gbm_bo_map(
bo_, x, y, width, height, GBM_BO_TRANSFER_READ, &stride_, &map_data_,
plane);
}
~GbmBoMap() {
if (IsValid())
gbm_bo_unmap(bo_, map_data_);
}
bool IsValid() const { return buffer_ != MAP_FAILED; }
uint32_t stride() const { return stride_; }
void* buffer() const { return buffer_; }
private:
gbm_bo* const bo_;
uint32_t stride_ = 0;
void* map_data_ = nullptr;
void* buffer_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(GbmBoMap);
};
// Finds the first valid CRTC and returns it and its associated device file.
std::pair<base::File, ScopedDrmModeCrtcPtr> FindFirstValidCrtc() {
std::vector<base::FilePath> paths;
{
base::FileEnumerator lister(base::FilePath(DRM_DEVICE_DIR),
false,
base::FileEnumerator::FILES,
DRM_DEVICE_GLOB);
for (base::FilePath name = lister.Next();
!name.empty();
name = lister.Next()) {
paths.push_back(name);
}
}
std::sort(paths.begin(), paths.end());
for (base::FilePath path : paths) {
base::File file(
path,
base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE);
if (!file.IsValid())
continue;
ScopedDrmModeResPtr resources(drmModeGetResources(file.GetPlatformFile()));
if (!resources)
continue;
for (int index_crtc = 0;
index_crtc < resources->count_crtcs;
++index_crtc) {
int32_t crtc_id = resources->crtcs[index_crtc];
ScopedDrmModeCrtcPtr crtc(
drmModeGetCrtc(file.GetPlatformFile(), crtc_id));
if (!crtc || !crtc->mode_valid || crtc->buffer_id == 0)
continue;
return std::make_pair(std::move(file), std::move(crtc));
}
}
return std::make_pair(base::File(), ScopedDrmModeCrtcPtr());
}
// Saves a BGRX image on memory as a RGB PNG file.
void SaveAsPng(const char* path, void* data, uint32_t width,
uint32_t height, uint32_t stride) {
png_struct* png = png_create_write_struct(
PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
CHECK(png) << "png_create_write_struct failed";
png_info* info = png_create_info_struct(png);
CHECK(info) << "png_create_info_struct failed";
CHECK_EQ(setjmp(png_jmpbuf(png)), 0) << "PNG encode failed";
base::ScopedFILE fp(fopen(path, "w"));
PCHECK(fp) << "Failed to open " << path << " for writing";
png_init_io(png, fp.get());
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
png_write_info(png, info);
png_set_bgr(png);
png_set_filler(png, 0, PNG_FILLER_AFTER);
std::vector<png_byte*> rows(height);
for (uint32_t i = 0; i < height; ++i)
rows[i] = static_cast<png_byte*>(data) + stride * i;
png_write_image(png, rows.data());
png_write_end(png, nullptr);
png_destroy_write_struct(&png, &info);
}
} // namespace
int main(int argc, char** argv) {
if (argc != 2 || argv[1][0] == '-') {
LOG(ERROR) << "Usage: screenshot path/to/out.png";
return 1;
}
base::File file;
ScopedDrmModeCrtcPtr crtc;
std::tie(file, crtc) = FindFirstValidCrtc();
if (!crtc) {
LOG(ERROR) << "No valid CRTC found. Is the screen on?";
return 1;
}
ScopedDrmModeFBPtr fb(drmModeGetFB(file.GetPlatformFile(), crtc->buffer_id));
CHECK(fb) << "drmModeGetFB failed";
ScopedGbmDevicePtr device(gbm_create_device(file.GetPlatformFile()));
CHECK(device) << "gbm_create_device failed";
base::ScopedFD buffer_fd;
{
int fd;
int rv = drmPrimeHandleToFD(file.GetPlatformFile(), fb->handle, 0, &fd);
CHECK_EQ(rv, 0) << "drmPrimeHandleToFD failed";
buffer_fd.reset(fd);
}
ScopedGbmBoPtr bo;
{
gbm_import_fd_data fd_data = {
buffer_fd.get(),
fb->width,
fb->height,
fb->pitch,
// TODO(djmk): The buffer format is hardcoded to ARGB8888, we should fix
// this to query for the frambuffer's format instead.
GBM_FORMAT_ARGB8888,
};
bo.reset(gbm_bo_import(device.get(), GBM_BO_IMPORT_FD, &fd_data,
GBM_BO_USE_SCANOUT));
}
CHECK(bo) << "gbm_bo_import failed";
GbmBoMap map(bo.get(), 0, 0, fb->width, fb->height, 0);
CHECK(map.IsValid()) << "gbm_bo_map failed";
SaveAsPng(argv[1], map.buffer(), fb->width, fb->height, map.stride());
return 0;
}