File indexing completed on 2024-04-21 05:45:52
0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0002 // SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org> 0003 0004 #include <QApplication> 0005 #include <QQmlApplicationEngine> 0006 #include <QIcon> 0007 #include <QQmlContext> 0008 #include <QStandardPaths> 0009 #include <QFile> 0010 #include <QWindow> 0011 #include <KStatusNotifierItem> 0012 #include <KDeclarative/KDeclarative> 0013 #include <KLocalizedContext> 0014 #include <KLocalizedString> 0015 #include <KAuthAction> 0016 #include <KAuthExecuteJob> 0017 #include <KJob> 0018 #include <KOSRelease> 0019 0020 class AuthHelper : public QObject 0021 { 0022 Q_OBJECT 0023 // Does the grub config for nomodeset exist (if not disable() will not be able to do anything) 0024 Q_PROPERTY(bool grubCfgExists MEMBER m_grubCfgExists CONSTANT) 0025 // Is a disable() call currently running 0026 Q_PROPERTY(bool busy MEMBER m_busy NOTIFY busyChanged) 0027 // Was nomodeset disabled (via disable() call) 0028 Q_PROPERTY(bool disabled MEMBER m_disabled NOTIFY busyChanged) 0029 // Last (fatal) error. Empty when no error. 0030 Q_PROPERTY(QString error MEMBER m_error NOTIFY errorChanged) 0031 // Is system running as a live session from an ISO or the like. 0032 Q_PROPERTY(bool liveSession MEMBER m_liveSession CONSTANT) 0033 // True when installing the live session results in nomodeset as well (e.g. configured in grub) 0034 Q_PROPERTY(bool livePersistent MEMBER m_livePersistent CONSTANT) 0035 public: 0036 using QObject::QObject; 0037 0038 Q_INVOKABLE void disable() 0039 { 0040 if (m_busy) { 0041 return; 0042 } 0043 setBusy(true); 0044 0045 KAuth::Action action(QStringLiteral("org.kde.nomodeset.disable")); 0046 action.setHelperId(QStringLiteral("org.kde.nomodeset")); 0047 0048 qDebug() << action.isValid() << action.hasHelper() << action.helperId() << action.status(); 0049 KAuth::ExecuteJob *job = action.execute(); 0050 connect(job, &KJob::result, this, [job, this] { 0051 qDebug() << job->error() << job->errorString() << job->errorText(); 0052 switch (static_cast<KAuth::ActionReply::Error>(job->error())) { 0053 case KAuth::ActionReply::NoError: 0054 m_disabled = true; 0055 writeIgnore(); 0056 break; 0057 case KAuth::ActionReply::AuthorizationDeniedError: 0058 Q_FALLTHROUGH(); 0059 case KAuth::ActionReply::UserCancelledError: 0060 // leave disabled state alone -> falls back to original page in gui 0061 break; 0062 default: 0063 m_error = job->errorString(); 0064 if (m_error.isEmpty()) { 0065 m_error = job->errorText(); 0066 } 0067 if (m_error.isEmpty()) { 0068 m_error = i18nc("@info:status", "Unknown error code: %1", QString::number(job->error())); 0069 } 0070 Q_EMIT errorChanged(); 0071 break; 0072 } 0073 0074 setBusy(false); 0075 }); 0076 job->start(); 0077 } 0078 0079 bool shouldIgnore() 0080 { 0081 return !QStandardPaths::locate(QStandardPaths::TempLocation, ignoreMaker()).isEmpty(); 0082 } 0083 0084 void writeIgnore() 0085 { 0086 const QString path = 0087 QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QLatin1Char('/') + ignoreMaker(); 0088 QFile file(path); 0089 file.open(QIODevice::WriteOnly); 0090 } 0091 0092 Q_SIGNALS: 0093 void busyChanged(); 0094 void disablingChanged(); 0095 void errorChanged(); 0096 0097 private: 0098 static QString ignoreMaker() 0099 { 0100 return QStringLiteral(".kde-nomodeset-ignore"); 0101 } 0102 0103 // BLOCKING on helper 0104 bool grubCfgExists() 0105 { 0106 KAuth::Action action(QStringLiteral("org.kde.nomodeset.grubcfgexists")); 0107 action.setHelperId(QStringLiteral("org.kde.nomodeset")); 0108 0109 qDebug() << action.isValid() << action.hasHelper() << action.helperId() << action.status(); 0110 KAuth::ExecuteJob *job = action.execute(); 0111 bool result = false; 0112 connect(job, &KJob::result, this, [job, &result] { 0113 qDebug() << job->error() << job->errorString() << job->errorText(); 0114 result = (job->error() == KAuth::ActionReply::NoError); 0115 }); 0116 job->exec(); 0117 return result; 0118 } 0119 0120 bool isLiveSession() 0121 { 0122 QFile cmdline("/proc/cmdline"); 0123 if (!cmdline.open(QFile::ReadOnly)) { 0124 return false; 0125 } 0126 if (cmdline.readAll().contains(QByteArray("boot=casper"))) { // ubuntus 0127 return true; 0128 } 0129 return false; 0130 } 0131 0132 void setBusy(bool busy) 0133 { 0134 if (m_busy == busy) { 0135 return; 0136 } 0137 m_busy = busy; 0138 Q_EMIT busyChanged(); 0139 } 0140 0141 const bool m_grubCfgExists = grubCfgExists(); 0142 bool m_disabled = false; 0143 bool m_busy = false; 0144 QString m_error; 0145 const bool m_liveSession = isLiveSession(); 0146 // make this a function should it cover more than neon at some point 0147 const bool m_livePersistent = (KOSRelease().id() == QLatin1String("neon")); 0148 }; 0149 0150 // QML is fairly heavy. Only load it on demand. This class wraps an entire engine's life time allowing us 0151 // to throw it away from the qml side (effectively a glorified window hide). 0152 class LifeTimeWrapper : public QObject 0153 { 0154 Q_OBJECT 0155 public: 0156 using QObject::QObject; 0157 Q_INVOKABLE void quit() 0158 { 0159 deleteLater(); 0160 } 0161 }; 0162 0163 int main(int argc, char **argv) 0164 { 0165 QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 0166 QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); 0167 0168 QApplication app(argc, argv); 0169 app.setWindowIcon(QIcon::fromTheme(QStringLiteral("video-card-inactive"))); 0170 app.setDesktopFileName(QStringLiteral("org.kde.nomodeset")); 0171 0172 AuthHelper helper; 0173 if (helper.shouldIgnore()) { 0174 // nomodeset was disabled in this boot already. Don't show the notification anymore until a reboot happens. 0175 qDebug() << "Marker file exists - a change is pending and needs a reboot"; 0176 return 0; 0177 } 0178 0179 KStatusNotifierItem item; 0180 item.setIconByName("video-card-inactive"); 0181 item.setToolTipIconByName("video-card-inactive"); 0182 item.setTitle(i18nc("@title", "Safe Graphics Mode Warning")); 0183 item.setToolTipTitle(i18nc("@title systray tooltip title", "Safe Graphics Mode")); 0184 item.setToolTipSubTitle(i18nc("@info:tooltip", 0185 "The system currently runs in Safe Graphics Mode - graphics card and display performance may be impaired")); 0186 item.setStatus(KStatusNotifierItem::NeedsAttention); 0187 item.setCategory(KStatusNotifierItem::SystemServices); 0188 item.setStandardActionsEnabled(true); 0189 0190 QObject::connect(&item, &KStatusNotifierItem::activateRequested, [&item, &helper] { 0191 if (!qApp->allWindows().isEmpty()) { // already open 0192 const QWindowList windows = qApp->allWindows(); 0193 for (auto window : windows) { 0194 if (window->isVisible()) { 0195 window->requestActivate(); 0196 } 0197 } 0198 return; 0199 } 0200 // No longer needs attention but will still be active. We really want the user to fix nomodeset. 0201 item.setStatus(KStatusNotifierItem::Active); 0202 0203 auto wrapper = new LifeTimeWrapper(&helper); 0204 auto engine = new QQmlApplicationEngine(wrapper); 0205 0206 auto l10nContext = new KLocalizedContext(engine); 0207 l10nContext->setTranslationDomain(QStringLiteral(TRANSLATION_DOMAIN)); 0208 0209 engine->rootContext()->setContextObject(l10nContext); 0210 engine->rootContext()->setContextProperty("AuthHelper", &helper); 0211 engine->rootContext()->setContextProperty("LifeTimeWrapper", wrapper); 0212 0213 KDeclarative::KDeclarative::setupEngine(engine); 0214 0215 engine->load(QUrl(QStringLiteral("qrc:/main.qml"))); 0216 }); 0217 0218 return app.exec(); 0219 } 0220 0221 #include "main.moc"