Program Listing for File WaylandRuntimeLoader.cpp
↰ Return to documentation for file (include\input\DefaultDevs\linux\wayland_things\WaylandRuntimeLoader.cpp)
#include "WaylandRuntimeLoader.hpp"
#include <cstdio>
#include <cstring>
#include <dlfcn.h>
namespace PDJE_DEFAULT_DEVICES {
namespace {
void *
SystemDlopen(const char *path, int flags)
{
return dlopen(path, flags);
}
void *
SystemDlsym(void *handle, const char *symbol)
{
return dlsym(handle, symbol);
}
int
SystemDlclose(void *handle)
{
return dlclose(handle);
}
const char *
SystemDlerror()
{
return dlerror();
}
WaylandDynLibOps
MakeSystemDynLibOps() noexcept
{
WaylandDynLibOps ops;
ops.dlopen_fn = &SystemDlopen;
ops.dlsym_fn = &SystemDlsym;
ops.dlclose_fn = &SystemDlclose;
ops.dlerror_fn = &SystemDlerror;
return ops;
}
bool
HasValidDynLibOps(const WaylandDynLibOps &ops) noexcept
{
return ops.dlopen_fn != nullptr && ops.dlsym_fn != nullptr &&
ops.dlclose_fn != nullptr && ops.dlerror_fn != nullptr;
}
inline void
SetError(char (&dst)[256], const char *msg) noexcept
{
if (msg == nullptr || msg[0] == '\0') {
dst[0] = '\0';
return;
}
std::snprintf(dst, sizeof(dst), "%s", msg);
}
inline void
SetMissingSymbolError(char (&dst)[256], const char *symbol) noexcept
{
std::snprintf(dst, sizeof(dst), "missing symbol: %s", symbol);
}
inline bool
IsLibraryMissingError(const char *msg) noexcept
{
if (msg == nullptr) {
return false;
}
return std::strstr(msg, "No such file") != nullptr ||
std::strstr(msg, "cannot open shared object file") != nullptr;
}
void *
TryLoadLibrary(const WaylandDynLibOps &ops,
const char *const *names,
const std::size_t count,
const int dlopen_flags,
LibLoadState &state,
char (&error)[256]) noexcept
{
state = LibLoadState::Unchecked;
error[0] = '\0';
const char *last_error = nullptr;
for (std::size_t i = 0; i < count; ++i) {
ops.dlerror_fn();
void *h = ops.dlopen_fn(names[i], dlopen_flags);
if (h != nullptr) {
state = LibLoadState::Loaded;
return h;
}
last_error = ops.dlerror_fn();
if (last_error != nullptr) {
SetError(error, last_error);
}
}
state = IsLibraryMissingError(last_error) ? LibLoadState::Missing
: LibLoadState::LoadError;
if (error[0] == '\0') {
SetError(error, "dlopen failed without a detailed error message.");
}
return nullptr;
}
bool
ResolveRequiredSymbols(const WaylandDynLibOps &ops,
void *handle,
char (&error)[256],
const char *const *symbols,
const std::size_t count) noexcept
{
for (std::size_t i = 0; i < count; ++i) {
ops.dlerror_fn();
void *sym = ops.dlsym_fn(handle, symbols[i]);
const char *err = ops.dlerror_fn();
if (sym == nullptr || err != nullptr) {
if (err != nullptr && err[0] != '\0') {
SetError(error, err);
} else {
SetMissingSymbolError(error, symbols[i]);
}
return false;
}
}
return true;
}
} // namespace
WaylandRuntimeLoader::WaylandRuntimeLoader() noexcept
: dynlib_ops(MakeSystemDynLibOps())
{}
#ifdef PDJE_UNIT_TESTING
WaylandRuntimeLoader::WaylandRuntimeLoader(WaylandDynLibOps ops) noexcept
: dynlib_ops(HasValidDynLibOps(ops) ? ops : MakeSystemDynLibOps())
{}
#endif
WaylandRuntimeLoader::~WaylandRuntimeLoader()
{
Unload();
}
void
WaylandRuntimeLoader::ClearStatusUnlocked() noexcept
{
status.ready = false;
status.wayland_client = LibLoadState::Unchecked;
status.xkbcommon = LibLoadState::Unchecked;
status.wayland_error[0] = '\0';
status.xkb_error[0] = '\0';
}
void
WaylandRuntimeLoader::UnloadUnlocked() noexcept
{
if (wayland_client_handle != nullptr) {
dynlib_ops.dlclose_fn(wayland_client_handle);
wayland_client_handle = nullptr;
}
if (xkbcommon_handle != nullptr) {
dynlib_ops.dlclose_fn(xkbcommon_handle);
xkbcommon_handle = nullptr;
}
}
bool
WaylandRuntimeLoader::ResolveWaylandSymbolsUnlocked() noexcept
{
static constexpr const char *kWaylandSymbols[] = {
"wl_display_connect",
"wl_display_disconnect",
"wl_display_dispatch",
"wl_display_get_fd"
};
return ResolveRequiredSymbols(dynlib_ops,
wayland_client_handle,
status.wayland_error,
kWaylandSymbols,
sizeof(kWaylandSymbols) /
sizeof(kWaylandSymbols[0]));
}
bool
WaylandRuntimeLoader::ResolveXKBCommonSymbolsUnlocked() noexcept
{
static constexpr const char *kXKBSymbols[] = {
"xkb_context_new",
"xkb_context_unref",
"xkb_keymap_new_from_names",
"xkb_keymap_unref",
"xkb_state_new",
"xkb_state_unref"
};
return ResolveRequiredSymbols(dynlib_ops,
xkbcommon_handle,
status.xkb_error,
kXKBSymbols,
sizeof(kXKBSymbols) /
sizeof(kXKBSymbols[0]));
}
bool
WaylandRuntimeLoader::EnsureLoaded() noexcept
{
std::lock_guard<std::mutex> guard(lock);
if (status.ready) {
return true;
}
UnloadUnlocked();
ClearStatusUnlocked();
static constexpr const char *kWaylandLibraryNames[] = {
"libwayland-client.so.0",
"libwayland-client.so"
};
static constexpr const char *kXKBLibraryNames[] = {
"libxkbcommon.so.0",
"libxkbcommon.so"
};
static constexpr int kDlopenFlags = RTLD_NOW | RTLD_LOCAL;
wayland_client_handle = TryLoadLibrary(dynlib_ops,
kWaylandLibraryNames,
sizeof(kWaylandLibraryNames) /
sizeof(kWaylandLibraryNames[0]),
kDlopenFlags,
status.wayland_client,
status.wayland_error);
xkbcommon_handle = TryLoadLibrary(dynlib_ops,
kXKBLibraryNames,
sizeof(kXKBLibraryNames) /
sizeof(kXKBLibraryNames[0]),
kDlopenFlags,
status.xkbcommon,
status.xkb_error);
if (wayland_client_handle != nullptr &&
!ResolveWaylandSymbolsUnlocked()) {
status.wayland_client = LibLoadState::SymbolMissing;
dynlib_ops.dlclose_fn(wayland_client_handle);
wayland_client_handle = nullptr;
}
if (xkbcommon_handle != nullptr &&
!ResolveXKBCommonSymbolsUnlocked()) {
status.xkbcommon = LibLoadState::SymbolMissing;
dynlib_ops.dlclose_fn(xkbcommon_handle);
xkbcommon_handle = nullptr;
}
status.ready = status.wayland_client == LibLoadState::Loaded &&
status.xkbcommon == LibLoadState::Loaded;
return status.ready;
}
void
WaylandRuntimeLoader::Unload() noexcept
{
std::lock_guard<std::mutex> guard(lock);
UnloadUnlocked();
status.ready = false;
}
} // namespace PDJE_DEFAULT_DEVICES