diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..de4cebb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: GPL-3.0-or-later + +cmake_minimum_required(VERSION 3.16) + +project(dde-graphics-driver-applet VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +find_package(Qt6 REQUIRED COMPONENTS Widgets DBus Quick) +find_package(Dtk6 REQUIRED COMPONENTS Widget Core) +find_package(dde-shell REQUIRED) + +add_library(dde-graphics-driver SHARED + graphicsdriverapplet.h + graphicsdriverapplet.cpp + gpuindicator.h + gpuindicator.cpp +) + +target_include_directories(dde-graphics-driver PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(dde-graphics-driver PRIVATE + dde-shell-frame + Qt6::Widgets + Qt6::DBus + Qt6::Quick + Dtk6::Widget + Dtk6::Core +) + +ds_install_package(PACKAGE org.deepin.ds.graphics-driver TARGET dde-graphics-driver) diff --git a/README.md b/README.md index b405902..388fea7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,107 @@ # 显卡切换任务栏插件 +Deepin显卡驱动管理任务栏插件,集成到DDE Shell任务栏中,提供显卡设备检测、驱动安装和切换功能。 + +## 功能特性 + +- 自动检测系统显卡设备 +- 显示当前安装的驱动版本 +- 列出可用的驱动版本 +- 一键切换显卡驱动 +- 实时显示安装进度 +- 安装完成后提示重启 + +## 依赖项 + +- dde-shell >= 2.0 +- Qt6 >= 6.0 +- Dtk6 >= 6.0 +- deepin-graphics-driver-manager(运行时依赖) + +## 构建 + +```bash +# 安装构建依赖 +sudo apt build-dep . + +# 构建项目 +cmake -Bbuild +cmake --build build + +# 安装插件 +cmake --install build +``` + +## 项目结构 + +``` +├── package/ +│ ├── metadata.json # 插件元数据 +│ └── driverview.qml # QML界面 +├── graphicsdriverapplet.h # 主插件类头文件 +├── graphicsdriverapplet.cpp # 主插件类实现 +├── gpuindicator.h # GPU指示器类头文件 +├── gpuindicator.cpp # GPU指示器类实现 +├── CMakeLists.txt # 构建配置 +└── README.md # 项目说明 +``` + +## 技术实现 + +### 架构设计 + +``` +┌─────────────────────────────────────────────────────────┐ +│ DDE Shell Taskbar │ +├─────────────────────────────────────────────────────────┤ +│ dde-graphics-driver-applet │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ GPU检测模块 │ │ 驱动管理 │ │ UI界面 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +├─────────────────────────────────────────────────────────┤ +│ D-Bus Interface │ +│ com.deepin.daemon.GraphicsDriver │ +├─────────────────────────────────────────────────────────┤ +│ deepin-graphics-driver-manager │ +│ (现有的驱动管理服务,无需修改) │ +└─────────────────────────────────────────────────────────┘ +``` + +### D-Bus接口 + +插件通过D-Bus与deepin-graphics-driver-manager服务通信: + +- `GetDevice()` - 获取显卡设备信息 +- `GetCurrDriverName()` - 获取当前驱动名称 +- `GetNewDriverName()` - 获取推荐驱动名称 +- `PrepareInstall(name, language)` - 准备安装驱动 +- `TestInstall()` - 测试安装 +- `RealInstall()` - 实际安装 +- `CancelInstall()` - 取消安装 + +### 信号 + +- `ReportProgress(ratio)` - 安装进度报告 +- `Cancel()` - 安装取消通知 + +## 使用说明 + +1. 插件安装后会自动加载到任务栏 +2. 点击任务栏图标打开驱动管理界面 +3. 查看当前显卡设备和驱动信息 +4. 选择要切换的驱动版本 +5. 确认后开始安装过程 +6. 安装完成后选择重启系统 + +## 许可证 + +GPL-3.0-or-later + +## 贡献 + +欢迎提交Issue和Pull Request。 + +## 相关项目 + +- [dde-shell](https://github.com/linuxdeepin/dde-shell) - Deepin Shell插件系统 +- [deepin-graphics-driver-manager](https://github.com/linuxdeepin/deepin-graphics-driver-manager) - 显卡驱动管理器 diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md new file mode 100644 index 0000000..406c3bf --- /dev/null +++ b/docs/DEVELOPMENT.md @@ -0,0 +1,254 @@ +# 开发指南 + +## 环境准备 + +### 1. 克隆dde-shell仓库 + +```bash +# 克隆dde-shell到本地 +git clone https://github.com/linuxdeepin/dde-shell.git ~/dde-shell + +# 或者作为子模块引入 +git submodule add https://github.com/linuxdeepin/dde-shell.git third_party/dde-shell +``` + +### 2. 安装构建依赖 + +```bash +# Debian/Deepin系统 +sudo apt build-dep . + +# 或手动安装依赖 +sudo apt install -y \ + cmake \ + g++ \ + qt6-base-dev \ + qt6-tools-dev \ + libdtk6-core-dev \ + libdtk6-widget-dev +``` + +### 3. 配置CMake + +```bash +# 创建构建目录 +mkdir build && cd build + +# 配置,指定dde-shell路径 +cmake .. -Ddde-shell_DIR=~/dde-shell/build +``` + +## 开发步骤 + +### 步骤1: 获取D-Bus接口定义 + +查看deepin-graphics-driver-manager的D-Bus接口: + +```bash +# 克隆仓库 +git clone https://github.com/linuxdeepin/deepin-graphics-driver-manager.git + +# 查看接口定义 +cat deepin-graphics-driver-manager/com.deepin.daemon.GraphicsDriver.xml +``` + +接口方法: +- `GetDevice()` - 获取显卡设备信息(返回JSON字符串) +- `GetCurrDriverName()` - 获取当前驱动名称 +- `GetNewDriverName()` - 获取推荐驱动名称 +- `PrepareInstall(name, language)` - 准备安装驱动 +- `TestInstall()` - 测试安装 +- `RealInstall()` - 实际安装 +- `CancelInstall()` - 取消安装 + +信号: +- `ReportProgress(ratio)` - 安装进度报告(ratio为百分比字符串) +- `Cancel()` - 安装取消通知 + +### 步骤2: 适配D-Bus接口 + +编辑`graphicsdriverapplet.cpp`,根据实际接口调整: + +```cpp +// 检查D-Bus服务是否可用 +QDBusConnection bus = QDBusConnection::sessionBus(); +if (!bus.isConnected()) { + qWarning() << "Cannot connect to D-Bus session bus"; + return false; +} + +// 检查服务是否存在 +QDBusInterface interface( + "com.deepin.daemon.GraphicsDriver", + "/com/deepin/daemon/GraphicsDriver", + "com.deepin.daemon.GraphicsDriver", + bus +); + +if (!interface.isValid()) { + qWarning() << "GraphicsDriver service is not available"; + return false; +} +``` + +### 步骤3: 完善GPU设备检测 + +根据`GetDevice()`返回的JSON格式,调整解析逻辑: + +```json +{ + "vendor": "NVIDIA", + "model": "GeForce GTX 1660 Ti", + "driverVersion": "470.103.01", + "driverStatus": "installed", + "drivers": [ + { + "name": "nvidia-driver-470", + "version": "470.103.01", + "description": "NVIDIA Driver 470 (推荐)" + }, + { + "name": "nvidia-driver-460", + "version": "460.103.01", + "description": "NVIDIA Driver 460" + } + ] +} +``` + +### 步骤4: 添加图标资源 + +创建`icons/`目录并添加图标: + +```bash +mkdir icons + +# 添加显卡图标(可以从deepin图标库获取) +wget https://github.com/linuxdeepin/deepin-icon-theme/raw/master/icons/deepin/scalable/apps/graphics-card.svg -P icons/ +``` + +在QML中引用: + +```qml +Image { + source: "qrc:/icons/graphics-card.svg" +} +``` + +### 步骤5: 测试插件 + +```bash +# 构建 +cmake -Bbuild +cmake --build build + +# 安装到本地测试 +cmake --install build --prefix ~/.local + +# 启动dde-shell测试模式 +dde-shell -t +``` + +### 步骤6: 调试技巧 + +```bash +# 查看D-Bus服务 +qdbus | grep GraphicsDriver + +# 监听D-Bus信号 +dbus-monitor "interface='com.deepin.daemon.GraphicsDriver'" + +# 查看插件日志 +QT_LOGGING_RULES="dde.shell*=true" dde-shell -t +``` + +## 常见问题 + +### 1. 插件未加载 + +检查`metadata.json`格式是否正确: + +```json +{ + "Plugin": { + "Version": "1.0", + "Id": "org.deepin.ds.graphics-driver", + "Url": "driverview.qml", + "Category": "DDE" + } +} +``` + +### 2. D-Bus连接失败 + +确认deepin-graphics-driver-manager服务已启动: + +```bash +# 检查服务状态 +systemctl status deepin-graphics-driver-manager + +# 手动启动服务 +sudo deepin-graphics-driver-manager -d +``` + +### 3. 权限问题 + +安装驱动需要root权限,确保PolicyKit配置正确: + +```bash +# 检查PolicyKit规则 +pkaction --action-id com.deepin.daemon.GraphicsDriver --verbose +``` + +## 代码规范 + +### 命名规范 + +- 类名:`PascalCase`(如`GraphicsDriverApplet`) +- 方法名:`camelCase`(如`refreshDeviceInfo`) +- 常量:`UPPER_SNAKE_CASE`(如`DBUS_SERVICE`) +- 成员变量:`m_`前缀(如`m_dbusInterface`) + +### 文件组织 + +- 头文件:类声明、信号、槽、Q_PROPERTY +- 实现文件:构造函数、方法实现、D_APPLET_CLASS宏 +- QML文件:界面布局、交互逻辑 + +## 发布流程 + +### 1. 版本号更新 + +更新`CMakeLists.txt`中的版本号: + +```cmake +project(dde-graphics-driver-applet VERSION 1.0.0 LANGUAGES CXX) +``` + +### 2. 构建Debian包 + +```bash +dpkg-buildpackage -uc -us -nc -b +``` + +### 3. 测试安装 + +```bash +sudo dpkg -i ../dde-graphics-driver-applet_1.0.0_amd64.deb +``` + +### 4. 提交到官方仓库 + +```bash +# Fork仓库后 +git remote add upstream https://github.com/linuxdeepin/dde-shell.git +git push origin master +# 创建Pull Request +``` + +## 相关资源 + +- [dde-shell插件开发文档](https://github.com/linuxdeepin/dde-shell/tree/master/docs/plugin) +- [deepin-graphics-driver-manager](https://github.com/linuxdeepin/deepin-graphics-driver-manager) +- [DTK开发文档](https://linuxdeepin.github.io/dtk/) +- [Qt D-Bus文档](https://doc.qt.io/qt-6/qtdbus-index.html) diff --git a/gpuindicator.cpp b/gpuindicator.cpp new file mode 100644 index 0000000..e9a21ac --- /dev/null +++ b/gpuindicator.cpp @@ -0,0 +1,128 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "gpuindicator.h" + +#include +#include +#include +#include + +GpuIndicator::GpuIndicator(QObject *parent) + : QObject(parent) +{ +} + +GpuIndicator::~GpuIndicator() +{ +} + +QString GpuIndicator::vendor() const +{ + return m_vendor; +} + +QString GpuIndicator::model() const +{ + return m_model; +} + +QString GpuIndicator::driverVersion() const +{ + return m_driverVersion; +} + +QString GpuIndicator::driverStatus() const +{ + return m_driverStatus; +} + +QString GpuIndicator::icon() const +{ + return determineIcon(); +} + +bool GpuIndicator::isNvidia() const +{ + return m_vendor.toLower().contains("nvidia"); +} + +bool GpuIndicator::isAmd() const +{ + return m_vendor.toLower().contains("amd") || m_vendor.toLower().contains("ati"); +} + +bool GpuIndicator::isIntel() const +{ + return m_vendor.toLower().contains("intel"); +} + +void GpuIndicator::updateFromJson(const QJsonObject &json) +{ + parseDeviceInfo(json); +} + +void GpuIndicator::updateFromDeviceInfo(const QString &jsonStr) +{ + QJsonDocument doc = QJsonDocument::fromJson(jsonStr.toUtf8()); + if (doc.isNull() || !doc.isObject()) { + qWarning() << "Invalid JSON:" << jsonStr; + return; + } + + parseDeviceInfo(doc.object()); +} + +void GpuIndicator::parseDeviceInfo(const QJsonObject &obj) +{ + bool changed = false; + + if (obj.contains("vendor")) { + QString vendor = obj["vendor"].toString(); + if (m_vendor != vendor) { + m_vendor = vendor; + changed = true; + } + } + + if (obj.contains("model")) { + QString model = obj["model"].toString(); + if (m_model != model) { + m_model = model; + changed = true; + } + } + + if (obj.contains("driverVersion")) { + QString version = obj["driverVersion"].toString(); + if (m_driverVersion != version) { + m_driverVersion = version; + changed = true; + } + } + + if (obj.contains("driverStatus")) { + QString status = obj["driverStatus"].toString(); + if (m_driverStatus != status) { + m_driverStatus = status; + changed = true; + } + } + + if (changed) { + emit infoChanged(); + } +} + +QString GpuIndicator::determineIcon() const +{ + if (isNvidia()) { + return "nvidia"; + } else if (isAmd()) { + return "amd"; + } else if (isIntel()) { + return "intel"; + } + return "graphics-card"; +} diff --git a/gpuindicator.h b/gpuindicator.h new file mode 100644 index 0000000..178d76d --- /dev/null +++ b/gpuindicator.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include + +class GpuIndicator : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString vendor READ vendor NOTIFY infoChanged) + Q_PROPERTY(QString model READ model NOTIFY infoChanged) + Q_PROPERTY(QString driverVersion READ driverVersion NOTIFY infoChanged) + Q_PROPERTY(QString driverStatus READ driverStatus NOTIFY infoChanged) + Q_PROPERTY(QString icon READ icon NOTIFY infoChanged) + Q_PROPERTY(bool isNvidia READ isNvidia NOTIFY infoChanged) + Q_PROPERTY(bool isAmd READ isAmd NOTIFY infoChanged) + Q_PROPERTY(bool isIntel READ isIntel NOTIFY infoChanged) + +public: + explicit GpuIndicator(QObject *parent = nullptr); + ~GpuIndicator(); + + QString vendor() const; + QString model() const; + QString driverVersion() const; + QString driverStatus() const; + QString icon() const; + bool isNvidia() const; + bool isAmd() const; + bool isIntel() const; + + Q_INVOKABLE void updateFromJson(const QJsonObject &json); + Q_INVOKABLE void updateFromDeviceInfo(const QString &jsonStr); + +signals: + void infoChanged(); + +private: + void parseDeviceInfo(const QJsonObject &obj); + QString determineIcon() const; + + QString m_vendor; + QString m_model; + QString m_driverVersion; + QString m_driverStatus; +}; diff --git a/graphicsdriverapplet.cpp b/graphicsdriverapplet.cpp new file mode 100644 index 0000000..ea0bc3a --- /dev/null +++ b/graphicsdriverapplet.cpp @@ -0,0 +1,330 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "graphicsdriverapplet.h" +#include "pluginfactory.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +DCORE_USE_NAMESPACE + +DS_BEGIN_NAMESPACE + +static const QString DBUS_SERVICE = "com.deepin.daemon.GraphicsDriver"; +static const QString DBUS_PATH = "/com/deepin/daemon/GraphicsDriver"; +static const QString DBUS_INTERFACE = "com.deepin.daemon.GraphicsDriver"; + +GraphicsDriverApplet::GraphicsDriverApplet(QObject *parent) + : DApplet(parent) + , m_dbusInterface(nullptr) + , m_progressTimer(new QTimer(this)) + , m_installProgress(0) + , m_isInstalling(false) + , m_isTestSuccess(false) +{ +} + +GraphicsDriverApplet::~GraphicsDriverApplet() +{ + if (m_dbusInterface) { + delete m_dbusInterface; + } +} + +bool GraphicsDriverApplet::load() +{ + initDBusConnection(); + + connect(m_progressTimer, &QTimer::timeout, this, &GraphicsDriverApplet::onPollProgress); + + return DApplet::load(); +} + +bool GraphicsDriverApplet::init() +{ + refreshDeviceInfo(); + + return DApplet::init(); +} + +QString GraphicsDriverApplet::currentDriver() const +{ + return m_currentDriver; +} + +QString GraphicsDriverApplet::newDriver() const +{ + return m_newDriver; +} + +QString GraphicsDriverApplet::deviceInfo() const +{ + return m_deviceInfo; +} + +int GraphicsDriverApplet::installProgress() const +{ + return m_installProgress; +} + +bool GraphicsDriverApplet::isInstalling() const +{ + return m_isInstalling; +} + +bool GraphicsDriverApplet::isTestSuccess() const +{ + return m_isTestSuccess; +} + +QJsonArray GraphicsDriverApplet::availableDrivers() const +{ + return m_availableDrivers; +} + +void GraphicsDriverApplet::refreshDeviceInfo() +{ + if (!m_dbusInterface || !m_dbusInterface->isValid()) { + qWarning() << "DBus interface is not valid"; + return; + } + + QDBusPendingCall call = m_dbusInterface->asyncCall("GetDevice"); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, &GraphicsDriverApplet::onDBusReply); + + QDBusPendingCall currCall = m_dbusInterface->asyncCall("GetCurrDriverName"); + QDBusPendingCallWatcher *currWatcher = new QDBusPendingCallWatcher(currCall, this); + connect(currWatcher, &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *call) { + QDBusPendingReply reply = *call; + if (reply.isError()) { + qWarning() << "Failed to get current driver:" << reply.error().message(); + } else { + m_currentDriver = reply.value(); + emit currentDriverChanged(); + } + call->deleteLater(); + }); + + QDBusPendingCall newCall = m_dbusInterface->asyncCall("GetNewDriverName"); + QDBusPendingCallWatcher *newWatcher = new QDBusPendingCallWatcher(newCall, this); + connect(newWatcher, &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *call) { + QDBusPendingReply reply = *call; + if (reply.isError()) { + qWarning() << "Failed to get new driver:" << reply.error().message(); + } else { + m_newDriver = reply.value(); + emit newDriverChanged(); + } + call->deleteLater(); + }); +} + +void GraphicsDriverApplet::prepareInstall(const QString &driverName) +{ + if (!m_dbusInterface || !m_dbusInterface->isValid()) { + qWarning() << "DBus interface is not valid"; + emit installFailed("DBus interface is not valid"); + return; + } + + m_pendingInstallDriver = driverName; + + QLocale locale; + QString language = locale.name(); + + QDBusPendingCall call = m_dbusInterface->asyncCall("PrepareInstall", driverName, language); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + connect(watcher, &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *call) { + QDBusPendingReply<> reply = *call; + if (reply.isError()) { + qWarning() << "Failed to prepare install:" << reply.error().message(); + emit installFailed(reply.error().message()); + } else { + qDebug() << "Prepare install succeeded"; + } + call->deleteLater(); + }); +} + +void GraphicsDriverApplet::testInstall() +{ + if (!m_dbusInterface || !m_dbusInterface->isValid()) { + qWarning() << "DBus interface is not valid"; + emit installFailed("DBus interface is not valid"); + return; + } + + setInstalling(true); + + QDBusPendingCall call = m_dbusInterface->asyncCall("TestInstall"); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, &GraphicsDriverApplet::onDBusReply); +} + +void GraphicsDriverApplet::realInstall() +{ + if (!m_dbusInterface || !m_dbusInterface->isValid()) { + qWarning() << "DBus interface is not valid"; + emit installFailed("DBus interface is not valid"); + return; + } + + QDBusPendingCall call = m_dbusInterface->asyncCall("RealInstall"); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + connect(watcher, &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *call) { + QDBusPendingReply<> reply = *call; + if (reply.isError()) { + qWarning() << "Failed to real install:" << reply.error().message(); + emit installFailed(reply.error().message()); + setInstalling(false); + } else { + qDebug() << "Real install succeeded"; + emit installSuccess(); + emit requestReboot(); + } + call->deleteLater(); + }); +} + +void GraphicsDriverApplet::cancelInstall() +{ + if (!m_dbusInterface || !m_dbusInterface->isValid()) { + return; + } + + m_dbusInterface->asyncCall("CancelInstall"); + setInstalling(false); +} + +void GraphicsDriverApplet::onDBusReply(QDBusPendingCallWatcher *call) +{ + QDBusPendingReply reply = *call; + if (reply.isError()) { + qWarning() << "DBus call failed:" << reply.error().message(); + } else { + QString result = reply.value(); + qDebug() << "DBus call result:" << result; + parseDeviceInfo(result); + } + call->deleteLater(); +} + +void GraphicsDriverApplet::onReportProgress(const QString &ratio) +{ + bool ok; + int progress = ratio.toInt(&ok); + if (ok) { + m_installProgress = progress; + emit installProgressChanged(); + } +} + +void GraphicsDriverApplet::onCancel() +{ + setInstalling(false); + m_installProgress = 0; + emit installProgressChanged(); +} + +void GraphicsDriverApplet::onPollProgress() +{ + if (!m_dbusInterface || !m_dbusInterface->isValid()) { + return; + } + + QDBusPendingCall call = m_dbusInterface->asyncCall("GetCurrDriverName"); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + connect(watcher, &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *call) { + QDBusPendingReply reply = *call; + if (!reply.isError()) { + QString newDriver = reply.value(); + if (newDriver != m_currentDriver) { + m_newDriver = newDriver; + emit newDriverChanged(); + } + } + call->deleteLater(); + }); +} + +void GraphicsDriverApplet::initDBusConnection() +{ + QDBusConnection bus = QDBusConnection::sessionBus(); + + if (!bus.isConnected()) { + qWarning() << "Cannot connect to D-Bus session bus"; + return; + } + + m_dbusInterface = new QDBusInterface( + DBUS_SERVICE, + DBUS_PATH, + DBUS_INTERFACE, + bus, + this + ); + + if (!m_dbusInterface->isValid()) { + qWarning() << "DBus interface is not valid:" << m_dbusInterface->lastError().message(); + return; + } + + connect(m_dbusInterface, SIGNAL(ReportProgress(QString)), + this, SLOT(onReportProgress(QString))); + connect(m_dbusInterface, SIGNAL(Cancel()), + this, SLOT(onCancel())); +} + +void GraphicsDriverApplet::updateDeviceInfo() +{ + refreshDeviceInfo(); +} + +void GraphicsDriverApplet::parseDeviceInfo(const QString &jsonStr) +{ + QJsonDocument doc = QJsonDocument::fromJson(jsonStr.toUtf8()); + if (doc.isNull() || !doc.isObject()) { + qWarning() << "Invalid JSON:" << jsonStr; + return; + } + + QJsonObject obj = doc.object(); + m_deviceInfo = jsonStr; + emit deviceInfoChanged(); + + if (obj.contains("drivers")) { + m_availableDrivers = obj["drivers"].toArray(); + emit availableDriversChanged(); + } +} + +void GraphicsDriverApplet::setInstalling(bool installing) +{ + if (m_isInstalling != installing) { + m_isInstalling = installing; + emit isInstallingChanged(); + + if (installing) { + m_progressTimer->start(1000); + } else { + m_progressTimer->stop(); + m_installProgress = 0; + emit installProgressChanged(); + } + } +} + +D_APPLET_CLASS(GraphicsDriverApplet) + +DS_END_NAMESPACE + +#include "graphicsdriverapplet.moc" diff --git a/graphicsdriverapplet.h b/graphicsdriverapplet.h new file mode 100644 index 0000000..0e84eba --- /dev/null +++ b/graphicsdriverapplet.h @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "applet.h" + +#include +#include +#include +#include +#include + +DS_BEGIN_NAMESPACE + +class GraphicsDriverApplet : public DApplet +{ + Q_OBJECT + + Q_PROPERTY(QString currentDriver READ currentDriver NOTIFY currentDriverChanged) + Q_PROPERTY(QString newDriver READ newDriver NOTIFY newDriverChanged) + Q_PROPERTY(QString deviceInfo READ deviceInfo NOTIFY deviceInfoChanged) + Q_PROPERTY(int installProgress READ installProgress NOTIFY installProgressChanged) + Q_PROPERTY(bool isInstalling READ isInstalling NOTIFY isInstallingChanged) + Q_PROPERTY(bool isTestSuccess READ isTestSuccess NOTIFY isTestSuccessChanged) + Q_PROPERTY(QJsonArray availableDrivers READ availableDrivers NOTIFY availableDriversChanged) + +public: + explicit GraphicsDriverApplet(QObject *parent = nullptr); + ~GraphicsDriverApplet(); + + virtual bool load() override; + virtual bool init() override; + + QString currentDriver() const; + QString newDriver() const; + QString deviceInfo() const; + int installProgress() const; + bool isInstalling() const; + bool isTestSuccess() const; + QJsonArray availableDrivers() const; + + Q_INVOKABLE void refreshDeviceInfo(); + Q_INVOKABLE void prepareInstall(const QString &driverName); + Q_INVOKABLE void testInstall(); + Q_INVOKABLE void realInstall(); + Q_INVOKABLE void cancelInstall(); + +signals: + void currentDriverChanged(); + void newDriverChanged(); + void deviceInfoChanged(); + void installProgressChanged(); + void isInstallingChanged(); + void isTestSuccessChanged(); + void availableDriversChanged(); + void installSuccess(); + void installFailed(const QString &error); + void requestReboot(); + +private slots: + void onDBusReply(QDBusPendingCallWatcher *call); + void onReportProgress(const QString &ratio); + void onCancel(); + void onPollProgress(); + +private: + void initDBusConnection(); + void updateDeviceInfo(); + void parseDeviceInfo(const QString &jsonStr); + void setInstalling(bool installing); + + QDBusInterface *m_dbusInterface; + QTimer *m_progressTimer; + + QString m_currentDriver; + QString m_newDriver; + QString m_deviceInfo; + int m_installProgress; + bool m_isInstalling; + bool m_isTestSuccess; + QJsonArray m_availableDrivers; + QString m_pendingInstallDriver; +}; + +DS_END_NAMESPACE diff --git a/package/driverview.qml b/package/driverview.qml new file mode 100644 index 0000000..9d3a6f9 --- /dev/null +++ b/package/driverview.qml @@ -0,0 +1,387 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.11 +import QtQuick.Controls 2.4 +import QtQuick.Layouts 1.11 +import org.deepin.ds 1.0 + +AppletItem { + id: root + objectName: "graphics driver applet" + implicitWidth: 320 + implicitHeight: 400 + + property var applet: Applet + + Connections { + target: root.applet + function onInstallSuccess() { + successDialog.open() + } + function onInstallFailed(error) { + errorDialog.text = error + errorDialog.open() + } + function onRequestReboot() { + rebootDialog.open() + } + } + + Rectangle { + anchors.fill: parent + color: palette.window + radius: 8 + + ColumnLayout { + anchors.fill: parent + anchors.margins: 16 + spacing: 12 + + RowLayout { + Layout.fillWidth: true + + Image { + source: "qrc:/icons/graphics-card.svg" + Layout.preferredWidth: 24 + Layout.preferredHeight: 24 + } + + Text { + text: qsTr("Graphics Driver Manager") + font.pixelSize: 14 + font.bold: true + color: palette.text + Layout.fillWidth: true + } + + Button { + text: qsTr("Refresh") + flat: true + onClicked: root.applet.refreshDeviceInfo() + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: palette.separator + } + + GroupBox { + Layout.fillWidth: true + title: qsTr("Device Information") + + ColumnLayout { + anchors.fill: parent + spacing: 8 + + RowLayout { + Text { + text: qsTr("Vendor:") + font.pixelSize: 12 + color: palette.placeholderText + Layout.preferredWidth: 80 + } + Text { + text: root.applet.deviceInfo ? JSON.parse(root.applet.deviceInfo).vendor || "-" : "-" + font.pixelSize: 12 + color: palette.text + Layout.fillWidth: true + } + } + + RowLayout { + Text { + text: qsTr("Model:") + font.pixelSize: 12 + color: palette.placeholderText + Layout.preferredWidth: 80 + } + Text { + text: root.applet.deviceInfo ? JSON.parse(root.applet.deviceInfo).model || "-" : "-" + font.pixelSize: 12 + color: palette.text + Layout.fillWidth: true + } + } + + RowLayout { + Text { + text: qsTr("Current Driver:") + font.pixelSize: 12 + color: palette.placeholderText + Layout.preferredWidth: 80 + } + Text { + text: root.applet.currentDriver || "-" + font.pixelSize: 12 + color: palette.text + Layout.fillWidth: true + } + } + } + } + + GroupBox { + Layout.fillWidth: true + Layout.fillHeight: true + title: qsTr("Available Drivers") + + ListView { + anchors.fill: parent + model: root.applet.availableDrivers + clip: true + + delegate: ItemDelegate { + width: parent.width + height: 60 + + contentItem: ColumnLayout { + spacing: 4 + + RowLayout { + Text { + text: modelData.name || qsTr("Unknown Driver") + font.pixelSize: 13 + font.bold: true + color: palette.text + Layout.fillWidth: true + } + + Badge { + visible: modelData.name === root.applet.currentDriver + text: qsTr("Current") + color: "green" + } + + Badge { + visible: modelData.name === root.applet.newDriver && modelData.name !== root.applet.currentDriver + text: qsTr("Recommended") + color: "blue" + } + } + + Text { + text: modelData.description || "" + font.pixelSize: 11 + color: palette.placeholderText + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 + } + + Button { + visible: modelData.name !== root.applet.currentDriver && !root.applet.isInstalling + text: qsTr("Switch") + flat: true + Layout.alignment: Qt.AlignRight + onClicked: { + root.applet.prepareInstall(modelData.name) + installConfirmDialog.driverName = modelData.name + installConfirmDialog.open() + } + } + } + } + } + } + + ColumnLayout { + Layout.fillWidth: true + visible: root.applet.isInstalling + spacing: 8 + + RowLayout { + Text { + text: qsTr("Installing...") + font.pixelSize: 12 + color: palette.text + Layout.fillWidth: true + } + + Text { + text: root.applet.installProgress + "%" + font.pixelSize: 12 + color: palette.text + } + } + + ProgressBar { + Layout.fillWidth: true + value: root.applet.installProgress / 100 + } + + Button { + text: qsTr("Cancel") + flat: true + Layout.alignment: Qt.AlignRight + onClicked: root.applet.cancelInstall() + } + } + } + } + + Dialog { + id: installConfirmDialog + property string driverName + title: qsTr("Confirm Driver Switch") + modal: true + anchors.centerIn: parent + width: 300 + + contentItem: ColumnLayout { + spacing: 16 + + Text { + text: qsTr("Are you sure you want to switch to driver: %1?").arg(installConfirmDialog.driverName) + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + RowLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignRight + spacing: 8 + + Button { + text: qsTr("Cancel") + flat: true + onClicked: installConfirmDialog.close() + } + + Button { + text: qsTr("Confirm") + onClicked: { + root.applet.testInstall() + installConfirmDialog.close() + } + } + } + } + } + + Dialog { + id: successDialog + title: qsTr("Installation Success") + modal: true + anchors.centerIn: parent + width: 300 + + contentItem: ColumnLayout { + spacing: 16 + + Text { + text: qsTr("Driver has been installed successfully. Do you want to reboot now?") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + RowLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignRight + spacing: 8 + + Button { + text: qsTr("Later") + flat: true + onClicked: successDialog.close() + } + + Button { + text: qsTr("Reboot Now") + onClicked: { + DUtil.reboot() + successDialog.close() + } + } + } + } + } + + Dialog { + id: errorDialog + property string text + title: qsTr("Installation Failed") + modal: true + anchors.centerIn: parent + width: 300 + + contentItem: ColumnLayout { + spacing: 16 + + Text { + text: errorDialog.text + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + RowLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignRight + + Button { + text: qsTr("OK") + onClicked: errorDialog.close() + } + } + } + } + + Dialog { + id: rebootDialog + title: qsTr("Reboot Required") + modal: true + anchors.centerIn: parent + width: 300 + + contentItem: ColumnLayout { + spacing: 16 + + Text { + text: qsTr("A reboot is required to apply the driver changes. Do you want to reboot now?") + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + RowLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignRight + spacing: 8 + + Button { + text: qsTr("Later") + flat: true + onClicked: rebootDialog.close() + } + + Button { + text: qsTr("Reboot Now") + onClicked: { + DUtil.reboot() + rebootDialog.close() + } + } + } + } + } + + component Badge: Rectangle { + property string text + property color color: "blue" + + width: badgeText.implicitWidth + 12 + height: 18 + radius: 9 + color: Qt.rgba(color.r, color.g, color.b, 0.2) + + Text { + id: badgeText + anchors.centerIn: parent + text: root.text + font.pixelSize: 10 + color: root.color + } + } +} diff --git a/package/metadata.json b/package/metadata.json new file mode 100644 index 0000000..ece0a8e --- /dev/null +++ b/package/metadata.json @@ -0,0 +1,8 @@ +{ + "Plugin": { + "Version": "1.0", + "Id": "org.deepin.ds.graphics-driver", + "Url": "driverview.qml", + "Category": "DDE" + } +}