feat: 移除D-Bus依赖,改用 lspci 直接检测显卡,重构弹窗UI
- C++ 后端:移除 D-Bus 通信,改用 lspci -mm + /sys/bus/pci 直接检测显卡和驱动 - 新增 gpuSummary、ready 属性,通过 /sys/module 和 /proc/driver 读取驱动版本 - QML:使用 DTK 配色,卡片式布局展示 GPU 列表,每张显卡独立卡片 - 移除安装/测试/取消按钮,聚焦显卡信息展示 - 翻译精简为 5 条,同步 zh_CN / zh_TW
This commit is contained in:
+141
-207
@@ -5,29 +5,20 @@
|
|||||||
#include "graphicsdriverapplet.h"
|
#include "graphicsdriverapplet.h"
|
||||||
#include <pluginfactory.h>
|
#include <pluginfactory.h>
|
||||||
|
|
||||||
#include <QDBusConnection>
|
#include <QProcess>
|
||||||
#include <QDBusConnectionInterface>
|
#include <QDir>
|
||||||
#include <QDBusPendingReply>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QJsonDocument>
|
#include <QFile>
|
||||||
#include <QJsonObject>
|
#include <QTextStream>
|
||||||
#include <QJsonArray>
|
#include <QRegularExpression>
|
||||||
|
|
||||||
DCORE_USE_NAMESPACE
|
DCORE_USE_NAMESPACE
|
||||||
|
|
||||||
DS_BEGIN_NAMESPACE
|
DS_BEGIN_NAMESPACE
|
||||||
|
|
||||||
static const QString DBUS_SERVICE = "com.deepin.daemon.GraphicsDriver";
|
|
||||||
static const QString DBUS_PATH = "/com/deepin/daemon/GraphicsDriver";
|
|
||||||
static const QString DBUS_INTERFACE = "com.deepin.daemon.GraphicsDriver";
|
|
||||||
|
|
||||||
GraphicsDriverApplet::GraphicsDriverApplet(QObject *parent)
|
GraphicsDriverApplet::GraphicsDriverApplet(QObject *parent)
|
||||||
: DApplet(parent)
|
: DApplet(parent)
|
||||||
, m_dbusInterface(nullptr)
|
, m_ready(false)
|
||||||
, m_progressTimer(new QTimer(this))
|
|
||||||
, m_installProgress(0)
|
|
||||||
, m_isInstalling(false)
|
|
||||||
, m_dbusAvailable(false)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,233 +28,176 @@ GraphicsDriverApplet::~GraphicsDriverApplet()
|
|||||||
|
|
||||||
bool GraphicsDriverApplet::load()
|
bool GraphicsDriverApplet::load()
|
||||||
{
|
{
|
||||||
initDBusConnection();
|
|
||||||
return DApplet::load();
|
return DApplet::load();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GraphicsDriverApplet::init()
|
bool GraphicsDriverApplet::init()
|
||||||
{
|
{
|
||||||
if (m_dbusAvailable) {
|
detectGpus();
|
||||||
refreshDeviceInfo();
|
|
||||||
}
|
|
||||||
return DApplet::init();
|
return DApplet::init();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString GraphicsDriverApplet::currentDriver() const
|
|
||||||
{
|
|
||||||
return m_currentDriver;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString GraphicsDriverApplet::deviceInfo() const
|
QString GraphicsDriverApplet::deviceInfo() const
|
||||||
{
|
{
|
||||||
return m_deviceInfo;
|
return m_deviceInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GraphicsDriverApplet::installProgress() const
|
QString GraphicsDriverApplet::currentDriver() const
|
||||||
{
|
{
|
||||||
return m_installProgress;
|
return m_currentDriver;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GraphicsDriverApplet::isInstalling() const
|
QString GraphicsDriverApplet::gpuSummary() const
|
||||||
{
|
{
|
||||||
return m_isInstalling;
|
return m_gpuSummary;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GraphicsDriverApplet::dbusAvailable() const
|
bool GraphicsDriverApplet::ready() const
|
||||||
{
|
{
|
||||||
return m_dbusAvailable;
|
return m_ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsDriverApplet::refreshDeviceInfo()
|
void GraphicsDriverApplet::refreshDeviceInfo()
|
||||||
{
|
{
|
||||||
if (!m_dbusInterface || !m_dbusInterface->isValid()) {
|
detectGpus();
|
||||||
qWarning() << "DBus interface is not valid";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get device info
|
|
||||||
QDBusPendingCall deviceCall = m_dbusInterface->asyncCall("GetDevice");
|
|
||||||
QDBusPendingCallWatcher *deviceWatcher = new QDBusPendingCallWatcher(deviceCall, this);
|
|
||||||
connect(deviceWatcher, &QDBusPendingCallWatcher::finished, this, &GraphicsDriverApplet::onGetDeviceReply);
|
|
||||||
|
|
||||||
// Get current driver
|
|
||||||
QDBusPendingCall currCall = m_dbusInterface->asyncCall("GetCurrDriverName");
|
|
||||||
QDBusPendingCallWatcher *currWatcher = new QDBusPendingCallWatcher(currCall, this);
|
|
||||||
connect(currWatcher, &QDBusPendingCallWatcher::finished, this, &GraphicsDriverApplet::onGetCurrDriverReply);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsDriverApplet::prepareInstall(const QString &driverName)
|
void GraphicsDriverApplet::detectGpus()
|
||||||
{
|
{
|
||||||
if (!m_dbusInterface || !m_dbusInterface->isValid()) {
|
m_gpus.clear();
|
||||||
emit installFailed("DBus interface is not valid");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QLocale locale;
|
// 通过 lspci 检测显卡
|
||||||
QString language = locale.name();
|
QStringList lines = runCommand("lspci", {"-mm"});
|
||||||
|
for (const QString &line : lines) {
|
||||||
|
if (line.contains("VGA compatible controller", Qt::CaseInsensitive) ||
|
||||||
|
line.contains("3D controller", Qt::CaseInsensitive) ||
|
||||||
|
line.contains("Display controller", Qt::CaseInsensitive)) {
|
||||||
|
|
||||||
QDBusPendingCall call = m_dbusInterface->asyncCall("PrepareInstall", driverName, language);
|
GpuInfo gpu;
|
||||||
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
|
// lspci -mm 格式: "Class" "Vendor" "Device" ...
|
||||||
connect(watcher, &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *call) {
|
// 提取 PCI ID(行首的 01:00.0 等)
|
||||||
QDBusPendingReply<> reply = *call;
|
int spaceIdx = line.indexOf(' ');
|
||||||
if (reply.isError()) {
|
if (spaceIdx > 0) {
|
||||||
qWarning() << "Failed to prepare install:" << reply.error().message();
|
gpu.pciId = line.left(spaceIdx);
|
||||||
emit installFailed(reply.error().message());
|
}
|
||||||
} else {
|
|
||||||
qDebug() << "Prepare install succeeded";
|
|
||||||
}
|
|
||||||
call->deleteLater();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphicsDriverApplet::testInstall()
|
gpu.name = parseGpuName(line);
|
||||||
{
|
|
||||||
if (!m_dbusInterface || !m_dbusInterface->isValid()) {
|
|
||||||
emit installFailed("DBus interface is not valid");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setInstalling(true);
|
// 检测驱动
|
||||||
|
QString sysPath = "/sys/bus/pci/devices/0000:" + gpu.pciId + "/driver";
|
||||||
|
QDir driverDir(sysPath);
|
||||||
|
if (driverDir.exists()) {
|
||||||
|
gpu.driver = driverDir.dirName();
|
||||||
|
|
||||||
QDBusPendingCall call = m_dbusInterface->asyncCall("TestInstall");
|
// 读取驱动版本
|
||||||
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
|
gpu.driverVersion = readDriverVersion(gpu.driver);
|
||||||
connect(watcher, &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *call) {
|
} else {
|
||||||
QDBusPendingReply<> reply = *call;
|
gpu.driver = "unknown";
|
||||||
if (reply.isError()) {
|
}
|
||||||
qWarning() << "Failed to test install:" << reply.error().message();
|
|
||||||
emit installFailed(reply.error().message());
|
|
||||||
setInstalling(false);
|
|
||||||
} else {
|
|
||||||
qDebug() << "Test install succeeded";
|
|
||||||
realInstall();
|
|
||||||
}
|
|
||||||
call->deleteLater();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphicsDriverApplet::realInstall()
|
m_gpus.append(gpu);
|
||||||
{
|
|
||||||
if (!m_dbusInterface || !m_dbusInterface->isValid()) {
|
|
||||||
emit installFailed("DBus interface is not valid");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDBusPendingCall call = m_dbusInterface->asyncCall("RealInstall");
|
|
||||||
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
|
|
||||||
connect(watcher, &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *call) {
|
|
||||||
QDBusPendingReply<> reply = *call;
|
|
||||||
if (reply.isError()) {
|
|
||||||
qWarning() << "Failed to real install:" << reply.error().message();
|
|
||||||
emit installFailed(reply.error().message());
|
|
||||||
setInstalling(false);
|
|
||||||
} else {
|
|
||||||
qDebug() << "Real install succeeded";
|
|
||||||
setInstalling(false);
|
|
||||||
emit installSuccess();
|
|
||||||
emit requestReboot();
|
|
||||||
}
|
|
||||||
call->deleteLater();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphicsDriverApplet::cancelInstall()
|
|
||||||
{
|
|
||||||
if (!m_dbusInterface || !m_dbusInterface->isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_dbusInterface->asyncCall("CancelInstall");
|
|
||||||
setInstalling(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphicsDriverApplet::onGetDeviceReply(QDBusPendingCallWatcher *call)
|
|
||||||
{
|
|
||||||
QDBusPendingReply<QString> reply = *call;
|
|
||||||
if (reply.isError()) {
|
|
||||||
qWarning() << "Failed to get device:" << reply.error().message();
|
|
||||||
} else {
|
|
||||||
m_deviceInfo = reply.value();
|
|
||||||
qDebug() << "Device info:" << m_deviceInfo;
|
|
||||||
emit deviceInfoChanged();
|
|
||||||
}
|
|
||||||
call->deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphicsDriverApplet::onGetCurrDriverReply(QDBusPendingCallWatcher *call)
|
|
||||||
{
|
|
||||||
QDBusPendingReply<QString> reply = *call;
|
|
||||||
if (reply.isError()) {
|
|
||||||
qWarning() << "Failed to get current driver:" << reply.error().message();
|
|
||||||
} else {
|
|
||||||
m_currentDriver = reply.value();
|
|
||||||
qDebug() << "Current driver:" << m_currentDriver;
|
|
||||||
emit currentDriverChanged();
|
|
||||||
}
|
|
||||||
call->deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphicsDriverApplet::onReportProgress(const QString &ratio)
|
|
||||||
{
|
|
||||||
bool ok;
|
|
||||||
int progress = ratio.toInt(&ok);
|
|
||||||
if (ok) {
|
|
||||||
m_installProgress = progress;
|
|
||||||
emit installProgressChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphicsDriverApplet::initDBusConnection()
|
|
||||||
{
|
|
||||||
QDBusConnection bus = QDBusConnection::sessionBus();
|
|
||||||
|
|
||||||
if (!bus.isConnected()) {
|
|
||||||
qWarning() << "Cannot connect to D-Bus session bus";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查服务是否已注册
|
|
||||||
bool registered = bus.interface()->isServiceRegistered(DBUS_SERVICE);
|
|
||||||
if (!registered) {
|
|
||||||
qWarning() << "GraphicsDriver D-Bus service is not registered";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_dbusInterface = new QDBusInterface(
|
|
||||||
DBUS_SERVICE,
|
|
||||||
DBUS_PATH,
|
|
||||||
DBUS_INTERFACE,
|
|
||||||
bus,
|
|
||||||
this
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!m_dbusInterface->isValid()) {
|
|
||||||
qWarning() << "DBus interface is not valid:" << m_dbusInterface->lastError().message();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_dbusAvailable = true;
|
|
||||||
emit dbusAvailableChanged();
|
|
||||||
|
|
||||||
connect(m_dbusInterface, SIGNAL(ReportProgress(QString)),
|
|
||||||
this, SLOT(onReportProgress(QString)));
|
|
||||||
|
|
||||||
qDebug() << "GraphicsDriver D-Bus connected successfully";
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphicsDriverApplet::setInstalling(bool installing)
|
|
||||||
{
|
|
||||||
if (m_isInstalling != installing) {
|
|
||||||
m_isInstalling = installing;
|
|
||||||
emit isInstallingChanged();
|
|
||||||
|
|
||||||
if (installing) {
|
|
||||||
m_progressTimer->start(1000);
|
|
||||||
} else {
|
|
||||||
m_progressTimer->stop();
|
|
||||||
m_installProgress = 0;
|
|
||||||
emit installProgressChanged();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成摘要信息
|
||||||
|
QStringList parts;
|
||||||
|
QStringList driverParts;
|
||||||
|
for (const GpuInfo &gpu : m_gpus) {
|
||||||
|
QString info = gpu.name;
|
||||||
|
if (!gpu.driver.isEmpty() && gpu.driver != "unknown") {
|
||||||
|
info += " (" + gpu.driver;
|
||||||
|
if (!gpu.driverVersion.isEmpty()) {
|
||||||
|
info += " " + gpu.driverVersion;
|
||||||
|
}
|
||||||
|
info += ")";
|
||||||
|
}
|
||||||
|
parts << info;
|
||||||
|
if (!gpu.driver.isEmpty() && !driverParts.contains(gpu.driver)) {
|
||||||
|
driverParts << gpu.driver;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_deviceInfo = parts.join("\n");
|
||||||
|
m_currentDriver = driverParts.join(", ");
|
||||||
|
m_gpuSummary = parts.join(" | ");
|
||||||
|
m_ready = !m_gpus.isEmpty();
|
||||||
|
|
||||||
|
qDebug() << "Detected GPUs:" << m_deviceInfo;
|
||||||
|
qDebug() << "Current drivers:" << m_currentDriver;
|
||||||
|
|
||||||
|
emit deviceInfoChanged();
|
||||||
|
emit currentDriverChanged();
|
||||||
|
emit gpuSummaryChanged();
|
||||||
|
emit readyChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList GraphicsDriverApplet::runCommand(const QString &cmd, const QStringList &args)
|
||||||
|
{
|
||||||
|
QProcess process;
|
||||||
|
process.start(cmd, args);
|
||||||
|
process.waitForFinished(5000);
|
||||||
|
QString output = QString::fromUtf8(process.readAllStandardOutput());
|
||||||
|
return output.split('\n', Qt::SkipEmptyParts);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString GraphicsDriverApplet::parseGpuName(const QString &line)
|
||||||
|
{
|
||||||
|
// lspci -mm 格式示例:
|
||||||
|
// 01:00.0 "VGA compatible controller" "NVIDIA Corporation" "GA106M [GeForce RTX 3060 Mobile / Max-Q]" "ASUSTeK Computer Inc." "Device 10a2"
|
||||||
|
// 提取引号中的内容
|
||||||
|
QStringList parts;
|
||||||
|
int pos = 0;
|
||||||
|
while (pos < line.length()) {
|
||||||
|
int start = line.indexOf('"', pos);
|
||||||
|
if (start < 0) break;
|
||||||
|
int end = line.indexOf('"', start + 1);
|
||||||
|
if (end < 0) break;
|
||||||
|
parts << line.mid(start + 1, end - start - 1);
|
||||||
|
pos = end + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parts.size() >= 3) {
|
||||||
|
// parts[0] = class, parts[1] = vendor, parts[2] = device
|
||||||
|
QString vendor = parts[1];
|
||||||
|
QString device = parts[2];
|
||||||
|
// 去掉方括号内的别名,保留主要名称
|
||||||
|
int bracket = device.indexOf('[');
|
||||||
|
if (bracket > 0) {
|
||||||
|
// 保留完整名称,方括号内是子系统信息
|
||||||
|
}
|
||||||
|
return vendor + " " + device;
|
||||||
|
}
|
||||||
|
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString GraphicsDriverApplet::readDriverVersion(const QString &driverName)
|
||||||
|
{
|
||||||
|
// 尝试从 /sys/module/<driver>/version 读取版本
|
||||||
|
QString path = "/sys/module/" + driverName + "/version";
|
||||||
|
QFile file(path);
|
||||||
|
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
QString version = QTextStream(&file).readAll().trimmed();
|
||||||
|
if (!version.isEmpty()) {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// nvidia 特殊处理:从 /proc/driver/nvidia/version 读取
|
||||||
|
if (driverName == "nvidia") {
|
||||||
|
QFile nvFile("/proc/driver/nvidia/version");
|
||||||
|
if (nvFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
QString content = QTextStream(&nvFile).readAll();
|
||||||
|
// 格式: NVRM version: NVIDIA UNIX x86_64 Kernel Module 535.183.01 ...
|
||||||
|
QRegularExpression re("Kernel Module\\s+(\\d+\\.\\d+\\.\\d+)");
|
||||||
|
auto match = re.match(content);
|
||||||
|
if (match.hasMatch()) {
|
||||||
|
return match.captured(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
D_APPLET_CLASS(GraphicsDriverApplet)
|
D_APPLET_CLASS(GraphicsDriverApplet)
|
||||||
|
|||||||
+26
-37
@@ -6,23 +6,27 @@
|
|||||||
|
|
||||||
#include <applet.h>
|
#include <applet.h>
|
||||||
|
|
||||||
#include <QDBusInterface>
|
#include <QString>
|
||||||
#include <QDBusPendingCallWatcher>
|
#include <QStringList>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QJsonArray>
|
|
||||||
|
|
||||||
DS_BEGIN_NAMESPACE
|
DS_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
struct GpuInfo {
|
||||||
|
QString name; // 显卡名称,如 "NVIDIA GeForce RTX 3060"
|
||||||
|
QString driver; // 驱动模块名,如 "nvidia"
|
||||||
|
QString driverVersion; // 驱动版本
|
||||||
|
QString pciId; // PCI ID,如 "01:00.0"
|
||||||
|
};
|
||||||
|
|
||||||
class GraphicsDriverApplet : public DApplet
|
class GraphicsDriverApplet : public DApplet
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
Q_PROPERTY(QString currentDriver READ currentDriver NOTIFY currentDriverChanged)
|
|
||||||
Q_PROPERTY(QString deviceInfo READ deviceInfo NOTIFY deviceInfoChanged)
|
Q_PROPERTY(QString deviceInfo READ deviceInfo NOTIFY deviceInfoChanged)
|
||||||
Q_PROPERTY(int installProgress READ installProgress NOTIFY installProgressChanged)
|
Q_PROPERTY(QString currentDriver READ currentDriver NOTIFY currentDriverChanged)
|
||||||
Q_PROPERTY(bool isInstalling READ isInstalling NOTIFY isInstallingChanged)
|
Q_PROPERTY(QString gpuSummary READ gpuSummary NOTIFY gpuSummaryChanged)
|
||||||
Q_PROPERTY(bool dbusAvailable READ dbusAvailable NOTIFY dbusAvailableChanged)
|
Q_PROPERTY(bool ready READ ready NOTIFY readyChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GraphicsDriverApplet(QObject *parent = nullptr);
|
explicit GraphicsDriverApplet(QObject *parent = nullptr);
|
||||||
@@ -31,45 +35,30 @@ public:
|
|||||||
virtual bool load() override;
|
virtual bool load() override;
|
||||||
virtual bool init() override;
|
virtual bool init() override;
|
||||||
|
|
||||||
QString currentDriver() const;
|
|
||||||
QString deviceInfo() const;
|
QString deviceInfo() const;
|
||||||
int installProgress() const;
|
QString currentDriver() const;
|
||||||
bool isInstalling() const;
|
QString gpuSummary() const;
|
||||||
bool dbusAvailable() const;
|
bool ready() const;
|
||||||
|
|
||||||
Q_INVOKABLE void refreshDeviceInfo();
|
Q_INVOKABLE void refreshDeviceInfo();
|
||||||
Q_INVOKABLE void prepareInstall(const QString &driverName);
|
|
||||||
Q_INVOKABLE void testInstall();
|
|
||||||
Q_INVOKABLE void realInstall();
|
|
||||||
Q_INVOKABLE void cancelInstall();
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void currentDriverChanged();
|
|
||||||
void deviceInfoChanged();
|
void deviceInfoChanged();
|
||||||
void installProgressChanged();
|
void currentDriverChanged();
|
||||||
void isInstallingChanged();
|
void gpuSummaryChanged();
|
||||||
void installSuccess();
|
void readyChanged();
|
||||||
void installFailed(const QString &error);
|
|
||||||
void requestReboot();
|
|
||||||
void dbusAvailableChanged();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onGetDeviceReply(QDBusPendingCallWatcher *call);
|
|
||||||
void onGetCurrDriverReply(QDBusPendingCallWatcher *call);
|
|
||||||
void onReportProgress(const QString &ratio);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initDBusConnection();
|
void detectGpus();
|
||||||
void setInstalling(bool installing);
|
static QStringList runCommand(const QString &cmd, const QStringList &args);
|
||||||
|
static QString parseGpuName(const QString &line);
|
||||||
|
static QString readDriverVersion(const QString &driverName);
|
||||||
|
|
||||||
QDBusInterface *m_dbusInterface;
|
QList<GpuInfo> m_gpus;
|
||||||
QTimer *m_progressTimer;
|
|
||||||
bool m_dbusAvailable;
|
|
||||||
|
|
||||||
QString m_currentDriver;
|
|
||||||
QString m_deviceInfo;
|
QString m_deviceInfo;
|
||||||
int m_installProgress;
|
QString m_currentDriver;
|
||||||
bool m_isInstalling;
|
QString m_gpuSummary;
|
||||||
|
bool m_ready;
|
||||||
};
|
};
|
||||||
|
|
||||||
DS_END_NAMESPACE
|
DS_END_NAMESPACE
|
||||||
|
|||||||
+203
-108
@@ -7,6 +7,7 @@ import QtQuick.Controls 2.15
|
|||||||
import QtQuick.Layouts 1.15
|
import QtQuick.Layouts 1.15
|
||||||
import org.deepin.ds 1.0
|
import org.deepin.ds 1.0
|
||||||
import org.deepin.ds.dock 1.0
|
import org.deepin.ds.dock 1.0
|
||||||
|
import org.deepin.dtk 1.0
|
||||||
|
|
||||||
AppletItem {
|
AppletItem {
|
||||||
id: root
|
id: root
|
||||||
@@ -17,24 +18,32 @@ AppletItem {
|
|||||||
implicitWidth: dockSize
|
implicitWidth: dockSize
|
||||||
implicitHeight: dockSize
|
implicitHeight: dockSize
|
||||||
|
|
||||||
// 访问 C++ 后端
|
|
||||||
readonly property var applet: Applet
|
readonly property var applet: Applet
|
||||||
readonly property bool dbusAvailable: applet && applet.dbusAvailable
|
readonly property bool ready: applet ? applet.ready : false
|
||||||
readonly property string currentDriver: applet ? (applet.currentDriver || qsTr("Unknown")) : qsTr("Unknown")
|
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 deviceInfo: applet ? (applet.deviceInfo || qsTr("Unknown")) : qsTr("Unknown")
|
||||||
readonly property bool isInstalling: applet ? applet.isInstalling : false
|
readonly property string currentDriver: applet ? (applet.currentDriver || qsTr("Unknown")) : qsTr("Unknown")
|
||||||
readonly property int installProgress: applet ? applet.installProgress : 0
|
|
||||||
|
property Palette basePalette: DockPalette.iconTextPalette
|
||||||
|
readonly property color primaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.9)
|
||||||
|
readonly property color secondaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.65)
|
||||||
|
readonly property color tertiaryText: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.5)
|
||||||
|
readonly property color cardBackground: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.06)
|
||||||
|
readonly property color cardBorder: Qt.rgba(basePalette.r, basePalette.g, basePalette.b, 0.1)
|
||||||
|
readonly property color accentBlue: Qt.rgba(36 / 255, 118 / 255, 220 / 255, 1)
|
||||||
|
readonly property color accentBlueLight: Qt.rgba(36 / 255, 118 / 255, 220 / 255, 0.15)
|
||||||
|
|
||||||
// 图标区域
|
// 图标区域
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "#3498db"
|
color: accentBlue
|
||||||
radius: 8
|
radius: 8
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "GPU"
|
text: "GPU"
|
||||||
font.pixelSize: root.dockSize * 0.3
|
font.pixelSize: root.dockSize * 0.3
|
||||||
|
font.bold: true
|
||||||
color: "white"
|
color: "white"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,7 +51,7 @@ AppletItem {
|
|||||||
// 悬停提示
|
// 悬停提示
|
||||||
PanelToolTip {
|
PanelToolTip {
|
||||||
id: toolTip
|
id: toolTip
|
||||||
text: qsTr("Graphics Driver: ") + root.currentDriver
|
text: root.ready ? root.gpuSummary : qsTr("No GPU detected")
|
||||||
toolTipX: DockPanelPositioner.x
|
toolTipX: DockPanelPositioner.x
|
||||||
toolTipY: DockPanelPositioner.y
|
toolTipY: DockPanelPositioner.y
|
||||||
}
|
}
|
||||||
@@ -73,8 +82,8 @@ AppletItem {
|
|||||||
// 弹出窗口
|
// 弹出窗口
|
||||||
PanelPopup {
|
PanelPopup {
|
||||||
id: driverPopup
|
id: driverPopup
|
||||||
width: 320
|
width: 360
|
||||||
height: 400
|
height: 320
|
||||||
popupX: DockPanelPositioner.x
|
popupX: DockPanelPositioner.x
|
||||||
popupY: DockPanelPositioner.y
|
popupY: DockPanelPositioner.y
|
||||||
|
|
||||||
@@ -90,114 +99,196 @@ AppletItem {
|
|||||||
padding: 16
|
padding: 16
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
contentItem: ColumnLayout {
|
||||||
spacing: 12
|
spacing: 14
|
||||||
|
|
||||||
Text {
|
// 标题区域
|
||||||
text: qsTr("Graphics Driver Manager")
|
RowLayout {
|
||||||
font.pixelSize: 16
|
|
||||||
font.bold: true
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 28
|
||||||
|
height: 28
|
||||||
|
color: accentBlueLight
|
||||||
|
radius: 8
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "GPU"
|
||||||
|
font.pixelSize: 12
|
||||||
|
font.bold: true
|
||||||
|
color: accentBlue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: qsTr("Graphics Driver Manager")
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.bold: true
|
||||||
|
color: root.primaryText
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 分隔线
|
||||||
Rectangle {
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 1
|
Layout.preferredHeight: 1
|
||||||
color: "#cccccc"
|
color: root.cardBorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// D-Bus 不可用提示
|
// 当前驱动信息
|
||||||
Rectangle {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
|
||||||
height: 40
|
|
||||||
visible: !root.dbusAvailable
|
|
||||||
color: "#fff3cd"
|
|
||||||
radius: 6
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: qsTr("Driver service is not available")
|
|
||||||
color: "#856404"
|
|
||||||
font.pixelSize: 12
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设备信息
|
|
||||||
GroupBox {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
title: qsTr("Device Information")
|
|
||||||
visible: root.dbusAvailable
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: qsTr("Current Driver: ") + root.currentDriver
|
|
||||||
font.pixelSize: 12
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: qsTr("Device: ") + root.deviceInfo
|
|
||||||
font.pixelSize: 12
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 操作按钮
|
|
||||||
Button {
|
|
||||||
text: qsTr("Refresh")
|
|
||||||
Layout.fillWidth: true
|
|
||||||
enabled: root.dbusAvailable && !root.isInstalling
|
|
||||||
onClicked: {
|
|
||||||
if (root.applet) {
|
|
||||||
root.applet.refreshDeviceInfo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: qsTr("Test Install")
|
|
||||||
Layout.fillWidth: true
|
|
||||||
enabled: root.dbusAvailable && !root.isInstalling
|
|
||||||
onClicked: {
|
|
||||||
if (root.applet) {
|
|
||||||
root.applet.testInstall()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 安装进度
|
|
||||||
ColumnLayout {
|
|
||||||
visible: root.isInstalling
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
visible: root.ready
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: qsTr("Installing... ") + root.installProgress + "%"
|
text: qsTr("Current Driver:")
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
|
color: root.secondaryText
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgressBar {
|
Text {
|
||||||
|
text: root.currentDriver
|
||||||
|
font.pixelSize: 12
|
||||||
|
font.bold: true
|
||||||
|
color: root.primaryText
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
value: root.installProgress / 100
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GPU 列表区域
|
||||||
|
Flickable {
|
||||||
|
id: gpuListFlick
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
contentWidth: width
|
||||||
|
contentHeight: gpuColumn.height
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: gpuColumn
|
||||||
|
width: gpuListFlick.width
|
||||||
|
spacing: 10
|
||||||
|
visible: root.ready
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: root.ready ? root.deviceInfo.split("\n").filter(function(line) { return line.trim().length > 0 }) : []
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
width: gpuColumn.width
|
||||||
|
height: 72
|
||||||
|
color: root.cardBackground
|
||||||
|
radius: 10
|
||||||
|
border.width: 1
|
||||||
|
border.color: root.cardBorder
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 12
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
// GPU 图标
|
||||||
|
Rectangle {
|
||||||
|
width: 44
|
||||||
|
height: 44
|
||||||
|
color: root.accentBlueLight
|
||||||
|
radius: 8
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "GPU"
|
||||||
|
font.pixelSize: 14
|
||||||
|
font.bold: true
|
||||||
|
color: root.accentBlue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GPU 信息
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 4
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: parseGpuName(modelData)
|
||||||
|
font.pixelSize: 13
|
||||||
|
font.bold: true
|
||||||
|
color: root.primaryText
|
||||||
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: parseDriverInfo(modelData)
|
||||||
|
font.pixelSize: 11
|
||||||
|
color: root.secondaryText
|
||||||
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
// 未检测到 GPU 提示
|
||||||
text: qsTr("Cancel")
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
anchors.centerIn: parent
|
||||||
onClicked: {
|
width: gpuListFlick.width - 32
|
||||||
if (root.applet) {
|
height: 80
|
||||||
root.applet.cancelInstall()
|
color: root.cardBackground
|
||||||
|
radius: 10
|
||||||
|
border.width: 1
|
||||||
|
border.color: root.cardBorder
|
||||||
|
visible: !root.ready
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "⚠"
|
||||||
|
font.pixelSize: 24
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: qsTr("No GPU detected")
|
||||||
|
font.pixelSize: 13
|
||||||
|
color: root.secondaryText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
// 刷新按钮
|
||||||
Layout.fillHeight: true
|
Button {
|
||||||
|
id: refreshBtn
|
||||||
|
text: qsTr("Refresh")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 36
|
||||||
|
font.pixelSize: 13
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: refreshBtn.hovered ? root.accentBlue : root.accentBlueLight
|
||||||
|
radius: 8
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Text {
|
||||||
|
text: refreshBtn.text
|
||||||
|
color: refreshBtn.hovered ? "white" : root.accentBlue
|
||||||
|
font.pixelSize: refreshBtn.font.pixelSize
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (root.applet) {
|
||||||
|
root.applet.refreshDeviceInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,6 +301,25 @@ AppletItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解析 GPU 名称的辅助函数
|
||||||
|
function parseGpuName(info) {
|
||||||
|
var parts = info.split("(")
|
||||||
|
if (parts.length > 0) {
|
||||||
|
return parts[0].trim()
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析驱动信息的辅助函数
|
||||||
|
function parseDriverInfo(info) {
|
||||||
|
var startIndex = info.indexOf("(")
|
||||||
|
var endIndex = info.lastIndexOf(")")
|
||||||
|
if (startIndex !== -1 && endIndex !== -1) {
|
||||||
|
return info.substring(startIndex + 1, endIndex)
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
// 点击处理
|
// 点击处理
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton
|
||||||
@@ -220,7 +330,6 @@ AppletItem {
|
|||||||
driverPopup.close()
|
driverPopup.close()
|
||||||
} else {
|
} else {
|
||||||
Panel.requestClosePopup()
|
Panel.requestClosePopup()
|
||||||
// 在打开前设置定位,确保弹窗出现在插件附近
|
|
||||||
const point = root.mapToItem(null, root.width / 2, root.height / 2)
|
const point = root.mapToItem(null, root.width / 2, root.height / 2)
|
||||||
driverPopup.DockPanelPositioner.bounding = Qt.rect(point.x, point.y, driverPopup.width, driverPopup.height)
|
driverPopup.DockPanelPositioner.bounding = Qt.rect(point.x, point.y, driverPopup.width, driverPopup.height)
|
||||||
driverPopup.open()
|
driverPopup.open()
|
||||||
@@ -228,18 +337,4 @@ AppletItem {
|
|||||||
toolTip.close()
|
toolTip.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 后端信号处理
|
|
||||||
Connections {
|
|
||||||
target: root.applet
|
|
||||||
function onInstallSuccess() {
|
|
||||||
console.log("Install succeeded")
|
|
||||||
}
|
|
||||||
function onInstallFailed(error) {
|
|
||||||
console.log("Install failed:", error)
|
|
||||||
}
|
|
||||||
function onRequestReboot() {
|
|
||||||
console.log("Reboot requested")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../package/driverview.qml"/>
|
<location filename="../package/driverview.qml"/>
|
||||||
<source>Graphics Driver: </source>
|
<source>No GPU detected</source>
|
||||||
<translation>显卡驱动:</translation>
|
<translation>未检测到显卡</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../package/driverview.qml"/>
|
<location filename="../package/driverview.qml"/>
|
||||||
@@ -20,43 +20,13 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../package/driverview.qml"/>
|
<location filename="../package/driverview.qml"/>
|
||||||
<source>Driver service is not available</source>
|
<source>Current Driver:</source>
|
||||||
<translation>驱动服务不可用</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="../package/driverview.qml"/>
|
|
||||||
<source>Device Information</source>
|
|
||||||
<translation>设备信息</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="../package/driverview.qml"/>
|
|
||||||
<source>Current Driver: </source>
|
|
||||||
<translation>当前驱动:</translation>
|
<translation>当前驱动:</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<location filename="../package/driverview.qml"/>
|
|
||||||
<source>Device: </source>
|
|
||||||
<translation>设备:</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<location filename="../package/driverview.qml"/>
|
<location filename="../package/driverview.qml"/>
|
||||||
<source>Refresh</source>
|
<source>Refresh</source>
|
||||||
<translation>刷新</translation>
|
<translation>刷新</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<location filename="../package/driverview.qml"/>
|
|
||||||
<source>Test Install</source>
|
|
||||||
<translation>测试安装</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="../package/driverview.qml"/>
|
|
||||||
<source>Installing... </source>
|
|
||||||
<translation>安装中...</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="../package/driverview.qml"/>
|
|
||||||
<source>Cancel</source>
|
|
||||||
<translation>取消</translation>
|
|
||||||
</message>
|
|
||||||
</context>
|
</context>
|
||||||
</TS>
|
</TS>
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../package/driverview.qml"/>
|
<location filename="../package/driverview.qml"/>
|
||||||
<source>Graphics Driver: </source>
|
<source>No GPU detected</source>
|
||||||
<translation>顯示卡驅動:</translation>
|
<translation>未偵測到顯示卡</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../package/driverview.qml"/>
|
<location filename="../package/driverview.qml"/>
|
||||||
@@ -20,43 +20,13 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../package/driverview.qml"/>
|
<location filename="../package/driverview.qml"/>
|
||||||
<source>Driver service is not available</source>
|
<source>Current Driver:</source>
|
||||||
<translation>驅動服務不可用</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="../package/driverview.qml"/>
|
|
||||||
<source>Device Information</source>
|
|
||||||
<translation>裝置資訊</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="../package/driverview.qml"/>
|
|
||||||
<source>Current Driver: </source>
|
|
||||||
<translation>目前驅動:</translation>
|
<translation>目前驅動:</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<location filename="../package/driverview.qml"/>
|
|
||||||
<source>Device: </source>
|
|
||||||
<translation>裝置:</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<location filename="../package/driverview.qml"/>
|
<location filename="../package/driverview.qml"/>
|
||||||
<source>Refresh</source>
|
<source>Refresh</source>
|
||||||
<translation>重新整理</translation>
|
<translation>重新整理</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<location filename="../package/driverview.qml"/>
|
|
||||||
<source>Test Install</source>
|
|
||||||
<translation>測試安裝</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="../package/driverview.qml"/>
|
|
||||||
<source>Installing... </source>
|
|
||||||
<translation>安裝中...</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="../package/driverview.qml"/>
|
|
||||||
<source>Cancel</source>
|
|
||||||
<translation>取消</translation>
|
|
||||||
</message>
|
|
||||||
</context>
|
</context>
|
||||||
</TS>
|
</TS>
|
||||||
|
|||||||
Reference in New Issue
Block a user