From 21cadc0f1b183809116007037b3e1e134f5adc39 Mon Sep 17 00:00:00 2001 From: lichaofan Date: Tue, 17 Mar 2026 11:09:17 +0800 Subject: [PATCH] feat: enable the configuration for device blacklist. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the DConfig mechanism to enable the configuration for device blacklist. 使用DConfig机制,开发设备黑名单功能,处在黑名单中的设备不会被应用显示。如果现有设备全部在黑名单中,则会有一个默认设备用于预览。 代码来自xiwo分支。 Bug: https://pms.uniontech.com/bug-view-349401.html --- src/assets/org.deepin.camera.encode.json | 10 ++ src/main.cpp | 6 + src/src/basepub/datamanager.cpp | 48 +++++++ src/src/basepub/datamanager.h | 12 ++ src/src/devnummonitor.cpp | 5 +- src/src/devnummonitor.h | 5 + src/src/mainwindow.cpp | 6 + src/src/videowidget.cpp | 160 ++++++++++++++++------- src/src/videowidget.h | 77 +++++++++++ 9 files changed, 283 insertions(+), 46 deletions(-) diff --git a/src/assets/org.deepin.camera.encode.json b/src/assets/org.deepin.camera.encode.json index 9b30cf0e0..18e14441f 100644 --- a/src/assets/org.deepin.camera.encode.json +++ b/src/assets/org.deepin.camera.encode.json @@ -91,6 +91,16 @@ "description": "Enable 8k preview", "permissions": "readwrite", "visibility": "private" + }, + "deviceBlacklist": { + "value": [], + "serial": 0, + "flags": ["global"], + "name": "device blacklist", + "name[zh_CN]": "设备黑名单", + "description": "Set device blacklist, format: vid,pid,name", + "permissions": "readwrite", + "visibility": "private" } } } diff --git a/src/main.cpp b/src/main.cpp index ce2f1917d..4481f2dc5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -199,6 +199,12 @@ int main(int argc, char *argv[]) set_enable_8k_preview(enable ? 1 : 0); } + if (dconfig && dconfig->isValid() && dconfig->keyList().contains("deviceBlacklist")) { + QStringList deviceBlacklist = dconfig->value("deviceBlacklist").toStringList(); + qInfo() << "device blacklist:" << deviceBlacklist; + DataManager::instance()->setDeviceBlacklist(deviceBlacklist); + } + if (!libVaDriverName.isEmpty()) { qputenv("LIBVA_DRIVER_NAME", libVaDriverName.toLocal8Bit()); } diff --git a/src/src/basepub/datamanager.cpp b/src/src/basepub/datamanager.cpp index 4fbbd3c65..3c0f5448b 100644 --- a/src/src/basepub/datamanager.cpp +++ b/src/src/basepub/datamanager.cpp @@ -55,6 +55,54 @@ bool DataManager::encExists(){ return m_H264EncoderExists; } +void DataManager::setDeviceBlacklist(const QStringList &blacklist) +{ + // 无论是否为空,都先清空现有黑名单 + m_deviceBlacklistSet.clear(); + if (blacklist.isEmpty()) { + qInfo() << "Empty blacklist provided"; + return; + } + + // 每一条黑名单项的格式都是 vid,pid,name + // 其中vid和pid都是4位十六进制数,name是设备名称 + // 后续匹配操作的时候,我们会忽略大小写 + for (const QString &item : blacklist) { + QStringList parts = item.split(","); + if (parts.size() != 3) { + qWarning() << "Drop blacklist item(format error):" << item; + continue; + } + + // 验证VID和PID是否为4位十六进制数 + static const QRegularExpression hexPattern("^[0-9a-fA-F]{4}$"); + if (!hexPattern.match(parts[0]).hasMatch() || !hexPattern.match(parts[1]).hasMatch()) { + qWarning() << "Drop blacklist item(invalid VID/PID):" << item; + continue; + } + + // 验证设备名称不为空,且长度不超过100个字符 + if (parts[2].trimmed().isEmpty() || parts[2].size() > 100) { + qWarning() << "Drop blacklist item(empty device name OR too long):" << item; + continue; + } + + qInfo() << "Add blacklist item:" << item; + m_deviceBlacklistSet.insert(item.toLower()); + } +}; + +bool DataManager::isDeviceValid(const QString &vid, const QString &pid, const QString &name) +{ + // 参数验证 + if (vid.isEmpty() || pid.isEmpty() || name.isEmpty()) { + return true; // 空参数视为有效,避免误判 + } + + QString key = vid.toLower() + "," + pid.toLower() + "," + name.toLower(); + return !m_deviceBlacklistSet.contains(key); +} + DataManager *DataManager::instance() { if (m_dataManager == nullptr) { diff --git a/src/src/basepub/datamanager.h b/src/src/basepub/datamanager.h index fd9d1f544..97fe125b0 100644 --- a/src/src/basepub/datamanager.h +++ b/src/src/basepub/datamanager.h @@ -187,6 +187,17 @@ class DataManager: public QObject * @return */ bool isEnable8kPreview() const { return m_enable8kPreview; }; + + /** + * @brief 设置Camera设备黑名单 + * @param blacklist + */ + void setDeviceBlacklist(const QStringList &blacklist); + /** + * @brief 检查当前设备是否有效 + * @return + */ + bool isDeviceValid(const QString &vid, const QString &pid, const QString &name); private: DataManager(); static DataManager *m_dataManager; @@ -200,5 +211,6 @@ class DataManager: public QObject bool m_isPreviewNoDelay = false; // 是否预览无延迟 bool m_enableUsbGroup = false; // 是否启用USB摄像头分组 bool m_enable8kPreview = false; // 是否启用8K预览 + QSet m_deviceBlacklistSet; // 设备黑名单 }; #endif // DATAMANAGER_H diff --git a/src/src/devnummonitor.cpp b/src/src/devnummonitor.cpp index 0283dcd94..4d50c5b5c 100644 --- a/src/src/devnummonitor.cpp +++ b/src/src/devnummonitor.cpp @@ -44,7 +44,10 @@ void DevNumMonitor::startCheck() void DevNumMonitor::timeOutSlot() { - check_device_list_events(get_v4l2_device_handler()); + // 检查设备列表变化事件 + if (check_device_list_events(get_v4l2_device_handler())) { + emit deviceListChanged(); + } if (get_device_list()->num_devices <= 1) { emit seltBtnStateDisable(); diff --git a/src/src/devnummonitor.h b/src/src/devnummonitor.h index 1176ffbc3..551c18949 100644 --- a/src/src/devnummonitor.h +++ b/src/src/devnummonitor.h @@ -60,6 +60,11 @@ class DevNumMonitor: public QObject */ void existDevice(); + /** + * @brief deviceListChanged 相机设备列表改变信号 + */ + void deviceListChanged(); + protected: /** * @brief run 运行 diff --git a/src/src/mainwindow.cpp b/src/src/mainwindow.cpp index d8aa03bd5..0b7c09359 100644 --- a/src/src/mainwindow.cpp +++ b/src/src/mainwindow.cpp @@ -1240,6 +1240,7 @@ void CMainWindow::loadAfterShow() if(DataManager::instance()->encodeEnv() != QCamera_Env) { connect(m_devnumMonitor, SIGNAL(existDevice()), m_videoPre, SLOT(onRestartDevices()));//重启设备 connect(m_devnumMonitor, SIGNAL(noDeviceFound()), m_videoPre, SLOT(onRestartDevices()));//重启设备 + connect(m_devnumMonitor, SIGNAL(deviceListChanged()), m_videoPre, SLOT(updateValidDevices())); // 更新可用设备列表 } else if (DataManager::instance()->encodeEnv() == QCamera_Env) { initCameraConnection(); } @@ -2037,6 +2038,11 @@ void CMainWindow::onLocalTimeChanged() void CMainWindow::setSelBtnShow() { + // 有效设备个数小于等于1,不显示切换按钮 + if (m_videoPre->getValidDeviceNum() <= 1) { + return; + } + m_bSwitchCameraShowEnable = true; if (m_cameraSwitchBtn->isHidden()) { showChildWidget(); diff --git a/src/src/videowidget.cpp b/src/src/videowidget.cpp index ec2dac2f7..58f28dd5f 100644 --- a/src/src/videowidget.cpp +++ b/src/src/videowidget.cpp @@ -276,8 +276,18 @@ void videowidget::delayInit() m_flashLabel->hide(); QString device = dc::Settings::get().getBackOption("device").toString(); - //启动视频 - switchCamera(device.toStdString().c_str(), ""); + // 启动视频预览 + // 如果配置的设备无效,切换到第一个有效的设备 + // 如果有效的设备也不存在,走默认流程 + updateValidDevices(); + if (!isDeviceValidByDevice(device)) { + QString validDevice = getFirstValidDevice(); + qWarning() << "INVALID device from config:" << device << ", found first valid device:" << validDevice; + switchCamera(validDevice.toStdString().c_str(), ""); + } else { + qInfo() << "VALID device from config:" << device; + switchCamera(device.toStdString().c_str(), ""); // 走默认逻辑 + } QObject::connect(DGuiApplicationHelper::instance(), &DGuiApplicationHelper::themeTypeChanged, this, &videowidget::onThemeTypeChanged); @@ -1135,38 +1145,25 @@ void videowidget::onChangeDev() groupNum = getUSBCameraGroup(devlist, vGroupData); qInfo() << __func__ << "groupNum:" << groupNum; } - if (groupNum == 1) { - if (devlist->num_devices == 2) { - for (int i = 0 ; i < devlist->num_devices; i++) { - const char *curDev = devlist->list_devices[i].device; - if (str != curDev) { - if (E_OK == switchCamera(curDev, devlist->list_devices[i].name)) { - break; - } - } - } + // 如果摄像头设备个数为0,分组情况就不用考虑了,直接显示无摄像头提示 + if (devlist->num_devices == 0) { + DataManager::instance()->setdevStatus(NOCAM); + showNocam(); + } else if (groupNum == 0) { + switchCamera("", ""); // 无有效设备,但是有设备,走默认逻辑 + } else if (groupNum == 1) { + if (m_validDevices.empty()) { + switchCamera("", ""); // 无有效设备,但是有设备,走默认逻辑 } else { - if (devlist->num_devices == 0) { - DataManager::instance()->setdevStatus(NOCAM); - showNocam(); - } - - for (int i = 0 ; i < devlist->num_devices; i++) { - const char *curDev = devlist->list_devices[i].device; - if (str == curDev) { - if (i == devlist->num_devices - 1) { - switchCamera(devlist->list_devices[0].device, devlist->list_devices[0].name); - break; - } else { - switchCamera(devlist->list_devices[i + 1].device, devlist->list_devices[i + 1].name); - break; - } - } - - if (str.isEmpty()) { - switchCamera(devlist->list_devices[0].device, devlist->list_devices[0].name); - break; - } + int idx = getValidDeviceIndexByDevice(str); + if (idx == -1 || idx >= m_validDevices.size() - 1) { + // 获取第一个有效设备,直接切换到该设备 + const ValidDevice &dev = m_validDevices[0]; + switchCamera(dev.getDevice().toStdString().c_str(), dev.getName().toStdString().c_str()); + } else { + // 切换到当前设备的下一个有效设备 + const ValidDevice &dev = m_validDevices[idx + 1]; + switchCamera(dev.getDevice().toStdString().c_str(), dev.getName().toStdString().c_str()); } } } else { @@ -1180,14 +1177,11 @@ void videowidget::onChangeDev() } } } else { - if (devlist->num_devices == 0) { - DataManager::instance()->setdevStatus(NOCAM); - showNocam(); - } - + bool found = false; // 标记是否找到当前设备 for (int i = 0 ; i < vGroupData.count(); i++) { const char *curDev = vGroupData[i].second[0]->device; if (str == curDev) { + found = true; if (i == vGroupData.count() - 1) { switchCamera(vGroupData[0].second[0]->device, vGroupData[0].second[0]->name); break; @@ -1196,11 +1190,10 @@ void videowidget::onChangeDev() break; } } - - if (str.isEmpty()) { - switchCamera(vGroupData[0].second[0]->device, vGroupData[0].second[0]->name); - break; - } + } + if (!found) { + // 未找到当前设备,切换到第一个设备 + switchCamera(vGroupData[0].second[0]->device, vGroupData[0].second[0]->name); } } } @@ -1257,12 +1250,16 @@ int videowidget::getUSBCameraGroup(v4l2_device_list_t *devlist, QVector> 来存储分组数据,但我们担心影响现有代码逻辑, // 所以暂时保留 QVector>> 来存储分组数据。 - if (devlist == nullptr) { + if (devlist == nullptr || devlist->list_devices == nullptr || devlist->num_devices == 0) { qWarning() << __func__ << "devlist is NULL!"; return 0; } - for (int i = 0 ; i < devlist->num_devices; i++) { + for (int i = 0; i < devlist->num_devices; i++) { + if (!isDeviceValidByDevice(devlist->list_devices[i].device)) { + continue; // 无效设备不参与分组 + } + QString location = QString(devlist->list_devices[i].location); int j = 0; @@ -1756,6 +1753,79 @@ void videowidget::onFilterDisplayChanged(int bDisplay) m_imgPrcThread->setFilterGroupState(bDisplay); } +QString videowidget::getFirstValidDevice() +{ + // 加锁,确保线程安全 + QReadLocker locker(&m_mutexValidDevices); + if (m_validDevices.isEmpty()) { + qWarning() << __func__ << "no valid device!"; + return ""; + } + + // 返回第一个有效相机设备的设备节点路径 + const ValidDevice &dev = m_validDevices.first(); + qInfo() << __func__ << dev.getVid() << dev.getPid() << dev.getName() << dev.getDevice(); + return dev.getDevice(); +} + +void videowidget::updateValidDevices() +{ + qInfo() << __func__; + // 加锁,确保线程安全 + QWriteLocker locker(&m_mutexValidDevices); + m_validDevices.clear(); // 清空有效相机设备列表 + + v4l2_device_list_t *devList = get_device_list(); + if (!devList) { + qWarning() << __func__ << "get device list FAILED!"; + return; + } + + v4l2_dev_sys_data_t *v4l2_devices = devList->list_devices; + if (!v4l2_devices) { + qWarning() << __func__ << "get device list FAILED!"; + return; + } + + for (int i = 0; i < devList->num_devices; i++) { + // 获取设备的VID和PID,不足4位用0填充 + QString vid = formatDeviceId(v4l2_devices[i].vendor); + QString pid = formatDeviceId(v4l2_devices[i].product); + if (DataManager::instance()->isDeviceValid(vid, pid, v4l2_devices[i].name)) { + qInfo() << __func__ << "found valid device:" << vid << pid << v4l2_devices[i].name << v4l2_devices[i].device; + // 添加有效相机设备 + m_validDevices.push_back(ValidDevice(vid, pid, v4l2_devices[i].name, v4l2_devices[i].device)); + continue; + } + + qInfo() << __func__ << "found invalid device(blacklist):" << vid << pid << v4l2_devices[i].name << v4l2_devices[i].device; + } +} + +bool videowidget::isDeviceValidByDevice(const QString &device) +{ + return getValidDeviceIndexByDevice(device) != -1; +} + +int videowidget::getValidDeviceIndexByDevice(const QString &device) +{ + // 加锁,确保线程安全 + QReadLocker locker(&m_mutexValidDevices); + for (int i = 0; i < m_validDevices.size(); i++) { + if (m_validDevices[i].getDevice() == device) { + return i; + } + } + return -1; +} + +int videowidget::getValidDeviceNum() +{ + // 加锁,确保线程安全 + QReadLocker locker(&m_mutexValidDevices); + return m_validDevices.size(); +} + videowidget::~videowidget() { m_imgPrcThread->stop(); diff --git a/src/src/videowidget.h b/src/src/videowidget.h index 54b03c12f..317038cbd 100644 --- a/src/src/videowidget.h +++ b/src/src/videowidget.h @@ -21,6 +21,7 @@ #include #include #include +#include #include "LPF_V4L2.h" #include "majorimageprocessingthread.h" @@ -64,6 +65,40 @@ class QGraphicsViewEx : public QGraphicsView virtual void mouseMoveEvent(QMouseEvent* e) override; }; +/** +* @brief ValidDevice 有效相机设备,与黑名单机制配套使用 +*/ +class ValidDevice +{ +public: + ValidDevice() = default; + ValidDevice(const QString &_vid, const QString &_pid, const QString &_name, const QString &_device) + : vid(_vid), pid(_pid), name(_name), device(_device) {} + + bool operator==(const ValidDevice &other) const { + return vid == other.vid && pid == other.pid && + name == other.name && device == other.device; + } + + // Setter 方法 + void setVid(const QString &_vid) { vid = _vid; } + void setPid(const QString &_pid) { pid = _pid; } + void setName(const QString &_name) { name = _name; } + void setDevice(const QString &_device) { device = _device; } + + // Getter 方法 + QString getVid() const { return vid; } + QString getPid() const { return pid; } + QString getName() const { return name; } + QString getDevice() const { return device; } + +private: + QString vid; // 相机VID + QString pid; // 相机PID + QString name; // 相机名称 + QString device; // 相机设备节点路径 +}; + /** * @brief videowidget 完成拍照,录像相关工作 * 连拍,延时拍照录像,闪光灯,文件保存等 @@ -266,6 +301,11 @@ class videowidget : public DWidget */ void showCamUsed(); + /** + * @brief getValidDeviceNum 获取有效设备数量 + * @return 有效设备数量 + */ + int getValidDeviceNum(); public slots: /** * @brief onTakePic 拍照事件响应 @@ -326,6 +366,11 @@ public slots: */ void onLockedScreen(bool bLocked); + /** + * @brief 更新有效相机设备列表 + */ + void updateValidDevices(); + private slots: /** * @brief ReceiveMajorImage 处理视频帧 mips、wayland下使用 @@ -433,6 +478,36 @@ private slots: * @return UOS_ 专业版 DEEPIN_ 社区版 CAMERA_ 其他 */ QString getSaveFilePrefix(); + + /** + * @brief formatDeviceId 格式化设备ID + * @param id 设备ID + * @return 格式化后的设备ID + */ + static QString formatDeviceId(uint id) { + // 格式化设备ID为4位十六进制数,不足4位用0填充 + return QString("%1").arg(id, 4, 16, QLatin1Char('0')); + } + + /** + * @brief 获取第一个有效的设备 + * @return 没找到时返回空字符串 + */ + QString getFirstValidDevice(); + + /** + * @brief 判断设备是否有效 + * @param device 设备识别路径 /dev/video* + * @return true 有效 false 无效 + */ + bool isDeviceValidByDevice(const QString &device); + + /** + * @brief 获取设备在有效设备列表中的索引 + * @param device 设备识别路径 /dev/video* + * @return 索引 无效时返回-1 + */ + int getValidDeviceIndexByDevice(const QString &device); public: MajorImageProcessingThread *m_imgPrcThread; AudioProcessingThread *m_audPrcThread; @@ -482,6 +557,8 @@ private slots: int m_exposure; bool m_isFlash = false; bool m_isLockedScreen = false; // 是否处于锁屏状态 + QVector m_validDevices; // 有效相机设备列表 + QReadWriteLock m_mutexValidDevices; // 有效相机设备列表读写锁 }; #endif // VIDEOWIDGET_H