diff --git a/.gitignore b/.gitignore index c501b9e..9feb935 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,4 @@ out/ *.po~ gnome-44-max/ gnome-45/ -kwin/src/xrdriveripc/xrdriveripc.py -kwin/VERSION -kwin/build-test/ -kwin/src/qml/calibrating.png -kwin/src/qml/custom_banner.png +kwin/ \ No newline at end of file diff --git a/kwin/CMakeLists.txt b/kwin/CMakeLists.txt deleted file mode 100644 index c3e8411..0000000 --- a/kwin/CMakeLists.txt +++ /dev/null @@ -1,55 +0,0 @@ -cmake_minimum_required(VERSION 3.20) - -project(breezy_desktop VERSION 0.0.1 LANGUAGES CXX) - -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -include(cmake/default-vars.cmake) - -find_package(ECM "5.100" REQUIRED NO_MODULE) -set(CMAKE_MODULE_PATH - ${CMAKE_MODULE_PATH} - ${ECM_MODULE_PATH} - ${ECM_KDE_MODULE_DIR} -) -include(cmake/qtversion.cmake) -include(FeatureSummary) -include(KDEInstallDirs) -include(KDECMakeSettings) -include(KDECompilerSettings NO_POLICY_SCOPE) - -include(cmake/qtversion.cmake) - -# required frameworks by Core -find_package(KF${QT_MAJOR_VERSION} ${KF_MIN_VERSION} REQUIRED COMPONENTS - Config - ConfigWidgets - CoreAddons - GlobalAccel - I18n - KCMUtils - WindowSystem - XmlGui -) - -if(${QT_MAJOR_VERSION} EQUAL 6) - find_package(KF${QT_MAJOR_VERSION} ${KF_MIN_VERSION} REQUIRED COMPONENTS KCMUtils) - find_package(KWin REQUIRED COMPONENTS kwineffects) - message(STATUS "Found KWin Version: ${KWin_VERSION}") -endif () -include(cmake/info.cmake) -find_package(epoxy REQUIRED) -find_package(XCB REQUIRED COMPONENTS XCB) -find_package(KWinDBusInterface CONFIG REQUIRED) - -add_subdirectory(src) -ki18n_install(po) - -feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) - -include(cmake/test.cmake) -include(cmake/pack.cmake) diff --git a/kwin/bin/breezy_kwin_uninstall b/kwin/bin/breezy_kwin_uninstall deleted file mode 100755 index 8f620b2..0000000 --- a/kwin/bin/breezy_kwin_uninstall +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env bash - -for_install=0 -if [[ -n "$1" ]] && [[ "$1" == "--for-install" ]]; then - for_install=1 -fi - -# we don't want the uninstall script to be able to cause a failure if being triggered by the setup script -[ "$for_install" -eq 0 ] && set -e - -if command -v qdbus >/dev/null 2>&1; then - QDBUS_CMD=$(command -v qdbus) -elif command -v qdbus6 >/dev/null 2>&1; then - QDBUS_CMD=$(command -v qdbus6) -fi - -if [ -n "$QDBUS_CMD" ]; then - if $QDBUS_CMD org.kde.KWin /Effects org.kde.kwin.Effects.unloadEffect breezy_desktop; then - [ "$for_install" -eq 0 ] && echo "Unloaded breezy_desktop effect" - fi -fi - -USER_HOME=$(realpath ~) -if [ "$for_install" -eq 0 ]; then - UA_EVENT_NAME="breezy_kwin_uninstall" - UA_CLIENT_ID="BreezyKWin" - #INJECT_UA_CALL -fi - -if [ -z "$XDG_BIN_HOME" ]; then - XDG_BIN_HOME="$USER_HOME/.local/bin" -fi - -if [ -z "$XDG_DATA_HOME" ]; then - XDG_DATA_HOME="$USER_HOME/.local/share" -fi - -if [ -z "$LIB_HOME" ]; then - LIB_HOME="$USER_HOME/.local/lib" -fi - -EFFECT_ID="breezy_desktop" -EFFECT_DIR="$XDG_DATA_HOME/kwin/effects/$EFFECT_ID" -PLUGIN_SO="$LIB_HOME/qt6/plugins/kwin/effects/plugins/${EFFECT_ID}.so" -CONFIG_SO="$LIB_HOME/qt6/plugins/kwin/effects/configs/${EFFECT_ID}_config.so" -BREEZY_LIBRARY_DIR="$LIB_HOME/breezy_kwin" - -if [[ -d "$EFFECT_DIR" ]]; then - [ "$for_install" -eq 0 ] && echo "Removing $EFFECT_DIR and its contents" - $SUDO rm -rf "$EFFECT_DIR" -fi - -if [[ -f "$PLUGIN_SO" ]]; then - [ "$for_install" -eq 0 ] && echo "Removing $PLUGIN_SO" - $SUDO rm -f "$PLUGIN_SO" -fi - -if [[ -f "$CONFIG_SO" ]]; then - [ "$for_install" -eq 0 ] && echo "Removing $CONFIG_SO" - $SUDO rm -f "$CONFIG_SO" -fi - -if [[ -d "$BREEZY_LIBRARY_DIR" ]]; then - [ "$for_install" -eq 0 ] && echo "Removing $BREEZY_LIBRARY_DIR and its contents" - $SUDO rm -rf "$BREEZY_LIBRARY_DIR" -fi - -if [[ -e "$XDG_BIN_HOME/xr_driver_uninstall" && "$for_install" -eq 0 ]]; then - echo "Uninstalling XRLinuxDriver" - sudo "$XDG_BIN_HOME/xr_driver_uninstall" -fi - -# this script is self-deleting, leave this as the last command -rm -f $XDG_BIN_HOME/breezy_kwin_uninstall \ No newline at end of file diff --git a/kwin/bin/package_kwin_plugin b/kwin/bin/package_kwin_plugin deleted file mode 100755 index a60efe5..0000000 --- a/kwin/bin/package_kwin_plugin +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash - -# exit when any command fails -set -e - -check_command() { - if ! command -v "$1" &>/dev/null; then - echo "Please install \"$1\" and make sure it's available in your \$PATH, then rerun the setup." - exit 1 - fi -} -check_command "cmake" -check_command "make" - -ARCH=${ARCH:-$(uname -m)} -if [ -n "${STEAMOS+x}" ]; then - ARCH="steamos" -fi -echo "Building Breezy KWin plugin for $ARCH" - -BUILD_PATH=build -rm -rf $BUILD_PATH -mkdir $BUILD_PATH - -pushd $BUILD_PATH > /dev/null -cmake .. -make -cpack -G TGZ -popd > /dev/null - -mkdir -p out -cp $BUILD_PATH/breezy_desktop.tar.gz out/breezyKWinPlugin-$ARCH.tar.gz \ No newline at end of file diff --git a/kwin/bin/setup b/kwin/bin/setup deleted file mode 100755 index 28a63f1..0000000 --- a/kwin/bin/setup +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env bash - -set -e - -if [ "$XDG_SESSION_TYPE" != "wayland" ]; then - printf "\033[1;33mWARNING:\033[0m Windowing system is %s\n" "$XDG_SESSION_TYPE" - printf "\033[1;33mWARNING:\033[0m Virtual display functionality requires Wayland\n" -fi - -USER_HOME=$(realpath ~) - -if [ -z "$XDG_BIN_HOME" ]; then - XDG_BIN_HOME="$USER_HOME/.local/bin" -fi - -if [ -d "$XDG_BIN_HOME" ]; then - # check ownership and permissions before doing chown and chmod - XDG_BIN_USER=$(stat -c %U $XDG_BIN_HOME) - XDG_BIN_GROUP=$(stat -c %G $XDG_BIN_HOME) - - USER=$(whoami) - GROUP=$(id -gn) - - if [ "$XDG_BIN_USER" != "$USER" ] || [ "$XDG_BIN_GROUP" != "$GROUP" ]; then - echo "Fixing ownership and permissions of $XDG_BIN_HOME" - sudo chown -R $USER:$GROUP $XDG_BIN_HOME - sudo chmod -R 700 $XDG_BIN_HOME - fi -fi - -UA_EVENT_NAME="breezy_kwin_install" -if [ -e "$XDG_BIN_HOME/breezy_kwin_uninstall" ]; then - echo "Cleaning up the previous installation" - - # ` || true` will ensure that this can't cause a failure, even with `set -e` - $XDG_BIN_HOME/breezy_kwin_uninstall --for-install || true - - UA_EVENT_NAME="breezy_kwin_update" -fi - -UA_CLIENT_ID="BreezyKWin" -UA_EVENT_VERSION="$1" -#INJECT_UA_CALL - -tar -xf $(pwd)/breezyKWinPlugin.tar.gz -pushd breezy_desktop/usr > /dev/null - -echo "Copying KWin plugin files to $USER_HOME/.local/{lib,share}" - -# locate the lib path that ends with qt6/plugins (handles multiarch dirs) -QT_PLUGIN_DIR_RELATIVE=$(find lib* -type d -path '*/qt6/plugins' -print -quit 2>/dev/null || true) -if [ -z "$QT_PLUGIN_DIR_RELATIVE" ]; then - QT_PLUGIN_DIR_RELATIVE="lib/qt6/plugins" -fi - -# directory structure matches XDG, so just recursive copy -chmod -R 755 . -cp -r . "$USER_HOME/.local/" - -popd > /dev/null - -mkdir -p $XDG_BIN_HOME -cp bin/breezy_kwin_uninstall $XDG_BIN_HOME - -# Install QT_PLUGIN_PATH snippet into ~/.bash_profile if not present -BASH_PROFILE="$HOME/.bash_profile" -QT_PLUGIN_DIR="$HOME/.local/$QT_PLUGIN_DIR_RELATIVE" -QT_PLUGIN_EXPORT="export QT_PLUGIN_PATH=\"$QT_PLUGIN_DIR:\$QT_PLUGIN_PATH\"" -if [[ ! -f "$BASH_PROFILE" ]] || ! grep -Fq "$QT_PLUGIN_EXPORT" "$BASH_PROFILE" 2>/dev/null; then - echo "Adding QT_PLUGIN_PATH to $BASH_PROFILE" - mkdir -p "$(dirname "$BASH_PROFILE")" - cat >> "$BASH_PROFILE" <> "$PLASMA_ENV_SCRIPT" < files - -if [[ "$1" == "--init" || ! $(docker buildx inspect breezykwinbuilder &>/dev/null; echo $?) -eq 0 ]]; then - # start fresh - echo "Creating new docker builder instance" - docker buildx rm breezykwinbuilder 2>/dev/null || true - docker buildx create --use --name breezykwinbuilder --driver docker-container --driver-opt image=moby/buildkit:latest -else - echo "Using existing docker builder instance" - docker buildx use breezykwinbuilder -fi - -echo "Building docker image" -docker buildx build --platform linux/amd64 -f ./docker-build/Dockerfile -t "breezy-kwin:amd64" --load . -# docker buildx build --platform linux/arm64 -f ./docker-build/Dockerfile -t "breezy-kwin:arm64" --load . -docker buildx build --platform linux/amd64 -f ./docker-build/Dockerfile.steamos -t "breezy-kwin-steamos:amd64" --load . \ No newline at end of file diff --git a/kwin/docker-build/run-build.sh b/kwin/docker-build/run-build.sh deleted file mode 100755 index ff19c84..0000000 --- a/kwin/docker-build/run-build.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -set -e - -USER=${SUDO_USER:-$USER} -GROUP=$(id -gn $USER) - -# Run containers for each architecture -if [[ "$1" == "x86_64" || -z "$1" ]]; then - sudo rm -rf build/ - docker run --rm -t -v ./:/source --platform linux/amd64 "breezy-kwin:amd64" - sudo chown -R $USER:$GROUP out/ -fi - -if [[ "$1" == "aarch64" || -z "$1" ]]; then - sudo rm -rf build/ - docker run --rm -t -v ./:/source --platform linux/arm64 "breezy-kwin:arm64" - sudo chown -R $USER:$GROUP out/ -fi - -if [[ "$1" == "steamos" || -z "$1" ]]; then - sudo rm -rf build/ - docker run --rm -t -v ./:/source --platform linux/amd64 "breezy-kwin-steamos:amd64" - sudo chown -R $USER:$GROUP out/ -fi - -# build directory structure is all owned by root because of docker, delete it all now -sudo chown -R $USER:$GROUP build/ \ No newline at end of file diff --git a/kwin/src/CMakeLists.txt b/kwin/src/CMakeLists.txt deleted file mode 100644 index 5b88fdd..0000000 --- a/kwin/src/CMakeLists.txt +++ /dev/null @@ -1,70 +0,0 @@ -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) - -kcoreaddons_add_plugin(breezy_desktop INSTALL_NAMESPACE "kwin/effects/plugins/") -target_sources(breezy_desktop PRIVATE - breezydesktopeffect.cpp - main.cpp -) -kconfig_add_kcfg_files(breezy_desktop breezydesktopconfig.kcfgc) - -# Split KWin version into numeric components (major, minor, patch) -string(REGEX MATCHALL "[0-9]+" KWIN_VERSION_COMPONENTS "${KWin_VERSION}") - -# defaults -set(KWIN_VERSION_MAJOR 0) -set(KWIN_VERSION_MINOR 0) -set(KWIN_VERSION_PATCH 0) - -list(LENGTH KWIN_VERSION_COMPONENTS _kwin_version_len) -if(_kwin_version_len GREATER 0) - list(GET KWIN_VERSION_COMPONENTS 0 KWIN_VERSION_MAJOR) -endif() -if(_kwin_version_len GREATER 1) - list(GET KWIN_VERSION_COMPONENTS 1 KWIN_VERSION_MINOR) -endif() -if(_kwin_version_len GREATER 2) - list(GET KWIN_VERSION_COMPONENTS 2 KWIN_VERSION_PATCH) -endif() - -# optional: a single encoded integer (major*10000 + minor*100 + patch) -math(EXPR KWIN_VERSION_ENCODED "${KWIN_VERSION_MAJOR} * 10000 + ${KWIN_VERSION_MINOR} * 100 + ${KWIN_VERSION_PATCH}") - -# Export as compile definitions. Keep the original string macro as well. -target_compile_definitions(breezy_desktop PRIVATE - KWIN_VERSION_STR=\"${KWin_VERSION}\" - KWIN_VERSION_MAJOR=${KWIN_VERSION_MAJOR} - KWIN_VERSION_MINOR=${KWIN_VERSION_MINOR} - KWIN_VERSION_PATCH=${KWIN_VERSION_PATCH} - 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 xrdriveripc) -target_link_libraries(breezy_desktop - Qt6::Core - Qt6::Gui - Qt6::Quick - Qt6::DBus - - KF6::ConfigCore - KF6::ConfigGui - KF6::CoreAddons - KF6::GlobalAccel - KF6::I18n - KF6::WindowSystem - - KWin::kwin - - xr_driver_ipc -) - - -install(DIRECTORY qml DESTINATION ${KDE_INSTALL_DATADIR}/kwin/effects/breezy_desktop) \ No newline at end of file diff --git a/kwin/src/breezydesktopconfig.kcfg b/kwin/src/breezydesktopconfig.kcfg deleted file mode 100644 index 2a8ec8b..0000000 --- a/kwin/src/breezydesktopconfig.kcfg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - 85 - 25 - 225 - - - - 105 - 25 - 225 - - - - false - - Enable zooming in on the focused display. - - - 0 - 0 - 100 - - How far apart the displays are visually (not logically) - - - 0 - -250 - 250 - - Horizontal offset as a percent of the viewport width (-2.50 to 2.50) - - - 0 - -250 - 250 - - Vertical offset as a percent of the viewport height (-2.50 to 2.50) - - - 0 - 0 - 3 - - How to arrange monitors: 0=Auto, 1=Horizontal, 2=Vertical, 3=Flat - - - 3 - 0 - 3 - - 0=None, 1=Medium, 2=High, 3=Very High - - - true - - Whether to remove any virtual displays when the effect is disabled - - - diff --git a/kwin/src/breezydesktopconfig.kcfgc b/kwin/src/breezydesktopconfig.kcfgc deleted file mode 100644 index f1666fe..0000000 --- a/kwin/src/breezydesktopconfig.kcfgc +++ /dev/null @@ -1,5 +0,0 @@ -File=breezydesktopconfig.kcfg -ClassName=BreezyDesktopConfig -Singleton=true -Mutators=true -Notifiers=true \ No newline at end of file diff --git a/kwin/src/breezydesktopeffect.cpp b/kwin/src/breezydesktopeffect.cpp deleted file mode 100644 index e27830d..0000000 --- a/kwin/src/breezydesktopeffect.cpp +++ /dev/null @@ -1,628 +0,0 @@ - -#include "core/rendertarget.h" -#include "core/renderviewport.h" -#include "kcm/shortcuts.h" -#include "breezydesktopeffect.h" -#include "breezydesktopconfig.h" -#include "effect/effect.h" -#include "effect/effecthandler.h" -#include "opengl/glutils.h" -#include "xrdriveripc.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -Q_LOGGING_CATEGORY(KWIN_XR, "kwin.xr") - -// A small DBus adaptor to expose effect controls to the KCM. -// Service is provided by KWin (org.kde.KWin). We only register an object path. -// Interface: com.xronlinux.BreezyDesktop, Path: /com/xronlinux/BreezyDesktop -namespace { -class BreezyDesktopDBusAdaptor : public QObject { - Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "com.xronlinux.BreezyDesktop") -public: - explicit BreezyDesktopDBusAdaptor(KWin::BreezyDesktopEffect *effect) - : QObject(effect), m_effect(effect) {} - -public Q_SLOTS: - void AddVirtualDisplay(int width, int height) { - QMetaObject::invokeMethod(m_effect, [this, width, height]() { - m_effect->addVirtualDisplay(QSize(width, height)); - }, Qt::QueuedConnection); - } - -private: - KWin::BreezyDesktopEffect *m_effect; -}; -} // namespace - -namespace DataView -{ - const QString SHM_DIR = QStringLiteral("/dev/shm"); - const QString SHM_PATH = SHM_DIR + QStringLiteral("/breezy_desktop_imu"); - - // Helper constants and functions for shared memory buffer offsets - constexpr int UINT8_SIZE = sizeof(uint8_t); - constexpr int BOOL_SIZE = UINT8_SIZE; - constexpr int UINT_SIZE = sizeof(uint32_t); - constexpr int FLOAT_SIZE = sizeof(float); - - // DataView info: [offset, size, count] - constexpr int OFFSET_INDEX = 0; - constexpr int SIZE_INDEX = 1; - constexpr int COUNT_INDEX = 2; - - // Computes the end offset, exclusive - constexpr int dataViewEnd(const int info[3]) { - return info[OFFSET_INDEX] + info[SIZE_INDEX] * info[COUNT_INDEX]; - } - - constexpr int VERSION[3] = {0, UINT8_SIZE, 1}; - constexpr int ENABLED[3] = {dataViewEnd(VERSION), BOOL_SIZE, 1}; - constexpr int LOOK_AHEAD_CFG[3] = {dataViewEnd(ENABLED), FLOAT_SIZE, 4}; - constexpr int DISPLAY_RES[3] = {dataViewEnd(LOOK_AHEAD_CFG), UINT_SIZE, 2}; - constexpr int DISPLAY_FOV[3] = {dataViewEnd(DISPLAY_RES), FLOAT_SIZE, 1}; - constexpr int LENS_DISTANCE_RATIO[3] = {dataViewEnd(DISPLAY_FOV), FLOAT_SIZE, 1}; - constexpr int SBS_ENABLED[3] = {dataViewEnd(LENS_DISTANCE_RATIO), BOOL_SIZE, 1}; - constexpr int CUSTOM_BANNER_ENABLED[3] = {dataViewEnd(SBS_ENABLED), BOOL_SIZE, 1}; - constexpr int SMOOTH_FOLLOW_ENABLED[3] = {dataViewEnd(CUSTOM_BANNER_ENABLED), BOOL_SIZE, 1}; - constexpr int SMOOTH_FOLLOW_ORIGIN_DATA[3] = {dataViewEnd(SMOOTH_FOLLOW_ENABLED), FLOAT_SIZE, 16}; - constexpr int IMU_DATE_MS[3] = {dataViewEnd(SMOOTH_FOLLOW_ORIGIN_DATA), UINT_SIZE, 2}; - constexpr int IMU_QUAT_ENTRIES = 4; - constexpr int IMU_QUAT_DATA[3] = {dataViewEnd(IMU_DATE_MS), FLOAT_SIZE, 4 * IMU_QUAT_ENTRIES}; - constexpr int IMU_PARITY_BYTE[3] = {dataViewEnd(IMU_QUAT_DATA), UINT8_SIZE, 1}; - constexpr int LENGTH = dataViewEnd(IMU_PARITY_BYTE); -} - -namespace KWin -{ - -BreezyDesktopEffect::BreezyDesktopEffect() -{ - qCCritical(KWIN_XR) << "\t\t\tBreezy - constructor"; - qmlRegisterUncreatableType("org.kde.kwin.effect.breezy_desktop", 1, 0, "BreezyDesktopEffect", QStringLiteral("BreezyDesktop cannot be created in QML")); - - setupGlobalShortcut( - BreezyShortcuts::TOGGLE, - [this]() { this->toggle(); } - ); - setupGlobalShortcut( - BreezyShortcuts::RECENTER, - [this]() { this->recenter(); } - ); - setupGlobalShortcut( - BreezyShortcuts::TOGGLE_ZOOM_ON_FOCUS, - [this]() { - this->setZoomOnFocusEnabled(!m_zoomOnFocusEnabled); - } - ); - - connect(effects, &EffectsHandler::cursorShapeChanged, this, &BreezyDesktopEffect::updateCursorImage); - updateCursorImage(); - reconfigure(ReconfigureAll); - - setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/effects/breezy_desktop/qml/main.qml")))); - - // Monitor the IMU file for changes, even if it doesn't exist at startup - m_shmDirectoryWatcher = new QFileSystemWatcher(this); - m_shmDirectoryWatcher->addPath(DataView::SHM_DIR); - - m_shmFileWatcher = new QFileSystemWatcher(this); - - // Setup file watcher with recreation detection - auto setupFileWatcher = [this]() { - if (QFile::exists(DataView::SHM_PATH) && ( - m_imuTimestamp == 0 || - QDateTime::currentMSecsSinceEpoch() - m_imuTimestamp > 50 || // file may have been deleted and recreated - !m_shmFileWatcher->files().contains(DataView::SHM_PATH) - )) { - m_shmFileWatcher->removePath(DataView::SHM_PATH); - disconnect(m_shmFileWatcher, &QFileSystemWatcher::fileChanged, this, &BreezyDesktopEffect::updateImuRotation); - m_shmFileWatcher->addPath(DataView::SHM_PATH); - connect(m_shmFileWatcher, &QFileSystemWatcher::fileChanged, this, &BreezyDesktopEffect::updateImuRotation); - } - }; - - // Handle directory changes (file creation/recreation) - connect(m_shmDirectoryWatcher, &QFileSystemWatcher::directoryChanged, this, setupFileWatcher); - - // Initial setup - setupFileWatcher(); - - m_cursorUpdateTimer = new QTimer(this); - connect(m_cursorUpdateTimer, &QTimer::timeout, this, &BreezyDesktopEffect::updateCursorPos); - m_cursorUpdateTimer->setInterval(16); // ~60Hz - m_cursorUpdateTimer->start(); - - // Register DBus object under KWin's session bus name - auto *adaptor = new BreezyDesktopDBusAdaptor(this); - const bool dbusOk = QDBusConnection::sessionBus().registerObject( - QStringLiteral("/com/xronlinux/BreezyDesktop"), - adaptor, - QDBusConnection::ExportAllSlots); - if (!dbusOk) { - qCWarning(KWIN_XR) << "Failed to register DBus object /com/xronlinux/BreezyDesktop"; - } -} - -BreezyDesktopEffect::~BreezyDesktopEffect() -{ - qCCritical(KWIN_XR) << "\t\t\tBreezy - destructor"; - if (m_shmFileWatcher) { - if (!DataView::SHM_PATH.isEmpty()) { - m_shmFileWatcher->removePath(DataView::SHM_PATH); - } - m_shmFileWatcher->deleteLater(); - m_shmFileWatcher = nullptr; - } - if (m_shmDirectoryWatcher) { - m_shmDirectoryWatcher->deleteLater(); - m_shmDirectoryWatcher = nullptr; - } - deactivate(); -} - -void BreezyDesktopEffect::setupGlobalShortcut(const BreezyShortcuts::Shortcut &shortcut, std::function triggeredFunc) { - QAction *action = new QAction(this); - action->setObjectName(shortcut.actionName); - action->setText(shortcut.actionText); - KGlobalAccel::self()->setDefaultShortcut(action, {shortcut.shortcut}); - KGlobalAccel::self()->setShortcut(action, {shortcut.shortcut}); - connect(action, &QAction::triggered, this, triggeredFunc); -} - -void BreezyDesktopEffect::recenter() { - XRDriverIPC::instance().writeControlFlags({ - {"recenter_screen", true} - }); -} - -void BreezyDesktopEffect::reconfigure(ReconfigureFlags) -{ - BreezyDesktopConfig::self()->read(); - setFocusedDisplayDistance(BreezyDesktopConfig::focusedDisplayDistance() / 100.0f); - setAllDisplaysDistance(BreezyDesktopConfig::allDisplaysDistance() / 100.0f); - setDisplaySpacing(BreezyDesktopConfig::displaySpacing() / 1000.0f); - setZoomOnFocusEnabled(BreezyDesktopConfig::zoomOnFocusEnabled()); - qreal horiz = BreezyDesktopConfig::displayHorizontalOffset() / 100.0f; - qreal vert = BreezyDesktopConfig::displayVerticalOffset() / 100.0f; - int wrap = BreezyDesktopConfig::displayWrappingScheme(); - int aaQuality = BreezyDesktopConfig::antialiasingQuality(); - bool removeVD = BreezyDesktopConfig::removeVirtualDisplaysOnDisable(); - bool changed = false; - if (!qFuzzyCompare(m_displayHorizontalOffset, horiz)) { m_displayHorizontalOffset = horiz; changed = true; } - if (!qFuzzyCompare(m_displayVerticalOffset, vert)) { m_displayVerticalOffset = vert; changed = true; } - if (m_displayWrappingScheme != wrap) { m_displayWrappingScheme = wrap; Q_EMIT displayWrappingSchemeChanged(); } - if (m_antialiasingQuality != aaQuality) { m_antialiasingQuality = aaQuality; Q_EMIT antialiasingQualityChanged(); } - if (m_removeVirtualDisplaysOnDisable != removeVD) { m_removeVirtualDisplaysOnDisable = removeVD; Q_EMIT removeVirtualDisplaysOnDisableChanged(); } - if (changed) Q_EMIT displayOffsetChanged(); -} - -QVariantMap BreezyDesktopEffect::initialProperties(Output *screen) -{ - return QVariantMap{ - {QStringLiteral("effect"), QVariant::fromValue(this)}, - {QStringLiteral("targetScreen"), QVariant::fromValue(screen)} - }; -} - -int BreezyDesktopEffect::requestedEffectChainPosition() const -{ - return 70; -} - -void BreezyDesktopEffect::toggle() -{ - if (isRunning()) { - qCCritical(KWIN_XR) << "\t\t\tBreezy - toggle - disabling"; - disableDriver(); - } else { - qCCritical(KWIN_XR) << "\t\t\tBreezy - toggle - enabling"; - enableDriver(); - } -} - -void BreezyDesktopEffect::activate() -{ - qCCritical(KWIN_XR) << "\t\t\tBreezy - activate"; - - if (!isRunning()) setRunning(true); - - connect(effects, &EffectsHandler::cursorShapeChanged, this, &BreezyDesktopEffect::updateCursorImage); - m_cursorUpdateTimer->start(); - - // QuickSceneEffect grabs the keyboard and mouse input, which pulls focus away from the active window - // and doesn't allow for interaction with anything on the desktop. These two calls fix that. - effects->ungrabKeyboard(); - effects->stopMouseInterception(this); - - hideCursor(); -} - -void BreezyDesktopEffect::deactivate() -{ - qCCritical(KWIN_XR) << "\t\t\tBreezy - deactivate"; - disconnect(effects, &EffectsHandler::cursorShapeChanged, this, &BreezyDesktopEffect::updateCursorImage); - m_cursorUpdateTimer->stop(); - showCursor(); - - if (m_removeVirtualDisplaysOnDisable) { - for (auto output : m_virtualOutputs) { - KWin::kwinApp()->outputBackend()->removeVirtualOutput(output); - } - m_virtualOutputs.clear(); - } - - setRunning(false); -} - -void BreezyDesktopEffect::enableDriver() -{ - qCCritical(KWIN_XR) << "\t\t\tBreezy - enableDriver"; - QJsonObject newConfig = QJsonObject(); - auto configJsonOpt = XRDriverIPC::instance().retrieveConfig(); - if (configJsonOpt) { - newConfig = configJsonOpt.value(); - } - newConfig.insert(QStringLiteral("disabled"), false); - newConfig.insert(QStringLiteral("output_mode"), QStringLiteral("external_only")); - newConfig.insert(QStringLiteral("external_mode"), QStringLiteral("breezy_desktop")); - XRDriverIPC::instance().writeConfig(newConfig); -} - -void BreezyDesktopEffect::disableDriver() -{ - qCCritical(KWIN_XR) << "\t\t\tBreezy - disableDriver"; - QJsonObject newConfig = QJsonObject(); - auto configJsonOpt = XRDriverIPC::instance().retrieveConfig(); - if (configJsonOpt) { - newConfig = configJsonOpt.value(); - } - newConfig.insert(QStringLiteral("external_mode"), QStringLiteral("none")); - XRDriverIPC::instance().writeConfig(newConfig); -} - -void BreezyDesktopEffect::addVirtualDisplay(QSize size) -{ - static int virtualDisplayCount = 0; - ++virtualDisplayCount; - QString name = QStringLiteral("BreezyDesktop_VirtualDisplay_%1x%2_%3").arg(size.width()).arg(size.height()).arg(virtualDisplayCount); - #if defined(KWIN_VERSION_ENCODED) && KWIN_VERSION_ENCODED >= 60290 - QString description = QStringLiteral("Breezy Display %1x%2 (%3)").arg(size.width()).arg(size.height()).arg(virtualDisplayCount); - auto output = KWin::kwinApp()->outputBackend()->createVirtualOutput(name, description, size, 1.0); - #else - auto output = KWin::kwinApp()->outputBackend()->createVirtualOutput(name, size, 1.0); - #endif - if (output) { - m_virtualOutputs.append(output); - } -} - -bool BreezyDesktopEffect::isEnabled() const { - return m_enabled; -} - -bool BreezyDesktopEffect::isZoomOnFocusEnabled() const { - return m_zoomOnFocusEnabled; -} - -void BreezyDesktopEffect::setZoomOnFocusEnabled(bool enabled) { - if (m_zoomOnFocusEnabled != enabled) { - m_zoomOnFocusEnabled = enabled; - if (m_zoomOnFocusEnabled && m_focusedDisplayDistance > m_allDisplaysDistance) { - setFocusedDisplayDistance(m_allDisplaysDistance); - BreezyDesktopConfig::setFocusedDisplayDistance(static_cast(m_focusedDisplayDistance * 100.0f)); - } - BreezyDesktopConfig::setZoomOnFocusEnabled(enabled); - BreezyDesktopConfig::self()->save(); - Q_EMIT zoomOnFocusChanged(); - } -} - -bool BreezyDesktopEffect::imuResetState() const { - return m_imuResetState; -} - -QList BreezyDesktopEffect::imuRotations() const { - return m_imuRotations; -} - -quint32 BreezyDesktopEffect::imuTimeElapsedMs() const { - return m_imuTimeElapsedMs; -} - -quint64 BreezyDesktopEffect::imuTimestamp() const { - return m_imuTimestamp; -} - -QList BreezyDesktopEffect::lookAheadConfig() const { - return m_lookAheadConfig; -} - -QList BreezyDesktopEffect::displayResolution() const { - return m_displayResolution; -} - -qreal BreezyDesktopEffect::focusedDisplayDistance() const { - return m_focusedDisplayDistance; -} - -void BreezyDesktopEffect::setFocusedDisplayDistance(qreal distance) { - if (distance != m_focusedDisplayDistance) { - m_focusedDisplayDistance = std::clamp(distance, 0.2, m_allDisplaysDistance); - Q_EMIT focusedDisplayDistanceChanged(); - } -} - -qreal BreezyDesktopEffect::allDisplaysDistance() const { - return m_allDisplaysDistance; -} - -void BreezyDesktopEffect::setAllDisplaysDistance(qreal distance) { - if (distance != m_allDisplaysDistance) { - qreal min = m_zoomOnFocusEnabled ? m_focusedDisplayDistance : 0.2; - m_allDisplaysDistance = std::clamp(distance, min, 2.5); - Q_EMIT allDisplaysDistanceChanged(); - } -} - -qreal BreezyDesktopEffect::displaySpacing() const { - return m_displaySpacing; -} - -void BreezyDesktopEffect::setDisplaySpacing(qreal spacing) { - if (spacing != m_displaySpacing) { - m_displaySpacing = spacing; - Q_EMIT displaySpacingChanged(); - } -} - -qreal BreezyDesktopEffect::displayHorizontalOffset() const { - return m_displayHorizontalOffset; -} - -qreal BreezyDesktopEffect::displayVerticalOffset() const { - return m_displayVerticalOffset; -} - -int BreezyDesktopEffect::displayWrappingScheme() const { - return m_displayWrappingScheme; -} - -qreal BreezyDesktopEffect::diagonalFOV() const { - return m_diagonalFOV; -} - -qreal BreezyDesktopEffect::lensDistanceRatio() const { - return m_lensDistanceRatio; -} - -bool BreezyDesktopEffect::sbsEnabled() const { - return m_sbsEnabled; -} - -bool BreezyDesktopEffect::customBannerEnabled() const { - return m_customBannerEnabled; -} - -int BreezyDesktopEffect::antialiasingQuality() const { - return m_antialiasingQuality; -} - -bool BreezyDesktopEffect::removeVirtualDisplaysOnDisable() const { - return m_removeVirtualDisplaysOnDisable; -} - -bool BreezyDesktopEffect::checkParityByte(const char* data) { - const uint8_t parityByte = static_cast(data[DataView::IMU_PARITY_BYTE[DataView::OFFSET_INDEX]]); - uint8_t parity = 0; - - const int dateBytes = DataView::IMU_DATE_MS[DataView::COUNT_INDEX] * DataView::IMU_DATE_MS[DataView::SIZE_INDEX]; - for (int i = 0; i < dateBytes; ++i) { - parity ^= static_cast(data[DataView::IMU_DATE_MS[DataView::OFFSET_INDEX] + i]); - } - - const int quatBytes = DataView::IMU_QUAT_DATA[DataView::COUNT_INDEX] * DataView::IMU_QUAT_DATA[DataView::SIZE_INDEX]; - for (int i = 0; i < quatBytes; ++i) { - parity ^= static_cast(data[DataView::IMU_QUAT_DATA[DataView::OFFSET_INDEX] + i]); - } - - return parityByte == parity; -} - -// TODO - can this be something callable from the camera qml code, so it's pulled only when needed? -static qint64 lastConfigUpdate = 0; -static qint64 activatedAt = 0; -void BreezyDesktopEffect::updateImuRotation() { - const QString shmPath = QStringLiteral("/dev/shm/breezy_desktop_imu"); - QFile shmFile(shmPath); - if (!shmFile.open(QIODevice::ReadOnly)) { - return; - } - QByteArray buffer = shmFile.readAll(); - shmFile.close(); - if (buffer.size() != DataView::LENGTH) return; - - const char* data = buffer.constData(); - if (!checkParityByte(data)) return; - - uint8_t version = static_cast(data[DataView::VERSION[DataView::OFFSET_INDEX]]); - uint8_t enabledFlag = static_cast(data[DataView::ENABLED[DataView::OFFSET_INDEX]]); - uint64_t imuDateMs; - memcpy(&imuDateMs, data + DataView::IMU_DATE_MS[DataView::OFFSET_INDEX], sizeof(imuDateMs)); - imuDateMs = qFromLittleEndian(imuDateMs); - - const qint64 currentTimeMs = QDateTime::currentMSecsSinceEpoch(); - const bool updateConfig = lastConfigUpdate == 0 || currentTimeMs - lastConfigUpdate > 1000; - - if (updateConfig) { - float lookAheadConfig[4]; - memcpy(&lookAheadConfig[0], data + DataView::LOOK_AHEAD_CFG[DataView::OFFSET_INDEX], sizeof(lookAheadConfig)); - m_lookAheadConfig.clear(); - m_lookAheadConfig.append(lookAheadConfig[0]); - m_lookAheadConfig.append(lookAheadConfig[1]); - m_lookAheadConfig.append(lookAheadConfig[2]); - m_lookAheadConfig.append(lookAheadConfig[3]); - - uint32_t displayResolution[2]; - memcpy(&displayResolution[0], data + DataView::DISPLAY_RES[DataView::OFFSET_INDEX], sizeof(displayResolution)); - m_displayResolution.clear(); - m_displayResolution.append(displayResolution[0]); - m_displayResolution.append(displayResolution[1]); - - float displayFov = 0.0f; - memcpy(&displayFov, data + DataView::DISPLAY_FOV[DataView::OFFSET_INDEX], sizeof(displayFov)); - m_diagonalFOV = displayFov; - - float lensDistanceRatio = 0.0f; - memcpy(&lensDistanceRatio, data + DataView::LENS_DISTANCE_RATIO[DataView::OFFSET_INDEX], sizeof(lensDistanceRatio)); - m_lensDistanceRatio = lensDistanceRatio; - - uint8_t sbsEnabled = false; - memcpy(&sbsEnabled, data + DataView::SBS_ENABLED[DataView::OFFSET_INDEX], sizeof(sbsEnabled)); - m_sbsEnabled = (sbsEnabled != 0); - - uint8_t customBannerEnabled = false; - memcpy(&customBannerEnabled, data + DataView::CUSTOM_BANNER_ENABLED[DataView::OFFSET_INDEX], sizeof(customBannerEnabled)); - m_customBannerEnabled = (customBannerEnabled != 0); - - lastConfigUpdate = currentTimeMs; - } - - const bool validKeepAlive = (currentTimeMs - imuDateMs) < 5000; - const bool validData = validKeepAlive && m_diagonalFOV != 0.0f; - const uint8_t expectedVersion = 4; - bool enabledFlagSet = (enabledFlag != 0); - bool validVersion = (version == expectedVersion); - const bool wasEnabled = m_enabled; - const bool enabled = enabledFlagSet && validVersion && validData; - if (!enabled) { - // give a grace period after enabling the effect - if (wasEnabled && (currentTimeMs - activatedAt > 1000)) { - qCCritical(KWIN_XR) << "\t\t\tBreezy - disabling effect; currentTimeMs:" << currentTimeMs - << "imuDateMs:" << imuDateMs - << "enabledFlag:" << enabledFlag - << "version:" << version - << "diagonalFOV:" << m_diagonalFOV; - deactivate(); - m_enabled = false; - Q_EMIT enabledStateChanged(); - return; - } - } else if (!wasEnabled) { - qCCritical(KWIN_XR) << "\t\t\tBreezy - enabling effect; currentTimeMs:" << currentTimeMs - << "imuDateMs:" << imuDateMs - << "enabledFlag:" << enabledFlag - << "version:" << version - << "diagonalFOV:" << m_diagonalFOV; - activate(); - m_enabled = true; - Q_EMIT enabledStateChanged(); - activatedAt = currentTimeMs; - } - - if (updateConfig) Q_EMIT devicePropertiesChanged(); - - float imuData[4 * DataView::IMU_QUAT_ENTRIES]; // 4 quaternion-sized rows - memcpy(imuData, data + DataView::IMU_QUAT_DATA[DataView::OFFSET_INDEX], sizeof(imuData)); - bool wasImuResetState = m_imuResetState; - m_imuResetState = (imuData[0] == 0.0f && imuData[1] == 0.0f && imuData[2] == 0.0f && imuData[3] == 1.0f); - if (m_imuResetState != wasImuResetState) { - if (m_imuResetState) recenter(); - Q_EMIT imuResetStateChanged(); - } - - // convert NWU to EUS by passing root.rotation values: -y, z, -x - QQuaternion quatT0(imuData[3], -imuData[1], imuData[2], -imuData[0]); - - int imuDataOffset = DataView::IMU_QUAT_ENTRIES; - QQuaternion quatT1(imuData[imuDataOffset + 3], -imuData[imuDataOffset + 1], imuData[imuDataOffset + 2], -imuData[imuDataOffset + 0]); - - imuDataOffset += DataView::IMU_QUAT_ENTRIES; - - // skip the 3rd quaternion - imuDataOffset += DataView::IMU_QUAT_ENTRIES; - - // set imuRotations to the last two rotations, leave out the elapsed time - m_imuRotations.clear(); - m_imuRotations.append(quatT0); - m_imuRotations.append(quatT1); - - // 4th row isn't actually a quaternion, it contains the timestamps for each of the 3 quaternions - // elapsed time between T0 and T1 is: imuData[0] - imuData[1] - m_imuTimeElapsedMs = static_cast(imuData[imuDataOffset + 0] - imuData[imuDataOffset + 1]); - - m_imuTimestamp = imuDateMs; -} - -QString BreezyDesktopEffect::cursorImageSource() const -{ - return m_cursorImageSource; -} - -QSize BreezyDesktopEffect::cursorImageSize() const -{ - return m_cursorImageSize; -} - -QPointF BreezyDesktopEffect::cursorPos() const -{ - return m_cursorPos; -} - -void BreezyDesktopEffect::showCursor() -{ - effects->showCursor(); -} - -void BreezyDesktopEffect::hideCursor() -{ - updateCursorImage(); - effects->hideCursor(); -} - -void BreezyDesktopEffect::updateCursorImage() -{ - const auto cursor = effects->cursorImage(); - if (!cursor.image().isNull()) { - QByteArray data; - QBuffer buffer(&data); - buffer.open(QIODevice::WriteOnly); - cursor.image().save(&buffer, "PNG"); - - m_cursorImageSource = QStringLiteral("data:image/png;base64,%1").arg(QString::fromLatin1(data.toBase64())); - m_cursorImageSize = cursor.image().size(); - } else { - m_cursorImageSource = QString(); - m_cursorImageSize = QSize(); - } - Q_EMIT cursorImageSourceChanged(); -} - -void BreezyDesktopEffect::updateCursorPos() -{ - // Update cursor position from effects - const auto cursor = effects->cursorImage(); - QPointF newPos = effects->cursorPos() - cursor.hotSpot(); - if (m_cursorPos != newPos) { - m_cursorPos = newPos; - Q_EMIT cursorPosChanged(); - } -} -} - -#include "breezydesktopeffect.moc" \ No newline at end of file diff --git a/kwin/src/breezydesktopeffect.h b/kwin/src/breezydesktopeffect.h deleted file mode 100644 index f49dba8..0000000 --- a/kwin/src/breezydesktopeffect.h +++ /dev/null @@ -1,148 +0,0 @@ -#pragma once - -#include "kcm/shortcuts.h" -#include - -#include -#include -#include -#include -#include - -namespace KWin -{ - class BreezyDesktopEffect : public QuickSceneEffect - { - Q_OBJECT - Q_PROPERTY(bool isEnabled READ isEnabled NOTIFY enabledStateChanged) - Q_PROPERTY(bool zoomOnFocusEnabled READ isZoomOnFocusEnabled WRITE setZoomOnFocusEnabled NOTIFY zoomOnFocusChanged) - Q_PROPERTY(bool imuResetState READ imuResetState NOTIFY imuResetStateChanged) - Q_PROPERTY(QList imuRotations READ imuRotations) - Q_PROPERTY(quint32 imuTimeElapsedMs READ imuTimeElapsedMs) - Q_PROPERTY(quint64 imuTimestamp READ imuTimestamp) - Q_PROPERTY(QString cursorImageSource READ cursorImageSource NOTIFY cursorImageSourceChanged) - Q_PROPERTY(QSize cursorImageSize READ cursorImageSize NOTIFY cursorImageSourceChanged) - Q_PROPERTY(QPointF cursorPos READ cursorPos NOTIFY cursorPosChanged) - Q_PROPERTY(QList lookAheadConfig READ lookAheadConfig NOTIFY devicePropertiesChanged) - Q_PROPERTY(QList displayResolution READ displayResolution NOTIFY devicePropertiesChanged) - Q_PROPERTY(qreal focusedDisplayDistance READ focusedDisplayDistance NOTIFY focusedDisplayDistanceChanged) - Q_PROPERTY(qreal allDisplaysDistance READ allDisplaysDistance NOTIFY allDisplaysDistanceChanged) - Q_PROPERTY(qreal displaySpacing READ displaySpacing NOTIFY displaySpacingChanged) - Q_PROPERTY(qreal displayHorizontalOffset READ displayHorizontalOffset NOTIFY displayOffsetChanged) - Q_PROPERTY(qreal displayVerticalOffset READ displayVerticalOffset NOTIFY displayOffsetChanged) - Q_PROPERTY(int displayWrappingScheme READ displayWrappingScheme NOTIFY displayWrappingSchemeChanged) - Q_PROPERTY(qreal diagonalFOV READ diagonalFOV NOTIFY devicePropertiesChanged) - Q_PROPERTY(qreal lensDistanceRatio READ lensDistanceRatio NOTIFY devicePropertiesChanged) - Q_PROPERTY(bool sbsEnabled READ sbsEnabled NOTIFY devicePropertiesChanged) - Q_PROPERTY(bool customBannerEnabled READ customBannerEnabled NOTIFY devicePropertiesChanged) - Q_PROPERTY(int antialiasingQuality READ antialiasingQuality NOTIFY antialiasingQualityChanged) - Q_PROPERTY(bool removeVirtualDisplaysOnDisable READ removeVirtualDisplaysOnDisable NOTIFY removeVirtualDisplaysOnDisableChanged) - - public: - - BreezyDesktopEffect(); - ~BreezyDesktopEffect() override; - - void reconfigure(ReconfigureFlags) override; - - int requestedEffectChainPosition() const override; - - QString cursorImageSource() const; - QSize cursorImageSize() const; - QPointF cursorPos() const; - - bool isEnabled() const; - bool isZoomOnFocusEnabled() const; - void setZoomOnFocusEnabled(bool enabled); - QList imuRotations() const; - quint32 imuTimeElapsedMs() const; - quint64 imuTimestamp() const; - bool imuResetState() const; - QList lookAheadConfig() const; - QList displayResolution() const; - qreal focusedDisplayDistance() const; - void setFocusedDisplayDistance(qreal distance); - qreal allDisplaysDistance() const; - void setAllDisplaysDistance(qreal distance); - qreal displaySpacing() const; - void setDisplaySpacing(qreal spacing); - qreal displayHorizontalOffset() const; - qreal displayVerticalOffset() const; - int displayWrappingScheme() const; - qreal diagonalFOV() const; - qreal lensDistanceRatio() const; - bool sbsEnabled() const; - bool customBannerEnabled() const; - int antialiasingQuality() const; - bool removeVirtualDisplaysOnDisable() const; - - void showCursor(); - void hideCursor(); - - public Q_SLOTS: - void activate(); - void deactivate(); - void enableDriver(); - void disableDriver(); - void toggle(); - void addVirtualDisplay(QSize size); - void updateImuRotation(); - void updateCursorImage(); - void updateCursorPos(); - - Q_SIGNALS: - void focusedDisplayDistanceChanged(); - void allDisplaysDistanceChanged(); - void displaySpacingChanged(); - void displayOffsetChanged(); - void displayWrappingSchemeChanged(); - void enabledStateChanged(); - void zoomOnFocusChanged(); - void imuResetStateChanged(); - void cursorImageSourceChanged(); - void cursorPosChanged(); - void devicePropertiesChanged(); - void antialiasingQualityChanged(); - void removeVirtualDisplaysOnDisableChanged(); - - protected: - QVariantMap initialProperties(Output *screen) override; - - private: - void teardown(); - bool checkParityByte(const char* data); - void setupGlobalShortcut(const BreezyShortcuts::Shortcut &shortcut, - std::function triggeredFunc); - void recenter(); - - QString m_cursorImageSource; - QSize m_cursorImageSize; - - bool m_enabled = false; - bool m_zoomOnFocusEnabled = false; - bool m_imuResetState; - QList m_imuRotations; - quint32 m_imuTimeElapsedMs; - quint64 m_imuTimestamp = 0; - QList m_lookAheadConfig; - QList m_displayResolution; - qreal m_diagonalFOV; - qreal m_lensDistanceRatio; - bool m_sbsEnabled; - bool m_customBannerEnabled; - QFileSystemWatcher *m_shmFileWatcher = nullptr; - QFileSystemWatcher *m_shmDirectoryWatcher = nullptr; - QPointF m_cursorPos; - QTimer *m_cursorUpdateTimer = nullptr; - qreal m_focusedDisplayDistance = 0.85; - qreal m_allDisplaysDistance = 1.05; - qreal m_displaySpacing = 0.0; - qreal m_displayHorizontalOffset = 0.0; - qreal m_displayVerticalOffset = 0.0; - int m_displayWrappingScheme = 0; // 0=auto,1=horizontal,2=vertical,3=flat - int m_antialiasingQuality = 3; // 0=None, 1=Medium, 2=High, 3=VeryHigh - bool m_removeVirtualDisplaysOnDisable = true; - QList m_virtualOutputs; - }; - -} // namespace KWin diff --git a/kwin/src/kcm/CMakeLists.txt b/kwin/src/kcm/CMakeLists.txt deleted file mode 100644 index 1df5bc2..0000000 --- a/kwin/src/kcm/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -set(breezy_desktop_config_SOURCES breezydesktopeffectkcm.cpp labeledslider.cpp) -ki18n_wrap_ui(breezy_desktop_config_SOURCES breezydesktopeffectkcm.ui) -qt_add_dbus_interface(breezy_desktop_config_SOURCES ${KWIN_EFFECTS_INTERFACE} kwineffects_interface) - -kcoreaddons_add_plugin(breezy_desktop_config INSTALL_NAMESPACE "kwin/effects/configs" SOURCES ${breezy_desktop_config_SOURCES}) -kconfig_add_kcfg_files(breezy_desktop_config ../breezydesktopconfig.kcfgc) -target_link_libraries(breezy_desktop_config - Qt6::DBus - KF6::ConfigCore - KF6::ConfigGui - KF6::ConfigWidgets - KF6::CoreAddons - KF6::GlobalAccel - KF6::I18n - KF6::KCMUtils - KF6::XmlGui - - 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() diff --git a/kwin/src/kcm/breezydesktopeffectkcm.cpp b/kwin/src/kcm/breezydesktopeffectkcm.cpp deleted file mode 100644 index a3b62e4..0000000 --- a/kwin/src/kcm/breezydesktopeffectkcm.cpp +++ /dev/null @@ -1,457 +0,0 @@ -#include "shortcuts.h" -#include "breezydesktopeffectkcm.h" -#include "breezydesktopconfig.h" -#include "labeledslider.h" -#include "xrdriveripc.h" - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -Q_LOGGING_CATEGORY(KWIN_XR, "kwin.xr") - -static const char EFFECT_GROUP[] = "Effect-breezy_desktop"; - -void addShortcutAction(KActionCollection *collection, const BreezyShortcuts::Shortcut &shortcut) -{ - QAction *action = collection->addAction(shortcut.actionName); - action->setText(shortcut.actionText); - action->setProperty("isConfigurationAction", true); - KGlobalAccel::self()->setDefaultShortcut(action, {shortcut.shortcut}); - KGlobalAccel::self()->setShortcut(action, {shortcut.shortcut}); -} - -K_PLUGIN_CLASS(BreezyDesktopEffectConfig) - -BreezyDesktopEffectConfig::BreezyDesktopEffectConfig(QObject *parent, const KPluginMetaData &data) - : KCModule(parent, data) -{ - ui.setupUi(widget()); - addConfig(BreezyDesktopConfig::self(), widget()); - - // Show/enable Virtual Display controls only when we're on Wayland - const bool isWaylandSession = QGuiApplication::platformName().contains(QStringLiteral("wayland"), Qt::CaseInsensitive) - || qEnvironmentVariable("XDG_SESSION_TYPE").compare(QStringLiteral("wayland"), Qt::CaseInsensitive) == 0; - if (isWaylandSession) { - if (auto lbl = widget()->findChild(QStringLiteral("labelVirtualDisplays"))) { - lbl->setVisible(true); - lbl->setEnabled(true); - } - if (auto row = widget()->findChild(QStringLiteral("widgetVirtualDisplayButtons"))) { - row->setVisible(true); - row->setEnabled(true); - } - if (auto chk = widget()->findChild(QStringLiteral("kcfg_RemoveVirtualDisplaysOnDisable"))) { - chk->setVisible(true); - chk->setEnabled(true); - } - } - - m_statePollTimer.setInterval(2000); - m_statePollTimer.setTimerType(Qt::CoarseTimer); - connect(&m_statePollTimer, &QTimer::timeout, this, &BreezyDesktopEffectConfig::pollDriverState); - m_statePollTimer.start(); - - m_configWatcher = KConfigWatcher::create(BreezyDesktopConfig::self()->sharedConfig()); - if (m_configWatcher) { - connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, - [this](const KConfigGroup &group) { - if (m_updatingFromConfig) { - return; - } - if (group.name() != QLatin1String(EFFECT_GROUP)) { - return; - } - BreezyDesktopConfig::self()->read(); - updateUiFromConfig(); - updateUnmanagedState(); - }); - } - - auto actionCollection = new KActionCollection(this, QStringLiteral("kwin")); - actionCollection->setComponentDisplayName(i18n("KWin")); - actionCollection->setConfigGroup(QStringLiteral("breezy_desktop")); - actionCollection->setConfigGlobal(true); - - addShortcutAction(actionCollection, BreezyShortcuts::TOGGLE); - addShortcutAction(actionCollection, BreezyShortcuts::RECENTER); - addShortcutAction(actionCollection, BreezyShortcuts::TOGGLE_ZOOM_ON_FOCUS); - ui.shortcutsEditor->addCollection(actionCollection); - connect(ui.shortcutsEditor, &KShortcutsEditor::keyChange, this, &BreezyDesktopEffectConfig::markAsChanged); - connect(ui.kcfg_EffectEnabled, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::updateDriverEnabled); - connect(ui.kcfg_ZoomOnFocusEnabled, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::save); - connect(ui.kcfg_FocusedDisplayDistance, &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_DisplayHorizontalOffset, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save); - connect(ui.kcfg_DisplayVerticalOffset, &QSlider::valueChanged, this, &BreezyDesktopEffectConfig::save); - connect(ui.kcfg_DisplayWrappingScheme, qOverload(&QComboBox::currentIndexChanged), this, &BreezyDesktopEffectConfig::save); - connect(ui.kcfg_AntialiasingQuality, qOverload(&QComboBox::currentIndexChanged), this, &BreezyDesktopEffectConfig::save); - connect(ui.kcfg_RemoveVirtualDisplaysOnDisable, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::save); - - if (auto label = widget()->findChild("labelAppNameVersion")) { - label->setText(QStringLiteral("Breezy Desktop - v%1").arg(QLatin1String(BREEZY_DESKTOP_VERSION_STR))); - } - - if (auto btnEmail = widget()->findChild("buttonSubmitEmail")) { - connect(btnEmail, &QPushButton::clicked, this, [this]() { - auto edit = widget()->findChild("lineEditLicenseEmail"); - auto labelStatus = widget()->findChild("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("lineEditLicenseEmail")) { - emailEdit->installEventFilter(this); - } - } - if (auto btnToken = widget()->findChild("buttonSubmitToken")) { - connect(btnToken, &QPushButton::clicked, this, [this]() { - auto edit = widget()->findChild("lineEditLicenseToken"); - auto labelStatus = widget()->findChild("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("lineEditLicenseToken")) { - tokenEdit->installEventFilter(this); - } - } - - // Wire Add Virtual Display buttons via DBus to the effect - auto callAddVirtualDisplay = [](int w, int h) { - QDBusInterface iface( - QStringLiteral("org.kde.KWin"), - QStringLiteral("/com/xronlinux/BreezyDesktop"), - QStringLiteral("com.xronlinux.BreezyDesktop"), - QDBusConnection::sessionBus()); - if (iface.isValid()) { - iface.call(QDBus::NoBlock, QStringLiteral("AddVirtualDisplay"), w, h); - } - }; - if (auto btn1080p = widget()->findChild("buttonAdd1080p")) { - connect(btn1080p, &QPushButton::clicked, this, [callAddVirtualDisplay]() { - callAddVirtualDisplay(1920, 1080); - }); - } - if (auto btn1440p = widget()->findChild("buttonAdd1440p")) { - connect(btn1440p, &QPushButton::clicked, this, [callAddVirtualDisplay]() { - callAddVirtualDisplay(2560, 1440); - }); - } - - // General tab: Open KDE Displays Settings - if (auto btnDisplays = widget()->findChild(QStringLiteral("buttonOpenDisplaysSettings"))) { - connect(btnDisplays, &QPushButton::clicked, this, [this]() { - // Try launching the KScreen KCM - if (!QProcess::startDetached(QStringLiteral("kcmshell6"), {QStringLiteral("kcm_kscreen")})) { - QDesktopServices::openUrl(QUrl(QStringLiteral("systemsettings://kcm_kscreen"))); - } - }); - } -} - -BreezyDesktopEffectConfig::~BreezyDesktopEffectConfig() -{ -} - -void BreezyDesktopEffectConfig::load() -{ - KCModule::load(); - updateUiFromConfig(); - updateUnmanagedState(); -} - -void BreezyDesktopEffectConfig::save() -{ - // Prevent reacting to the file change we ourselves are about to write. - m_updatingFromConfig = true; - updateConfigFromUi(); - BreezyDesktopConfig::self()->save(); - KCModule::save(); - ui.kcfg_FocusedDisplayDistance->setEnabled(ui.kcfg_ZoomOnFocusEnabled->isChecked()); - m_updatingFromConfig = false; - updateUnmanagedState(); - - OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Effects"), QDBusConnection::sessionBus()); - interface.reconfigureEffect(QStringLiteral("breezy_desktop")); -} - -void BreezyDesktopEffectConfig::defaults() -{ - KCModule::defaults(); - updateUiFromDefaultConfig(); - updateUnmanagedState(); -} - -void BreezyDesktopEffectConfig::updateConfigFromUi() -{ - ui.shortcutsEditor->save(); -} - -void BreezyDesktopEffectConfig::updateUiFromConfig() -{ - ui.kcfg_FocusedDisplayDistance->setValue(BreezyDesktopConfig::self()->focusedDisplayDistance()); - ui.kcfg_AllDisplaysDistance->setValue(BreezyDesktopConfig::self()->allDisplaysDistance()); - ui.kcfg_DisplaySpacing->setValue(BreezyDesktopConfig::self()->displaySpacing()); - ui.kcfg_DisplayHorizontalOffset->setValue(BreezyDesktopConfig::self()->displayHorizontalOffset()); - ui.kcfg_DisplayVerticalOffset->setValue(BreezyDesktopConfig::self()->displayVerticalOffset()); - ui.kcfg_DisplayWrappingScheme->setCurrentIndex(BreezyDesktopConfig::self()->displayWrappingScheme()); - ui.kcfg_AntialiasingQuality->setCurrentIndex(BreezyDesktopConfig::self()->antialiasingQuality()); - ui.kcfg_RemoveVirtualDisplaysOnDisable->setChecked(BreezyDesktopConfig::self()->removeVirtualDisplaysOnDisable()); - ui.kcfg_ZoomOnFocusEnabled->setChecked(BreezyDesktopConfig::self()->zoomOnFocusEnabled()); - ui.kcfg_FocusedDisplayDistance->setEnabled(ui.kcfg_ZoomOnFocusEnabled->isChecked()); -} - -void BreezyDesktopEffectConfig::updateUiFromDefaultConfig() -{ - ui.shortcutsEditor->allDefault(); -} - -void BreezyDesktopEffectConfig::updateUnmanagedState() -{ -} - -void BreezyDesktopEffectConfig::updateDriverEnabled() -{ - if (driverEnabled() == ui.kcfg_EffectEnabled->isChecked()) { - return; - } - - QJsonObject newConfig = QJsonObject(); - auto configJsonOpt = XRDriverIPC::instance().retrieveConfig(); - if (configJsonOpt) { - newConfig = configJsonOpt.value(); - } - if (ui.kcfg_EffectEnabled->isChecked()) { - newConfig.insert(QStringLiteral("disabled"), false); - newConfig.insert(QStringLiteral("output_mode"), QStringLiteral("external_only")); - newConfig.insert(QStringLiteral("external_mode"), QStringLiteral("breezy_desktop")); - } else { - newConfig.insert(QStringLiteral("external_mode"), QStringLiteral("none")); - } - XRDriverIPC::instance().writeConfig(newConfig); -} - -bool BreezyDesktopEffectConfig::driverEnabled() -{ - auto configJsonOpt = XRDriverIPC::instance().retrieveConfig(); - if (!configJsonOpt) return false; - auto configJson = configJsonOpt.value(); - bool driverDisabled = configJson.value(QStringLiteral("disabled")).toBool(); - QString driverOutputMode = configJson.value(QStringLiteral("output_mode")).toString(); - QJsonArray driverExternalMode = configJson.value(QStringLiteral("external_mode")).toArray(); - return !driverDisabled && - driverOutputMode == QStringLiteral("external_only") && - driverExternalMode.contains(QJsonValue(QStringLiteral("breezy_desktop"))); -} - -void BreezyDesktopEffectConfig::pollDriverState() -{ - auto &bridge = XRDriverIPC::instance(); - auto stateJsonOpt = bridge.retrieveDriverState(); - if (!stateJsonOpt) return; - auto stateJson = stateJsonOpt.value(); - m_connectedDeviceBrand = stateJson.value(QStringLiteral("connected_device_brand")).toString(); - m_connectedDeviceModel = stateJson.value(QStringLiteral("connected_device_model")).toString(); - - const bool wasDeviceConnected = m_deviceConnected; - m_deviceConnected = !m_connectedDeviceBrand.isEmpty() && !m_connectedDeviceModel.isEmpty(); - if (ui.labelDeviceConnectionStatus->text().isEmpty() || m_deviceConnected != wasDeviceConnected) { - ui.labelDeviceConnectionStatus->setText(m_deviceConnected ? - QStringLiteral("%1 %2 connected").arg(m_connectedDeviceBrand, m_connectedDeviceModel) : - QStringLiteral("No device connected")); - } - - bool effectEnabled = driverEnabled(); - if (ui.kcfg_EffectEnabled->isChecked() != effectEnabled) ui.kcfg_EffectEnabled->setChecked(effectEnabled); - - 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 widgets, bool inProgress) { - for (auto *obj : widgets) { - if (auto *w = qobject_cast(obj)) { - w->setEnabled(!inProgress); - } - } -} - -bool BreezyDesktopEffectConfig::eventFilter(QObject *watched, QEvent *event) { - if (event->type() == QEvent::KeyPress) { - auto *ke = static_cast(event); - if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) { - if (auto *edit = qobject_cast(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(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("tabLicenseDetails"); - if (!tab) return; - auto labelSummary = tab->findChild("labelLicenseSummary"); - if (!labelSummary) return; - auto donate = tab->findChild("labelDonateLink"); - auto globalWarn = widget()->findChild("labelGlobalLicenseWarning"); - - QString status = tr("disabled"); - QString renewalDescriptor = QStringLiteral(""); - auto uiView = rootObj.value(QStringLiteral("ui_view")).toObject(); - auto license = uiView.value(QStringLiteral("license")).toObject(); - bool warningState = false; - bool expired = false; - 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(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()) { - if (isTrial.toBool()) { - status = tr("in trial"); - auto secsVal = prodFeatureObj.value(QStringLiteral("funds_needed_in_seconds")); - if (secsVal.isDouble()) { - qint64 secs = static_cast(secsVal.toDouble()); - QString remaining = secondsToRemainingString(secs); - warningState = !remaining.isEmpty(); - if (warningState) { - QString timeDescriptor = tr("%1 remaining").arg(remaining); - renewalDescriptor = tr(" (%1)").arg(timeDescriptor); - } - } - } - } else { - expired = true; - } - } - } - } - const QString message = tr("Productivity Tier features are %1%2").arg(status, renewalDescriptor); - labelSummary->setText(message); - - if (donate) donate->setVisible(warningState || expired); - - if (globalWarn) { - if (warningState || expired) { - globalWarn->setText(message + (expired ? tr(" — effect disabled") : QString())); - globalWarn->setVisible(true); - } else { - globalWarn->clear(); - globalWarn->setVisible(false); - } - } - - if (expired) { - if (ui.tabWidget) ui.tabWidget->setEnabled(false); - OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"), QStringLiteral("/Effects"), QDBusConnection::sessionBus()); - interface.unloadEffect(QStringLiteral("breezy_desktop")); - } else { - if (ui.tabWidget) ui.tabWidget->setEnabled(true); - } -} - -#include "breezydesktopeffectkcm.moc" \ No newline at end of file diff --git a/kwin/src/kcm/breezydesktopeffectkcm.h b/kwin/src/kcm/breezydesktopeffectkcm.h deleted file mode 100644 index 1a54f4b..0000000 --- a/kwin/src/kcm/breezydesktopeffectkcm.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -#include "ui_breezydesktopeffectkcm.h" - -class KConfigWatcher; -class KConfigGroup; - -class BreezyDesktopEffectConfig : public KCModule -{ - Q_OBJECT - -public: - BreezyDesktopEffectConfig(QObject *parent, const KPluginMetaData &data); - ~BreezyDesktopEffectConfig() override; - -public Q_SLOTS: - void load() override; - void save() override; - void defaults() override; - -private: - void updateDriverEnabled(); - void updateUiFromConfig(); - void updateUiFromDefaultConfig(); - void updateConfigFromUi(); - void updateUnmanagedState(); - bool driverEnabled(); - void pollDriverState(); - void refreshLicenseUi(const QJsonObject &rootObj); - void showStatus(QLabel *label, bool success, const QString &message); - void setRequestInProgress(std::initializer_list widgets, bool inProgress); - bool eventFilter(QObject *watched, QEvent *event) override; - - ::Ui::BreezyDesktopEffectConfig ui; - - KConfigWatcher::Ptr m_configWatcher; - bool m_updatingFromConfig = false; - bool m_deviceConnected = false; - QString m_connectedDeviceBrand; - QString m_connectedDeviceModel; - QTimer m_statePollTimer; // periodic driver state polling - bool m_licenseLoading = false; -}; diff --git a/kwin/src/kcm/breezydesktopeffectkcm.ui b/kwin/src/kcm/breezydesktopeffectkcm.ui deleted file mode 100644 index 1b57da3..0000000 --- a/kwin/src/kcm/breezydesktopeffectkcm.ui +++ /dev/null @@ -1,529 +0,0 @@ - - - BreezyDesktopEffectConfig - - - - - - - - - Qt::AlignHCenter|Qt::AlignVCenter - - - - 14 - 75 - true - - - - - - - - - - - false - - - true - - - Qt::AlignHCenter|Qt::AlignVCenter - - - color: rgb(200,0,0); font-weight: bold; - - - - - - - QTabWidget::North - - - QTabWidget::Rounded - - - - &General - - - - - - XR Effect enabled - - - true - - - - - - - Zoom on Focus - - - false - - - - - - - Focused Display Distance: - - - - - - - 2 - - - QSlider::TicksBelow - - - 25 - - - Qt::Horizontal - - - true - - - - - - - All Displays Distance: - - - - - - - 2 - - - QSlider::TicksBelow - - - 25 - - - Qt::Horizontal - - - true - - - - - - - Display Spacing: - - - - - - - Qt::Horizontal - - - true - - - - - - - Add Virtual Display: - - - false - - - false - - - - - - - false - - - false - - - - - - + 1080p - - - - - - - + 1440p - - - - - - - Rearrange displays - - - - - - - - - - - 0 - 0 - - - - - - - - - &Advanced - - - - - - Display Wrapping Scheme: - - - - - - - - Auto - - - - - Horizontal - - - - - Vertical - - - - - Flat - - - - - - - - Anti-aliasing quality: - - - - - - - - None - - - - - Medium - - - - - High - - - - - Very High - - - - - - - - Display Horizontal Offset: - - - - - - - 2 - - - QSlider::TicksBelow - - - 50 - - - Qt::Horizontal - - - true - - - - - - - Display Vertical Offset: - - - - - - - 2 - - - QSlider::TicksBelow - - - 50 - - - Qt::Horizontal - - - true - - - - - - - false - - - false - - - Remove virtual displays on disable - - true - - - - - - - &License Details - - - - - - - - - true - - - true - - - - - - - <a href="https://ko-fi.com/wheaney">Renew or support on Ko‑fi</a> - - - true - - - Qt::AlignHCenter|Qt::AlignVCenter - - - false - - - - - - - Request a token - - - - - - you@example.com - - - - - - - Submit - - - - - - - - - - false - - - true - - - - - - - - - - Verify token - - - - - - - - - Verify - - - - - - - - - - false - - - true - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - &About - - - - - - Breezy Desktop Effect - v0.0.0 - - - Qt::AlignHCenter|Qt::AlignVCenter - - - - 14 - 75 - true - - - - - - - - Author: Wayne Heaney <wayne@xronlinux.com> - - - Qt::AlignHCenter|Qt::AlignVCenter - - - - - - - License: GPL-3.0 - - - Qt::AlignHCenter|Qt::AlignVCenter - - - true - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - KShortcutsEditor - QWidget -
kshortcutseditor.h
- 1 -
-
- - - -
diff --git a/kwin/src/kcm/labeledslider.cpp b/kwin/src/kcm/labeledslider.cpp deleted file mode 100644 index 23cdc32..0000000 --- a/kwin/src/kcm/labeledslider.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "labeledslider.h" \ No newline at end of file diff --git a/kwin/src/kcm/labeledslider.h b/kwin/src/kcm/labeledslider.h deleted file mode 100644 index 77a62e0..0000000 --- a/kwin/src/kcm/labeledslider.h +++ /dev/null @@ -1,159 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include // for std::max - -/* - * LabeledSlider - * Horizontal QSlider that draws numeric labels at tick intervals and (optionally) a value bubble. - * Usage: - * auto *s = new LabeledSlider(parent); - * s->setMinimum(20); - * s->setMaximum(250); - * s->setTickInterval(20); - * s->setTickPosition(QSlider::TicksBelow); - * s->setShowValueBubble(true); - */ - -class LabeledSlider : public QSlider { - Q_OBJECT - Q_PROPERTY(bool showValueBubble READ showValueBubble WRITE setShowValueBubble) - // decimalShift: number of places to shift the decimal point left for display ONLY. - // Example: raw value 250 with decimalShift=2 displays as 2.50. Underlying slider value - // (signals, stored config) remains 250. - Q_PROPERTY(int decimalShift READ decimalShift WRITE setDecimalShift) -public: - explicit LabeledSlider(QWidget *parent = nullptr) - : QSlider(Qt::Horizontal, parent) - { - setTickPosition(QSlider::TicksBelow); - } - - bool showValueBubble() const { return m_showValueBubble; } - void setShowValueBubble(bool on) { - if (m_showValueBubble == on) return; - m_showValueBubble = on; - update(); - } - - int decimalShift() const { return m_decimalShift; } - void setDecimalShift(int shift) { - // clamp to sensible range - if (shift < 0) shift = 0; - if (shift > 6) shift = 6; // avoid large power-of-10 overflow - if (m_decimalShift == shift) return; - m_decimalShift = shift; - updateGeometry(); - update(); - } - - QSize sizeHint() const override { - QSize sz = QSlider::sizeHint(); - int extraH = 0; - if (labelInterval() > 0) { - // Reserve space for bottom labels - QFontMetrics fm(font()); - extraH += fm.height() + 4; - } - if (m_showValueBubble) { - QFontMetrics fm(font()); - extraH = std::max(extraH, fm.height() + 8); // bubble might be above - } - sz.setHeight(sz.height() + extraH); - return sz; - } - -protected: - void paintEvent(QPaintEvent *e) override { - QSlider::paintEvent(e); - - QStyleOptionSlider opt; - initStyleOption(&opt); - - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing, true); - - const int minV = minimum(); - const int maxV = maximum(); - - // Draw labels below ticks - if (labelInterval() > 0) { - QFontMetrics fm(font()); - const int baselineY = height() - fm.descent() - 1; - int interval = labelInterval(); - for (int v = minV; v <= maxV; v += interval) { - // Use style geometry for handle at this position to match tick placement. - QStyleOptionSlider optPos = opt; - optPos.sliderPosition = v; - optPos.sliderValue = v; - QRect handleAtVal = style()->subControlRect(QStyle::CC_Slider, &optPos, QStyle::SC_SliderHandle, this); - int x = handleAtVal.center().x(); - QString text = valueToDisplayString(v); - int halfW = fm.horizontalAdvance(text) / 2; - QRect r(x - halfW, baselineY - fm.ascent(), fm.horizontalAdvance(text), fm.height()); - p.drawText(r, Qt::AlignCenter, text); - } - } - - // Draw floating value bubble over handle - if (m_showValueBubble) { - // Handle rect - const QRect handle = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); - QString valText = valueToDisplayString(value()); - QFontMetrics fm(font()); - QRect textRect = fm.boundingRect(valText); - textRect.adjust(-6, -4, 6, 4); - - // Position bubble above the handle; add extra lift - const int extraLift = 10; // requested additional pixels - const int gap = 4; // minimal gap between handle top and bubble - int topY = handle.top() - gap - extraLift - textRect.height(); - if (topY < 0) topY = 0; // clamp to widget - textRect.moveTop(topY); - textRect.moveLeft(handle.center().x() - textRect.width()/2); - - // Bubble shape - QPainterPath path; - path.addRoundedRect(textRect, 6, 6); - - p.setPen(Qt::NoPen); - p.setBrush(palette().toolTipBase()); - p.drawPath(path); - - p.setPen(palette().toolTipText().color()); - p.drawText(textRect, Qt::AlignCenter, valText); - } - } - -private: - QString valueToDisplayString(int raw) const { - if (m_decimalShift == 0) { - return QString::number(raw); - } - int divisor = 1; - for (int i = 0; i < m_decimalShift; ++i) divisor *= 10; - int whole = raw / divisor; - int frac = std::abs(raw % divisor); - QString fracStr = QString::number(frac).rightJustified(m_decimalShift, QLatin1Char('0')); - QString result = QString::number(std::abs(whole)) + QLatin1Char('.') + fracStr; - if (raw < 0) result.prepend(QLatin1Char('-')); - return result; - } - - bool m_showValueBubble = true; - int m_decimalShift = 0; // display-only decimal shift -private: - int labelInterval() const { - int ti = tickInterval(); - if (ti > 0) return ti; - // Heuristic fallback: divide range into ~10 segments. - int range = maximum() - minimum(); - if (range <= 0) return 0; - int approx = range / 10; - if (approx <= 0) approx = range; // single label at ends - return approx; - } -}; \ No newline at end of file diff --git a/kwin/src/kcm/shortcuts.h b/kwin/src/kcm/shortcuts.h deleted file mode 100644 index f5792a6..0000000 --- a/kwin/src/kcm/shortcuts.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace BreezyShortcuts { - struct Shortcut { - QKeySequence shortcut; - QString actionName; - QString actionText; - }; - - const Shortcut TOGGLE = { - Qt::CTRL | Qt::META | Qt::Key_Backslash, - QStringLiteral("Toggle XR Effect"), - QStringLiteral("Toggle XR Effect") - }; - - const Shortcut RECENTER = { - Qt::CTRL | Qt::META | Qt::Key_Space, - QStringLiteral("Recenter"), - QStringLiteral("Recenter") - }; - - const Shortcut TOGGLE_ZOOM_ON_FOCUS = { - Qt::CTRL | Qt::META | Qt::Key_0, - QStringLiteral("Toggle Zoom on Focus"), - QStringLiteral("Toggle Zoom on Focus") - }; -} diff --git a/kwin/src/main.cpp b/kwin/src/main.cpp deleted file mode 100644 index fd243a0..0000000 --- a/kwin/src/main.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "breezydesktopeffect.h" - -namespace KWin -{ - -KWIN_EFFECT_FACTORY(BreezyDesktopEffect, "metadata.json") - -} // namespace KWin - -#include "main.moc" diff --git a/kwin/src/metadata.json b/kwin/src/metadata.json deleted file mode 100644 index 8d582c4..0000000 --- a/kwin/src/metadata.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "KPackageStructure": "KWin/Effect", - "KPlugin": { - "Authors": [ - { - "Email": "wayne@xronlinux.com", - "Name": "Wayne Heaney" - } - ], - "Category": "Tools", - "Description": "Breezy Desktop XR Effect", - "EnabledByDefault": true, - "License": "GPL", - "Name": "Breezy Desktop XR" - }, - "X-KDE-ConfigModule": "breezy_desktop_config" -} diff --git a/kwin/src/qml/BreezyDesktop.qml b/kwin/src/qml/BreezyDesktop.qml deleted file mode 100644 index 4520c1e..0000000 --- a/kwin/src/qml/BreezyDesktop.qml +++ /dev/null @@ -1,171 +0,0 @@ -import QtQuick -import QtQuick3D - - -Node { - id: breezyDesktop - - property var viewportResolution: effect.displayResolution - property var screens: root.screens - property var fovDetails: root.fovDetails - property var monitorPlacements: root.monitorPlacements - property int focusedMonitorIndex: -1 - - Displays { - id: displays - } - - function displayAtIndex(index) { - if (index < 0 || index >= screens.length) { - return null; - } - return breezyDesktopDisplays.objectAt(index); - } - - Repeater3D { - id: breezyDesktopDisplays - model: breezyDesktop.screens.length - delegate: BreezyDesktopDisplay { - screen: breezyDesktop.screens[index] - monitorPlacement: breezyDesktop.monitorPlacements[index] - - property real monitorDistance: effect.allDisplaysDistance - property real targetDistance: effect.allDisplaysDistance - property real screenRotationY: displays.radianToDegree(monitorPlacement.rotationAngleRadians.y) - property real screenRotationX: displays.radianToDegree(monitorPlacement.rotationAngleRadians.x) - property matrix4x4 rotationMatrix: { - const matrix = Qt.matrix4x4(); - matrix.rotate(screenRotationY, Qt.vector3d(0, 1, 0)); - matrix.rotate(screenRotationX, Qt.vector3d(1, 0, 0)); - return matrix; - } - - property vector3d screenScale: { - const geometry = screen.geometry; - - // apparently the default model unit size is 100x100, so we scale it up to the screen size - return Qt.vector3d(geometry.width / 100, geometry.height / 100, 1); - } - - scale: screenScale - eulerRotation.y: screenRotationY - eulerRotation.x: screenRotationX - position: { - const displayNwu = - monitorPlacement.centerNoRotate - .times(monitorDistance / effect.allDisplaysDistance); - - - return rotationMatrix.times(displays.nwuToEusVector(displayNwu)); - } - } - } - - Timer { - interval: 500 // 500ms - 2x per second to avoid running this check too frequently - repeat: true - running: true - onTriggered: { - if (effect.imuRotations && effect.imuRotations.length > 0) { - let focusedIndex = -1; - - if (effect.zoomOnFocusEnabled) { - focusedIndex = displays.findFocusedMonitor( - displays.eusToNwuQuat(effect.imuRotations[0]), - breezyDesktop.monitorPlacements.map(monitorVectors => monitorVectors.centerLook), - breezyDesktop.focusedMonitorIndex, - false, // TODO smooth follow - breezyDesktop.fovDetails, - breezyDesktop.screens.map(screen => screen.geometry) - ); - } - - if (focusedIndex !== breezyDesktop.focusedMonitorIndex) { - const unfocusedIndex = breezyDesktop.focusedMonitorIndex; - const focusedDisplay = focusedIndex !== -1 ? breezyDesktop.displayAtIndex(focusedIndex) : null; - const allDisplaysDistanceBinding = Qt.binding(function() { return effect.allDisplaysDistance; }); - const focusedDisplayDistanceBinding = Qt.binding(function() { return effect.focusedDisplayDistance; }); - if (focusedDisplay === null) { - const unfocusedDisplay = breezyDesktop.displayAtIndex(unfocusedIndex); - zoomOutAnimation.target = unfocusedDisplay; - zoomOutAnimation.target.targetDistance = effect.allDisplaysDistance; - zoomOutAnimation.start(); - } else { - if (unfocusedIndex === -1) { - zoomInAnimation.target = focusedDisplay; - focusedDisplay.targetDistance = effect.focusedDisplayDistance; - zoomInAnimation.start(); - } else { - zoomInSeqAnimation.target = focusedDisplay; - focusedDisplay.targetDistance = effect.focusedDisplayDistance; - - const unfocusedDisplay = breezyDesktop.displayAtIndex(unfocusedIndex); - zoomOutSeqAnimation.target = unfocusedDisplay; - zoomOutSeqAnimation.target.targetDistance = effect.allDisplaysDistance; - - zoomOnFocusSequence.start(); - } - } - breezyDesktop.focusedMonitorIndex = focusedIndex; - } - } - } - } - - NumberAnimation { - id: zoomOutAnimation - property: "monitorDistance" - to: effect.allDisplaysDistance - duration: 150 - running: false - onFinished: { - const unfocusedDisplay = zoomOutAnimation.target; - if (unfocusedDisplay) { - unfocusedDisplay.monitorDistance = Qt.binding(function() { return effect.allDisplaysDistance; }); - } - } - } - - NumberAnimation { - id: zoomInAnimation - property: "monitorDistance" - to: effect.focusedDisplayDistance - duration: 300 - running: false - onFinished: { - const focusedDisplay = zoomInAnimation.target; - if (focusedDisplay) { - focusedDisplay.monitorDistance = Qt.binding(function() { return effect.focusedDisplayDistance; }); - } - } - } - - SequentialAnimation { - id: zoomOnFocusSequence - running: false - onFinished: { - const focusedDisplay = zoomInSeqAnimation.target; - if (focusedDisplay) { - focusedDisplay.monitorDistance = Qt.binding(function() { return effect.focusedDisplayDistance; }); - } - const unfocusedDisplay = zoomOutSeqAnimation.target; - if (unfocusedDisplay) { - unfocusedDisplay.monitorDistance = Qt.binding(function() { return effect.allDisplaysDistance; }); - } - } - - NumberAnimation { - id: zoomOutSeqAnimation - property: "monitorDistance" - to: effect.allDisplaysDistance - duration: 150 - } - PauseAnimation { duration: 50 } - NumberAnimation { - id: zoomInSeqAnimation - property: "monitorDistance" - to: effect.focusedDisplayDistance - duration: 300 - } - } -} diff --git a/kwin/src/qml/BreezyDesktopDisplay.qml b/kwin/src/qml/BreezyDesktopDisplay.qml deleted file mode 100644 index e192c11..0000000 --- a/kwin/src/qml/BreezyDesktopDisplay.qml +++ /dev/null @@ -1,53 +0,0 @@ -import QtQuick -import QtQuick3D - -Model { - id: display - - required property QtObject screen - required property var monitorPlacement - required property int index - - property string cursorImageSource: effect.cursorImageSource - property size cursorImageSize: effect.cursorImageSize - property point cursorPos: effect.cursorPos - - source: "#Rectangle" - materials: [ - CustomMaterial { - id: customMat - depthDrawMode: CustomMaterial.AlwaysDepthDraw - shadingMode: CustomMaterial.Unshaded - - property real screenWidth: display.screen.geometry.width - property real screenHeight: display.screen.geometry.height - property real cursorX: display.cursorPos.x - display.screen.geometry.x - property real cursorY: display.cursorPos.y - display.screen.geometry.y - property real cursorW: display.cursorImageSize.width - property real cursorH: display.cursorImageSize.height - property bool showCursor: cursorX >= 0 && cursorX < screenWidth && cursorY >= 0 && cursorY < screenHeight - - property TextureInput desktopTex: TextureInput { - texture: Texture { - sourceItem: DesktopView { - screen: display.screen - width: display.screen.geometry.width - height: display.screen.geometry.height - } - } - } - property TextureInput cursorTex: TextureInput { - texture: Texture { - sourceItem: Image { - source: effect.cursorImageSource - width: effect.cursorImageSize.width - height: effect.cursorImageSize.height - } - } - } - - fragmentShader: "cursorOverlay.frag" - vertexShader: "cursorOverlay.vert" - } - ] -} diff --git a/kwin/src/qml/CameraController.qml b/kwin/src/qml/CameraController.qml deleted file mode 100644 index ce69a9c..0000000 --- a/kwin/src/qml/CameraController.qml +++ /dev/null @@ -1,80 +0,0 @@ -import QtQuick -import QtQuick3D - -Item { - id: root - - required property Camera camera - - property var displayResolution: effect.displayResolution - property real diagonalFOV: effect.diagonalFOV - property real lensDistanceRatio: effect.lensDistanceRatio - property bool sbsEnabled: effect.sbsEnabled - property bool customBannerEnabled: effect.customBannerEnabled - - implicitWidth: parent.width - implicitHeight: parent.height - - Displays { - id: displays - } - - function updateCamera(rotation) { - camera.eulerRotation = rotation; - } - - // how far to look ahead is how old the IMU data is plus a constant that is either the default for this device or an override - function lookAheadMS(imuDateMs, lookAheadConfig, override) { - // how stale the imu data is - const dataAge = Date.now() - imuDateMs; - - const lookAheadConstant = lookAheadConfig[0]; - const lookAheadMultiplier = lookAheadConfig[1]; - return (override === -1 ? lookAheadConstant : override) + dataAge; - } - - function applyLookAhead(quatT0, quatT1, elapsedTimeMs, lookAheadMs) { - // convert both quats to euler angles - const eulerT0 = quatT0.toEulerAngles(); - const eulerT1 = quatT1.toEulerAngles(); - - // compute the rate of change of the angles based on the elapsed time - const deltaX = (eulerT0.x - eulerT1.x); - const deltaY = (eulerT0.y - eulerT1.y); - const deltaZ = (eulerT0.z - eulerT1.z); - - // how much of the delta to apply based on the look-ahead time - const timeConstant = lookAheadMs / elapsedTimeMs; - - return Qt.vector3d( - eulerT0.x + deltaX * timeConstant, - eulerT0.y + deltaY * timeConstant, - eulerT0.z + deltaZ * timeConstant, - ); - } - - function updateFOV() { - const aspectRatio = displayResolution[0] / displayResolution[1]; - camera.fieldOfView = displays.radianToDegree(displays.diagonalToCrossFOVs( - displays.degreeToRadian(root.diagonalFOV), - aspectRatio - ).vertical); - } - - onDisplayResolutionChanged: updateFOV(); - onDiagonalFOVChanged: updateFOV(); - - FrameAnimation { - running: true - onTriggered: { - if (effect.imuRotations && effect.imuRotations.length > 0) { - updateCamera(applyLookAhead( - effect.imuRotations[0], - effect.imuRotations[1], - effect.imuTimeElapsedMs, - lookAheadMS(effect.imuTimestamp, effect.lookAheadConfig, -1) - )); - } - } - } -} diff --git a/kwin/src/qml/DesktopView.qml b/kwin/src/qml/DesktopView.qml deleted file mode 100644 index 10d9940..0000000 --- a/kwin/src/qml/DesktopView.qml +++ /dev/null @@ -1,43 +0,0 @@ -import QtQuick -import org.kde.kwin as KWinComponents - -Item { - id: desktopView - - required property QtObject screen - - function overlapsScreen(win, screenGeom) { - if (!win) return false - const winLeft = win.x - const winTop = win.y - const winRight = winLeft + win.width - const winBottom = winTop + win.height - - const scrLeft = screenGeom.x - const scrTop = screenGeom.y - const scrRight = scrLeft + screenGeom.width - const scrBottom = scrTop + screenGeom.height - - return winLeft < scrRight && - winRight > scrLeft && - winTop < scrBottom && - winBottom > scrTop - } - - Repeater { - model: KWinComponents.WindowModel {} - - KWinComponents.WindowThumbnail { - // Only show if window overlaps this screen (any amount) and not minimized. - readonly property bool onThisActivity: model.window.activities.length === 0 || model.window.activities.includes(KWinComponents.Workspace.currentActivity) - readonly property bool onThisDesktop: onThisActivity && (model.window.onAllDesktops || model.window.desktops.includes(KWinComponents.Workspace.currentDesktop)) - readonly property bool onThisScreen: onThisDesktop && desktopView.overlapsScreen(model.window, desktopView.screen.geometry) - - wId: model.window.internalId - x: model.window.x - desktopView.screen.geometry.x - y: model.window.y - desktopView.screen.geometry.y - z: model.window.stackingOrder - visible: onThisScreen && !model.window.minimized - } - } -} diff --git a/kwin/src/qml/Displays.qml b/kwin/src/qml/Displays.qml deleted file mode 100644 index dee70b6..0000000 --- a/kwin/src/qml/Displays.qml +++ /dev/null @@ -1,405 +0,0 @@ -import QtQuick - -QtObject { - readonly property real focusThreshold: 0.95 / 2.0 - readonly property real unfocusThreshold: 1.1 / 2.0 - - // Converts degrees to radians - function degreeToRadian(degree) { - return degree * Math.PI / 180; - } - - function radianToDegree(radian) { - return radian * 180 / Math.PI; - } - - function nwuToEusVector(vector) { - // Converts NWU vector to EUS vector - return Qt.vector3d(-vector.y, vector.z, -vector.x); - } - - function eusToNwuQuat(quaternion) { - // Converts EUS quaternion to NWU quaternion - return Qt.quaternion(quaternion.scalar, -quaternion.z, -quaternion.x, quaternion.y); - } - - // Converts diagonal FOV in radians and aspect ratio to horizontal and vertical FOVs - function diagonalToCrossFOVs(diagonalFOVRadians, aspectRatio) { - var flatDiagonalFOV = 2 * Math.tan(diagonalFOVRadians / 2); - var flatVerticalFOV = flatDiagonalFOV / Math.sqrt(1 + aspectRatio * aspectRatio); - var flatHorizontalFOV = flatVerticalFOV * aspectRatio; - return { - diagonal: diagonalFOVRadians, - horizontal: 2 * Math.atan(flatHorizontalFOV / 2), - vertical: 2 * Math.atan(flatVerticalFOV / 2) - } - } - - function actualWrapScheme(screens, viewportWidth, viewportHeight) { - const minX = Math.min(...screens.map(screen => screen.geometry.x)); - const maxX = Math.max(...screens.map(screen => screen.geometry.x + screen.geometry.width)); - const minY = Math.min(...screens.map(screen => screen.geometry.y)); - const maxY = Math.max(...screens.map(screen => screen.geometry.y + screen.geometry.height)); - - if ((maxX - minX) / viewportWidth >= (maxY - minY) / viewportHeight) { - return 'horizontal'; - } else { - return 'vertical'; - } - } - - function fovDetails(screens, viewportWidth, viewportHeight, viewportDiagonalFOV, lensDistanceRatio, defaultDisplayDistance, wrappingChoice) { - const aspect = viewportWidth / viewportHeight; - const fovRadians = diagonalToCrossFOVs(degreeToRadian(viewportDiagonalFOV), aspect); - const defaultDistanceVerticalRadians = 2 * Math.atan(Math.tan(fovRadians.vertical / 2) / defaultDisplayDistance); - const defaultDistanceHorizontalRadians = 2 * Math.atan(Math.tan(fovRadians.horizontal / 2) / defaultDisplayDistance); - - // distance needed for the FOV-sized monitor to fill up the screen - const fullScreenDistance = viewportHeight / 2 / Math.tan(fovRadians.vertical / 2); - const lensDistancePixels = fullScreenDistance / (1.0 - lensDistanceRatio) - fullScreenDistance; - - // distance of a display at the default (most zoomed out) distance, plus the lens distance constant - const lensToScreenDistance = viewportHeight / 2 / Math.tan(defaultDistanceVerticalRadians / 2); - const completeScreenDistancePixels = lensToScreenDistance + lensDistancePixels; - - let monitorWrappingScheme = actualWrapScheme(screens, viewportWidth, viewportHeight); - if (wrappingChoice === 1) monitorWrappingScheme = 'horizontal'; - else if (wrappingChoice === 2) monitorWrappingScheme = 'vertical'; - else if (wrappingChoice === 3) monitorWrappingScheme = 'flat'; - - return { - widthPixels: viewportWidth, - heightPixels: viewportHeight, - defaultDistanceVerticalRadians, - defaultDistanceHorizontalRadians, - lensDistancePixels, - completeScreenDistancePixels, - monitorWrappingScheme: monitorWrappingScheme, - curvedDisplay: false // or true - }; - } - - // Utility constant - readonly property real segmentsPerRadian: 20.0 / degreeToRadian(90.0) - - // FOV conversion functions for flat and curved displays - property var fovConversionFns: ({ - flat: { - centerToFovEdgeDistance: function(centerDistance, fovLength) { - return Math.sqrt(Math.pow(fovLength / 2, 2) + Math.pow(centerDistance, 2)); - }, - fovEdgeToScreenCenterDistance: function(edgeDistance, screenLength) { - return Math.sqrt(Math.pow(edgeDistance, 2) - Math.pow(screenLength / 2, 2)); - }, - lengthToRadians: function(fovRadians, fovLength, screenEdgeDistance, toLength) { - return Math.asin(toLength / 2 / screenEdgeDistance) * 2; - }, - angleToLength: function(fovRadians, fovLength, screenDistance, toAngleOpposite, toAngleAdjacent) { - return toAngleOpposite / toAngleAdjacent * screenDistance; - }, - radiansToSegments: function(screenRadians) { return 1; } - }, - curved: { - centerToFovEdgeDistance: function(centerDistance, fovLength) { - return centerDistance; - }, - fovEdgeToScreenCenterDistance: function(edgeDistance, screenLength) { - return edgeDistance; - }, - lengthToRadians: function(fovRadians, fovLength, screenEdgeDistance, toLength) { - return fovRadians / fovLength * toLength; - }, - angleToLength: function(fovRadians, fovLength, screenDistance, toAngleOpposite, toAngleAdjacent) { - return fovLength / fovRadians * Math.atan2(toAngleOpposite, toAngleAdjacent); - }, - radiansToSegments: function(screenRadians) { - return Math.ceil(screenRadians * segmentsPerRadian); - } - } - }) - - function monitorWrap(cachedMonitorRadians, monitorSpacingPixels, monitorBeginPixel, monitorLengthPixels, lengthToRadianFn) { - var closestWrapPixel = monitorBeginPixel; - var closestWrap = cachedMonitorRadians[monitorBeginPixel]; - if (closestWrap === undefined) { - var keys = Object.keys(cachedMonitorRadians); - closestWrapPixel = keys.reduce(function(previousPixel, currentPixel) { - if (previousPixel === undefined) return currentPixel; - - var currentDelta = currentPixel - monitorBeginPixel; - var previousDelta = previousPixel - monitorBeginPixel; - - if (previousDelta % monitorLengthPixels !== 0) { - if (currentDelta % monitorLengthPixels === 0) return currentPixel; - if (previousDelta < 0 && currentDelta > 0) return currentPixel; - if (Math.abs(currentDelta) < Math.abs(previousDelta)) return currentPixel; - } - return previousPixel; - }, undefined); - closestWrap = cachedMonitorRadians[closestWrapPixel]; - } - - var spacingRadians = lengthToRadianFn(monitorSpacingPixels); - if (closestWrapPixel !== monitorBeginPixel) { - var gapPixels = monitorBeginPixel - closestWrapPixel; - var gapRadians = lengthToRadianFn(gapPixels); - var appliedSpacingRadians = Math.floor(gapPixels / monitorLengthPixels) * spacingRadians; - closestWrap = closestWrap + gapRadians + appliedSpacingRadians; - closestWrapPixel = monitorBeginPixel; - cachedMonitorRadians[closestWrapPixel] = closestWrap; - } - - var monitorRadians = lengthToRadianFn(monitorLengthPixels); - var centerRadians = closestWrap + monitorRadians / 2; - var endRadians = closestWrap + monitorRadians; - - var nextMonitorPixel = monitorBeginPixel + monitorLengthPixels; - if (cachedMonitorRadians[nextMonitorPixel] === undefined) - cachedMonitorRadians[nextMonitorPixel] = endRadians + spacingRadians; - - return { - begin: closestWrap, - center: centerRadians, - end: endRadians - } - } - - function horizontalMonitorSort(monitors) { - return monitors.map(function(monitor, index) { - return { originalIndex: index, monitorDetails: monitor }; - }).sort(function(a, b) { - var aMon = a.monitorDetails; - var bMon = b.monitorDetails; - if (aMon.y !== bMon.y) return aMon.y - bMon.y; - return aMon.x - bMon.x; - }); - } - - function verticalMonitorSort(monitors) { - return monitors.map(function(monitor, index) { - return { originalIndex: index, monitorDetails: monitor }; - }).sort(function(a, b) { - var aMon = a.monitorDetails; - var bMon = b.monitorDetails; - if (aMon.x !== bMon.x) return aMon.x - bMon.x; - return aMon.y - bMon.y; - }); - } - - // fovDetails: { widthPixels, heightPixels, defaultDistanceHorizontalRadians, defaultDistanceVerticalRadians, completeScreenDistancePixels, monitorWrappingScheme, curvedDisplay } - // monitorDetailsList: [{x, y, width, height}, ...] - // monitorSpacing: number (percentage, e.g. 0.05 for 5%) - function monitorsToPlacements(fovDetails, monitorDetailsList, monitorSpacing) { - var monitorPlacements = []; - var cachedMonitorRadians = {}; - - var conversionFns = fovDetails.curvedDisplay ? fovConversionFns.curved : fovConversionFns.flat; - - if (fovDetails.monitorWrappingScheme === 'horizontal') { - var sideEdgeRadius = conversionFns.centerToFovEdgeDistance(fovDetails.completeScreenDistancePixels, fovDetails.widthPixels); - var monitorSpacingPixels = monitorSpacing * fovDetails.widthPixels; - var lengthToRadianFn = function(targetWidth) { - return conversionFns.lengthToRadians( - fovDetails.defaultDistanceHorizontalRadians, - fovDetails.widthPixels, - sideEdgeRadius, - targetWidth - ); - }; - - cachedMonitorRadians[0] = -fovDetails.defaultDistanceHorizontalRadians / 2; - horizontalMonitorSort(monitorDetailsList).forEach(function(obj) { - var monitorDetails = obj.monitorDetails; - var originalIndex = obj.originalIndex; - var monitorWrapDetails = monitorWrap(cachedMonitorRadians, monitorSpacingPixels, monitorDetails.x, monitorDetails.width, lengthToRadianFn); - var monitorCenterRadius = conversionFns.fovEdgeToScreenCenterDistance(sideEdgeRadius, monitorDetails.width); - var upTopPixels = -monitorDetails.y - (monitorDetails.y / fovDetails.heightPixels) * monitorSpacingPixels; - var upCenterOffsetPixels = (monitorDetails.height - fovDetails.heightPixels) / 2; - var upCenterPixels = upTopPixels - upCenterOffsetPixels; - - monitorPlacements.push({ - originalIndex: originalIndex, - centerNoRotate: Qt.vector3d( - monitorCenterRadius, - 0, - upCenterPixels - ), - centerLook: Qt.vector3d( - monitorCenterRadius * Math.cos(monitorWrapDetails.center), - -monitorCenterRadius * Math.sin(monitorWrapDetails.center), - upCenterPixels - ).normalized(), - rotationAngleRadians: { - x: 0, - y: -monitorWrapDetails.center - } - }); - }); - } else if (fovDetails.monitorWrappingScheme === 'vertical') { - var topEdgeRadius = conversionFns.centerToFovEdgeDistance(fovDetails.completeScreenDistancePixels, fovDetails.heightPixels); - var monitorSpacingPixels = monitorSpacing * fovDetails.heightPixels; - var lengthToRadianFn = function(targetHeight) { - return conversionFns.lengthToRadians( - fovDetails.defaultDistanceVerticalRadians, - fovDetails.heightPixels, - topEdgeRadius, - targetHeight - ); - }; - - cachedMonitorRadians[0] = -fovDetails.defaultDistanceVerticalRadians / 2; - verticalMonitorSort(monitorDetailsList).forEach(function(obj) { - var monitorDetails = obj.monitorDetails; - var originalIndex = obj.originalIndex; - var monitorWrapDetails = monitorWrap(cachedMonitorRadians, monitorSpacingPixels, monitorDetails.y, monitorDetails.height, lengthToRadianFn); - var monitorCenterRadius = conversionFns.fovEdgeToScreenCenterDistance(topEdgeRadius, monitorDetails.height); - var westLeftPixels = -monitorDetails.x - (monitorDetails.x / fovDetails.widthPixels) * monitorSpacingPixels; - var westCenterOffsetPixels = (monitorDetails.width - fovDetails.widthPixels) / 2; - var westCenterPixels = westLeftPixels - westCenterOffsetPixels; - - monitorPlacements.push({ - originalIndex: originalIndex, - centerNoRotate: Qt.vector3d( - monitorCenterRadius, - westCenterPixels, - 0 - ), - centerLook: Qt.vector3d( - monitorCenterRadius * Math.cos(monitorWrapDetails.center), - westCenterPixels, - -monitorCenterRadius * Math.sin(monitorWrapDetails.center) - ).normalized(), - rotationAngleRadians: { - x: -monitorWrapDetails.center, - y: 0 - } - }); - }); - } else { - var monitorSpacingPixels = monitorSpacing * fovDetails.widthPixels; - monitorDetailsList.forEach(function(monitorDetails, index) { - var upTopPixels = -monitorDetails.y - (monitorDetails.y / fovDetails.heightPixels) * monitorSpacingPixels; - var westLeftPixels = -monitorDetails.x - (monitorDetails.x / fovDetails.widthPixels) * monitorSpacingPixels; - var westCenterOffsetPixels = (monitorDetails.width - fovDetails.widthPixels) / 2; - var upCenterOffsetPixels = (monitorDetails.height - fovDetails.heightPixels) / 2; - var westCenterPixels = westLeftPixels - westCenterOffsetPixels; - var upCenterPixels = upTopPixels - upCenterOffsetPixels; - - monitorPlacements.push({ - originalIndex: index, - centerNoRotate: Qt.vector3d( - fovDetails.completeScreenDistancePixels, - westCenterPixels, - upCenterPixels - ), - centerLook: Qt.vector3d( - fovDetails.completeScreenDistancePixels, - westCenterPixels, - upCenterPixels - ).normalized(), - rotationAngleRadians: { - x: 0, - y: 0 - } - }); - }); - } - - // put them back in the original monitor order before returning - monitorPlacements.sort(function(a, b) { return a.originalIndex - b.originalIndex; }); - - return monitorPlacements; - } - - // returns how far the look vector is from the center of the monitor, as a percentage of the monitor's dimensions - function getMonitorDistance(fovDetails, lookUpPixels, lookWestPixels, monitorVector, monitorDetails, upAngleToLength, westAngleToLength) { - var vectorUpPixels = upAngleToLength( - fovDetails.defaultDistanceVerticalRadians, - fovDetails.heightPixels, - fovDetails.completeScreenDistancePixels, - monitorVector.z, - monitorVector.x - ); - var upPercentage = Math.abs(lookUpPixels - vectorUpPixels) / monitorDetails.height; - - var vectorWestPixels = westAngleToLength( - fovDetails.defaultDistanceHorizontalRadians, - fovDetails.widthPixels, - fovDetails.completeScreenDistancePixels, - monitorVector.y, - monitorVector.x - ); - var westPercentage = Math.abs(lookWestPixels - vectorWestPixels) / monitorDetails.width; - - // how close we are to any edge is the largest of the two percentages - return Math.max(upPercentage, westPercentage); - } - - function findFocusedMonitor(quaternion, monitorVectors, currentFocusedIndex, smoothFollowEnabled, fovDetails, monitorsDetails) { - var lookVector = Qt.vector3d(1.0, 0.0, 0.0); // NWU vector pointing to the center of the screen - var rotatedLookVector = quaternion.times(lookVector); - - // Use curved or flat conversion functions depending on wrapping scheme - var upConversionFns = fovDetails.monitorWrappingScheme === "vertical" ? fovConversionFns.curved : fovConversionFns.flat; - var lookUpPixels = upConversionFns.angleToLength( - fovDetails.defaultDistanceVerticalRadians, - fovDetails.heightPixels, - fovDetails.completeScreenDistancePixels, - rotatedLookVector.z, - rotatedLookVector.x - ); - var westConversionFns = fovDetails.monitorWrappingScheme === "horizontal" ? fovConversionFns.curved : fovConversionFns.flat; - var lookWestPixels = westConversionFns.angleToLength( - fovDetails.defaultDistanceHorizontalRadians, - fovDetails.widthPixels, - fovDetails.completeScreenDistancePixels, - rotatedLookVector.y, - rotatedLookVector.x - ); - - // Check current focused monitor first - if (currentFocusedIndex !== -1) { - var focusedDistance = getMonitorDistance( - fovDetails, - lookUpPixels, - lookWestPixels, - monitorVectors[currentFocusedIndex], - monitorsDetails[currentFocusedIndex], - upConversionFns.angleToLength, - westConversionFns.angleToLength - ) * effect.focusedDisplayDistance / effect.allDisplaysDistance; - - if (smoothFollowEnabled || focusedDistance < unfocusThreshold) - return currentFocusedIndex; - } - - var closestIndex = -1; - var closestDistance = Number.POSITIVE_INFINITY; - - // Find the closest monitor - for (var i = 0; i < monitorVectors.length; ++i) { - if (i === currentFocusedIndex) - continue; - var distance = getMonitorDistance( - fovDetails, - lookUpPixels, - lookWestPixels, - monitorVectors[i], - monitorsDetails[i], - upConversionFns.angleToLength, - westConversionFns.angleToLength - ); - - if (distance < closestDistance) { - closestIndex = i; - closestDistance = distance; - } - } - - if (smoothFollowEnabled || closestDistance < focusThreshold) - return closestIndex; - - // Unfocus all displays - return -1; - } -} \ No newline at end of file diff --git a/kwin/src/qml/SingleDesktopView.qml b/kwin/src/qml/SingleDesktopView.qml deleted file mode 100644 index 88eeccd..0000000 --- a/kwin/src/qml/SingleDesktopView.qml +++ /dev/null @@ -1,53 +0,0 @@ -import QtQuick - -Item { - id: singleDesktopView - property point cursorPos: effect.cursorPos - property bool supportsXR: false - property bool showCalibratingBanner: false - - function cursorInBounds() { - const x = cursorPos.x - const y = cursorPos.y - const screenGeom = targetScreen.geometry - return x >= screenGeom.x && - x < screenGeom.x + screenGeom.width && - y >= screenGeom.y && - y < screenGeom.y + screenGeom.height - } - - DesktopView { - id: desktopViewComponent - screen: targetScreen - width: targetScreen.geometry.width - height: targetScreen.geometry.height - } - - Image { - id: cursorImg - x: 0 - y: 0 - z: 9999 // ensure on top - } - - Image { - source: effect.customBannerEnabled ? "custom_banner.png" : "calibrating.png" - visible: supportsXR && showCalibratingBanner - anchors.horizontalCenter: desktopViewComponent.horizontalCenter - anchors.bottom: desktopViewComponent.bottom - } - - onCursorPosChanged: { - if (singleDesktopView.cursorInBounds()) { - const newX = effect.cursorPos.x - targetScreen.geometry.x - const newY = effect.cursorPos.y - targetScreen.geometry.y - const newSrc = effect.cursorImageSource - if (cursorImg.x !== newX) cursorImg.x = newX - if (cursorImg.y !== newY) cursorImg.y = newY - if (cursorImg.source !== newSrc) cursorImg.source = newSrc - if (!cursorImg.visible) cursorImg.visible = true - } else if (cursorImg.visible) { - cursorImg.visible = false - } - } -} \ No newline at end of file diff --git a/kwin/src/qml/cursorOverlay.frag b/kwin/src/qml/cursorOverlay.frag deleted file mode 100644 index 4660e60..0000000 --- a/kwin/src/qml/cursorOverlay.frag +++ /dev/null @@ -1,18 +0,0 @@ -VARYING vec3 pos; -VARYING vec2 texcoord; - -void MAIN() { - vec2 tex = vec2(texcoord.x, 1.0 - texcoord.y); - vec4 color = texture(desktopTex, tex); - if (showCursor) { - vec2 fragCoord = tex * vec2(screenWidth, screenHeight); - vec2 cursorTopLeft = vec2(cursorX, cursorY); - vec2 cursorBottomRight = cursorTopLeft + vec2(cursorW, cursorH); - if (fragCoord.x >= cursorTopLeft.x && fragCoord.x < cursorBottomRight.x && fragCoord.y >= cursorTopLeft.y && fragCoord.y < cursorBottomRight.y) { - vec2 rel = (fragCoord - cursorTopLeft) / vec2(cursorW, cursorH); - vec4 cursorCol = texture(cursorTex, rel); - color = mix(color, cursorCol, cursorCol.a); - } - } - FRAGCOLOR = color; -} \ No newline at end of file diff --git a/kwin/src/qml/cursorOverlay.vert b/kwin/src/qml/cursorOverlay.vert deleted file mode 100644 index 4dc6bf3..0000000 --- a/kwin/src/qml/cursorOverlay.vert +++ /dev/null @@ -1,10 +0,0 @@ -VARYING vec3 pos; -VARYING vec2 texcoord; - -// this is a no-op vertex shader, CustomMaterial required one -void MAIN() -{ - pos = VERTEX; - texcoord = UV0; - POSITION = MODELVIEWPROJECTION_MATRIX * vec4(pos, 1.0); -} \ No newline at end of file diff --git a/kwin/src/qml/main.qml b/kwin/src/qml/main.qml deleted file mode 100644 index d99b850..0000000 --- a/kwin/src/qml/main.qml +++ /dev/null @@ -1,151 +0,0 @@ -import QtQuick -import QtQuick3D -import org.kde.kwin as KWinComponents -import org.kde.kwin.effect.breezy_desktop - -Item { - id: root - antialiasing: true - focus: false - - readonly property var supportedModels: [ - "VITURE", - "nreal air", - "Air", - "Air 2", - "Air 2 Pro", - "Air 2 Ultra", - "SmartGlasses", // TCL/RayNeo - "Rokid Max", - "Rokid Max 2", - "Rokid Air" - ] - required property QtObject effect - required property QtObject targetScreen - - property real viewportDiagonalFOVDegrees: effect.diagonalFOV - property var viewportResolution: effect.displayResolution - property var screens: KWinComponents.Workspace.screens - // .filter(function(screen) { - // return supportedModels.includes(screen.model); - // }) - - // x value for placing the viewport in the middle of all screens - property real screensXMid: { - let xMin = Number.MAX_VALUE; - let xMax = Number.MIN_VALUE; - - for (let i = 0; i < screens.length; i++) { - const geometry = screens[i].geometry; - xMin = Math.min(xMin, geometry.x); - xMax = Math.max(xMax, geometry.x + geometry.width); - } - - return (xMin + xMax) / 2 - (viewportResolution[0] / 2); - } - - // y value for placing the viewport in the middle of all screens - property real screensYMid: { - let yMin = Number.MAX_VALUE; - let yMax = Number.MIN_VALUE; - - for (let i = 0; i < screens.length; i++) { - const geometry = screens[i].geometry; - yMin = Math.min(yMin, geometry.y); - yMax = Math.max(yMax, geometry.y + geometry.height); - } - - return (yMin + yMax) / 2 - (viewportResolution[1] / 2); - } - - Displays { - id: displays - } - - property var fovDetails: displays.fovDetails( - screens, - viewportResolution[0], - viewportResolution[1], - viewportDiagonalFOVDegrees, - effect.lensDistanceRatio, - effect.allDisplaysDistance, - effect.displayWrappingScheme - ) - - property var monitorPlacements: { - const dx = effect.displayHorizontalOffset * viewportResolution[0]; - const dy = effect.displayVerticalOffset * viewportResolution[1]; - const adjustedGeometries = screens.map(screen => { - const g = screen.geometry; - return { - x: g.x - screensXMid + dx, - y: g.y - screensYMid + dy, - width: g.width, - height: g.height - }; - }); - return displays.monitorsToPlacements(fovDetails, adjustedGeometries, effect.displaySpacing); - } - - property bool targetScreenSupported: supportedModels.some(model => root.targetScreen.model.includes(model)) - property bool imuResetState: effect.imuResetState - property bool isEnabled: effect.isEnabled - - Component { - id: desktopViewComponent - SingleDesktopView { - supportsXR: targetScreenSupported - showCalibratingBanner: isEnabled && imuResetState - } - } - - Component { - id: view3DComponent - View3D { - anchors.fill: parent - environment: SceneEnvironment { - antialiasingMode: root.effect.antialiasingQuality === 0 ? SceneEnvironment.NoAA : SceneEnvironment.SSAA - antialiasingQuality: root.effect.antialiasingQuality === 0 ? SceneEnvironment.Medium : ( - root.effect.antialiasingQuality === 1 ? SceneEnvironment.Medium : ( - root.effect.antialiasingQuality === 2 ? SceneEnvironment.High : SceneEnvironment.VeryHigh)) - } - - PerspectiveCamera { - id: camera - } - - BreezyDesktop { - id: breezyDesktop - } - - CameraController { - id: cameraController - anchors.fill: parent - camera: camera - } - } - } - - Loader { - id: viewLoader - anchors.fill: parent - } - - function checkLoadedComponent() { - console.log(`Breezy - checking screen ${targetScreen.model}: ${targetScreenSupported} ${isEnabled} ${imuResetState}`); - const show3DView = targetScreenSupported && isEnabled && !imuResetState; - viewLoader.sourceComponent = show3DView ? view3DComponent : desktopViewComponent; - } - - onImuResetStateChanged: { - checkLoadedComponent(); - } - - onIsEnabledChanged: { - checkLoadedComponent(); - } - - Component.onCompleted: { - checkLoadedComponent(); - } -} diff --git a/kwin/src/xrdriveripc/CMakeLists.txt b/kwin/src/xrdriveripc/CMakeLists.txt deleted file mode 100644 index 81887b6..0000000 --- a/kwin/src/xrdriveripc/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -add_library(xr_driver_ipc STATIC - xrdriveripc.cpp -) - -# Ensure position independent code so the static archive can link into the KWin effect plugin (a shared module) -set_target_properties(xr_driver_ipc PROPERTIES POSITION_INDEPENDENT_CODE ON) - -# Generate an export header so symbols can be visible outside the shared lib -include(GenerateExportHeader) -generate_export_header(xr_driver_ipc EXPORT_FILE_NAME xr_driver_ipc_export.h) - -target_include_directories(xr_driver_ipc - PUBLIC - ${CMAKE_CURRENT_BINARY_DIR} # for generated export header -) - -target_compile_options(xr_driver_ipc PRIVATE - $<$:/EHsc> - $<$>:-fexceptions> -) - -target_link_libraries(xr_driver_ipc - PRIVATE Qt6::Core -) - -install(FILES xrdriveripc.py xrdriveripc_runner.py DESTINATION ${KDE_INSTALL_DATADIR}/kwin/effects/breezy_desktop) \ No newline at end of file diff --git a/kwin/src/xrdriveripc/xrdriveripc.cpp b/kwin/src/xrdriveripc/xrdriveripc.cpp deleted file mode 100644 index 4508f3d..0000000 --- a/kwin/src/xrdriveripc/xrdriveripc.cpp +++ /dev/null @@ -1,111 +0,0 @@ -// New implementation using QProcess to call python -#include "xrdriveripc.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -XRDriverIPC &XRDriverIPC::instance() { - static XRDriverIPC inst; - if (!inst.m_initialized) { - QString installedFile = QStandardPaths::locate( - QStandardPaths::GenericDataLocation, - QStringLiteral("kwin/effects/breezy_desktop/xrdriveripc.py"), - QStandardPaths::LocateFile); - if (installedFile.isEmpty()) { - throw std::runtime_error("Cannot locate kwin/effects/breezy_desktop/xrdriveripc.py"); - } - inst.m_pythonDir = QFileInfo(installedFile).path(); - inst.m_initialized = true; - } - return inst; -} - -std::string XRDriverIPC::configHome() const { - QString configHome = QString::fromUtf8(qgetenv("XDG_CONFIG_HOME")); - if (configHome.isEmpty()) { - QString homeDir = QString::fromUtf8(qgetenv("HOME")); - configHome = homeDir + QStringLiteral("/.config"); - } - return configHome.toStdString(); -} - -QByteArray XRDriverIPC::invokePython(const QString &method, - const QByteArray &payloadJson, - const QString &singleArg) const { - QProcess proc; - QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - env.insert(QStringLiteral("BREEZY_METHOD"), method); - env.insert(QStringLiteral("BREEZY_CONFIG_HOME"), QString::fromStdString(configHome())); - if (!singleArg.isEmpty()) env.insert(QStringLiteral("BREEZY_ARG"), singleArg); - if (!payloadJson.isEmpty()) env.insert(QStringLiteral("BREEZY_PAYLOAD"), QString::fromUtf8(payloadJson)); - proc.setProcessEnvironment(env); - // Expect xrdriveripc_runner.py to reside in the same directory as xrdriveripc.py (m_pythonDir) - QString wrapperPath = m_pythonDir + QStringLiteral("/xrdriveripc_runner.py"); - proc.start(QStringLiteral("python3"), QStringList() << wrapperPath); - if (!proc.waitForStarted(5000)) { - std::cerr << "Failed to start python process" << std::endl; - return {}; - } - proc.closeWriteChannel(); - if (!proc.waitForFinished(15000)) { - proc.kill(); - std::cerr << "Python process timeout" << std::endl; - return {}; - } - if (proc.exitStatus() != QProcess::NormalExit || proc.exitCode() != 0) { - std::cerr << "Python process failed (" << proc.exitCode() << "):\n" - << proc.readAllStandardError().toStdString() << std::endl; - return {}; - } - return proc.readAllStandardOutput().trimmed(); -} - -std::optional XRDriverIPC::retrieveConfig() { - QByteArray out = invokePython(QStringLiteral("retrieve_config"), {}, QStringLiteral("0")); - if (out.isEmpty()) return std::nullopt; - QJsonParseError err; auto doc = QJsonDocument::fromJson(out, &err); - if (err.error != QJsonParseError::NoError || !doc.isObject()) return std::nullopt; - return doc.object(); -} - -std::optional XRDriverIPC::retrieveDriverState() { - QByteArray out = invokePython(QStringLiteral("retrieve_driver_state"), {}, {}); - if (out.isEmpty()) return std::nullopt; - QJsonParseError err; auto doc = QJsonDocument::fromJson(out, &err); - if (err.error != QJsonParseError::NoError || !doc.isObject()) return std::nullopt; - return doc.object(); -} - -bool XRDriverIPC::writeConfig(const QJsonObject &configUpdate) { - QByteArray payload = QJsonDocument(configUpdate).toJson(QJsonDocument::Compact); - QByteArray out = invokePython(QStringLiteral("write_config"), payload, {}); - return !out.isEmpty(); -} - -bool XRDriverIPC::writeControlFlags(const std::map &flags) { - QJsonObject obj; for (const auto &kv : flags) obj.insert(QString::fromStdString(kv.first), kv.second); - QByteArray payload = QJsonDocument(obj).toJson(QJsonDocument::Compact); - QByteArray out = invokePython(QStringLiteral("write_control_flags"), payload, {}); - return !out.isEmpty(); -} - -bool XRDriverIPC::requestToken(const std::string &email) { - QByteArray out = invokePython(QStringLiteral("request_token"), {}, QString::fromStdString(email)); - if (out.isEmpty()) return false; - QString result = QString::fromUtf8(out).trimmed().toLower(); - return result == QStringLiteral("true"); -} - -bool XRDriverIPC::verifyToken(const std::string &token) { - QByteArray out = invokePython(QStringLiteral("verify_token"), {}, QString::fromStdString(token)); - if (out.isEmpty()) return false; - QString result = QString::fromUtf8(out).trimmed().toLower(); - return result == QStringLiteral("true"); -} diff --git a/kwin/src/xrdriveripc/xrdriveripc.h b/kwin/src/xrdriveripc/xrdriveripc.h deleted file mode 100644 index ee6ca81..0000000 --- a/kwin/src/xrdriveripc/xrdriveripc.h +++ /dev/null @@ -1,103 +0,0 @@ -// C++ bridge now invoking xrdriveripc via external python process -#pragma once - -#include -#include -#include -#include - -// Export header generated by CMake (GenerateExportHeader) -#ifdef __has_include -# if __has_include("xr_driver_ipc_export.h") -# include "xr_driver_ipc_export.h" -# endif -#endif - -#ifndef XR_DRIVER_IPC_EXPORT -# define XR_DRIVER_IPC_EXPORT __attribute__((visibility("default"))) -#endif - -namespace XRStateEntry { - inline constexpr const char *Heartbeat = "heartbeat"; - inline constexpr const char *HardwareId = "hardware_id"; - inline constexpr const char *ConnectedDeviceBrand = "connected_device_brand"; - inline constexpr const char *ConnectedDeviceModel = "connected_device_model"; - inline constexpr const char *MagnetSupported = "magnet_supported"; - inline constexpr const char *MagnetCalibrationType = "magnet_calibration_type"; - inline constexpr const char *UsingMagnet = "using_magnet"; - inline constexpr const char *MagnetStale = "magnet_stale"; - inline constexpr const char *MagnetCalibrating = "magnet_calibrating"; - inline constexpr const char *GyroCalibrating = "gyro_calibrating"; - inline constexpr const char *AccelCalibrating = "accel_calibrating"; - inline constexpr const char *SbsModeEnabled = "sbs_mode_enabled"; - inline constexpr const char *SbsModeSupported = "sbs_mode_supported"; - inline constexpr const char *FirmwareUpdateRecommended = "firmware_update_recommended"; - inline constexpr const char *BreezyDesktopSmoothFollowEnabled = "breezy_desktop_smooth_follow_enabled"; - inline constexpr const char *IsGamescopeReshadeIPCConnected = "is_gamescope_reshade_ipc_connected"; - inline constexpr const char *UiView = "ui_view_enabled"; - - namespace UIView { - inline constexpr const char *DriverRunning = "driver_running"; - - namespace License { - inline constexpr const char *Tiers = "tiers"; - inline constexpr const char *Features = "features"; - inline constexpr const char *HardwareId = "hardware_id"; - inline constexpr const char *ConfirmedToken = "confirmed_token"; - inline constexpr const char *ActionNeeded = "action_needed"; - inline constexpr const char *EnabledFeatures = "enabled_features"; - } - } -} - -namespace XRConfigEntry { - inline constexpr const char *Disabled = "disabled"; - inline constexpr const char *GamescopeReshadeWaylandDisabled = "gamescope_reshade_wayland_disabled"; - inline constexpr const char *OutputMode = "output_mode"; - inline constexpr const char *ExternalMode = "external_mode"; - inline constexpr const char *MouseSensitivity = "mouse_sensitivity"; - inline constexpr const char *DisplayZoom = "display_zoom"; - inline constexpr const char *LookAhead = "look_ahead"; - inline constexpr const char *SbsDisplaySize = "sbs_display_size"; - inline constexpr const char *SbsDisplayDistance = "sbs_display_distance"; - inline constexpr const char *SbsContent = "sbs_content"; - inline constexpr const char *SbsModeStretched = "sbs_mode_stretched"; - inline constexpr const char *SideviewPosition = "sideview_position"; - inline constexpr const char *SideviewDisplaySize = "sideview_display_size"; - inline constexpr const char *VirtualDisplaySmoothFollowEnabled = "virtual_display_smooth_follow_enabled"; - inline constexpr const char *SideviewSmoothFollowEnabled = "sideview_smooth_follow_enabled"; - inline constexpr const char *SideviewFollowThreshold = "sideview_follow_threshold"; - inline constexpr const char *CurvedDisplay = "curved_display"; - inline constexpr const char *MultiTapEnabled = "multi_tap_enabled"; - inline constexpr const char *SmoothFollowTrackRoll = "smooth_follow_track_roll"; - inline constexpr const char *SmoothFollowTrackPitch = "smooth_follow_track_pitch"; - inline constexpr const char *SmoothFollowTrackYaw = "smooth_follow_track_yaw"; - inline constexpr const char *Debug = "debug"; -} - -class XR_DRIVER_IPC_EXPORT XRDriverIPC { -public: - static XRDriverIPC &instance(); - - std::optional retrieveConfig(); - std::optional retrieveDriverState(); - bool writeConfig(const QJsonObject &configUpdate); - bool writeControlFlags(const std::map &flags); - bool requestToken(const std::string &email); - bool verifyToken(const std::string &token); - - -private: - XRDriverIPC() = default; - ~XRDriverIPC() = default; - XRDriverIPC(const XRDriverIPC&) = delete; - XRDriverIPC& operator=(const XRDriverIPC&) = delete; - - std::string configHome() const; - QByteArray invokePython(const QString &method, - const QByteArray &payloadJson, - const QString &singleArg) const; - - bool m_initialized = false; - QString m_pythonDir; // directory containing xrdriveripc.py -}; diff --git a/kwin/src/xrdriveripc/xrdriveripc_runner.py b/kwin/src/xrdriveripc/xrdriveripc_runner.py deleted file mode 100644 index 3a47250..0000000 --- a/kwin/src/xrdriveripc/xrdriveripc_runner.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -"""Wrapper script invoked by xrdriveripc.cpp via QProcess. - -It reads environment variables to determine which XRDriverIPC method to call -and prints the JSON-serialized result to stdout, mirroring the prior inline -python one-liner implementation. -""" - -from __future__ import annotations - -import json -import os -import sys -import traceback - -class Logger: - def info(self, *args, **kwargs): - pass - - def error(self, *args, **kwargs): - pass - - -def main() -> int: - # Ensure the current directory (where xrdriveripc.py lives) is in sys.path - script_dir = os.path.dirname(os.path.abspath(__file__)) - if script_dir not in sys.path: - sys.path.insert(0, script_dir) - - try: - import xrdriveripc # type: ignore - except Exception as e: # pragma: no cover - import failure path - print("Failed to import xrdriveripc: %s" % e, file=sys.stderr) - return 2 - - method = os.environ.get("BREEZY_METHOD") - if not method: - print("BREEZY_METHOD not set", file=sys.stderr) - return 2 - - config_home = os.environ.get("BREEZY_CONFIG_HOME") - inst = xrdriveripc.XRDriverIPC(logger=Logger(), config_home=config_home) - - arg = os.environ.get("BREEZY_ARG") - payload_raw = os.environ.get("BREEZY_PAYLOAD") - - # Dispatch replicating previous inline logic - try: - if method == "retrieve_config": - res = getattr(inst, method)(int(arg) if arg else 1) - elif method in ("write_config", "write_control_flags") and payload_raw: - res = getattr(inst, method)(json.loads(payload_raw)) - elif method in ("request_token", "verify_token") and arg: - res = getattr(inst, method)(arg) - else: - res = getattr(inst, method)() - except Exception: # pragma: no cover - runtime failure path - traceback.print_exc() - return 3 - - try: - print(json.dumps(res)) - except Exception: # pragma: no cover - traceback.print_exc() - return 3 - return 0 - - -if __name__ == "__main__": # pragma: no cover - sys.exit(main())