Commit 35dcbd3d authored by Aleix Pol Gonzalez's avatar Aleix Pol Gonzalez 🐧
Browse files

Improve Plasma user feedback introspection

Summary:
Before loading the view, query all plasma processes and show why they're using
KUserFeedback.

Depends on D26569

Test Plan: Manual testing

Reviewers: #plasma, davidedmundson

Reviewed By: #plasma, davidedmundson

Subscribers: ngraham, davidedmundson, plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D26570
parent 2212a9af
......@@ -25,11 +25,26 @@
#include <KPluginFactory>
#include <KAboutData>
#include <KLocalizedString>
#include <QVector>
#include <KUserFeedback/Provider>
#include <KUserFeedback/FeedbackConfigUiController>
K_PLUGIN_CLASS_WITH_JSON(Feedback, "kcm_feedback.json");
//Program to icon hash
static QHash<QString, QString> s_programs = {
{ "plasmashell", "plasmashell" },
{ "plasma-discover", "plasmadiscover" }
};
inline void swap(QJsonValueRef v1, QJsonValueRef v2)
{
QJsonValue temp(v1);
v1 = QJsonValue(v2);
v2 = temp;
}
Feedback::Feedback(QObject *parent, const QVariantList &args)
: KQuickAddons::ConfigModule(parent)
//UserFeedback.conf is used by KUserFeedback which uses QSettings and won't go through globals
......@@ -43,10 +58,73 @@ Feedback::Feedback(QObject *parent, const QVariantList &args)
connect(this, &Feedback::plasmaFeedbackLevelChanged, this, [this](){
setNeedsSave(true);
});
QVector<QProcess*> processes;
for (const auto exec: s_programs.keys()) {
QProcess* p = new QProcess(this);
p->setProgram(exec);
p->setArguments({QStringLiteral("--feedback")});
p->start();
connect(p, QOverload<int>::of(&QProcess::finished), this, &Feedback::programFinished);
processes << p;
}
}
Feedback::~Feedback() = default;
void Feedback::programFinished(int exitCode)
{
auto mo = KUserFeedback::Provider::staticMetaObject;
const int modeEnumIdx = mo.indexOfEnumerator("TelemetryMode");
Q_ASSERT(modeEnumIdx >= 0);
const auto modeEnum = mo.enumerator(modeEnumIdx);
QProcess* p = qobject_cast<QProcess*>(sender());
const QString program = p->program();
if (exitCode) {
qWarning() << "Could not check" << program;
return;
}
QTextStream stream(p);
for (QString line; stream.readLineInto(&line); ) {
int sepIdx = line.indexOf(QLatin1String(": "));
if (sepIdx < 0) {
break;
}
const QString mode = line.left(sepIdx);
bool ok;
const int modeValue = modeEnum.keyToValue(qPrintable(mode), &ok);
if (!ok) {
qWarning() << "error:" << mode << "is not a valid mode";
continue;
}
const QString description = line.mid(sepIdx + 1);
m_uses[modeValue][description] << s_programs[program];
}
p->deleteLater();
m_feedbackSources = {};
for (auto it = m_uses.constBegin(), itEnd = m_uses.constEnd(); it != itEnd; ++it) {
const auto modeUses = *it;
for (auto itMode = modeUses.constBegin(), itModeEnd = modeUses.constEnd(); itMode != itModeEnd; ++itMode) {
m_feedbackSources << QJsonObject({
{ "mode", it.key() },
{ "icons", *itMode },
{ "description", itMode.key() }
});
}
}
std::sort(m_feedbackSources.begin(), m_feedbackSources.end(), [](const QJsonValue& valueL, const QJsonValue& valueR){
const QJsonObject objL(valueL.toObject()), objR(valueR.toObject());
const auto modeL = objL["mode"].toInt(), modeR = objR["mode"].toInt();
return modeL < modeR || (modeL == modeR && objL["description"].toString() < objR["description"].toString());
});
Q_EMIT feedbackSourcesChanged();
}
bool Feedback::feedbackEnabled() const
{
KUserFeedback::Provider p;
......
......@@ -28,6 +28,7 @@ class Feedback : public KQuickAddons::ConfigModule
{
Q_OBJECT
Q_PROPERTY(QJsonArray feedbackSources MEMBER m_feedbackSources NOTIFY feedbackSourcesChanged)
Q_PROPERTY(bool feedbackEnabled READ feedbackEnabled CONSTANT)
Q_PROPERTY(int plasmaFeedbackLevel READ plasmaFeedbackLevel WRITE setPlasmaFeedbackLevel NOTIFY plasmaFeedbackLevelChanged)
......@@ -40,15 +41,20 @@ class Feedback : public KQuickAddons::ConfigModule
void setPlasmaFeedbackLevel(int plasmaFeedbackLevel);
void programFinished(int exitCode);
public Q_SLOTS:
void load() override;
void save() override;
void defaults() override;
Q_SIGNALS:
void feedbackSourcesChanged();
void plasmaFeedbackLevelChanged(bool plasmaFeedbackLevel);
private:
KSharedConfig::Ptr m_plasmaConfig;
int m_plasmaFeedbackLevel = 0;
QHash<int, QHash<QString, QJsonArray>> m_uses;
QJsonArray m_feedbackSources;
};
......@@ -70,6 +70,7 @@ SimpleKCM {
QQC2.Slider {
id: statisticsModeSlider
Kirigami.FormData.label: i18n("Plasma:")
readonly property var currentMode: modeOptions[value]
enabled: kcm.feedbackEnabled
Layout.fillWidth: true
Layout.minimumWidth: Kirigami.Units.gridUnit * 21
......@@ -81,20 +82,15 @@ SimpleKCM {
stepSize: 1
snapMode: QQC2.Slider.SnapAlways
function findIndex(array, what) {
function findIndex(array, what, defaultValue) {
for (var v in array) {
if (array[v] == what)
return v;
}
return null;
return defaultValue;
}
Connections {
target: kcm
onPlasmaFeedbackLevelChanged: {
statisticsModeSlider.value = statisticsModeSlider.findIndex(statisticsModeSlider.modeOptions, kcm.plasmaFeedbackLevel)
}
}
value: findIndex(modeOptions, kcm.plasmaFeedbackLevel, 0)
onMoved: {
kcm.plasmaFeedbackLevel = modeOptions[value]
......@@ -111,7 +107,7 @@ SimpleKCM {
Layout.maximumWidth: statisticsModeSlider.width
wrapMode: Text.WordWrap
level: 3
text: feedbackController.telemetryName(statisticsModeSlider.modeOptions[statisticsModeSlider.value])
text: feedbackController.telemetryName(statisticsModeSlider.currentMode)
}
QQC2.Label {
Layout.alignment: Qt.AlignHCenter
......@@ -120,7 +116,37 @@ SimpleKCM {
text: {
feedbackController.applicationName
return feedbackController.telemetryDescription(statisticsModeSlider.modeOptions[statisticsModeSlider.value])
return feedbackController.telemetryDescription(statisticsModeSlider.currentMode)
}
}
ColumnLayout {
Layout.maximumWidth: root.width * 0.5
Repeater {
model: kcm.feedbackSources
delegate: QQC2.Label {
visible: modelData.mode <= statisticsModeSlider.currentMode
text: "· " + modelData.description
MouseArea {
anchors.fill: parent
hoverEnabled: true
QQC2.ToolTip {
width: iconsLayout.implicitWidth + Kirigami.Units.largeSpacing * 2
height: iconsLayout.implicitHeight + Kirigami.Units.smallSpacing * 2
visible: parent.containsMouse
RowLayout {
id: iconsLayout
anchors.centerIn: parent
Repeater {
model: modelData.icons
delegate: Image {
source: "image://icon/" + modelData
}
}
}
}
}
}
}
opacity: 0.6
}
......
......@@ -30,6 +30,9 @@
#include <KAboutData>
#include <KQuickAddons/QtQuickSettings>
#ifdef WITH_KUSERFEEDBACKCORE
#include <KUserFeedback/Provider>
#endif
#include <kdbusservice.h>
#include <klocalizedstring.h>
......@@ -117,12 +120,20 @@ int main(int argc, char *argv[])
QCommandLineOption testOption(QStringList() << QStringLiteral("test"),
i18n("Enables test mode and specifies the layout javascript file to set up the testing environment"), i18n("file"), QStringLiteral("layout.js"));
#ifdef WITH_KUSERFEEDBACKCORE
QCommandLineOption feedbackOption(QStringList() << QStringLiteral("feedback"),
i18n("Lists the available options for user feedback"));
#endif
cliOptions.addOption(dbgOption);
cliOptions.addOption(noRespawnOption);
cliOptions.addOption(shellPluginOption);
cliOptions.addOption(standaloneOption);
cliOptions.addOption(testOption);
cliOptions.addOption(replaceOption);
#ifdef WITH_KUSERFEEDBACKCORE
cliOptions.addOption(feedbackOption);
#endif
aboutData.setupCommandLine(&cliOptions);
cliOptions.process(app);
......@@ -138,6 +149,14 @@ int main(int argc, char *argv[])
ShellCorona* corona = new ShellCorona(&app);
corona->setShell(cliOptions.value(shellPluginOption));
#ifdef WITH_KUSERFEEDBACKCORE
if (cliOptions.isSet(feedbackOption)) {
QTextStream(stdout) << corona->feedbackProvider()->describeDataSources();
return 0;
}
#endif
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, corona, &QObject::deleteLater);
if (!cliOptions.isSet(noRespawnOption) && !cliOptions.isSet(testOption)) {
......
......@@ -110,6 +110,9 @@ ShellCorona::ShellCorona(QObject *parent)
m_waylandPlasmaShell(nullptr),
m_closingDown(false),
m_strutManager(new StrutManager(this))
#ifdef WITH_KUSERFEEDBACKCORE
, m_feedbackProvider(new KUserFeedback::Provider(this))
#endif
{
setupWaylandIntegration();
qmlRegisterUncreatableType<DesktopView>("org.kde.plasma.shell", 2, 0, "Desktop", QStringLiteral("It is not possible to create objects of type Desktop"));
......@@ -317,25 +320,25 @@ void ShellCorona::setShell(const QString &shell)
t->setThemeName(themeName);
}
#ifdef WITH_KUSERFEEDBACKCORE
auto feedbackProvider = new KUserFeedback::Provider(this);
feedbackProvider->setProductIdentifier(QStringLiteral("org.kde.plasmashell"));
feedbackProvider->setFeedbackServer(QUrl(QStringLiteral("https://telemetry.kde.org/")));
feedbackProvider->setSubmissionInterval(7);
feedbackProvider->setApplicationStartsUntilEncouragement(5);
feedbackProvider->setEncouragementDelay(30);
feedbackProvider->addDataSource(new KUserFeedback::ApplicationVersionSource);
feedbackProvider->addDataSource(new KUserFeedback::CompilerInfoSource);
feedbackProvider->addDataSource(new KUserFeedback::PlatformInfoSource);
feedbackProvider->addDataSource(new KUserFeedback::QtVersionSource);
feedbackProvider->addDataSource(new KUserFeedback::UsageTimeSource);
feedbackProvider->addDataSource(new KUserFeedback::OpenGLInfoSource);
feedbackProvider->addDataSource(new KUserFeedback::ScreenInfoSource);
feedbackProvider->addDataSource(new PanelCountSource(this));
m_feedbackProvider->setProductIdentifier(QStringLiteral("org.kde.plasmashell"));
m_feedbackProvider->setFeedbackServer(QUrl(QStringLiteral("https://telemetry.kde.org/")));
m_feedbackProvider->setSubmissionInterval(7);
m_feedbackProvider->setApplicationStartsUntilEncouragement(5);
m_feedbackProvider->setEncouragementDelay(30);
m_feedbackProvider->addDataSource(new KUserFeedback::ApplicationVersionSource);
m_feedbackProvider->addDataSource(new KUserFeedback::CompilerInfoSource);
m_feedbackProvider->addDataSource(new KUserFeedback::PlatformInfoSource);
m_feedbackProvider->addDataSource(new KUserFeedback::QtVersionSource);
m_feedbackProvider->addDataSource(new KUserFeedback::UsageTimeSource);
m_feedbackProvider->addDataSource(new KUserFeedback::OpenGLInfoSource);
m_feedbackProvider->addDataSource(new KUserFeedback::ScreenInfoSource);
m_feedbackProvider->addDataSource(new PanelCountSource(this));
{
auto plasmaConfig = KSharedConfig::openConfig(QStringLiteral("PlasmaUserFeedback"));
feedbackProvider->setTelemetryMode(KUserFeedback::Provider::TelemetryMode(plasmaConfig->group("Global").readEntry("FeedbackLevel", int(KUserFeedback::Provider::NoTelemetry))));
m_feedbackProvider->setTelemetryMode(KUserFeedback::Provider::TelemetryMode(plasmaConfig->group("Global").readEntry("FeedbackLevel", int(KUserFeedback::Provider::NoTelemetry))));
}
#endif
......
......@@ -66,6 +66,10 @@ namespace KWayland
class PlasmaShell;
}
}
namespace KUserFeedback
{
class Provider;
};
class ShellCorona : public Plasma::Corona, QDBusContext
{
......@@ -119,6 +123,8 @@ public:
QString defaultContainmentPlugin() const;
KUserFeedback::Provider* feedbackProvider() const { return m_feedbackProvider; }
Q_SIGNALS:
void glInitializationFailed();
......@@ -263,6 +269,7 @@ private:
QString m_testModeLayout;
StrutManager *m_strutManager;
KUserFeedback::Provider *m_feedbackProvider = nullptr;
};
#endif // SHELLCORONA_H
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment