Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/ll-driver-detect/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pfl_add_executable(
./src/main.cpp
./src/driver_detection_config.cpp
./src/nvidia_driver_detector.cpp
./src/intel_media_driver_detector.cpp
./src/application_singleton.cpp
./src/driver_detection_manager.cpp
./src/dbus_notifier.cpp
Expand Down
6 changes: 4 additions & 2 deletions apps/ll-driver-detect/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## 功能概述

- **可扩展的驱动检测**: 采用插件式架构 (`DriverDetectionManager`),可以轻松扩展以支持多种类型的显卡驱动(目前已支持 NVIDIA)。
- **可扩展的驱动检测**: 采用插件式架构 (`DriverDetectionManager`),可以轻松扩展以支持多种类型的显卡驱动(目前已支持 NVIDIA 与 Intel 媒体驱动)。
- **单例运行保证**: 通过文件锁 (`ApplicationSingleton`) 确保在任何时候只有一个检测进程在运行,避免资源浪费和重复通知。
- **简化的用户配置**: 提供一个简单的配置选项,允许用户选择“不再提醒”,配置被保存在 `~/.config/linglong/driver_detection.json` 中。
- **统一的用户通知**: 当检测到一个或多个可安装的驱动时,通过 DBus 向用户发送一个统一的通知。
Expand Down Expand Up @@ -57,7 +57,7 @@ ll-driver-detect --help

1. **获取单例锁**: 程序启动时,`ApplicationSingleton` 会尝试在 `$XDG_CONFIG_HOME/linglong/ll-driver-detect.lock`(如果 `XDG_CONFIG_HOME` 未设置,则默认为 `~/.config/linglong/ll-driver-detect.lock`)获取文件锁。如果锁已被另一实例持有,则当前进程会安静地退出。
2. **初始化驱动管理器**: 创建一个 `DriverDetectionManager` 实例。
3. **注册与检测**: 管理器的构造函数会注册所有可用的 `DriverDetector` 子类(例如 `NVIDIADriverDetector`)。随后,管理器调用每个检测器的 `detect()` 方法。
3. **注册与检测**: 管理器的构造函数会注册所有可用的 `DriverDetector` 子类(例如 `NVIDIADriverDetector`、`IntelMediaDriverDetector`)。随后,管理器调用每个检测器的 `detect()` 方法。
4. **结果聚合**: `detect()` 方法返回驱动信息(如果检测成功),包括驱动标识、版本、玲珑包名和当前安装状态。管理器收集所有成功检测到的驱动信息。
5. **执行操作**:
- 如果没有检测到任何驱动,程序退出。
Expand Down Expand Up @@ -86,6 +86,7 @@ apps/ll-driver-detect/
│ ├── driver_detection_manager.h/.cpp # 驱动检测协调器
│ ├── driver_detector.h/.cpp # 驱动检测器基类
│ ├── nvidia_driver_detector.h/.cpp # NVIDIA 驱动检测器实现
│ ├── intel_media_driver_detector.h/.cpp # Intel 媒体驱动检测器实现
│ ├── driver_detection_config.h/.cpp # 配置管理
│ └── dbus_notifier.h/.cpp # DBus 通知实现
├── README.md # 本文档
Expand All @@ -98,6 +99,7 @@ apps/ll-driver-detect/
- **`DriverDetectionManager`**: 负责注册和运行所有具体的驱动检测器。
- **`DriverDetector`**: 驱动检测器的抽象基类,定义了 `detect()` 接口。
- **`NVIDIADriverDetector`**: `DriverDetector` 的一个实现,专门用于检测 NVIDIA 驱动。
- **`IntelMediaDriverDetector`**: `DriverDetector` 的一个实现,用于检测 Intel 视频编解码(VAAPI)驱动。
- **`DriverDetectionConfigManager`**: 负责加载和保存 `driver_detection.json` 配置文件。
- **`DBusNotifier`**: 封装了通过 DBus 发送通知的逻辑。

Expand Down
4 changes: 3 additions & 1 deletion apps/ll-driver-detect/src/driver_detection_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

#include "driver_detection_manager.h"

#include "intel_media_driver_detector.h"

Check warning on line 7 in apps/ll-driver-detect/src/driver_detection_manager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/utils/cmd.h" not found.
#include "linglong/utils/cmd.h"

Check warning on line 8 in apps/ll-driver-detect/src/driver_detection_manager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/utils/log/log.h" not found.
#include "linglong/utils/log/log.h"
#include "nvidia_driver_detector.h"

Expand Down Expand Up @@ -49,7 +50,8 @@
// Register NVIDIA detector
detectors_.push_back(std::make_unique<NVIDIADriverDetector>());

// TODO: Add other driver detector
// Register Intel media (VAAPI) detector
detectors_.push_back(std::make_unique<IntelMediaDriverDetector>());

LogD("Registered {} driver detectors", detectors_.size());
}
Expand Down
262 changes: 262 additions & 0 deletions apps/ll-driver-detect/src/intel_media_driver_detector.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "intel_media_driver_detector.h"

