Wayland: Initial progress

This commit is contained in:
Vaxry 2024-06-18 18:45:05 +02:00
parent 6ccfdd74df
commit 01766c0956
13 changed files with 864 additions and 12 deletions

3
.gitignore vendored
View File

@ -34,3 +34,6 @@
build/
.vscode/
.cache/
protocols/*.cpp
protocols/*.hpp

View File

@ -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})

View File

@ -1,8 +1,11 @@
#pragma once
#include <hyprutils/memory/SharedPtr.hpp>
#include <hyprutils/signal/Signal.hpp>
#include <vector>
#include <functional>
#include <mutex>
#include <condition_variable>
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<IBackendImplementation> implementations;
SBackendOptions options;
bool terminate = false;
std::vector<SBackendImplementationOptions> implementationOptions;
std::vector<Hyprutils::Memory::CSharedPointer<IBackendImplementation>> implementations;
SBackendOptions options;
//
struct {
std::condition_variable loopSignal;
std::mutex loopMutex;
std::atomic<bool> shouldProcess = false;
std::mutex loopRequestMutex;
std::mutex eventLock;
} m_sEventLoopInternals;
};
};

View File

@ -0,0 +1,106 @@
#pragma once
#include "./Backend.hpp"
#include "../output/Output.hpp"
#include "../input/Input.hpp"
#include <hyprutils/memory/WeakPtr.hpp>
#include <wayland-client.h>
#include <wayland.hpp>
#include <xdg-shell.hpp>
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<CWaylandBackend> backend_);
std::string name;
Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend;
struct {
Hyprutils::Memory::CSharedPointer<CWlSurface> surface;
Hyprutils::Memory::CSharedPointer<CXdgSurface> xdgSurface;
Hyprutils::Memory::CSharedPointer<CXdgToplevel> xdgToplevel;
} waylandState;
friend class CWaylandBackend;
friend class CWaylandPointer;
};
class CWaylandKeyboard : public IKeyboard {
public:
CWaylandKeyboard(Hyprutils::Memory::CSharedPointer<CWlKeyboard> keyboard_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend_);
virtual ~CWaylandKeyboard();
virtual const std::string& getName();
Hyprutils::Memory::CSharedPointer<CWlKeyboard> keyboard;
Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend;
private:
const std::string name = "wl_keyboard";
};
class CWaylandPointer : public IPointer {
public:
CWaylandPointer(Hyprutils::Memory::CSharedPointer<CWlPointer> pointer_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend_);
virtual ~CWaylandPointer();
virtual const std::string& getName();
Hyprutils::Memory::CSharedPointer<CWlPointer> pointer;
Hyprutils::Memory::CWeakPointer<CWaylandBackend> 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<CWaylandBackend> self;
private:
CWaylandBackend(Hyprutils::Memory::CSharedPointer<CBackend> backend);
void initSeat();
void initShell();
void createOutput(const std::string& szName);
//
Hyprutils::Memory::CWeakPointer<CBackend> backend;
std::vector<Hyprutils::Memory::CSharedPointer<CWaylandOutput>> outputs;
std::vector<Hyprutils::Memory::CSharedPointer<CWaylandKeyboard>> keyboards;
std::vector<Hyprutils::Memory::CSharedPointer<CWaylandPointer>> pointers;
// pointer focus
Hyprutils::Memory::CWeakPointer<CWaylandOutput> focusedOutput;
uint32_t lastEnterSerial = 0;
struct {
wl_display* display = nullptr;
// hw-s types
Hyprutils::Memory::CSharedPointer<CWlRegistry> registry;
Hyprutils::Memory::CSharedPointer<CWlSeat> seat;
Hyprutils::Memory::CSharedPointer<CXdgWmBase> xdg;
Hyprutils::Memory::CSharedPointer<CWlCompositor> compositor;
} waylandState;
friend class CBackend;
friend class CWaylandKeyboard;
friend class CWaylandPointer;
friend class CWaylandOutput;
};
};

View File

@ -0,0 +1,72 @@
#pragma once
#include <array>
#include <tuple>
#include <hyprutils/signal/Signal.hpp>
#include <hyprutils/math/Region.hpp>
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<uint32_t, 4> offsets = {0};
std::array<uint32_t, 4> strides = {0};
std::array<int, 4> 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<uint8_t*, uint32_t, size_t> 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;
};
};

View File

@ -1,13 +1,28 @@
#pragma once
#include <hyprutils/signal/Signal.hpp>
#include <hyprutils/math/Vector2D.hpp>
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;
};
}

View File

@ -0,0 +1,66 @@
#pragma once
#include <vector>
#include <hyprutils/signal/Signal.hpp>
#include <hyprutils/memory/SharedPtr.hpp>
#include <hyprutils/math/Region.hpp>
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<IOutput> parent);
Hyprutils::Math::CRegion damage;
bool enabled = false;
bool adaptiveSync = false;
eOutputPresentationMode presentationMode = AQ_OUTPUT_PRESENTATION_VSYNC;
std::vector<uint16_t> gammaLut;
Hyprutils::Math::Vector2D lastModeSize;
Hyprutils::Memory::CWeakPointer<SOutputMode> 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<Hyprutils::Memory::CSharedPointer<SOutputMode>> modes;
Hyprutils::Memory::CSharedPointer<COutputState> 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;
};
}

0
protocols/.gitkeep Normal file
View File

View File

@ -1,29 +1,92 @@
#include <aquamarine/backend/Backend.hpp>
#include <aquamarine/backend/Wayland.hpp>
#include <sys/poll.h>
#include <thread>
#include <chrono>
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<CBackend> Aquamarine::CBackend::create(const std::vector<SBackendImplementationOptions>& backends, const SBackendOptions& options) {
auto backend = SP<CBackend>(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<CWaylandBackend>(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<pollfd> 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<std::mutex> 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<std::mutex> lg(m_sEventLoopInternals.eventLock);
for (size_t i = 0; i < pollFDs.size(); ++i) {
implementations.at(i)->dispatchEvents();
}
}
}

265
src/backend/Wayland.cpp Normal file
View File

@ -0,0 +1,265 @@
#include <aquamarine/backend/Wayland.hpp>
#include <wayland.hpp>
#include <xdg-shell.hpp>
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<CBackend> 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<CWlRegistry>((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<CWlSeat>((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<CXdgWmBase>((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<CWlCompositor>((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<CWaylandOutput>(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<CWlKeyboard> keyboard_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> 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<CWlPointer> pointer_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> 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<CWaylandKeyboard>(SP<CWlKeyboard>(waylandState.seat->sendGetKeyboard()), self));
else if (!HAS_KEYBOARD && !keyboards.empty())
keyboards.clear();
if (HAS_POINTER && pointers.empty())
keyboards.emplace_back(makeShared<CWaylandKeyboard>(SP<CWlKeyboard>(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<CWaylandBackend> backend_) : name(name_), backend(backend_) {
errno = 0;
waylandState.surface = SP<CWlSurface>(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<CXdgSurface>(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<CXdgToplevel>(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() {
;
}

41
src/buffer/Buffer.cpp Normal file
View File

@ -0,0 +1,41 @@
#include <aquamarine/buffer/Buffer.hpp>
#include "Shared.hpp"
using namespace Aquamarine;
SDMABUFAttrs Aquamarine::IBuffer::dmabuf() {
return SDMABUFAttrs{};
}
SSHMAttrs Aquamarine::IBuffer::shm() {
return SSHMAttrs{};
}
std::tuple<uint8_t*, uint32_t, size_t> 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;
}

16
src/include/Shared.hpp Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include <iostream>
#include <format>
#include <signal.h>
#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, "?")

41
tests/SimpleWindow.cpp Normal file
View File

@ -0,0 +1,41 @@
#include <aquamarine/backend/Backend.hpp>
#include <iostream>
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<Aquamarine::SBackendImplementationOptions> 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;
}