Fix issue where Qt 6.5 and below don't have the Quick3D.Helpers modules, fall back to a standard Rectange geometry, disable curved display UI option

Add better logging to the python IPC integration
This commit is contained in:
wheaney 2025-09-22 13:47:12 -07:00
parent 2bcc2e07e3
commit 40a8613aa1
9 changed files with 208 additions and 91 deletions

View File

@ -25,7 +25,7 @@ print_missing_dependencies() {
printf "\n\033[1;31mMissing required components\033[0m\n"
echo ""
echo "Install the corresponding packages with your package manager, then rerun this setup:"
echo " Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y extra-cmake-modules kwin-dev libkf6config-dev libkf6configwidgets-dev libkf6coreaddons-dev libkf6kcmutils-dev libkf6globalaccel-dev libkf6i18n-dev libkf6windowsystem-dev libkf6xmlgui-dev qt6-base-dev qt6-declarative-dev libdrm-dev"
echo " Debian/Ubuntu: sudo apt-get update && sudo apt-get install -y extra-cmake-modules kwin-dev libkf6config-dev libkf6configwidgets-dev libkf6coreaddons-dev libkf6kcmutils-dev libkf6globalaccel-dev libkf6i18n-dev libkf6windowsystem-dev libkf6xmlgui-dev qt6-base-dev qt6-declarative-dev libdrm-dev && (sudo apt-get install -y qml6-module-qtquick3d-helpers || true)"
echo " Fedora/RHEL: sudo dnf install -y extra-cmake-modules kwin-devel kf6-kconfig-devel kf6-kconfigwidgets-devel kf6-kcoreaddons-devel kf6-kcmutils-devel kf6-kglobalaccel-devel kf6-ki18n-devel kf6-kwindowsystem-devel kf6-kxmlgui-devel qt6-qtbase-devel qt6-qttools-devel wayland-devel libepoxy-devel libdrm-devel"
echo " Arch: sudo pacman -S --needed extra-cmake-modules qt6-base qt6-declarative qt6-tools qt6-quick3d kconfig kconfigwidgets kcoreaddons kglobalaccel ki18n kcmutils kxmlgui kwindowsystem kwin"
echo ""

View File

@ -55,6 +55,10 @@ public Q_SLOTS:
return m_effect->listVirtualDisplays();
}
bool CurvedDisplaySupported() {
return m_effect->curvedDisplaySupported();
}
private:
KWin::BreezyDesktopEffect *m_effect;
};
@ -240,7 +244,7 @@ void BreezyDesktopEffect::reconfigure(ReconfigureFlags)
if (m_removeVirtualDisplaysOnDisable != removeVD) { m_removeVirtualDisplaysOnDisable = removeVD; Q_EMIT removeVirtualDisplaysOnDisableChanged(); }
if (m_mirrorPhysicalDisplays != mirrorPhysicalDisplays) { m_mirrorPhysicalDisplays = mirrorPhysicalDisplays; Q_EMIT mirrorPhysicalDisplaysChanged(); }
bool curved = BreezyDesktopConfig::curvedDisplay();
bool curved = BreezyDesktopConfig::curvedDisplay() && m_curvedDisplaySupported;
if (m_curvedDisplay != curved) { m_curvedDisplay = curved; Q_EMIT curvedDisplayChanged(); }
// this one doesn't have a signal, just always assign it
@ -523,6 +527,22 @@ bool BreezyDesktopEffect::curvedDisplay() const {
return m_curvedDisplay;
}
bool BreezyDesktopEffect::curvedDisplaySupported() const {
return m_curvedDisplaySupported;
}
void BreezyDesktopEffect::setCurvedDisplaySupported(bool supported) {
if (m_curvedDisplaySupported != supported) {
m_curvedDisplaySupported = supported;
Q_EMIT curvedDisplaySupportedChanged();
}
if (!supported && m_curvedDisplay) {
m_curvedDisplay = false;
Q_EMIT curvedDisplayChanged();
}
}
QList<QQuaternion> BreezyDesktopEffect::smoothFollowOrigin() const {
return m_smoothFollowOrigin;
}

View File

@ -46,6 +46,7 @@ namespace KWin
Q_PROPERTY(bool removeVirtualDisplaysOnDisable READ removeVirtualDisplaysOnDisable NOTIFY removeVirtualDisplaysOnDisableChanged)
Q_PROPERTY(bool mirrorPhysicalDisplays READ mirrorPhysicalDisplays NOTIFY mirrorPhysicalDisplaysChanged)
Q_PROPERTY(bool curvedDisplay READ curvedDisplay NOTIFY curvedDisplayChanged)
Q_PROPERTY(bool curvedDisplaySupported READ curvedDisplaySupported WRITE setCurvedDisplaySupported NOTIFY curvedDisplaySupportedChanged)
public:
@ -92,6 +93,7 @@ namespace KWin
bool removeVirtualDisplaysOnDisable() const;
bool mirrorPhysicalDisplays() const;
bool curvedDisplay() const;
void setCurvedDisplaySupported(bool supported);
void showCursor();
void hideCursor();
@ -109,6 +111,7 @@ namespace KWin
QVariantList listVirtualDisplays() const;
bool removeVirtualDisplay(const QString &id);
void moveCursorToFocusedDisplay();
bool curvedDisplaySupported() const;
Q_SIGNALS:
void lookAheadOverrideChanged();
@ -127,6 +130,7 @@ namespace KWin
void removeVirtualDisplaysOnDisableChanged();
void mirrorPhysicalDisplaysChanged();
void curvedDisplayChanged();
void curvedDisplaySupportedChanged();
void cursorImageSourceChanged();
void cursorPosChanged();
@ -177,6 +181,7 @@ namespace KWin
bool m_removeVirtualDisplaysOnDisable = true;
bool m_mirrorPhysicalDisplays = false;
bool m_curvedDisplay = false;
bool m_curvedDisplaySupported = false;
float m_smoothFollowThreshold = 1.0f;
bool m_allDisplaysFollowMode = false;
bool m_focusedSmoothFollowEnabled = false;

View File

@ -527,6 +527,13 @@ QVariantList BreezyDesktopEffectConfig::dbusRemoveVirtualDisplay(const QString &
return list.isValid() ? list.value() : QVariantList{};
}
bool BreezyDesktopEffectConfig::dbusCurvedDisplaySupported() const {
QDBusInterface iface = makeVDInterface();
if (!iface.isValid()) return false;
QDBusReply<bool> reply = iface.call(QStringLiteral("CurvedDisplaySupported"));
return reply.isValid() && reply.value();
}
void BreezyDesktopEffectConfig::renderVirtualDisplays(const QVariantList &rows) {
auto listContainer = widget()->findChild<QWidget*>(QStringLiteral("widgetVirtualDisplayList"));
auto listLayout = listContainer ? qobject_cast<QVBoxLayout*>(listContainer->layout()) : nullptr;
@ -651,6 +658,24 @@ void BreezyDesktopEffectConfig::pollDriverState()
QStringLiteral("No device connected"));
}
if (m_deviceConnected) {
if (!dbusCurvedDisplaySupported()) {
if (m_curvedDisplaySupported) {
m_curvedDisplaySupported = false;
ui.kcfg_CurvedDisplay->setEnabled(false);
ui.kcfg_CurvedDisplay->setChecked(false);
ui.kcfg_CurvedDisplay->setToolTip(QObject::tr("This feature requires Qt version 6.6 or higher"));
}
} else {
if (!m_curvedDisplaySupported) {
m_curvedDisplaySupported = true;
ui.kcfg_CurvedDisplay->setEnabled(true);
ui.kcfg_CurvedDisplay->setChecked(BreezyDesktopConfig::self()->curvedDisplay());
ui.kcfg_CurvedDisplay->setToolTip(QString());
}
}
}
bool effectEnabled = driverEnabled(configJsonOpt);
if (ui.EffectEnabled->isChecked() != effectEnabled) ui.EffectEnabled->setChecked(effectEnabled);
bool multitap = multitapEnabled(configJsonOpt);

View File

@ -57,6 +57,8 @@ private:
QVariantList dbusRemoveVirtualDisplay(const QString &id) const;
void renderVirtualDisplays(const QVariantList &rows);
bool dbusCurvedDisplaySupported() const;
::Ui::BreezyDesktopEffectConfig ui;
KConfigWatcher::Ptr m_configWatcher;
@ -70,4 +72,5 @@ private:
QTimer m_statePollTimer; // periodic driver state polling
QTimer m_virtualDisplayPollTimer; // periodic virtual display list polling
bool m_licenseLoading = false;
bool m_curvedDisplaySupported = true;
};

View File

