Add KWin UI tab for license details

This commit is contained in:
wheaney 2025-09-01 15:29:19 -07:00
parent 449fda2e9e
commit b266a20949
12 changed files with 399 additions and 73 deletions

2
.gitignore vendored
View File

@ -5,3 +5,5 @@ gschemas.compiled
out/ out/
*.po~ *.po~
kwin/src/xrdriveripc/xrdriveripc.py kwin/src/xrdriveripc/xrdriveripc.py
kwin/VERSION
kwin/build-test/

View File

@ -62,6 +62,7 @@ cp $XR_DRIVER_DIR/bin/xr_driver_setup $PACKAGE_DIR/bin
# alternative to symlinking, since the Docker build can't resolve to the parent directory # alternative to symlinking, since the Docker build can't resolve to the parent directory
# this file is in .gitignore so it doesn't get duplicated # this file is in .gitignore so it doesn't get duplicated
cp ui/modules/PyXRLinuxDriverIPC/xrdriveripc.py $KWIN_DIR/src/xrdriveripc/xrdriveripc.py cp ui/modules/PyXRLinuxDriverIPC/xrdriveripc.py $KWIN_DIR/src/xrdriveripc/xrdriveripc.py
cp VERSION $KWIN_DIR
pushd $KWIN_DIR pushd $KWIN_DIR
docker-build/init.sh docker-build/init.sh

View File