#include <string_view>

#include "linglong/package/version.h"
#include "linglong/utils/cmd.h"
#include "linglong/utils/error/error.h"
#include "linglong/utils/log/log.h"

#include <nlohmann/json.hpp>

#include <algorithm>
#include <filesystem>

namespace linglong::driver::detect {

IntelMediaDriverDetector::IntelMediaDriverDetector(
std::vector<std::filesystem::path> intelGpuModulePaths)
: intelGpuModulePaths_(std::move(intelGpuModulePaths))
{
}

utils::error::Result<GraphicsDriverInfo> IntelMediaDriverDetector::detect()
{
LINGLONG_TRACE("Intel media driver detection started");

// Without an Intel GPU there is no reason to talk to the repository at all, so
// bail out before running any ll-cli command.
if (!isIntelGpuPresent()) {
return LINGLONG_ERR("No Intel GPU found on the host");
}

const std::string packageName = getDriverIdentify();

// Check if package exists in repo
auto packageInfoResult = getPackageInfoFromRemoteRepo(packageName);
if (!packageInfoResult) {
return LINGLONG_ERR("Failed to get package info from remote repo: "
+ packageInfoResult.error().message());
}

auto installedResult = checkPackageInstalled(packageName);
if (!installedResult) {
return LINGLONG_ERR("Failed to check if package is installed: "
+ installedResult.error().message());
}

if (*installedResult) {
if (!packageInfoResult->first) {
LogW("Intel media driver package is installed locally but not found in remote repo: {}",
packageName);
return LINGLONG_ERR("Cannot find Intel media driver package in remote repo.");
}

auto upgradableResult = checkPackageUpgradable(packageName);
if (!upgradableResult) {
return LINGLONG_ERR("Failed to check if package is upgradable: "
+ upgradableResult.error().message());
}
if (*upgradableResult) {
LogD("Intel media driver package can be upgraded: {}, install from remote repo.",
packageName);
return packageInfoResult->second;
}
return LINGLONG_ERR("Intel media driver package is already installed and up-to-date.");
}

if (!packageInfoResult->first) {
return LINGLONG_ERR("Intel media driver package not found in remote repo");
}

LogD("Intel media driver package is not installed, install from remote repo.");
return packageInfoResult->second;
}

utils::error::Result<std::pair<bool, GraphicsDriverInfo>>
IntelMediaDriverDetector::getPackageInfoFromRemoteRepo(const std::string &packageName)
{
using json = nlohmann::json;

LINGLONG_TRACE("Check if Intel media driver package exists in repository");

GraphicsDriverInfo driverInfo;
driverInfo.identify = getDriverIdentify();
driverInfo.packageName = packageName;

// Execute ll-cli search command to look for the driver package. The result is a
// map from repository alias to the packages found in that repository.
auto ret = linglong::utils::Cmd("ll-cli").exec({ "--json", "search", packageName });
if (!ret) {
return LINGLONG_ERR("Search command failed: " + ret.error().message());
}

bool found = false;

try {
json j = json::parse(*ret);

for (const auto &[repoAlias, packages] : j.items()) {
if (!packages.is_array())
continue;

for (const auto &item : packages) {
if (!item.is_object())
continue;

// The search API performs a fuzzy match, so unrelated packages can
// show up here. Only accept the exact identify we are looking for.
auto idIter = item.find("id");
if (idIter == item.end() || !idIter->is_string()
|| idIter->get<std::string_view>() != packageName) {
continue;
}

auto versionIter = item.find("version");
if (versionIter == item.end() || !versionIter->is_string())
continue;

std::string_view currentVersionStr = versionIter->get<std::string_view>();

// A package may be published in several repositories; keep the newest
// version and remember which repository provides it.
if (!found || compareVersions(currentVersionStr, driverInfo.packageVersion)) {
driverInfo.repoName = repoAlias;
driverInfo.packageVersion = currentVersionStr;
found = true;
}
}
}
} catch (const nlohmann::json::parse_error &e) {
return LINGLONG_ERR("Failed to parse search result JSON: " + std::string(e.what()));
}

return std::make_pair(found, driverInfo);
}

utils::error::Result<bool>
IntelMediaDriverDetector::checkPackageInstalled(const std::string &packageName)
{
LINGLONG_TRACE("Check if Intel media driver package is installed");

auto installedInfo = getInstalledGraphicsDriverInfo(packageName);
if (!installedInfo) {
return LINGLONG_ERR(installedInfo);
}

return installedInfo->first;
}

utils::error::Result<bool>
IntelMediaDriverDetector::checkPackageUpgradable(const std::string &packageName)
{
LINGLONG_TRACE("Check if Intel media driver package is upgradable");

auto upgradableDriverInfo = getPackageInfoFromRemoteRepo(packageName);
if (!upgradableDriverInfo) {
return LINGLONG_ERR("Failed to get upgradable package info: "
+ upgradableDriverInfo.error().message());
}

if (!upgradableDriverInfo->first) {
return LINGLONG_ERR("Intel media driver package not found in remote repo");
}

auto installedDriverInfo = getInstalledGraphicsDriverInfo(packageName);
if (!installedDriverInfo) {
return LINGLONG_ERR("Failed to get installed package info: "
+ installedDriverInfo.error().message());
}

if (!installedDriverInfo->first) {
return LINGLONG_ERR("Intel media driver package is not installed");
}

LogD("{} current version:{}, latest version {}",
packageName,
installedDriverInfo->second.packageVersion,
upgradableDriverInfo->second.packageVersion);

return compareVersions(upgradableDriverInfo->second.packageVersion,
installedDriverInfo->second.packageVersion);
}

bool IntelMediaDriverDetector::isIntelGpuPresent() const
{
// The i915 and xe kernel modules both expose an Intel GPU to userspace; the
// presence of either module directory is what we treat as "Intel GPU present".
return std::any_of(intelGpuModulePaths_.begin(),
intelGpuModulePaths_.end(),
[](const std::filesystem::path &modulePath) {
return std::filesystem::exists(modulePath);
});
}

utils::error::Result<std::pair<bool, GraphicsDriverInfo>>
IntelMediaDriverDetector::getInstalledGraphicsDriverInfo(const std::string &packageName) const
{
using json = nlohmann::json;

LINGLONG_TRACE("Get installed Intel media graphics driver info");

GraphicsDriverInfo driverInfo;
driverInfo.identify = getDriverIdentify();
driverInfo.packageName = packageName;

auto listResult = linglong::utils::Cmd("ll-cli").exec({ "--json", "list", "--type=extension" });
if (!listResult) {
return LINGLONG_ERR(listResult);
}

bool installed = false;
try {
json installedExtensions = json::parse(*listResult);
if (!installedExtensions.is_array()) {
return LINGLONG_ERR("Invalid list result JSON: expected an array");
}

for (const auto &item : installedExtensions) {
if (!item.is_object())
continue;

auto idIter = item.find("id");
if (idIter == item.end() || !idIter->is_string()
|| idIter->get<std::string_view>() != packageName) {
continue;
}

auto versionIter = item.find("version");
if (versionIter == item.end() || !versionIter->is_string()) {
return LINGLONG_ERR("Installed package found but version field is missing: "
+ packageName);
}

driverInfo.packageVersion = versionIter->get<std::string_view>();
installed = true;
break;
}
} catch (const nlohmann::json::parse_error &e) {
return LINGLONG_ERR("Failed to parse installed package JSON: " + std::string(e.what()));
}

return std::make_pair(installed, driverInfo);
}

bool IntelMediaDriverDetector::compareVersions(std::string_view v1, std::string_view v2) const noexcept
{
auto ver1 = package::Version::parse(std::string(v1));
auto ver2 = package::Version::parse(std::string(v2));

if (!ver1 || !ver2) {
LogW("Failed to parse versions: {} , {}", v1, v2);
return false;
}

return *ver1 > *ver2;
}

} // namespace linglong::driver::detect
63 changes: 63 additions & 0 deletions apps/ll-driver-detect/src/intel_media_driver_detector.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include "driver_detector.h"

