Add KWin UI tab for license details
This commit is contained in:
parent
449fda2e9e
commit
b266a20949
|
|
@ -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/
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,167 @@
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QWidget" name="tabLicenseDetails">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>&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>&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 <wayne@xronlinux.com></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>
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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]()
|
||||||
Loading…
Reference in New Issue