@ -1,4 +1,11 @@
add_subdirectory(xrdriveripc) add_subdirectory(xrdriveripc)
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/../VERSION" BREEZY_DESKTOP_VERSION_RAW)
if(NOT BREEZY_DESKTOP_VERSION_RAW)
set(BREEZY_DESKTOP_VERSION_RAW "dev")
endif()
string(STRIP "${BREEZY_DESKTOP_VERSION_RAW}" BREEZY_DESKTOP_VERSION)
add_subdirectory(kcm) add_subdirectory(kcm)
kcoreaddons_add_plugin(breezy_desktop INSTALL_NAMESPACE "kwin/effects/plugins/") kcoreaddons_add_plugin(breezy_desktop INSTALL_NAMESPACE "kwin/effects/plugins/")
@ -32,11 +39,12 @@ math(EXPR KWIN_VERSION_ENCODED "${KWIN_VERSION_MAJOR} * 10000 + ${KWIN_VERSION_M
# Export as compile definitions. Keep the original string macro as well. # Export as compile definitions. Keep the original string macro as well.
target_compile_definitions(breezy_desktop PRIVATE target_compile_definitions(breezy_desktop PRIVATE
KWIN_VERSION_STR=\"${KWin_VERSION}\" KWIN_VERSION_STR=\"${KWin_VERSION}\"
KWIN_VERSION_MAJOR=${KWIN_VERSION_MAJOR} KWIN_VERSION_MAJOR=${KWIN_VERSION_MAJOR}
KWIN_VERSION_MINOR=${KWIN_VERSION_MINOR} KWIN_VERSION_MINOR=${KWIN_VERSION_MINOR}
KWIN_VERSION_PATCH=${KWIN_VERSION_PATCH} KWIN_VERSION_PATCH=${KWIN_VERSION_PATCH}
KWIN_VERSION_ENCODED=${KWIN_VERSION_ENCODED} KWIN_VERSION_ENCODED=${KWIN_VERSION_ENCODED}
BREEZY_DESKTOP_VERSION_STR=\"${BREEZY_DESKTOP_VERSION}\"
) )
target_include_directories(breezy_desktop PRIVATE /usr/include/kwin) target_include_directories(breezy_desktop PRIVATE /usr/include/kwin)
target_include_directories(breezy_desktop PRIVATE xrdriveripc) target_include_directories(breezy_desktop PRIVATE xrdriveripc)

View File

@ -221,11 +221,11 @@ void BreezyDesktopEffect::deactivate()
void BreezyDesktopEffect::enableDriver() void BreezyDesktopEffect::enableDriver()
{ {
qCCritical(KWIN_XR) << "\t\t\tBreezy - enableDriver"; qCCritical(KWIN_XR) << "\t\t\tBreezy - enableDriver";
XRDriverIPC::instance().writeConfig({ QJsonObject obj;
{"disabled", false}, obj.insert(QStringLiteral("disabled"), false);
{"output_mode", "external_only"}, obj.insert(QStringLiteral("output_mode"), QStringLiteral("external_only"));
{"external_mode", "breezy_desktop"} obj.insert(QStringLiteral("external_mode"), QStringLiteral("breezy_desktop"));
}); XRDriverIPC::instance().writeConfig(obj);
} }
void BreezyDesktopEffect::realDeactivate() void BreezyDesktopEffect::realDeactivate()

View File

@ -16,3 +16,8 @@ target_link_libraries(breezy_desktop_config
xr_driver_ipc xr_driver_ipc
) )
# Ensure the version macro is available to the KCM as well (defined in parent CMakeLists)
if(BREEZY_DESKTOP_VERSION)
target_compile_definitions(breezy_desktop_config PRIVATE BREEZY_DESKTOP_VERSION_STR=\"${BREEZY_DESKTOP_VERSION}\")
endif()

View File

@ -14,8 +14,17 @@
#include <KPluginFactory> #include <KPluginFactory>
#include <QAction> #include <QAction>
#include <QTableWidget>
#include <QHeaderView>
#include <QPushButton>
#include <QLineEdit>
#include <QProgressBar>
#include <QLabel>
#include <QJsonValue>
#include <QJsonArray>
#include <QFileDialog> #include <QFileDialog>
#include <QKeyEvent>
Q_LOGGING_CATEGORY(KWIN_XR, "kwin.xr") Q_LOGGING_CATEGORY(KWIN_XR, "kwin.xr")
@ -72,6 +81,44 @@ BreezyDesktopEffectConfig::BreezyDesktopEffectConfig(QObject *parent, const KPlu
connect(ui.kcfg_FocusedDisplayDistance, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save); connect(ui.kcfg_FocusedDisplayDistance, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save);
connect(ui.kcfg_AllDisplaysDistance, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save); connect(ui.kcfg_AllDisplaysDistance, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save);
connect(ui.kcfg_DisplaySpacing, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save); connect(ui.kcfg_DisplaySpacing, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save);
if (auto label = widget()->findChild<QLabel*>("labelAppNameVersion")) {
label->setText(QStringLiteral("Breezy Desktop - v%1").arg(QLatin1String(BREEZY_DESKTOP_VERSION_STR)));
}
if (auto btnEmail = widget()->findChild<QPushButton*>("buttonSubmitEmail")) {
connect(btnEmail, &QPushButton::clicked, this, [this]() {
auto edit = widget()->findChild<QLineEdit*>("lineEditLicenseEmail");
auto labelStatus = widget()->findChild<QLabel*>("labelEmailStatus");
if (!edit || edit->text().trimmed().isEmpty() || !labelStatus) return;
setRequestInProgress({edit, sender()}, true);
labelStatus->setVisible(false);
bool success = XRDriverIPC::instance().requestToken(edit->text().trimmed().toStdString());
showStatus(labelStatus, success, success ? tr("Request sent. Check your email for instructions.") : tr("Failed to send request."));
setRequestInProgress({edit, sender()}, false);
});
if (auto emailEdit = widget()->findChild<QLineEdit*>("lineEditLicenseEmail")) {
emailEdit->installEventFilter(this);
}
}
if (auto btnToken = widget()->findChild<QPushButton*>("buttonSubmitToken")) {
connect(btnToken, &QPushButton::clicked, this, [this]() {
auto edit = widget()->findChild<QLineEdit*>("lineEditLicenseToken");
auto labelStatus = widget()->findChild<QLabel*>("labelTokenStatus");
if (!edit || edit->text().trimmed().isEmpty() || !labelStatus) return;
setRequestInProgress({edit, sender()}, true);
labelStatus->setVisible(false);
bool success = XRDriverIPC::instance().verifyToken(edit->text().trimmed().toStdString());
if (success) {
XRDriverIPC::instance().writeControlFlags({{"refresh_device_license", true}});
}
showStatus(labelStatus, success, success ? tr("Your license has been refreshed.") : tr("Invalid or expired token."));
setRequestInProgress({edit, sender()}, false);
});
if (auto tokenEdit = widget()->findChild<QLineEdit*>("lineEditLicenseToken")) {
tokenEdit->installEventFilter(this);
}
}
} }
BreezyDesktopEffectConfig::~BreezyDesktopEffectConfig() BreezyDesktopEffectConfig::~BreezyDesktopEffectConfig()
@ -133,11 +180,12 @@ void BreezyDesktopEffectConfig::updateUnmanagedState()
void BreezyDesktopEffectConfig::pollDriverState() void BreezyDesktopEffectConfig::pollDriverState()
{ {
auto &bridge = XRDriverIPC::instance(); auto &bridge = XRDriverIPC::instance();
auto stateOpt = bridge.retrieveDriverState(); auto stateJsonOpt = bridge.retrieveDriverState();
const XRDict &state = stateOpt.value(); if (!stateJsonOpt) return;
auto stateJson = stateJsonOpt.value();
m_connectedDeviceBrand = QString::fromStdString(XRDriverIPC::string(state, XRStateEntry::ConnectedDeviceBrand)); m_connectedDeviceBrand = stateJson.value(QStringLiteral("connected_device_brand")).toString();
m_connectedDeviceModel = QString::fromStdString(XRDriverIPC::string(state, XRStateEntry::ConnectedDeviceModel)); m_connectedDeviceModel = stateJson.value(QStringLiteral("connected_device_model")).toString();
const bool wasDeviceConnected = m_deviceConnected; const bool wasDeviceConnected = m_deviceConnected;
m_deviceConnected = !m_connectedDeviceBrand.isEmpty() && !m_connectedDeviceModel.isEmpty(); m_deviceConnected = !m_connectedDeviceBrand.isEmpty() && !m_connectedDeviceModel.isEmpty();
@ -146,6 +194,137 @@ void BreezyDesktopEffectConfig::pollDriverState()
QStringLiteral("%1 %2 connected").arg(m_connectedDeviceBrand, m_connectedDeviceModel) : QStringLiteral("%1 %2 connected").arg(m_connectedDeviceBrand, m_connectedDeviceModel) :
QStringLiteral("No device connected")); QStringLiteral("No device connected"));
} }
refreshLicenseUi(stateJson);
}
void BreezyDesktopEffectConfig::showStatus(QLabel *label, bool success, const QString &message) {
if (!label) return;
QPalette pal = label->palette();
pal.setColor(QPalette::WindowText, success ? QColor(Qt::darkGreen) : QColor(Qt::red));
label->setPalette(pal);
label->setText(message);
label->setVisible(true);
}
void BreezyDesktopEffectConfig::setRequestInProgress(std::initializer_list<QObject*> widgets, bool inProgress) {
for (auto *obj : widgets) {
if (auto *w = qobject_cast<QWidget*>(obj)) {
w->setEnabled(!inProgress);
}
}
}
bool BreezyDesktopEffectConfig::eventFilter(QObject *watched, QEvent *event) {
if (event->type() == QEvent::KeyPress) {
auto *ke = static_cast<QKeyEvent*>(event);
if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
if (auto *edit = qobject_cast<QLineEdit*>(watched)) {
// Determine which button to invoke
QString objName = edit->objectName();
QString buttonName;
if (objName == QLatin1String("lineEditLicenseEmail")) buttonName = QStringLiteral("buttonSubmitEmail");
else if (objName == QLatin1String("lineEditLicenseToken")) buttonName = QStringLiteral("buttonSubmitToken");
if (!buttonName.isEmpty()) {
if (auto btn = widget()->findChild<QPushButton*>(buttonName)) {
// Trigger click but stop further propagation so dialog doesn't accept/close
QMetaObject::invokeMethod(btn, "click", Qt::QueuedConnection);
event->accept();
return true; // eat event
}
}
}
}
}
return KCModule::eventFilter(watched, event);
}
static QString secondsToRemainingString(qint64 secs) {
if (secs <= 0) return {};
if (secs / 60 < 60) {
return QObject::tr("less than an hour");
}
if (secs / 3600 < 24) {
qint64 hours = secs / 3600;
if (hours == 1) return QObject::tr("1 hour");
return QObject::tr("%1 hours").arg(hours);
}
if ((secs / 86400) < 30 ) {
qint64 days = secs / 86400;
if (days == 1) return QObject::tr("1 day");
return QObject::tr("%1 days").arg(days);
}
return {};
}
void BreezyDesktopEffectConfig::refreshLicenseUi(const QJsonObject &rootObj) {
auto tab = widget()->findChild<QWidget*>("tabLicenseDetails");
if (!tab) return;
auto labelSummary = tab->findChild<QLabel*>("labelLicenseSummary");
if (!labelSummary) return;
QString status = tr("disabled");
QString renewalDescriptor = QStringLiteral("");
auto uiView = rootObj.value(QStringLiteral("ui_view")).toObject();
auto license = uiView.value(QStringLiteral("license")).toObject();
bool warningState = true;
if (!license.isEmpty()) {
auto tiers = license.value(QStringLiteral("tiers")).toObject();
QJsonValue prodTier = tiers.value(QStringLiteral("subscriber"));
QJsonObject prodTierObj = prodTier.isUndefined() ? QJsonObject() : prodTier.toObject();
auto features = license.value(QStringLiteral("features")).toObject();
QJsonValue prodFeature = features.value(QStringLiteral("productivity_basic"));
QJsonObject prodFeatureObj = prodFeature.isUndefined() ? QJsonObject() : prodFeature.toObject();
if (!prodTierObj.isEmpty() && !prodFeatureObj.isEmpty()) {
const QString activePeriod = prodTierObj.value(QStringLiteral("active_period")).toString();
const bool isActive = !activePeriod.isEmpty();
if (isActive) {
status = tr("active");
QString periodDescriptor = activePeriod.contains(QStringLiteral("lifetime"), Qt::CaseInsensitive) ?
tr("lifetime") :
tr("%1 license").arg(activePeriod);
QString timeDescriptor;
auto secsVal = prodTierObj.value(QStringLiteral("funds_needed_in_seconds"));
if (secsVal.isDouble()) {
qint64 secs = static_cast<qint64>(secsVal.toDouble());
QString remaining = secondsToRemainingString(secs);
if (!remaining.isEmpty()) {
timeDescriptor = tr("%1 remaining").arg(remaining);
}
}
renewalDescriptor = tr(" (%1)").arg(periodDescriptor);
warningState = !timeDescriptor.isEmpty();
if (warningState) {
auto fundsNeeded = prodTierObj.value(QStringLiteral("funds_needed_by_period")).toObject().value(activePeriod).toDouble();
if (fundsNeeded > 0.0) {
QString fundsNeededDescriptor = tr("$%1 USD to renew").arg(fundsNeeded);
renewalDescriptor = tr(" (%1, %2, %3)").arg(periodDescriptor, fundsNeededDescriptor, timeDescriptor);
}
}
} else {
QJsonValue isEnabled = prodFeatureObj.value(QStringLiteral("is_enabled"));
QJsonValue isTrial = prodFeatureObj.value(QStringLiteral("is_trial"));
if (isEnabled.toBool() && isTrial.toBool()) {
status = tr("in trial");
auto secsVal = prodFeatureObj.value(QStringLiteral("funds_needed_in_seconds"));
if (secsVal.isDouble()) {
qint64 secs = static_cast<qint64>(secsVal.toDouble());
QString remaining = secondsToRemainingString(secs);
warningState = !remaining.isEmpty();
if (warningState) {
QString timeDescriptor = tr("%1 remaining").arg(remaining);
renewalDescriptor = tr(" (%1)").arg(timeDescriptor);
}
}
}
}
}
}
labelSummary->setText(tr("Productivity Tier features are %1%2").arg(status, renewalDescriptor));
} }
#include "breezydesktopeffectkcm.moc" #include "breezydesktopeffectkcm.moc"

