fix: Touchscreen plugin do not support hot plugging

bug: https://172.17.50.104/bug-view-153525.html
detail: Monitor touchscreen device changed by QAbstractNativeEventFilter, currently only supported to work under X11.
If touchscreen state change, will show or hide page of touchscreen by gsetting of
org.ukui.control-center.plugins:/org/ukui/control-center/plugins/
This commit is contained in:
amphetaminewei 2023-01-17 22:23:02 +08:00 committed by yan wang
parent d37d985ed9
commit 7e98c9df85
7 changed files with 273 additions and 2 deletions

View File

@ -0,0 +1,158 @@
#include "devicemonitoreventfilter.h"
#include "geeventmemmover.h"
#include <memory>
#include <QByteArray>
#include <QTimer>
#include <QDebug>
// some lib of x11 need include after qt, because of some definitions
#include <X11/extensions/XInput2.h>
#include <xcb/xcb.h>
#include <xcb/xinput.h>
struct FreeDeleter {
void operator()(void* p) const Q_DECL_NOTHROW { return std::free(p); }
};
#define XCB_REPLY(call, ...) \
std::unique_ptr<call##_reply_t, FreeDeleter>( \
call##_reply(XCB_REPLY_CONNECTION_ARG(__VA_ARGS__), call(__VA_ARGS__), nullptr) \
)
#define XCB_REPLY_CONNECTION_ARG(connection, ...) connection
namespace {
bool existTouchDevice(xcb_input_touch_mode_t type);
bool findTouchFromReply(xcb_input_xi_query_device_reply_t* queryReply , xcb_input_touch_mode_t type);
bool findTouchModeFromDevice(xcb_input_xi_device_info_t* deviceInfo, xcb_input_touch_mode_t type);
}
DeviceMonitorEventFilter::DeviceMonitorEventFilter(QObject* parent)
: QObject(parent), QAbstractNativeEventFilter(), m_timer(new QTimer(this))
{
m_timer->setSingleShot(true);
}
bool DeviceMonitorEventFilter::nativeEventFilter(const QByteArray &eventType,
void* message, long* result)
{
//! \todo support wayland
Q_UNUSED(result)
if (eventType != "xcb_generic_event_t") {
qWarning() << __FILE__ << __FUNCTION__ << __LINE__
<< "Event type is:" << eventType
<< "But currently only supported xcb_generic_event_t";
return false;
}
return handleXcbGeEvent(message);
}
bool DeviceMonitorEventFilter::handleXcbGeEvent(void* message)
{
xcb_generic_event_t* event = static_cast<xcb_generic_event_t *>(message);
if (!event) {
qWarning() << __FILE__ << __FUNCTION__ << __LINE__ << "Message is empty";
return false;
}
const uint8_t type = event->response_type & ~0x80;
if (type != XCB_GE_GENERIC) {
return false;
}
GeEventMemMover ge(event);
switch (ge->event_type) {
case XI_HierarchyChanged: {
auto* he = reinterpret_cast<xcb_input_hierarchy_event_t *>(event);
if ((he->flags & (XCB_INPUT_HIERARCHY_MASK_SLAVE_REMOVED
| XCB_INPUT_HIERARCHY_MASK_SLAVE_ADDED))) {
// XI_HierarchyChanged events are sent continuously as the device changes
if (m_timer->isActive())
break;
m_timer->start(20);
setupTouchDevices();
}
break;
}
default:
break;
}
return false;
}
void DeviceMonitorEventFilter::setupTouchDevices()
{
bool existTouchScreen = existTouchDevice(XCB_INPUT_TOUCH_MODE_DIRECT);
qDebug() << __FILE__ << __FUNCTION__ << __LINE__
<< "Touch Screen exist state change to:" << existTouchScreen;
Q_EMIT touchScreenChanged(existTouchScreen);
}
namespace {
bool findTouchModeFromDevice(xcb_input_xi_device_info_t* deviceInfo, xcb_input_touch_mode_t type)
{
// iterate over all classes of device
auto classes_it = xcb_input_xi_device_info_classes_iterator(deviceInfo);
for (; classes_it.rem > 0; xcb_input_device_class_next(&classes_it)) {
xcb_input_device_class_t* classinfo = classes_it.data;
if (XCB_INPUT_DEVICE_CLASS_TYPE_TOUCH != classinfo->type)
continue;
auto* touchClassInfo = reinterpret_cast<xcb_input_touch_class_t *>(classinfo);
if (type == touchClassInfo->mode)
return true;
}
return false;
}
bool findTouchFromReply(xcb_input_xi_query_device_reply_t* queryReply , xcb_input_touch_mode_t type)
{
// iterate over all devices
auto it = xcb_input_xi_query_device_infos_iterator(queryReply);
for (; it.rem > 0; xcb_input_xi_device_info_next(&it)) {
xcb_input_xi_device_info_t* deviceInfo = it.data;
// touch device is slave pointer device
if (deviceInfo->type != XCB_INPUT_DEVICE_TYPE_SLAVE_POINTER)
continue;
if (findTouchModeFromDevice(deviceInfo, type))
return true;
}
return false;
}
bool existTouchDevice(xcb_input_touch_mode_t type)
{
// connect to xserver
int primaryScreen = 0;
xcb_connection_t* conn = xcb_connect(nullptr, &primaryScreen);
if (!conn) {
qWarning() << __FILE__ << __FUNCTION__ << __LINE__
<< "The X11 connection broke. Did the X11 server die?";
return false;
}
int error = xcb_connection_has_error(conn);
if (error) {
qWarning() << __FILE__ << __FUNCTION__ << __LINE__
<< "The X11 connection broke. Did the X11 server die?"
<< "Error:" << error;
xcb_disconnect(conn);
return false;
}
// query all device
auto reply = XCB_REPLY(xcb_input_xi_query_device, conn, XCB_INPUT_DEVICE_ALL);
if (!reply) {
qWarning() << __FILE__ << __FUNCTION__ << __LINE__
<< "Failed to query device";
xcb_disconnect(conn);
return false;
}
xcb_disconnect(conn);
return findTouchFromReply(reply.get(), type);
}
} // namespace

