Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e474de717 | |||
| 64408bf842 | |||
| f7c6801793 | |||
| 2570a028ea | |||
| cda61d9d72 | |||
| dd6b5ed8b9 | |||
| e771a160ab | |||
| f79171dbcd | |||
| c4743dde12 | |||
| 46854dfd36 | |||
| d5a2c380ab |
@@ -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` (1s 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.
|
||||
+7
-2
@@ -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)
|
||||
@@ -12,6 +12,7 @@ set(CMAKE_AUTOMOC ON)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Quick DBus LinguistTools)
|
||||
find_package(Dtk6 REQUIRED COMPONENTS Core)
|
||||
find_package(DDEShell REQUIRED)
|
||||
|
||||
# 插件 ID
|
||||
set(PLUGIN_ID "org.deepin.ds.graphics-driver")
|
||||
@@ -23,25 +24,28 @@ set(TRANSLATIONS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/translations)
|
||||
set(TS_FILES
|
||||
${TRANSLATIONS_DIR}/${PLUGIN_ID}_zh_CN.ts
|
||||
${TRANSLATIONS_DIR}/${PLUGIN_ID}_zh_TW.ts
|
||||
${TRANSLATIONS_DIR}/${PLUGIN_ID}_en.ts
|
||||
)
|
||||
|
||||
# 生成 .qm 编译翻译文件
|
||||
set(QM_FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${PLUGIN_ID}_zh_CN.qm
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${PLUGIN_ID}_zh_TW.qm
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${PLUGIN_ID}_en.qm
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${QM_FILES}
|
||||
COMMAND /usr/lib/qt6/bin/lrelease ${TRANSLATIONS_DIR}/${PLUGIN_ID}_zh_CN.ts -qm ${CMAKE_CURRENT_BINARY_DIR}/${PLUGIN_ID}_zh_CN.qm
|
||||
COMMAND /usr/lib/qt6/bin/lrelease ${TRANSLATIONS_DIR}/${PLUGIN_ID}_zh_TW.ts -qm ${CMAKE_CURRENT_BINARY_DIR}/${PLUGIN_ID}_zh_TW.qm
|
||||
COMMAND /usr/lib/qt6/bin/lrelease ${TRANSLATIONS_DIR}/${PLUGIN_ID}_en.ts -qm ${CMAKE_CURRENT_BINARY_DIR}/${PLUGIN_ID}_en.qm
|
||||
DEPENDS ${TS_FILES}
|
||||
COMMENT "Compiling translations to .qm files"
|
||||
)
|
||||
|
||||
# 手动更新翻译源文件(不加入 ALL,仅在需要时手动 make update_translations)
|
||||
add_custom_target(update_translations
|
||||
COMMAND /usr/lib/qt6/bin/lupdate ${CMAKE_CURRENT_SOURCE_DIR}/package -ts ${TS_FILES}
|
||||
COMMAND /usr/lib/qt6/bin/lupdate ${CMAKE_CURRENT_SOURCE_DIR}/package ${CMAKE_CURRENT_SOURCE_DIR}/graphicsdriverapplet.cpp -ts ${TS_FILES}
|
||||
COMMENT "Updating translation source files"
|
||||
)
|
||||
|
||||
@@ -63,6 +67,7 @@ target_link_libraries(org.deepin.ds.graphics-driver PRIVATE
|
||||
Qt6::Quick
|
||||
Qt6::DBus
|
||||
Dtk6::Core
|
||||
Dde::Shell
|
||||
)
|
||||
|
||||
# 安装到dde-shell插件目录
|
||||
|
||||
@@ -1,53 +1,100 @@
|
||||
# 显卡切换任务栏插件
|
||||
# GPU 显卡驱动管理任务栏插件
|
||||
|
||||
Deepin显卡驱动管理任务栏插件,集成到DDE Shell任务栏中,提供显卡设备检测、驱动安装和切换功能。
|
||||
Deepin V25 DDE Shell 任务栏插件,实时检测 GPU 设备、显示驱动信息和温度/使用率监控。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 自动检测系统显卡设备
|
||||
- 显示当前安装的驱动版本
|
||||
- 列出可用的驱动版本
|
||||
- 一键切换显卡驱动
|
||||
- 实时显示安装进度
|
||||
- 安装完成后提示重启
|
||||
- **GPU 设备检测** - 通过 `lspci` + `sysfs` 检测所有显卡(NVIDIA/AMD/Intel)
|
||||
- **驱动信息显示** - 驱动模块名 + 版本号(NVIDIA 用 nvidia-smi/proc,AMD 用内核版本)
|
||||
- **实时温度监控** - 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 插件系统
|
||||
|
||||
Vendored
+10
@@ -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
|
||||
|
||||
@@ -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/)
|
||||
+470
-6
@@ -12,6 +12,12 @@
|
||||
#include <QTextStream>
|
||||
#include <QRegularExpression>
|
||||
#include <QFileInfo>
|
||||
#include <QSettings>
|
||||
#include <algorithm>
|
||||
#include <QDateTime>
|
||||
#include <QDBusInterface>
|
||||
#include <QDBusMessage>
|
||||
#include <QVariantList>
|
||||
|
||||
DCORE_USE_NAMESPACE
|
||||
|
||||
@@ -20,7 +26,14 @@ DS_BEGIN_NAMESPACE
|
||||
GraphicsDriverApplet::GraphicsDriverApplet(QObject *parent)
|
||||
: DApplet(parent)
|
||||
, m_ready(false)
|
||||
, m_lastAlertTime(0)
|
||||
, m_tempAlertEnabled(true)
|
||||
, m_switchPending(false)
|
||||
, m_restoreCountdown(0)
|
||||
, m_restoreTimer(nullptr)
|
||||
{
|
||||
QSettings settings("deepin", "graphics-driver-applet");
|
||||
m_tempAlertEnabled = settings.value("tempAlertEnabled", true).toBool();
|
||||
}
|
||||
|
||||
GraphicsDriverApplet::~GraphicsDriverApplet()
|
||||
@@ -35,6 +48,29 @@ bool GraphicsDriverApplet::load()
|
||||
bool GraphicsDriverApplet::init()
|
||||
{
|
||||
detectGpus();
|
||||
|
||||
// 检查是否有未确认的 GPU 切换(上次切换后可能黑屏)
|
||||
QFile backup(backupFilePath());
|
||||
if (backup.exists()) {
|
||||
// 有备份文件,说明上次切换未确认,启动 30 秒回退计时器
|
||||
m_switchPending = true;
|
||||
m_restoreCountdown = 30;
|
||||
emit switchPendingChanged();
|
||||
emit restoreCountdownChanged();
|
||||
|
||||
m_restoreTimer = new QTimer(this);
|
||||
connect(m_restoreTimer, &QTimer::timeout, this, [this]() {
|
||||
m_restoreCountdown--;
|
||||
emit restoreCountdownChanged();
|
||||
if (m_restoreCountdown <= 0) {
|
||||
restoreFromBackup();
|
||||
}
|
||||
});
|
||||
m_restoreTimer->start(1000);
|
||||
|
||||
qDebug() << "Unconfirmed GPU switch detected, auto-restore in 30s";
|
||||
}
|
||||
|
||||
return DApplet::init();
|
||||
}
|
||||
|
||||
@@ -63,6 +99,77 @@ QStringList GraphicsDriverApplet::gpuStats() const
|
||||
return m_gpuStats;
|
||||
}
|
||||
|
||||
QString GraphicsDriverApplet::gpuMode() const
|
||||
{
|
||||
return m_gpuMode;
|
||||
}
|
||||
|
||||
QStringList GraphicsDriverApplet::gpuPrimary() const
|
||||
{
|
||||
return m_gpuPrimary;
|
||||
}
|
||||
|
||||
QVariantList GraphicsDriverApplet::tempHistory() const
|
||||
{
|
||||
QVariantList result;
|
||||
for (const QList<int> &history : m_tempHistory) {
|
||||
QVariantList gpuHistory;
|
||||
for (int temp : history) {
|
||||
gpuHistory << temp;
|
||||
}
|
||||
result << QVariant(gpuHistory);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList GraphicsDriverApplet::gpuProcesses() const
|
||||
{
|
||||
return m_gpuProcesses;
|
||||
}
|
||||
|
||||
bool GraphicsDriverApplet::tempAlertEnabled() const
|
||||
{
|
||||
return m_tempAlertEnabled;
|
||||
}
|
||||
|
||||
void GraphicsDriverApplet::setTempAlertEnabled(bool enabled)
|
||||
{
|
||||
if (m_tempAlertEnabled == enabled) return;
|
||||
m_tempAlertEnabled = enabled;
|
||||
QSettings settings("deepin", "graphics-driver-applet");
|
||||
settings.setValue("tempAlertEnabled", enabled);
|
||||
emit tempAlertEnabledChanged();
|
||||
}
|
||||
|
||||
bool GraphicsDriverApplet::primeOffloadEnabled() const
|
||||
{
|
||||
QString configPath = QDir::homePath() + "/.config/environment.d/nvidia-prime.conf";
|
||||
QFile file(configPath);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return false;
|
||||
QString content = QTextStream(&file).readAll();
|
||||
return content.contains("__NV_PRIME_RENDER_OFFLOAD=1");
|
||||
}
|
||||
|
||||
bool GraphicsDriverApplet::switchPending() const
|
||||
{
|
||||
return m_switchPending;
|
||||
}
|
||||
|
||||
int GraphicsDriverApplet::restoreCountdown() const
|
||||
{
|
||||
return m_restoreCountdown;
|
||||
}
|
||||
|
||||
QString GraphicsDriverApplet::primeConfigPath()
|
||||
{
|
||||
return QDir::homePath() + "/.config/environment.d/nvidia-prime.conf";
|
||||
}
|
||||
|
||||
QString GraphicsDriverApplet::backupFilePath()
|
||||
{
|
||||
return QDir::homePath() + "/.config/environment.d/.gpu-switch-backup";
|
||||
}
|
||||
|
||||
void GraphicsDriverApplet::refreshDeviceInfo()
|
||||
{
|
||||
detectGpus();
|
||||
@@ -76,6 +183,7 @@ void GraphicsDriverApplet::refreshStats()
|
||||
void GraphicsDriverApplet::detectGpus()
|
||||
{
|
||||
m_gpus.clear();
|
||||
m_tempHistory.clear();
|
||||
|
||||
// 通过 lspci 检测显卡
|
||||
QStringList lines = runCommand("lspci", {"-mm"});
|
||||
@@ -87,11 +195,16 @@ void GraphicsDriverApplet::detectGpus()
|
||||
GpuInfo gpu;
|
||||
// 初始化统计字段
|
||||
gpu.drmCard = -1;
|
||||
gpu.isPrimary = false;
|
||||
gpu.temperature = -1;
|
||||
gpu.gpuUsage = -1;
|
||||
gpu.memUsage = -1;
|
||||
gpu.memTotal = -1;
|
||||
gpu.memUsed = -1;
|
||||
gpu.gpuClock = -1;
|
||||
gpu.memClock = -1;
|
||||
gpu.powerDraw = -1;
|
||||
gpu.fanSpeed = -1;
|
||||
|
||||
// lspci -mm 格式: "Class" "Vendor" "Device" ...
|
||||
// 提取 PCI ID(行首的 01:00.0 等)
|
||||
@@ -119,10 +232,21 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 排序:核显(isPrimary)排在前面
|
||||
std::stable_sort(m_gpus.begin(), m_gpus.end(), [](const GpuInfo &a, const GpuInfo &b) {
|
||||
return a.isPrimary > b.isPrimary;
|
||||
});
|
||||
|
||||
// 生成摘要信息
|
||||
QStringList parts;
|
||||
QStringList driverParts;
|
||||
@@ -146,16 +270,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 +373,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 条目
|
||||
@@ -260,10 +433,11 @@ int GraphicsDriverApplet::findDrmCard(const QString &pciId)
|
||||
void GraphicsDriverApplet::readGpuStats()
|
||||
{
|
||||
// 先处理 NVIDIA GPU(单次 nvidia-smi 调用获取所有 NVIDIA GPU 数据)
|
||||
// nvidia-smi 输出格式: pci.bus_id,temperature.gpu,utilization.gpu,utilization.memory,memory.total,memory.used
|
||||
// nvidia-smi 输出格式: pci.bus_id,temperature.gpu,utilization.gpu,utilization.memory,
|
||||
// memory.total,memory.used,clocks.gr,clocks.mem,power.draw,fan.speed
|
||||
QMap<QString, QStringList> nvidiaData; // pciId -> stats
|
||||
QStringList nvLines = runCommand("nvidia-smi", {
|
||||
"--query-gpu=pci.bus_id,temperature.gpu,utilization.gpu,utilization.memory,memory.total,memory.used",
|
||||
"--query-gpu=pci.bus_id,temperature.gpu,utilization.gpu,utilization.memory,memory.total,memory.used,clocks.gr,clocks.mem,power.draw,fan.speed",
|
||||
"--format=csv,noheader,nounits"
|
||||
});
|
||||
for (const QString &line : nvLines) {
|
||||
@@ -278,6 +452,17 @@ void GraphicsDriverApplet::readGpuStats()
|
||||
|
||||
// 逐个 GPU 读取统计
|
||||
for (GpuInfo &gpu : m_gpus) {
|
||||
// 每次刷新前重置实时统计字段,确保数据源不可用时不会残留旧值
|
||||
gpu.temperature = -1;
|
||||
gpu.gpuUsage = -1;
|
||||
gpu.memUsage = -1;
|
||||
gpu.memTotal = -1;
|
||||
gpu.memUsed = -1;
|
||||
gpu.gpuClock = -1;
|
||||
gpu.memClock = -1;
|
||||
gpu.powerDraw = -1;
|
||||
gpu.fanSpeed = -1;
|
||||
|
||||
if (gpu.driver == "nvidia" && nvidiaData.contains(gpu.pciId)) {
|
||||
// NVIDIA: 使用 nvidia-smi 数据
|
||||
QStringList &parts = nvidiaData[gpu.pciId];
|
||||
@@ -286,6 +471,19 @@ void GraphicsDriverApplet::readGpuStats()
|
||||
gpu.memUsage = parts[3].trimmed().toInt();
|
||||
gpu.memTotal = parts[4].trimmed().toInt();
|
||||
gpu.memUsed = parts[5].trimmed().toInt();
|
||||
// 频率 (parts[6]=GPU, parts[7]=显存)
|
||||
if (parts.size() > 6) gpu.gpuClock = parts[6].trimmed().toInt();
|
||||
if (parts.size() > 7) gpu.memClock = parts[7].trimmed().toInt();
|
||||
// 功耗 (parts[8]),[N/A] 时保持 -1
|
||||
if (parts.size() > 8) {
|
||||
QString pwr = parts[8].trimmed();
|
||||
if (pwr != "[N/A]" && !pwr.isEmpty()) gpu.powerDraw = pwr.toDouble();
|
||||
}
|
||||
// 风扇转速 (parts[9]),[N/A] 时保持 -1
|
||||
if (parts.size() > 9) {
|
||||
QString fan = parts[9].trimmed();
|
||||
if (fan != "[N/A]" && !fan.isEmpty()) gpu.fanSpeed = fan.toInt();
|
||||
}
|
||||
} else if (gpu.drmCard >= 0) {
|
||||
// AMD / Intel / 其他: 使用 sysfs
|
||||
QString cardPath = QString("/sys/class/drm/card%1/device").arg(gpu.drmCard);
|
||||
@@ -318,31 +516,297 @@ void GraphicsDriverApplet::readGpuStats()
|
||||
if (nameFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QString name = QTextStream(&nameFile).readAll().trimmed();
|
||||
if (name == gpu.driver || name == "amdgpu" || name == "radeon") {
|
||||
QFile tempFile(hwmonDir.absoluteFilePath(hwmon + "/temp1_input"));
|
||||
QString hwmonPath = hwmonDir.absoluteFilePath(hwmon);
|
||||
// 温度
|
||||
QFile tempFile(hwmonPath + "/temp1_input");
|
||||
if (tempFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
int tempMilli = QTextStream(&tempFile).readAll().trimmed().toInt();
|
||||
gpu.temperature = tempMilli / 1000;
|
||||
}
|
||||
// 功耗 (µW -> W)
|
||||
QFile powerFile(hwmonPath + "/power1_average");
|
||||
if (powerFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
qint64 microWatts = QTextStream(&powerFile).readAll().trimmed().toLongLong();
|
||||
gpu.powerDraw = microWatts / 1000000.0;
|
||||
}
|
||||
// 风扇转速 (RPM)
|
||||
QFile fanFile(hwmonPath + "/fan1_input");
|
||||
if (fanFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
gpu.fanSpeed = QTextStream(&fanFile).readAll().trimmed().toInt();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GPU 频率: Intel i915 使用 gt/gt0/rps_act_freq_mhz,AMD 使用 hwmon freq1_input
|
||||
QString gtPath = QString("/sys/class/drm/card%1/gt/gt0/rps_act_freq_mhz").arg(gpu.drmCard);
|
||||
QFile gtFreqFile(gtPath);
|
||||
if (gtFreqFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
gpu.gpuClock = QTextStream(>FreqFile).readAll().trimmed().toInt();
|
||||
}
|
||||
}
|
||||
|
||||
// --- 以下为 Intel 核显等无 hwmon/vram sysfs 接口时的回退逻辑 ---
|
||||
|
||||
// 温度回退:扫描 thermal_zone,匹配 Intel GPU 的 ACPI 标识 (B0D4)
|
||||
// B0D4 是 Intel GPU 在 ACPI 规范中的标准热区标识
|
||||
if (gpu.temperature < 0) {
|
||||
QDir thermalDir("/sys/class/thermal");
|
||||
QStringList zones = thermalDir.entryList(QStringList() << "thermal_zone*", QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
for (const QString &zone : zones) {
|
||||
QFile typeFile(thermalDir.absoluteFilePath(zone + "/type"));
|
||||
if (typeFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QString type = QTextStream(&typeFile).readAll().trimmed();
|
||||
if (type == "B0D4") {
|
||||
QFile tempFile(thermalDir.absoluteFilePath(zone + "/temp"));
|
||||
if (tempFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
int tempMilli = QTextStream(&tempFile).readAll().trimmed().toInt();
|
||||
gpu.temperature = tempMilli / 1000;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 显存回退:Intel 核显使用共享系统内存,读取 /proc/meminfo 作为参考
|
||||
// 注意:这是系统内存使用量,非核显独占,仅作参考
|
||||
if (gpu.memTotal < 0) {
|
||||
QFile meminfo("/proc/meminfo");
|
||||
if (meminfo.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QString content = QTextStream(&meminfo).readAll();
|
||||
QRegularExpression totalRe("MemTotal:\\s*(\\d+)\\s*kB");
|
||||
QRegularExpression availRe("MemAvailable:\\s*(\\d+)\\s*kB");
|
||||
auto totalMatch = totalRe.match(content);
|
||||
auto availMatch = availRe.match(content);
|
||||
if (totalMatch.hasMatch()) {
|
||||
gpu.memTotal = totalMatch.captured(1).toLongLong() / 1024; // kB -> MB
|
||||
}
|
||||
if (availMatch.hasMatch() && gpu.memTotal > 0) {
|
||||
qint64 availMB = availMatch.captured(1).toLongLong() / 1024;
|
||||
gpu.memUsed = gpu.memTotal - availMB;
|
||||
if (gpu.memUsed >= 0) {
|
||||
gpu.memUsage = gpu.memUsed * 100 / gpu.memTotal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生成统计数据 (格式: temp|gpuUsage|memUsage|memUsed|memTotal)
|
||||
// 生成统计数据 (格式: temp|gpuUsage|memUsage|memUsed|memTotal|gpuClock|memClock|powerDraw|fanSpeed)
|
||||
m_gpuStats.clear();
|
||||
for (const GpuInfo &gpu : m_gpus) {
|
||||
m_gpuStats << QString("%1|%2|%3|%4|%5")
|
||||
m_gpuStats << QString("%1|%2|%3|%4|%5|%6|%7|%8|%9")
|
||||
.arg(gpu.temperature)
|
||||
.arg(gpu.gpuUsage)
|
||||
.arg(gpu.memUsage)
|
||||
.arg(gpu.memUsed)
|
||||
.arg(gpu.memTotal);
|
||||
.arg(gpu.memTotal)
|
||||
.arg(gpu.gpuClock)
|
||||
.arg(gpu.memClock)
|
||||
.arg(gpu.powerDraw, 0, 'f', 1)
|
||||
.arg(gpu.fanSpeed);
|
||||
}
|
||||
|
||||
emit gpuStatsChanged();
|
||||
|
||||
// 更新温度历史(保留最近 60 个采样点)
|
||||
if (m_tempHistory.size() != m_gpus.size()) {
|
||||
m_tempHistory.resize(m_gpus.size());
|
||||
}
|
||||
for (int i = 0; i < m_gpus.size(); ++i) {
|
||||
m_tempHistory[i].append(m_gpus[i].temperature);
|
||||
if (m_tempHistory[i].size() > 60) {
|
||||
m_tempHistory[i].removeFirst();
|
||||
}
|
||||
}
|
||||
emit tempHistoryChanged();
|
||||
|
||||
// 温度告警检测
|
||||
checkTempAlert();
|
||||
|
||||
// 读取 NVIDIA 进程列表
|
||||
readGpuProcesses();
|
||||
}
|
||||
|
||||
// 温度告警:检测是否有 GPU 温度超过 80°C,30 秒内不重复告警
|
||||
void GraphicsDriverApplet::checkTempAlert()
|
||||
{
|
||||
if (!m_tempAlertEnabled) return;
|
||||
|
||||
qint64 now = QDateTime::currentSecsSinceEpoch();
|
||||
if (now - m_lastAlertTime < 30) return;
|
||||
|
||||
for (const GpuInfo &gpu : m_gpus) {
|
||||
if (gpu.temperature >= 80) {
|
||||
sendNotification(
|
||||
tr("GPU Temperature Alert"),
|
||||
tr("%1 temperature reached %2°C, please check cooling")
|
||||
.arg(gpu.name)
|
||||
.arg(gpu.temperature)
|
||||
);
|
||||
m_lastAlertTime = now;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 通过 D-Bus 发送桌面通知
|
||||
void GraphicsDriverApplet::sendNotification(const QString &summary, const QString &body)
|
||||
{
|
||||
QDBusInterface notify("org.freedesktop.Notifications",
|
||||
"/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications",
|
||||
QDBusConnection::sessionBus());
|
||||
if (notify.isValid()) {
|
||||
notify.call("Notify", "dde-shell-graphics-driver", 0u,
|
||||
"dialog-warning", summary, body,
|
||||
QStringList(), QVariantMap(), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
// 读取 NVIDIA GPU 上运行的进程列表
|
||||
void GraphicsDriverApplet::readGpuProcesses()
|
||||
{
|
||||
m_gpuProcesses.clear();
|
||||
|
||||
// 仅当存在 NVIDIA GPU 时查询
|
||||
bool hasNvidia = false;
|
||||
for (const GpuInfo &gpu : m_gpus) {
|
||||
if (gpu.driver == "nvidia") { hasNvidia = true; break; }
|
||||
}
|
||||
if (!hasNvidia) {
|
||||
emit gpuProcessesChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
// nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv,noheader,nounits
|
||||
QStringList lines = runCommand("nvidia-smi", {
|
||||
"--query-compute-apps=pid,process_name,used_memory",
|
||||
"--format=csv,noheader,nounits"
|
||||
});
|
||||
for (const QString &line : lines) {
|
||||
QStringList parts = line.split(",");
|
||||
if (parts.size() >= 3) {
|
||||
m_gpuProcesses << QString("%1|%2|%3")
|
||||
.arg(parts[0].trimmed())
|
||||
.arg(parts[1].trimmed())
|
||||
.arg(parts[2].trimmed());
|
||||
}
|
||||
}
|
||||
|
||||
emit gpuProcessesChanged();
|
||||
}
|
||||
|
||||
// GPU 切换:调用 prime-select 切换 PRIME 模式(需要 polkit 提权)
|
||||
bool GraphicsDriverApplet::switchGpu(const QString &mode)
|
||||
{
|
||||
// mode: "nvidia" / "intel" / "on-demand"
|
||||
QString configPath = primeConfigPath();
|
||||
QString backupPath = backupFilePath();
|
||||
QString configDir = QDir::homePath() + "/.config/environment.d";
|
||||
QDir().mkpath(configDir);
|
||||
|
||||
// 保存当前模式到备份文件(用于回退)
|
||||
QString prevMode = primeOffloadEnabled() ? "nvidia" : "on-demand";
|
||||
QFile backupFile(backupPath);
|
||||
if (backupFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QTextStream(&backupFile) << prevMode;
|
||||
backupFile.close();
|
||||
}
|
||||
|
||||
if (mode == "intel") {
|
||||
QFile::remove(configPath);
|
||||
} else if (mode == "nvidia") {
|
||||
QFile file(configPath);
|
||||
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QTextStream(&file) << "__NV_PRIME_RENDER_OFFLOAD=1\n"
|
||||
<< "__GLX_VENDOR_LIBRARY_NAME=nvidia\n";
|
||||
file.close();
|
||||
}
|
||||
} else if (mode == "on-demand") {
|
||||
QFile::remove(configPath);
|
||||
}
|
||||
|
||||
sendNotification(tr("GPU Mode Switched"),
|
||||
tr("Switched to %1 mode. Please re-login to take effect.").arg(mode));
|
||||
|
||||
detectGpuMode();
|
||||
emit primeOffloadEnabledChanged();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GraphicsDriverApplet::confirmSwitchSuccess()
|
||||
{
|
||||
// 用户确认显示正常,删除备份文件
|
||||
QFile::remove(backupFilePath());
|
||||
|
||||
m_switchPending = false;
|
||||
m_restoreCountdown = 0;
|
||||
emit switchPendingChanged();
|
||||
emit restoreCountdownChanged();
|
||||
|
||||
if (m_restoreTimer) {
|
||||
m_restoreTimer->stop();
|
||||
m_restoreTimer->deleteLater();
|
||||
m_restoreTimer = nullptr;
|
||||
}
|
||||
|
||||
qDebug() << "GPU switch confirmed by user";
|
||||
}
|
||||
|
||||
void GraphicsDriverApplet::restoreSwitch()
|
||||
{
|
||||
// 用户手动请求回退
|
||||
restoreFromBackup();
|
||||
}
|
||||
|
||||
void GraphicsDriverApplet::restoreFromBackup()
|
||||
{
|
||||
// 从备份文件恢复
|
||||
QFile backupFile(backupFilePath());
|
||||
if (!backupFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
m_switchPending = false;
|
||||
emit switchPendingChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
QString prevMode = QTextStream(&backupFile).readAll().trimmed();
|
||||
backupFile.close();
|
||||
QFile::remove(backupFilePath());
|
||||
|
||||
QString configPath = primeConfigPath();
|
||||
|
||||
if (prevMode == "nvidia") {
|
||||
QFile file(configPath);
|
||||
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QTextStream(&file) << "__NV_PRIME_RENDER_OFFLOAD=1\n"
|
||||
<< "__GLX_VENDOR_LIBRARY_NAME=nvidia\n";
|
||||
file.close();
|
||||
}
|
||||
} else {
|
||||
QFile::remove(configPath);
|
||||
}
|
||||
|
||||
m_switchPending = false;
|
||||
m_restoreCountdown = 0;
|
||||
emit switchPendingChanged();
|
||||
emit restoreCountdownChanged();
|
||||
emit primeOffloadEnabledChanged();
|
||||
|
||||
if (m_restoreTimer) {
|
||||
m_restoreTimer->stop();
|
||||
m_restoreTimer->deleteLater();
|
||||
m_restoreTimer = nullptr;
|
||||
}
|
||||
|
||||
sendNotification(tr("GPU Mode Restored"),
|
||||
tr("Reverted to %1 mode. Please re-login to take effect.").arg(prevMode));
|
||||
|
||||
qDebug() << "GPU mode restored to" << prevMode;
|
||||
}
|
||||
|
||||
D_APPLET_CLASS(GraphicsDriverApplet)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QTimer>
|
||||
#include <QVariantList>
|
||||
|
||||
DS_BEGIN_NAMESPACE
|
||||
|
||||
@@ -18,6 +19,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 表示不可用
|
||||
@@ -25,6 +27,10 @@ struct GpuInfo {
|
||||
int memUsage; // 显存使用率 (%),-1 表示不可用
|
||||
int memTotal; // 显存总量 (MB),-1 表示不可用
|
||||
int memUsed; // 显存已用 (MB),-1 表示不可用
|
||||
int gpuClock; // GPU 频率 (MHz),-1 表示不可用
|
||||
int memClock; // 显存频率 (MHz),-1 表示不可用
|
||||
double powerDraw; // 功耗 (W),-1 表示不可用
|
||||
int fanSpeed; // 风扇转速 (%),-1 表示不可用
|
||||
};
|
||||
|
||||
class GraphicsDriverApplet : public DApplet
|
||||
@@ -35,7 +41,15 @@ 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)
|
||||
Q_PROPERTY(QVariantList tempHistory READ tempHistory NOTIFY tempHistoryChanged)
|
||||
Q_PROPERTY(QStringList gpuProcesses READ gpuProcesses NOTIFY gpuProcessesChanged)
|
||||
Q_PROPERTY(bool tempAlertEnabled READ tempAlertEnabled WRITE setTempAlertEnabled NOTIFY tempAlertEnabledChanged)
|
||||
Q_PROPERTY(bool primeOffloadEnabled READ primeOffloadEnabled NOTIFY primeOffloadEnabledChanged)
|
||||
Q_PROPERTY(bool switchPending READ switchPending NOTIFY switchPendingChanged)
|
||||
Q_PROPERTY(int restoreCountdown READ restoreCountdown NOTIFY restoreCountdownChanged)
|
||||
|
||||
public:
|
||||
explicit GraphicsDriverApplet(QObject *parent = nullptr);
|
||||
@@ -48,21 +62,48 @@ public:
|
||||
QString currentDriver() const;
|
||||
QString gpuSummary() const;
|
||||
QStringList gpuStats() const;
|
||||
QString gpuMode() const;
|
||||
QStringList gpuPrimary() const;
|
||||
bool ready() const;
|
||||
QVariantList tempHistory() const;
|
||||
QStringList gpuProcesses() const;
|
||||
bool tempAlertEnabled() const;
|
||||
void setTempAlertEnabled(bool enabled);
|
||||
bool primeOffloadEnabled() const;
|
||||
bool switchPending() const;
|
||||
int restoreCountdown() const;
|
||||
|
||||
Q_INVOKABLE void refreshDeviceInfo();
|
||||
Q_INVOKABLE void refreshStats();
|
||||
Q_INVOKABLE bool switchGpu(const QString &mode);
|
||||
Q_INVOKABLE void confirmSwitchSuccess();
|
||||
Q_INVOKABLE void restoreSwitch();
|
||||
|
||||
signals:
|
||||
void deviceInfoChanged();
|
||||
void currentDriverChanged();
|
||||
void gpuSummaryChanged();
|
||||
void gpuStatsChanged();
|
||||
void gpuModeChanged();
|
||||
void gpuPrimaryChanged();
|
||||
void readyChanged();
|
||||
void tempHistoryChanged();
|
||||
void gpuProcessesChanged();
|
||||
void tempAlertEnabledChanged();
|
||||
void primeOffloadEnabledChanged();
|
||||
void switchPendingChanged();
|
||||
void restoreCountdownChanged();
|
||||
|
||||
private:
|
||||
void detectGpus();
|
||||
void readGpuStats();
|
||||
void readGpuProcesses();
|
||||
void detectGpuMode();
|
||||
void checkTempAlert();
|
||||
void sendNotification(const QString &summary, const QString &body);
|
||||
void restoreFromBackup();
|
||||
static QString primeConfigPath();
|
||||
static QString backupFilePath();
|
||||
static int findDrmCard(const QString &pciId);
|
||||
static QStringList runCommand(const QString &cmd, const QStringList &args);
|
||||
static QString parseGpuName(const QString &line);
|
||||
@@ -73,7 +114,19 @@ private:
|
||||
QString m_currentDriver;
|
||||
QString m_gpuSummary;
|
||||
QStringList m_gpuStats;
|
||||
QString m_gpuMode;
|
||||
QStringList m_gpuPrimary;
|
||||
bool m_ready;
|
||||
// 温度历史:每个 GPU 保留最近 60 个温度采样
|
||||
QVector<QList<int>> m_tempHistory;
|
||||
// NVIDIA 进程列表:每行 "pid|name|usedMemory"
|
||||
QStringList m_gpuProcesses;
|
||||
// 温度告警冷却时间戳
|
||||
qint64 m_lastAlertTime;
|
||||
bool m_tempAlertEnabled;
|
||||
bool m_switchPending;
|
||||
int m_restoreCountdown;
|
||||
QTimer *m_restoreTimer;
|
||||
};
|
||||
|
||||
DS_END_NAMESPACE
|
||||
|
||||
+999
-93
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="en_US">
|
||||
<context>
|
||||
<name>GraphicsDriverApplet</name>
|
||||
<message>
|
||||
<location filename="../graphicsdriverapplet.cpp" line="564"/>
|
||||
<source>GPU Temperature Alert</source>
|
||||
<translation>GPU Temperature Alert</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../graphicsdriverapplet.cpp" line="565"/>
|
||||
<source>%1 temperature reached %2°C, please check cooling</source>
|
||||
<translation>%1 temperature reached %2°C, please check cooling</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>driverview</name>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="23"/>
|
||||
<location filename="../package/driverview.qml" line="77"/>
|
||||
<location filename="../package/driverview.qml" line="615"/>
|
||||
<location filename="../package/driverview.qml" line="830"/>
|
||||
<source>No GPU detected</source>
|
||||
<translation>No GPU detected</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="24"/>
|
||||
<location filename="../package/driverview.qml" line="25"/>
|
||||
<location filename="../package/driverview.qml" line="364"/>
|
||||
<location filename="../package/driverview.qml" line="802"/>
|
||||
<source>Unknown</source>
|
||||
<translation>Unknown</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="175"/>
|
||||
<source>Graphics Driver Manager</source>
|
||||
<translation>Graphics Driver Manager</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="198"/>
|
||||
<source>Current Driver:</source>
|
||||
<translation>Current Driver:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="259"/>
|
||||
<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 mode: the integrated GPU handles display, the discrete GPU renders on demand. 3D apps use PRIME Render Offload to invoke the discrete GPU.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="261"/>
|
||||
<source>Bumblebee mode: a legacy dual-GPU solution, now deprecated.</source>
|
||||
<translation>Bumblebee mode: a legacy dual-GPU solution, now deprecated.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="263"/>
|
||||
<source>Solo mode: only one GPU detected.</source>
|
||||
<translation>Solo mode: only one GPU detected.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="265"/>
|
||||
<source>Hybrid mode: dual GPU detected, but no known switching solution found.</source>
|
||||
<translation>Hybrid mode: dual GPU detected, but no known switching solution found.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="362"/>
|
||||
<source>GPU: </source>
|
||||
<translation>GPU: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="363"/>
|
||||
<source>Driver: </source>
|
||||
<translation>Driver: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="364"/>
|
||||
<source>Version: </source>
|
||||
<translation>Version: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="426"/>
|
||||
<source>Primary</source>
|
||||
<translation>Primary</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="527"/>
|
||||
<source>Fan</source>
|
||||
<translation>Fan</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="640"/>
|
||||
<source>GPU Processes</source>
|
||||
<translation>GPU Processes</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="681"/>
|
||||
<source>NVIDIA</source>
|
||||
<translation>NVIDIA</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="702"/>
|
||||
<source>Intel</source>
|
||||
<translation>Intel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="723"/>
|
||||
<source>On-Demand</source>
|
||||
<translation>On-Demand</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="744"/>
|
||||
<location filename="../package/driverview.qml" line="885"/>
|
||||
<source>Refresh</source>
|
||||
<translation>Refresh</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="892"/>
|
||||
<source>Switch to NVIDIA</source>
|
||||
<translation>Switch to NVIDIA</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="900"/>
|
||||
<source>Switch to Intel</source>
|
||||
<translation>Switch to Intel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="908"/>
|
||||
<source>Switch to On-Demand</source>
|
||||
<translation>Switch to On-Demand</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
@@ -1,47 +1,237 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="zh_CN">
|
||||
<context>
|
||||
<name>GraphicsDriverApplet</name>
|
||||
<message>
|
||||
<location filename="../graphicsdriverapplet.cpp" line="564"/>
|
||||
<source>GPU Temperature Alert</source>
|
||||
<translation>GPU 温度告警</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../graphicsdriverapplet.cpp" line="565"/>
|
||||
<source>%1 temperature reached %2°C, please check cooling</source>
|
||||
<translation>%1 温度已达 %2°C,请检查散热</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>driverview</name>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<location filename="../package/driverview.qml" line="24"/>
|
||||
<location filename="../package/driverview.qml" line="25"/>
|
||||
<location filename="../package/driverview.qml" line="364"/>
|
||||
<location filename="../package/driverview.qml" line="802"/>
|
||||
<source>Unknown</source>
|
||||
<translation>未知</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<location filename="../package/driverview.qml" line="23"/>
|
||||
<location filename="../package/driverview.qml" line="77"/>
|
||||
<location filename="../package/driverview.qml" line="615"/>
|
||||
<location filename="../package/driverview.qml" line="830"/>
|
||||
<source>No GPU detected</source>
|
||||
<translation>未检测到显卡</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<location filename="../package/driverview.qml" line="175"/>
|
||||
<source>Graphics Driver Manager</source>
|
||||
<translation>显卡驱动管理</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<location filename="../package/driverview.qml" line="198"/>
|
||||
<source>Current Driver:</source>
|
||||
<translation>当前驱动:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<location filename="../package/driverview.qml" line="640"/>
|
||||
<source>GPU Processes</source>
|
||||
<translation>GPU 进程</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="681"/>
|
||||
<source>NVIDIA</source>
|
||||
<translation>NVIDIA</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="702"/>
|
||||
<source>Intel</source>
|
||||
<translation>Intel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="723"/>
|
||||
<source>On-Demand</source>
|
||||
<translation>按需</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="744"/>
|
||||
<location filename="../package/driverview.qml" line="885"/>
|
||||
<source>Refresh</source>
|
||||
<translation>刷新</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<location filename="../package/driverview.qml" line="892"/>
|
||||
<source>Switch to NVIDIA</source>
|
||||
<translation>切换到 NVIDIA</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="900"/>
|
||||
<source>Switch to Intel</source>
|
||||
<translation>切换到 Intel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="908"/>
|
||||
<source>Switch to On-Demand</source>
|
||||
<translation>切换到按需模式</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="362"/>
|
||||
<source>GPU: </source>
|
||||
<translation>显卡名称:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<location filename="../package/driverview.qml" line="363"/>
|
||||
<source>Driver: </source>
|
||||
<translation>驱动:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<location filename="../package/driverview.qml" line="364"/>
|
||||
<source>Version: </source>
|
||||
<translation>版本号:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="426"/>
|
||||
<source>Primary</source>
|
||||
<translation>主显</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="259"/>
|
||||
<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" line="261"/>
|
||||
<source>Bumblebee mode: a legacy dual-GPU solution, now deprecated.</source>
|
||||
<translation>Bumblebee 模式:旧版双 GPU 方案,已淘汰。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="263"/>
|
||||
<source>Solo mode: only one GPU detected.</source>
|
||||
<translation>Solo 模式:仅检测到一块 GPU。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="265"/>
|
||||
<source>Hybrid mode: dual GPU detected, but no known switching solution found.</source>
|
||||
<translation>Hybrid 模式:检测到双 GPU,但未发现已知切换方案。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="527"/>
|
||||
<source>Fan</source>
|
||||
<translation>风扇</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Settings</source>
|
||||
<translation>设置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>General Settings</source>
|
||||
<translation>常规设置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Show notification when GPU temperature exceeds threshold</source>
|
||||
<translation>GPU 温度超过阈值时显示通知</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Alert Temperature Threshold</source>
|
||||
<translation>告警温度阈值</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Temperature Trend</source>
|
||||
<translation>温度趋势</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Switch to</source>
|
||||
<translation>切换到</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Switch to NVIDIA discrete GPU for maximum performance. Higher power consumption.</source>
|
||||
<translation>切换到 NVIDIA 独显以获得最高性能,功耗较高。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Switch to integrated GPU for power saving. NVIDIA GPU will be disabled.</source>
|
||||
<translation>切换到核显以节省功耗,NVIDIA 独显将被禁用。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Hybrid mode: integrated GPU for display, NVIDIA for on-demand rendering.</source>
|
||||
<translation>混合模式:核显负责显示,NVIDIA 按需渲染。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Auto-cancel if not confirmed</source>
|
||||
<translation>未确认将自动取消</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Cancel</source>
|
||||
<translation>取消</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Confirm</source>
|
||||
<translation>确认</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Integrated</source>
|
||||
<translation>核显</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Display OK?</source>
|
||||
<translation>显示是否正常?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>GPU mode was changed. If display is abnormal, it will auto-revert in %1s.</source>
|
||||
<translation>GPU 模式已切换。如果显示异常,将在 %1 秒后自动回退。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Revert</source>
|
||||
<translation>回退</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Display OK</source>
|
||||
<translation>显示正常</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>GPU Mode Switched</source>
|
||||
<translation>GPU 模式已切换</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Switched to %1 mode. Please re-login to take effect.</source>
|
||||
<translation>已切换到 %1 模式,请重新登录生效。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>GPU Mode Restored</source>
|
||||
<translation>GPU 模式已恢复</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Reverted to %1 mode. Please re-login to take effect.</source>
|
||||
<translation>已恢复到 %1 模式,请重新登录生效。</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
||||
@@ -1,47 +1,237 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="zh_TW">
|
||||
<context>
|
||||
<name>GraphicsDriverApplet</name>
|
||||
<message>
|
||||
<location filename="../graphicsdriverapplet.cpp" line="564"/>
|
||||
<source>GPU Temperature Alert</source>
|
||||
<translation>GPU 溫度警報</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../graphicsdriverapplet.cpp" line="565"/>
|
||||
<source>%1 temperature reached %2°C, please check cooling</source>
|
||||
<translation>%1 溫度已達 %2°C,請檢查散熱</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>driverview</name>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<location filename="../package/driverview.qml" line="24"/>
|
||||
<location filename="../package/driverview.qml" line="25"/>
|
||||
<location filename="../package/driverview.qml" line="364"/>
|
||||
<location filename="../package/driverview.qml" line="802"/>
|
||||
<source>Unknown</source>
|
||||
<translation>未知</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<location filename="../package/driverview.qml" line="23"/>
|
||||
<location filename="../package/driverview.qml" line="77"/>
|
||||
<location filename="../package/driverview.qml" line="615"/>
|
||||
<location filename="../package/driverview.qml" line="830"/>
|
||||
<source>No GPU detected</source>
|
||||
<translation>未偵測到顯示卡</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<location filename="../package/driverview.qml" line="175"/>
|
||||
<source>Graphics Driver Manager</source>
|
||||
<translation>顯示卡驅動管理</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<location filename="../package/driverview.qml" line="198"/>
|
||||
<source>Current Driver:</source>
|
||||
<translation>目前驅動:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<location filename="../package/driverview.qml" line="640"/>
|
||||
<source>GPU Processes</source>
|
||||
<translation>GPU 行程</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="681"/>
|
||||
<source>NVIDIA</source>
|
||||
<translation>NVIDIA</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="702"/>
|
||||
<source>Intel</source>
|
||||
<translation>Intel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="723"/>
|
||||
<source>On-Demand</source>
|
||||
<translation>按需</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="744"/>
|
||||
<location filename="../package/driverview.qml" line="885"/>
|
||||
<source>Refresh</source>
|
||||
<translation>重新整理</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<location filename="../package/driverview.qml" line="892"/>
|
||||
<source>Switch to NVIDIA</source>
|
||||
<translation>切換到 NVIDIA</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="900"/>
|
||||
<source>Switch to Intel</source>
|
||||
<translation>切換到 Intel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="908"/>
|
||||
<source>Switch to On-Demand</source>
|
||||
<translation>切換到按需模式</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="362"/>
|
||||
<source>GPU: </source>
|
||||
<translation>顯示卡名稱:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<location filename="../package/driverview.qml" line="363"/>
|
||||
<source>Driver: </source>
|
||||
<translation>驅動:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<location filename="../package/driverview.qml" line="364"/>
|
||||
<source>Version: </source>
|
||||
<translation>版本號:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="426"/>
|
||||
<source>Primary</source>
|
||||
<translation>主顯</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="259"/>
|
||||
<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" line="261"/>
|
||||
<source>Bumblebee mode: a legacy dual-GPU solution, now deprecated.</source>
|
||||
<translation>Bumblebee 模式:舊版雙 GPU 方案,已淘汰。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="263"/>
|
||||
<source>Solo mode: only one GPU detected.</source>
|
||||
<translation>Solo 模式:僅偵測到一塊 GPU。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="265"/>
|
||||
<source>Hybrid mode: dual GPU detected, but no known switching solution found.</source>
|
||||
<translation>Hybrid 模式:偵測到雙 GPU,但未發現已知切換方案。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml" line="527"/>
|
||||
<source>Fan</source>
|
||||
<translation>風扇</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Settings</source>
|
||||
<translation>設定</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>General Settings</source>
|
||||
<translation>一般設定</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Show notification when GPU temperature exceeds threshold</source>
|
||||
<translation>GPU 溫度超過閾值時顯示通知</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Alert Temperature Threshold</source>
|
||||
<translation>告警溫度閾值</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Temperature Trend</source>
|
||||
<translation>溫度趨勢</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Switch to</source>
|
||||
<translation>切換到</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Switch to NVIDIA discrete GPU for maximum performance. Higher power consumption.</source>
|
||||
<translation>切換到 NVIDIA 獨顯以獲得最高性能,功耗較高。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Switch to integrated GPU for power saving. NVIDIA GPU will be disabled.</source>
|
||||
<translation>切換到核顯以節省功耗,NVIDIA 獨顯將被禁用。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Hybrid mode: integrated GPU for display, NVIDIA for on-demand rendering.</source>
|
||||
<translation>混合模式:核顯負責顯示,NVIDIA 按需渲染。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Auto-cancel if not confirmed</source>
|
||||
<translation>未確認將自動取消</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Cancel</source>
|
||||
<translation>取消</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Confirm</source>
|
||||
<translation>確認</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Integrated</source>
|
||||
<translation>核顯</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Display OK?</source>
|
||||
<translation>顯示是否正常?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>GPU mode was changed. If display is abnormal, it will auto-revert in %1s.</source>
|
||||
<translation>GPU 模式已切換。如果顯示異常,將在 %1 秒後自動回退。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Revert</source>
|
||||
<translation>回退</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Display OK</source>
|
||||
<translation>顯示正常</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>GPU Mode Switched</source>
|
||||
<translation>GPU 模式已切換</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Switched to %1 mode. Please re-login to take effect.</source>
|
||||
<translation>已切換到 %1 模式,請重新登入生效。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>GPU Mode Restored</source>
|
||||
<translation>GPU 模式已恢復</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../package/driverview.qml"/>
|
||||
<source>Reverted to %1 mode. Please re-login to take effect.</source>
|
||||
<translation>已恢復到 %1 模式,請重新登入生效。</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
||||
Reference in New Issue
Block a user