mirror of
https://github.com/hyprwm/aquamarine.git
synced 2024-12-21 19:19:49 +01:00
Wayland: Initial progress
This commit is contained in:
parent
6ccfdd74df
commit
01766c0956
13 changed files with 864 additions and 12 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -34,3 +34,6 @@
|
|||
build/
|
||||
.vscode/
|
||||
.cache/
|
||||
|
||||
protocols/*.cpp
|
||||
protocols/*.hpp
|
|
@ -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})
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
106
include/aquamarine/backend/Wayland.hpp
Normal file
106
include/aquamarine/backend/Wayland.hpp
Normal 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;
|
||||
};
|
||||
};
|
72
include/aquamarine/buffer/Buffer.hpp
Normal file
72
include/aquamarine/buffer/Buffer.hpp
Normal 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;
|
||||
};
|
||||
|
||||
};
|
|
@ -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;
|
||||
};
|
||||
}
|
66
include/aquamarine/output/Output.hpp
Normal file
66
include/aquamarine/output/Output.hpp
Normal 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
0
protocols/.gitkeep
Normal 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
265
src/backend/Wayland.cpp
Normal 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
41
src/buffer/Buffer.cpp
Normal 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
16
src/include/Shared.hpp
Normal 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
41
tests/SimpleWindow.cpp
Normal 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;
|
||||
}
|
Loading…
Reference in a new issue