Program Listing for File DefaultDevs.cpp
↰ Return to documentation for file (include\input\DefaultDevs\linux\DefaultDevs.cpp)
#include "DefaultDevs.hpp"
#include "LinuxInputContracts.hpp"
#include <exception>
#include <fcntl.h>
#include <unistd.h>
namespace PDJE_DEFAULT_DEVICES {
namespace {
} // namespace
DefaultDevs::DefaultDevs() : input_buffer(1024)
{
}
DefaultDevs::~DefaultDevs()
{
TerminateLoop();
}
bool
DefaultDevs::IsWaylandSyntheticId(const std::string &id) noexcept
{
return LINUX_INPUT_CONTRACTS::IsWaylandSyntheticId(id);
}
bool
DefaultDevs::HasValidWaylandHostContext() const noexcept
{
return platform_ctx0_ != nullptr && platform_ctx1_ != nullptr;
}
std::string
DefaultDevs::GetCurrentBackendString() const
{
switch (active_backend) {
case ActiveBackendKind::Evdev:
return "evdev";
case ActiveBackendKind::Wayland:
return "wayland";
default:
return "none";
}
}
void
DefaultDevs::Ready()
{
if (!wayland_loader.EnsureLoaded()) {
const auto &status = wayland_loader.Status();
if (status.wayland_client == LibLoadState::Missing) {
warnlog("Wayland runtime unavailable: missing libwayland-client");
if (status.wayland_error[0] != '\0') {
warnlog(status.wayland_error);
}
} else if (status.wayland_client != LibLoadState::Loaded) {
warnlog(
"Wayland runtime unavailable: failed to load libwayland-client");
if (status.wayland_error[0] != '\0') {
warnlog(status.wayland_error);
}
}
if (status.xkbcommon == LibLoadState::Missing) {
warnlog("Wayland runtime unavailable: missing libxkbcommon");
if (status.xkb_error[0] != '\0') {
warnlog(status.xkb_error);
}
} else if (status.xkbcommon != LibLoadState::Loaded) {
warnlog("Wayland runtime unavailable: failed to load libxkbcommon");
if (status.xkb_error[0] != '\0') {
warnlog(status.xkb_error);
}
}
}
if (!evdev_core) {
evdev_core.emplace();
evdev_core->set_Input_Transfer(&input_buffer);
}
}
bool
DefaultDevs::ConfigureWayland(const std::vector<DeviceData> &devs)
{
const bool has_host_handles = HasValidWaylandHostContext();
if (!has_host_handles && !use_internal_window_) {
warnlog("evdev failed and wayland fallback unavailable: null host "
"context handle");
return false;
}
if (!has_host_handles && use_internal_window_) {
warnlog("evdev failed; trying wayland fallback via internal window");
}
if (!wayland_loader.EnsureLoaded()) {
warnlog("evdev failed and wayland fallback unavailable: wayland "
"runtime not loaded");
return false;
}
if (!wayland_core) {
wayland_core.emplace();
}
wayland_core->set_Input_Transfer(&input_buffer);
if (!wayland_core->Configure(platform_ctx0_,
platform_ctx1_,
devs,
use_internal_window_)) {
warnlog("failed to configure wayland fallback backend on linux.");
if (!wayland_core->LastError().empty()) {
warnlog(wayland_core->LastError());
}
return false;
}
active_backend = ActiveBackendKind::Wayland;
wayland_source_mode = wayland_core->UsingInternalWindow()
? WaylandSourceMode::InternalWindow
: WaylandSourceMode::HostHandles;
return true;
}
bool
DefaultDevs::Config(const std::vector<DeviceData> &devs)
{
try {
if (!evdev_core) {
Ready();
}
active_backend = ActiveBackendKind::None;
wayland_source_mode = WaylandSourceMode::None;
std::vector<DeviceData> evdev_targets;
std::vector<DeviceData> wayland_targets;
bool saw_evdev = false;
bool saw_wayland = false;
for (const auto &i : devs) {
if (i.Name.empty()) {
continue;
}
auto searched = stored_dev.find(i.Name);
if (searched != stored_dev.end()) {
if (searched->second.backend_kind == StoredBackendKind::Wayland) {
saw_wayland = true;
wayland_targets.push_back(i);
} else {
saw_evdev = true;
evdev_targets.push_back(i);
}
continue;
}
if (IsWaylandSyntheticId(i.device_specific_id)) {
saw_wayland = true;
wayland_targets.push_back(i);
}
}
if (saw_evdev && saw_wayland) {
warnlog("linux input config rejected mixed evdev/wayland backend "
"selection.");
return false;
}
if (saw_wayland) {
return ConfigureWayland(wayland_targets);
}
if (!saw_evdev) {
return false;
}
if (!evdev_core) {
evdev_core.emplace();
}
evdev_core->Reset();
evdev_core->set_Input_Transfer(&input_buffer);
std::size_t added_count = 0;
std::size_t open_failed_count = 0;
for (const auto &i : evdev_targets) {
auto searched = stored_dev.find(i.Name);
if (searched == stored_dev.end()) {
continue;
}
const auto add_result = evdev_core->Add(searched->second.dev_path,
searched->second.dev_type,
i.Name);
if (add_result.ok) {
++added_count;
continue;
}
if (add_result.open_failed) {
++open_failed_count;
}
warnlog("failed to add input device on linux:");
warnlog(i.Name);
}
if (added_count > 0) {
active_backend = ActiveBackendKind::Evdev;
wayland_source_mode = WaylandSourceMode::None;
if (wayland_core) {
wayland_core->Reset();
}
return true;
}
if (open_failed_count == 0) {
warnlog("linux evdev config failed, and no /dev open failures were "
"detected. skipping wayland fallback.");
return false;
}
return ConfigureWayland(evdev_targets);
} catch (const std::exception &e) {
critlog("failed on Device Configure on linux. What:");
critlog(e.what());
return false;
}
}
std::vector<DeviceData>
DefaultDevs::GetDevices()
{
DEV_LIST lsdev;
fs::path device_root("/dev/input/");
stored_dev.clear();
try {
for (const auto &dev : fs::directory_iterator(device_root)) {
if (!dev.is_character_file()) {
continue;
}
const std::string dev_path = dev.path().string();
if (dev_path.find("event") == std::string::npos) {
continue;
}
int FD = open(dev_path.c_str(), O_RDONLY | O_NONBLOCK);
if (FD < 0) {
continue;
}
libevdev *info = nullptr;
DeviceData dd;
if (libevdev_new_from_fd(FD, &info) == 0) {
const char *dev_name = libevdev_get_name(info);
if (dev_name) {
dd.Name = std::string(dev_name);
dd.device_specific_id = dev.path().string();
stored_dev[dd.Name].dev_path = dev.path();
stored_dev[dd.Name].source_id = dd.device_specific_id;
stored_dev[dd.Name].backend_kind = StoredBackendKind::Evdev;
if (libevdev_has_event_type(info, EV_KEY) &&
libevdev_has_event_code(info, EV_KEY, KEY_A) &&
libevdev_has_event_code(info, EV_KEY, KEY_SPACE) &&
libevdev_has_event_code(info, EV_KEY, KEY_ENTER)) {
dd.Type = PDJE_Dev_Type::KEYBOARD;
stored_dev[dd.Name].dev_type = PDJE_Dev_Type::KEYBOARD;
} else if (libevdev_has_event_type(info, EV_REL) &&
libevdev_has_event_code(info, EV_REL, REL_X) &&
libevdev_has_event_code(info, EV_REL, REL_Y)) {
dd.Type = PDJE_Dev_Type::MOUSE;
stored_dev[dd.Name].dev_type = PDJE_Dev_Type::MOUSE;
} else if (libevdev_has_event_type(info, EV_ABS) &&
(libevdev_has_event_code(
info, EV_KEY, BTN_GAMEPAD) ||
libevdev_has_event_code(
info, EV_KEY, BTN_JOYSTICK))) {
stored_dev[dd.Name].dev_type = PDJE_Dev_Type::UNKNOWN;
dd.Type = PDJE_Dev_Type::UNKNOWN;
} else if (libevdev_has_event_type(info, EV_ABS) &&
libevdev_has_event_code(
info, EV_KEY, BTN_TOUCH)) {
dd.Type = PDJE_Dev_Type::MOUSE;
stored_dev[dd.Name].dev_type = PDJE_Dev_Type::MOUSE;
} else {
dd.Type = PDJE_Dev_Type::UNKNOWN;
stored_dev[dd.Name].dev_type = PDJE_Dev_Type::UNKNOWN;
}
lsdev.push_back(dd);
}
libevdev_free(info);
close(FD);
} else {
close(FD);
continue;
}
}
} catch (const std::exception &e) {
warnlog("linux evdev device scan failed; trying wayland fallback "
"discovery.");
warnlog(e.what());
}
if (!lsdev.empty()) {
return lsdev;
}
const bool has_host_handles = HasValidWaylandHostContext();
if (!has_host_handles && !use_internal_window_) {
return lsdev;
}
if (!wayland_loader.EnsureLoaded()) {
return lsdev;
}
DeviceData kb;
kb.Type = PDJE_Dev_Type::KEYBOARD;
kb.Name = LINUX_INPUT_CONTRACTS::GetWaylandSyntheticName(
kb.Type, !has_host_handles);
kb.device_specific_id = LINUX_INPUT_CONTRACTS::kWaylandKeyboardId;
stored_dev[kb.Name].backend_kind = StoredBackendKind::Wayland;
stored_dev[kb.Name].source_id = kb.device_specific_id;
stored_dev[kb.Name].dev_type = kb.Type;
lsdev.push_back(kb);
DeviceData mouse;
mouse.Type = PDJE_Dev_Type::MOUSE;
mouse.Name = LINUX_INPUT_CONTRACTS::GetWaylandSyntheticName(
mouse.Type, !has_host_handles);
mouse.device_specific_id = LINUX_INPUT_CONTRACTS::kWaylandPointerId;
stored_dev[mouse.Name].backend_kind = StoredBackendKind::Wayland;
stored_dev[mouse.Name].source_id = mouse.device_specific_id;
stored_dev[mouse.Name].dev_type = mouse.Type;
lsdev.push_back(mouse);
return lsdev;
}
void
DefaultDevs::RunLoop()
{
if (input_thread) {
return;
}
Ready();
switch (active_backend) {
case ActiveBackendKind::Evdev:
if (!evdev_core) {
critlog("linux input backend selected evdev but core is missing.");
return;
}
input_thread.emplace([this]() { evdev_core->Trig(); });
return;
case ActiveBackendKind::Wayland:
if (!wayland_core) {
critlog("linux input backend selected wayland but core is missing.");
return;
}
input_thread.emplace([this]() { wayland_core->Trig(); });
return;
case ActiveBackendKind::None:
default:
warnlog("RunLoop called before linux input backend was configured.");
return;
}
}
void
DefaultDevs::TerminateLoop()
{
try {
if (!input_thread) {
return;
}
if (active_backend == ActiveBackendKind::Evdev && evdev_core) {
evdev_core->Stop();
} else if (active_backend == ActiveBackendKind::Wayland &&
wayland_core) {
wayland_core->Stop();
}
if (input_thread->joinable()) {
input_thread->join();
}
input_thread.reset();
if (active_backend == ActiveBackendKind::Evdev) {
evdev_core.reset();
} else if (active_backend == ActiveBackendKind::Wayland) {
if (wayland_core) {
wayland_core->Reset();
}
wayland_core.reset();
}
wayland_source_mode = WaylandSourceMode::None;
} catch (const std::exception &e) {
critlog("input_thread join failed on linux. What: ");
critlog(e.what());
return;
}
}
}; // namespace PDJE_DEFAULT_DEVICES