From 01766c09568a8d89f63097bc74ff329040f53d3f Mon Sep 17 00:00:00 2001 From: Vaxry Date: Tue, 18 Jun 2024 18:45:05 +0200 Subject: [PATCH] Wayland: Initial progress --- .gitignore | 3 + CMakeLists.txt | 46 ++++- include/aquamarine/backend/Backend.hpp | 39 +++- include/aquamarine/backend/Wayland.hpp | 106 ++++++++++ include/aquamarine/buffer/Buffer.hpp | 72 +++++++ include/aquamarine/input/Input.hpp | 50 ++++- include/aquamarine/output/Output.hpp | 66 ++++++ protocols/.gitkeep | 0 src/backend/Backend.cpp | 131 +++++++++++- src/backend/Wayland.cpp | 265 +++++++++++++++++++++++++ src/buffer/Buffer.cpp | 41 ++++ src/include/Shared.hpp | 16 ++ tests/SimpleWindow.cpp | 41 ++++ 13 files changed, 864 insertions(+), 12 deletions(-) create mode 100644 include/aquamarine/backend/Wayland.hpp create mode 100644 include/aquamarine/buffer/Buffer.hpp create mode 100644 include/aquamarine/output/Output.hpp create mode 100644 protocols/.gitkeep create mode 100644 src/backend/Wayland.cpp create mode 100644 src/buffer/Buffer.cpp create mode 100644 src/include/Shared.hpp create mode 100644 tests/SimpleWindow.cpp diff --git a/.gitignore b/.gitignore index 2c633ed..a6c7f43 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,6 @@ build/ .vscode/ .cache/ + +protocols/*.cpp +protocols/*.hpp \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index fda3316..a1d9dae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR}) set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR}) find_package(PkgConfig REQUIRED) -pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols hyprutils>=0.1.2) +pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols hyprutils>=0.1.2 pixman-1 wayland-client) configure_file(aquamarine.pc.in aquamarine.pc @ONLY) @@ -36,7 +36,7 @@ file(GLOB_RECURSE PUBLIC_HEADERS CONFIGURE_DEPENDS "include/*.hpp") add_library(aquamarine SHARED ${SRCFILES}) target_include_directories( aquamarine PUBLIC "./include" - PRIVATE "./src" + PRIVATE "./src" "./src/include" "./protocols" ) set_target_properties(aquamarine PROPERTIES VERSION ${AQUAMARINE_VERSION} @@ -44,6 +44,48 @@ set_target_properties(aquamarine PROPERTIES ) target_link_libraries(aquamarine PkgConfig::deps) +# Protocols +pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) +message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}") +pkg_get_variable(WAYLAND_CLIENT_DIR wayland-client pkgdatadir) +message(STATUS "Found wayland-client at ${WAYLAND_CLIENT_DIR}") + +function(protocolNew protoPath protoName external) + if (external) + set(path ${CMAKE_SOURCE_DIR}/${protoPath}) + else() + set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath}) + endif() + add_custom_command( + OUTPUT ${CMAKE_SOURCE_DIR}/protocols/${protoName}.cpp + ${CMAKE_SOURCE_DIR}/protocols/${protoName}.hpp + COMMAND hyprwayland-scanner --client ${path}/${protoName}.xml ${CMAKE_SOURCE_DIR}/protocols/ + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) + target_sources(aquamarine PRIVATE protocols/${protoName}.cpp protocols/${protoName}.hpp) +endfunction() +function(protocolWayland) + add_custom_command( + OUTPUT ${CMAKE_SOURCE_DIR}/protocols/wayland.cpp + ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp + COMMAND hyprwayland-scanner --wayland-enums --client ${WAYLAND_CLIENT_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/ + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) + target_sources(aquamarine PRIVATE protocols/wayland.cpp protocols/wayland.hpp) +endfunction() + +protocolWayland() + +protocolNew("stable/xdg-shell" "xdg-shell" false) + +# tests +add_custom_target(tests) + +add_executable(simpleWindow "tests/SimpleWindow.cpp") +target_link_libraries(simpleWindow PRIVATE aquamarine PkgConfig::deps) +add_test(NAME "simpleWindow" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND simpleWindow "simpleWindow") +add_dependencies(tests simpleWindow) + # Installation install(TARGETS aquamarine) install(DIRECTORY "include/aquamarine" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/include/aquamarine/backend/Backend.hpp b/include/aquamarine/backend/Backend.hpp index 1e1affd..31b7504 100644 --- a/include/aquamarine/backend/Backend.hpp +++ b/include/aquamarine/backend/Backend.hpp @@ -1,8 +1,11 @@ #pragma once #include +#include #include #include +#include +#include namespace Aquamarine { enum eBackendType { @@ -47,8 +50,13 @@ namespace Aquamarine { class IBackendImplementation { public: - virtual ~IBackendImplementation(); - virtual eBackendType type() = 0; + virtual ~IBackendImplementation() { + ; + } + virtual eBackendType type() = 0; + virtual bool start() = 0; + virtual int pollFD() = 0; + virtual bool dispatchEvents() = 0; }; class CBackend { @@ -63,10 +71,33 @@ namespace Aquamarine { void log(eBackendLogLevel level, const std::string& msg); + /* Enters the event loop synchronously. For simple clients, this is probably what you want. For more complex ones, + see async methods further below */ + void enterLoop(); + + struct { + Hyprutils::Signal::CSignal newOutput; + Hyprutils::Signal::CSignal newPointer; + Hyprutils::Signal::CSignal newKeyboard; + Hyprutils::Signal::CSignal newTouch; + } events; + private: CBackend(); - std::vector implementations; - SBackendOptions options; + bool terminate = false; + + std::vector implementationOptions; + std::vector> implementations; + SBackendOptions options; + + // + struct { + std::condition_variable loopSignal; + std::mutex loopMutex; + std::atomic shouldProcess = false; + std::mutex loopRequestMutex; + std::mutex eventLock; + } m_sEventLoopInternals; }; }; \ No newline at end of file diff --git a/include/aquamarine/backend/Wayland.hpp b/include/aquamarine/backend/Wayland.hpp new file mode 100644 index 0000000..b14ef6c --- /dev/null +++ b/include/aquamarine/backend/Wayland.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include "./Backend.hpp" +#include "../output/Output.hpp" +#include "../input/Input.hpp" +#include +#include +#include +#include + +namespace Aquamarine { + class CBackend; + class CWaylandBackend; + + class CWaylandOutput : public IOutput { + public: + virtual ~CWaylandOutput(); + virtual void commit(); + + private: + CWaylandOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_); + + std::string name; + Hyprutils::Memory::CWeakPointer backend; + + struct { + Hyprutils::Memory::CSharedPointer surface; + Hyprutils::Memory::CSharedPointer xdgSurface; + Hyprutils::Memory::CSharedPointer xdgToplevel; + } waylandState; + + friend class CWaylandBackend; + friend class CWaylandPointer; + }; + + class CWaylandKeyboard : public IKeyboard { + public: + CWaylandKeyboard(Hyprutils::Memory::CSharedPointer keyboard_, Hyprutils::Memory::CWeakPointer backend_); + virtual ~CWaylandKeyboard(); + + virtual const std::string& getName(); + + Hyprutils::Memory::CSharedPointer keyboard; + Hyprutils::Memory::CWeakPointer backend; + + private: + const std::string name = "wl_keyboard"; + }; + + class CWaylandPointer : public IPointer { + public: + CWaylandPointer(Hyprutils::Memory::CSharedPointer pointer_, Hyprutils::Memory::CWeakPointer backend_); + virtual ~CWaylandPointer(); + + virtual const std::string& getName(); + + Hyprutils::Memory::CSharedPointer pointer; + Hyprutils::Memory::CWeakPointer backend; + + private: + const std::string name = "wl_pointer"; + }; + + class CWaylandBackend : public IBackendImplementation { + public: + virtual ~CWaylandBackend(); + virtual eBackendType type(); + virtual bool start(); + virtual int pollFD(); + virtual bool dispatchEvents(); + + Hyprutils::Memory::CWeakPointer self; + + private: + CWaylandBackend(Hyprutils::Memory::CSharedPointer backend); + + void initSeat(); + void initShell(); + void createOutput(const std::string& szName); + + // + Hyprutils::Memory::CWeakPointer backend; + std::vector> outputs; + std::vector> keyboards; + std::vector> pointers; + + // pointer focus + Hyprutils::Memory::CWeakPointer focusedOutput; + uint32_t lastEnterSerial = 0; + + struct { + wl_display* display = nullptr; + + // hw-s types + Hyprutils::Memory::CSharedPointer registry; + Hyprutils::Memory::CSharedPointer seat; + Hyprutils::Memory::CSharedPointer xdg; + Hyprutils::Memory::CSharedPointer compositor; + } waylandState; + + friend class CBackend; + friend class CWaylandKeyboard; + friend class CWaylandPointer; + friend class CWaylandOutput; + }; +}; diff --git a/include/aquamarine/buffer/Buffer.hpp b/include/aquamarine/buffer/Buffer.hpp new file mode 100644 index 0000000..2565bba --- /dev/null +++ b/include/aquamarine/buffer/Buffer.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include + +namespace Aquamarine { + enum eBufferCapability { + BUFFER_CAPABILITY_DATAPTR = (1 << 0), + }; + + enum eBufferType { + BUFFER_TYPE_DMABUF = 0, + BUFFER_TYPE_SHM, + BUFFER_TYPE_MISC, + }; + + class CWLBufferResource; + + struct SDMABUFAttrs { + bool success = false; + Hyprutils::Math::Vector2D size; + uint32_t format = 0; // fourcc + uint64_t modifier = 0; + + int planes = 1; + std::array offsets = {0}; + std::array strides = {0}; + std::array fds = {-1, -1, -1, -1}; + }; + + struct SSHMAttrs { + bool success = false; + int fd = 0; + uint32_t format = 0; + Hyprutils::Math::Vector2D size; + int stride = 0; + int64_t offset = 0; + }; + + class IBuffer { + public: + virtual ~IBuffer() { + ; + }; + + virtual eBufferCapability caps() = 0; + virtual eBufferType type() = 0; + virtual void update(const Hyprutils::Math::CRegion& damage) = 0; + virtual bool isSynchronous() = 0; // whether the updates to this buffer are synchronous, aka happen over cpu + virtual SDMABUFAttrs dmabuf(); + virtual SSHMAttrs shm(); + virtual std::tuple beginDataPtr(uint32_t flags); + virtual void endDataPtr(); + virtual void sendRelease(); + virtual void lock(); + virtual void unlock(); + virtual bool locked(); + + Hyprutils::Math::Vector2D size; + bool opaque = false; + + struct { + Hyprutils::Signal::CSignal destroy; + } events; + + private: + int locks = 0; + }; + +}; diff --git a/include/aquamarine/input/Input.hpp b/include/aquamarine/input/Input.hpp index 36f92ce..ff46eb1 100644 --- a/include/aquamarine/input/Input.hpp +++ b/include/aquamarine/input/Input.hpp @@ -1,13 +1,28 @@ #pragma once #include +#include struct libinput_device; namespace Aquamarine { class IKeyboard { public: - virtual libinput_device* getLibinputHandle(); + virtual ~IKeyboard() { + events.destroy.emit(); + } + virtual libinput_device* getLibinputHandle(); + virtual const std::string& getName() = 0; + + struct SKeyEvent { + uint32_t timeMs = 0; + uint32_t key = 0; + bool pressed = false; + }; + + struct SModifiersEvent { + uint32_t depressed = 0, latched = 0, locked = 0, group = 0; + }; struct { Hyprutils::Signal::CSignal destroy; @@ -18,7 +33,37 @@ namespace Aquamarine { class IPointer { public: - virtual libinput_device* getLibinputHandle(); + virtual ~IPointer() { + events.destroy.emit(); + } + + virtual libinput_device* getLibinputHandle(); + virtual const std::string& getName() = 0; + + enum ePointerAxis { + AQ_POINTER_AXIS_VERTICAL = 0, + AQ_POINTER_AXIS_HORIZONTAL, + }; + + struct SMoveEvent { + Hyprutils::Math::Vector2D delta; + }; + + struct SWarpEvent { + Hyprutils::Math::Vector2D absolute; + }; + + struct SButtonEvent { + uint32_t timeMs = 0; + uint32_t button = 0; + bool pressed = false; + }; + + struct SAxisEvent { + uint32_t timeMs = 0; + ePointerAxis axis = AQ_POINTER_AXIS_VERTICAL; + double value = 0.0; + }; struct { Hyprutils::Signal::CSignal destroy; @@ -26,6 +71,7 @@ namespace Aquamarine { Hyprutils::Signal::CSignal warp; Hyprutils::Signal::CSignal button; Hyprutils::Signal::CSignal axis; + Hyprutils::Signal::CSignal frame; } events; }; } \ No newline at end of file diff --git a/include/aquamarine/output/Output.hpp b/include/aquamarine/output/Output.hpp new file mode 100644 index 0000000..7b0add0 --- /dev/null +++ b/include/aquamarine/output/Output.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include +#include + +namespace Aquamarine { + struct SOutputMode { + Hyprutils::Math::Vector2D pixelSize; + unsigned int refreshRate = 0 /* in mHz */; + bool preferred = false; + }; + + enum eOutputPresentationMode { + AQ_OUTPUT_PRESENTATION_VSYNC = 0, + AQ_OUTPUT_PRESENTATION_IMMEDIATE, // likely tearing + }; + + class IOutput; + + class COutputState { + public: + COutputState(Hyprutils::Memory::CSharedPointer parent); + + Hyprutils::Math::CRegion damage; + bool enabled = false; + bool adaptiveSync = false; + eOutputPresentationMode presentationMode = AQ_OUTPUT_PRESENTATION_VSYNC; + std::vector gammaLut; + Hyprutils::Math::Vector2D lastModeSize; + Hyprutils::Memory::CWeakPointer mode; + }; + + class IOutput { + public: + virtual ~IOutput() { + ; + } + + virtual void commit() = 0; + + std::string name, description, make, model, serial; + Hyprutils::Math::Vector2D physicalSize; + bool enabled = false; + bool adaptiveSync = false; + bool nonDesktop = false; + + // + std::vector> modes; + Hyprutils::Memory::CSharedPointer state; + + // + struct SStateEvent { + Hyprutils::Math::Vector2D size; + }; + + struct { + Hyprutils::Signal::CSignal destroy; + Hyprutils::Signal::CSignal frame; + Hyprutils::Signal::CSignal needsFrame; + Hyprutils::Signal::CSignal present; + Hyprutils::Signal::CSignal state; + } events; + }; +} \ No newline at end of file diff --git a/protocols/.gitkeep b/protocols/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/Backend.cpp b/src/backend/Backend.cpp index b3aba98..a2fd423 100644 --- a/src/backend/Backend.cpp +++ b/src/backend/Backend.cpp @@ -1,29 +1,92 @@ #include +#include +#include +#include +#include using namespace Hyprutils::Memory; using namespace Aquamarine; #define SP CSharedPointer +static const char* backendTypeToName(eBackendType type) { + switch (type) { + case AQ_BACKEND_DRM: return "drm"; + case AQ_BACKEND_HEADLESS: return "headless"; + case AQ_BACKEND_WAYLAND: return "wayland"; + default: break; + } + return "invalid"; +} + Aquamarine::CBackend::CBackend() { ; } +Aquamarine::SBackendImplementationOptions::SBackendImplementationOptions() { + backendType = AQ_BACKEND_WAYLAND; + backendRequestMode = AQ_BACKEND_REQUEST_IF_AVAILABLE; +} + +Aquamarine::SBackendOptions::SBackendOptions() { + logFunction = nullptr; +} + Hyprutils::Memory::CSharedPointer Aquamarine::CBackend::create(const std::vector& backends, const SBackendOptions& options) { auto backend = SP(new CBackend()); - backend->options = options; + backend->options = options; + backend->implementationOptions = backends; - backend->log(AQ_LOG_DEBUG, "Hello world!\n"); + backend->log(AQ_LOG_DEBUG, "Creating an Aquamarine backend!"); + + for (auto& b : backends) { + if (b.backendType == AQ_BACKEND_WAYLAND) { + auto ref = SP(new CWaylandBackend(backend)); + backend->implementations.emplace_back(ref); + ref->self = ref; + } else { + backend->log(AQ_LOG_ERROR, std::format("Unknown backend id: {}", (int)b.backendType)); + continue; + } + } return backend; } Aquamarine::CBackend::~CBackend() { - log(AQ_LOG_DEBUG, "Bye world!\n"); + ; } bool Aquamarine::CBackend::start() { - log(AQ_LOG_DEBUG, "Starting world!\n"); + log(AQ_LOG_DEBUG, "Starting the Aquamarine backend!"); + + bool fallback = false; + int started = 0; + + for (size_t i = 0; i < implementations.size(); ++i) { + const bool ok = implementations.at(i)->start(); + + if (!ok) { + log(AQ_LOG_ERROR, std::format("Requested backend ({}) could not start, enabling fallbacks", backendTypeToName(implementationOptions.at(i).backendType))); + fallback = true; + if (implementationOptions.at(i).backendRequestMode == AQ_BACKEND_REQUEST_MANDATORY) { + log(AQ_LOG_CRITICAL, + std::format("Requested backend ({}) could not start and it's mandatory, cannot continue!", backendTypeToName(implementationOptions.at(i).backendType))); + implementations.clear(); + return false; + } + } else + started++; + } + + if (implementations.empty() || started <= 0) { + log(AQ_LOG_CRITICAL, "No backend could be opened. Make sure there was a correct backend passed to CBackend, and that your environment supports at least one of them."); + return false; + } + + // erase failed impls + std::erase_if(implementations, [](const auto& i) { return i->pollFD() < 0; }); + return true; } @@ -33,3 +96,63 @@ void Aquamarine::CBackend::log(eBackendLogLevel level, const std::string& msg) { options.logFunction(level, msg); } + +void Aquamarine::CBackend::enterLoop() { + std::vector pollFDs; + + for (auto& i : implementations) { + auto fd = i->pollFD(); + + pollFDs.emplace_back(pollfd{.fd = fd, .events = POLLIN, .revents = 0}); + } + + std::thread pollThr([this, &pollFDs]() { + int ret = 0; + while (1) { + ret = poll(pollFDs.data(), pollFDs.size(), 5000 /* 5 seconds, reasonable. It's because we might need to terminate */); + if (ret < 0) { + log(AQ_LOG_CRITICAL, std::format("Polling fds failed with {}", errno)); + terminate = true; + exit(1); + } + + for (size_t i = 0; i < pollFDs.size(); ++i) { + if (pollFDs[i].revents & POLLHUP) { + log(AQ_LOG_CRITICAL, std::format("disconnected from pollfd {}", i)); + terminate = true; + exit(1); + } + } + + if (terminate) + break; + + if (ret != 0) { + std::lock_guard lg(m_sEventLoopInternals.loopRequestMutex); + m_sEventLoopInternals.shouldProcess = true; + m_sEventLoopInternals.loopSignal.notify_all(); + } + } + }); + + while (1) { + m_sEventLoopInternals.loopRequestMutex.unlock(); // unlock, we are ready to take events + + std::unique_lock lk(m_sEventLoopInternals.loopMutex); + if (m_sEventLoopInternals.shouldProcess == false) // avoid a lock if a thread managed to request something already since we .unlock()ed + m_sEventLoopInternals.loopSignal.wait_for(lk, std::chrono::seconds(5), [this] { return m_sEventLoopInternals.shouldProcess == true; }); // wait for events + + m_sEventLoopInternals.loopRequestMutex.lock(); // lock incoming events + + if (terminate) + break; + + m_sEventLoopInternals.shouldProcess = false; + + std::lock_guard lg(m_sEventLoopInternals.eventLock); + + for (size_t i = 0; i < pollFDs.size(); ++i) { + implementations.at(i)->dispatchEvents(); + } + } +} diff --git a/src/backend/Wayland.cpp b/src/backend/Wayland.cpp new file mode 100644 index 0000000..5584763 --- /dev/null +++ b/src/backend/Wayland.cpp @@ -0,0 +1,265 @@ +#include +#include +#include + +using namespace Aquamarine; +using namespace Hyprutils::Memory; +using namespace Hyprutils::Math; +#define SP CSharedPointer + +Aquamarine::CWaylandBackend::~CWaylandBackend() { + ; +} +eBackendType Aquamarine::CWaylandBackend::type() { + return AQ_BACKEND_WAYLAND; +} + +Aquamarine::CWaylandBackend::CWaylandBackend(Hyprutils::Memory::CSharedPointer backend_) : backend(backend_) { + ; +} + +bool Aquamarine::CWaylandBackend::start() { + backend->log(AQ_LOG_DEBUG, "Starting the Wayland backend!"); + + waylandState.display = wl_display_connect(nullptr); + + if (!waylandState.display) { + backend->log(AQ_LOG_ERROR, "Wayland backend cannot start: wl_display_connect failed (is a wayland compositor running?)"); + return false; + } + + waylandState.registry = makeShared((wl_proxy*)wl_display_get_registry(waylandState.display)); + + backend->log(AQ_LOG_DEBUG, std::format("Got registry at 0x{:x}", (uintptr_t)waylandState.registry->resource())); + + waylandState.registry->setGlobal([this](CWlRegistry* r, uint32_t id, const char* name, uint32_t version) { + backend->log(AQ_LOG_TRACE, std::format(" | received global: {} (version {}) with id {}", name, version, id)); + + const std::string NAME = name; + + if (NAME == "wl_seat") { + backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 9, id)); + waylandState.seat = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)waylandState.registry->resource(), id, &wl_seat_interface, 9)); + initSeat(); + } else if (NAME == "xdg_wm_base") { + backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 6, id)); + waylandState.xdg = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)waylandState.registry->resource(), id, &xdg_wm_base_interface, 6)); + initShell(); + } 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)); + } + }); + waylandState.registry->setGlobalRemove([this](CWlRegistry* r, uint32_t id) { ; }); + + wl_display_roundtrip(waylandState.display); + + if (!waylandState.xdg || !waylandState.compositor || !waylandState.seat) { + backend->log(AQ_LOG_ERROR, "Wayland backend cannot start: Missing protocols"); + return false; + } + + dispatchEvents(); + + createOutput("WAYLAND1"); + + return true; +} + +void Aquamarine::CWaylandBackend::createOutput(const std::string& szName) { + outputs.emplace_back(SP(new CWaylandOutput(szName, self))); +} + +int Aquamarine::CWaylandBackend::pollFD() { + if (!waylandState.display) + return -1; + + return wl_display_get_fd(waylandState.display); +} + +bool Aquamarine::CWaylandBackend::dispatchEvents() { + wl_display_flush(waylandState.display); + + if (wl_display_prepare_read(waylandState.display) == 0) { + wl_display_read_events(waylandState.display); + wl_display_dispatch_pending(waylandState.display); + } else + wl_display_dispatch(waylandState.display); + + int ret = 0; + do { + ret = wl_display_dispatch_pending(waylandState.display); + wl_display_flush(waylandState.display); + } while (ret > 0); + + return true; +} + +Aquamarine::CWaylandKeyboard::CWaylandKeyboard(Hyprutils::Memory::CSharedPointer keyboard_, Hyprutils::Memory::CWeakPointer backend_) : + keyboard(keyboard_), backend(backend_) { + if (!keyboard->resource()) + return; + + backend->backend->log(AQ_LOG_DEBUG, "New wayland keyboard wl_keyboard"); + + keyboard->setKey([this](CWlKeyboard* r, uint32_t serial, uint32_t timeMs, uint32_t key, wl_keyboard_key_state state) { + events.key.emit(SKeyEvent{ + .timeMs = timeMs, + .key = key, + .pressed = state == WL_KEYBOARD_KEY_STATE_PRESSED, + }); + }); + + keyboard->setModifiers([this](CWlKeyboard* r, uint32_t serial, uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) { + events.key.emit(SModifiersEvent{ + .depressed = depressed, + .latched = latched, + .locked = locked, + .group = group, + }); + }); +} + +Aquamarine::CWaylandKeyboard::~CWaylandKeyboard() { + ; +} + +const std::string& Aquamarine::CWaylandKeyboard::getName() { + return name; +} + +Aquamarine::CWaylandPointer::CWaylandPointer(Hyprutils::Memory::CSharedPointer pointer_, Hyprutils::Memory::CWeakPointer backend_) : + pointer(pointer_), backend(backend_) { + if (!pointer->resource()) + return; + + backend->backend->log(AQ_LOG_DEBUG, "New wayland pointer wl_pointer"); + + pointer->setMotion([this](CWlPointer* r, uint32_t serial, wl_fixed_t x, wl_fixed_t y) { + if (!backend->focusedOutput || !backend->focusedOutput->state->mode) + return; + + Vector2D local = {wl_fixed_to_double(x), wl_fixed_to_double(y)}; + local = local / backend->focusedOutput->state->mode->pixelSize; + + events.warp.emit(SWarpEvent{ + .absolute = local, + }); + }); + + pointer->setEnter([this](CWlPointer* r, uint32_t serial, wl_proxy* surface, wl_fixed_t x, wl_fixed_t y) { + backend->lastEnterSerial = serial; + + for (auto& o : backend->outputs) { + if (o->waylandState.surface->resource() != surface) + continue; + + backend->focusedOutput = o; + backend->backend->log(AQ_LOG_DEBUG, std::format("[wayland] focus changed: {}", o->name)); + break; + } + }); + + pointer->setButton([this](CWlPointer* r, uint32_t serial, uint32_t timeMs, uint32_t button, wl_pointer_button_state state) { + events.button.emit(SButtonEvent{ + .timeMs = timeMs, + .button = button, + .pressed = state == WL_POINTER_BUTTON_STATE_PRESSED, + }); + }); + + pointer->setAxis([this](CWlPointer* r, uint32_t timeMs, wl_pointer_axis axis, wl_fixed_t value) { + events.axis.emit(SAxisEvent{ + .timeMs = timeMs, + .axis = axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL ? AQ_POINTER_AXIS_HORIZONTAL : AQ_POINTER_AXIS_VERTICAL, + .value = wl_fixed_to_double(value), + }); + }); + + pointer->setFrame([this](CWlPointer* r) { events.frame.emit(); }); +} + +Aquamarine::CWaylandPointer::~CWaylandPointer() { + ; +} + +const std::string& Aquamarine::CWaylandPointer::getName() { + return name; +} + +void Aquamarine::CWaylandBackend::initSeat() { + waylandState.seat->setCapabilities([this](CWlSeat* r, wl_seat_capability cap) { + const bool HAS_KEYBOARD = ((uint32_t)cap) & WL_SEAT_CAPABILITY_KEYBOARD; + const bool HAS_POINTER = ((uint32_t)cap) & WL_SEAT_CAPABILITY_POINTER; + + if (HAS_KEYBOARD && keyboards.empty()) + keyboards.emplace_back(makeShared(SP(waylandState.seat->sendGetKeyboard()), self)); + else if (!HAS_KEYBOARD && !keyboards.empty()) + keyboards.clear(); + + if (HAS_POINTER && pointers.empty()) + keyboards.emplace_back(makeShared(SP(waylandState.seat->sendGetKeyboard()), self)); + else if (!HAS_POINTER && !keyboards.empty()) + keyboards.clear(); + }); +} + +void Aquamarine::CWaylandBackend::initShell() { + waylandState.xdg->setPing([](CXdgWmBase* r, uint32_t serial) { r->sendPong(serial); }); +} + +Aquamarine::CWaylandOutput::CWaylandOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_) : name(name_), backend(backend_) { + errno = 0; + + waylandState.surface = SP(backend->waylandState.compositor->sendCreateSurface()); + + if (!waylandState.surface->resource()) { + backend->backend->log(AQ_LOG_ERROR, std::format("Output {} failed: no surface given. Errno: {}", name, errno)); + return; + } + + waylandState.xdgSurface = SP(backend->waylandState.xdg->sendGetXdgSurface(waylandState.surface->resource())); + + if (!waylandState.xdgSurface->resource()) { + backend->backend->log(AQ_LOG_ERROR, std::format("Output {} failed: no xdgSurface given. Errno: {}", name, errno)); + return; + } + + waylandState.xdgSurface->setConfigure([this](CXdgSurface* r, uint32_t serial) { + backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: configure surface with {}", name, serial)); + r->sendAckConfigure(serial); + }); + + waylandState.xdgToplevel = SP(waylandState.xdgSurface->sendGetToplevel()); + + if (!waylandState.xdgToplevel->resource()) { + backend->backend->log(AQ_LOG_ERROR, std::format("Output {} failed: no xdgToplevel given. Errno: {}", name, errno)); + return; + } + + waylandState.xdgToplevel->setWmCapabilities( + [this](CXdgToplevel* r, wl_array* arr) { backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: wm_capabilities received", name)); }); + + waylandState.xdgToplevel->setConfigure([this](CXdgToplevel* r, int32_t w, int32_t h, wl_array* arr) { + backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: configure toplevel with {}x{}", name, w, h)); + events.state.emit(SStateEvent{.size = {w, h}}); + }); + + waylandState.surface->sendAttach(nullptr, 0, 0); + waylandState.surface->sendCommit(); + + backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: initialized", name)); +} + +Aquamarine::CWaylandOutput::~CWaylandOutput() { + if (waylandState.xdgToplevel) + waylandState.xdgToplevel->sendDestroy(); + if (waylandState.xdgSurface) + waylandState.xdgSurface->sendDestroy(); + if (waylandState.surface) + waylandState.surface->sendDestroy(); +} + +void Aquamarine::CWaylandOutput::commit() { + ; +} \ No newline at end of file diff --git a/src/buffer/Buffer.cpp b/src/buffer/Buffer.cpp new file mode 100644 index 0000000..8197742 --- /dev/null +++ b/src/buffer/Buffer.cpp @@ -0,0 +1,41 @@ +#include +#include "Shared.hpp" + +using namespace Aquamarine; + +SDMABUFAttrs Aquamarine::IBuffer::dmabuf() { + return SDMABUFAttrs{}; +} + +SSHMAttrs Aquamarine::IBuffer::shm() { + return SSHMAttrs{}; +} + +std::tuple Aquamarine::IBuffer::beginDataPtr(uint32_t flags) { + return {nullptr, 0, 0}; +} + +void Aquamarine::IBuffer::endDataPtr() { + ; // empty +} + +void Aquamarine::IBuffer::sendRelease() { + ; +} + +void Aquamarine::IBuffer::lock() { + locks++; +} + +void Aquamarine::IBuffer::unlock() { + locks--; + + ASSERT(locks >= 0); + + if (locks <= 0) + sendRelease(); +} + +bool Aquamarine::IBuffer::locked() { + return locks; +} diff --git a/src/include/Shared.hpp b/src/include/Shared.hpp new file mode 100644 index 0000000..932da2b --- /dev/null +++ b/src/include/Shared.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +#define RASSERT(expr, reason, ...) \ + if (!(expr)) { \ + std::cout << std::format("\n==========================================================================================\nASSERTION FAILED! \n\n{}\n\nat: line {} in {}", \ + std::format(reason, ##__VA_ARGS__), __LINE__, \ + ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })()); \ + std::cout << "[Aquamarine] Assertion failed!"; \ + raise(SIGABRT); \ + } + +#define ASSERT(expr) RASSERT(expr, "?") \ No newline at end of file diff --git a/tests/SimpleWindow.cpp b/tests/SimpleWindow.cpp new file mode 100644 index 0000000..3f8a3d0 --- /dev/null +++ b/tests/SimpleWindow.cpp @@ -0,0 +1,41 @@ +#include +#include + +static const char* aqLevelToString(Aquamarine::eBackendLogLevel level) { + switch (level) { + case Aquamarine::eBackendLogLevel::AQ_LOG_TRACE: return "TRACE"; + case Aquamarine::eBackendLogLevel::AQ_LOG_DEBUG: return "DEBUG"; + case Aquamarine::eBackendLogLevel::AQ_LOG_ERROR: return "ERROR"; + case Aquamarine::eBackendLogLevel::AQ_LOG_WARNING: return "WARNING"; + case Aquamarine::eBackendLogLevel::AQ_LOG_CRITICAL: return "CRITICAL"; + default: break; + } + + return "UNKNOWN"; +} + +void aqLog(Aquamarine::eBackendLogLevel level, std::string msg) { + std::cout << "[AQ] [" << aqLevelToString(level) << "] " << msg << "\n"; +} + +int main(int argc, char** argv, char** envp) { + Aquamarine::SBackendOptions options; + options.logFunction = aqLog; + + std::vector implementations; + Aquamarine::SBackendImplementationOptions waylandOptions; + waylandOptions.backendType = Aquamarine::eBackendType::AQ_BACKEND_WAYLAND; + waylandOptions.backendRequestMode = Aquamarine::eBackendRequestMode::AQ_BACKEND_REQUEST_IF_AVAILABLE; + implementations.emplace_back(waylandOptions); + + auto aqBackend = Aquamarine::CBackend::create(implementations, options); + + if (!aqBackend->start()) { + std::cout << "Failed to start the aq backend\n"; + return 1; + } + + aqBackend->enterLoop(); + + return 0; +} \ No newline at end of file