View File

@ -0,0 +1,29 @@
#ifndef DEVICEMONITOREVENTFILTER_H
#define DEVICEMONITOREVENTFILTER_H
#include <QAbstractNativeEventFilter>
#include <QObject>
class QTimer;
// If you are using multiple inheritance, moc assumes that the first inherited class is
// a subclass of QObject. Also, be sure that only the first inherited class is a QObject.
class DeviceMonitorEventFilter : public QObject, public QAbstractNativeEventFilter
{
Q_OBJECT
public:
explicit DeviceMonitorEventFilter(QObject* parent = nullptr);
~DeviceMonitorEventFilter() override = default;
bool nativeEventFilter(const QByteArray &eventType,
void* message, long int* result) override;
Q_SIGNALS:
void touchScreenChanged(bool exist);
private:
void setupTouchDevices();
bool handleXcbGeEvent(void* message);
// Filter recurring events for short periods of time(20ms)
QTimer* m_timer = nullptr;
};
#endif // DEVICEMONITOREVENTFILTER_H

View File

@ -0,0 +1,33 @@
#pragma once
#include <QDebug>
#include <xcb/xcb.h>
#include <cstring>
class GeEventMemMover
{
public:
GeEventMemMover(xcb_generic_event_t *event)
: m_event(reinterpret_cast<xcb_ge_generic_event_t *>(event))
{
// xcb event structs contain stuff that wasn't on the wire, the full_sequence field
// adds an extra 4 bytes and generic events cookie data is on the wire right after the standard 32 bytes.
// Move this data back to have the same layout in memory as it was on the wire
// and allow casting, overwriting the full_sequence field.
memmove((char*) m_event + 32, (char*) m_event + 36, m_event->length * 4);
}
~GeEventMemMover()
{
// move memory layout back, so that Qt can do the same without breaking
memmove((char*) m_event + 36, (char *) m_event + 32, m_event->length * 4);
}
xcb_ge_generic_event_t *operator->() const {
return m_event;
}
private:
xcb_ge_generic_event_t *m_event;
};

