Remove kwin directory from gnome backport
This commit is contained in:
parent
2f2549fa7e
commit
719a65aad3
|
|
@ -6,8 +6,4 @@ out/
|
||||||
*.po~
|
*.po~
|
||||||
gnome-44-max/
|
gnome-44-max/
|
||||||
gnome-45/
|
gnome-45/
|
||||||
kwin/src/xrdriveripc/xrdriveripc.py
|
kwin/
|
||||||
kwin/VERSION
|
|
||||||
kwin/build-test/
|
|
||||||
kwin/src/qml/calibrating.png
|
|
||||||
kwin/src/qml/custom_banner.png
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
104
kwin/bin/setup
104
kwin/bin/setup
|
|
@ -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" <<EOF
|
|
||||||
|
|
||||||
# Added by Breezy Desktop installer: QT plugin path setup
|
|
||||||
$QT_PLUGIN_EXPORT
|
|
||||||
export QT_DEBUG_PLUGINS=1
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
PLASMA_ENV_SCRIPT="$HOME/.config/plasma-workspace/env/breezy_desktop.sh"
|
|
||||||
if [[ ! -f "$PLASMA_ENV_SCRIPT" ]]; then
|
|
||||||
echo "Adding QT_PLUGIN_PATH to $PLASMA_ENV_SCRIPT"
|
|
||||||
mkdir -p "$(dirname "$PLASMA_ENV_SCRIPT")"
|
|
||||||
cat >> "$PLASMA_ENV_SCRIPT" <<EOF
|
|
||||||
|
|
||||||
# Added by Breezy Desktop installer: QT plugin path setup
|
|
||||||
$QT_PLUGIN_EXPORT
|
|
||||||
export QT_DEBUG_PLUGINS=1
|
|
||||||
EOF
|
|
||||||
fi
|
|
||||||
|
|
||||||
# set up the XR driver using the local binary
|
|
||||||
echo "Installing xrDriver (requires sudo)"
|
|
||||||
echo "BEGIN - xr_driver_setup"
|
|
||||||
if [ -z "$1" ]
|
|
||||||
then
|
|
||||||
sudo bin/xr_driver_setup $(pwd)/xrDriver.tar.gz
|
|
||||||
else
|
|
||||||
sudo bin/xr_driver_setup -v $1 $(pwd)/xrDriver.tar.gz
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "END - xr_driver_setup"
|
|
||||||
|
|
||||||
printf "\n\033[1;33m!!! IMPORTANT !!!\033[0m You must log out and back in, then enable Breezy Desktop from the Desktop Effects in System Settings\n\n"
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
||||||
|
|
||||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
|
||||||
set(CMAKE_INSTALL_PREFIX "/usr" CACHE PATH "Force path to set CMAKE_INSTALL_PREFIX" FORCE)
|
|
||||||
endif(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
|
||||||
if(NOT CMAKE_BUILD_TYPE)
|
|
||||||
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE)
|
|
||||||
endif()
|
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DQT_NO_DEBUG_OUTPUT")
|
|
||||||
|
|
||||||
set(QT_MIN_VERSION "6.4.0")
|
|
||||||
set(QT_MAJOR_VERSION 6)
|
|
||||||
set(KF_MIN_VERSION 6)
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
if(${KF_MIN_VERSION} EQUAL 6)
|
|
||||||
set(KWIN_EFFECT_INCLUDE_FILE "/usr/include/kwin/effect/effect.h")
|
|
||||||
else ()
|
|
||||||
set(KWIN_EFFECT_INCLUDE_FILE "/usr/include/kwineffects.h")
|
|
||||||
endif ()
|
|
||||||
execute_process(
|
|
||||||
COMMAND sh -c "grep '#define KWIN_EFFECT_API_VERSION_MINOR' ${KWIN_EFFECT_INCLUDE_FILE} | awk '{print \$NF}'"
|
|
||||||
OUTPUT_VARIABLE KWIN_EFFECT_API_VERSION_MINOR OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
||||||
)
|
|
||||||
message(STATUS "Found KWinEffect API Version: ${KWIN_EFFECT_API_VERSION_MINOR}")
|
|
||||||
|
|
||||||
#below is a very useful way of finding variables and contains:
|
|
||||||
|
|
||||||
#get_cmake_property(_variableNames VARIABLES)
|
|
||||||
#list (SORT _variableNames)
|
|
||||||
#foreach (_variableName ${_variableNames})
|
|
||||||
# string(TOLOWER "${_variableName}" KEY)
|
|
||||||
# string(TOLOWER "${${_variableName}}" VALUE)
|
|
||||||
# string(FIND "${KEY}" "kwin" INDEX1)
|
|
||||||
# string(FIND "${VALUE}" "kwin" INDEX2)
|
|
||||||
# if (${INDEX1} GREATER -1 OR ${INDEX2} GREATER -1)
|
|
||||||
# message(STATUS "VARIABLE ${_variableName}=${${_variableName}}")
|
|
||||||
# endif ()
|
|
||||||
#endforeach()
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# these are cache variables, so they could be overwritten with -D,
|
|
||||||
set(CPACK_PACKAGE_NAME ${CMAKE_PROJECT_NAME} CACHE STRING ${CMAKE_PROJECT_NAME})
|
|
||||||
set(CPACK_PACKAGING_INSTALL_PREFIX "/usr")
|
|
||||||
set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}")
|
|
||||||
set(CPACK_PACKAGE_VERSION "${CMAKE_PROJECT_VERSION}")
|
|
||||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Breezy Desktop - KWin Plugin")
|
|
||||||
set(CPACK_PACKAGE_CONTACT "wayne@xronlinux.com")
|
|
||||||
|
|
||||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Wayne Heaney")
|
|
||||||
set(CPACK_DEBIAN_PACKAGE_SECTION "kde")
|
|
||||||
|
|
||||||
# autogenerate dependency information
|
|
||||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
|
||||||
set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS ON)
|
|
||||||
set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS_POLICY "=")
|
|
||||||
|
|
||||||
include(CPack)
|
|
||||||
# To generate deb files, install 'dpkg-dev' package and then run 'cpack -G DEB'
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
find_package(KF6 QUIET COMPONENTS ConfigWidgets)
|
|
||||||
|
|
||||||
if(${KF6_FOUND} EQUAL 0)
|
|
||||||
set(QT_MIN_VERSION "5.15")
|
|
||||||
set(QT_MAJOR_VERSION 5)
|
|
||||||
set(KF_MIN_VERSION "5.78")
|
|
||||||
endif ()
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
add_test (NAME KWinEffectSupport COMMAND sh ${CMAKE_CURRENT_SOURCE_DIR}/tools/isSupported.sh)
|
|
||||||
set_property (TEST KWinEffectSupport PROPERTY PASS_REGULAR_EXPRESSION "true")
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
# To run the build from the package root:
|
|
||||||
# docker buildx build --platform linux/amd64,linux/arm64 -f ./docker-build/Dockerfile -t "breezy-kwin" .
|
|
||||||
# docker run --rm -t -v ./:/source -v --platform linux/amd64 "breezy-kwin:amd64"
|
|
||||||
# docker run --rm -t -v ./:/source -v --platform linux/arm64 "breezy-kwin:arm64"
|
|
||||||
|
|
||||||
FROM --platform=$TARGETPLATFORM archlinux:base-20250817.0.405639@sha256:31f0749bdb81517dc8f379feac0a3860b097f1da1f53c8315c1bae0817d6c0a1
|
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
|
||||||
RUN echo "Target platform: $TARGETPLATFORM"
|
|
||||||
|
|
||||||
RUN pacman -Sy --noconfirm --needed \
|
|
||||||
ca-certificates \
|
|
||||||
base-devel \
|
|
||||||
cmake \
|
|
||||||
pkgconf \
|
|
||||||
git \
|
|
||||||
curl \
|
|
||||||
wget \
|
|
||||||
extra-cmake-modules \
|
|
||||||
qt6-base \
|
|
||||||
qt6-declarative \
|
|
||||||
qt6-tools \
|
|
||||||
kconfig \
|
|
||||||
kconfigwidgets \
|
|
||||||
kcoreaddons \
|
|
||||||
kglobalaccel \
|
|
||||||
ki18n \
|
|
||||||
kcmutils \
|
|
||||||
kxmlgui \
|
|
||||||
kwindowsystem \
|
|
||||||
kwin \
|
|
||||||
&& pacman -Scc --noconfirm
|
|
||||||
|
|
||||||
WORKDIR /source
|
|
||||||
|
|
||||||
CMD bin/package_kwin_plugin
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
# To run the build from the package root:
|
|
||||||
# docker buildx build --platform linux/amd64,linux/arm64 -f ./docker-build/Dockerfile.steamos -t "breezy-kwin-steamos" .
|
|
||||||
# docker run --rm -t -v ./:/source -v --platform linux/amd64 "breezy-kwin-steamos:amd64"
|
|
||||||
# docker run --rm -t -v ./:/source -v --platform linux/arm64 "breezy-kwin-steamos:arm64"
|
|
||||||
|
|
||||||
FROM --platform=$TARGETPLATFORM ghcr.io/steamdeckhomebrew/holo-base:3.7@sha256:8da120a3e89c750abd0090c0aab86d543a55d667c3002c8d64960f7fd82ccdd6
|
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
|
||||||
ENV STEAMOS=1
|
|
||||||
RUN echo "SteamOS build - target platform: $TARGETPLATFORM"
|
|
||||||
|
|
||||||
RUN pacman -Sy --noconfirm --needed \
|
|
||||||
ca-certificates \
|
|
||||||
base-devel \
|
|
||||||
cmake \
|
|
||||||
pkgconf \
|
|
||||||
git \
|
|
||||||
curl \
|
|
||||||
wget \
|
|
||||||
extra-cmake-modules \
|
|
||||||
qt6-base \
|
|
||||||
qt6-declarative \
|
|
||||||
qt6-tools \
|
|
||||||
kconfig \
|
|
||||||
kconfigwidgets \
|
|
||||||
kcoreaddons \
|
|
||||||
kglobalaccel \
|
|
||||||
ki18n \
|
|
||||||
kcmutils \
|
|
||||||
kxmlgui \
|
|
||||||
kwindowsystem \
|
|
||||||
kwin \
|
|
||||||
&& pacman -Scc --noconfirm
|
|
||||||
|
|
||||||
WORKDIR /source
|
|
||||||
|
|
||||||
CMD bin/package_kwin_plugin
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# might be needed on a fresh docker setup:
|
|
||||||
# install qemu and qemu-user-static packages
|
|
||||||
# sudo docker context rm default
|
|
||||||
# docker run --privileged --rm tonistiigi/binfmt --install all
|
|
||||||
# sudo docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
|
||||||
# ls -l /proc/sys/fs/binfmt_misc/ # should contain qemu-<arch> 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 .
|
|
||||||
|
|
@ -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/
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
|
|
||||||
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
|
|
||||||
<kcfgfile name="kwinrc"/>
|
|
||||||
<group name="Effect-breezy_desktop">
|
|
||||||
<entry name="FocusedDisplayDistance" type="Int">
|
|
||||||
<default>85</default>
|
|
||||||
<min>25</min>
|
|
||||||
<max>225</max>
|
|
||||||
<label>Focused Display Distance</label>
|
|
||||||
</entry>
|
|
||||||
<entry name="AllDisplaysDistance" type="Int">
|
|
||||||
<default>105</default>
|
|
||||||
<min>25</min>
|
|
||||||
<max>225</max>
|
|
||||||
<label>All Displays Distance</label>
|
|
||||||
</entry>
|
|
||||||
<entry name="ZoomOnFocusEnabled" type="Bool">
|
|
||||||
<default>false</default>
|
|
||||||
<label>Zoom on Focus Enabled</label>
|
|
||||||
<description>Enable zooming in on the focused display.</description>
|
|
||||||
</entry>
|
|
||||||
<entry name="DisplaySpacing" type="Int">
|
|
||||||
<default>0</default>
|
|
||||||
<min>0</min>
|
|
||||||
<max>100</max>
|
|
||||||
<label>Display Spacing</label>
|
|
||||||
<description>How far apart the displays are visually (not logically)</description>
|
|
||||||
</entry>
|
|
||||||
<entry name="DisplayHorizontalOffset" type="Int">
|
|
||||||
<default>0</default>
|
|
||||||
<min>-250</min>
|
|
||||||
<max>250</max>
|
|
||||||
<label>Display Horizontal Offset</label>
|
|
||||||
<description>Horizontal offset as a percent of the viewport width (-2.50 to 2.50)</description>
|
|
||||||
</entry>
|
|
||||||
<entry name="DisplayVerticalOffset" type="Int">
|
|
||||||
<default>0</default>
|
|
||||||
<min>-250</min>
|
|
||||||
<max>250</max>
|
|
||||||
<label>Display Vertical Offset</label>
|
|
||||||
<description>Vertical offset as a percent of the viewport height (-2.50 to 2.50)</description>
|
|
||||||
</entry>
|
|
||||||
<entry name="DisplayWrappingScheme" type="Int">
|
|
||||||
<default>0</default>
|
|
||||||
<min>0</min>
|
|
||||||
<max>3</max>
|
|
||||||
<label>Display Wrapping Scheme</label>
|
|
||||||
<description>How to arrange monitors: 0=Auto, 1=Horizontal, 2=Vertical, 3=Flat</description>
|
|
||||||
</entry>
|
|
||||||
<entry name="AntialiasingQuality" type="Int">
|
|
||||||
<default>3</default>
|
|
||||||
<min>0</min>
|
|
||||||
<max>3</max>
|
|
||||||
<label>Antialiasing Quality</label>
|
|
||||||
<description>0=None, 1=Medium, 2=High, 3=Very High</description>
|
|
||||||
</entry>
|
|
||||||
<entry name="RemoveVirtualDisplaysOnDisable" type="Bool">
|
|
||||||
<default>true</default>
|
|
||||||
<label>Remove virtual displays on disable</label>
|
|
||||||
<description>Whether to remove any virtual displays when the effect is disabled</description>
|
|
||||||
</entry>
|
|
||||||
</group>
|
|
||||||
</kcfg>
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
File=breezydesktopconfig.kcfg
|
|
||||||
ClassName=BreezyDesktopConfig
|
|
||||||
Singleton=true
|
|
||||||
Mutators=true
|
|
||||||
Notifiers=true
|
|
||||||
|
|
@ -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 <kwin/main.h>
|
|
||||||
#include <core/outputbackend.h>
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <QAction>
|
|
||||||
#include <QBuffer>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QFileSystemWatcher>
|
|
||||||
#include <QLoggingCategory>
|
|
||||||
#include <QQuickItem>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QDBusConnection>
|
|
||||||
|
|
||||||
#include <KGlobalAccel>
|
|
||||||
#include <KLocalizedString>
|
|
||||||
|
|
||||||
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<BreezyDesktopEffect>("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<void()> 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<int>(m_focusedDisplayDistance * 100.0f));
|
|
||||||
}
|
|
||||||
BreezyDesktopConfig::setZoomOnFocusEnabled(enabled);
|
|
||||||
BreezyDesktopConfig::self()->save();
|
|
||||||
Q_EMIT zoomOnFocusChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BreezyDesktopEffect::imuResetState() const {
|
|
||||||
return m_imuResetState;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QQuaternion> BreezyDesktopEffect::imuRotations() const {
|
|
||||||
return m_imuRotations;
|
|
||||||
}
|
|
||||||
|
|
||||||
quint32 BreezyDesktopEffect::imuTimeElapsedMs() const {
|
|
||||||
return m_imuTimeElapsedMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
quint64 BreezyDesktopEffect::imuTimestamp() const {
|
|
||||||
return m_imuTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<qreal> BreezyDesktopEffect::lookAheadConfig() const {
|
|
||||||
return m_lookAheadConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<quint32> 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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(data[DataView::VERSION[DataView::OFFSET_INDEX]]);
|
|
||||||
uint8_t enabledFlag = static_cast<uint8_t>(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<quint32>(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"
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "kcm/shortcuts.h"
|
|
||||||
#include <effect/quickeffect.h>
|
|
||||||
|
|
||||||
#include <QAction>
|
|
||||||
#include <QFileSystemWatcher>
|
|
||||||
#include <QImage>
|
|
||||||
#include <QKeySequence>
|
|
||||||
#include <QQuaternion>
|
|
||||||
|
|
||||||
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<QQuaternion> 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<qreal> lookAheadConfig READ lookAheadConfig NOTIFY devicePropertiesChanged)
|
|
||||||
Q_PROPERTY(QList<quint32> 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<QQuaternion> imuRotations() const;
|
|
||||||
quint32 imuTimeElapsedMs() const;
|
|
||||||
quint64 imuTimestamp() const;
|
|
||||||
bool imuResetState() const;
|
|
||||||
QList<qreal> lookAheadConfig() const;
|
|
||||||
QList<quint32> 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<void()> triggeredFunc);
|
|
||||||
void recenter();
|
|
||||||
|
|
||||||
QString m_cursorImageSource;
|
|
||||||
QSize m_cursorImageSize;
|
|
||||||
|
|
||||||
bool m_enabled = false;
|
|
||||||
bool m_zoomOnFocusEnabled = false;
|
|
||||||
bool m_imuResetState;
|
|
||||||
QList<QQuaternion> m_imuRotations;
|
|
||||||
quint32 m_imuTimeElapsedMs;
|
|
||||||
quint64 m_imuTimestamp = 0;
|
|
||||||
QList<qreal> m_lookAheadConfig;
|
|
||||||
QList<quint32> 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<Output *> m_virtualOutputs;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace KWin
|
|
||||||
|
|
@ -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()
|
|
||||||
|
|
@ -1,457 +0,0 @@
|
||||||
#include "shortcuts.h"
|
|
||||||
#include "breezydesktopeffectkcm.h"
|
|
||||||
#include "breezydesktopconfig.h"
|
|
||||||
#include "labeledslider.h"
|
|
||||||
#include "xrdriveripc.h"
|
|
||||||
|
|
||||||
#include <kwineffects_interface.h>
|
|
||||||
|
|
||||||
#include <KActionCollection>
|
|
||||||
#include <KGlobalAccel>
|
|
||||||
#include <KLocalizedString>
|
|
||||||
#include <KConfigWatcher>
|
|
||||||
#include <KSharedConfig>
|
|
||||||
#include <KPluginFactory>
|
|
||||||
|
|
||||||
#include <QAction>
|
|
||||||
#include <QGuiApplication>
|
|
||||||
#include <QKeyEvent>
|
|
||||||
#include <QLineEdit>
|
|
||||||
#include <QLabel>
|
|
||||||
#include <QJsonValue>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QDesktopServices>
|
|
||||||
#include <QUrl>
|
|
||||||
#include <QProcess>
|
|
||||||
#include <QComboBox>
|
|
||||||
#include <QDBusInterface>
|
|
||||||
#include <QDBusConnection>
|
|
||||||
|
|
||||||
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<QLabel*>(QStringLiteral("labelVirtualDisplays"))) {
|
|
||||||
lbl->setVisible(true);
|
|
||||||
lbl->setEnabled(true);
|
|
||||||
}
|
|
||||||
if (auto row = widget()->findChild<QWidget*>(QStringLiteral("widgetVirtualDisplayButtons"))) {
|
|
||||||
row->setVisible(true);
|
|
||||||
row->setEnabled(true);
|
|
||||||
}
|
|
||||||
if (auto chk = widget()->findChild<QWidget*>(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<int>(&QComboBox::currentIndexChanged), this, &BreezyDesktopEffectConfig::save);
|
|
||||||
connect(ui.kcfg_AntialiasingQuality, qOverload<int>(&QComboBox::currentIndexChanged), this, &BreezyDesktopEffectConfig::save);
|
|
||||||
connect(ui.kcfg_RemoveVirtualDisplaysOnDisable, &QCheckBox::toggled, this, &BreezyDesktopEffectConfig::save);
|
|
||||||
|
|
||||||
if (auto label = widget()->findChild<QLabel*>("labelAppNameVersion")) {
|
|
||||||
label->setText(QStringLiteral("Breezy Desktop - v%1").arg(QLatin1String(BREEZY_DESKTOP_VERSION_STR)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto btnEmail = widget()->findChild<QPushButton*>("buttonSubmitEmail")) {
|
|
||||||
connect(btnEmail, &QPushButton::clicked, this, [this]() {
|
|
||||||
auto edit = widget()->findChild<QLineEdit*>("lineEditLicenseEmail");
|
|
||||||
auto labelStatus = widget()->findChild<QLabel*>("labelEmailStatus");
|
|
||||||
if (!edit || edit->text().trimmed().isEmpty() || !labelStatus) return;
|
|
||||||
setRequestInProgress({edit, sender()}, true);
|
|
||||||
labelStatus->setVisible(false);
|
|
||||||
bool success = XRDriverIPC::instance().requestToken(edit->text().trimmed().toStdString());
|
|
||||||
showStatus(labelStatus, success, success ? tr("Request sent. Check your email for instructions.") : tr("Failed to send request."));
|
|
||||||
setRequestInProgress({edit, sender()}, false);
|
|
||||||
});
|
|
||||||
if (auto emailEdit = widget()->findChild<QLineEdit*>("lineEditLicenseEmail")) {
|
|
||||||
emailEdit->installEventFilter(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (auto btnToken = widget()->findChild<QPushButton*>("buttonSubmitToken")) {
|
|
||||||
connect(btnToken, &QPushButton::clicked, this, [this]() {
|
|
||||||
auto edit = widget()->findChild<QLineEdit*>("lineEditLicenseToken");
|
|
||||||
auto labelStatus = widget()->findChild<QLabel*>("labelTokenStatus");
|
|
||||||
if (!edit || edit->text().trimmed().isEmpty() || !labelStatus) return;
|
|
||||||
setRequestInProgress({edit, sender()}, true);
|
|
||||||
labelStatus->setVisible(false);
|
|
||||||
bool success = XRDriverIPC::instance().verifyToken(edit->text().trimmed().toStdString());
|
|
||||||
if (success) {
|
|
||||||
XRDriverIPC::instance().writeControlFlags({{"refresh_device_license", true}});
|
|
||||||
}
|
|
||||||
showStatus(labelStatus, success, success ? tr("Your license has been refreshed.") : tr("Invalid or expired token."));
|
|
||||||
setRequestInProgress({edit, sender()}, false);
|
|
||||||
});
|
|
||||||
if (auto tokenEdit = widget()->findChild<QLineEdit*>("lineEditLicenseToken")) {
|
|
||||||
tokenEdit->installEventFilter(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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<QPushButton*>("buttonAdd1080p")) {
|
|
||||||
connect(btn1080p, &QPushButton::clicked, this, [callAddVirtualDisplay]() {
|
|
||||||
callAddVirtualDisplay(1920, 1080);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (auto btn1440p = widget()->findChild<QPushButton*>("buttonAdd1440p")) {
|
|
||||||
connect(btn1440p, &QPushButton::clicked, this, [callAddVirtualDisplay]() {
|
|
||||||
callAddVirtualDisplay(2560, 1440);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// General tab: Open KDE Displays Settings
|
|
||||||
if (auto btnDisplays = widget()->findChild<QPushButton*>(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<QObject*> widgets, bool inProgress) {
|
|
||||||
for (auto *obj : widgets) {
|
|
||||||
if (auto *w = qobject_cast<QWidget*>(obj)) {
|
|
||||||
w->setEnabled(!inProgress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BreezyDesktopEffectConfig::eventFilter(QObject *watched, QEvent *event) {
|
|
||||||
if (event->type() == QEvent::KeyPress) {
|
|
||||||
auto *ke = static_cast<QKeyEvent*>(event);
|
|
||||||
if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
|
|
||||||
if (auto *edit = qobject_cast<QLineEdit*>(watched)) {
|
|
||||||
// Determine which button to invoke
|
|
||||||
QString objName = edit->objectName();
|
|
||||||
QString buttonName;
|
|
||||||
if (objName == QLatin1String("lineEditLicenseEmail")) buttonName = QStringLiteral("buttonSubmitEmail");
|
|
||||||
else if (objName == QLatin1String("lineEditLicenseToken")) buttonName = QStringLiteral("buttonSubmitToken");
|
|
||||||
if (!buttonName.isEmpty()) {
|
|
||||||
if (auto btn = widget()->findChild<QPushButton*>(buttonName)) {
|
|
||||||
// Trigger click but stop further propagation so dialog doesn't accept/close
|
|
||||||
QMetaObject::invokeMethod(btn, "click", Qt::QueuedConnection);
|
|
||||||
event->accept();
|
|
||||||
return true; // eat event
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return KCModule::eventFilter(watched, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
static QString secondsToRemainingString(qint64 secs) {
|
|
||||||
if (secs <= 0) return {};
|
|
||||||
|
|
||||||
if (secs / 60 < 60) {
|
|
||||||
return QObject::tr("less than an hour");
|
|
||||||
}
|
|
||||||
if (secs / 3600 < 24) {
|
|
||||||
qint64 hours = secs / 3600;
|
|
||||||
if (hours == 1) return QObject::tr("1 hour");
|
|
||||||
return QObject::tr("%1 hours").arg(hours);
|
|
||||||
}
|
|
||||||
if ((secs / 86400) < 30 ) {
|
|
||||||
qint64 days = secs / 86400;
|
|
||||||
if (days == 1) return QObject::tr("1 day");
|
|
||||||
return QObject::tr("%1 days").arg(days);
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
void BreezyDesktopEffectConfig::refreshLicenseUi(const QJsonObject &rootObj) {
|
|
||||||
auto tab = widget()->findChild<QWidget*>("tabLicenseDetails");
|
|
||||||
if (!tab) return;
|
|
||||||
auto labelSummary = tab->findChild<QLabel*>("labelLicenseSummary");
|
|
||||||
if (!labelSummary) return;
|
|
||||||
auto donate = tab->findChild<QLabel*>("labelDonateLink");
|
|
||||||
auto globalWarn = widget()->findChild<QLabel*>("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<qint64>(secsVal.toDouble());
|
|
||||||
QString remaining = secondsToRemainingString(secs);
|
|
||||||
if (!remaining.isEmpty()) {
|
|
||||||
timeDescriptor = tr("%1 remaining").arg(remaining);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
renewalDescriptor = tr(" (%1)").arg(periodDescriptor);
|
|
||||||
warningState = !timeDescriptor.isEmpty();
|
|
||||||
if (warningState) {
|
|
||||||
auto fundsNeeded = prodTierObj.value(QStringLiteral("funds_needed_by_period")).toObject().value(activePeriod).toDouble();
|
|
||||||
if (fundsNeeded > 0.0) {
|
|
||||||
QString fundsNeededDescriptor = tr("$%1 USD to renew").arg(fundsNeeded);
|
|
||||||
renewalDescriptor = tr(" (%1, %2, %3)").arg(periodDescriptor, fundsNeededDescriptor, timeDescriptor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
QJsonValue isEnabled = prodFeatureObj.value(QStringLiteral("is_enabled"));
|
|
||||||
QJsonValue isTrial = prodFeatureObj.value(QStringLiteral("is_trial"));
|
|
||||||
if (isEnabled.toBool()) {
|
|
||||||
if (isTrial.toBool()) {
|
|
||||||
status = tr("in trial");
|
|
||||||
auto secsVal = prodFeatureObj.value(QStringLiteral("funds_needed_in_seconds"));
|
|
||||||
if (secsVal.isDouble()) {
|
|
||||||
qint64 secs = static_cast<qint64>(secsVal.toDouble());
|
|
||||||
QString remaining = secondsToRemainingString(secs);
|
|
||||||
warningState = !remaining.isEmpty();
|
|
||||||
if (warningState) {
|
|
||||||
QString timeDescriptor = tr("%1 remaining").arg(remaining);
|
|
||||||
renewalDescriptor = tr(" (%1)").arg(timeDescriptor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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"
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <KCModule>
|
|
||||||
#include <KConfigWatcher>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#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<QObject*> 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;
|
|
||||||
};
|
|
||||||
|
|
@ -1,529 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>BreezyDesktopEffectConfig</class>
|
|
||||||
<widget class="QWidget" name="BreezyDesktopEffectConfig">
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="labelDeviceConnectionStatus">
|
|
||||||
<property name="text">
|
|
||||||
<string></string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignHCenter|Qt::AlignVCenter</set>
|
|
||||||
</property>
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<pointsize>14</pointsize>
|
|
||||||
<weight>75</weight>
|
|
||||||
<bold>true</bold>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="labelGlobalLicenseWarning">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="visible">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignHCenter|Qt::AlignVCenter</set>
|
|
||||||
</property>
|
|
||||||
<property name="styleSheet">
|
|
||||||
<string notr="true">color: rgb(200,0,0); font-weight: bold;</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QTabWidget" name="tabWidget">
|
|
||||||
<property name="tabPosition">
|
|
||||||
<enum>QTabWidget::North</enum>
|
|
||||||
</property>
|
|
||||||
<property name="tabShape">
|
|
||||||
<enum>QTabWidget::Rounded</enum>
|
|
||||||
</property>
|
|
||||||
<widget class="QWidget" name="tabGeneral">
|
|
||||||
<attribute name="title">
|
|
||||||
<string>&General</string>
|
|
||||||
</attribute>
|
|
||||||
<layout class="QFormLayout" name="formLayout">
|
|
||||||
<item row="0" column="0" colspan="2">
|
|
||||||
<widget class="QCheckBox" name="kcfg_EffectEnabled">
|
|
||||||
<property name="text">
|
|
||||||
<string>XR Effect enabled</string>
|
|
||||||
</property>
|
|
||||||
<property name="checked">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0" colspan="2">
|
|
||||||
<widget class="QCheckBox" name="kcfg_ZoomOnFocusEnabled">
|
|
||||||
<property name="text">
|
|
||||||
<string>Zoom on Focus</string>
|
|
||||||
</property>
|
|
||||||
<property name="checked">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="labelFocusedDisplayDistance">
|
|
||||||
<property name="text">
|
|
||||||
<string>Focused Display Distance:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="1">
|
|
||||||
<widget class="LabeledSlider" name="kcfg_FocusedDisplayDistance">
|
|
||||||
<property name="decimalShift">
|
|
||||||
<double>2</double>
|
|
||||||
</property>
|
|
||||||
<property name="tickPosition">
|
|
||||||
<enum>QSlider::TicksBelow</enum>
|
|
||||||
</property>
|
|
||||||
<property name="tickInterval">
|
|
||||||
<double>25</double>
|
|
||||||
</property>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="tracking">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QLabel" name="labelAllDisplaysDistance">
|
|
||||||
<property name="text">
|
|
||||||
<string>All Displays Distance:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="1">
|
|
||||||
<widget class="LabeledSlider" name="kcfg_AllDisplaysDistance">
|
|
||||||
<property name="decimalShift">
|
|
||||||
<double>2</double>
|
|
||||||
</property>
|
|
||||||
<property name="tickPosition">
|
|
||||||
<enum>QSlider::TicksBelow</enum>
|
|
||||||
</property>
|
|
||||||
<property name="tickInterval">
|
|
||||||
<double>25</double>
|
|
||||||
</property>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="tracking">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="0">
|
|
||||||
<widget class="QLabel" name="labelDisplaySpacing">
|
|
||||||
<property name="text">
|
|
||||||
<string>Display Spacing:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="1">
|
|
||||||
<widget class="QSlider" name="kcfg_DisplaySpacing">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="tracking">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="5" column="0">
|
|
||||||
<widget class="QLabel" name="labelVirtualDisplays">
|
|
||||||
<property name="text">
|
|
||||||
<string>Add Virtual Display:</string>
|
|
||||||
</property>
|
|
||||||
<property name="visible">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="5" column="1">
|
|
||||||
<widget class="QWidget" name="widgetVirtualDisplayButtons">
|
|
||||||
<property name="visible">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<layout class="QHBoxLayout" name="layoutVirtualDisplayButtons">
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="buttonAdd1080p">
|
|
||||||
<property name="text">
|
|
||||||
<string>+ 1080p</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="buttonAdd1440p">
|
|
||||||
<property name="text">
|
|
||||||
<string>+ 1440p</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="buttonOpenDisplaysSettings">
|
|
||||||
<property name="text">
|
|
||||||
<string>Rearrange displays</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="6" column="0" colspan="2">
|
|
||||||
<widget class="KShortcutsEditor" name="shortcutsEditor" native="true">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<widget class="QWidget" name="tabAdvanced">
|
|
||||||
<attribute name="title">
|
|
||||||
<string>&Advanced</string>
|
|
||||||
</attribute>
|
|
||||||
<layout class="QFormLayout" name="formAdvanced">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="labelDisplayWrappingScheme">
|
|
||||||
<property name="text">
|
|
||||||
<string>Display Wrapping Scheme:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QComboBox" name="kcfg_DisplayWrappingScheme">
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Auto</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Horizontal</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Vertical</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Flat</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLabel" name="labelAntialiasingQuality">
|
|
||||||
<property name="text">
|
|
||||||
<string>Anti-aliasing quality:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QComboBox" name="kcfg_AntialiasingQuality">
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>None</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Medium</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>High</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>Very High</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="labelDisplayHorizontalOffset">
|
|
||||||
<property name="text">
|
|
||||||
<string>Display Horizontal Offset:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="1">
|
|
||||||
<widget class="LabeledSlider" name="kcfg_DisplayHorizontalOffset">
|
|
||||||
<property name="decimalShift">
|
|
||||||
<double>2</double>
|
|
||||||
</property>
|
|
||||||
<property name="tickPosition">
|
|
||||||
<enum>QSlider::TicksBelow</enum>
|
|
||||||
</property>
|
|
||||||
<property name="tickInterval">
|
|
||||||
<double>50</double>
|
|
||||||
</property>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="tracking">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QLabel" name="labelDisplayVerticalOffset">
|
|
||||||
<property name="text">
|
|
||||||
<string>Display Vertical Offset:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="1">
|
|
||||||
<widget class="LabeledSlider" name="kcfg_DisplayVerticalOffset">
|
|
||||||
<property name="decimalShift">
|
|
||||||
<double>2</double>
|
|
||||||
</property>
|
|
||||||
<property name="tickPosition">
|
|
||||||
<enum>QSlider::TicksBelow</enum>
|
|
||||||
</property>
|
|
||||||
<property name="tickInterval">
|
|
||||||
<double>50</double>
|
|
||||||
</property>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="tracking">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="0" colspan="2">
|
|
||||||
<widget class="QCheckBox" name="kcfg_RemoveVirtualDisplaysOnDisable">
|
|
||||||
<property name="visible">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Remove virtual displays on disable</string>
|
|
||||||
</property>
|
|
||||||
<property name="checked"><bool>true</bool></property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<widget class="QWidget" name="tabLicenseDetails">
|
|
||||||
<attribute name="title">
|
|
||||||
<string>&License Details</string>
|
|
||||||
</attribute>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayoutLicense">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="labelLicenseSummary">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="visible">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="labelDonateLink">
|
|
||||||
<property name="text">
|
|
||||||
<string><a href="https://ko-fi.com/wheaney">Renew or support on Ko‑fi</a></string>
|
|
||||||
</property>
|
|
||||||
<property name="openExternalLinks">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignHCenter|Qt::AlignVCenter</set>
|
|
||||||
</property>
|
|
||||||
<property name="visible">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="groupBoxEmail">
|
|
||||||
<property name="title">
|
|
||||||
<string>Request a token</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayoutEmail">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLineEdit" name="lineEditLicenseEmail">
|
|
||||||
<property name="placeholderText">
|
|
||||||
<string>you@example.com</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QPushButton" name="buttonSubmitEmail">
|
|
||||||
<property name="text">
|
|
||||||
<string>Submit</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0" colspan="2">
|
|
||||||
<widget class="QLabel" name="labelEmailStatus">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="visible">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="groupBoxToken">
|
|
||||||
<property name="title">
|
|
||||||
<string>Verify token</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayoutToken">
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLineEdit" name="lineEditLicenseToken"/>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QPushButton" name="buttonSubmitToken">
|
|
||||||
<property name="text">
|
|
||||||
<string>Verify</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0" colspan="2">
|
|
||||||
<widget class="QLabel" name="labelTokenStatus">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="visible">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="verticalSpacerLicense">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>40</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<widget class="QWidget" name="tabAbout">
|
|
||||||
<attribute name="title">
|
|
||||||
<string>&About</string>
|
|
||||||
</attribute>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayoutAbout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="labelAppNameVersion">
|
|
||||||
<property name="text">
|
|
||||||
<string>Breezy Desktop Effect - v0.0.0</string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignHCenter|Qt::AlignVCenter</set>
|
|
||||||
</property>
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<pointsize>14</pointsize>
|
|
||||||
<weight>75</weight>
|
|
||||||
<bold>true</bold>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Author: Wayne Heaney <wayne@xronlinux.com></string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignHCenter|Qt::AlignVCenter</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>License: GPL-3.0</string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignHCenter|Qt::AlignVCenter</set>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="verticalSpacerAbout">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>40</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<customwidgets>
|
|
||||||
<customwidget>
|
|
||||||
<class>KShortcutsEditor</class>
|
|
||||||
<extends>QWidget</extends>
|
|
||||||
<header>kshortcutseditor.h</header>
|
|
||||||
<container>1</container>
|
|
||||||
</customwidget>
|
|
||||||
</customwidgets>
|
|
||||||
<resources/>
|
|
||||||
<connections>
|
|
||||||
</connections>
|
|
||||||
</ui>
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
#include "labeledslider.h"
|
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QSlider>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QPainterPath>
|
|
||||||
#include <QStyleOptionSlider>
|
|
||||||
#include <algorithm> // 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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QKeySequence>
|
|
||||||
#include <Qt>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
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")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
#include "breezydesktopeffect.h"
|
|
||||||
|
|
||||||
namespace KWin
|
|
||||||
{
|
|
||||||
|
|
||||||
KWIN_EFFECT_FACTORY(BreezyDesktopEffect, "metadata.json")
|
|
||||||
|
|
||||||
} // namespace KWin
|
|
||||||
|
|
||||||
#include "main.moc"
|
|
||||||
|
|
@ -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"
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
$<$<CXX_COMPILER_ID:MSVC>:/EHsc>
|
|
||||||
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-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)
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
// New implementation using QProcess to call python
|
|
||||||
#include "xrdriveripc.h"
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <cmath>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QProcess>
|
|
||||||
#include <QProcessEnvironment>
|
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QJsonValue>
|
|
||||||
|
|
||||||
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<QJsonObject> 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<QJsonObject> 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<std::string, bool> &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");
|
|
||||||
}
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
// C++ bridge now invoking xrdriveripc via external python process
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
// 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<QJsonObject> retrieveConfig();
|
|
||||||
std::optional<QJsonObject> retrieveDriverState();
|
|
||||||
bool writeConfig(const QJsonObject &configUpdate);
|
|
||||||
bool writeControlFlags(const std::map<std::string, bool> &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
|
|
||||||
};
|
|
||||||
|
|
@ -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())
|
|
||||||
Loading…
Reference in New Issue