#include <filesystem>
#include <string>
#include <string_view>
#include <vector>

namespace linglong::driver::detect {

// Detects the Intel video codec (VAAPI) driver package org.deepin.driver.media.intel.
//
// Unlike the NVIDIA package the Intel package carries no version segment in its
// identify: a single package covers both new and legacy Intel GPUs, so the package
// name is the identify itself and the version is resolved from the remote repository.
class IntelMediaDriverDetector : public DriverDetector
{
public:
static constexpr auto kIntelMediaPackageIdentify = "org.deepin.driver.media.intel";

// Paths of the kernel modules that expose an Intel GPU. Both the legacy i915
// driver and the newer xe driver are probed so detection works on old and new
// Intel hardware. The list is injectable, exactly like the NVIDIA version file
// path, so tests never depend on the hardware of the machine running them.
explicit IntelMediaDriverDetector(
std::vector<std::filesystem::path> intelGpuModulePaths = { "/sys/module/i915",
"/sys/module/xe" });
~IntelMediaDriverDetector() override = default;

// Main detection method - returns driver info if detected
utils::error::Result<GraphicsDriverInfo> detect() override;

// Check if the driver package is installed
utils::error::Result<bool> checkPackageInstalled(const std::string &packageName) override;

// Check if the driver package can be upgraded
utils::error::Result<bool> checkPackageUpgradable(const std::string &packageName) override;

// Check if the driver package exists in repo
virtual utils::error::Result<std::pair<bool, GraphicsDriverInfo>>
getPackageInfoFromRemoteRepo(const std::string &packageName);

// Get the type of driver this detector handles
std::string getDriverIdentify() const override { return kIntelMediaPackageIdentify; }

protected:
bool compareVersions(std::string_view v1, std::string_view v2) const noexcept;

private:
// Returns true as soon as one of the configured Intel GPU module paths exists
bool isIntelGpuPresent() const;
utils::error::Result<std::pair<bool, GraphicsDriverInfo>>
getInstalledGraphicsDriverInfo(const std::string &packageName) const;

std::vector<std::filesystem::path> intelGpuModulePaths_;
};

} // namespace linglong::driver::detect
2 changes: 1 addition & 1 deletion docs/pages/en/guide/reference/driver.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ Drivers not included in the base that require additional installation:

