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);