diff --git a/include/aquamarine/backend/Backend.hpp b/include/aquamarine/backend/Backend.hpp index bf1480d..1fbdf4a 100644 --- a/include/aquamarine/backend/Backend.hpp +++ b/include/aquamarine/backend/Backend.hpp @@ -75,6 +75,7 @@ namespace Aquamarine { virtual void onReady() = 0; virtual std::vector getRenderFormats() = 0; virtual std::vector getCursorFormats() = 0; + virtual bool createOutput() = 0; }; class CBackend { diff --git a/include/aquamarine/backend/DRM.hpp b/include/aquamarine/backend/DRM.hpp index f84d943..7b9e4bf 100644 --- a/include/aquamarine/backend/DRM.hpp +++ b/include/aquamarine/backend/DRM.hpp @@ -289,6 +289,7 @@ namespace Aquamarine { virtual void onReady(); virtual std::vector getRenderFormats(); virtual std::vector getCursorFormats(); + virtual bool createOutput(); Hyprutils::Memory::CWeakPointer self; diff --git a/include/aquamarine/backend/Headless.hpp b/include/aquamarine/backend/Headless.hpp new file mode 100644 index 0000000..25cb279 --- /dev/null +++ b/include/aquamarine/backend/Headless.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include "./Backend.hpp" +#include "../allocator/Swapchain.hpp" +#include "../output/Output.hpp" +#include + +namespace Aquamarine { + class CBackend; + class CHeadlessBackend; + + class CHeadlessOutput : public IOutput { + public: + virtual ~CHeadlessOutput(); + virtual bool commit(); + virtual bool test(); + virtual Hyprutils::Memory::CSharedPointer getBackend(); + virtual void scheduleFrame(); + virtual bool destroy(); + + Hyprutils::Memory::CWeakPointer self; + + private: + CHeadlessOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_); + + Hyprutils::Memory::CWeakPointer backend; + + Hyprutils::Memory::CSharedPointer> framecb; + bool frameScheduled = false; + + friend class CHeadlessBackend; + }; + + class CHeadlessBackend : public IBackendImplementation { + public: + virtual ~CHeadlessBackend(); + virtual eBackendType type(); + virtual bool start(); + virtual std::vector> pollFDs(); + virtual int drmFD(); + virtual bool dispatchEvents(); + 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(); + virtual bool createOutput(); + + Hyprutils::Memory::CWeakPointer self; + + private: + CHeadlessBackend(Hyprutils::Memory::CSharedPointer backend_); + + Hyprutils::Memory::CWeakPointer backend; + std::vector> outputs; + + size_t outputIDCounter = 0; + + class CTimer { + public: + std::chrono::steady_clock::time_point when; + std::function what; + bool expired(); + }; + + struct { + int timerfd = -1; + std::vector timers; + } timers; + + void dispatchTimers(); + void updateTimerFD(); + + friend class CBackend; + friend class CHeadlessOutput; + }; +}; diff --git a/include/aquamarine/backend/Wayland.hpp b/include/aquamarine/backend/Wayland.hpp index 54bf1a7..ce570f0 100644 --- a/include/aquamarine/backend/Wayland.hpp +++ b/include/aquamarine/backend/Wayland.hpp @@ -48,6 +48,7 @@ namespace Aquamarine { virtual void moveCursor(const Hyprutils::Math::Vector2D& coord); virtual void scheduleFrame(); virtual Hyprutils::Math::Vector2D cursorPlaneSize(); + virtual bool destroy(); Hyprutils::Memory::CWeakPointer self; @@ -131,6 +132,7 @@ namespace Aquamarine { virtual void onReady(); virtual std::vector getRenderFormats(); virtual std::vector getCursorFormats(); + virtual bool createOutput(); Hyprutils::Memory::CWeakPointer self; @@ -153,6 +155,9 @@ namespace Aquamarine { Hyprutils::Memory::CWeakPointer focusedOutput; uint32_t lastEnterSerial = 0; + // state + size_t lastOutputID = 0; + // dmabuf formats std::vector dmabufFormats; diff --git a/include/aquamarine/output/Output.hpp b/include/aquamarine/output/Output.hpp index 6d8dab1..a4a5f6b 100644 --- a/include/aquamarine/output/Output.hpp +++ b/include/aquamarine/output/Output.hpp @@ -86,6 +86,7 @@ namespace Aquamarine { friend class IOutput; friend class CWaylandOutput; friend class CDRMOutput; + friend class CHeadlessOutput; }; class IOutput { @@ -104,6 +105,7 @@ namespace Aquamarine { virtual Hyprutils::Math::Vector2D cursorPlaneSize(); // -1, -1 means no set size, 0, 0 means error virtual void scheduleFrame(); virtual size_t getGammaSize(); + virtual bool destroy(); // not all backends allow this!!! std::string name, description, make, model, serial; Hyprutils::Math::Vector2D physicalSize; diff --git a/src/backend/Backend.cpp b/src/backend/Backend.cpp index bff068a..fdaad24 100644 --- a/src/backend/Backend.cpp +++ b/src/backend/Backend.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -78,7 +79,10 @@ Hyprutils::Memory::CSharedPointer Aquamarine::CBackend::create(const s for (auto& r : ref) { backend->implementations.emplace_back(r); } - + } else if (b.backendType == AQ_BACKEND_HEADLESS) { + auto ref = SP(new CHeadlessBackend(backend)); + backend->implementations.emplace_back(ref); + ref->self = ref; } else { backend->log(AQ_LOG_ERROR, std::format("Unknown backend id: {}", (int)b.backendType)); continue; diff --git a/src/backend/Headless.cpp b/src/backend/Headless.cpp new file mode 100644 index 0000000..3a57856 --- /dev/null +++ b/src/backend/Headless.cpp @@ -0,0 +1,175 @@ +#include +#include +#include +#include +#include + +using namespace Aquamarine; +using namespace Hyprutils::Memory; +using namespace Hyprutils::Math; +#define SP CSharedPointer + +#define TIMESPEC_NSEC_PER_SEC 1000000000L + +static void timespecAddNs(timespec* pTimespec, int64_t delta) { + int delta_ns_low = delta % TIMESPEC_NSEC_PER_SEC; + int delta_s_high = delta / TIMESPEC_NSEC_PER_SEC; + + pTimespec->tv_sec += delta_s_high; + + pTimespec->tv_nsec += (long)delta_ns_low; + if (pTimespec->tv_nsec >= TIMESPEC_NSEC_PER_SEC) { + pTimespec->tv_nsec -= TIMESPEC_NSEC_PER_SEC; + ++pTimespec->tv_sec; + } +} + +Aquamarine::CHeadlessOutput::CHeadlessOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_) : backend(backend_) { + name = name_; + + framecb = makeShared>([this]() { + frameScheduled = false; + events.frame.emit(); + }); +} + +Aquamarine::CHeadlessOutput::~CHeadlessOutput() { + backend->backend->removeIdleEvent(framecb); + events.destroy.emit(); +} + +bool Aquamarine::CHeadlessOutput::commit() { + events.commit.emit(); + state->onCommit(); + return true; +} + +bool Aquamarine::CHeadlessOutput::test() { + return true; +} + +Hyprutils::Memory::CSharedPointer Aquamarine::CHeadlessOutput::getBackend() { + return backend.lock(); +} + +void Aquamarine::CHeadlessOutput::scheduleFrame() { + // FIXME: limit fps to the committed framerate. + + if (frameScheduled) + return; + + frameScheduled = true; + backend->backend->addIdleEvent(framecb); +} + +bool Aquamarine::CHeadlessOutput::destroy() { + std::erase(backend->outputs, self.lock()); + return true; +} + +Aquamarine::CHeadlessBackend::~CHeadlessBackend() { + ; +} + +Aquamarine::CHeadlessBackend::CHeadlessBackend(SP backend_) : backend(backend_) { + timers.timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); +} + +eBackendType Aquamarine::CHeadlessBackend::type() { + return eBackendType::AQ_BACKEND_HEADLESS; +} + +bool Aquamarine::CHeadlessBackend::start() { + return true; +} + +std::vector> Aquamarine::CHeadlessBackend::pollFDs() { + return {makeShared(timers.timerfd, [this]() { dispatchTimers(); })}; +} + +int Aquamarine::CHeadlessBackend::drmFD() { + return -1; +} + +bool Aquamarine::CHeadlessBackend::dispatchEvents() { + return true; +} + +uint32_t Aquamarine::CHeadlessBackend::capabilities() { + return 0; +} + +bool Aquamarine::CHeadlessBackend::setCursor(SP buffer, const Hyprutils::Math::Vector2D& hotspot) { + return false; +} + +void Aquamarine::CHeadlessBackend::onReady() { + ; +} + +std::vector Aquamarine::CHeadlessBackend::getRenderFormats() { + // FIXME: allow any modifier. Maybe set INVALID to mean that? or a special value? + return {SDRMFormat{.drmFormat = DRM_FORMAT_RGBA8888, .modifiers = {DRM_FORMAT_MOD_LINEAR}}, + SDRMFormat{.drmFormat = DRM_FORMAT_RGBA1010102, .modifiers = {DRM_FORMAT_MOD_LINEAR}}}; +} + +std::vector Aquamarine::CHeadlessBackend::getCursorFormats() { + return {}; // No cursor support +} + +bool Aquamarine::CHeadlessBackend::createOutput() { + auto output = SP(new CHeadlessOutput(std::format("HEADLESS-{}", ++outputIDCounter), self.lock())); + outputs.emplace_back(output); + output->swapchain = CSwapchain::create(backend->allocator, self.lock()); + output->self = output; + backend->events.newOutput.emit(SP(output)); + + return true; +} + +void Aquamarine::CHeadlessBackend::dispatchTimers() { + std::vector toFire; + for (size_t i = 0; i < timers.timers.size(); ++i) { + if (timers.timers.at(i).expired()) { + toFire.emplace_back(timers.timers.at(i)); + timers.timers.erase(timers.timers.begin() + i); + i--; + continue; + } + } + + for (auto& copy : toFire) { + if (copy.what) + copy.what(); + } + + updateTimerFD(); +} + +void Aquamarine::CHeadlessBackend::updateTimerFD() { + long long lowest = 42069133769LL; + const auto clocknow = std::chrono::steady_clock::now(); + + for (auto& t : timers.timers) { + auto delta = std::chrono::duration_cast(t.when - clocknow).count(); + + if (delta < lowest) + lowest = delta; + } + + if (lowest < 0) + lowest = 0; + + timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + timespecAddNs(&now, lowest * 1000); // µs -> ns => * 1000 + + itimerspec ts = {.it_value = now}; + + if (timerfd_settime(timers.timerfd, TFD_TIMER_ABSTIME, &ts, nullptr)) + backend->log(AQ_LOG_ERROR, std::format("headless: failed to arm timerfd: {}", strerror(errno))); +} + +bool Aquamarine::CHeadlessBackend::CTimer::expired() { + return std::chrono::steady_clock::now() > when; +} diff --git a/src/backend/Wayland.cpp b/src/backend/Wayland.cpp index fe26673..a540517 100644 --- a/src/backend/Wayland.cpp +++ b/src/backend/Wayland.cpp @@ -125,7 +125,7 @@ bool Aquamarine::CWaylandBackend::start() { dispatchEvents(); - createOutput("WAYLAND1"); + createOutput(); return true; } @@ -431,6 +431,11 @@ std::vector Aquamarine::CWaylandBackend::getCursorFormats() { return dmabufFormats; } +bool Aquamarine::CWaylandBackend::createOutput() { + createOutput(std::format("WAYLAND-{}", ++lastOutputID)); + return true; +} + Aquamarine::CWaylandOutput::CWaylandOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_) : backend(backend_) { name = name_; @@ -469,12 +474,7 @@ Aquamarine::CWaylandOutput::CWaylandOutput(const std::string& name_, Hyprutils:: sendFrameAndSetCallback(); }); - waylandState.xdgToplevel->setClose([this](CCXdgToplevel* r) { - events.destroy.emit(); - waylandState.surface->sendAttach(nullptr, 0, 0); - waylandState.surface->sendCommit(); - std::erase(backend->outputs, self.lock()); - }); + waylandState.xdgToplevel->setClose([this](CCXdgToplevel* r) { destroy(); }); auto inputRegion = makeShared(backend->waylandState.compositor->sendCreateRegion()); inputRegion->sendAdd(0, 0, INT32_MAX, INT32_MAX); @@ -489,6 +489,7 @@ Aquamarine::CWaylandOutput::CWaylandOutput(const std::string& name_, Hyprutils:: } Aquamarine::CWaylandOutput::~CWaylandOutput() { + backend->idleCallbacks.clear(); // FIXME: mega hack to avoid a UAF in frame events if (waylandState.xdgToplevel) waylandState.xdgToplevel->sendDestroy(); if (waylandState.xdgSurface) @@ -497,6 +498,15 @@ Aquamarine::CWaylandOutput::~CWaylandOutput() { waylandState.surface->sendDestroy(); } +bool Aquamarine::CWaylandOutput::destroy() { + events.destroy.emit(); + waylandState.surface->sendAttach(nullptr, 0, 0); + waylandState.surface->sendCommit(); + waylandState.frameCallback.reset(); + std::erase(backend->outputs, self.lock()); + return true; +} + bool Aquamarine::CWaylandOutput::test() { return true; // TODO: } diff --git a/src/backend/drm/DRM.cpp b/src/backend/drm/DRM.cpp index 753cf9b..cb10a9f 100644 --- a/src/backend/drm/DRM.cpp +++ b/src/backend/drm/DRM.cpp @@ -651,6 +651,10 @@ std::vector Aquamarine::CDRMBackend::getCursorFormats() { return {}; } +bool Aquamarine::CDRMBackend::createOutput() { + return false; +} + bool Aquamarine::SDRMPlane::init(drmModePlane* plane) { id = plane->plane_id; diff --git a/src/output/Output.cpp b/src/output/Output.cpp index af5bb73..8c12bc9 100644 --- a/src/output/Output.cpp +++ b/src/output/Output.cpp @@ -35,6 +35,10 @@ size_t Aquamarine::IOutput::getGammaSize() { return 0; } +bool Aquamarine::IOutput::destroy() { + return false; +} + const Aquamarine::COutputState::SInternalState& Aquamarine::COutputState::state() { return internalState; }