- NVIDIA proprietary drivers: Install via `sudo ll-cli install org.deepin.driver.display.nvidia.570-124-04`. The `570-124-04` is the driver version number, which must match the driver version installed on the host system. Check the host driver version through the `/sys/module/nvidia/version` file.
- Glenfly graphics drivers: Install via `sudo ll-cli install com.glenfly.driver.display.arise`.
- Intel video codec drivers (VAAPI): Install via `sudo ll-cli install org.deepin.driver.media.intel`, which includes support for both new and legacy Intel graphics cards.
- Intel video codec drivers (VAAPI): Install via `sudo ll-cli install org.deepin.driver.media.intel`, which includes support for both new and legacy Intel graphics cards. When an Intel GPU is present on the host and the package is available in the configured remote repository, `ll-driver-detect` detects this driver and offers to install it.
2 changes: 1 addition & 1 deletion docs/pages/guide/reference/driver.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@

- 英伟达闭源驱动,通过 `sudo ll-cli install org.deepin.driver.display.nvidia.570-124-04` 安装。其中 `570-124-04` 是驱动版本号,需要与宿主机安装的驱动版本匹配,通过宿主机 `/sys/module/nvidia/version` 文件查看宿主机驱动的版本。
- 格兰菲显卡驱动,通过 `sudo ll-cli install com.glenfly.driver.display.arise` 安装。
- 英特尔视频编解码驱动(VAAPI),通过 `sudo ll-cli install org.deepin.driver.media.intel` 安装,包含了新/旧 Intel 显卡的支持。
- 英特尔视频编解码驱动(VAAPI),通过 `sudo ll-cli install org.deepin.driver.media.intel` 安装,包含了新/旧 Intel 显卡的支持。当宿主机上检测到 Intel 显卡、且配置的远端仓库中存在该软件包时,`ll-driver-detect` 会检测该驱动并提示安装。
Loading
Loading