1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2023 Dave Vasilevsky <dave@vasilevsky.ca>
4
5 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only
6*/
7
8#include "gpudetection_p.h"
9
10#ifdef WITH_QTDBUS
11#include <QDBusArgument>
12#include <QDBusConnection>
13#include <QDBusInterface>
14#include <QDBusReply>
15#include <QDBusVariant>
16#endif
17#include <QMap>
18#include <QString>
19
20#include <KProcess>
21
22static void checkGpu();
23// Returns true if switcheroo present
24static bool checkGpuWithSwitcheroo();
25static void checkGpuWithSolid();
26
27// TODO: GPUs are hot-swappable, watch for changes using dbus PropertiesChanged
28enum class GpuCheck {
29 NotChecked,
30 Present,
31 Absent,
32};
33static GpuCheck s_gpuCheck = GpuCheck::NotChecked;
34static QProcessEnvironment s_gpuEnv;
35
36static void checkGpu()
37{
38 if (s_gpuCheck == GpuCheck::NotChecked) {
39 if (!checkGpuWithSwitcheroo()) {
40 checkGpuWithSolid();
41 }
42 }
43}
44
45static bool checkGpuWithSwitcheroo()
46{
47#ifdef WITH_QTDBUS
48 QDBusInterface switcheroo(QStringLiteral("net.hadess.SwitcherooControl"),
49 QStringLiteral("/net/hadess/SwitcherooControl"),
50 QStringLiteral("org.freedesktop.DBus.Properties"),
51 QDBusConnection::systemBus());
52 if (!switcheroo.isValid()) {
53 return false;
54 }
55
56 QDBusReply<QDBusVariant> reply = switcheroo.call(QStringLiteral("Get"), QStringLiteral("net.hadess.SwitcherooControl"), QStringLiteral("GPUs"));
57 if (!reply.isValid()) {
58 return false;
59 }
60
61 QDBusArgument arg = qvariant_cast<QDBusArgument>(v: reply.value().variant());
62 QList<QVariantMap> gpus;
63 arg >> gpus;
64
65 if (gpus.isEmpty()) {
66 // No GPU(s) found
67 return false;
68 } else if (gpus.size() == 1) {
69 // There is only one GPU, no need to check for others
70 s_gpuCheck = GpuCheck::Absent;
71 return true;
72 }
73
74 QVariantMap defaultGpu;
75 QVariantMap firstDiscreteGpu;
76 QVariantMap firstNonDefaultGpu;
77
78 for (const auto &gpu : std::as_const(t&: gpus)) {
79 if (defaultGpu.isEmpty() && gpu[QStringLiteral("Default")].toBool()) {
80 defaultGpu = gpu;
81 } else if (firstNonDefaultGpu.isEmpty()) {
82 firstNonDefaultGpu = gpu;
83 }
84 if (firstDiscreteGpu.isEmpty() && gpu[QStringLiteral("Discrete")].toBool()) {
85 firstDiscreteGpu = gpu;
86 }
87 }
88
89 if (!defaultGpu.isEmpty() && defaultGpu[QStringLiteral("Discrete")].toBool()) {
90 // If the default GPU is discrete there is no need to do anything special
91 s_gpuCheck = GpuCheck::Absent;
92 return true;
93 }
94
95 // Otherwise prefer the discrete GPU over any other random non-default GPU (legacy behavior)
96 for (const auto &gpu : {firstDiscreteGpu, firstNonDefaultGpu}) {
97 if (gpu.isEmpty()) {
98 continue;
99 }
100 s_gpuCheck = GpuCheck::Present;
101 auto envList = gpu[QStringLiteral("Environment")].toStringList();
102 for (int i = 0; i + 1 < envList.size(); i += 2) {
103 s_gpuEnv.insert(name: envList[i], value: envList[i + 1]);
104 }
105 return true;
106 }
107#endif
108
109 // No discrete or non-default GPU found
110 s_gpuCheck = GpuCheck::Absent;
111 return true;
112}
113
114static void checkGpuWithSolid()
115{
116#ifdef WITH_QTDBUS
117 // TODO: Consider moving this check into kio, instead of using Solid
118 QDBusInterface iface(QStringLiteral("org.kde.Solid.PowerManagement"),
119 QStringLiteral("/org/kde/Solid/PowerManagement"),
120 QStringLiteral("org.kde.Solid.PowerManagement"),
121 QDBusConnection::sessionBus());
122 if (iface.isValid()) {
123 QDBusReply<bool> reply = iface.call(QStringLiteral("hasDualGpu"));
124 if (reply.isValid() && reply.value()) {
125 s_gpuCheck = GpuCheck::Present;
126 s_gpuEnv.insert(QStringLiteral("DRI_PRIME"), QStringLiteral("1"));
127 return;
128 }
129 }
130
131 s_gpuCheck = GpuCheck::Absent;
132#endif
133}
134
135namespace KIO
136{
137
138bool hasDiscreteGpu()
139{
140 checkGpu();
141 return s_gpuCheck == GpuCheck::Present;
142}
143
144QProcessEnvironment discreteGpuEnvironment()
145{
146 checkGpu();
147 return s_gpuEnv;
148}
149
150}
151

source code of kio/src/gui/gpudetection.cpp