View File

@ -0,0 +1,10 @@
INCLUDEPATH += $$PWD
HEADERS += \
$$PWD/devicemonitoreventfilter.h \
$$PWD/geeventmemmover.h
SOURCES += \
$$PWD/devicemonitoreventfilter.cpp
LIBS += -lxcb-xinput -lxcb

View File

@ -1,8 +1,12 @@
#include "touchscreen-settings.h"
#include "touchscreen.h"
#include "gestureguidance.h"
#include "devicemonitoreventfilter.h"
#include <ukuisdk/kylin-com4cxx.h>
#include <string>
#include <QLocale>
#include <QTranslator>
#include <QApplication>
@ -10,11 +14,15 @@
#include <QString>
#include <QWidget>
#include <QIcon>
#include <QDebug>
#include <QGSettings>
#include <QByteArray>
static const std::string PROJECT_V10SP1 = "V10SP1";
static const std::string PROJECT_INTEL = "V10SP1-edu";
static const std::string PLUGINS_SCHEMA = "org.ukui.control-center.plugins";
static const std::string PLUGINS_DIR = "/org/ukui/control-center/plugins/";
static const std::string KEY_SHOW = "show";
TouchscreenSettings::TouchscreenSettings() : mFirstLoad(true)
{
@ -24,6 +32,11 @@ TouchscreenSettings::TouchscreenSettings() : mFirstLoad(true)
prjCodeName = QString::fromStdString(KDKGetPrjCodeName());
productFeatures = QString::fromStdString(KDKGetOSRelease("PRODUCT_FEATURES"));
m_deviceMonitor = new DeviceMonitorEventFilter(this);
qApp->installNativeEventFilter(m_deviceMonitor);
connect(m_deviceMonitor, &DeviceMonitorEventFilter::touchScreenChanged,
this, &TouchscreenSettings::setShow);
}
TouchscreenSettings::~TouchscreenSettings()
@ -112,3 +125,25 @@ QString TouchscreenSettings::translationPath() const
void TouchscreenSettings::initSearchText()
{
}
void TouchscreenSettings::setShow(bool isShow)
{
const QByteArray id(PLUGINS_SCHEMA.c_str());
if (!QGSettings::isSchemaInstalled(id)) {
qWarning() << __FILE__ << __FUNCTION__ << __LINE__
<< "schema :" << id << "is not install";
return;
}
QString path(QString::fromStdString(PLUGINS_DIR + "TouchScreen/"));
QGSettings settings(id, path.toUtf8());
// It is signaled whenever the device list changes,
// so it is possible that the touchscreen state has not changed
if (settings.get(KEY_SHOW.c_str()).toBool() == isShow)
return;
settings.set(KEY_SHOW.c_str(), isShow);
qDebug() << __FILE__ << __FUNCTION__ << __LINE__
<< "Set GSetting" << id << path << KEY_SHOW.c_str() << isShow;
}

View File

@ -8,6 +8,7 @@
class QWidget;
class QString;
class QIcon;
class DeviceMonitorEventFilter;
class TouchscreenSettings : public QObject, CommonInterface
{
Q_OBJECT
@ -30,11 +31,15 @@ public:
private:
QString prjCodeName;
QString productFeatures;
QWidget *pluginWidget;
QWidget *pluginWidget = nullptr;
bool mFirstLoad;
DeviceMonitorEventFilter* m_deviceMonitor = nullptr;
private:
void initSearchText(); // 搜索翻译
private Q_SLOTS:
void setShow(bool isShow); // 显示/隐藏插件
};
#endif // TOUCHSCREENSETTINGS_H

View File

@ -32,6 +32,7 @@ LIBS += $$[QT_INSTALL_LIBS]/libukui-com4cxx.so
include(../gesture-widget/gesture-widget.pri)
include(item-widget/item-widget.pri)
include(flow-layout/flow-layout.pri)
include(../native-filter/native-filter.pri)
#include(video-widget/video-widget.pri)
SOURCES += \