mirror of
https://github.com/hyprwm/aquamarine.git
synced 2024-12-22 11:39:49 +01:00
DRM: Init DRM/Libinput session code
Adds the bare basic of DRM code, which only initializes the environment atm. We cannot render yet.
This commit is contained in:
parent
edbec62fbd
commit
f888bfb6e4
10 changed files with 1529 additions and 2 deletions
|
@ -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 pixman-1 wayland-client libdrm gbm)
|
||||
pkg_check_modules(deps REQUIRED IMPORTED_TARGET libseat libinput wayland-client wayland-protocols hyprutils>=0.1.2 pixman-1 wayland-client libdrm gbm libudev)
|
||||
|
||||
configure_file(aquamarine.pc.in aquamarine.pc @ONLY)
|
||||
|
||||
|
@ -83,7 +83,7 @@ protocolNew("stable/linux-dmabuf" "linux-dmabuf-v1" false)
|
|||
add_custom_target(tests)
|
||||
|
||||
add_executable(simpleWindow "tests/SimpleWindow.cpp")
|
||||
target_link_libraries(simpleWindow PRIVATE aquamarine PkgConfig::deps)
|
||||
target_link_libraries(simpleWindow PRIVATE PkgConfig::deps aquamarine)
|
||||
add_test(NAME "simpleWindow" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND simpleWindow "simpleWindow")
|
||||
add_dependencies(tests simpleWindow)
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <condition_variable>
|
||||
#include "../allocator/Allocator.hpp"
|
||||
#include "Misc.hpp"
|
||||
#include "Session.hpp"
|
||||
|
||||
namespace Aquamarine {
|
||||
enum eBackendType {
|
||||
|
@ -108,6 +109,7 @@ namespace Aquamarine {
|
|||
|
||||
Hyprutils::Memory::CSharedPointer<IAllocator> allocator;
|
||||
bool ready = false;
|
||||
Hyprutils::Memory::CSharedPointer<CSession> session;
|
||||
|
||||
private:
|
||||
CBackend();
|
||||
|
@ -118,6 +120,7 @@ namespace Aquamarine {
|
|||
std::vector<Hyprutils::Memory::CSharedPointer<IBackendImplementation>> implementations;
|
||||
SBackendOptions options;
|
||||
Hyprutils::Memory::CWeakPointer<CBackend> self;
|
||||
std::vector<int> sessionFDs;
|
||||
|
||||
//
|
||||
struct {
|
||||
|
|
214
include/aquamarine/backend/DRM.hpp
Normal file
214
include/aquamarine/backend/DRM.hpp
Normal file
|
@ -0,0 +1,214 @@
|
|||
#pragma once
|
||||
|
||||
#include "./Backend.hpp"
|
||||
#include "../allocator/Swapchain.hpp"
|
||||
#include "../output/Output.hpp"
|
||||
#include "../input/Input.hpp"
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include <wayland-client.h>
|
||||
#include <xf86drmMode.h>
|
||||
|
||||
namespace Aquamarine {
|
||||
class CDRMBackend;
|
||||
struct SDRMConnector;
|
||||
|
||||
struct SDRMFB {
|
||||
uint32_t id = 0;
|
||||
Hyprutils::Memory::CSharedPointer<IBuffer> buffer;
|
||||
Hyprutils::Memory::CWeakPointer<CDRMBackend> backend;
|
||||
};
|
||||
|
||||
struct SDRMLayer {
|
||||
Hyprutils::Memory::CSharedPointer<SDRMFB> current /* displayed */, queued /* submitted */, pending /* to be submitted */;
|
||||
Hyprutils::Memory::CWeakPointer<CDRMBackend> backend;
|
||||
};
|
||||
|
||||
struct SDRMPlane {
|
||||
bool init(drmModePlane* plane);
|
||||
|
||||
uint64_t type = 0;
|
||||
uint32_t id = 0;
|
||||
uint32_t initialID = 0;
|
||||
|
||||
Hyprutils::Memory::CSharedPointer<SDRMFB> current /* displayed */, queued /* submitted */;
|
||||
Hyprutils::Memory::CWeakPointer<CDRMBackend> backend;
|
||||
Hyprutils::Memory::CWeakPointer<SDRMPlane> self;
|
||||
std::vector<SDRMFormat> formats;
|
||||
|
||||
union UDRMPlaneProps {
|
||||
struct {
|
||||
uint32_t type;
|
||||
uint32_t rotation; // Not guaranteed to exist
|
||||
uint32_t in_formats; // Not guaranteed to exist
|
||||
|
||||
// atomic-modesetting only
|
||||
|
||||
uint32_t src_x;
|
||||
uint32_t src_y;
|
||||
uint32_t src_w;
|
||||
uint32_t src_h;
|
||||
uint32_t crtc_x;
|
||||
uint32_t crtc_y;
|
||||
uint32_t crtc_w;
|
||||
uint32_t crtc_h;
|
||||
uint32_t fb_id;
|
||||
uint32_t crtc_id;
|
||||
uint32_t fb_damage_clips;
|
||||
uint32_t hotspot_x;
|
||||
uint32_t hotspot_y;
|
||||
};
|
||||
uint32_t props[16] = {0};
|
||||
};
|
||||
UDRMPlaneProps props;
|
||||
};
|
||||
|
||||
struct SDRMCRTC {
|
||||
uint32_t id = 0;
|
||||
std::vector<SDRMLayer> layers;
|
||||
int32_t refresh = 0;
|
||||
|
||||
struct {
|
||||
int gammaSize = 0;
|
||||
} legacy;
|
||||
|
||||
Hyprutils::Memory::CSharedPointer<SDRMPlane> primary;
|
||||
Hyprutils::Memory::CSharedPointer<SDRMPlane> cursor;
|
||||
Hyprutils::Memory::CWeakPointer<CDRMBackend> backend;
|
||||
|
||||
union UDRMCRTCProps {
|
||||
struct {
|
||||
// None of these are guaranteed to exist
|
||||
uint32_t vrr_enabled;
|
||||
uint32_t gamma_lut;
|
||||
uint32_t gamma_lut_size;
|
||||
|
||||
// atomic-modesetting only
|
||||
|
||||
uint32_t active;
|
||||
uint32_t mode_id;
|
||||
};
|
||||
uint32_t props[6] = {0};
|
||||
};
|
||||
UDRMCRTCProps props;
|
||||
};
|
||||
|
||||
class CDRMOutput : public IOutput {
|
||||
public:
|
||||
virtual ~CDRMOutput();
|
||||
virtual bool commit();
|
||||
virtual bool test();
|
||||
virtual Hyprutils::Memory::CSharedPointer<IBackendImplementation> getBackend();
|
||||
virtual bool setCursor(Hyprutils::Memory::CSharedPointer<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot);
|
||||
virtual void moveCursor(const Hyprutils::Math::Vector2D& coord);
|
||||
virtual void scheduleFrame();
|
||||
virtual Hyprutils::Math::Vector2D maxCursorSize();
|
||||
|
||||
Hyprutils::Memory::CWeakPointer<CDRMOutput> self;
|
||||
|
||||
private:
|
||||
CDRMOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer<CDRMBackend> backend_, Hyprutils::Memory::CSharedPointer<SDRMConnector> connector_);
|
||||
|
||||
Hyprutils::Memory::CWeakPointer<CDRMBackend> backend;
|
||||
Hyprutils::Memory::CSharedPointer<SDRMConnector> connector;
|
||||
|
||||
friend struct SDRMConnector;
|
||||
};
|
||||
|
||||
struct SDRMConnector {
|
||||
~SDRMConnector();
|
||||
|
||||
bool init(drmModeConnector* connector);
|
||||
void connect(drmModeConnector* connector);
|
||||
void disconnect();
|
||||
Hyprutils::Memory::CSharedPointer<SDRMCRTC> getCurrentCRTC(const drmModeConnector* connector);
|
||||
drmModeModeInfo* getCurrentMode();
|
||||
void parseEDID(std::vector<uint8_t> data);
|
||||
|
||||
Hyprutils::Memory::CSharedPointer<CDRMOutput> output;
|
||||
Hyprutils::Memory::CWeakPointer<CDRMBackend> backend;
|
||||
Hyprutils::Memory::CWeakPointer<SDRMConnector> self;
|
||||
std::string szName;
|
||||
drmModeConnection status = DRM_MODE_DISCONNECTED;
|
||||
uint32_t id = 0;
|
||||
std::array<uint64_t, 2> maxBpcBounds = {0, 0};
|
||||
Hyprutils::Memory::CSharedPointer<SDRMCRTC> crtc;
|
||||
int32_t refresh = 0;
|
||||
uint32_t possibleCrtcs = 0;
|
||||
std::string make, serial, model;
|
||||
|
||||
bool cursorEnabled = false;
|
||||
Hyprutils::Math::Vector2D cursorPos, cursorSize, cursorHotspot;
|
||||
Hyprutils::Memory::CSharedPointer<SDRMFB> pendingCursorFB;
|
||||
|
||||
union UDRMConnectorProps {
|
||||
struct {
|
||||
uint32_t edid;
|
||||
uint32_t dpms;
|
||||
uint32_t link_status; // not guaranteed to exist
|
||||
uint32_t path;
|
||||
uint32_t vrr_capable; // not guaranteed to exist
|
||||
uint32_t subconnector; // not guaranteed to exist
|
||||
uint32_t non_desktop;
|
||||
uint32_t panel_orientation; // not guaranteed to exist
|
||||
uint32_t content_type; // not guaranteed to exist
|
||||
uint32_t max_bpc; // not guaranteed to exist
|
||||
|
||||
// atomic-modesetting only
|
||||
|
||||
uint32_t crtc_id;
|
||||
};
|
||||
uint32_t props[4] = {0};
|
||||
};
|
||||
UDRMConnectorProps props;
|
||||
};
|
||||
|
||||
class CDRMBackend : public IBackendImplementation {
|
||||
public:
|
||||
virtual ~CDRMBackend();
|
||||
virtual eBackendType type();
|
||||
virtual bool start();
|
||||
virtual int pollFD();
|
||||
virtual int drmFD();
|
||||
virtual bool dispatchEvents();
|
||||
virtual uint32_t capabilities();
|
||||
virtual bool setCursor(Hyprutils::Memory::CSharedPointer<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot);
|
||||
virtual void onReady();
|
||||
virtual std::vector<SDRMFormat> getRenderFormats();
|
||||
virtual std::vector<SDRMFormat> getCursorFormats();
|
||||
|
||||
Hyprutils::Memory::CWeakPointer<CDRMBackend> self;
|
||||
|
||||
private:
|
||||
CDRMBackend(Hyprutils::Memory::CSharedPointer<CBackend> backend);
|
||||
|
||||
static Hyprutils::Memory::CSharedPointer<CDRMBackend> attempt(Hyprutils::Memory::CSharedPointer<CBackend> backend);
|
||||
bool registerGPU(Hyprutils::Memory::CSharedPointer<CSessionDevice> gpu_, Hyprutils::Memory::CSharedPointer<CDRMBackend> primary_ = {});
|
||||
bool checkFeatures();
|
||||
bool initResources();
|
||||
bool grabFormats();
|
||||
void scanConnectors();
|
||||
|
||||
Hyprutils::Memory::CSharedPointer<CSessionDevice> gpu;
|
||||
Hyprutils::Memory::CWeakPointer<CDRMBackend> primary;
|
||||
|
||||
Hyprutils::Memory::CWeakPointer<CBackend> backend;
|
||||
|
||||
std::vector<Hyprutils::Memory::CSharedPointer<SDRMCRTC>> crtcs;
|
||||
std::vector<Hyprutils::Memory::CSharedPointer<SDRMPlane>> planes;
|
||||
std::vector<Hyprutils::Memory::CSharedPointer<SDRMConnector>> connectors;
|
||||
std::vector<SDRMFormat> formats;
|
||||
|
||||
struct {
|
||||
Hyprutils::Math::Vector2D cursorSize;
|
||||
bool supportsAsyncCommit = false;
|
||||
bool supportsAddFb2Modifiers = false;
|
||||
} drmProps;
|
||||
|
||||
friend class CBackend;
|
||||
friend struct SDRMFB;
|
||||
friend struct SDRMConnector;
|
||||
friend struct SDRMCRTC;
|
||||
friend struct SDRMPlane;
|
||||
friend struct CDRMOutput;
|
||||
};
|
||||
};
|
11
include/aquamarine/backend/Misc.hpp
Normal file
11
include/aquamarine/backend/Misc.hpp
Normal file
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Aquamarine {
|
||||
struct SDRMFormat {
|
||||
uint32_t drmFormat = 0; /* DRM_FORMAT_INVALID */
|
||||
std::vector<uint64_t> modifiers;
|
||||
};
|
||||
};
|
90
include/aquamarine/backend/Session.hpp
Normal file
90
include/aquamarine/backend/Session.hpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
#pragma once
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <hyprutils/signal/Signal.hpp>
|
||||
#include <hyprutils/memory/SharedPtr.hpp>
|
||||
#include <vector>
|
||||
|
||||
struct udev;
|
||||
struct udev_monitor;
|
||||
struct udev_device;
|
||||
struct libseat;
|
||||
|
||||
namespace Aquamarine {
|
||||
class CBackend;
|
||||
class CSession;
|
||||
|
||||
class CSessionDevice {
|
||||
public:
|
||||
CSessionDevice(Hyprutils::Memory::CSharedPointer<CSession> session_, const std::string& path_);
|
||||
~CSessionDevice();
|
||||
|
||||
static Hyprutils::Memory::CSharedPointer<CSessionDevice> openIfKMS(Hyprutils::Memory::CSharedPointer<CSession> session_, const std::string& path_);
|
||||
|
||||
bool supportsKMS();
|
||||
|
||||
int fd = -1;
|
||||
int deviceID = -1;
|
||||
dev_t dev;
|
||||
std::string path;
|
||||
|
||||
enum eChangeEventType {
|
||||
AQ_SESSION_EVENT_CHANGE_HOTPLUG = 0,
|
||||
AQ_SESSION_EVENT_CHANGE_LEASE,
|
||||
};
|
||||
|
||||
struct SChangeEvent {
|
||||
eChangeEventType type = AQ_SESSION_EVENT_CHANGE_HOTPLUG;
|
||||
|
||||
struct {
|
||||
uint32_t connectorID = 0, propID = 0;
|
||||
} hotplug;
|
||||
};
|
||||
|
||||
struct {
|
||||
Hyprutils::Signal::CSignal change;
|
||||
Hyprutils::Signal::CSignal remove;
|
||||
} events;
|
||||
|
||||
private:
|
||||
Hyprutils::Memory::CWeakPointer<CSession> session;
|
||||
};
|
||||
|
||||
class CSession {
|
||||
public:
|
||||
~CSession();
|
||||
|
||||
static Hyprutils::Memory::CSharedPointer<CSession> attempt(Hyprutils::Memory::CSharedPointer<CBackend> backend_);
|
||||
|
||||
bool active = true; // whether the current vt is ours
|
||||
uint32_t vt = 0; // 0 means unsupported
|
||||
std::string seatName;
|
||||
|
||||
std::vector<Hyprutils::Memory::CSharedPointer<CSessionDevice>> sessionDevices;
|
||||
|
||||
udev* udevHandle = nullptr;
|
||||
udev_monitor* udevMonitor = nullptr;
|
||||
libseat* libseatHandle = nullptr;
|
||||
|
||||
std::vector<int> pollFDs();
|
||||
|
||||
void dispatchPendingEventsAsync();
|
||||
|
||||
struct SAddDrmCardEvent {
|
||||
std::string path;
|
||||
};
|
||||
|
||||
struct {
|
||||
Hyprutils::Signal::CSignal changeActive;
|
||||
Hyprutils::Signal::CSignal addDrmCard;
|
||||
Hyprutils::Signal::CSignal destroy;
|
||||
} events;
|
||||
|
||||
private:
|
||||
Hyprutils::Memory::CWeakPointer<CBackend> backend;
|
||||
|
||||
void dispatchUdevEvents();
|
||||
|
||||
friend class CSessionDevice;
|
||||
};
|
||||
};
|
|
@ -1,5 +1,6 @@
|
|||
#include <aquamarine/backend/Backend.hpp>
|
||||
#include <aquamarine/backend/Wayland.hpp>
|
||||
#include <aquamarine/backend/DRM.hpp>
|
||||
#include <aquamarine/allocator/GBM.hpp>
|
||||
#include <sys/poll.h>
|
||||
#include <thread>
|
||||
|
@ -49,6 +50,14 @@ Hyprutils::Memory::CSharedPointer<CBackend> Aquamarine::CBackend::create(const s
|
|||
auto ref = SP<CWaylandBackend>(new CWaylandBackend(backend));
|
||||
backend->implementations.emplace_back(ref);
|
||||
ref->self = ref;
|
||||
} else if (b.backendType == AQ_BACKEND_DRM) {
|
||||
auto ref = CDRMBackend::attempt(backend);
|
||||
if (!ref) {
|
||||
backend->log(AQ_LOG_ERROR, "DRM Backend failed");
|
||||
continue;
|
||||
}
|
||||
backend->implementations.emplace_back(ref);
|
||||
ref->self = ref;
|
||||
} else {
|
||||
backend->log(AQ_LOG_ERROR, std::format("Unknown backend id: {}", (int)b.backendType));
|
||||
continue;
|
||||
|
@ -108,6 +117,8 @@ bool Aquamarine::CBackend::start() {
|
|||
b->onReady();
|
||||
}
|
||||
|
||||
sessionFDs = session->pollFDs();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -186,6 +197,10 @@ std::vector<int> Aquamarine::CBackend::getPollFDs() {
|
|||
result.push_back(fd);
|
||||
}
|
||||
|
||||
for (auto& sfd : sessionFDs) {
|
||||
result.push_back(sfd);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -204,6 +219,9 @@ void Aquamarine::CBackend::dispatchEventsAsync() {
|
|||
for (auto& i : implementations) {
|
||||
i->dispatchEvents();
|
||||
}
|
||||
|
||||
if (session)
|
||||
session->dispatchPendingEventsAsync();
|
||||
}
|
||||
|
||||
bool Aquamarine::CBackend::hasSession() {
|
||||
|
|
235
src/backend/Session.cpp
Normal file
235
src/backend/Session.cpp
Normal file
|
@ -0,0 +1,235 @@
|
|||
#include <aquamarine/backend/Backend.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <libseat.h>
|
||||
#include <libudev.h>
|
||||
#include <cstring>
|
||||
#include <xf86drm.h>
|
||||
#include <sys/stat.h>
|
||||
#include <xf86drmMode.h>
|
||||
}
|
||||
|
||||
using namespace Aquamarine;
|
||||
using namespace Hyprutils::Memory;
|
||||
#define SP CSharedPointer
|
||||
|
||||
// we can't really do better with libseat logs
|
||||
// because they don't allow us to pass "data" or anything...
|
||||
// Nobody should create multiple backends anyways really
|
||||
Hyprutils::Memory::CSharedPointer<CBackend> backendInUse;
|
||||
|
||||
static Aquamarine::eBackendLogLevel logLevelFromLibseat(enum libseat_log_level level) {
|
||||
switch (level) {
|
||||
case LIBSEAT_LOG_LEVEL_ERROR: return AQ_LOG_ERROR;
|
||||
case LIBSEAT_LOG_LEVEL_SILENT: return AQ_LOG_TRACE;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return AQ_LOG_DEBUG;
|
||||
}
|
||||
|
||||
static void libseatLog(libseat_log_level level, const char* fmt, va_list args) {
|
||||
if (!backendInUse)
|
||||
return;
|
||||
|
||||
static char string[1024];
|
||||
vsnprintf(string, sizeof(string), fmt, args);
|
||||
|
||||
backendInUse->log(logLevelFromLibseat(level), std::format("[libseat] {}", string));
|
||||
}
|
||||
|
||||
static void libseatEnableSeat(struct libseat* seat, void* data) {
|
||||
auto PSESSION = (Aquamarine::CSession*)data;
|
||||
PSESSION->active = true;
|
||||
PSESSION->events.changeActive.emit();
|
||||
}
|
||||
|
||||
static void libseatDisableSeat(struct libseat* seat, void* data) {
|
||||
auto PSESSION = (Aquamarine::CSession*)data;
|
||||
PSESSION->active = false;
|
||||
PSESSION->events.changeActive.emit();
|
||||
libseat_disable_seat(PSESSION->libseatHandle);
|
||||
}
|
||||
|
||||
static const libseat_seat_listener libseatListener = {
|
||||
.enable_seat = libseatEnableSeat,
|
||||
.disable_seat = libseatDisableSeat,
|
||||
};
|
||||
|
||||
Aquamarine::CSessionDevice::CSessionDevice(Hyprutils::Memory::CSharedPointer<CSession> session_, const std::string& path_) : session(session_), path(path_) {
|
||||
deviceID = libseat_open_device(session->libseatHandle, path.c_str(), &fd);
|
||||
if (deviceID < 0) {
|
||||
session->backend->log(AQ_LOG_ERROR, std::format("libseat: Couldn't open device at {}", path_));
|
||||
return;
|
||||
}
|
||||
|
||||
struct stat stat_;
|
||||
if (fstat(fd, &stat_) < 0) {
|
||||
session->backend->log(AQ_LOG_ERROR, std::format("libseat: Couldn't stat device at {}", path_));
|
||||
deviceID = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
dev = stat_.st_rdev;
|
||||
}
|
||||
|
||||
Aquamarine::CSessionDevice::~CSessionDevice() {
|
||||
if (fd < 0)
|
||||
return;
|
||||
libseat_close_device(session->libseatHandle, fd);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
bool Aquamarine::CSessionDevice::supportsKMS() {
|
||||
if (deviceID < 0)
|
||||
return false;
|
||||
|
||||
bool kms = drmIsKMS(fd);
|
||||
|
||||
if (kms)
|
||||
session->backend->log(AQ_LOG_DEBUG, std::format("libseat: Device {} supports kms", path));
|
||||
else
|
||||
session->backend->log(AQ_LOG_DEBUG, std::format("libseat: Device {} does not support kms", path));
|
||||
|
||||
return kms;
|
||||
}
|
||||
|
||||
SP<CSessionDevice> Aquamarine::CSessionDevice::openIfKMS(SP<CSession> session_, const std::string& path_) {
|
||||
auto dev = makeShared<CSessionDevice>(session_, path_);
|
||||
if (!dev->supportsKMS())
|
||||
return nullptr;
|
||||
return dev;
|
||||
}
|
||||
|
||||
SP<CSession> Aquamarine::CSession::attempt(Hyprutils::Memory::CSharedPointer<CBackend> backend_) {
|
||||
if (!backend_)
|
||||
return nullptr;
|
||||
|
||||
auto session = makeShared<CSession>();
|
||||
session->backend = backend_;
|
||||
backendInUse = backend_;
|
||||
|
||||
// ------------ Libseat
|
||||
|
||||
libseat_set_log_handler(libseatLog);
|
||||
libseat_set_log_level(LIBSEAT_LOG_LEVEL_INFO);
|
||||
|
||||
session->libseatHandle = libseat_open_seat(&libseatListener, session.get());
|
||||
|
||||
if (!session->libseatHandle) {
|
||||
session->backend->log(AQ_LOG_ERROR, "libseat: failed to open a seat");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto seatName = libseat_seat_name(session->libseatHandle);
|
||||
if (!seatName) {
|
||||
session->backend->log(AQ_LOG_ERROR, "libseat: failed to get seat name");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
session->seatName = seatName;
|
||||
|
||||
// dispatch any already pending events
|
||||
session->dispatchPendingEventsAsync();
|
||||
|
||||
// ----------- Udev
|
||||
|
||||
session->udevHandle = udev_new();
|
||||
if (!session->udevHandle) {
|
||||
session->backend->log(AQ_LOG_ERROR, "udev: failed to create a new context");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
session->udevMonitor = udev_monitor_new_from_netlink(session->udevHandle, "udev");
|
||||
if (!session->udevMonitor) {
|
||||
session->backend->log(AQ_LOG_ERROR, "udev: failed to create a new udevMonitor");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
udev_monitor_filter_add_match_subsystem_devtype(session->udevMonitor, "drm", nullptr);
|
||||
udev_monitor_enable_receiving(session->udevMonitor);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
Aquamarine::CSession::~CSession() {
|
||||
sessionDevices.clear();
|
||||
|
||||
if (libseatHandle)
|
||||
libseat_close_seat(libseatHandle);
|
||||
if (udevMonitor)
|
||||
udev_monitor_unref(udevMonitor);
|
||||
if (udevHandle)
|
||||
udev_unref(udevHandle);
|
||||
|
||||
libseatHandle = nullptr;
|
||||
udevMonitor = nullptr;
|
||||
udevHandle = nullptr;
|
||||
}
|
||||
|
||||
static bool isDRMCard(const char* sysname) {
|
||||
const char prefix[] = DRM_PRIMARY_MINOR_NAME;
|
||||
if (strncmp(sysname, prefix, strlen(prefix)) != 0)
|
||||
return false;
|
||||
|
||||
for (size_t i = strlen(prefix); sysname[i] != '\0'; i++) {
|
||||
if (sysname[i] < '0' || sysname[i] > '9')
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Aquamarine::CSession::dispatchUdevEvents() {
|
||||
auto device = udev_monitor_receive_device(udevMonitor);
|
||||
|
||||
if (!device)
|
||||
return;
|
||||
|
||||
auto sysname = udev_device_get_sysname(device);
|
||||
auto devnode = udev_device_get_devnode(device);
|
||||
auto action = udev_device_get_action(device);
|
||||
|
||||
backend->log(AQ_LOG_DEBUG, std::format("udev: new udev {} event for {}", action ? action : "unknown", sysname ? sysname : "unknown"));
|
||||
|
||||
if (!isDRMCard(sysname) || !action || !devnode) {
|
||||
udev_device_unref(device);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action == std::string{"add"})
|
||||
events.addDrmCard.emit(SAddDrmCardEvent{.path = devnode});
|
||||
else if (action == std::string{"change"} || action == std::string{"remove"}) {
|
||||
dev_t deviceNum = udev_device_get_devnum(device);
|
||||
|
||||
for (auto& d : sessionDevices) {
|
||||
if (d->dev != deviceNum)
|
||||
continue;
|
||||
|
||||
if (action == std::string{"change"}) {
|
||||
backend->log(AQ_LOG_DEBUG, std::format("udev: DRM device {} changed", sysname ? sysname : "unknown"));
|
||||
backend->log(AQ_LOG_ERROR, "udev: FIXME: change event is a STUB");
|
||||
} else if (action == std::string{"remove"}) {
|
||||
backend->log(AQ_LOG_DEBUG, std::format("udev: DRM device {} removed", sysname ? sysname : "unknown"));
|
||||
backend->log(AQ_LOG_ERROR, "udev: FIXME: remove event is a STUB");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
udev_device_unref(device);
|
||||
return;
|
||||
}
|
||||
|
||||
void Aquamarine::CSession::dispatchPendingEventsAsync() {
|
||||
if (libseat_dispatch(libseatHandle, 0) == -1)
|
||||
backend->log(AQ_LOG_ERROR, "Couldn't dispatch libseat events");
|
||||
|
||||
dispatchUdevEvents();
|
||||
}
|
||||
|
||||
std::vector<int> Aquamarine::CSession::pollFDs() {
|
||||
if (!libseatHandle || !udevMonitor || !udevHandle)
|
||||
return {};
|
||||
|
||||
return {libseat_get_fd(libseatHandle), udev_monitor_get_fd(udevMonitor)};
|
||||
}
|
760
src/backend/drm/DRM.cpp
Normal file
760
src/backend/drm/DRM.cpp
Normal file
|
@ -0,0 +1,760 @@
|
|||
#include <aquamarine/backend/DRM.hpp>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <deque>
|
||||
#include <cstring>
|
||||
|
||||
extern "C" {
|
||||
#include <libseat.h>
|
||||
#include <libudev.h>
|
||||
#include <xf86drm.h>
|
||||
#include <xf86drmMode.h>
|
||||
}
|
||||
|
||||
#include "Props.hpp"
|
||||
|
||||
using namespace Aquamarine;
|
||||
using namespace Hyprutils::Memory;
|
||||
using namespace Hyprutils::Math;
|
||||
#define SP CSharedPointer
|
||||
|
||||
Aquamarine::CDRMBackend::CDRMBackend(SP<CBackend> backend_) : backend(backend_) {
|
||||
;
|
||||
}
|
||||
|
||||
static udev_enumerate* enumDRMCards(udev* udev) {
|
||||
auto enumerate = udev_enumerate_new(udev);
|
||||
if (!enumerate)
|
||||
return nullptr;
|
||||
|
||||
udev_enumerate_add_match_subsystem(enumerate, "drm");
|
||||
udev_enumerate_add_match_sysname(enumerate, DRM_PRIMARY_MINOR_NAME "[0-9]*");
|
||||
|
||||
if (udev_enumerate_scan_devices(enumerate)) {
|
||||
udev_enumerate_unref(enumerate);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return enumerate;
|
||||
}
|
||||
|
||||
static std::vector<SP<CSessionDevice>> scanGPUs(SP<CBackend> backend) {
|
||||
// FIXME: This provides no explicit way to set a preferred gpu
|
||||
|
||||
auto enumerate = enumDRMCards(backend->session->udevHandle);
|
||||
|
||||
if (!enumerate) {
|
||||
backend->log(AQ_LOG_ERROR, "drm: couldn't enumerate gpus with udev");
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!udev_enumerate_get_list_entry(enumerate)) {
|
||||
// TODO: wait for them.
|
||||
backend->log(AQ_LOG_ERROR, "drm: No gpus in scanGPUs.");
|
||||
return {};
|
||||
}
|
||||
|
||||
udev_list_entry* entry = nullptr;
|
||||
size_t i = 0;
|
||||
|
||||
std::deque<SP<CSessionDevice>> devices;
|
||||
|
||||
udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(enumerate)) {
|
||||
auto path = udev_list_entry_get_name(entry);
|
||||
auto device = udev_device_new_from_syspath(backend->session->udevHandle, path);
|
||||
if (!device) {
|
||||
backend->log(AQ_LOG_WARNING, std::format("drm: Skipping device {}", path ? path : "unknown"));
|
||||
continue;
|
||||
}
|
||||
|
||||
backend->log(AQ_LOG_DEBUG, std::format("drm: Enumerated device {}", path ? path : "unknown"));
|
||||
|
||||
auto seat = udev_device_get_property_value(device, "ID_SEAT");
|
||||
if (!seat)
|
||||
seat = "seat0";
|
||||
|
||||
if (!backend->session->seatName.empty() && backend->session->seatName != seat) {
|
||||
backend->log(AQ_LOG_WARNING, std::format("drm: Skipping device {} because seat {} doesn't match our {}", path ? path : "unknown", seat, backend->session->seatName));
|
||||
udev_device_unref(device);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto pciDevice = udev_device_get_parent_with_subsystem_devtype(device, "pci", nullptr);
|
||||
bool isBootVGA = false;
|
||||
if (pciDevice) {
|
||||
auto id = udev_device_get_sysattr_value(pciDevice, "boot_vga");
|
||||
isBootVGA = id && id == std::string{"1"};
|
||||
}
|
||||
|
||||
if (!udev_device_get_devnode(device)) {
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: Skipping device {}, no devnode", path ? path : "unknown"));
|
||||
udev_device_unref(device);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto sessionDevice = CSessionDevice::openIfKMS(backend->session, udev_device_get_devnode(device));
|
||||
if (!sessionDevice) {
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: Skipping device {}, not a KMS device", path ? path : "unknown"));
|
||||
udev_device_unref(device);
|
||||
continue;
|
||||
}
|
||||
|
||||
udev_device_unref(device);
|
||||
|
||||
if (isBootVGA)
|
||||
devices.push_front(sessionDevice);
|
||||
else
|
||||
devices.push_back(sessionDevice);
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
udev_enumerate_unref(enumerate);
|
||||
|
||||
std::vector<SP<CSessionDevice>> vecDevices;
|
||||
for (auto& d : devices) {
|
||||
vecDevices.push_back(d);
|
||||
}
|
||||
|
||||
return vecDevices;
|
||||
}
|
||||
|
||||
SP<CDRMBackend> Aquamarine::CDRMBackend::attempt(SP<CBackend> backend) {
|
||||
if (!backend->session)
|
||||
backend->session = CSession::attempt(backend);
|
||||
|
||||
if (!backend->session) {
|
||||
backend->log(AQ_LOG_ERROR, "Failed to open a session");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!backend->session->active) {
|
||||
backend->log(AQ_LOG_DEBUG, "Session is not active, waiting for 5s");
|
||||
|
||||
auto started = std::chrono::system_clock::now();
|
||||
|
||||
while (!backend->session->active) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
backend->session->dispatchPendingEventsAsync();
|
||||
|
||||
if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - started).count() >= 5000) {
|
||||
backend->log(AQ_LOG_DEBUG, "Session timeout reached");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!backend->session->active) {
|
||||
backend->log(AQ_LOG_DEBUG, "Session could not be activated in time");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto gpus = scanGPUs(backend);
|
||||
|
||||
if (gpus.empty()) {
|
||||
backend->log(AQ_LOG_ERROR, "drm: Found no gpus to use, cannot continue");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
backend->log(AQ_LOG_DEBUG, std::format("drm: Found {} GPUs", gpus.size()));
|
||||
|
||||
// FIXME: this will ignore multi-gpu setups and only create one backend
|
||||
auto drmBackend = SP<CDRMBackend>(new CDRMBackend(backend));
|
||||
drmBackend->self = drmBackend;
|
||||
|
||||
if (!drmBackend->registerGPU(gpus.at(0))) {
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: Failed to register gpu at fd {}", gpus[0]->fd));
|
||||
return nullptr;
|
||||
} else
|
||||
backend->log(AQ_LOG_DEBUG, std::format("drm: Registered gpu at fd {}", gpus[0]->fd));
|
||||
|
||||
// TODO: consider listening for new devices
|
||||
// But if you expect me to handle gpu hotswaps you are probably insane LOL
|
||||
|
||||
if (!drmBackend->checkFeatures()) {
|
||||
backend->log(AQ_LOG_ERROR, "drm: Failed checking features");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!drmBackend->initResources()) {
|
||||
backend->log(AQ_LOG_ERROR, "drm: Failed initializing resources");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
backend->log(AQ_LOG_DEBUG, std::format("drm: Basic init pass for gpu {}", gpus[0]->path));
|
||||
|
||||
drmBackend->grabFormats();
|
||||
|
||||
drmBackend->scanConnectors();
|
||||
|
||||
return drmBackend;
|
||||
}
|
||||
|
||||
Aquamarine::CDRMBackend::~CDRMBackend() {
|
||||
;
|
||||
}
|
||||
|
||||
bool Aquamarine::CDRMBackend::checkFeatures() {
|
||||
uint64_t curW = 0, curH = 0;
|
||||
if (drmGetCap(gpu->fd, DRM_CAP_CURSOR_WIDTH, &curW))
|
||||
curW = 64;
|
||||
if (drmGetCap(gpu->fd, DRM_CAP_CURSOR_HEIGHT, &curH))
|
||||
curH = 64;
|
||||
|
||||
drmProps.cursorSize = Hyprutils::Math::Vector2D{(double)curW, (double)curH};
|
||||
|
||||
uint64_t cap = 0;
|
||||
if (drmGetCap(gpu->fd, DRM_CAP_PRIME, &cap) || !(cap & DRM_PRIME_CAP_IMPORT)) {
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: DRM_PRIME_CAP_IMPORT unsupported"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (drmGetCap(gpu->fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap) || !cap) {
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: DRM_CAP_CRTC_IN_VBLANK_EVENT unsupported"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (drmGetCap(gpu->fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap) || !cap) {
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: DRM_PRIME_CAP_IMPORT unsupported"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (drmSetClientCap(gpu->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) {
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: DRM_CLIENT_CAP_UNIVERSAL_PLANES unsupported"));
|
||||
return false;
|
||||
}
|
||||
|
||||
drmProps.supportsAsyncCommit = drmGetCap(gpu->fd, DRM_CAP_ASYNC_PAGE_FLIP, &cap) == 0 && cap == 1;
|
||||
drmProps.supportsAddFb2Modifiers = drmGetCap(gpu->fd, DRM_CAP_ADDFB2_MODIFIERS, &cap) == 0 && cap == 1;
|
||||
|
||||
// TODO: allow no-modifiers?
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Aquamarine::CDRMBackend::initResources() {
|
||||
auto resources = drmModeGetResources(gpu->fd);
|
||||
if (!resources) {
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetResources failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
backend->log(AQ_LOG_DEBUG, std::format("drm: found {} CRTCs", resources->count_crtcs));
|
||||
|
||||
for (size_t i = 0; i < resources->count_crtcs; ++i) {
|
||||
auto CRTC = makeShared<SDRMCRTC>();
|
||||
CRTC->id = resources->crtcs[i];
|
||||
CRTC->backend = self;
|
||||
|
||||
auto drmCRTC = drmModeGetCrtc(gpu->fd, CRTC->id);
|
||||
if (!drmCRTC) {
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetCrtc for crtc {} failed", CRTC->id));
|
||||
drmModeFreeResources(resources);
|
||||
crtcs.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
CRTC->legacy.gammaSize = drmCRTC->gamma_size;
|
||||
drmModeFreeCrtc(drmCRTC);
|
||||
|
||||
if (!getDRMCRTCProps(gpu->fd, CRTC->id, &CRTC->props)) {
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: getDRMCRTCProps for crtc {} failed", CRTC->id));
|
||||
drmModeFreeResources(resources);
|
||||
crtcs.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
crtcs.emplace_back(CRTC);
|
||||
}
|
||||
|
||||
if (crtcs.size() > 32) {
|
||||
backend->log(AQ_LOG_CRITICAL, "drm: Cannot support more than 32 CRTCs");
|
||||
return false;
|
||||
}
|
||||
|
||||
// initialize planes
|
||||
auto planeResources = drmModeGetPlaneResources(gpu->fd);
|
||||
if (!planeResources) {
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetPlaneResources failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
backend->log(AQ_LOG_DEBUG, std::format("drm: found {} planes", planeResources->count_planes));
|
||||
|
||||
for (uint32_t i = 0; i < planeResources->count_planes; ++i) {
|
||||
auto id = planeResources->planes[i];
|
||||
auto plane = drmModeGetPlane(gpu->fd, id);
|
||||
if (!plane) {
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetPlane for plane {} failed", id));
|
||||
drmModeFreeResources(resources);
|
||||
crtcs.clear();
|
||||
planes.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto aqPlane = makeShared<SDRMPlane>();
|
||||
aqPlane->backend = self;
|
||||
aqPlane->self = aqPlane;
|
||||
if (!aqPlane->init((drmModePlane*)plane)) {
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: aqPlane->init for plane {} failed", id));
|
||||
drmModeFreeResources(resources);
|
||||
crtcs.clear();
|
||||
planes.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
drmModeFreePlane(plane);
|
||||
}
|
||||
|
||||
drmModeFreePlaneResources(planeResources);
|
||||
drmModeFreeResources(resources);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Aquamarine::CDRMBackend::grabFormats() {
|
||||
// FIXME: do this properly maybe?
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Aquamarine::CDRMBackend::registerGPU(SP<CSessionDevice> gpu_, SP<CDRMBackend> primary_) {
|
||||
gpu = gpu_;
|
||||
primary = primary_;
|
||||
|
||||
auto drmName = drmGetDeviceNameFromFd2(gpu->fd);
|
||||
auto drmVer = drmGetVersion(gpu->fd);
|
||||
|
||||
backend->log(AQ_LOG_DEBUG, std::format("drm: Starting backend for {}, with driver {}", drmName ? drmName : "unknown", drmVer->name ? drmVer->name : "unknown"));
|
||||
|
||||
drmFreeVersion(drmVer);
|
||||
|
||||
// FIXME: listen to change and remove events from session
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
eBackendType Aquamarine::CDRMBackend::type() {
|
||||
return eBackendType::AQ_BACKEND_DRM;
|
||||
}
|
||||
|
||||
void Aquamarine::CDRMBackend::scanConnectors() {
|
||||
backend->log(AQ_LOG_DEBUG, std::format("drm: Scanning connectors for {}", gpu->path));
|
||||
|
||||
auto resources = drmModeGetResources(gpu->fd);
|
||||
if (!resources) {
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: Scanning connectors for {} failed", gpu->path));
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < resources->count_connectors; ++i) {
|
||||
uint32_t connectorID = resources->connectors[i];
|
||||
|
||||
SP<SDRMConnector> conn;
|
||||
auto drmConn = drmModeGetConnector(gpu->fd, connectorID);
|
||||
|
||||
backend->log(AQ_LOG_DEBUG, std::format("drm: Scanning connector id {}", connectorID));
|
||||
|
||||
if (!drmConn) {
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: Failed to get connector id {}", connectorID));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = std::find_if(connectors.begin(), connectors.end(), [connectorID](const auto& e) { return e->id == connectorID; });
|
||||
if (it == connectors.end()) {
|
||||
backend->log(AQ_LOG_DEBUG, std::format("drm: Initializing connector id {}", connectorID));
|
||||
conn = connectors.emplace_back(SP<SDRMConnector>(new SDRMConnector()));
|
||||
conn->self = conn;
|
||||
conn->backend = self;
|
||||
if (!conn->init(drmConn)) {
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: Connector id {} failed initializing", connectorID));
|
||||
connectors.pop_back();
|
||||
continue;
|
||||
}
|
||||
} else
|
||||
conn = *it;
|
||||
|
||||
backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} connection state:", (int)drmConn->connection));
|
||||
|
||||
if (conn->status == DRM_MODE_DISCONNECTED && drmConn->connection == DRM_MODE_CONNECTED) {
|
||||
backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} connected", conn->szName));
|
||||
conn->connect(drmConn);
|
||||
} else if (conn->status == DRM_MODE_CONNECTED && drmConn->connection == DRM_MODE_DISCONNECTED) {
|
||||
backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} disconnected", conn->szName));
|
||||
conn->disconnect();
|
||||
}
|
||||
|
||||
drmModeFreeConnector(drmConn);
|
||||
}
|
||||
|
||||
drmModeFreeResources(resources);
|
||||
}
|
||||
|
||||
bool Aquamarine::CDRMBackend::start() {
|
||||
scanConnectors();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int Aquamarine::CDRMBackend::pollFD() {
|
||||
return gpu->fd;
|
||||
}
|
||||
|
||||
int Aquamarine::CDRMBackend::drmFD() {
|
||||
return gpu->fd;
|
||||
}
|
||||
|
||||
static void handlePF(int fd, unsigned seq, unsigned tv_sec, unsigned tv_usec, unsigned crtc_id, void* data) {
|
||||
// FIXME:
|
||||
}
|
||||
|
||||
bool Aquamarine::CDRMBackend::dispatchEvents() {
|
||||
drmEventContext event = {
|
||||
.version = 3,
|
||||
.page_flip_handler2 = ::handlePF,
|
||||
};
|
||||
|
||||
if (drmHandleEvent(gpu->fd, &event) != 0)
|
||||
backend->log(AQ_LOG_ERROR, std::format("drm: Failed to handle event on fd {}", gpu->fd));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t Aquamarine::CDRMBackend::capabilities() {
|
||||
return eBackendCapabilities::AQ_BACKEND_CAPABILITY_POINTER;
|
||||
}
|
||||
|
||||
bool Aquamarine::CDRMBackend::setCursor(SP<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Aquamarine::CDRMBackend::onReady() {
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<SDRMFormat> Aquamarine::CDRMBackend::getRenderFormats() {
|
||||
for (auto& p : planes) {
|
||||
if (p->type != DRM_PLANE_TYPE_PRIMARY)
|
||||
continue;
|
||||
|
||||
return p->formats;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<SDRMFormat> Aquamarine::CDRMBackend::getCursorFormats() {
|
||||
for (auto& p : planes) {
|
||||
if (p->type != DRM_PLANE_TYPE_CURSOR)
|
||||
continue;
|
||||
|
||||
return p->formats;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Aquamarine::SDRMPlane::init(drmModePlane* plane) {
|
||||
id = plane->plane_id;
|
||||
|
||||
if (!getDRMPlaneProps(backend->gpu->fd, id, &props))
|
||||
return false;
|
||||
|
||||
if (!getDRMProp(backend->gpu->fd, id, props.type, &type))
|
||||
return false;
|
||||
|
||||
initialID = id;
|
||||
|
||||
for (size_t i = 0; i < plane->count_formats; ++i) {
|
||||
if (type != DRM_PLANE_TYPE_CURSOR)
|
||||
formats.emplace_back(SDRMFormat{.drmFormat = plane->formats[i], .modifiers = {DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_INVALID}});
|
||||
else
|
||||
formats.emplace_back(SDRMFormat{.drmFormat = plane->formats[i], .modifiers = {DRM_FORMAT_MOD_LINEAR}});
|
||||
}
|
||||
|
||||
if (props.in_formats && backend->drmProps.supportsAddFb2Modifiers) {
|
||||
uint64_t blobID = 0;
|
||||
if (!getDRMProp(backend->gpu->fd, id, props.in_formats, &blobID))
|
||||
return false;
|
||||
|
||||
auto blob = drmModeGetPropertyBlob(backend->gpu->fd, blobID);
|
||||
if (!blob)
|
||||
return false;
|
||||
|
||||
drmModeFormatModifierIterator iter = {0};
|
||||
while (drmModeFormatModifierBlobIterNext(blob, &iter)) {
|
||||
auto it = std::find_if(formats.begin(), formats.end(), [iter](const auto& e) { return e.drmFormat == iter.fmt; });
|
||||
|
||||
if (it == formats.end())
|
||||
formats.emplace_back(SDRMFormat{.drmFormat = iter.fmt, .modifiers = {iter.mod}});
|
||||
else
|
||||
it->modifiers.emplace_back(iter.mod);
|
||||
}
|
||||
|
||||
drmModeFreePropertyBlob(blob);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < backend->crtcs.size(); ++i) {
|
||||
uint32_t crtcBit = (1 << i);
|
||||
if (!(plane->possible_crtcs & crtcBit))
|
||||
continue;
|
||||
|
||||
auto CRTC = backend->crtcs.at(i);
|
||||
if (type == DRM_PLANE_TYPE_PRIMARY && !CRTC->primary) {
|
||||
CRTC->primary = self.lock();
|
||||
break;
|
||||
}
|
||||
|
||||
if (type == DRM_PLANE_TYPE_CURSOR && !CRTC->cursor) {
|
||||
CRTC->cursor = self.lock();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SP<SDRMCRTC> Aquamarine::SDRMConnector::getCurrentCRTC(const drmModeConnector* connector) {
|
||||
uint32_t crtcID = 0;
|
||||
if (props.crtc_id) {
|
||||
uint64_t value = 0;
|
||||
if (!getDRMProp(backend->gpu->fd, id, props.crtc_id, &value)) {
|
||||
backend->backend->log(AQ_LOG_ERROR, "drm: Failed to get CRTC_ID");
|
||||
return nullptr;
|
||||
}
|
||||
crtcID = static_cast<uint32_t>(value);
|
||||
} else if (connector->encoder_id) {
|
||||
auto encoder = drmModeGetEncoder(backend->gpu->fd, connector->encoder_id);
|
||||
if (!encoder) {
|
||||
backend->backend->log(AQ_LOG_ERROR, "drm: drmModeGetEncoder failed");
|
||||
return nullptr;
|
||||
}
|
||||
crtcID = encoder->crtc_id;
|
||||
drmModeFreeEncoder(encoder);
|
||||
} else
|
||||
return nullptr;
|
||||
|
||||
auto it = std::find_if(backend->crtcs.begin(), backend->crtcs.end(), [crtcID](const auto& e) { return e->id == crtcID; });
|
||||
|
||||
if (it == backend->crtcs.end()) {
|
||||
backend->backend->log(AQ_LOG_ERROR, std::format("drm: Failed to find a CRTC with ID {}", crtcID));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return *it;
|
||||
}
|
||||
|
||||
bool Aquamarine::SDRMConnector::init(drmModeConnector* connector) {
|
||||
id = connector->connector_id;
|
||||
|
||||
if (!getDRMConnectorProps(backend->gpu->fd, id, &props))
|
||||
return false;
|
||||
|
||||
auto name = drmModeGetConnectorTypeName(connector->connector_type);
|
||||
if (!name)
|
||||
name = "ERROR";
|
||||
|
||||
szName = std::format("{}-{}", name, connector->connector_type_id);
|
||||
|
||||
possibleCrtcs = drmModeConnectorGetPossibleCrtcs(backend->gpu->fd, connector);
|
||||
if (!possibleCrtcs)
|
||||
backend->backend->log(AQ_LOG_ERROR, "drm: No CRTCs possible");
|
||||
|
||||
crtc = getCurrentCRTC(connector);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Aquamarine::SDRMConnector::~SDRMConnector() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
static int32_t calculateRefresh(const drmModeModeInfo& mode) {
|
||||
int32_t refresh = (mode.clock * 1000000LL / mode.htotal + mode.vtotal / 2) / mode.vtotal;
|
||||
|
||||
if (mode.flags & DRM_MODE_FLAG_INTERLACE)
|
||||
refresh *= 2;
|
||||
|
||||
if (mode.flags & DRM_MODE_FLAG_DBLSCAN)
|
||||
refresh /= 2;
|
||||
|
||||
if (mode.vscan > 1)
|
||||
refresh /= mode.vscan;
|
||||
|
||||
return refresh;
|
||||
}
|
||||
|
||||
drmModeModeInfo* Aquamarine::SDRMConnector::getCurrentMode() {
|
||||
if (!crtc)
|
||||
return nullptr;
|
||||
|
||||
if (crtc->props.mode_id) {
|
||||
size_t size = 0;
|
||||
return (drmModeModeInfo*)getDRMPropBlob(backend->gpu->fd, crtc->id, crtc->props.mode_id, &size);
|
||||
;
|
||||
}
|
||||
|
||||
auto drmCrtc = drmModeGetCrtc(backend->gpu->fd, crtc->id);
|
||||
if (!drmCrtc)
|
||||
return nullptr;
|
||||
if (!drmCrtc->mode_valid) {
|
||||
drmModeFreeCrtc(drmCrtc);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
drmModeModeInfo* modeInfo = (drmModeModeInfo*)malloc(sizeof(drmModeModeInfo));
|
||||
if (!modeInfo) {
|
||||
drmModeFreeCrtc(drmCrtc);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
*modeInfo = drmCrtc->mode;
|
||||
drmModeFreeCrtc(drmCrtc);
|
||||
|
||||
return modeInfo;
|
||||
}
|
||||
|
||||
void Aquamarine::SDRMConnector::parseEDID(std::vector<uint8_t> data) {
|
||||
// TODO: libdisplay-info prolly
|
||||
}
|
||||
|
||||
void Aquamarine::SDRMConnector::connect(drmModeConnector* connector) {
|
||||
if (output) {
|
||||
backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Not connecting connector {} because it's already connected", szName));
|
||||
return;
|
||||
}
|
||||
|
||||
backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Connecting connector {}, CRTC ID {}", szName, crtc ? crtc->id : -1));
|
||||
|
||||
output = SP<CDRMOutput>(new CDRMOutput(szName, backend, self.lock()));
|
||||
output->self = output;
|
||||
output->connector = self.lock();
|
||||
|
||||
backend->backend->log(AQ_LOG_DEBUG, "drm: Dumping detected modes:");
|
||||
|
||||
auto currentModeInfo = getCurrentMode();
|
||||
|
||||
for (int i = 0; i < connector->count_modes; ++i) {
|
||||
auto& drmMode = connector->modes[i];
|
||||
|
||||
if (drmMode.flags & DRM_MODE_FLAG_INTERLACE) {
|
||||
backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Skipping mode {} because it's interlaced", i));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto aqMode = makeShared<SOutputMode>();
|
||||
aqMode->pixelSize = {drmMode.hdisplay, drmMode.vdisplay};
|
||||
aqMode->refreshRate = calculateRefresh(drmMode);
|
||||
aqMode->preferred = (drmMode.type & DRM_MODE_TYPE_PREFERRED);
|
||||
|
||||
output->modes.emplace_back(aqMode);
|
||||
|
||||
if (currentModeInfo && std::memcmp(&drmMode, currentModeInfo, sizeof(drmModeModeInfo))) {
|
||||
output->state->setMode(aqMode);
|
||||
|
||||
//uint64_t modeID = 0;
|
||||
// getDRMProp(backend->gpu->fd, crtc->id, crtc->props.mode_id, &modeID);
|
||||
|
||||
crtc->refresh = calculateRefresh(drmMode);
|
||||
}
|
||||
|
||||
backend->backend->log(AQ_LOG_DEBUG,
|
||||
std::format("drm: Mode {}: {}x{}@{:.2f}Hz {}", i, (int)aqMode->pixelSize.x, (int)aqMode->pixelSize.y, aqMode->refreshRate / 1000.0,
|
||||
aqMode->preferred ? " (preferred)" : ""));
|
||||
}
|
||||
|
||||
output->physicalSize = {(double)connector->mmWidth, (double)connector->mmHeight};
|
||||
|
||||
backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Physical size {} (mm)", output->physicalSize));
|
||||
|
||||
switch (connector->subpixel) {
|
||||
case DRM_MODE_SUBPIXEL_NONE: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_NONE; break;
|
||||
case DRM_MODE_SUBPIXEL_UNKNOWN: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_UNKNOWN; break;
|
||||
case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_HORIZONTAL_RGB; break;
|
||||
case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_HORIZONTAL_BGR; break;
|
||||
case DRM_MODE_SUBPIXEL_VERTICAL_RGB: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_VERTICAL_RGB; break;
|
||||
case DRM_MODE_SUBPIXEL_VERTICAL_BGR: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_VERTICAL_BGR; break;
|
||||
default: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_UNKNOWN;
|
||||
}
|
||||
|
||||
uint64_t prop = 0;
|
||||
if (getDRMProp(backend->gpu->fd, id, props.non_desktop, &prop)) {
|
||||
if (prop == 1)
|
||||
backend->backend->log(AQ_LOG_DEBUG, "drm: Non-desktop connector");
|
||||
output->nonDesktop = prop;
|
||||
}
|
||||
|
||||
maxBpcBounds.fill(0);
|
||||
|
||||
if (props.max_bpc && !introspectDRMPropRange(backend->gpu->fd, props.max_bpc, maxBpcBounds.data(), &maxBpcBounds[1]))
|
||||
backend->backend->log(AQ_LOG_ERROR, "drm: Failed to check max_bpc");
|
||||
|
||||
size_t edidLen = 0;
|
||||
uint8_t* edidData = (uint8_t*)getDRMPropBlob(backend->gpu->fd, id, props.edid, &edidLen);
|
||||
|
||||
std::vector<uint8_t> edid{edidData, edidData + edidLen};
|
||||
parseEDID(edid);
|
||||
|
||||
free(edidData);
|
||||
edid = {};
|
||||
|
||||
// TODO: subconnectors
|
||||
|
||||
output->make = make;
|
||||
output->model = model;
|
||||
output->serial = serial;
|
||||
output->description = std::format("{} {} {} ({})", make, model, serial, szName);
|
||||
|
||||
backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Description {}", output->description));
|
||||
|
||||
status = DRM_MODE_CONNECTED;
|
||||
|
||||
backend->backend->events.newOutput.emit(output);
|
||||
}
|
||||
|
||||
void Aquamarine::SDRMConnector::disconnect() {
|
||||
if (!output) {
|
||||
backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Not disconnecting connector {} because it's already disconnected", szName));
|
||||
return;
|
||||
}
|
||||
|
||||
output->events.destroy.emit();
|
||||
output.reset();
|
||||
|
||||
status = DRM_MODE_DISCONNECTED;
|
||||
}
|
||||
|
||||
Aquamarine::CDRMOutput::~CDRMOutput() {
|
||||
;
|
||||
}
|
||||
|
||||
bool Aquamarine::CDRMOutput::commit() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Aquamarine::CDRMOutput::test() {
|
||||
return true;
|
||||
}
|
||||
|
||||
SP<IBackendImplementation> Aquamarine::CDRMOutput::getBackend() {
|
||||
return backend.lock();
|
||||
}
|
||||
|
||||
bool Aquamarine::CDRMOutput::setCursor(SP<IBuffer> buffer, const Vector2D& hotspot) {
|
||||
return false; // FIXME:
|
||||
}
|
||||
|
||||
void Aquamarine::CDRMOutput::moveCursor(const Vector2D& coord) {
|
||||
; // FIXME:
|
||||
}
|
||||
|
||||
void Aquamarine::CDRMOutput::scheduleFrame() {
|
||||
;
|
||||
}
|
||||
|
||||
Vector2D Aquamarine::CDRMOutput::maxCursorSize() {
|
||||
return backend->drmProps.cursorSize;
|
||||
}
|
||||
|
||||
Aquamarine::CDRMOutput::CDRMOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer<CDRMBackend> backend_, Hyprutils::Memory::CSharedPointer<SDRMConnector> connector_) :
|
||||
backend(backend_), connector(connector_) {
|
||||
name = name_;
|
||||
}
|
183
src/backend/drm/Props.cpp
Normal file
183
src/backend/drm/Props.cpp
Normal file
|
@ -0,0 +1,183 @@
|
|||
#include <aquamarine/backend/DRM.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <libseat.h>
|
||||
#include <libudev.h>
|
||||
#include <xf86drm.h>
|
||||
#include <xf86drmMode.h>
|
||||
}
|
||||
|
||||
#include <cstring>
|
||||
|
||||
using namespace Aquamarine;
|
||||
using namespace Hyprutils::Memory;
|
||||
#define SP CSharedPointer
|
||||
|
||||
struct prop_info {
|
||||
const char* name;
|
||||
size_t index;
|
||||
};
|
||||
|
||||
static const struct prop_info connector_info[] = {
|
||||
#define INDEX(name) (offsetof(SDRMConnector::UDRMConnectorProps, name) / sizeof(uint32_t))
|
||||
{"CRTC_ID", INDEX(crtc_id)},
|
||||
{"DPMS", INDEX(dpms)},
|
||||
{"EDID", INDEX(edid)},
|
||||
{"PATH", INDEX(path)},
|
||||
{"content type", INDEX(content_type)},
|
||||
{"link-status", INDEX(link_status)},
|
||||
{"max bpc", INDEX(max_bpc)},
|
||||
{"non-desktop", INDEX(non_desktop)},
|
||||
{"panel orientation", INDEX(panel_orientation)},
|
||||
{"subconnector", INDEX(subconnector)},
|
||||
{"vrr_capable", INDEX(vrr_capable)},
|
||||
#undef INDEX
|
||||
};
|
||||
|
||||
static const struct prop_info crtc_info[] = {
|
||||
#define INDEX(name) (offsetof(SDRMCRTC::UDRMCRTCProps, name) / sizeof(uint32_t))
|
||||
{"ACTIVE", INDEX(active)}, {"GAMMA_LUT", INDEX(gamma_lut)}, {"GAMMA_LUT_SIZE", INDEX(gamma_lut_size)}, {"MODE_ID", INDEX(mode_id)}, {"VRR_ENABLED", INDEX(vrr_enabled)},
|
||||
#undef INDEX
|
||||
};
|
||||
|
||||
static const struct prop_info plane_info[] = {
|
||||
#define INDEX(name) (offsetof(SDRMPlane::UDRMPlaneProps, name) / sizeof(uint32_t))
|
||||
{"CRTC_H", INDEX(crtc_h)}, {"CRTC_ID", INDEX(crtc_id)},
|
||||
{"CRTC_W", INDEX(crtc_w)}, {"CRTC_X", INDEX(crtc_x)},
|
||||
{"CRTC_Y", INDEX(crtc_y)}, {"FB_DAMAGE_CLIPS", INDEX(fb_damage_clips)},
|
||||
{"FB_ID", INDEX(fb_id)}, {"HOTSPOT_X", INDEX(hotspot_x)},
|
||||
{"HOTSPOT_Y", INDEX(hotspot_y)}, {"IN_FORMATS", INDEX(in_formats)},
|
||||
{"SRC_H", INDEX(src_h)}, {"SRC_W", INDEX(src_w)},
|
||||
{"SRC_X", INDEX(src_x)}, {"SRC_Y", INDEX(src_y)},
|
||||
{"rotation", INDEX(rotation)}, {"type", INDEX(type)},
|
||||
#undef INDEX
|
||||
};
|
||||
|
||||
namespace Aquamarine {
|
||||
|
||||
static int comparePropInfo(const void* arg1, const void* arg2) {
|
||||
const char* key = (const char*)arg1;
|
||||
const prop_info* elem = (prop_info*)arg2;
|
||||
|
||||
return strcmp(key, elem->name);
|
||||
}
|
||||
|
||||
static bool scanProperties(int fd, uint32_t id, uint32_t type, uint32_t* result, const prop_info* info, size_t info_len) {
|
||||
drmModeObjectProperties* props = drmModeObjectGetProperties(fd, id, type);
|
||||
if (!props)
|
||||
return false;
|
||||
|
||||
for (uint32_t i = 0; i < props->count_props; ++i) {
|
||||
drmModePropertyRes* prop = drmModeGetProperty(fd, props->props[i]);
|
||||
if (!prop)
|
||||
continue;
|
||||
|
||||
const prop_info* p = (prop_info*)bsearch(prop->name, info, info_len, sizeof(info[0]), comparePropInfo);
|
||||
if (p)
|
||||
result[p->index] = prop->prop_id;
|
||||
|
||||
drmModeFreeProperty(prop);
|
||||
}
|
||||
|
||||
drmModeFreeObjectProperties(props);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getDRMConnectorProps(int fd, uint32_t id, SDRMConnector::UDRMConnectorProps* out) {
|
||||
return scanProperties(fd, id, DRM_MODE_OBJECT_CONNECTOR, out->props, connector_info, sizeof(connector_info) / sizeof(connector_info[0]));
|
||||
}
|
||||
|
||||
bool getDRMCRTCProps(int fd, uint32_t id, SDRMCRTC::UDRMCRTCProps* out) {
|
||||
return scanProperties(fd, id, DRM_MODE_OBJECT_CRTC, out->props, crtc_info, sizeof(crtc_info) / sizeof(crtc_info[0]));
|
||||
}
|
||||
|
||||
bool getDRMPlaneProps(int fd, uint32_t id, SDRMPlane::UDRMPlaneProps* out) {
|
||||
return scanProperties(fd, id, DRM_MODE_OBJECT_PLANE, out->props, plane_info, sizeof(plane_info) / sizeof(plane_info[0]));
|
||||
}
|
||||
|
||||
bool getDRMProp(int fd, uint32_t obj, uint32_t prop, uint64_t* ret) {
|
||||
drmModeObjectProperties* props = drmModeObjectGetProperties(fd, obj, DRM_MODE_OBJECT_ANY);
|
||||
if (!props)
|
||||
return false;
|
||||
|
||||
bool found = false;
|
||||
|
||||
for (uint32_t i = 0; i < props->count_props; ++i) {
|
||||
if (props->props[i] == prop) {
|
||||
*ret = props->prop_values[i];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
drmModeFreeObjectProperties(props);
|
||||
return found;
|
||||
}
|
||||
|
||||
void* getDRMPropBlob(int fd, uint32_t obj, uint32_t prop, size_t* ret_len) {
|
||||
uint64_t blob_id;
|
||||
if (!getDRMProp(fd, obj, prop, &blob_id))
|
||||
return nullptr;
|
||||
|
||||
drmModePropertyBlobRes* blob = drmModeGetPropertyBlob(fd, blob_id);
|
||||
if (!blob)
|
||||
return nullptr;
|
||||
|
||||
void* ptr = malloc(blob->length);
|
||||
if (!ptr) {
|
||||
drmModeFreePropertyBlob(blob);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
memcpy(ptr, blob->data, blob->length);
|
||||
*ret_len = blob->length;
|
||||
|
||||
drmModeFreePropertyBlob(blob);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
char* getDRMPropEnum(int fd, uint32_t obj, uint32_t prop_id) {
|
||||
uint64_t value;
|
||||
if (!getDRMProp(fd, obj, prop_id, &value))
|
||||
return nullptr;
|
||||
|
||||
drmModePropertyRes* prop = drmModeGetProperty(fd, prop_id);
|
||||
if (!prop)
|
||||
return nullptr;
|
||||
|
||||
char* str = nullptr;
|
||||
for (int i = 0; i < prop->count_enums; i++) {
|
||||
if (prop->enums[i].value == value) {
|
||||
str = strdup(prop->enums[i].name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
drmModeFreeProperty(prop);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
bool introspectDRMPropRange(int fd, uint32_t prop_id, uint64_t* min, uint64_t* max) {
|
||||
drmModePropertyRes* prop = drmModeGetProperty(fd, prop_id);
|
||||
if (!prop)
|
||||
return false;
|
||||
|
||||
if (drmModeGetPropertyType(prop) != DRM_MODE_PROP_RANGE) {
|
||||
drmModeFreeProperty(prop);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prop->count_values != 2)
|
||||
abort();
|
||||
|
||||
if (min != nullptr)
|
||||
*min = prop->values[0];
|
||||
if (max != nullptr)
|
||||
*max = prop->values[1];
|
||||
|
||||
drmModeFreeProperty(prop);
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
13
src/backend/drm/Props.hpp
Normal file
13
src/backend/drm/Props.hpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <aquamarine/backend/DRM.hpp>
|
||||
|
||||
namespace Aquamarine {
|
||||
bool getDRMConnectorProps(int fd, uint32_t id, SDRMConnector::UDRMConnectorProps* out);
|
||||
bool getDRMCRTCProps(int fd, uint32_t id, SDRMCRTC::UDRMCRTCProps* out);
|
||||
bool getDRMPlaneProps(int fd, uint32_t id, SDRMPlane::UDRMPlaneProps* out);
|
||||
bool getDRMProp(int fd, uint32_t obj, uint32_t prop, uint64_t* ret);
|
||||
void* getDRMPropBlob(int fd, uint32_t obj, uint32_t prop, size_t* ret_len);
|
||||
char* getDRMPropEnum(int fd, uint32_t obj, uint32_t prop_id);
|
||||
bool introspectDRMPropRange(int fd, uint32_t prop_id, uint64_t* min, uint64_t* max);
|
||||
};
|
Loading…
Reference in a new issue