4 Commits

Author SHA1 Message Date
Jokul f79171dbcd chore: 版本号升级至 1.1.0
- CMakeLists.txt: 1.0.0 -> 1.1.0
- metadata.json: 1.0 -> 1.1
- debian/changelog: 新增 1.1.0-1 版本记录
2026-07-14 00:26:43 +08:00
Jokul c4743dde12 docs: 添加 AGENTS.md 指导文件
包含构建安装、deb打包、架构说明、数据采集方式、翻译注意事项、
QML约定等高价值上下文,帮助后续开发会话快速上手
2026-07-14 00:25:43 +08:00
Jokul 46854dfd36 docs: 更新README和设计文档
- README: 移除已废弃的D-Bus接口和依赖,更新为当前实际功能
- README: 添加数据采集方式对比表、QML属性接口文档、安装方式
- 新增 docs/GPU_SWITCH_AND_DRIVER_INSTALL.md: 显卡切换和驱动安装设计方案
2026-07-14 00:23:33 +08:00
Jokul d5a2c380ab feat: 添加GPU切换模式检测和主副显标识
- C++后端:GpuInfo添加isPrimary字段,读取boot_vga判断主副显
- C++后端:detectGpuMode()检测PRIME/Bumblebee/Solo/Hybrid模式
- C++后端:新增gpuMode和gpuPrimary属性
- QML前端:卡片显示绿色主显标签(boot_vga=1的GPU)
- QML前端:弹窗显示GPU模式标签(PRIME等)和帮助图标
- 帮助图标悬停显示模式说明文字
- 翻译更新:主显/主顯 + 四种模式的中文说明
2026-07-14 00:19:56 +08:00
11 changed files with 642 additions and 80 deletions
+84
View File
@@ -0,0 +1,84 @@
# AGENTS.md
## Build & Install
```bash
# Quick build + install (requires sudo password in terminal)
bash install.sh
# Or manual:
cmake -Bbuild && cmake --build build
sudo cmake --install build
systemctl --user restart dde-shell@DDE
```
- `sudo` requires a real TTY — agent bash tool cannot provide passwords. Tell the user to run install commands in their terminal.
- `install.sh` order: build → remove old → install → restart. If build fails, old plugin is NOT removed (set -e).
- **Never declare a Q_INVOKABLE method or Q_PROPERTY without implementing it before building.** The .so will load but dde-shell will crash with `undefined symbol`, taking down the entire taskbar.
## Deb Packaging
```bash
bash build-deb.sh # output to release/
bash build-deb.sh --install # build + install + restart
```
- `dpkg-buildpackage` outputs to `../`, `build-deb.sh` moves artifacts to `release/`.
- `debian/compat` must NOT exist — compat level is declared in `debian/control` via `debhelper-compat (= 13)`.
## Architecture
DDE Shell taskbar plugin. C++ backend (`graphicsdriverapplet.cpp`) + QML frontend (`package/driverview.qml`).
- Root element: `AppletItem` with `import org.deepin.ds.dock 1.0`
- Plugin ID: `org.deepin.ds.graphics-driver`, parent: `org.deepin.ds.dock`
- `dockOrder: 21` = right side of taskbar (20-30 range)
- C++ exposes properties via `Q_PROPERTY` + `Q_INVOKABLE`, QML accesses via `applet` (the `Applet` attached object)
- `PanelPopup` for click popup, `PanelToolTip` for hover tooltip
- `DockPanelPositioner.bounding` must be set before calling `open()` on popup/tooltip
## LSP False Positives
The LSP reports errors like `'applet.h' file not found` and `Unknown type name 'DS_BEGIN_NAMESPACE'`. These are **pre-existing and expected** — the dde-shell headers are in `/usr/include/dde-shell/` which LSP doesn't index. Do NOT attempt to fix them. Verify with `cmake --build build` instead.
## Data Collection (no D-Bus)
All GPU data is collected directly from sysfs/command-line tools (no D-Bus service):
| Data | NVIDIA | AMD/Intel |
|------|--------|-----------|
| GPU detect | `lspci -mm` | `lspci -mm` |
| Driver name | `/sys/bus/pci/devices/0000:XX:XX.X/driver` symlink → `symLinkTarget().section('/', -1)` | same |
| Driver version | `/proc/driver/nvidia/version` (regex `Kernel Module\s+(\d+\.\d+\.\d+)`) | `/proc/sys/kernel/osrelease` (prefixed "Linux ") |
| Temperature | `nvidia-smi --query-gpu=...` | `/sys/class/hwmon/hwmonN/temp1_input` (÷1000) |
| GPU usage | `nvidia-smi` | `/sys/class/drm/cardN/device/gpu_busy_percent` |
| VRAM | `nvidia-smi` | `/sys/class/drm/cardN/device/mem_info_vram_*` (bytes → MB) |
| Primary GPU | `/sys/bus/pci/devices/.../boot_vga` = `1` | same |
| GPU mode | Check `/usr/share/X11/xorg.conf.d/nvidia-drm-outputclass.conf` existence → PRIME | — |
- PCI→DRM card mapping: scan `/sys/bus/pci/devices/0000:XX:XX.X/drm/` for `cardN` entries (regex `^card(\d+)$`).
- `gpuStats` property format: QStringList, one line per GPU: `"temp|gpuUsage|memUsage|memUsed|memTotal"` (-1 = unavailable).
- `nvidia-smi` query: `--query-gpu=pci.bus_id,temperature.gpu,utilization.gpu,utilization.memory,memory.total,memory.used --format=csv,noheader,nounits`. Match by PCI bus ID (last two segments).
## Translations
- `.ts` files in `translations/`, compiled to `.qm` at build time via `lrelease`.
- `lrelease` path: `/usr/lib/qt6/bin/lrelease`
- Manual `lupdate` target: `cmake --build build --target update_translations`
- For simple string additions, edit `.ts` files directly (add `<message>` blocks).
- **Watch for Unicode dashes**: previous edits introduced em-dash (U+2014) where ASCII hyphen was intended. The `edit` tool may fail to match strings containing these. Use Python `re.sub` as fallback.
## QML Conventions
- Colors derived from `DockPalette.iconTextPalette` (basePalette): `primaryText`, `secondaryText`, `tertiaryText`, `cardBackground`, `cardBorder`, `accentBlue`, `accentBlueLight`.
- Existing JS helper functions in driverview.qml: `parseGpuName()`, `parseDriverInfo()`, `parseDriverName()`, `parseDriverVersion()`, `parseGpuVendorShort()`, `buildToolTipText()`.
- `deviceInfo` format: each line = `"GPU Name (driver version)"`. Parsed by splitting on `(` and `)`.
- Timer pattern: `statsRefreshTimer` (2s interval) starts on popup open AND tooltip hover, stops on close/leave.
## No Tests
No test suite exists. Verify changes by building + installing + visually checking the taskbar.
## Reference Plugin
`/home/Jokul/Downloads/dde-weather/` — another dde-shell plugin using the same `AppletItem`/`PanelPopup` pattern. Useful reference for UI structure.
+1 -1
View File
@@ -4,7 +4,7 @@
cmake_minimum_required(VERSION 3.16)
project(dde-graphics-driver-applet VERSION 1.0.0 LANGUAGES CXX)
project(dde-graphics-driver-applet VERSION 1.1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
+136 -72
View File
@@ -1,53 +1,100 @@
# 显卡切换任务栏插件
# GPU 显卡驱动管理任务栏插件
Deepin显卡驱动管理任务栏插件,集成到DDE Shell任务栏中,提供显卡设备检测、驱动安装和切换功能
Deepin V25 DDE Shell 任务栏插件,实时检测 GPU 设备、显示驱动信息和温度/使用率监控
## 功能特性
- 自动检测系统显卡设备
- 显示当前安装的驱动版本
- 列出可用的驱动版本
- 一键切换显卡驱动
- 实时显示安装进度
- 安装完成后提示重启
- **GPU 设备检测** - 通过 `lspci` + `sysfs` 检测所有显卡(NVIDIA/AMD/Intel
- **驱动信息显示** - 驱动模块名 + 版本号(NVIDIA 用 nvidia-smi/procAMD 用内核版本)
- **实时温度监控** - NVIDIA 用 `nvidia-smi`AMD 用 `hwmon`2 秒自动刷新
- **GPU 使用率** - 进度条显示,NVIDIA 用 `nvidia-smi`AMD 用 `gpu_busy_percent`
- **显存使用** - 已用/总量 (MB),带使用率百分比
- **主副显标识** - 通过 `boot_vga` 区分主显卡(绿色"主显"标签)
- **GPU 切换模式检测** - 自动识别 PRIME / Bumblebee / Solo / Hybrid 模式
- **国际化** - 支持简体中文、繁体中文、英文
## 截图
```
┌─────────────────────────────────────┐
│ GPU 显卡驱动管理 │
│─────────────────────────────────────│
│ 当前驱动: nvidia, amdgpu [PRIME] │
│─────────────────────────────────────│
│ ┌─────────────────────────────────┐ │
│ │ NV NVIDIA GeForce RTX 3060 主显│ │
│ │ nvidia 580.119.02 │ │
│ │ 54°C 108/6144 MB │ │
│ │ GPU ████░░░░░░ 36% │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ AMD AMD Radeon Vega │ │
│ │ amdgpu Linux 6.18.36 │ │
│ │ 90°C 471/512 MB │ │
│ │ GPU ████████░░ 71% │ │
│ └─────────────────────────────────┘ │
│ [ 刷新 ] │
└─────────────────────────────────────┘
```
## 依赖项
- Qt6 >= 6.0
- Dtk6 Core
- deepin-graphics-driver-manager(运行时依赖)
### 构建依赖
```
cmake (>= 3.16)
g++
qt6-base-dev
qt6-l10n-tools # lrelease 翻译编译
libdde-shell-dev # DDE Shell 插件头文件
libdtk6core-dev # DTK6 Core
```
### 运行时依赖
```
dde-shell # 任务栏宿主
lspci # GPU 检测(pciutils 包)
nvidia-smi # NVIDIA 监控(可选,nvidia-driver 包自带)
```
## 构建
```bash
# 安装构建依赖
sudo apt install -y cmake g++ qt6-base-dev qt6-tools-dev libdtk6core-dev
sudo apt install -y cmake g++ qt6-base-dev qt6-l10n-tools libdde-shell-dev libdtk6core-dev
# 构建项目
# 构建
cmake -Bbuild
cmake --build build
```
## 安装
```bash
# 安装插件到系统(deepin v25需关闭磐石)
sudo cmake --install build
### 方式一:一键脚本
# 重启任务栏加载插件
```bash
bash install.sh
```
### 方式二:手动安装
```bash
sudo cmake --install build
systemctl --user restart dde-shell@DDE
```
或者注销并重新登录。
### 方式三:deb 包
```bash
bash build-deb.sh --install
```
## 卸载
```bash
# 卸载插件
sudo rm -f /usr/lib/x86_64-linux-gnu/dde-shell/libdde-graphics-driver.so
sudo rm -rf /usr/lib/x86_64-linux-gnu/dde-shell/org.deepin.ds.graphics-driver
# 重启任务栏
sudo rm -f /usr/lib/x86_64-linux-gnu/dde-shell/org.deepin.ds.graphics-driver.so
sudo rm -rf /usr/share/dde-shell/org.deepin.ds.graphics-driver
systemctl --user restart dde-shell@DDE
```
@@ -55,73 +102,90 @@ systemctl --user restart dde-shell@DDE
```
├── package/
│ ├── metadata.json # 插件元数据
│ └── driverview.qml # QML界面
├── graphicsdriverapplet.h # 主插件类头文件
├── graphicsdriverapplet.cpp # 主插件类实现
├── gpuindicator.h # GPU指示器类头文件
├── gpuindicator.cpp # GPU指示器类实现
├── debian/ # Debian打包文件
├── CMakeLists.txt # 构建配置
── README.md # 项目说明
│ ├── metadata.json # 插件元数据ID、父插件、dockOrder
│ └── driverview.qml # QML 界面(弹窗、卡片、tooltip
├── translations/
│ ├── *_zh_CN.ts # 简体中文翻译
│ └── *_zh_TW.ts # 繁体中文翻译
├── graphicsdriverapplet.h # C++ 插件类头文件
├── graphicsdriverapplet.cpp # C++ 插件类实现
├── debian/ # Debian 打包配置
── docs/ # 设计文档
│ └── GPU_SWITCH_AND_DRIVER_INSTALL.md
├── CMakeLists.txt # 构建配置
├── install.sh # 一键构建安装脚本
├── build-deb.sh # 一键 deb 打包脚本
└── README.md
```
## 技术实现
### 架构设计
### 架构
```
┌─────────────────────────────────────────────────────────
DDE Shell Taskbar
├─────────────────────────────────────────────────────────
dde-graphics-driver-applet
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ │ GPU检测模块 │驱动管理 │ │ UI界面
└─────────────┘ └─────────────┘ └─────────────┘
├─────────────────────────────────────────────────────────┤
D-Bus Interface
com.deepin.daemon.GraphicsDriver
├─────────────────────────────────────────────────────────
│ deepin-graphics-driver-manager │
│ (现有的驱动管理服务,无需修改) │
└─────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ DDE Shell Taskbar │
├──────────────────────────────────────────────┤
org.deepin.ds.graphics-driver │
│ ┌──────────┐ ┌──────────┐ ┌───────────
│ │ GPU 检测 │ │ 实时监控 │ │ QML UI │ │
│ lspci │ │ nvidia-smi│ │ 卡片
│ │ sysfs │ │ hwmon │ │ tooltip │ │
│ boot_vga │ │ drm 进度条
└──────────┘ └──────────┘ └───────────┘
──────────────────────────────────────────────
```
### D-Bus接口
### 数据采集方式
插件通过D-Bus与deepin-graphics-driver-manager服务通信:
| 数据 | NVIDIA | AMD/Intel |
|------|--------|-----------|
| GPU 检测 | `lspci -mm` | `lspci -mm` |
| 驱动名 | `/sys/bus/pci/.../driver` symlink | 同左 |
| 驱动版本 | `/proc/driver/nvidia/version` | `/proc/sys/kernel/osrelease` |
| 温度 | `nvidia-smi` | `/sys/class/hwmon/hwmonN/temp1_input` |
| GPU 使用率 | `nvidia-smi` | `/sys/class/drm/cardN/device/gpu_busy_percent` |
| 显存 | `nvidia-smi` | `/sys/class/drm/cardN/device/mem_info_vram_*` |
| 主副显 | `/sys/bus/pci/.../boot_vga` | 同左 |
| 切换模式 | `nvidia-drm-outputclass.conf` 检测 | - |
- `GetDevice()` - 获取显卡设备信息
- `GetCurrDriverName()` - 获取当前驱动名称
- `GetNewDriverName()` - 获取推荐驱动名称
- `PrepareInstall(name, language)` - 准备安装驱动
- `TestInstall()` - 测试安装
- `RealInstall()` - 实际安装
- `CancelInstall()` - 取消安装
### QML 属性接口
### 信号
- `ReportProgress(ratio)` - 安装进度报告
- `Cancel()` - 安装取消通知
| 属性 | 类型 | 说明 |
|------|------|------|
| `applet.ready` | bool | GPU 检测完成 |
| `applet.deviceInfo` | QString | GPU 信息(每行一个) |
| `applet.currentDriver` | QString | 当前驱动列表 |
| `applet.gpuSummary` | QString | GPU 摘要 |
| `applet.gpuStats` | QStringList | 实时统计(temp\|gpu\|mem\|used\|total |
| `applet.gpuMode` | QString | 切换模式(PRIME/Bumblebee/Solo/Hybrid |
| `applet.gpuPrimary` | QStringList | 主副显标识(true/false |
| `applet.refreshStats()` | void | 刷新实时统计 |
| `applet.refreshDeviceInfo()` | void | 重新检测 GPU |
## 使用说明
1. 插件安装后自动加载到任务栏
2. 点击任务栏图标打开驱动管理界面
3. 查看当前显卡设备和驱动信息
4. 选择要切换的驱动版本
5. 确认后开始安装过程
6. 安装完成后选择重启系统
1. 插件安装后自动加载到任务栏右侧
2. **悬停**任务栏图标 - 显示 GPU 温度和使用率摘要
3. **点击**图标 - 打开弹窗,查看详细 GPU 信息
4. 弹窗内数据每 2 秒自动刷新
5. **悬停**卡片 - 显示该 GPU 的驱动详情 tooltip
6. **点击刷新** - 重新检测 GPU 设备
## 开发计划
- [x] GPU 检测与驱动信息显示
- [x] 温度/使用率/显存实时监控
- [x] 主副显标识与 GPU 模式检测
- [ ] PRIME 显卡切换(全局开关)
- [ ] 驱动安装(pkexec + apt
详见 [设计方案文档](docs/GPU_SWITCH_AND_DRIVER_INSTALL.md)
## 许可证
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) - 显卡驱动管理器
- [dde-shell](https://github.com/linuxdeepin/dde-shell) - Deepin Shell 插件系统
+10
View File
@@ -1,3 +1,13 @@
dde-graphics-driver-applet (1.1.0-1) unstable; urgency=medium
* feat: GPU temperature, usage and VRAM real-time monitoring
* feat: primary/secondary GPU identification via boot_vga
* feat: GPU switching mode detection (PRIME/Bumblebee/Solo/Hybrid)
* feat: help tooltip for GPU mode explanation
* docs: update README and add AGENTS.md
-- Jokul <dev@jokul.space> Tue, 14 Jul 2026 00:00:00 +0800
dde-graphics-driver-applet (1.0.1-1) unstable; urgency=medium
* fix: resolve driver name detection using symlink target
+194
View File
@@ -0,0 +1,194 @@
# 显卡切换与驱动安装设计方案
## 背景
当前插件已实现 GPU 检测、温度/使用率监控、主副显标识和 GPU 模式检测。
本文档描述后续两个功能的架构方案:**显卡切换** 和 **驱动安装**
---
## 一、显卡切换(PRIME Render Offload
### 1.1 原理
Linux 双 GPU 笔记本(核显+独显)通过 PRIME Render Offload 实现按需渲染:
- 核显(AMD/Intel)负责日常显示输出
- 独显(NVIDIA)按需渲染 3D 应用
- 通过环境变量控制渲染 GPU
- `__NV_PRIME_RENDER_OFFLOAD=1` - 启用 NVIDIA offload
- `__GLX_VENDOR_LIBRARY_NAME=nvidia` - 指定 OpenGL 实现为 NVIDIA
### 1.2 系统现状
| 检测项 | 当前状态 |
|--------|---------|
| PRIME 配置文件 | `/usr/share/X11/xorg.conf.d/nvidia-drm-outputclass.conf` 存在 |
| prime-select 工具 | 不存在 |
| nvidia-prime 包 | 未安装 |
| boot_vga | AMD=1(主显),NVIDIA=0(副显) |
| 当前渲染器 | AMD Radeon(核显主渲染) |
### 1.3 方案设计
#### 方案 A:全局开关(推荐先实现)
**原理**:通过 `~/.config/environment.d/` 设置全局环境变量,重新登录后生效。
**实现**
- 插件弹窗中添加 PRIME 开关按钮
- 开启时写入 `~/.config/environment.d/nvidia-prime.conf`
```ini
__NV_PRIME_RENDER_OFFLOAD=1
__GLX_VENDOR_LIBRARY_NAME=nvidia
```
- 关闭时删除该文件
- 不需要 root 权限
- 需要重新登录生效,UI 上提示用户
**优点**:不需要 root,实现简单,全局生效
**缺点**:需要重新登录
#### 方案 B:按应用启动
**原理**:提供快捷方式,设置环境变量后启动指定应用。
**实现**
- 任务栏右键菜单添加"用独显启动"选项
- 弹出应用选择器或输入命令
- 执行 `__NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia <command>`
- 即时生效,不影响全局
**优点**:即时生效,不影响全局
**缺点**:需要手动选择应用
#### 方案 C:两者结合(最终目标)
同时提供全局开关和按应用启动,用户可选择使用方式。
### 1.4 实现计划
1. C++ 后端新增 `primeEnabled` 属性和 `togglePrime(bool)` 方法
2. C++ 后端读写 `~/.config/environment.d/nvidia-prime.conf`
3. QML 弹窗中添加 PRIME 开关控件
4. 开关状态变更后提示"需要重新登录生效"
5. (后续)右键菜单添加"用独显启动应用"
### 1.5 数据接口
```cpp
// 新增属性
Q_PROPERTY(bool primeEnabled READ primeEnabled NOTIFY primeEnabledChanged)
// 新增方法
Q_INVOKABLE void togglePrime(bool enabled);
// primeEnabled 获取逻辑
// 1. 检查 ~/.config/environment.d/nvidia-prime.conf 是否存在
// 2. 检查文件内容是否包含 __NV_PRIME_RENDER_OFFLOAD=1
```
---
## 二、驱动安装
### 2.1 原理
驱动安装需要 root 权限执行 `apt install/remove`,核心问题是如何从任务栏插件(普通用户)安全地获取特权。
### 2.2 系统现状
| 检测项 | 当前状态 |
|--------|---------|
| deepin-graphics-driver-manager | 未安装 |
| D-Bus 驱动管理服务 | 不存在 |
| pkexec | 可用 (`/usr/bin/pkexec`) |
| polkit apt 策略 | `com.deepin.pkexec.aptInstallDepend.policy` 存在 |
| nvidia-driver 包 | 已安装,版本 580.119.02 |
### 2.3 方案对比
| 方案 | 实现 | 优点 | 缺点 |
|------|------|------|------|
| **A. pkexec + apt(推荐)** | pkexec 弹授权框执行 apt | 简单直接,无需额外服务 | 每次操作需授权 |
| **B. D-Bus 系统服务** | 创建 systemd 服务暴露接口 | 规范,可记住授权 | 工作量大,需额外包 |
| **C. 复用 deepin-graphics-driver-manager** | 安装该包调用其接口 | 功能最全 | V25 兼容性未知 |
### 2.4 推荐方案:pkexec + apt
#### 流程
```
用户点击"安装驱动"
插件检测可安装的驱动包列表 (apt list nvidia-driver*)
用户选择驱动版本
pkexec apt install -y nvidia-driver-xxx
polkit 弹出密码授权框
用户输入密码
apt 执行安装
插件显示安装结果
```
#### 实现细节
1. **驱动状态检测**
- 已安装:读取 `/sys/module/nvidia/version` 或 `/proc/driver/nvidia/version`
- 可用版本:`apt list nvidia-driver* 2>/dev/null`
- 加载状态:`lsmod | grep nvidia`
2. **安装/卸载**
- 创建 helper 脚本 `/usr/share/dde-shell/org.deepin.ds.graphics-driver/scripts/install-driver.sh`
- 脚本接收包名参数,执行 apt install/remove
- 插件通过 `pkexec` 调用该脚本
- 利用系统已有的 `com.deepin.pkexec.aptInstallDepend.policy` polkit 策略
3. **进度显示**
- pkexec 执行期间显示"安装中..."状态
- QProcess 监听 pkexec 进程退出码判断成功/失败
### 2.5 数据接口
```cpp
// 新增属性
Q_PROPERTY(QStringList availableDrivers READ availableDrivers NOTIFY availableDriversChanged)
Q_PROPERTY(bool driverInstalling READ driverInstalling NOTIFY driverInstallingChanged)
// 新增方法
Q_INVOKABLE void installDriver(const QString &packageName);
Q_INVOKABLE void uninstallDriver(const QString &packageName);
// availableDrivers 获取逻辑
// 执行 apt list nvidia-driver*,解析输出
```
### 2.6 安全考虑
- helper 脚本放置在 `/usr/share/` 下,需要 root 权限修改
- 脚本中硬编码允许的包名前缀(如 `nvidia-driver`),防止注入
- 不直接传递用户输入给 apt,只传递预定义的包名
- polkit 策略限制只有本插件可以调用
---
## 三、实现优先级
| 优先级 | 功能 | 难度 | 预计工作量 |
|--------|------|------|-----------|
| P0 | PRIME 全局开关(方案A | 低 | 0.5 天 |
| P1 | 驱动安装 pkexec 方案 | 中 | 1-2 天 |
| P2 | 按应用启动独显(方案B) | 中 | 1 天 |
| P3 | D-Bus 系统服务(可选) | 高 | 2-3 天 |
## 四、参考资源
- [NVIDIA PRIME Render Offload 官方文档](https://download.nvidia.com/XFree86/Linux-x86_64/580.119.02/README/primerenderoffload.html)
- [deepin-graphics-driver-manager 源码](http://git.jokul.space/Jokul/deepin-graphics-driver-manager)
- [Polkit 设计文档](https://www.freedesktop.org/software/polkit/docs/latest/)
- [systemd environment.d 说明](https://systemd.io/ENVIRONMENT/)
+66
View File
@@ -63,6 +63,16 @@ QStringList GraphicsDriverApplet::gpuStats() const
return m_gpuStats;
}
QString GraphicsDriverApplet::gpuMode() const
{
return m_gpuMode;
}
QStringList GraphicsDriverApplet::gpuPrimary() const
{
return m_gpuPrimary;
}
void GraphicsDriverApplet::refreshDeviceInfo()
{
detectGpus();
@@ -87,6 +97,7 @@ void GraphicsDriverApplet::detectGpus()
GpuInfo gpu;
// 初始化统计字段
gpu.drmCard = -1;
gpu.isPrimary = false;
gpu.temperature = -1;
gpu.gpuUsage = -1;
gpu.memUsage = -1;
@@ -119,6 +130,12 @@ void GraphicsDriverApplet::detectGpus()
// 查找 DRM 卡号
gpu.drmCard = findDrmCard(gpu.pciId);
// 读取 boot_vga 判断主副显
QFile bootVgaFile("/sys/bus/pci/devices/0000:" + gpu.pciId + "/boot_vga");
if (bootVgaFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
gpu.isPrimary = QTextStream(&bootVgaFile).readAll().trimmed() == "1";
}
m_gpus.append(gpu);
}
}
@@ -146,16 +163,27 @@ void GraphicsDriverApplet::detectGpus()
m_gpuSummary = parts.join(" | ");
m_ready = !m_gpus.isEmpty();
// 生成主副显标识
m_gpuPrimary.clear();
for (const GpuInfo &gpu : m_gpus) {
m_gpuPrimary << (gpu.isPrimary ? "true" : "false");
}
qDebug() << "Detected GPUs:" << m_deviceInfo;
qDebug() << "Current drivers:" << m_currentDriver;
emit deviceInfoChanged();
emit currentDriverChanged();
emit gpuSummaryChanged();
emit gpuStatsChanged();
emit gpuPrimaryChanged();
emit readyChanged();
// 读取实时统计信息
readGpuStats();
// 检测 GPU 切换模式
detectGpuMode();
}
QStringList GraphicsDriverApplet::runCommand(const QString &cmd, const QStringList &args)
@@ -238,6 +266,44 @@ QString GraphicsDriverApplet::readDriverVersion(const QString &driverName)
return QString();
}
void GraphicsDriverApplet::detectGpuMode()
{
// 单 GPU
if (m_gpus.size() <= 1) {
m_gpuMode = "Solo";
emit gpuModeChanged();
return;
}
// 多 GPU:检测 PRIME / Bumblebee
// PRIME: nvidia-drm-outputclass.conf 存在
if (QFile::exists("/usr/share/X11/xorg.conf.d/nvidia-drm-outputclass.conf") ||
QFile::exists("/etc/X11/xorg.conf.d/70-nvidia.conf")) {
m_gpuMode = "PRIME";
emit gpuModeChanged();
return;
}
// Bumblebee: 检查 bumblebeed 服务
QStringList lsmod = runCommand("lsmod", {});
bool bumblebeeLoaded = false;
for (const QString &line : lsmod) {
if (line.contains("bumblebee", Qt::CaseInsensitive)) {
bumblebeeLoaded = true;
break;
}
}
if (bumblebeeLoaded || QFile::exists("/usr/sbin/bumblebeed")) {
m_gpuMode = "Bumblebee";
emit gpuModeChanged();
return;
}
// 多 GPU 但无已知切换方案
m_gpuMode = "Hybrid";
emit gpuModeChanged();
}
int GraphicsDriverApplet::findDrmCard(const QString &pciId)
{
// 扫描 /sys/bus/pci/devices/0000:XX:XX.X/drm/ 目录,找到 cardN 条目
+10
View File
@@ -18,6 +18,7 @@ struct GpuInfo {
QString driverVersion; // 驱动版本
QString pciId; // PCI ID,如 "01:00.0"
int drmCard; // DRM 卡号,如 0, 1(-1 表示未找到)
bool isPrimary; // 是否为主显卡(boot_vga=1
// 实时统计
int temperature; // 温度 (°C),-1 表示不可用
@@ -35,6 +36,8 @@ class GraphicsDriverApplet : public DApplet
Q_PROPERTY(QString currentDriver READ currentDriver NOTIFY currentDriverChanged)
Q_PROPERTY(QString gpuSummary READ gpuSummary NOTIFY gpuSummaryChanged)
Q_PROPERTY(QStringList gpuStats READ gpuStats NOTIFY gpuStatsChanged)
Q_PROPERTY(QString gpuMode READ gpuMode NOTIFY gpuModeChanged)
Q_PROPERTY(QStringList gpuPrimary READ gpuPrimary NOTIFY gpuPrimaryChanged)
Q_PROPERTY(bool ready READ ready NOTIFY readyChanged)
public:
@@ -48,6 +51,8 @@ public:
QString currentDriver() const;
QString gpuSummary() const;
QStringList gpuStats() const;
QString gpuMode() const;
QStringList gpuPrimary() const;
bool ready() const;
Q_INVOKABLE void refreshDeviceInfo();
@@ -58,11 +63,14 @@ signals:
void currentDriverChanged();
void gpuSummaryChanged();
void gpuStatsChanged();
void gpuModeChanged();
void gpuPrimaryChanged();
void readyChanged();
private:
void detectGpus();
void readGpuStats();
void detectGpuMode();
static int findDrmCard(const QString &pciId);
static QStringList runCommand(const QString &cmd, const QStringList &args);
static QString parseGpuName(const QString &line);
@@ -73,6 +81,8 @@ private:
QString m_currentDriver;
QString m_gpuSummary;
QStringList m_gpuStats;
QString m_gpuMode;
QStringList m_gpuPrimary;
bool m_ready;
};
+90 -6
View File
@@ -23,6 +23,7 @@ AppletItem {
readonly property string gpuSummary: applet ? (applet.gpuSummary || qsTr("No GPU detected")) : qsTr("No GPU detected")
readonly property string deviceInfo: applet ? (applet.deviceInfo || qsTr("Unknown")) : qsTr("Unknown")
readonly property string currentDriver: applet ? (applet.currentDriver || qsTr("Unknown")) : qsTr("Unknown")
readonly property string gpuMode: applet ? (applet.gpuMode || "") : ""
property Palette basePalette: DockPalette.iconTextPalette
readonly property color primaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.9)
@@ -189,6 +190,65 @@ AppletItem {
Layout.fillWidth: true
elide: Text.ElideRight
}
// GPU 模式标签
Rectangle {
visible: root.gpuMode.length > 0
width: modeText.implicitWidth + 16
height: 20
color: root.accentBlueLight
radius: 10
Text {
id: modeText
anchors.centerIn: parent
text: root.gpuMode
font.pixelSize: 11
font.bold: true
color: root.accentBlue
}
}
// 帮助图标
Item {
visible: root.gpuMode.length > 0
width: 16
height: 16
Rectangle {
anchors.fill: parent
radius: width / 2
color: helpHover.hovered ? root.accentBlue : root.tertiaryText
}
Text {
anchors.centerIn: parent
text: "?"
font.pixelSize: 10
font.bold: true
color: "white"
}
HoverHandler {
id: helpHover
}
ToolTip {
visible: helpHover.hovered
delay: 200
text: {
if (root.gpuMode === "PRIME")
return qsTr("PRIME mode: the integrated GPU handles display, the discrete GPU renders on demand. 3D apps use PRIME Render Offload to invoke the discrete GPU.")
if (root.gpuMode === "Bumblebee")
return qsTr("Bumblebee mode: a legacy dual-GPU solution, now deprecated.")
if (root.gpuMode === "Solo")
return qsTr("Solo mode: only one GPU detected.")
if (root.gpuMode === "Hybrid")
return qsTr("Hybrid mode: dual GPU detected, but no known switching solution found.")
return ""
}
}
}
}
// GPU 列表区域
@@ -220,6 +280,7 @@ AppletItem {
border.color: root.cardBorder
property string statsLine: root.applet && root.applet.gpuStats ? (root.applet.gpuStats[index] || "") : ""
property bool isPrimaryGpu: root.applet && root.applet.gpuPrimary ? (root.applet.gpuPrimary[index] === "true") : false
property string tempStr: {
var parts = statsLine.split("|")
var temp = parts[0]
@@ -300,13 +361,36 @@ AppletItem {
spacing: 4
Layout.fillWidth: true
Text {
text: parseGpuName(modelData)
font.pixelSize: 13
font.bold: true
color: root.primaryText
RowLayout {
spacing: 6
Layout.fillWidth: true
elide: Text.ElideRight
Text {
text: parseGpuName(modelData)
font.pixelSize: 13
font.bold: true
color: root.primaryText
Layout.fillWidth: true
elide: Text.ElideRight
}
// 主/副显标识
Rectangle {
visible: gpuCard.isPrimaryGpu
width: badgeText.implicitWidth + 12
height: 16
color: Qt.rgba(22/255, 163/255, 74/255, 0.15)
radius: 8
Text {
id: badgeText
anchors.centerIn: parent
text: qsTr("Primary")
font.pixelSize: 10
font.bold: true
color: Qt.rgba(22/255, 163/255, 74/255, 1)
}
}
}
Text {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"Plugin": {
"Version": "1.0",
"Version": "1.1",
"Id": "org.deepin.ds.graphics-driver",
"Url": "driverview.qml",
"Parent": "org.deepin.ds.dock",
@@ -43,5 +43,30 @@
<source>Version: </source>
<translation></translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Primary</source>
<translation></translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>PRIME mode: the integrated GPU handles display, the discrete GPU renders on demand. 3D apps use PRIME Render Offload to invoke the discrete GPU.</source>
<translation>PRIME 3D PRIME Render Offload </translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Bumblebee mode: a legacy dual-GPU solution, now deprecated.</source>
<translation>Bumblebee GPU </translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Solo mode: only one GPU detected.</source>
<translation>Solo GPU</translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Hybrid mode: dual GPU detected, but no known switching solution found.</source>
<translation>Hybrid GPU</translation>
</message>
</context>
</TS>
@@ -43,5 +43,30 @@
<source>Version: </source>
<translation></translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Primary</source>
<translation></translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>PRIME mode: the integrated GPU handles display, the discrete GPU renders on demand. 3D apps use PRIME Render Offload to invoke the discrete GPU.</source>
<translation>PRIME 3D PRIME Render Offload 調</translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Bumblebee mode: a legacy dual-GPU solution, now deprecated.</source>
<translation>Bumblebee GPU </translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Solo mode: only one GPU detected.</source>
<translation>Solo GPU</translation>
</message>
<message>
<location filename="../package/driverview.qml"/>
<source>Hybrid mode: dual GPU detected, but no known switching solution found.</source>
<translation>Hybrid GPU</translation>
</message>
</context>
</TS>