View File

@ -30,6 +30,10 @@ private:
void updateConfigFromUi(); void updateConfigFromUi();
void updateUnmanagedState(); void updateUnmanagedState();
void pollDriverState(); void pollDriverState();
void refreshLicenseUi(const QJsonObject &rootObj);
void showStatus(QLabel *label, bool success, const QString &message);
void setRequestInProgress(std::initializer_list<QObject*> widgets, bool inProgress);
bool eventFilter(QObject *watched, QEvent *event) override;
::Ui::BreezyDesktopEffectConfig ui; ::Ui::BreezyDesktopEffectConfig ui;
@ -39,4 +43,5 @@ private:
QString m_connectedDeviceBrand; QString m_connectedDeviceBrand;
QString m_connectedDeviceModel; QString m_connectedDeviceModel;
QTimer m_statePollTimer; // periodic driver state polling QTimer m_statePollTimer; // periodic driver state polling
bool m_licenseLoading = false;
}; };

View File

@ -141,6 +141,167 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tabLicenseDetails">
<attribute name="title">
<string>&amp;License Details</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayoutLicense">
<item>
<widget class="QLabel" name="labelLicenseSummary">
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="visible">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBoxEmail">
<property name="title">
<string>Request a token</string>
</property>
<layout class="QGridLayout" name="gridLayoutEmail">
<item row="0" column="0">
<widget class="QLineEdit" name="lineEditLicenseEmail">
<property name="placeholderText">
<string>you@example.com</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="buttonSubmitEmail">
<property name="text">
<string>Submit</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="labelEmailStatus">
<property name="text">
<string/>
</property>
<property name="visible">
<bool>false</bool>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBoxToken">
<property name="title">
<string>Verify token</string>
</property>
<layout class="QGridLayout" name="gridLayoutToken">
<item row="0" column="0">
<widget class="QLineEdit" name="lineEditLicenseToken"/>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="buttonSubmitToken">
<property name="text">
<string>Verify</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="labelTokenStatus">
<property name="text">
<string/>
</property>
<property name="visible">
<bool>false</bool>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacerLicense">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabAbout">
<attribute name="title">
<string>&amp;About</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayoutAbout">
<item>
<widget class="QLabel" name="labelAppNameVersion">
<property name="text">
<string>Breezy Desktop Effect - v0.0.0</string>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignVCenter</set>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
</widget>
</item>
<item>
<widget class="QLabel">
<property name="text">
<string>Author: Wayne Heaney &lt;wayne@xronlinux.com&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel">
<property name="text">
<string>License: GPL-3.0</string>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacerAbout">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -27,14 +27,6 @@ XRDriverIPC &XRDriverIPC::instance() {
return inst; return inst;
} }
std::string XRDriverIPC::string(const XRDict &dict, const std::string &key) {
auto it = dict.find(key);
if (it != dict.end() && std::holds_alternative<std::string>(it->second)) {
return std::get<std::string>(it->second);
}
return {};
}
std::string XRDriverIPC::configHome() const { std::string XRDriverIPC::configHome() const {
QString configHome = QString::fromUtf8(qgetenv("XDG_CONFIG_HOME")); QString configHome = QString::fromUtf8(qgetenv("XDG_CONFIG_HOME"));
if (configHome.isEmpty()) { if (configHome.isEmpty()) {
@ -75,47 +67,24 @@ QByteArray XRDriverIPC::invokePython(const QString &method,
return proc.readAllStandardOutput().trimmed(); return proc.readAllStandardOutput().trimmed();
} }
static XRDict jsonToXRDict(const QJsonObject &obj) { std::optional<QJsonObject> XRDriverIPC::retrieveConfig() {
XRDict out;
for (auto it = obj.begin(); it != obj.end(); ++it) {
const QString &k = it.key();
const QJsonValue &v = it.value();
if (v.isBool()) out[k.toStdString()] = v.toBool();
else if (v.isDouble() && std::floor(v.toDouble()) == v.toDouble())
out[k.toStdString()] = (int)v.toDouble();
else if (v.isDouble()) out[k.toStdString()] = v.toDouble();
else if (v.isString()) out[k.toStdString()] = v.toString().toStdString();
else out[k.toStdString()] = std::monostate{};
}
return out;
}
std::optional<XRDict> XRDriverIPC::retrieveConfig() {
QByteArray out = invokePython(QStringLiteral("retrieve_config"), {}, QStringLiteral("1")); QByteArray out = invokePython(QStringLiteral("retrieve_config"), {}, QStringLiteral("1"));
if (out.isEmpty()) return std::nullopt; if (out.isEmpty()) return std::nullopt;
QJsonParseError err; auto doc = QJsonDocument::fromJson(out, &err); QJsonParseError err; auto doc = QJsonDocument::fromJson(out, &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) return std::nullopt; if (err.error != QJsonParseError::NoError || !doc.isObject()) return std::nullopt;
return jsonToXRDict(doc.object()); return doc.object();
} }
std::optional<XRDict> XRDriverIPC::retrieveDriverState() { std::optional<QJsonObject> XRDriverIPC::retrieveDriverState() {
QByteArray out = invokePython(QStringLiteral("retrieve_driver_state"), {}, {}); QByteArray out = invokePython(QStringLiteral("retrieve_driver_state"), {}, {});
if (out.isEmpty()) return std::nullopt; if (out.isEmpty()) return std::nullopt;
QJsonParseError err; auto doc = QJsonDocument::fromJson(out, &err); QJsonParseError err; auto doc = QJsonDocument::fromJson(out, &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) return std::nullopt; if (err.error != QJsonParseError::NoError || !doc.isObject()) return std::nullopt;
return jsonToXRDict(doc.object()); return doc.object();
} }
bool XRDriverIPC::writeConfig(const XRDict &configUpdate) { bool XRDriverIPC::writeConfig(const QJsonObject &configUpdate) {
QJsonObject obj; QByteArray payload = QJsonDocument(configUpdate).toJson(QJsonDocument::Compact);
for (const auto &kv : configUpdate) {
const std::string &k = kv.first; const XRValue &v = kv.second;
if (std::holds_alternative<bool>(v)) obj.insert(QString::fromStdString(k), std::get<bool>(v));
else if (std::holds_alternative<int>(v)) obj.insert(QString::fromStdString(k), std::get<int>(v));
else if (std::holds_alternative<double>(v)) obj.insert(QString::fromStdString(k), std::get<double>(v));
else if (std::holds_alternative<std::string>(v)) obj.insert(QString::fromStdString(k), QString::fromStdString(std::get<std::string>(v)));
}
QByteArray payload = QJsonDocument(obj).toJson(QJsonDocument::Compact);
QByteArray out = invokePython(QStringLiteral("write_config"), payload, {}); QByteArray out = invokePython(QStringLiteral("write_config"), payload, {});
return !out.isEmpty(); return !out.isEmpty();
} }
@ -130,13 +99,13 @@ bool XRDriverIPC::writeControlFlags(const std::map<std::string, bool> &flags) {
bool XRDriverIPC::requestToken(const std::string &email) { bool XRDriverIPC::requestToken(const std::string &email) {
QByteArray out = invokePython(QStringLiteral("request_token"), {}, QString::fromStdString(email)); QByteArray out = invokePython(QStringLiteral("request_token"), {}, QString::fromStdString(email));
if (out.isEmpty()) return false; if (out.isEmpty()) return false;
auto value = QJsonValue(QString::fromStdString(out.toStdString())); QString result = QString::fromUtf8(out).trimmed().toLower();
return value.isBool() ? value.toBool() : false; return result == QStringLiteral("true");
} }
bool XRDriverIPC::verifyToken(const std::string &token) { bool XRDriverIPC::verifyToken(const std::string &token) {
QByteArray out = invokePython(QStringLiteral("verify_token"), {}, QString::fromStdString(token)); QByteArray out = invokePython(QStringLiteral("verify_token"), {}, QString::fromStdString(token));
if (out.isEmpty()) return false; if (out.isEmpty()) return false;
auto value = QJsonValue(QString::fromStdString(out.toStdString())); QString result = QString::fromUtf8(out).trimmed().toLower();
return value.isBool() ? value.toBool() : false; return result == QStringLiteral("true");
} }

View File

@ -1,13 +1,10 @@
// C++ bridge now invoking xrdriveripc via external python process // C++ bridge now invoking xrdriveripc via external python process
#pragma once #pragma once
#include <string>
#include <map>
#include <variant>
#include <vector>
#include <optional>
#include <QString> #include <QString>
#include <QByteArray> #include <QByteArray>
#include <QJsonObject>
#include <optional>
// Export header generated by CMake (GenerateExportHeader) // Export header generated by CMake (GenerateExportHeader)
#ifdef __has_include #ifdef __has_include
@ -78,18 +75,13 @@ namespace XRConfigEntry {
inline constexpr const char *Debug = "debug"; inline constexpr const char *Debug = "debug";
} }
// Simple variant type for config/state key values we care about
using XRValue = std::variant<std::monostate, bool, int, double, std::string>;
using XRDict = std::map<std::string, XRValue>;
class XR_DRIVER_IPC_EXPORT XRDriverIPC { class XR_DRIVER_IPC_EXPORT XRDriverIPC {
public: public:
static XRDriverIPC &instance(); static XRDriverIPC &instance();
static std::string string(const XRDict &dict, const std::string &key);
std::optional<XRDict> retrieveConfig(); std::optional<QJsonObject> retrieveConfig();
std::optional<XRDict> retrieveDriverState(); std::optional<QJsonObject> retrieveDriverState();
bool writeConfig(const XRDict &configUpdate); bool writeConfig(const QJsonObject &configUpdate);
bool writeControlFlags(const std::map<std::string, bool> &flags); bool writeControlFlags(const std::map<std::string, bool> &flags);
bool requestToken(const std::string &email); bool requestToken(const std::string &email);
bool verifyToken(const std::string &token); bool verifyToken(const std::string &token);

View File

@ -13,6 +13,13 @@ import os
import sys import sys
import traceback import traceback
class Logger:
def info(self, *args, **kwargs):
pass
def error(self, *args, **kwargs):
pass
def main() -> int: def main() -> int:
# Ensure the current directory (where xrdriveripc.py lives) is in sys.path # Ensure the current directory (where xrdriveripc.py lives) is in sys.path
@ -32,7 +39,7 @@ def main() -> int:
return 2 return 2
config_home = os.environ.get("BREEZY_CONFIG_HOME") config_home = os.environ.get("BREEZY_CONFIG_HOME")
inst = xrdriveripc.XRDriverIPC(config_home=config_home) inst = xrdriveripc.XRDriverIPC(logger=Logger(), config_home=config_home)
arg = os.environ.get("BREEZY_ARG") arg = os.environ.get("BREEZY_ARG")
payload_raw = os.environ.get("BREEZY_PAYLOAD") payload_raw = os.environ.get("BREEZY_PAYLOAD")

View File

@ -26,12 +26,9 @@ class LicenseFeatureRow(Adw.ActionRow):
self.set_subtitle(f"{status}{details}") self.set_subtitle(f"{status}{details}")
def _feature_name(self, feature): def _feature_name(self, feature):
print(f"Translating feature: {feature}")
print(f"_ is: {_}")
feature_names = { feature_names = {
'sbs': lambda: gettext.gettext('Side-by-side mode (gaming)'), 'sbs': lambda: _('Side-by-side mode (gaming)'),
'smooth_follow': lambda: _('Smooth Follow (gaming)'), 'smooth_follow': lambda: _('Smooth Follow (gaming)'),
'productivity_basic': lambda: _('Breezy Desktop (productivity)') 'productivity_basic': lambda: _('Breezy Desktop (productivity)')
} }
print(f"Translated string: {feature_names[feature]()}")
return feature_names[feature]() return feature_names[feature]()