From 9771f11e7bee191027290570d2b08be043fb0ac2 Mon Sep 17 00:00:00 2001 From: fuleyi Date: Thu, 3 Sep 2026 10:53:03 +0800 Subject: [PATCH] feat: switch camera hotkey to daemon hardware privacy switch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Add CameraController to dde-shortcut-tool exposing camera toggle/on/off. It reads the real device state through org.deepin.dde.Daemon1.GetCameraPrivacy and switches it through SetCameraPrivacy, so the camera is disabled at the hardware level. 2. The hotkey only drives the hardware switch and shows the matching OSD; it never launches or closes the camera application. When the daemon reports no switchable camera, the hotkey stays silent. 3. Point the webcam shortcut config at dde-shortcut-tool camera toggle so the hotkey no longer needs the camera-switch helper script. Log: 摄像头热键改为仅通过系统接口硬件级开关摄像头,不关联相机应用 Influence: 1. Press the camera hotkey on a machine with a USB camera and confirm the camera toggles off/on with the OSD shown, and the built-in mic on the same device keeps working. 2. Press the camera hotkey on a machine without a switchable camera and confirm nothing happens (no application is launched). 3. Build dde-shortcut-tool and confirm camera toggle/on/off commands work without errors. feat: 摄像头热键改为仅通过系统接口硬件级开关摄像头 1. 在 dde-shortcut-tool 中新增 CameraController,提供 camera toggle/on/off,经 org.deepin.dde.Daemon1.GetCameraPrivacy 读取状态、 SetCameraPrivacy 开关,硬件级禁用摄像头。 2. 热键仅驱动硬件开关并显示 OSD,不启停相机应用;守护进程无可切换 摄像头时热键静默不动作。 3. 将 webcam 快捷键配置指向 dde-shortcut-tool camera toggle,热键不再 依赖 camera-switch 辅助脚本。 Log: 摄像头热键改为仅通过系统接口硬件级开关摄像头,不关联相机应用 Influence: 1. 有 USB 摄像头的机型按热键,确认开关且有 OSD,同设备内置麦克风 不受影响。 2. 无可切换摄像头的机型按热键,确认无动作(不启动相机应用)。 3. 构建 dde-shortcut-tool,确认 camera toggle/on/off 命令无报错。 PMS: BUG-375443 --- debian/control | 3 +- src/plugin-qt/shortcut/CMakeLists.txt | 1 - .../org.deepin.shortcut.json | 6 +- src/plugin-qt/shortcut/scripts/camera-switch | 11 - .../tools/dde-shortcut-tool/CMakeLists.txt | 2 + .../dde-shortcut-tool/cameracontroller.cpp | 192 ++++++++++++++++++ .../dde-shortcut-tool/cameracontroller.h | 54 +++++ .../shortcut/tools/dde-shortcut-tool/main.cpp | 2 + 8 files changed, 256 insertions(+), 15 deletions(-) delete mode 100644 src/plugin-qt/shortcut/scripts/camera-switch create mode 100644 src/plugin-qt/shortcut/tools/dde-shortcut-tool/cameracontroller.cpp create mode 100644 src/plugin-qt/shortcut/tools/dde-shortcut-tool/cameracontroller.h diff --git a/debian/control b/debian/control index 609e5f2e..4208a83f 100644 --- a/debian/control +++ b/debian/control @@ -47,7 +47,8 @@ Depends: deepin-service-manager (>> 1.0.21), dbus, deepin-power-control (>= 2.0.1), - deepin-security-loader + deepin-security-loader, + dde-daemon (>> 6.1.104) Recommends: iio-sensor-proxy Breaks: dde-daemon (<< 6.1.104.1) diff --git a/src/plugin-qt/shortcut/CMakeLists.txt b/src/plugin-qt/shortcut/CMakeLists.txt index 61934240..0b183aad 100644 --- a/src/plugin-qt/shortcut/CMakeLists.txt +++ b/src/plugin-qt/shortcut/CMakeLists.txt @@ -172,7 +172,6 @@ install(FILES configs/org.deepin.dde.keybinding.ini DESTINATION share/deepin/org.deepin.dde.keybinding) install(PROGRAMS - scripts/camera-switch scripts/toggle-grand-search DESTINATION libexec/dde-services/keybinding) diff --git a/src/plugin-qt/shortcut/configs/org.deepin.dde.keybinding.shortcut.webcam/org.deepin.shortcut.json b/src/plugin-qt/shortcut/configs/org.deepin.dde.keybinding.shortcut.webcam/org.deepin.shortcut.json index 4b88d8fa..6ebfaa65 100644 --- a/src/plugin-qt/shortcut/configs/org.deepin.dde.keybinding.shortcut.webcam/org.deepin.shortcut.json +++ b/src/plugin-qt/shortcut/configs/org.deepin.dde.keybinding.shortcut.webcam/org.deepin.shortcut.json @@ -46,9 +46,11 @@ }, "triggerValue": { "value": [ - "/usr/libexec/dde-services/keybinding/camera-switch" + "/usr/bin/dde-shortcut-tool", + "camera", + "toggle" ], - "serial": 0, + "serial": 1, "flags": [], "name": "triggerValue", "name[zh_CN]": "快捷键触发动作", diff --git a/src/plugin-qt/shortcut/scripts/camera-switch b/src/plugin-qt/shortcut/scripts/camera-switch deleted file mode 100644 index 75efb0ef..00000000 --- a/src/plugin-qt/shortcut/scripts/camera-switch +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -# SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. -# SPDX-License-Identifier: LGPL-3.0-or-later - -user_id=$(id -u) || exit 1 - -if /usr/bin/pgrep -u "$user_id" -x deepin-camera >/dev/null 2>&1; then - exec /usr/bin/pkill -u "$user_id" -x deepin-camera -fi - -exec /usr/bin/dde-am deepin-camera diff --git a/src/plugin-qt/shortcut/tools/dde-shortcut-tool/CMakeLists.txt b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/CMakeLists.txt index 8b996792..978429cb 100644 --- a/src/plugin-qt/shortcut/tools/dde-shortcut-tool/CMakeLists.txt +++ b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/CMakeLists.txt @@ -22,6 +22,8 @@ add_executable(dde-shortcut-tool basecontroller.h audiocontroller.cpp audiocontroller.h + cameracontroller.cpp + cameracontroller.h displaycontroller.cpp displaycontroller.h touchpadcontroller.cpp diff --git a/src/plugin-qt/shortcut/tools/dde-shortcut-tool/cameracontroller.cpp b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/cameracontroller.cpp new file mode 100644 index 00000000..e3a51125 --- /dev/null +++ b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/cameracontroller.cpp @@ -0,0 +1,192 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "cameracontroller.h" + +#include +#include +#include +#include + + +namespace { + +// dde-system-daemon owns the camera privacy switch: it applies the V4L2 +// privacy control when the hardware exposes one and otherwise unbinds the +// uvcvideo driver on the video interface (which keeps a built-in mic on the +// same device working). +constexpr const char *kDaemonService = "org.deepin.dde.Daemon1"; +constexpr const char *kDaemonPath = "/org/deepin/dde/Daemon1"; +constexpr const char *kDaemonInterface = "org.deepin.dde.Daemon1"; + + +} // namespace + +CameraController::CameraController(QObject *parent) + : BaseController(parent) + , m_daemonInterface(nullptr) +{ + m_daemonInterface = new QDBusInterface( + kDaemonService, + kDaemonPath, + kDaemonInterface, + QDBusConnection::systemBus(), + this + ); + + if (!m_daemonInterface->isValid()) { + qWarning() << "Failed to connect to Daemon1 service:" + << m_daemonInterface->lastError().message(); + } +} + +CameraController::~CameraController() +{ +} + +QStringList CameraController::commandActions() +{ + return QStringList{ + "toggle", + "on", + "off" + }; +} + +QMap CameraController::commandActionHelp() +{ + return { + {"toggle", "Toggle camera privacy (camera off/on)"}, + {"on", "Enable the camera (privacy off)"}, + {"off", "Disable the camera (privacy on)"} + }; +} + +QStringList CameraController::supportedActions() const +{ + return commandActions(); +} + +bool CameraController::execute(const QString &action, const QStringList &args) +{ + Q_UNUSED(args); + + if (action == "toggle") { + return toggle(); + } else if (action == "on") { + return setPrivacy(false); + } else if (action == "off") { + return setPrivacy(true); + } + + qWarning() << "Unknown camera action:" << action; + return false; +} + +QString CameraController::actionHelp(const QString &action) const +{ + return commandActionHelp().value(action); +} + +bool CameraController::toggle() +{ + bool known = false; + const bool privacy = currentPrivacy(&known); + if (!known) { + // No camera device to switch: the hotkey only drives hardware, so do + // nothing rather than launching a camera application. + return false; + } + return setPrivacy(!privacy); +} + +bool CameraController::setPrivacy(bool privacy) +{ + bool applied = false; + if (!requestPrivacy(privacy, &applied)) { + return false; + } + + if (!applied) { + // No camera the daemon can switch at the hardware level: stay silent + // and do not fall back to the camera application. + return false; + } + + showOSD(privacy ? "CameraOff" : "CameraOn"); + return true; +} + + +bool CameraController::currentPrivacy(bool *known) const +{ + if (known) { + *known = false; + } + + if (!m_daemonInterface || !m_daemonInterface->isValid()) { + qWarning() << "Daemon1 interface not available"; + return false; + } + + QDBusMessage reply = m_daemonInterface->call("GetCameraPrivacy"); + if (reply.type() != QDBusMessage::ReplyMessage) { + qWarning() << "GetCameraPrivacy failed:" << reply.errorMessage(); + return false; + } + + const QList values = reply.arguments(); + if (values.size() < 2) { + qWarning() << "GetCameraPrivacy returned unexpected arguments:" << values.size(); + return false; + } + + const bool privacy = values.at(0).toBool(); + const bool deviceKnown = values.at(1).toBool(); + if (known) { + *known = deviceKnown; + } + return privacy; +} + +bool CameraController::requestPrivacy(bool privacy, bool *applied) +{ + if (applied) { + *applied = false; + } + + if (!m_daemonInterface || !m_daemonInterface->isValid()) { + qWarning() << "Daemon1 interface not available"; + return false; + } + + QDBusReply reply = m_daemonInterface->call("SetCameraPrivacy", privacy); + if (!reply.isValid()) { + qWarning() << "SetCameraPrivacy failed:" << reply.error().message(); + return false; + } + + if (applied) { + *applied = reply.value(); + } + qDebug() << "SetCameraPrivacy privacy:" << privacy << "applied:" << reply.value(); + return true; +} + + +void CameraController::showOSD(const QString &signal) +{ + QDBusInterface osdInterface( + "org.deepin.dde.Osd1", + "/org/deepin/dde/shell/osd", + "org.deepin.dde.shell.osd", + QDBusConnection::sessionBus() + ); + + if (osdInterface.isValid()) { + osdInterface.call("ShowOSD", signal); + } else { + qWarning() << "Failed to connect to OSD interface"; + } +} diff --git a/src/plugin-qt/shortcut/tools/dde-shortcut-tool/cameracontroller.h b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/cameracontroller.h new file mode 100644 index 00000000..dd9991a0 --- /dev/null +++ b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/cameracontroller.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef CAMERACONTROLLER_H +#define CAMERACONTROLLER_H + +#include "basecontroller.h" + +#include + +class QDBusInterface; + +/** + * @brief Camera hotkey controller + * + * Toggles camera privacy through the system daemon + * (org.deepin.dde.Daemon1.SetCameraPrivacy). The hotkey only drives the + * hardware switch; it shows the matching OSD and never launches or closes a + * camera application. + */ +class CameraController : public BaseController +{ + Q_OBJECT + +public: + explicit CameraController(QObject *parent = nullptr); + ~CameraController() override; + + static QString commandName() { return "camera"; } + static QStringList commandActions(); + static QMap commandActionHelp(); + + // BaseController interface + QString name() const override { return commandName(); } + QStringList supportedActions() const override; + bool execute(const QString &action, const QStringList &args = QStringList()) override; + QString actionHelp(const QString &action) const override; + +private: + bool toggle(); + bool setPrivacy(bool privacy); + // Returns the daemon's privacy state; known is false when no camera is + // available to report on. + bool currentPrivacy(bool *known) const; + // Asks the daemon to switch the camera; applied reports whether hardware + // switching happened. + bool requestPrivacy(bool privacy, bool *applied); + void showOSD(const QString &signal); + + QDBusInterface *m_daemonInterface; +}; + +#endif // CAMERACONTROLLER_H diff --git a/src/plugin-qt/shortcut/tools/dde-shortcut-tool/main.cpp b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/main.cpp index bc3d5f38..aebcd303 100644 --- a/src/plugin-qt/shortcut/tools/dde-shortcut-tool/main.cpp +++ b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/main.cpp @@ -6,6 +6,7 @@ #include "commandparser.h" #include "audiocontroller.h" +#include "cameracontroller.h" #include "displaycontroller.h" #include "touchpadcontroller.h" #include "powercontroller.h" @@ -50,6 +51,7 @@ int main(int argc, char *argv[]) registerControllerFactory(parser); registerControllerFactory(parser); registerControllerFactory(parser); + registerControllerFactory(parser); // Execute command and return result return parser.run(argc, argv);