mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-05 15:30:00 +01:00
Re-implementation of TabletInput for Linux with X11 input for compatibility with Flatpak
This commit is contained in:
@@ -103,20 +103,28 @@ if(UNIX)
|
|||||||
${X11_INCLUDE_DIR}
|
${X11_INCLUDE_DIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
# libinput and libudev for tablet pressure support
|
# XInput2 for tablet pressure support (optional)
|
||||||
if (PKG_CONFIG_FOUND)
|
find_package(X11 COMPONENTS Xi)
|
||||||
pkg_check_modules(LIBINPUT libinput>=1.19)
|
if(X11_Xi_FOUND)
|
||||||
pkg_check_modules(LIBUDEV libudev)
|
add_definitions(-DHAVE_X11TABLETINPUT)
|
||||||
endif()
|
macro_log_feature(X11_Xi_FOUND "XInput2" "X11 Input extension for tablet support" "https://www.x.org/wiki/" FALSE)
|
||||||
|
|
||||||
if(LIBINPUT_FOUND AND LIBUDEV_FOUND)
|
|
||||||
include_directories(${LIBINPUT_INCLUDE_DIRS} ${LIBUDEV_INCLUDE_DIRS})
|
|
||||||
link_directories(${LIBINPUT_LIBRARY_DIRS} ${LIBUDEV_LIBRARY_DIRS})
|
|
||||||
add_definitions(-DHAVE_LIBINPUT)
|
|
||||||
macro_log_feature(LIBINPUT_FOUND "libinput" "Input device library for tablet support" "https://wayland.freedesktop.org/libinput" FALSE)
|
|
||||||
macro_log_feature(LIBUDEV_FOUND "libudev" "Device management library" "https://www.freedesktop.org/software/systemd/man/libudev.html" FALSE)
|
|
||||||
else()
|
else()
|
||||||
message(STATUS "libinput or libudev not found - tablet pressure support will be disabled")
|
|
||||||
|
# alternatively libinput and libudev for tablet pressure support
|
||||||
|
if (PKG_CONFIG_FOUND)
|
||||||
|
pkg_check_modules(LIBINPUT libinput>=1.19)
|
||||||
|
pkg_check_modules(LIBUDEV libudev)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(LIBINPUT_FOUND AND LIBUDEV_FOUND)
|
||||||
|
include_directories(${LIBINPUT_INCLUDE_DIRS} ${LIBUDEV_INCLUDE_DIRS})
|
||||||
|
link_directories(${LIBINPUT_LIBRARY_DIRS} ${LIBUDEV_LIBRARY_DIRS})
|
||||||
|
add_definitions(-DHAVE_LIBINPUT)
|
||||||
|
macro_log_feature(LIBINPUT_FOUND "libinput" "Input device library for tablet support" "https://wayland.freedesktop.org/libinput" FALSE)
|
||||||
|
macro_log_feature(LIBUDEV_FOUND "libudev" "Device management library" "https://www.freedesktop.org/software/systemd/man/libudev.html" FALSE)
|
||||||
|
else()
|
||||||
|
message(STATUS "XInput2 (Xi) or libinput or libudev not found - tablet pressure support will be disabled")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ TabletInput::~TabletInput()
|
|||||||
terminate();
|
terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TabletInput::init(void* platform_handle)
|
bool TabletInput::init()
|
||||||
{
|
{
|
||||||
// Create event monitor for tablet events
|
// Create event monitor for tablet events
|
||||||
NSEventMask mask = NSEventMaskTabletPoint |
|
NSEventMask mask = NSEventMaskTabletPoint |
|
||||||
|
|||||||
@@ -120,7 +120,15 @@ IF(APPLE)
|
|||||||
ELSE(APPLE)
|
ELSE(APPLE)
|
||||||
|
|
||||||
# Add Linux tablet input implementation
|
# Add Linux tablet input implementation
|
||||||
list(APPEND VMIX_SRCS TabletInput_linux.cpp)
|
if(X11_Xi_FOUND)
|
||||||
|
# Use X11/XInput2 version (works in Flatpak and everywhere)
|
||||||
|
list(APPEND VMIX_SRCS TabletInput_x11.cpp)
|
||||||
|
else()
|
||||||
|
if(LIBINPUT_FOUND AND LIBUDEV_FOUND)
|
||||||
|
# Use X11/XInput2 version (works in Flatpak and everywhere)
|
||||||
|
list(APPEND VMIX_SRCS TabletInput_linux_libinput.cpp)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
ENDIF(APPLE)
|
ENDIF(APPLE)
|
||||||
|
|
||||||
@@ -164,7 +172,12 @@ ELSE(APPLE)
|
|||||||
X11::xcb
|
X11::xcb
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add libinput and libudev for tablet support on Linux
|
# Add XInput2 library for tablet support on Linux (if available)
|
||||||
|
if(X11_Xi_FOUND)
|
||||||
|
list(APPEND PLATFORM_LIBS ${X11_Xi_LIB})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Add libinput and libudev for tablet support on Linux (if available)
|
||||||
if(LIBINPUT_FOUND AND LIBUDEV_FOUND)
|
if(LIBINPUT_FOUND AND LIBUDEV_FOUND)
|
||||||
list(APPEND PLATFORM_LIBS ${LIBINPUT_LIBRARIES} ${LIBUDEV_LIBRARIES})
|
list(APPEND PLATFORM_LIBS ${LIBINPUT_LIBRARIES} ${LIBUDEV_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -23,9 +23,13 @@
|
|||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
// Platform-specific forward declarations
|
// Platform-specific forward declarations
|
||||||
#if defined(LINUX) && defined(HAVE_LIBINPUT)
|
#ifdef LINUX
|
||||||
|
// X11 forward declarations (for XInput2)
|
||||||
|
typedef struct _XDisplay Display;
|
||||||
|
#if defined(HAVE_LIBINPUT)
|
||||||
struct libinput;
|
struct libinput;
|
||||||
struct udev;
|
struct udev;
|
||||||
|
#endif
|
||||||
#elif defined(APPLE)
|
#elif defined(APPLE)
|
||||||
#ifdef __OBJC__
|
#ifdef __OBJC__
|
||||||
@class NSEvent;
|
@class NSEvent;
|
||||||
@@ -54,7 +58,7 @@ public:
|
|||||||
static TabletInput& instance();
|
static TabletInput& instance();
|
||||||
|
|
||||||
// Initialize tablet input system
|
// Initialize tablet input system
|
||||||
bool init(void* platform_handle = nullptr);
|
bool init();
|
||||||
|
|
||||||
// Poll for new tablet events (call once per frame)
|
// Poll for new tablet events (call once per frame)
|
||||||
void pollEvents();
|
void pollEvents();
|
||||||
@@ -80,10 +84,21 @@ private:
|
|||||||
TabletData data_;
|
TabletData data_;
|
||||||
bool active_;
|
bool active_;
|
||||||
|
|
||||||
#if defined(LINUX) && defined(HAVE_LIBINPUT)
|
#ifdef LINUX
|
||||||
|
#if defined(HAVE_X11TABLETINPUT)
|
||||||
|
// X11/XInput2 members (used when libinput is not available or in Flatpak)
|
||||||
|
Display *display_;
|
||||||
|
int xi_opcode_;
|
||||||
|
int pressure_valuator_;
|
||||||
|
int tilt_x_valuator_;
|
||||||
|
int tilt_y_valuator_;
|
||||||
|
#endif
|
||||||
|
#if defined(HAVE_LIBINPUT)
|
||||||
|
// libinput members (used for native builds with libinput)
|
||||||
struct udev *udev_;
|
struct udev *udev_;
|
||||||
struct libinput *li_;
|
struct libinput *li_;
|
||||||
int fd_;
|
int fd_;
|
||||||
|
#endif
|
||||||
#elif defined(APPLE)
|
#elif defined(APPLE)
|
||||||
void* monitor_; // Event monitor handle
|
void* monitor_; // Event monitor handle
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of vimix - video live mixer
|
|
||||||
*
|
|
||||||
* **Copyright** (C) 2019-2025 Bruno Herbelin <bruno.herbelin@gmail.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
**/
|
|
||||||
|
|
||||||
#ifdef LINUX
|
|
||||||
|
|
||||||
#include "TabletInput.h"
|
|
||||||
#include "Log.h"
|
|
||||||
|
|
||||||
#ifdef HAVE_LIBINPUT
|
|
||||||
#include <libinput.h>
|
|
||||||
#include <libudev.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <cerrno>
|
|
||||||
|
|
||||||
static int open_restricted(const char *path, int flags, void *user_data)
|
|
||||||
{
|
|
||||||
int fd = open(path, flags);
|
|
||||||
return fd < 0 ? -errno : fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void close_restricted(int fd, void *user_data)
|
|
||||||
{
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct libinput_interface interface = {
|
|
||||||
.open_restricted = open_restricted,
|
|
||||||
.close_restricted = close_restricted,
|
|
||||||
};
|
|
||||||
|
|
||||||
TabletInput::TabletInput()
|
|
||||||
: data_({0.0f, false, 0.0f, 0.0f, false, false})
|
|
||||||
, active_(false)
|
|
||||||
, udev_(nullptr)
|
|
||||||
, li_(nullptr)
|
|
||||||
, fd_(-1)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
TabletInput::~TabletInput()
|
|
||||||
{
|
|
||||||
terminate();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TabletInput::init(void* platform_handle)
|
|
||||||
{
|
|
||||||
udev_ = udev_new();
|
|
||||||
if (!udev_) {
|
|
||||||
Log::Info("TabletInput: Failed to initialize udev");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
li_ = libinput_udev_create_context(&interface, nullptr, udev_);
|
|
||||||
if (!li_) {
|
|
||||||
Log::Info("TabletInput: Failed to create libinput context");
|
|
||||||
udev_unref(udev_);
|
|
||||||
udev_ = nullptr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (libinput_udev_assign_seat(li_, "seat0") != 0) {
|
|
||||||
Log::Info("TabletInput: Failed to assign seat");
|
|
||||||
libinput_unref(li_);
|
|
||||||
li_ = nullptr;
|
|
||||||
udev_unref(udev_);
|
|
||||||
udev_ = nullptr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fd_ = libinput_get_fd(li_);
|
|
||||||
|
|
||||||
// Set non-blocking
|
|
||||||
int flags = fcntl(fd_, F_GETFL, 0);
|
|
||||||
fcntl(fd_, F_SETFL, flags | O_NONBLOCK);
|
|
||||||
|
|
||||||
active_ = true;
|
|
||||||
Log::Info("TabletInput: Linux tablet input initialized (libinput)");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TabletInput::pollEvents()
|
|
||||||
{
|
|
||||||
if (!li_) return;
|
|
||||||
|
|
||||||
libinput_dispatch(li_);
|
|
||||||
|
|
||||||
struct libinput_event *event;
|
|
||||||
while ((event = libinput_get_event(li_)) != nullptr) {
|
|
||||||
enum libinput_event_type type = libinput_event_get_type(event);
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case LIBINPUT_EVENT_TABLET_TOOL_AXIS:
|
|
||||||
case LIBINPUT_EVENT_TABLET_TOOL_TIP:
|
|
||||||
case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: {
|
|
||||||
struct libinput_event_tablet_tool *tablet_event =
|
|
||||||
libinput_event_get_tablet_tool_event(event);
|
|
||||||
|
|
||||||
// Update pressure
|
|
||||||
if (libinput_event_tablet_tool_pressure_has_changed(tablet_event)) {
|
|
||||||
data_.has_pressure = true;
|
|
||||||
data_.pressure = libinput_event_tablet_tool_get_pressure(tablet_event);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update tilt (if available)
|
|
||||||
if (libinput_event_tablet_tool_tilt_x_has_changed(tablet_event)) {
|
|
||||||
data_.tilt_x = libinput_event_tablet_tool_get_tilt_x(tablet_event) / 90.0f;
|
|
||||||
}
|
|
||||||
if (libinput_event_tablet_tool_tilt_y_has_changed(tablet_event)) {
|
|
||||||
data_.tilt_y = libinput_event_tablet_tool_get_tilt_y(tablet_event) / 90.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update proximity
|
|
||||||
if (type == LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY) {
|
|
||||||
data_.in_proximity = (libinput_event_tablet_tool_get_proximity_state(tablet_event)
|
|
||||||
== LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN);
|
|
||||||
if (!data_.in_proximity) {
|
|
||||||
data_.has_pressure = true;
|
|
||||||
data_.pressure = 0.0f;
|
|
||||||
data_.tip_down = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update tip state
|
|
||||||
if (type == LIBINPUT_EVENT_TABLET_TOOL_TIP) {
|
|
||||||
data_.tip_down = (libinput_event_tablet_tool_get_tip_state(tablet_event)
|
|
||||||
== LIBINPUT_TABLET_TOOL_TIP_DOWN);
|
|
||||||
if (!data_.tip_down) {
|
|
||||||
data_.has_pressure = true;
|
|
||||||
data_.pressure = 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
libinput_event_destroy(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TabletInput::terminate()
|
|
||||||
{
|
|
||||||
if (li_) {
|
|
||||||
libinput_unref(li_);
|
|
||||||
li_ = nullptr;
|
|
||||||
}
|
|
||||||
if (udev_) {
|
|
||||||
udev_unref(udev_);
|
|
||||||
udev_ = nullptr;
|
|
||||||
}
|
|
||||||
active_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#else // !HAVE_LIBINPUT
|
|
||||||
|
|
||||||
// Stub implementation when libinput is not available
|
|
||||||
|
|
||||||
TabletInput::TabletInput()
|
|
||||||
: data_({0.0f, false, 0.0f, 0.0f, false, false})
|
|
||||||
, active_(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
TabletInput::~TabletInput()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TabletInput::init(void* platform_handle)
|
|
||||||
{
|
|
||||||
Log::Info("TabletInput: libinput not available - tablet pressure support disabled");
|
|
||||||
active_ = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TabletInput::pollEvents()
|
|
||||||
{
|
|
||||||
// No-op when libinput is not available
|
|
||||||
}
|
|
||||||
|
|
||||||
void TabletInput::terminate()
|
|
||||||
{
|
|
||||||
active_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // HAVE_LIBINPUT
|
|
||||||
|
|
||||||
#endif // LINUX
|
|
||||||
|
|||||||
206
src/TabletInput_linux_libinput.cpp
Normal file
206
src/TabletInput_linux_libinput.cpp
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of vimix - video live mixer
|
||||||
|
*
|
||||||
|
* **Copyright** (C) 2019-2025 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
**/
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#ifdef LINUX
|
||||||
|
|
||||||
|
#include "TabletInput.h"
|
||||||
|
#include "Log.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBINPUT
|
||||||
|
#include <libinput.h>
|
||||||
|
#include <libudev.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <cerrno>
|
||||||
|
|
||||||
|
static int open_restricted(const char *path, int flags, void *user_data)
|
||||||
|
{
|
||||||
|
int fd = open(path, flags);
|
||||||
|
return fd < 0 ? -errno : fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void close_restricted(int fd, void *user_data)
|
||||||
|
{
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct libinput_interface interface = {
|
||||||
|
.open_restricted = open_restricted,
|
||||||
|
.close_restricted = close_restricted,
|
||||||
|
};
|
||||||
|
|
||||||
|
TabletInput::TabletInput()
|
||||||
|
: data_({0.0f, false, 0.0f, 0.0f, false, false})
|
||||||
|
, active_(false)
|
||||||
|
, udev_(nullptr)
|
||||||
|
, li_(nullptr)
|
||||||
|
, fd_(-1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TabletInput::~TabletInput()
|
||||||
|
{
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TabletInput::init()
|
||||||
|
{
|
||||||
|
udev_ = udev_new();
|
||||||
|
if (!udev_) {
|
||||||
|
Log::Info("TabletInput: Failed to initialize udev");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
li_ = libinput_udev_create_context(&interface, nullptr, udev_);
|
||||||
|
if (!li_) {
|
||||||
|
Log::Info("TabletInput: Failed to create libinput context");
|
||||||
|
udev_unref(udev_);
|
||||||
|
udev_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (libinput_udev_assign_seat(li_, "seat0") != 0) {
|
||||||
|
Log::Info("TabletInput: Failed to assign seat");
|
||||||
|
libinput_unref(li_);
|
||||||
|
li_ = nullptr;
|
||||||
|
udev_unref(udev_);
|
||||||
|
udev_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd_ = libinput_get_fd(li_);
|
||||||
|
|
||||||
|
// Set non-blocking
|
||||||
|
int flags = fcntl(fd_, F_GETFL, 0);
|
||||||
|
fcntl(fd_, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
|
||||||
|
active_ = true;
|
||||||
|
Log::Info("TabletInput: Linux tablet input initialized (libinput)");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabletInput::pollEvents()
|
||||||
|
{
|
||||||
|
if (!li_) return;
|
||||||
|
|
||||||
|
libinput_dispatch(li_);
|
||||||
|
|
||||||
|
struct libinput_event *event;
|
||||||
|
while ((event = libinput_get_event(li_)) != nullptr) {
|
||||||
|
enum libinput_event_type type = libinput_event_get_type(event);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case LIBINPUT_EVENT_TABLET_TOOL_AXIS:
|
||||||
|
case LIBINPUT_EVENT_TABLET_TOOL_TIP:
|
||||||
|
case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: {
|
||||||
|
struct libinput_event_tablet_tool *tablet_event =
|
||||||
|
libinput_event_get_tablet_tool_event(event);
|
||||||
|
|
||||||
|
// Update pressure
|
||||||
|
if (libinput_event_tablet_tool_pressure_has_changed(tablet_event)) {
|
||||||
|
data_.has_pressure = true;
|
||||||
|
data_.pressure = libinput_event_tablet_tool_get_pressure(tablet_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update tilt (if available)
|
||||||
|
if (libinput_event_tablet_tool_tilt_x_has_changed(tablet_event)) {
|
||||||
|
data_.tilt_x = libinput_event_tablet_tool_get_tilt_x(tablet_event) / 90.0f;
|
||||||
|
}
|
||||||
|
if (libinput_event_tablet_tool_tilt_y_has_changed(tablet_event)) {
|
||||||
|
data_.tilt_y = libinput_event_tablet_tool_get_tilt_y(tablet_event) / 90.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update proximity
|
||||||
|
if (type == LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY) {
|
||||||
|
data_.in_proximity = (libinput_event_tablet_tool_get_proximity_state(tablet_event)
|
||||||
|
== LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN);
|
||||||
|
if (!data_.in_proximity) {
|
||||||
|
data_.has_pressure = true;
|
||||||
|
data_.pressure = 0.0f;
|
||||||
|
data_.tip_down = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update tip state
|
||||||
|
if (type == LIBINPUT_EVENT_TABLET_TOOL_TIP) {
|
||||||
|
data_.tip_down = (libinput_event_tablet_tool_get_tip_state(tablet_event)
|
||||||
|
== LIBINPUT_TABLET_TOOL_TIP_DOWN);
|
||||||
|
if (!data_.tip_down) {
|
||||||
|
data_.has_pressure = true;
|
||||||
|
data_.pressure = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
libinput_event_destroy(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabletInput::terminate()
|
||||||
|
{
|
||||||
|
if (li_) {
|
||||||
|
libinput_unref(li_);
|
||||||
|
li_ = nullptr;
|
||||||
|
}
|
||||||
|
if (udev_) {
|
||||||
|
udev_unref(udev_);
|
||||||
|
udev_ = nullptr;
|
||||||
|
}
|
||||||
|
active_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // !HAVE_LIBINPUT
|
||||||
|
|
||||||
|
// Stub implementation when libinput is not available
|
||||||
|
|
||||||
|
TabletInput::TabletInput()
|
||||||
|
: data_({0.0f, false, 0.0f, 0.0f, false, false})
|
||||||
|
, active_(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TabletInput::~TabletInput()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TabletInput::init()
|
||||||
|
{
|
||||||
|
Log::Info("TabletInput: libinput not available - tablet pressure support disabled");
|
||||||
|
active_ = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabletInput::pollEvents()
|
||||||
|
{
|
||||||
|
// No-op when libinput is not available
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabletInput::terminate()
|
||||||
|
{
|
||||||
|
active_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // HAVE_LIBINPUT
|
||||||
|
|
||||||
|
#endif // LINUX
|
||||||
249
src/TabletInput_x11.cpp
Normal file
249
src/TabletInput_x11.cpp
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of vimix - video live mixer
|
||||||
|
*
|
||||||
|
* **Copyright** (C) 2019-2023 Bruno Herbelin <bruno.herbelin@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
**/
|
||||||
|
|
||||||
|
#ifdef LINUX
|
||||||
|
|
||||||
|
#include "TabletInput.h"
|
||||||
|
#include "Log.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_X11TABLETINPUT
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/extensions/XInput2.h>
|
||||||
|
#include <cstring>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TabletInput::TabletInput()
|
||||||
|
: data_({0.0f, false, 0.0f, 0.0f, false, false})
|
||||||
|
, active_(false)
|
||||||
|
#ifdef HAVE_X11TABLETINPUT
|
||||||
|
, display_(nullptr)
|
||||||
|
, xi_opcode_(-1)
|
||||||
|
, pressure_valuator_(-1)
|
||||||
|
, tilt_x_valuator_(-1)
|
||||||
|
, tilt_y_valuator_(-1)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TabletInput::~TabletInput()
|
||||||
|
{
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TabletInput::init()
|
||||||
|
{
|
||||||
|
#ifdef HAVE_X11TABLETINPUT
|
||||||
|
// Open X11 display connection
|
||||||
|
display_ = XOpenDisplay(nullptr);
|
||||||
|
if (!display_) {
|
||||||
|
Log::Info("TabletInput: Failed to open X11 display");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for XInput2 extension
|
||||||
|
int event, error;
|
||||||
|
if (!XQueryExtension(display_, "XInputExtension", &xi_opcode_, &event, &error)) {
|
||||||
|
Log::Info("TabletInput: XInput extension not available");
|
||||||
|
XCloseDisplay(display_);
|
||||||
|
display_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check XInput2 version
|
||||||
|
int major = 2, minor = 2;
|
||||||
|
if (XIQueryVersion(display_, &major, &minor) != Success) {
|
||||||
|
Log::Info("TabletInput: XInput2 2.2 not available");
|
||||||
|
XCloseDisplay(display_);
|
||||||
|
display_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select events for all devices
|
||||||
|
XIEventMask eventmask;
|
||||||
|
unsigned char mask[XIMaskLen(XI_LASTEVENT)] = {0};
|
||||||
|
|
||||||
|
XISetMask(mask, XI_Motion);
|
||||||
|
XISetMask(mask, XI_ButtonPress);
|
||||||
|
XISetMask(mask, XI_ButtonRelease);
|
||||||
|
|
||||||
|
eventmask.deviceid = XIAllDevices;
|
||||||
|
eventmask.mask_len = sizeof(mask);
|
||||||
|
eventmask.mask = mask;
|
||||||
|
|
||||||
|
Window root = DefaultRootWindow(display_);
|
||||||
|
XISelectEvents(display_, root, &eventmask, 1);
|
||||||
|
|
||||||
|
// Find tablet devices and their pressure valuators
|
||||||
|
int ndevices;
|
||||||
|
XIDeviceInfo *devices = XIQueryDevice(display_, XIAllDevices, &ndevices);
|
||||||
|
|
||||||
|
for (int i = 0; i < ndevices; i++) {
|
||||||
|
XIDeviceInfo *device = &devices[i];
|
||||||
|
|
||||||
|
// Look for devices that have valuators (tablets, styluses)
|
||||||
|
if (device->use == XISlavePointer || device->use == XIFloatingSlave) {
|
||||||
|
// Check valuator classes for pressure
|
||||||
|
for (int j = 0; j < device->num_classes; j++) {
|
||||||
|
if (device->classes[j]->type == XIValuatorClass) {
|
||||||
|
XIValuatorClassInfo *v = (XIValuatorClassInfo*)device->classes[j];
|
||||||
|
|
||||||
|
// Try to identify pressure axis
|
||||||
|
// Pressure is usually valuator 2, but we check the label
|
||||||
|
Atom pressure_atom = XInternAtom(display_, "Abs Pressure", True);
|
||||||
|
Atom tilt_x_atom = XInternAtom(display_, "Abs Tilt X", True);
|
||||||
|
Atom tilt_y_atom = XInternAtom(display_, "Abs Tilt Y", True);
|
||||||
|
|
||||||
|
if (v->label == pressure_atom) {
|
||||||
|
pressure_valuator_ = v->number;
|
||||||
|
Log::Info("TabletInput: Found pressure valuator %d on device '%s'",
|
||||||
|
pressure_valuator_, device->name);
|
||||||
|
}
|
||||||
|
else if (v->label == tilt_x_atom) {
|
||||||
|
tilt_x_valuator_ = v->number;
|
||||||
|
}
|
||||||
|
else if (v->label == tilt_y_atom) {
|
||||||
|
tilt_y_valuator_ = v->number;
|
||||||
|
}
|
||||||
|
// Fallback: assume valuator 2 is pressure if we haven't found it
|
||||||
|
else if (pressure_valuator_ == -1 && v->number == 2) {
|
||||||
|
pressure_valuator_ = v->number;
|
||||||
|
Log::Info("TabletInput: Using valuator 2 as pressure (fallback)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XIFreeDeviceInfo(devices);
|
||||||
|
|
||||||
|
if (pressure_valuator_ == -1) {
|
||||||
|
Log::Info("TabletInput: No pressure valuator found - tablet may not be connected");
|
||||||
|
// Don't fail init, just continue without pressure detection
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
data_.has_pressure = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
XFlush(display_);
|
||||||
|
active_ = true;
|
||||||
|
Log::Info("TabletInput: X11/XInput2 tablet input initialized");
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
Log::Info("TabletInput: XInput2 not available - tablet support disabled");
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabletInput::pollEvents()
|
||||||
|
{
|
||||||
|
#ifdef HAVE_X11TABLETINPUT
|
||||||
|
if (!display_) return;
|
||||||
|
|
||||||
|
// Process all pending X11 events
|
||||||
|
while (XPending(display_) > 0) {
|
||||||
|
XEvent ev;
|
||||||
|
XNextEvent(display_, &ev);
|
||||||
|
|
||||||
|
// Check for XInput2 events
|
||||||
|
if (ev.type == GenericEvent && ev.xcookie.extension == xi_opcode_) {
|
||||||
|
if (XGetEventData(display_, &ev.xcookie)) {
|
||||||
|
XIDeviceEvent *device_event = (XIDeviceEvent*)ev.xcookie.data;
|
||||||
|
|
||||||
|
switch (ev.xcookie.evtype) {
|
||||||
|
case XI_Motion:
|
||||||
|
case XI_ButtonPress:
|
||||||
|
case XI_ButtonRelease: {
|
||||||
|
// Extract pressure from valuators
|
||||||
|
if (pressure_valuator_ >= 0) {
|
||||||
|
double *values = device_event->valuators.values;
|
||||||
|
unsigned char *mask = device_event->valuators.mask;
|
||||||
|
|
||||||
|
int val_index = 0;
|
||||||
|
for (int i = 0; i <= pressure_valuator_; i++) {
|
||||||
|
if (XIMaskIsSet(mask, i)) {
|
||||||
|
if (i == pressure_valuator_) {
|
||||||
|
// Normalize pressure (typically 0-65535 range)
|
||||||
|
data_.pressure = values[val_index] / 65535.0f;
|
||||||
|
if (data_.pressure > 1.0f) data_.pressure = 1.0f;
|
||||||
|
if (data_.pressure < 0.0f) data_.pressure = 0.0f;
|
||||||
|
}
|
||||||
|
val_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract tilt
|
||||||
|
if (tilt_x_valuator_ >= 0 || tilt_y_valuator_ >= 0) {
|
||||||
|
double *values = device_event->valuators.values;
|
||||||
|
unsigned char *mask = device_event->valuators.mask;
|
||||||
|
|
||||||
|
int val_index = 0;
|
||||||
|
int max_valuator = tilt_x_valuator_ > tilt_y_valuator_ ?
|
||||||
|
tilt_x_valuator_ : tilt_y_valuator_;
|
||||||
|
|
||||||
|
for (int i = 0; i <= max_valuator; i++) {
|
||||||
|
if (XIMaskIsSet(mask, i)) {
|
||||||
|
if (i == tilt_x_valuator_) {
|
||||||
|
data_.tilt_x = (values[val_index] - 32767.5f) / 32767.5f;
|
||||||
|
}
|
||||||
|
if (i == tilt_y_valuator_) {
|
||||||
|
data_.tilt_y = (values[val_index] - 32767.5f) / 32767.5f;
|
||||||
|
}
|
||||||
|
val_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update button state
|
||||||
|
if (ev.xcookie.evtype == XI_ButtonPress) {
|
||||||
|
data_.tip_down = true;
|
||||||
|
data_.in_proximity = true;
|
||||||
|
}
|
||||||
|
else if (ev.xcookie.evtype == XI_ButtonRelease) {
|
||||||
|
data_.tip_down = false;
|
||||||
|
if (data_.pressure < 0.01f) {
|
||||||
|
data_.in_proximity = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ev.xcookie.evtype == XI_Motion) {
|
||||||
|
data_.in_proximity = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XFreeEventData(display_, &ev.xcookie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabletInput::terminate()
|
||||||
|
{
|
||||||
|
#ifdef HAVE_X11TABLETINPUT
|
||||||
|
if (display_) {
|
||||||
|
XCloseDisplay(display_);
|
||||||
|
display_ = nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
active_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LINUX
|
||||||
Reference in New Issue
Block a user