quickshell/src/wayland/screencopy/image_copy_capture/image_copy_capture.cpp

225 lines
6.5 KiB
C++

#include "image_copy_capture.hpp"
#include <cstdint>
#include <private/qwaylandscreen_p.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qrect.h>
#include <qscreen.h>
#include <qtmetamacros.h>
#include <qwayland-ext-image-copy-capture-v1.h>
#include <qwaylandclientextension.h>
#include <sys/types.h>
#include <wayland-ext-image-copy-capture-v1-client-protocol.h>
#include <wayland-util.h>
#include "../manager.hpp"
#include "image_copy_capture_p.hpp"
namespace qs::wayland::screencopy::icc {
namespace {
Q_LOGGING_CATEGORY(logIcc, "quickshell.wayland.screencopy.icc", QtWarningMsg);
}
using IccCaptureSession = QtWayland::ext_image_copy_capture_session_v1;
using IccCaptureFrame = QtWayland::ext_image_copy_capture_frame_v1;
IccScreencopyContext::IccScreencopyContext(::ext_image_copy_capture_session_v1* session)
: IccCaptureSession(session) {}
IccScreencopyContext::~IccScreencopyContext() {
if (this->IccCaptureSession::object()) {
this->IccCaptureSession::destroy();
}
if (this->IccCaptureFrame::object()) {
this->IccCaptureFrame::destroy();
}
}
void IccScreencopyContext::captureFrame() {
if (this->IccCaptureFrame::object() || this->capturePending) return;
if (this->statePending) this->capturePending = true;
else this->doCapture();
}
void IccScreencopyContext::ext_image_copy_capture_session_v1_buffer_size(
uint32_t width,
uint32_t height
) {
this->clearOldState();
this->request.width = width;
this->request.height = height;
}
void IccScreencopyContext::ext_image_copy_capture_session_v1_shm_format(uint32_t format) {
this->clearOldState();
this->request.shm.formats.push(format);
}
void IccScreencopyContext::ext_image_copy_capture_session_v1_dmabuf_device(wl_array* device) {
this->clearOldState();
if (device->size != sizeof(dev_t)) {
qCFatal(logIcc) << "The size of dev_t used by the compositor and quickshell is mismatched. Try "
"recompiling both.";
}
this->request.dmabuf.device = *reinterpret_cast<dev_t*>(device->data);
}
void IccScreencopyContext::ext_image_copy_capture_session_v1_dmabuf_format(
uint32_t format,
wl_array* modifiers
) {
this->clearOldState();
auto* modifierArray = reinterpret_cast<uint64_t*>(modifiers->data);
auto modifierCount = modifiers->size / sizeof(uint64_t);
auto reqFormat = buffer::WlBufferRequest::DmaFormat(format);
for (uint16_t i = 0; i != modifierCount; i++) {
reqFormat.modifiers.push(modifierArray[i]); // NOLINT
}
this->request.dmabuf.formats.push(reqFormat);
}
void IccScreencopyContext::ext_image_copy_capture_session_v1_done() {
this->statePending = false;
if (this->capturePending) {
this->doCapture();
}
}
void IccScreencopyContext::ext_image_copy_capture_session_v1_stopped() {
qCInfo(logIcc) << "Ending recording due to screencopy stop for" << this;
emit this->stopped();
}
void IccScreencopyContext::clearOldState() {
if (!this->statePending) {
this->request = buffer::WlBufferRequest();
this->statePending = true;
}
}
void IccScreencopyContext::doCapture() {
this->capturePending = false;
auto newBuffer = false;
auto* backbuffer = this->mSwapchain.createBackbuffer(this->request, &newBuffer);
this->IccCaptureFrame::init(this->IccCaptureSession::create_frame());
this->IccCaptureFrame::attach_buffer(backbuffer->buffer());
if (newBuffer) {
// If the buffer was replaced, it will be blank and the compositor needs
// to repaint the whole thing.
this->IccCaptureFrame::damage_buffer(
0,
0,
static_cast<int>(this->request.width),
static_cast<int>(this->request.height)
);
// We don't care about partial damage if the whole buffer was replaced.
this->lastDamage = QRect();
} else if (!this->lastDamage.isEmpty()) {
// If buffers were swapped between the last frame and the current one, request a repaint
// of the backbuffer in the same places that changes to the frontbuffer were recorded.
this->IccCaptureFrame::damage_buffer(
this->lastDamage.x(),
this->lastDamage.y(),
this->lastDamage.width(),
this->lastDamage.height()
);
// We don't need to do this more than once per buffer swap.
this->lastDamage = QRect();
}
this->IccCaptureFrame::capture();
}
void IccScreencopyContext::ext_image_copy_capture_frame_v1_transform(uint32_t transform) {
this->mSwapchain.backbuffer()->transform = transform;
}
void IccScreencopyContext::ext_image_copy_capture_frame_v1_damage(
int32_t x,
int32_t y,
int32_t width,
int32_t height
) {
this->damage = this->damage.united(QRect(x, y, width, height));
}
void IccScreencopyContext::ext_image_copy_capture_frame_v1_ready() {
this->IccCaptureFrame::destroy();
this->mSwapchain.swapBuffers();
this->lastDamage = this->damage;
this->damage = QRect();
emit this->frameCaptured();
}
void IccScreencopyContext::ext_image_copy_capture_frame_v1_failed(uint32_t reason) {
switch (static_cast<IccCaptureFrame::failure_reason>(reason)) {
case IccCaptureFrame::failure_reason_buffer_constraints:
qFatal(logIcc) << "Got a buffer_constraints failure, however the buffer matches the last sent "
"size. There is a bug in quickshell or your compositor.";
break;
case IccCaptureFrame::failure_reason_stopped:
// Handled in the ExtCaptureSession handler.
break;
case IccCaptureFrame::failure_reason_unknown:
qCWarning(logIcc) << "Ending recording due to screencopy failure for" << this;
emit this->stopped();
break;
}
}
IccManager::IccManager(): QWaylandClientExtensionTemplate(1) { this->initialize(); }
IccManager* IccManager::instance() {
static auto* instance = new IccManager();
return instance;
}
ScreencopyContext*
IccManager::createSession(::ext_image_capture_source_v1* source, bool paintCursors) {
auto* session = this->create_session(
source,
paintCursors ? QtWayland::ext_image_copy_capture_manager_v1::options_paint_cursors : 0
);
return new IccScreencopyContext(session);
}
IccOutputSourceManager::IccOutputSourceManager(): QWaylandClientExtensionTemplate(1) {
this->initialize();
}
IccOutputSourceManager* IccOutputSourceManager::instance() {
static auto* instance = new IccOutputSourceManager();
return instance;
}
ScreencopyContext* IccOutputSourceManager::captureOutput(QScreen* screen, bool paintCursors) {
auto* waylandScreen = dynamic_cast<QtWaylandClient::QWaylandScreen*>(screen->handle());
if (!waylandScreen) return nullptr;
return IccManager::instance()->createSession(
this->create_source(waylandScreen->output()),
paintCursors
);
}
} // namespace qs::wayland::screencopy::icc