diff --git a/include/aquamarine/allocator/Swapchain.hpp b/include/aquamarine/allocator/Swapchain.hpp index 0d8d391..90fdc68 100644 --- a/include/aquamarine/allocator/Swapchain.hpp +++ b/include/aquamarine/allocator/Swapchain.hpp @@ -18,6 +18,7 @@ namespace Aquamarine { bool contains(Hyprutils::Memory::CSharedPointer buffer); Hyprutils::Memory::CSharedPointer next(int* age); + const SSwapchainOptions& currentOptions(); private: bool fullReconfigure(const SSwapchainOptions& options_); diff --git a/include/aquamarine/backend/Backend.hpp b/include/aquamarine/backend/Backend.hpp index 017239a..6da64c3 100644 --- a/include/aquamarine/backend/Backend.hpp +++ b/include/aquamarine/backend/Backend.hpp @@ -7,6 +7,7 @@ #include #include #include "../allocator/Allocator.hpp" +#include "Misc.hpp" namespace Aquamarine { enum eBackendType { @@ -59,13 +60,15 @@ namespace Aquamarine { AQ_BACKEND_CAPABILITY_POINTER = (1 << 0), }; - virtual eBackendType type() = 0; - virtual bool start() = 0; - virtual int pollFD() = 0; - virtual int drmFD() = 0; - virtual bool dispatchEvents() = 0; - virtual uint32_t capabilities() = 0; - virtual void onReady() = 0; + virtual eBackendType type() = 0; + virtual bool start() = 0; + virtual int pollFD() = 0; + virtual int drmFD() = 0; + virtual bool dispatchEvents() = 0; + virtual uint32_t capabilities() = 0; + virtual void onReady() = 0; + virtual std::vector getRenderFormats() = 0; + virtual std::vector getCursorFormats() = 0; }; class CBackend { diff --git a/include/aquamarine/backend/Wayland.hpp b/include/aquamarine/backend/Wayland.hpp index 6c2906c..6a05802 100644 --- a/include/aquamarine/backend/Wayland.hpp +++ b/include/aquamarine/backend/Wayland.hpp @@ -15,6 +15,7 @@ namespace Aquamarine { class CBackend; class CWaylandBackend; class CWaylandOutput; + class CWaylandPointer; typedef std::function FIdleCallback; @@ -46,6 +47,7 @@ namespace Aquamarine { virtual bool setCursor(Hyprutils::Memory::CSharedPointer buffer, const Hyprutils::Math::Vector2D& hotspot); virtual void moveCursor(const Hyprutils::Math::Vector2D& coord); virtual void scheduleFrame(); + virtual Hyprutils::Math::Vector2D maxCursorSize(); Hyprutils::Memory::CWeakPointer self; @@ -58,6 +60,7 @@ namespace Aquamarine { void sendFrameAndSetCallback(); void onFrameDone(); + void onEnter(Hyprutils::Memory::CSharedPointer pointer, uint32_t serial); // frame loop bool frameScheduledWhileWaiting = false; @@ -68,6 +71,14 @@ namespace Aquamarine { std::vector, Hyprutils::Memory::CSharedPointer>> buffers; } backendState; + struct { + Hyprutils::Memory::CSharedPointer cursorBuffer; + Hyprutils::Memory::CSharedPointer cursorSurface; + Hyprutils::Memory::CSharedPointer cursorWlBuffer; + uint32_t serial = 0; + Hyprutils::Math::Vector2D hotspot; + } cursorState; + struct { Hyprutils::Memory::CSharedPointer surface; Hyprutils::Memory::CSharedPointer xdgSurface; @@ -118,6 +129,8 @@ namespace Aquamarine { virtual uint32_t capabilities(); virtual bool setCursor(Hyprutils::Memory::CSharedPointer buffer, const Hyprutils::Math::Vector2D& hotspot); virtual void onReady(); + virtual std::vector getRenderFormats(); + virtual std::vector getCursorFormats(); Hyprutils::Memory::CWeakPointer self; @@ -140,12 +153,16 @@ namespace Aquamarine { Hyprutils::Memory::CWeakPointer focusedOutput; uint32_t lastEnterSerial = 0; + // dmabuf formats + std::vector dmabufFormats; + struct { wl_display* display = nullptr; // hw-s types Hyprutils::Memory::CSharedPointer registry; Hyprutils::Memory::CSharedPointer seat; + Hyprutils::Memory::CSharedPointer shm; Hyprutils::Memory::CSharedPointer xdg; Hyprutils::Memory::CSharedPointer compositor; Hyprutils::Memory::CSharedPointer dmabuf; diff --git a/include/aquamarine/output/Output.hpp b/include/aquamarine/output/Output.hpp index 27d4215..7aa6307 100644 --- a/include/aquamarine/output/Output.hpp +++ b/include/aquamarine/output/Output.hpp @@ -62,6 +62,7 @@ namespace Aquamarine { virtual Hyprutils::Memory::CSharedPointer preferredMode(); virtual bool setCursor(Hyprutils::Memory::CSharedPointer buffer, const Hyprutils::Math::Vector2D& hotspot); virtual void moveCursor(const Hyprutils::Math::Vector2D& coord); // includes the hotspot + virtual Hyprutils::Math::Vector2D maxCursorSize(); // -1, -1 means no limit, 0, 0 means error virtual void scheduleFrame(); std::string name, description, make, model, serial; diff --git a/src/allocator/Swapchain.cpp b/src/allocator/Swapchain.cpp index 7225958..ff76bd5 100644 --- a/src/allocator/Swapchain.cpp +++ b/src/allocator/Swapchain.cpp @@ -91,3 +91,7 @@ bool Aquamarine::CSwapchain::resize(size_t newSize) { bool Aquamarine::CSwapchain::contains(Hyprutils::Memory::CSharedPointer buffer) { return std::find(buffers.begin(), buffers.end(), buffer) != buffers.end(); } + +const SSwapchainOptions& Aquamarine::CSwapchain::currentOptions() { + return options; +} diff --git a/src/backend/Wayland.cpp b/src/backend/Wayland.cpp index c161683..fa4e322 100644 --- a/src/backend/Wayland.cpp +++ b/src/backend/Wayland.cpp @@ -2,16 +2,62 @@ #include #include #include "Shared.hpp" +#include "FormatUtils.hpp" #include #include #include #include +#include using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; #define SP CSharedPointer +static std::pair openExclusiveShm() { + // Only absolute paths can be shared across different shm_open() calls + srand(time(nullptr)); + std::string name = std::format("/aq{:x}", rand() % RAND_MAX); + + for (size_t i = 0; i < 69; ++i) { + int fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) + return {fd, name}; + } + + return {-1, ""}; +} + +static int allocateSHMFile(size_t len) { + auto [fd, name] = openExclusiveShm(); + if (fd < 0) + return -1; + + shm_unlink(name.c_str()); + + int ret; + do { + ret = ftruncate(fd, len); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) { + close(fd); + return -1; + } + + return fd; +} + +wl_shm_format shmFormatFromDRM(uint32_t drmFormat) { + switch (drmFormat) { + case DRM_FORMAT_XRGB8888: return WL_SHM_FORMAT_XRGB8888; + case DRM_FORMAT_ARGB8888: return WL_SHM_FORMAT_ARGB8888; + default: return (wl_shm_format)drmFormat; + } + + return (wl_shm_format)drmFormat; +} + Aquamarine::CWaylandBackend::~CWaylandBackend() { if (drmState.fd >= 0) close(drmState.fd); @@ -55,6 +101,9 @@ bool Aquamarine::CWaylandBackend::start() { } else if (NAME == "wl_compositor") { backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 6, id)); waylandState.compositor = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)waylandState.registry->resource(), id, &wl_compositor_interface, 6)); + } else if (NAME == "wl_shm") { + backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 1, id)); + waylandState.shm = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)waylandState.registry->resource(), id, &wl_shm_interface, 1)); } else if (NAME == "zwp_linux_dmabuf_v1") { backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 5, id)); waylandState.dmabuf = @@ -69,7 +118,7 @@ bool Aquamarine::CWaylandBackend::start() { wl_display_roundtrip(waylandState.display); - if (!waylandState.xdg || !waylandState.compositor || !waylandState.seat || !waylandState.dmabuf || waylandState.dmabufFailed) { + if (!waylandState.xdg || !waylandState.compositor || !waylandState.seat || !waylandState.dmabuf || waylandState.dmabufFailed || !waylandState.shm) { backend->log(AQ_LOG_ERROR, "Wayland backend cannot start: Missing protocols"); return false; } @@ -208,10 +257,20 @@ Aquamarine::CWaylandPointer::CWaylandPointer(SP pointer_, Hyprutils backend->focusedOutput = o; backend->backend->log(AQ_LOG_DEBUG, std::format("[wayland] focus changed: {}", o->name)); + o->onEnter(pointer, serial); break; } }); + pointer->setLeave([this](CCWlPointer* r, uint32_t serial, wl_proxy* surface) { + for (auto& o : backend->outputs) { + if (o->waylandState.surface->resource() != surface) + continue; + + o->cursorState.serial = 0; + } + }); + pointer->setButton([this](CCWlPointer* r, uint32_t serial, uint32_t timeMs, uint32_t button, wl_pointer_button_state state) { events.button.emit(SButtonEvent{ .timeMs = timeMs, @@ -310,7 +369,42 @@ bool Aquamarine::CWaylandBackend::initDmabuf() { backend->log(AQ_LOG_DEBUG, std::format("zwp_linux_dmabuf_v1: Got node {}", drmState.nodeName)); }); - // TODO: format table and tranche + waylandState.dmabufFeedback->setFormatTable([this](CCZwpLinuxDmabufFeedbackV1* r, int32_t fd, uint32_t size) { +#pragma pack(push, 1) + struct wlDrmFormatMarshalled { + uint32_t drmFormat; + char pad[4]; + uint64_t modifier; + }; +#pragma pack(pop) + static_assert(sizeof(wlDrmFormatMarshalled) == 16); + + auto formatTable = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (formatTable == MAP_FAILED) { + backend->log(AQ_LOG_ERROR, std::format("zwp_linux_dmabuf_v1: Failed to mmap the format table")); + return; + } + + const auto FORMATS = (wlDrmFormatMarshalled*)formatTable; + + for (size_t i = 0; i < size / 16; ++i) { + auto& fmt = FORMATS[i]; + + auto modName = drmGetFormatModifierName(fmt.modifier); + backend->log(AQ_LOG_DEBUG, std::format("zwp_linux_dmabuf_v1: Got format {} with modifier {}", fourccToName(fmt.drmFormat), modName ? modName : "UNKNOWN")); + free(modName); + + auto it = std::find_if(dmabufFormats.begin(), dmabufFormats.end(), [&fmt](const auto& e) { return e.drmFormat == fmt.drmFormat; }); + if (it == dmabufFormats.end()) { + dmabufFormats.emplace_back(SDRMFormat{.drmFormat = fmt.drmFormat, .modifiers = {fmt.modifier}}); + continue; + } + + it->modifiers.emplace_back(fmt.modifier); + } + + munmap(formatTable, size); + }); wl_display_roundtrip(waylandState.display); @@ -327,6 +421,14 @@ bool Aquamarine::CWaylandBackend::initDmabuf() { return true; } +std::vector Aquamarine::CWaylandBackend::getRenderFormats() { + return dmabufFormats; +} + +std::vector Aquamarine::CWaylandBackend::getCursorFormats() { + return dmabufFormats; +} + Aquamarine::CWaylandOutput::CWaylandOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_) : backend(backend_) { name = name_; @@ -496,6 +598,84 @@ void Aquamarine::CWaylandOutput::onFrameDone() { } bool Aquamarine::CWaylandOutput::setCursor(Hyprutils::Memory::CSharedPointer buffer, const Hyprutils::Math::Vector2D& hotspot) { + if (!cursorState.cursorSurface) + cursorState.cursorSurface = makeShared(backend->waylandState.compositor->sendCreateSurface()); + + if (!cursorState.cursorSurface) { + backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to create a wl_surface for the cursor", name)); + return false; + } + + if (!buffer) { + cursorState.cursorBuffer.reset(); + cursorState.cursorWlBuffer.reset(); + backend->pointers.at(0)->pointer->sendSetCursor(cursorState.serial, nullptr, cursorState.hotspot.x, cursorState.hotspot.y); + return true; + } + + cursorState.cursorBuffer = buffer; + cursorState.hotspot = hotspot; + + if (buffer->shm().success) { + auto attrs = buffer->shm(); + auto [pixelData, fmt, bufLen] = buffer->beginDataPtr(0); + + int fd = allocateSHMFile(bufLen); + if (fd < 0) { + backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to allocate a shm file", name)); + return false; + } + + void* data = mmap(nullptr, bufLen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to mmap the cursor pixel data", name)); + close(fd); + return false; + } + + memcpy(data, pixelData, bufLen); + munmap(data, bufLen); + + auto pool = makeShared(backend->waylandState.shm->sendCreatePool(fd, bufLen)); + if (!pool) { + backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to submit a wl_shm pool", name)); + close(fd); + return false; + } + + cursorState.cursorWlBuffer = makeShared(pool->sendCreateBuffer(0, attrs.size.x, attrs.size.y, attrs.stride, shmFormatFromDRM(attrs.format))); + + pool.reset(); + + close(fd); + } else if (auto attrs = buffer->dmabuf(); attrs.success) { + auto params = makeShared(backend->waylandState.dmabuf->sendCreateParams()); + + for (int i = 0; i < attrs.planes; ++i) { + params->sendAdd(attrs.fds.at(i), i, attrs.offsets.at(i), attrs.strides.at(i), attrs.modifier >> 32, attrs.modifier & 0xFFFFFFFF); + } + + cursorState.cursorWlBuffer = makeShared(params->sendCreateImmed(attrs.size.x, attrs.size.y, attrs.format, (zwpLinuxBufferParamsV1Flags)0)); + } else { + backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to create a buffer for cursor: No known attrs (tried dmabuf / shm)", name)); + return false; + } + + if (!cursorState.cursorWlBuffer) { + backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: Failed to create a buffer for cursor", name)); + return false; + } + + cursorState.cursorSurface->sendSetBufferScale(1); + cursorState.cursorSurface->sendSetBufferTransform(WL_OUTPUT_TRANSFORM_NORMAL); + cursorState.cursorSurface->sendAttach(cursorState.cursorWlBuffer.get(), 0, 0); + cursorState.cursorSurface->sendDamage(0, 0, INT32_MAX, INT32_MAX); + cursorState.cursorSurface->sendCommit(); + + // this may fail if we are not in focus + if (!backend->pointers.empty() && cursorState.serial) + backend->pointers.at(0)->pointer->sendSetCursor(cursorState.serial, cursorState.cursorSurface.get(), cursorState.hotspot.x, cursorState.hotspot.y); + return true; } @@ -503,6 +683,19 @@ void Aquamarine::CWaylandOutput::moveCursor(const Hyprutils::Math::Vector2D& coo return; } +void Aquamarine::CWaylandOutput::onEnter(SP pointer, uint32_t serial) { + cursorState.serial = serial; + + if (!cursorState.cursorSurface) + return; + + pointer->sendSetCursor(serial, cursorState.cursorSurface.get(), cursorState.hotspot.x, cursorState.hotspot.y); +} + +Hyprutils::Math::Vector2D Aquamarine::CWaylandOutput::maxCursorSize() { + return {-1, -1}; // no limit +} + void Aquamarine::CWaylandOutput::scheduleFrame() { if (frameScheduled) return; diff --git a/src/output/Output.cpp b/src/output/Output.cpp index a490d8a..207aee3 100644 --- a/src/output/Output.cpp +++ b/src/output/Output.cpp @@ -22,3 +22,7 @@ bool Aquamarine::IOutput::setCursor(Hyprutils::Memory::CSharedPointer b void Aquamarine::IOutput::scheduleFrame() { ; } + +Hyprutils::Math::Vector2D Aquamarine::IOutput::maxCursorSize() { + return {}; // error +}