diff --git a/bin/breezy_kwin_setup b/bin/breezy_kwin_setup index f62cfd5..def1b69 100755 --- a/bin/breezy_kwin_setup +++ b/bin/breezy_kwin_setup @@ -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 "" diff --git a/kwin/src/breezydesktopeffect.cpp b/kwin/src/breezydesktopeffect.cpp index c12d876..05e99d3 100644 --- a/kwin/src/breezydesktopeffect.cpp +++ b/kwin/src/breezydesktopeffect.cpp @@ -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 BreezyDesktopEffect::smoothFollowOrigin() const { return m_smoothFollowOrigin; } diff --git a/kwin/src/breezydesktopeffect.h b/kwin/src/breezydesktopeffect.h index e3a7fbb..1334d90 100644 --- a/kwin/src/breezydesktopeffect.h +++ b/kwin/src/breezydesktopeffect.h @@ -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; diff --git a/kwin/src/kcm/breezydesktopeffectkcm.cpp b/kwin/src/kcm/breezydesktopeffectkcm.cpp index cede422..d0c9267 100644 --- a/kwin/src/kcm/breezydesktopeffectkcm.cpp +++ b/kwin/src/kcm/breezydesktopeffectkcm.cpp @@ -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 reply = iface.call(QStringLiteral("CurvedDisplaySupported")); + return reply.isValid() && reply.value(); +} + void BreezyDesktopEffectConfig::renderVirtualDisplays(const QVariantList &rows) { auto listContainer = widget()->findChild(QStringLiteral("widgetVirtualDisplayList")); auto listLayout = listContainer ? qobject_cast(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); diff --git a/kwin/src/kcm/breezydesktopeffectkcm.h b/kwin/src/kcm/breezydesktopeffectkcm.h index 28b82e4..aa457f9 100644 --- a/kwin/src/kcm/breezydesktopeffectkcm.h +++ b/kwin/src/kcm/breezydesktopeffectkcm.h @@ -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; }; diff --git a/kwin/src/qml/BreezyDesktop.qml b/kwin/src/qml/BreezyDesktop.qml index 11205d5..3f3fc3b 100644 --- a/kwin/src/qml/BreezyDesktop.qml +++ b/kwin/src/qml/BreezyDesktop.qml @@ -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: { diff --git a/kwin/src/qml/BreezyDesktopDisplay.qml b/kwin/src/qml/BreezyDesktopDisplay.qml index 0ab6763..e4ce5d7 100644 --- a/kwin/src/qml/BreezyDesktopDisplay.qml +++ b/kwin/src/qml/BreezyDesktopDisplay.qml @@ -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: [ diff --git a/kwin/src/qml/CurvableDisplayMesh.qml b/kwin/src/qml/CurvableDisplayMesh.qml new file mode 100644 index 0000000..28f33cd --- /dev/null +++ b/kwin/src/qml/CurvableDisplayMesh.qml @@ -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: [] }; + } +} diff --git a/kwin/src/xrdriveripc/xrdriveripc_runner.py b/kwin/src/xrdriveripc/xrdriveripc_runner.py index 3a47250..3595e4c 100644 --- a/kwin/src/xrdriveripc/xrdriveripc_runner.py +++ b/kwin/src/xrdriveripc/xrdriveripc_runner.py @@ -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: