feat: 添加GPU切换模式检测和主副显标识

- C++后端:GpuInfo添加isPrimary字段,读取boot_vga判断主副显
- C++后端:detectGpuMode()检测PRIME/Bumblebee/Solo/Hybrid模式
- C++后端:新增gpuMode和gpuPrimary属性
- QML前端:卡片显示绿色主显标签(boot_vga=1的GPU)
- QML前端:弹窗显示GPU模式标签(PRIME等)和帮助图标
- 帮助图标悬停显示模式说明文字
- 翻译更新:主显/主顯 + 四种模式的中文说明
This commit is contained in:
2026-07-14 00:19:56 +08:00
parent 403ae8a69b
commit d5a2c380ab
5 changed files with 216 additions and 6 deletions
+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;
};
+84
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,6 +361,10 @@ AppletItem {
spacing: 4
Layout.fillWidth: true
RowLayout {
spacing: 6
Layout.fillWidth: true
Text {
text: parseGpuName(modelData)
font.pixelSize: 13
@@ -309,6 +374,25 @@ AppletItem {
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 {
text: parseDriverInfo(modelData)
font.pixelSize: 11
@@ -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>