Skip to content

Commit 3d0e674

Browse files
add-uosdeepin-bot[bot]
authored andcommitted
feat(input): add keyboard enable/disable functionality
https://pms.uniontech.com/bug-view-346127.htmle functionality Add support for enabling/disabling USB keyboard devices through udev rules. The implementation includes: - Backend service method to create/remove udev rules for keyboard control - Device management logic to handle keyboard enable/disable operations - DBus interface for communication between frontend and backend - VID/PID validation and sanitization for USB device identification - Authorization checks to ensure privileged operations are protected log: add keyboard enable/disable functionality bug: https://pms.uniontech.com/bug-view-346127.html
1 parent 9be8811 commit 3d0e674

11 files changed

Lines changed: 360 additions & 12 deletions

File tree

deepin-devicemanager-server/deepin-devicecontrol/src/controlinterface.cpp

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#include <QProcess>
1717
#include <QDir>
1818
#include <QFile>
19+
#include <QFileInfo>
20+
#include <QTextStream>
1921
#include <QRegularExpression>
2022
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2123
#include <polkit-qt5-1/PolkitQt1/Authority>
@@ -204,6 +206,93 @@ bool ControlInterface::enablePrinter(const QString &hclass, const QString &name,
204206
return true;
205207
}
206208

209+
bool ControlInterface::enableKeyboard(const QString& vid, const QString& pid, const QString &hclass, const QString &name, const QString &sPath, const QString &value, bool enable_device, const QString strDriver)
210+
{
211+
qCInfo(appLog) << "Enable keyboard request:" << hclass << name << sPath << value << "enable:" << enable_device;
212+
213+
if (!getUserAuthorPasswd()) {
214+
qCWarning(appLog) << "Authorization failed for keyboard enable operation";
215+
return false;
216+
}
217+
218+
// Use EnableUtils's validation function
219+
QString safeVid, safePid;
220+
if (!EnableUtils::validateAndNormalizeVidPid(vid, pid, safeVid, safePid)) {
221+
qCWarning(appLog) << "VID or PID validation failed. VID:" << vid << "PID:" << pid;
222+
return false;
223+
}
224+
225+
QString rulesFile = QString(UDEV_RULES_PATH_LOCAL "/99-keyboard-device-control-%1-%2.rules").arg(vid).arg(pid);
226+
227+
QString ruleContent;
228+
if (enable_device) {
229+
ruleContent = QString("# enable keyboard - VID:%1 PID:%2\n"
230+
"ACTION==\"add|change\", SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"%1\", ATTRS{idProduct}==\"%2\", "
231+
"ATTR{authorized}=\"1\"").arg(safeVid, safePid);
232+
qCInfo(appLog) << "Creating rule to enable keyboard";
233+
} else {
234+
ruleContent = QString("# disable keyboard - VID:%1 PID:%2\n"
235+
"ACTION==\"add|change\", SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"%1\", ATTRS{idProduct}==\"%2\", "
236+
"ATTR{authorized}=\"0\"").arg(safeVid, safePid);
237+
qCInfo(appLog) << "Creating rule to disable keyboard";
238+
}
239+
240+
QFileInfo fileInfo(rulesFile);
241+
QDir rulesDir = fileInfo.absoluteDir();
242+
if (!rulesDir.exists()) {
243+
qCWarning(appLog) << "Udev rules directory does not exist:" << rulesDir.absolutePath();
244+
return false;
245+
}
246+
247+
QFile file(rulesFile);
248+
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
249+
qCWarning(appLog) << "Failed to open udev rules file for writing:" << rulesFile;
250+
qCWarning(appLog) << "Error:" << file.errorString();
251+
return false;
252+
}
253+
254+
QTextStream out(&file);
255+
out << ruleContent;
256+
file.close();
257+
258+
if (file.error() != QFile::NoError) {
259+
qCWarning(appLog) << "Failed to write udev rule. Error:" << file.errorString();
260+
return false;
261+
}
262+
263+
QProcess reloadProcess;
264+
qCInfo(appLog) << "Reloading udev rules";
265+
reloadProcess.start("udevadm", QStringList() << "control" << "--reload-rules");
266+
reloadProcess.waitForFinished(10000);
267+
268+
if (reloadProcess.exitCode() != 0) {
269+
qCWarning(appLog) << "Failed to reload udev rules. Error:" << reloadProcess.readAllStandardError();
270+
return false;
271+
}
272+
273+
QProcess triggerProcess;
274+
qCInfo(appLog) << "Triggering udev rules";
275+
triggerProcess.start("udevadm", QStringList() << "trigger");
276+
triggerProcess.waitForFinished(10000);
277+
278+
if (triggerProcess.exitCode() != 0) {
279+
qCWarning(appLog) << "Failed to trigger udev rules. Error:" << triggerProcess.readAllStandardError();
280+
return false;
281+
}
282+
283+
if (!enable_device) {
284+
EnableSqlManager::getInstance()->insertDataToAuthorizedTable(hclass, name, sPath, value, true, strDriver);
285+
qCInfo(appLog) << "insertDataToAuthorizedTable";
286+
} else {
287+
EnableSqlManager::getInstance()->removeDataFromAuthorizedTable(value);
288+
qCInfo(appLog) << "removeDataFromAuthorizedTable";
289+
}
290+
291+
qCInfo(appLog) << "Keyboard udev rule created and applied successfully";
292+
emit sigUpdate();
293+
return true;
294+
}
295+
207296
void ControlInterface::disableInDevice()
208297
{
209298
if (!getUserAuthorPasswd())

deepin-devicemanager-server/deepin-devicecontrol/src/controlinterface.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,23 @@
1313
#include <QObject>
1414
#include <QDBusContext>
1515

16+
// Udev rules directory paths
17+
/*
18+
* Udev rules are read from the following directories in priority order:
19+
* 1. /etc/udev/rules.d/ - Highest priority, for local administrator custom rules
20+
* 2. /run/udev/rules.d/ - Runtime rules (may not exist on all systems)
21+
* 3. /usr/local/lib/udev/rules.d/ - Local software rules (recommended for applications)
22+
* 4. /usr/lib/udev/rules.d/ - System default rules
23+
*
24+
* All rules files are processed in lexical order, regardless of the directory they live in.
25+
* Files with identical filenames replace each other. Files in /etc have the highest priority.
26+
* Rule files must have the extension .rules; other extensions are ignored.
27+
*/
28+
#define UDEV_RULES_PATH_LOCAL "/usr/local/lib/udev/rules.d"
29+
#define UDEV_RULES_PATH_SYSTEM "/etc/udev/rules.d"
30+
#define UDEV_RULES_PATH_RUNTIME "/run/udev/rules.d"
31+
#define UDEV_RULES_PATH_LIB "/usr/lib/udev/rules.d"
32+
1633
class DriverManager;
1734
class ModCore;
1835
class ControlInterface : public QDBusService, protected QDBusContext
@@ -69,6 +86,17 @@ public slots:
6986
* @return
7087
*/
7188
Q_SCRIPTABLE bool enablePrinter(const QString &hclass, const QString &name, const QString &path, bool enable_device);
89+
/**
90+
* @brief enableKeyboard 启用/禁用键盘设备
91+
* @param hclass 设备类型
92+
* @param name 设备名称
93+
* @param sPath 设备路径
94+
* @param value 设备唯一标识
95+
* @param enable_device 是否启用
96+
* @param strDriver 驱动名称
97+
* @return 操作是否成功
98+
*/
99+
Q_SCRIPTABLE bool enableKeyboard(const QString& vid, const QString& pid, const QString &hclass, const QString &name, const QString &sPath, const QString &value, bool enable_device, const QString strDriver);
72100
/**
73101
* @brief disableOutDevice 禁用设备
74102
* @param devInfo 设备信息

deepin-devicemanager-server/deepin-devicecontrol/src/enablecontrol/enableutils.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,45 @@ bool EnableUtils::ioctlOperateNetworkLogicalName(const QString &logicalName, boo
204204
return true;
205205
}
206206

207+
bool EnableUtils::validateAndNormalizeVidPid(const QString &vid, const QString &pid, QString &normalizedVid, QString &normalizedPid)
208+
{
209+
qCDebug(appLog) << "Validating and normalizing VID:" << vid << "PID:" << pid;
210+
211+
// Check if VID or PID is empty
212+
if (vid.isEmpty() || pid.isEmpty()) {
213+
qCWarning(appLog) << "VID or PID is empty for validation";
214+
return false;
215+
}
216+
217+
// Normalize VID
218+
normalizedVid = vid.toLower();
219+
if (normalizedVid.startsWith("0x")) {
220+
normalizedVid = normalizedVid.mid(2);
221+
}
222+
223+
// Normalize PID
224+
normalizedPid = pid.toLower();
225+
if (normalizedPid.startsWith("0x")) {
226+
normalizedPid = normalizedPid.mid(2);
227+
}
228+
229+
// Validate VID and PID format (should be 4 characters each after normalization)
230+
if (normalizedVid.length() != 4 || normalizedPid.length() != 4) {
231+
qCWarning(appLog) << "Invalid VID or PID format after normalization. VID:" << normalizedVid << "PID:" << normalizedPid;
232+
return false;
233+
}
234+
235+
// Validate that VID and PID contain only hex characters
236+
QRegularExpression hexPattern("^[0-9a-f]{4}$");
237+
if (!hexPattern.match(normalizedVid).hasMatch() || !hexPattern.match(normalizedPid).hasMatch()) {
238+
qCWarning(appLog) << "VID or PID contains non-hex characters. VID:" << normalizedVid << "PID:" << normalizedPid;
239+
return false;
240+
}
241+
242+
qCDebug(appLog) << "VID and PID validation successful. Normalized VID:" << normalizedVid << "Normalized PID:" << normalizedPid;
243+
return true;
244+
}
245+
207246
bool EnableUtils::getMapInfo(const QString &item, QMap<QString, QString> &mapInfo)
208247
{
209248
qCDebug(appLog) << "Parsing device info map";

deepin-devicemanager-server/deepin-devicecontrol/src/enablecontrol/enableutils.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ class EnableUtils
1818
*/
1919
static void disableOutDevice(const QString &info);
2020

21-
2221
/**
2322
* @brief disableInDevice 禁用非外设
2423
*/
@@ -31,7 +30,17 @@ class EnableUtils
3130
* @return
3231
*/
3332
static bool ioctlOperateNetworkLogicalName(const QString &logicalName, bool enable);
34-
33+
34+
/**
35+
* @brief validateAndNormalizeVidPid 验证并标准化VID和PID
36+
* @param vid 输入的VID
37+
* @param pid 输入的PID
38+
* @param normalizedVid 输出的标准化VID
39+
* @param normalizedPid 输出的标准化PID
40+
* @return 验证是否成功
41+
*/
42+
static bool validateAndNormalizeVidPid(const QString &vid, const QString &pid, QString &normalizedVid, QString &normalizedPid);
43+
3544
/**
3645
* @brief getMapInfo 解析usb信息
3746
* @param item

deepin-devicemanager/src/DeviceManager/DeviceInput.cpp

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,24 @@ TomlFixMethod DeviceInput::setInfoFromTomlOneByOne(const QMap<QString, QString>
102102
void DeviceInput::setInfoFromHwinfo(const QMap<QString, QString> &mapInfo)
103103
{
104104
qCDebug(appLog) << "setInfoFromHwinfo";
105+
if (mapInfo.find("path") != mapInfo.end()) {
106+
qCDebug(appLog) << "Path found in mapInfo, setting basic attributes.";
107+
setAttribute(mapInfo, "name", m_Name);
108+
setAttribute(mapInfo, "driver", m_Driver);
109+
setAttribute(mapInfo, "path", m_SysPath);
110+
setAttribute(mapInfo, "unique_id", m_VID_PID);
111+
if (m_SysPath.contains("usb", Qt::CaseInsensitive)) {
112+
m_Interface = "USB";
113+
}
114+
if (m_VID_PID.size() == 10) {
115+
m_VID = m_VID_PID.mid(2, 4);
116+
m_PID = m_VID_PID.last(4);
117+
}
118+
m_Enable = false;
119+
// m_CanUninstall = !driverIsKernelIn(m_Driver);
120+
qCWarning(appLog) << "Device disabled, skip further processing";
121+
return;
122+
}
105123

106124
//取触摸板的状态
107125
if (mapInfo.find("Model") != mapInfo.end() && mapInfo["Model"].contains("Touchpad", Qt::CaseInsensitive)) {
@@ -125,6 +143,8 @@ void DeviceInput::setInfoFromHwinfo(const QMap<QString, QString> &mapInfo)
125143
setAttribute(mapInfo, "Unique ID", m_UniqueID);
126144
setAttribute(mapInfo, "Module Alias", m_Modalias);
127145
setAttribute(mapInfo, "VID_PID", m_VID_PID);
146+
setAttribute(mapInfo, "PID", m_PID);
147+
setAttribute(mapInfo, "VID", m_VID);
128148
m_PhysID = m_VID_PID;
129149
// 防止Serial ID为空
130150
if (m_SerialID.isEmpty()) {
@@ -500,6 +520,45 @@ const QString DeviceInput::getOverviewInfo()
500520
EnableDeviceStatus DeviceInput::setEnable(bool e)
501521
{
502522
qCDebug(appLog) << "setEnable";
523+
524+
// 处理键盘设备
525+
if (m_HardwareClass == "keyboard") {
526+
qCDebug(appLog) << "setEnable keyboard device";
527+
528+
if (m_Interface.contains("USB", Qt::CaseInsensitive)) {
529+
qCDebug(appLog) << "USB keyboard detected, using backend service";
530+
531+
QString vid = getVID();
532+
QString pid = getPID();
533+
534+
if (vid.isEmpty() || pid.isEmpty()) {
535+
qCWarning(appLog) << "Cannot enable keyboard: VID or PID is empty. VID:" << vid << "PID:" << pid;
536+
return EDS_Faild;
537+
}
538+
539+
// Use DeviceManager's validation function
540+
QString safeVid, safePid;
541+
if (!DeviceManager::instance()->validateKeyboardVidPid(vid, pid, safeVid, safePid)) {
542+
qCWarning(appLog) << "VID or PID validation failed. VID:" << vid << "PID:" << pid;
543+
return EDS_Faild;
544+
}
545+
546+
bool res = DBusEnableInterface::getInstance()->enableKeyboard(safeVid, safePid, m_HardwareClass, m_Name, m_SysPath, m_VID_PID, e, m_Driver);
547+
if (res) {
548+
m_Enable = e;
549+
qCDebug(appLog) << "Keyboard enable operation successful:" << e;
550+
} else {
551+
qCWarning(appLog) << "Keyboard enable operation failed";
552+
}
553+
554+
return res ? EDS_Success : EDS_Faild;
555+
} else {
556+
qCDebug(appLog) << "Non-USB keyboard detected, cannot disable/enable using udev rules";
557+
return EDS_Faild;
558+
}
559+
}
560+
561+
// 处理触摸板设备
503562
if (m_Name.contains("Touchpad", Qt::CaseInsensitive)) {
504563
qCDebug(appLog) << "setEnable touchpad";
505564
DBusTouchPad::instance()->setEnable(e);
@@ -537,12 +596,6 @@ EnableDeviceStatus DeviceInput::setEnable(bool e)
537596
bool DeviceInput::enable()
538597
{
539598
// qCDebug(appLog) << "enable";
540-
// 键盘不可禁用
541-
if (m_HardwareClass == "keyboard") {
542-
// qCDebug(appLog) << "enable keyboard";
543-
m_Enable = true;
544-
}
545-
// qCDebug(appLog) << "enable end";
546599
return m_Enable;
547600
}
548601

deepin-devicemanager/src/DeviceManager/DeviceInput.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@ class DeviceInput : public DeviceBaseInfo
154154
* @brief loadTableData:加载表头信息
155155
*/
156156
void loadTableData() override;
157-
158157
private:
159158

160159
/**

0 commit comments

Comments
 (0)