@ -171,6 +171,15 @@ Node {
return matrix;
}
// only for the Rectangle geometry fallback
property vector3d rectangleFallbackScale: {
const geometry = screen.geometry;
// default geometry unit size is 100x100, so we scale it up to the screen size
return Qt.vector3d(geometry.width / 100, geometry.height / 100, 1);
}
scale: effect.curvedDisplaySupported ? Qt.vector3d(1, 1, 1) : rectangleFallbackScale
eulerRotation.y: screenRotationY
eulerRotation.x: screenRotationX
position: {

View File

@ -1,6 +1,5 @@
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Model {
id: display
@ -18,95 +17,31 @@ Model {
id: displays
}
geometry: ProceduralMesh {
id: mesh
// Default to simple rectangle source so we work on older Qt6
// We'll attempt to dynamically load CurvableDisplayMesh.qml in onCompleted
source: "#Rectangle"
property var _meshArrays: generateMesh()
positions: _meshArrays.positions
uv0s: _meshArrays.uvs
indexes: _meshArrays.indices
primitiveMode: ProceduralMesh.TriangleStrip
function generateMesh() {
if (!display.fovDetails || !display.screen)
return { positions: [], uvs: [], indices: [] };
const fov = display.fovDetails;
const monitor = display.screen.geometry;
const conv = fov.curvedDisplay ? displays.fovConversionFns.curved
: displays.fovConversionFns.flat;
const horizontalWrap = fov.monitorWrappingScheme === 'horizontal';
const verticalWrap = fov.monitorWrappingScheme === 'vertical';
const sideEdgeDistance = conv.centerToFovEdgeDistance(
fov.completeScreenDistancePixels, fov.widthPixels);
const horizontalRadians = conv.lengthToRadians(
fov.defaultDistanceHorizontalRadians,
fov.widthPixels,
sideEdgeDistance,
monitor.width
);
const topEdgeDistance = conv.centerToFovEdgeDistance(
fov.completeScreenDistancePixels, fov.heightPixels);
const verticalRadians = conv.lengthToRadians(
fov.defaultDistanceVerticalRadians,
fov.heightPixels,
topEdgeDistance,
monitor.height
);
const positions = [];
const uvs = [];
const indices = [];
const radius = fov.completeScreenDistancePixels;
function vertexFor(s, t) {
let z = 0;
const xOffset = s - 0.5;
let x = xOffset * monitor.width;
if (fov.curvedDisplay && horizontalWrap) {
const xOffsetRadians = xOffset * horizontalRadians;
x = Math.sin(xOffsetRadians) * radius;
z = radius - Math.cos(xOffsetRadians) * radius;
Component.onCompleted: {
try {
const component = Qt.createComponent(Qt.resolvedUrl("CurvableDisplayMesh.qml"), Component.PreferSynchronous);
if (component.status === Component.Ready) {
const mesh = component.createObject(display, {
fovDetails: Qt.binding(() => display.fovDetails),
monitorGeometry: Qt.binding(() => display.screen ? display.screen.geometry : null),
fovConversionFns: Qt.binding(() => displays.fovConversionFns)
});
if (mesh) {
display.source = "";
display.geometry = mesh;
effect.curvedDisplaySupported = true;
}
const yOffset = t - 0.5;
let y = yOffset * monitor.height;
if (fov.curvedDisplay && verticalWrap) {
const yOffsetRadians = yOffset * verticalRadians;
y = Math.sin(yOffsetRadians) * radius;
z = radius - Math.cos(yOffsetRadians) * radius;
}
return { pos: Qt.vector3d(x, y, z), uv: Qt.vector2d(s, t) };
} else {
console.error("Breezy - CurvableDisplayMesh not available:", component.errorString());
effect.curvedDisplaySupported = false;
}
let segments = 1;
if (horizontalWrap) segments = conv.radiansToSegments(horizontalRadians);
if (verticalWrap) segments = conv.radiansToSegments(verticalRadians);
for (let i = 0; i <= segments; i++) {
const texFraction = i / segments;
// !verticalWrap also covers "flat" wrap scheme
const texX0 = !verticalWrap ? texFraction : 0;
const texX1 = !verticalWrap ? texFraction : 1;
const texY0 = verticalWrap ? texFraction : 1;
const texY1 = verticalWrap ? texFraction : 0;
let vtxB = vertexFor(texX0, texY0);
let vtxT = vertexFor(texX1, texY1);
positions.push(vtxB.pos);
positions.push(vtxT.pos);
uvs.push(vtxB.uv);
uvs.push(vtxT.uv);
}
return { positions: positions, uvs: uvs, indices: [] };
} catch (e) {
console.error("Breezy - CurvableDisplayMesh loading error:", e);
effect.curvedDisplaySupported = false;
}
}
materials: [

View File

@ -0,0 +1,103 @@
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
ProceduralMesh {
id: mesh
property var fovDetails
property var monitorGeometry
property var fovConversionFns
property var _meshArrays: generateMesh()
positions: _meshArrays.positions
uv0s: _meshArrays.uvs
indexes: _meshArrays.indices
primitiveMode: ProceduralMesh.TriangleStrip
onFovDetailsChanged: _meshArrays = generateMesh()
onMonitorGeometryChanged: _meshArrays = generateMesh()
onFovConversionFnsChanged: _meshArrays = generateMesh()
function generateMesh() {
if (!mesh.fovDetails || !mesh.monitorGeometry || !mesh.fovConversionFns)
return { positions: [], uvs: [], indices: [] };
const fov = mesh.fovDetails;
const monitor = mesh.monitorGeometry;
const conv = fov.curvedDisplay ? mesh.fovConversionFns.curved
: mesh.fovConversionFns.flat;
const horizontalWrap = fov.monitorWrappingScheme === 'horizontal';
const verticalWrap = fov.monitorWrappingScheme === 'vertical';
const sideEdgeDistance = conv.centerToFovEdgeDistance(
fov.completeScreenDistancePixels, fov.widthPixels);
const horizontalRadians = conv.lengthToRadians(
fov.defaultDistanceHorizontalRadians,
fov.widthPixels,
sideEdgeDistance,
monitor.width
);
const topEdgeDistance = conv.centerToFovEdgeDistance(
fov.completeScreenDistancePixels, fov.heightPixels);
const verticalRadians = conv.lengthToRadians(
fov.defaultDistanceVerticalRadians,
fov.heightPixels,
topEdgeDistance,
monitor.height
);
const positions = [];
const uvs = [];
const indices = [];
const radius = fov.completeScreenDistancePixels;
function vertexFor(s, t) {
let z = 0;
const xOffset = s - 0.5;
let x = xOffset * monitor.width;
if (fov.curvedDisplay && horizontalWrap) {
const xOffsetRadians = xOffset * horizontalRadians;
x = Math.sin(xOffsetRadians) * radius;
z = radius - Math.cos(xOffsetRadians) * radius;
}
const yOffset = t - 0.5;
let y = yOffset * monitor.height;
if (fov.curvedDisplay && verticalWrap) {
const yOffsetRadians = yOffset * verticalRadians;
y = Math.sin(yOffsetRadians) * radius;
z = radius - Math.cos(yOffsetRadians) * radius;
}
return { pos: Qt.vector3d(x, y, z), uv: Qt.vector2d(s, t) };
}
let segments = 1;
if (horizontalWrap) segments = conv.radiansToSegments(horizontalRadians);
if (verticalWrap) segments = conv.radiansToSegments(verticalRadians);
for (let i = 0; i <= segments; i++) {
const texFraction = i / segments;
// !verticalWrap also covers "flat" wrap scheme
const texX0 = !verticalWrap ? texFraction : 0;
const texX1 = !verticalWrap ? texFraction : 1;
const texY0 = verticalWrap ? texFraction : 1;
const texY1 = verticalWrap ? texFraction : 0;
let vtxB = vertexFor(texX0, texY0);
let vtxT = vertexFor(texX1, texY1);
positions.push(vtxB.pos);
positions.push(vtxT.pos);
uvs.push(vtxB.uv);
uvs.push(vtxT.uv);
}
return { positions: positions, uvs: uvs, indices: [] };
}
}

View File

@ -8,17 +8,34 @@ python one-liner implementation.
from __future__ import annotations
import logging
import json
import os
import sys
import traceback
from logging.handlers import TimedRotatingFileHandler
state_home = os.environ.get('XDG_STATE_HOME', '~/.local/state')
state_dir = os.path.expanduser(state_home)
breezy_state_dir = os.path.join(state_dir, 'breezy_kwin')
log_dir = os.path.join(breezy_state_dir, 'logs')
os.makedirs(log_dir, exist_ok=True)
logger = logging.getLogger('xrdriveripc')
logger.setLevel(logging.INFO)
logname = os.path.join(log_dir, "xrdriveripc.log")
handler = TimedRotatingFileHandler(logname, when="midnight", backupCount=30)
handler.suffix = "%Y%m%d"
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
class Logger:
def info(self, *args, **kwargs):
pass
logger.info(*args, **kwargs)
def error(self, *args, **kwargs):
pass
logger.error(*args, **kwargs)
def main() -> int: