diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f947193..41aebbe 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: "ubuntu:24.04" +image: "debian:unstable" stages: - check @@ -13,6 +13,52 @@ commitcheck: # only run for merge requests - if [ -z "$CI_MERGE_REQUEST_TITLTE" ]; then true; else python ./dist/tagging/check_conventional_commit.py "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"; fi +cargo:fmtcheck: + image: "rust:slim" + stage: check + script: + - rustup component add rustfmt + # Create blank versions of our configured files + # so rustfmt does not yell about non-existent files or completely empty files + - echo -e "" >> src/constants.rs + - rustc -Vv && cargo -Vv + - cargo fmt --version + - cargo fmt --all -- --check + +cargo:clippy: + stage: check + variables: + RUSTFLAGS: "-Dwarnings" + script: + - apt-get update + - apt-get install libgtk-4-dev libadwaita-1-dev libssl-dev libjxl-dev libvte-2.91-gtk4-dev meson ninja-build git desktop-file-utils gettext file libusb-dev libusb-1.0-0-dev libopenxr-dev curl -y + - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/rustup.sh + - chmod +x /tmp/rustup.sh + - /tmp/rustup.sh -y + - source "$HOME/.cargo/env" + - rustup component add clippy + - rustc -Vv && cargo -Vv + - cp src/constants.rs.in src/constants.rs + - cargo clippy --version + - cargo clippy --all-targets --all-features + +cargo:test: + stage: check + script: + - apt-get update + - apt-get install libgtk-4-dev libadwaita-1-dev libssl-dev libjxl-dev libvte-2.91-gtk4-dev meson ninja-build git desktop-file-utils gettext file libusb-dev libusb-1.0-0-dev libopenxr-dev curl -y + - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/rustup.sh + - chmod +x /tmp/rustup.sh + - /tmp/rustup.sh -y + - source "$HOME/.cargo/env" + - rustc --version && cargo --version # Print version info for debugging + - meson setup build -Dprefix="$PWD/build/localprefix" -Dprofile=development + - ninja -C build + - cargo test --workspace --verbose + cache: + paths: + - /var/cache/apt + appimage: stage: deploy script: @@ -22,7 +68,6 @@ appimage: - chmod +x /tmp/rustup.sh - /tmp/rustup.sh -y - source "$HOME/.cargo/env" - - rustup component add clippy - bash ./dist/appimage/build_appimage.sh artifacts: paths: diff --git a/Cargo.lock b/Cargo.lock index cb2d639..c2bfdd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "addr2line" @@ -420,15 +420,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.20" @@ -445,16 +436,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "delicious-adwaita" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53548c789a95211e0ce6d26c213067002b9b4360f8de69046d84de78ad9da3f" -dependencies = [ - "gtk4", - "libadwaita", -] - [[package]] name = "deranged" version = "0.3.11" @@ -573,11 +554,10 @@ dependencies = [ [[package]] name = "envision" -version = "3.1.1" +version = "1.1.0" dependencies = [ "anyhow", "ash", - "delicious-adwaita", "gettext-rs", "git2", "gtk4", @@ -594,12 +574,8 @@ dependencies = [ "rusb", "serde", "serde_json", - "serde_yaml", "sha2", "tokio", - "tracing", - "tracing-appender", - "tracing-subscriber", "tracker", "uuid", "vte4", @@ -1084,9 +1060,9 @@ dependencies = [ [[package]] name = "gtk4" -version = "0.9.6" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1c491051f030994fd0cde6f3c44f3f5640210308cff1298c7673c47408091d" +checksum = "9376d14d7e33486c54823a42bef296e882b9f25cb4c52b52f4d1d57bbadb5b6d" dependencies = [ "cairo-rs", "field-offset", @@ -1117,9 +1093,9 @@ dependencies = [ [[package]] name = "gtk4-sys" -version = "0.9.6" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41e03b01e54d77c310e1d98647d73f996d04b2f29b9121fe493ea525a7ec03d6" +checksum = "e653b0a9001ba9be1ffddb9373bfe9a111f688222f5aeee2841481300d91b55a" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1534,9 +1510,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libadwaita" -version = "0.7.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500135d29c16aabf67baafd3e7741d48e8b8978ca98bac39e589165c8dc78191" +checksum = "8611ee9fb85e7606c362b513afcaf5b59853f79e4d98caaaf581d99465014247" dependencies = [ "gdk4", "gio", @@ -1722,15 +1698,6 @@ dependencies = [ "libc", ] -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - [[package]] name = "memchr" version = "2.7.4" @@ -1853,16 +1820,6 @@ dependencies = [ "zbus 4.4.0", ] -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "num-conv" version = "0.1.0" @@ -1988,12 +1945,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "pango" version = "0.20.6" @@ -2229,17 +2180,8 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] @@ -2250,15 +2192,9 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.5" @@ -2567,19 +2503,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - [[package]] name = "sha1" version = "0.10.6" @@ -2602,15 +2525,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "shlex" version = "1.3.0" @@ -2799,16 +2713,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "time" version = "0.3.36" @@ -2816,12 +2720,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde", "time-core", - "time-macros", ] [[package]] @@ -2830,16 +2732,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" -[[package]] -name = "time-macros" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tinystr" version = "0.7.6" @@ -2952,18 +2844,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-appender" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" -dependencies = [ - "crossbeam-channel", - "thiserror", - "time", - "tracing-subscriber", -] - [[package]] name = "tracing-attributes" version = "0.1.28" @@ -2982,49 +2862,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", - "tracing-serde", ] [[package]] @@ -3088,12 +2925,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - [[package]] name = "untrusted" version = "0.9.0" @@ -3133,12 +2964,6 @@ dependencies = [ "rand", ] -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index 4ecd109..3ae3d2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,7 @@ [package] name = "envision" -version = "3.1.1" +version = "1.1.0" edition = "2021" -authors = [ - "Gabriele Musco ", -] -description = "Orchestrator for the free XR stack" -repository = "https://gitlab.com/gabmus/envision" -documentation = "https://gitlab.com/gabmus/envision" -license = "AGPL-3.0-or-later" -keywords = ["desktop", "linux", "vr", "xr", "gtk"] -readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -40,8 +31,3 @@ sha2 = "0.10.8" tokio = { version = "1.39.3", features = ["process"] } notify-rust = "4.11.3" zbus = { version = "5.1.1", features = ["tokio"] } -tracing-subscriber = { version = "0.3.19", features = ["env-filter", "json"] } -tracing = "0.1.41" -tracing-appender = "0.2.3" -serde_yaml = "0.9.34" -delicious-adwaita = { version = "0.3.0", features = ["all_themes"] } diff --git a/README.md b/README.md index d9830f3..05a865c 100644 --- a/README.md +++ b/README.md @@ -60,10 +60,6 @@ cd envision -# Debugging - -To view all the logs you need to run envision with the env var `RUST_LOG=trace`. - # Common issues ## NOSUID with systemd-homed diff --git a/data/org.gabmus.envision.metainfo.xml.in.in b/data/org.gabmus.envision.metainfo.xml.in.in index 76b4dd4..9dceb07 100644 --- a/data/org.gabmus.envision.metainfo.xml.in.in +++ b/data/org.gabmus.envision.metainfo.xml.in.in @@ -2,182 +2,22 @@ @APP_ID@ CC0 - AGPL-3.0-or-later + AGPL-3.0 @PRETTY_NAME@ - Orchestrator for the free XR stack + GUI for Monado -

Orchestrator for the free XR stack

+

GUI for Monado

- + @REPO_URL@ @REPO_URL@/issues - - -

Fixes

-
    -
  • add libusb and libusb-dev deps
  • -
  • Revert "disable and blacklist wayvr dashboard plugin"
  • -
-
-
- - -

What's new

-
    -
  • don't set openvrpaths as read only during profile startup
  • -
  • small design changes to build window ui
  • -
  • add support for vapor openvr compatibility module
  • -
  • remove monado vulkan layers check for nvidia
  • -
-

Fixes

-
    -
  • disable and blacklist wayvr dashboard plugin
  • -
  • monado dependencies: use wayland-protocols-devel on Fedora
  • -
-

Other changes

-
    -
  • clippy
  • -
-
-
- - -

Fixes

-
    -
  • libnotify headers path in wivrn depcheck
  • -
-
-
- - -

Breaking changes

-
    -
  • plugin store
  • -
-

What's new

-
    -
  • Add WayVR Dashboard to the plugin list
  • -
  • wivrn: replace pulse dependency with pipewire
  • -
  • set wivrn launch options in the default profile
  • -
  • support for plugin dependencies and wayvr dashboards (using unreleased api)
  • -
  • launch options for plugins
  • -
  • homepage and author in plugin details
  • -
  • write rolling logs to file
  • -
  • add xrizer as an option for openvr compatibility module
  • -
  • switch wlx manifest
  • -
  • fetch plugins manifests online
  • -
  • add telescope to plugin store
  • -
  • ask to build profile after editing it
  • -
  • add cpu to debug info
  • -
  • make env var description selectable
  • -
  • press enter on env var entry to add
  • -
  • clearer messaging around setcap failures; getcap after setcap
  • -
  • version command line option
  • -
  • single stage ci with tests, clippy and fmt check all in one
  • -
  • use ubuntu for the ci
  • -
-

Fixes

-
    -
  • onnxruntime build error when latest release has no artifacts
  • -
  • typo in install wivrn box
  • -
  • typo in XRT_COMPOSITOR_SCALE_PERCENTAGE
  • -
  • add plugin to config via function instead of signal
  • -
  • refresh all rows on plugin install (fixes dependencies showing up as not installed)
  • -
  • always mark plugin executable as executable
  • -
  • wrap single plugin cmd parts in single quotes
  • -
  • use correct wayland-protocols package name for open suse
  • -
  • typo in plugin schema
  • -
  • remove canonicalize from get steamvr bin dir path function
  • -
  • actually return steamvr dir in get_steamvr_base_dir
  • -
  • canonicalize some steamvr related paths to hopefully resolve symlinks
  • -
  • get ovr compatibility module runtime dir from profile ovr compatibility module struct
  • -
  • use exists() to verify existance of socket file
  • -
  • correct wording of lighthouse calibration
  • -
  • get steamvr bin dir by parsing libraryfolders.vdf
  • -
  • switch to searching for the xml for deb based distros
  • -
  • Include not shared object wayland-protocols
  • -
  • add libbsd deps for monado
  • -
  • add wayland drm-lease protocols dep for monado
  • -
  • use boost dev packages
  • -
  • debian package name for gstreamer plugins base
  • -
  • print active runtime related informative logs as debug
  • -
-

Other changes

-
    -
  • plugins: point to stardust hosted manifest
  • -
  • cargo: revert back to serde_yaml over unsound advisory
  • -
-
-
- - -

Fixes

-
    -
  • add screenshots to appdata
  • -
-
-
- - -

Breaking changes

-
    -
  • enable support for different openvr compatibility modules other than opencomposite
  • -
-

What's new

-
    -
  • add metadata to Cargo.toml; get developers from Cargo.toml authors; rectify SPDX id for license as AGPL-3.0-or-later
  • -
  • refactor builders cmake vars and env to use inner blocks
  • -
  • disable wivrnctl; refactor cmake vars in wivrn builder
  • -
  • make left and right qwerty controllers appear as no controller detected
  • -
  • try to find libmonado and openxr shared objects by reading openxr config
  • -
  • prefer symlinks over generating files for openxr active runtime json file
  • -
  • move steam library folders parser to own module; function to find steam openxr json; format
  • -
  • proper logging framework
  • -
-

Fixes

-
    -
  • build profile can be specified manually
  • -
  • update wivrn libmonado path to wirvn/libmonado_wivrn.so
  • -
  • create openxr config dir when starting profile
  • -
  • add libnotify-dev dependency for wivrn
  • -
  • openssl dep is an include
  • -
  • add openssl-devel dep for wivrn
  • -
  • negative logic and early return in start xrservice func
  • -
  • use let err instead of match in restore xr files func
  • -
-

Other changes

-
    -
  • format
  • -
-
-
- - -

Fixes

-
    -
  • remove wivrn pairing mode timer
  • -
-
-

What's new

diff --git a/data/screenshots/01.png b/data/screenshots/01.png deleted file mode 100644 index 7591b4b..0000000 Binary files a/data/screenshots/01.png and /dev/null differ diff --git a/data/screenshots/02.png b/data/screenshots/02.png deleted file mode 100644 index 4074f24..0000000 Binary files a/data/screenshots/02.png and /dev/null differ diff --git a/data/screenshots/03.png b/data/screenshots/03.png deleted file mode 100644 index d29fe47..0000000 Binary files a/data/screenshots/03.png and /dev/null differ diff --git a/data/screenshots/04.png b/data/screenshots/04.png deleted file mode 100644 index e51e5d0..0000000 Binary files a/data/screenshots/04.png and /dev/null differ diff --git a/dist/appimage/build_appimage.sh b/dist/appimage/build_appimage.sh index 14dcbf7..510cd7d 100755 --- a/dist/appimage/build_appimage.sh +++ b/dist/appimage/build_appimage.sh @@ -8,7 +8,6 @@ if [[ ! -f Cargo.toml ]]; then fi meson setup appimage_build -Dprefix=/usr -Dprofile=default -meson test -C appimage_build --print-errorlogs DESTDIR="$PWD/AppDir" ninja -C appimage_build install curl -SsLO https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage chmod +x linuxdeploy-x86_64.AppImage diff --git a/dist/arch/PKGBUILD b/dist/arch/PKGBUILD index ee1a4b2..3ff8a64 100644 --- a/dist/arch/PKGBUILD +++ b/dist/arch/PKGBUILD @@ -33,6 +33,7 @@ makedepends=( ) optdepends=( 'libudev0-shim: steamvr_lh lighthouse driver support' + 'monado-vulkan-layers-git: Vulkan layers for NVIDIA users' ) provides=(envision) conflicts=(envision) diff --git a/meson.build b/meson.build index d58f4ed..78c35eb 100644 --- a/meson.build +++ b/meson.build @@ -1,9 +1,9 @@ project( 'envision', 'rust', - version: '3.1.1', # version number row + version: '1.1.0', # version number row meson_version: '>= 0.59', - license: 'AGPL-3.0-or-later', + license: 'AGPL-3.0', ) i18n = import('i18n') @@ -38,30 +38,17 @@ iconsdir = datadir / 'icons' podir = meson.project_source_root() / 'po' gettext_package = meson.project_name() -opt_profile = get_option('profile') - -# if a profile isn't specified infer from git -if opt_profile == 'default' - # are we building a tagged version? - if run_command('git', 'describe', '--tags', '--exact-match').returncode() != 0 - profile = 'Devel' - vcs_tag = run_command('git', 'rev-parse', '--short', 'HEAD', check: false).stdout().strip() - if vcs_tag == '' - version_suffix = '-devel' - else - version_suffix = '-@0@'.format(vcs_tag) - endif - application_id = '@0@.@1@'.format(base_id, profile) - else - profile = '' - version_suffix = '' - application_id = base_id - endif -elif opt_profile == 'development' +# are we building a tagged version? +if run_command('git', 'describe', '--tags', '--exact-match').returncode() != 0 profile = 'Devel' - version_suffix = '-devel' + vcs_tag = run_command('git', 'rev-parse', '--short', 'HEAD', check: false).stdout().strip() + if vcs_tag == '' + version_suffix = '-devel' + else + version_suffix = '-@0@'.format(vcs_tag) + endif application_id = '@0@.@1@'.format(base_id, profile) -elif opt_profile == 'release' +else profile = '' version_suffix = '' application_id = base_id diff --git a/meson_options.txt b/meson_options.txt index aaebed1..7d397e1 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -3,7 +3,6 @@ option( type: 'combo', choices: [ 'default', - 'release', 'development' ], value: 'default', diff --git a/scripts/build_mercury.sh b/scripts/build_mercury.sh index b3db66a..fffd979 100755 --- a/scripts/build_mercury.sh +++ b/scripts/build_mercury.sh @@ -11,22 +11,7 @@ if [[ -z $PREFIX ]] || [[ -z $CACHE_DIR ]]; then exit 1 fi -ONNX_RELEASES=$(curl -sSL "https://api.github.com/repos/microsoft/onnxruntime/releases") -NUM_RELEASES=$(echo "$ONNX_RELEASES" | jq -r '[ select (.[]!=null) ] | length') - -for (( IDX=0; IDX VecDeque { let mut jobs = VecDeque::::new(); @@ -38,52 +35,28 @@ pub fn get_build_basalt_jobs(profile: &Profile, clean_build: bool) -> VecDeque = HashMap::new(); + cmake_vars.insert("CMAKE_EXPORT_COMPILE_COMMANDS".into(), "ON".into()); + cmake_vars.insert("CMAKE_BUILD_TYPE".into(), "RelWithDebInfo".into()); + cmake_vars.insert( + "CMAKE_INSTALL_PREFIX".into(), + profile.prefix.to_string_lossy().to_string(), + ); + cmake_vars.insert("BUILD_TESTS".into(), "OFF".into()); + cmake_vars.insert("BASALT_INSTANTIATIONS_DOUBLE".into(), "OFF".into()); + cmake_vars.insert( + "CMAKE_INSTALL_LIBDIR".into(), + profile.prefix.join("lib").to_string_lossy().to_string(), + ); + + let mut cmake_env: HashMap = HashMap::new(); + cmake_env.insert("CMAKE_BUILD_PARALLEL_LEVEL".into(), "2".into()); + cmake_env.insert("CMAKE_BUILD_TYPE".into(), "RelWithDebInfo".into()); + cmake_env.insert("BUILD_TESTS".into(), "off".into()); let cmake = Cmake { - env: Some({ - let mut cmake_env: HashMap = HashMap::new(); - for (k, v) in [ - // The basalt build uses a lot of RAM, so we have to limit the number of - // build processes to not starve the system of memory - // Limit to 6 build processes at most - ( - "CMAKE_BUILD_PARALLEL_LEVEL", - std::cmp::min( - 6, - std::thread::available_parallelism() - .map(NonZero::get) - .unwrap_or(2), - ) - .to_string(), - ), - ("CMAKE_BUILD_TYPE", "RelWithDebInfo".into()), - ("CMAKE_POLICY_VERSION_MINIMUM", "3.5".into()), - ("BUILD_TESTS", "off".into()), - ] { - cmake_env.insert(k.to_string(), v); - } - cmake_env - }), - vars: Some({ - let mut cmake_vars: HashMap = HashMap::new(); - for (k, v) in [ - ("CMAKE_EXPORT_COMPILE_COMMANDS", "ON"), - ("CMAKE_BUILD_TYPE", "RelWithDebInfo"), - ("BUILD_TESTS", "OFF"), - ("BASALT_INSTANTIATIONS_DOUBLE", "OFF"), - ] { - cmake_vars.insert(k.to_string(), v.to_string()); - } - cmake_vars.insert( - "CMAKE_INSTALL_PREFIX".into(), - profile.prefix.to_string_lossy().to_string(), - ); - cmake_vars.insert( - "CMAKE_INSTALL_LIBDIR".into(), - profile.prefix.join("lib").to_string_lossy().to_string(), - ); - cmake_vars - }), + env: Some(cmake_env), + vars: Some(cmake_vars), source_dir: profile.features.basalt.path.as_ref().unwrap().clone(), build_dir: build_dir.clone(), }; diff --git a/src/builders/build_libsurvive.rs b/src/builders/build_libsurvive.rs index 9f59353..b4b0dc3 100644 --- a/src/builders/build_libsurvive.rs +++ b/src/builders/build_libsurvive.rs @@ -44,30 +44,24 @@ pub fn get_build_libsurvive_jobs(profile: &Profile, clean_build: bool) -> VecDeq .as_ref() .unwrap() .join("build"); + let mut cmake_vars: HashMap = HashMap::new(); + cmake_vars.insert("CMAKE_EXPORT_COMPILE_COMMANDS".into(), "ON".into()); + cmake_vars.insert("CMAKE_BUILD_TYPE".into(), "RelWithDebInfo".into()); + cmake_vars.insert("ENABLE_api_example".into(), "OFF".into()); + cmake_vars.insert("USE_HIDAPI".into(), "ON".into()); + cmake_vars.insert("CMAKE_SKIP_INSTALL_RPATH".into(), "YES".into()); + cmake_vars.insert( + "CMAKE_INSTALL_PREFIX".into(), + profile.prefix.to_string_lossy().to_string(), + ); + cmake_vars.insert( + "CMAKE_INSTALL_LIBDIR".into(), + profile.prefix.join("lib").to_string_lossy().to_string(), + ); let cmake = Cmake { env: None, - vars: Some({ - let mut cmake_vars: HashMap = HashMap::new(); - for (k, v) in [ - ("CMAKE_EXPORT_COMPILE_COMMANDS", "ON"), - ("CMAKE_BUILD_TYPE", "RelWithDebInfo"), - ("ENABLE_api_example", "OFF"), - ("USE_HIDAPI", "ON"), - ("CMAKE_SKIP_INSTALL_RPATH", "YES"), - ] { - cmake_vars.insert(k.to_string(), v.to_string()); - } - cmake_vars.insert( - "CMAKE_INSTALL_PREFIX".into(), - profile.prefix.to_string_lossy().to_string(), - ); - cmake_vars.insert( - "CMAKE_INSTALL_LIBDIR".into(), - profile.prefix.join("lib").to_string_lossy().to_string(), - ); - cmake_vars - }), + vars: Some(cmake_vars), source_dir: profile.features.libsurvive.path.as_ref().unwrap().clone(), build_dir: build_dir.clone(), }; diff --git a/src/builders/build_monado.rs b/src/builders/build_monado.rs index 8837874..f379d6f 100644 --- a/src/builders/build_monado.rs +++ b/src/builders/build_monado.rs @@ -43,43 +43,37 @@ pub fn get_build_monado_jobs(profile: &Profile, clean_build: bool) -> VecDeque = HashMap::new(); + cmake_vars.insert("CMAKE_EXPORT_COMPILE_COMMANDS".into(), "ON".into()); + cmake_vars.insert("CMAKE_BUILD_TYPE".into(), "RelWithDebInfo".into()); + cmake_vars.insert("XRT_HAVE_SYSTEM_CJSON".into(), "NO".into()); + cmake_vars.insert( + "CMAKE_LIBDIR".into(), + profile.prefix.join("lib").to_string_lossy().to_string(), + ); + cmake_vars.insert( + "CMAKE_INSTALL_PREFIX".into(), + profile.prefix.to_string_lossy().to_string(), + ); + cmake_vars.insert( + "CMAKE_C_FLAGS".into(), + format!("-Wl,-rpath='{}/lib'", profile.prefix.to_string_lossy(),), + ); + cmake_vars.insert( + "CMAKE_CXX_FLAGS".into(), + format!("-Wl,-rpath='{}/lib'", profile.prefix.to_string_lossy(),), + ); + profile.xrservice_cmake_flags.iter().for_each(|(k, v)| { + if k == "CMAKE_C_FLAGS" || k == "CMAKE_CXX_FLAGS" { + cmake_vars.insert(k.clone(), format!("{} {}", cmake_vars.get(k).unwrap(), v)); + } else { + cmake_vars.insert(k.clone(), v.clone()); + } + }); let cmake = Cmake { env: Some(env), - vars: Some({ - let mut cmake_vars: HashMap = HashMap::new(); - for (k, v) in [ - ("CMAKE_EXPORT_COMPILE_COMMANDS", "ON"), - ("CMAKE_BUILD_TYPE", "RelWithDebInfo"), - ("XRT_HAVE_SYSTEM_CJSON", "NO"), - ] { - cmake_vars.insert(k.to_string(), v.to_string()); - } - cmake_vars.insert( - "CMAKE_LIBDIR".into(), - profile.prefix.join("lib").to_string_lossy().to_string(), - ); - cmake_vars.insert( - "CMAKE_INSTALL_PREFIX".into(), - profile.prefix.to_string_lossy().to_string(), - ); - cmake_vars.insert( - "CMAKE_C_FLAGS".into(), - format!("-Wl,-rpath='{}/lib'", profile.prefix.to_string_lossy(),), - ); - cmake_vars.insert( - "CMAKE_CXX_FLAGS".into(), - format!("-Wl,-rpath='{}/lib'", profile.prefix.to_string_lossy(),), - ); - profile.xrservice_cmake_flags.iter().for_each(|(k, v)| { - if k == "CMAKE_C_FLAGS" || k == "CMAKE_CXX_FLAGS" { - cmake_vars.insert(k.clone(), format!("{} {}", cmake_vars.get(k).unwrap(), v)); - } else { - cmake_vars.insert(k.clone(), v.clone()); - } - }); - cmake_vars - }), + vars: Some(cmake_vars), source_dir: profile.xrservice_path.clone(), build_dir: build_dir.clone(), }; diff --git a/src/builders/build_opencomposite.rs b/src/builders/build_opencomposite.rs index ce6e2da..631b69f 100644 --- a/src/builders/build_opencomposite.rs +++ b/src/builders/build_opencomposite.rs @@ -19,15 +19,13 @@ pub fn get_build_opencomposite_jobs(profile: &Profile, clean_build: bool) -> Vec let git = Git { repo: profile - .ovr_comp - .repo + .opencomposite_repo .as_ref() .unwrap_or(&"https://gitlab.com/znixian/OpenOVR.git".into()) .clone(), - dir: profile.ovr_comp.path.clone(), + dir: profile.opencomposite_path.clone(), branch: profile - .ovr_comp - .branch + .opencomposite_branch .as_ref() .unwrap_or(&"openxr".into()) .clone(), @@ -35,20 +33,14 @@ pub fn get_build_opencomposite_jobs(profile: &Profile, clean_build: bool) -> Vec jobs.extend(git.get_pre_build_jobs(profile.pull_on_build)); - let build_dir = profile.ovr_comp.path.join("build"); + let build_dir = profile.opencomposite_path.join("build"); + let mut cmake_vars: HashMap = HashMap::new(); + cmake_vars.insert("CMAKE_EXPORT_COMPILE_COMMANDS".into(), "ON".into()); + cmake_vars.insert("CMAKE_BUILD_TYPE".into(), "RelWithDebInfo".into()); let cmake = Cmake { env: None, - vars: Some({ - let mut cmake_vars: HashMap = HashMap::new(); - for (k, v) in [ - ("CMAKE_EXPORT_COMPILE_COMMANDS", "ON"), - ("CMAKE_BUILD_TYPE", "RelWithDebInfo"), - ] { - cmake_vars.insert(k.to_string(), v.to_string()); - } - cmake_vars - }), - source_dir: profile.ovr_comp.path.clone(), + vars: Some(cmake_vars), + source_dir: profile.opencomposite_path.clone(), build_dir: build_dir.clone(), }; if !Path::new(&build_dir).is_dir() || clean_build { diff --git a/src/builders/build_vapor.rs b/src/builders/build_vapor.rs deleted file mode 100644 index 5a05932..0000000 --- a/src/builders/build_vapor.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::{ - build_tools::{cmake::Cmake, git::Git}, - profile::Profile, - termcolor::TermColor, - ui::job_worker::job::WorkerJob, - util::file_utils::rm_rf, -}; -use std::{ - collections::{HashMap, VecDeque}, - path::Path, -}; - -pub fn get_build_vapor_jobs(profile: &Profile, clean_build: bool) -> VecDeque { - let mut jobs = VecDeque::::new(); - jobs.push_back(WorkerJob::new_printer( - "Building VapoR...", - Some(TermColor::Blue), - )); - - let git = Git { - repo: profile - .ovr_comp - .repo - .as_ref() - .unwrap_or(&"https://github.com/micheal65536/VapoR.git".into()) - .clone(), - dir: profile.ovr_comp.path.clone(), - branch: profile - .ovr_comp - .branch - .as_ref() - .unwrap_or(&"master".into()) - .clone(), - }; - - jobs.extend(git.get_pre_build_jobs(profile.pull_on_build)); - - let build_dir = profile.ovr_comp.path.join("build"); - let install_dir = build_dir.join("install_pfx"); - let cmake = Cmake { - env: None, - vars: Some({ - let mut cmake_vars: HashMap = HashMap::new(); - for (k, v) in [ - ("VAPOR_LOG_SILENT", "ON"), - ("USE_SYSTEM_OPENXR", "OFF"), - ("CMAKE_BUILD_TYPE", "RelWithDebInfo"), - ] { - cmake_vars.insert(k.to_string(), v.to_string()); - } - cmake_vars.insert( - "CMAKE_INSTALL_PREFIX".into(), - install_dir.to_string_lossy().to_string(), - ); - cmake_vars - }), - source_dir: profile.ovr_comp.path.clone(), - build_dir: build_dir.clone(), - }; - if !Path::new(&build_dir).is_dir() || clean_build { - rm_rf(&build_dir); - jobs.push_back(cmake.get_prepare_job()); - } - jobs.push_back(cmake.get_build_job()); - jobs.push_back(cmake.get_install_job()); - - jobs -} diff --git a/src/builders/build_wivrn.rs b/src/builders/build_wivrn.rs index cd9d757..f2a415d 100644 --- a/src/builders/build_wivrn.rs +++ b/src/builders/build_wivrn.rs @@ -34,29 +34,23 @@ pub fn get_build_wivrn_jobs(profile: &Profile, clean_build: bool) -> VecDeque = HashMap::new(); + cmake_vars.insert("CMAKE_EXPORT_COMPILE_COMMANDS".into(), "ON".into()); + cmake_vars.insert("CMAKE_BUILD_TYPE".into(), "RelWithDebInfo".into()); + cmake_vars.insert("XRT_HAVE_SYSTEM_CJSON".into(), "NO".into()); + cmake_vars.insert("WIVRN_BUILD_CLIENT".into(), "OFF".into()); + cmake_vars.insert( + "CMAKE_INSTALL_PREFIX".into(), + profile.prefix.to_string_lossy().to_string(), + ); + + profile.xrservice_cmake_flags.iter().for_each(|(k, v)| { + cmake_vars.insert(k.clone(), v.clone()); + }); let cmake = Cmake { env: None, - vars: Some({ - let mut cmake_vars: HashMap = HashMap::new(); - for (k, v) in [ - ("CMAKE_EXPORT_COMPILE_COMMANDS", "ON"), - ("CMAKE_BUILD_TYPE", "RelWithDebInfo"), - ("XRT_HAVE_SYSTEM_CJSON", "NO"), - ("WIVRN_BUILD_CLIENT", "OFF"), - ("WIVRN_BUILD_WIVRNCTL", "OFF"), - ] { - cmake_vars.insert(k.to_string(), v.to_string()); - } - cmake_vars.insert( - "CMAKE_INSTALL_PREFIX".into(), - profile.prefix.to_string_lossy().to_string(), - ); - profile.xrservice_cmake_flags.iter().for_each(|(k, v)| { - cmake_vars.insert(k.clone(), v.clone()); - }); - cmake_vars - }), + vars: Some(cmake_vars), source_dir: profile.xrservice_path.clone(), build_dir: build_dir.clone(), }; diff --git a/src/builders/build_xrizer.rs b/src/builders/build_xrizer.rs deleted file mode 100644 index 6fc1b6b..0000000 --- a/src/builders/build_xrizer.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::{ - build_tools::git::Git, profile::Profile, termcolor::TermColor, ui::job_worker::job::WorkerJob, - util::file_utils::rm_rf, -}; -use std::{collections::VecDeque, path::Path}; - -pub fn get_build_xrizer_jobs(profile: &Profile, clean_build: bool) -> VecDeque { - let mut jobs = VecDeque::::new(); - jobs.push_back(WorkerJob::new_printer( - "Building xrizer...", - Some(TermColor::Blue), - )); - - let git = Git { - repo: profile - .ovr_comp - .repo - .as_ref() - .unwrap_or(&"https://github.com/Supreeeme/xrizer".into()) - .clone(), - dir: profile.ovr_comp.path.clone(), - branch: profile - .ovr_comp - .branch - .as_ref() - .unwrap_or(&"main".into()) - .clone(), - }; - - jobs.extend(git.get_pre_build_jobs(profile.pull_on_build)); - - let build_dir = profile.ovr_comp.path.join("target"); - if !Path::new(&build_dir).is_dir() || clean_build { - rm_rf(&build_dir); - } - - jobs.push_back(WorkerJob::new_cmd( - None, - "sh".into(), - Some(vec![ - "-c".into(), - format!( - "cd '{}' && cargo xbuild --release", - profile.ovr_comp.path.to_string_lossy() - ), - ]), - )); - - jobs -} diff --git a/src/builders/mod.rs b/src/builders/mod.rs index 9d57245..c84be3b 100644 --- a/src/builders/mod.rs +++ b/src/builders/mod.rs @@ -4,6 +4,4 @@ pub mod build_mercury; pub mod build_monado; pub mod build_opencomposite; pub mod build_openhmd; -pub mod build_vapor; pub mod build_wivrn; -pub mod build_xrizer; diff --git a/src/config.rs b/src/config.rs index 80cf0f5..1ee2430 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,48 +7,21 @@ use crate::{ lighthouse::lighthouse_profile, openhmd::openhmd_profile, simulated::simulated_profile, survive::survive_profile, wivrn::wivrn_profile, wmr::wmr_profile, }, - ui::plugins::Plugin, util::file_utils::get_writer, }; use serde::{de::Error, Deserialize, Serialize}; use std::{ - collections::HashMap, fs::File, io::BufReader, path::{Path, PathBuf}, }; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct PluginConfig { - pub plugin: Plugin, - pub enabled: bool, -} - -impl From<&Plugin> for PluginConfig { - fn from(p: &Plugin) -> Self { - Self { - plugin: p.clone(), - enabled: true, - } - } -} - -impl From<&PluginConfig> for Plugin { - fn from(cp: &PluginConfig) -> Self { - cp.plugin.clone() - } -} - const DEFAULT_WIN_SIZE: [i32; 2] = [360, 400]; const fn default_win_size() -> [i32; 2] { DEFAULT_WIN_SIZE } -fn default_theme_name() -> String { - "Follow system".into() -} - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Config { pub selected_profile_uuid: String, @@ -56,10 +29,6 @@ pub struct Config { pub user_profiles: Vec, #[serde(default = "default_win_size")] pub win_size: [i32; 2], - #[serde(default)] - pub plugins: HashMap, - #[serde(default = "default_theme_name")] - pub theme_name: String, } impl Default for Config { @@ -68,10 +37,8 @@ impl Default for Config { // TODO: using an empty string here is ugly selected_profile_uuid: "".to_string(), debug_view_enabled: false, - user_profiles: Vec::default(), + user_profiles: vec![], win_size: DEFAULT_WIN_SIZE, - plugins: HashMap::default(), - theme_name: default_theme_name(), } } } @@ -98,42 +65,10 @@ impl Config { } fn from_path(path: &Path) -> Self { - let mut this: Self = File::open(path) + File::open(path) .ok() .and_then(|file| serde_json::from_reader(BufReader::new(file)).ok()) - .unwrap_or_default(); - - let mut needs_save = false; - - // remap legacy opencomposite data to new ovr_comp - #[allow(deprecated)] - for prof in this.user_profiles.iter_mut() { - if prof - .ovr_comp - .path - .file_name() - .unwrap_or_default() - .to_string_lossy() - == "__envision__fallbackovrcomp" - { - prof.ovr_comp.path = prof.opencomposite_path.clone(); - needs_save = true; - } - if prof.opencomposite_repo.is_some() && prof.ovr_comp.repo.is_none() { - prof.ovr_comp.repo = prof.opencomposite_repo.take(); - needs_save = true; - } - if prof.opencomposite_branch.is_some() && prof.ovr_comp.branch.is_none() { - prof.ovr_comp.branch = prof.opencomposite_branch.take(); - needs_save = true; - } - } - - if needs_save { - this.save_to_path(path).expect("Failed to save config"); - } - - this + .unwrap_or_default() } fn save_to_path(&self, path: &Path) -> Result<(), serde_json::Error> { diff --git a/src/constants.rs.in b/src/constants.rs.in index db00776..0c71035 100644 --- a/src/constants.rs.in +++ b/src/constants.rs.in @@ -16,6 +16,10 @@ pub const LOCALE_DIR: &str = "@LOCALEDIR@"; pub const BUILD_PROFILE: &str = "@PROFILE@"; pub const BUILD_DATETIME: &str = "@BUILD_DATETIME@"; +pub fn get_developers() -> Vec { + vec!["Gabriele Musco ".into()] +} + pub fn get_artists() -> Vec { vec!["App Icon: Yannick (@Yandr)".into()] } diff --git a/src/depcheck/basalt_deps.rs b/src/depcheck/basalt_deps.rs index 00a29b6..2a05826 100644 --- a/src/depcheck/basalt_deps.rs +++ b/src/depcheck/basalt_deps.rs @@ -1,7 +1,7 @@ use super::{ boost_deps::boost_deps, common::{dep_cmake, dep_eigen, dep_gpp, dep_libgl, dep_ninja, dep_opencv}, - DepType, DepcheckResultGetMissing, Dependency, DependencyCheckResult, + DepType, Dependency, DependencyCheckResult, }; use crate::linux_distro::LinuxDistro; use std::collections::HashMap; @@ -181,5 +181,9 @@ pub fn check_basalt_deps() -> Vec { } pub fn get_missing_basalt_deps() -> Vec { - check_basalt_deps().filter_missing_deps() + check_basalt_deps() + .iter() + .filter(|res| !res.found) + .map(|res| res.dependency.clone()) + .collect() } diff --git a/src/depcheck/boost_deps.rs b/src/depcheck/boost_deps.rs index 38df244..6db64cf 100644 --- a/src/depcheck/boost_deps.rs +++ b/src/depcheck/boost_deps.rs @@ -52,8 +52,8 @@ pub fn boost_deps() -> Vec { packages: HashMap::from([ (LinuxDistro::Arch, "boost".into()), (LinuxDistro::Debian, "libboost-all-dev".into()), - (LinuxDistro::Fedora, "boost-devel".into()), - (LinuxDistro::Alpine, "boost-dev".into()), + (LinuxDistro::Fedora, "boost".into()), + (LinuxDistro::Alpine, "boost".into()), (LinuxDistro::Gentoo, "dev-libs/boost".into()), (LinuxDistro::Suse, package.into()), ]), diff --git a/src/depcheck/common.rs b/src/depcheck/common.rs index 901d0ff..56ac4ef 100644 --- a/src/depcheck/common.rs +++ b/src/depcheck/common.rs @@ -303,35 +303,3 @@ pub fn dep_adb() -> Dependency { ]), } } - -pub fn dep_getcap_setcap() -> Dependency { - Dependency { - name: "libcap".into(), - dep_type: DepType::Executable, - filename: "setcap".into(), - packages: HashMap::from([ - (LinuxDistro::Arch, "libcap".into()), - (LinuxDistro::Debian, "libcap2-bin".into()), - (LinuxDistro::Fedora, "libcap".into()), - (LinuxDistro::Alpine, "libcap".into()), - (LinuxDistro::Gentoo, "sys-libs/libcap".into()), - (LinuxDistro::Suse, "libcap-progs".into()), - ]), - } -} - -pub fn dep_glslc() -> Dependency { - Dependency { - name: "glslc".into(), - dep_type: DepType::Executable, - filename: "glslc".into(), - packages: HashMap::from([ - (LinuxDistro::Arch, "shaderc".into()), - (LinuxDistro::Debian, "glslc".into()), - (LinuxDistro::Fedora, "glslc".into()), - (LinuxDistro::Alpine, "shaderc".into()), - (LinuxDistro::Gentoo, "media-libs/shaderc".into()), - (LinuxDistro::Suse, "shaderc".into()), - ]), - } -} diff --git a/src/depcheck/libsurvive_deps.rs b/src/depcheck/libsurvive_deps.rs index 46eec55..c9e8d9c 100644 --- a/src/depcheck/libsurvive_deps.rs +++ b/src/depcheck/libsurvive_deps.rs @@ -1,6 +1,6 @@ use super::{ common::{dep_cmake, dep_eigen, dep_gcc, dep_git, dep_gpp, dep_ninja}, - DepcheckResultGetMissing, Dependency, DependencyCheckResult, + Dependency, DependencyCheckResult, }; fn libsurvive_deps() -> Vec { @@ -19,5 +19,9 @@ pub fn check_libsurvive_deps() -> Vec { } pub fn get_missing_libsurvive_deps() -> Vec { - check_libsurvive_deps().filter_missing_deps() + check_libsurvive_deps() + .iter() + .filter(|res| !res.found) + .map(|res| res.dependency.clone()) + .collect() } diff --git a/src/depcheck/mercury_deps.rs b/src/depcheck/mercury_deps.rs index a7e59be..cad3c80 100644 --- a/src/depcheck/mercury_deps.rs +++ b/src/depcheck/mercury_deps.rs @@ -1,6 +1,4 @@ -use super::{ - common::dep_opencv, DepType, DepcheckResultGetMissing, Dependency, DependencyCheckResult, -}; +use super::{common::dep_opencv, DepType, Dependency, DependencyCheckResult}; use crate::linux_distro::LinuxDistro; use std::collections::HashMap; @@ -41,5 +39,9 @@ pub fn check_mercury_deps() -> Vec { } pub fn get_missing_mercury_deps() -> Vec { - check_mercury_deps().filter_missing_deps() + check_mercury_deps() + .iter() + .filter(|res| !res.found) + .map(|res| res.dependency.clone()) + .collect() } diff --git a/src/depcheck/mod.rs b/src/depcheck/mod.rs index f2565eb..54da91d 100644 --- a/src/depcheck/mod.rs +++ b/src/depcheck/mod.rs @@ -6,7 +6,6 @@ pub mod mercury_deps; pub mod monado_deps; pub mod openhmd_deps; pub mod wivrn_deps; -pub mod xrizer_deps; use crate::linux_distro::LinuxDistro; use std::{collections::HashMap, env, fmt::Display, path::Path}; @@ -17,7 +16,6 @@ pub enum DepType { Executable, Include, UdevRule, - Share, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -51,7 +49,6 @@ impl Dependency { .collect(), DepType::Include => include_paths(), DepType::UdevRule => udev_rules_paths(), - DepType::Share => share_paths(), } { let path_s = &format!("{dir}/{fname}", dir = dir, fname = self.filename); let path = Path::new(&path_s); @@ -109,24 +106,6 @@ impl Display for DependencyCheckResult { } } -pub trait DepcheckResultGetMissing { - fn filter_missing_deps(self) -> Vec; -} - -impl DepcheckResultGetMissing for Vec { - fn filter_missing_deps(self) -> Vec { - self.into_iter() - .filter_map(|res| { - if !res.found { - Some(res.dependency) - } else { - None - } - }) - .collect() - } -} - fn shared_obj_paths() -> Vec { vec![ "/lib".into(), @@ -136,24 +115,6 @@ fn shared_obj_paths() -> Vec { "/usr/local/lib64".into(), "/usr/lib/x86_64-linux-gnu".into(), "/usr/lib/aarch64-linux-gnu".into(), - // Debian puts libclang in /usr/lib/llvm-[llvm major version]/lib. - "/usr/lib/llvm-15/lib".into(), - "/usr/lib/llvm-16/lib".into(), - "/usr/lib/llvm-19/lib".into(), - // Fedora puts libclang in /usr/lib64/llvm[llvm major version]/lib as well as /usr/lib64. - "/usr/lib64/llvm15/lib".into(), - "/usr/lib64/llvm16/lib".into(), - "/usr/lib64/llvm17/lib".into(), - "/usr/lib64/llvm18/lib".into(), - "/usr/lib64/llvm19/lib".into(), - "/usr/lib64/llvm20/lib".into(), - // Gentoo puts libclang in /usr/lib/llvm/[llvm major version]/lib64. - "/usr/lib/llvm/15/lib64".into(), - "/usr/lib/llvm/16/lib64".into(), - "/usr/lib/llvm/17/lib64".into(), - "/usr/lib/llvm/18/lib64".into(), - "/usr/lib/llvm/19/lib64".into(), - "/usr/lib/llvm/20/lib64".into(), "/lib/x86_64-linux-gnu".into(), "/lib/aarch64-linux-gnu".into(), "/app/lib".into(), @@ -177,8 +138,6 @@ fn include_paths() -> Vec { "/usr/include/ffmpeg/libpostproc".into(), "/usr/include/ffmpeg/libswresample".into(), "/usr/include/ffmpeg/libswscale".into(), - // opensuse puts wayland-client.h here - "/usr/include/wayland".into(), ] } @@ -186,10 +145,6 @@ fn udev_rules_paths() -> Vec { vec!["/usr/lib/udev/rules.d".into()] } -fn share_paths() -> Vec { - vec!["/usr/share".into()] -} - #[cfg(test)] mod tests { use super::{DepType, Dependency}; diff --git a/src/depcheck/monado_deps.rs b/src/depcheck/monado_deps.rs index f9e1b9b..f92ffd4 100644 --- a/src/depcheck/monado_deps.rs +++ b/src/depcheck/monado_deps.rs @@ -4,12 +4,9 @@ use super::{ dep_libgl, dep_libudev, dep_libx11, dep_libxcb, dep_ninja, dep_openxr, dep_vulkan_headers, dep_vulkan_icd_loader, }, - DepType, DepcheckResultGetMissing, Dependency, DependencyCheckResult, -}; -use crate::{ - depcheck::common::{dep_glslc, dep_libxrandr}, - linux_distro::LinuxDistro, + DepType, Dependency, DependencyCheckResult, }; +use crate::{depcheck::common::dep_libxrandr, linux_distro::LinuxDistro}; use std::collections::HashMap; fn monado_deps() -> Vec { @@ -33,37 +30,25 @@ fn monado_deps() -> Vec { (LinuxDistro::Suse, "wayland-devel".into()), ]), }, - Dependency { - name: "wayland-protocols".into(), - dep_type: DepType::Share, - filename: "wayland-protocols/staging/drm-lease/drm-lease-v1.xml".into(), - packages: HashMap::from([ - (LinuxDistro::Arch, "wayland-protocols".into()), - (LinuxDistro::Debian, "wayland-protocols".into()), - (LinuxDistro::Fedora, "wayland-protocols-devel".into()), - (LinuxDistro::Gentoo, "dev-libs/wayland-protocols".into()), - (LinuxDistro::Suse, "wayland-protocols-devel".into()), - ]), - }, - Dependency { - name: "libbsd".into(), - dep_type: DepType::SharedObject, - filename: "libbsd.so".into(), - packages: HashMap::from([ - (LinuxDistro::Arch, "libbsd".into()), - (LinuxDistro::Debian, "libbsd-dev".into()), - (LinuxDistro::Fedora, "libbsd-devel".into()), - (LinuxDistro::Gentoo, "dev-libs/libbsd".into()), - (LinuxDistro::Suse, "libbsd-devel".into()), - ]), - }, dep_cmake(), dep_eigen(), dep_git(), dep_ninja(), dep_gcc(), dep_gpp(), - dep_glslc(), + Dependency { + name: "glslc".into(), + dep_type: DepType::Executable, + filename: "glslc".into(), + packages: HashMap::from([ + (LinuxDistro::Arch, "shaderc".into()), + (LinuxDistro::Debian, "glslc".into()), + (LinuxDistro::Fedora, "glslc".into()), + (LinuxDistro::Alpine, "shaderc".into()), + (LinuxDistro::Gentoo, "media-libs/shaderc".into()), + (LinuxDistro::Suse, "shaderc".into()), + ]), + }, dep_glslang_validator(), Dependency { name: "sdl2".into(), @@ -74,34 +59,10 @@ fn monado_deps() -> Vec { (LinuxDistro::Debian, "libsdl2-dev".into()), (LinuxDistro::Fedora, "SDL2-devel".into()), (LinuxDistro::Gentoo, "media-libs/libsdl2".into()), - (LinuxDistro::Suse, "sdl2-compat-devel".into()), + (LinuxDistro::Suse, "SDL2-devel".into()), ]), }, dep_libudev(), - Dependency { - name: "libusb".into(), - dep_type: DepType::SharedObject, - filename: "libusb-1.0.so".into(), - packages: HashMap::from([ - (LinuxDistro::Arch, "libusb".into()), - (LinuxDistro::Debian, "libusb-1.0-0".into()), - (LinuxDistro::Fedora, "libusb1".into()), - (LinuxDistro::Gentoo, "dev-libs/libusb".into()), - (LinuxDistro::Suse, "libusb-1_0-0".into()), - ]), - }, - Dependency { - name: "libusb-dev".into(), - dep_type: DepType::Include, - filename: "libusb-1.0/libusb.h".into(), - packages: HashMap::from([ - (LinuxDistro::Arch, "libusb".into()), - (LinuxDistro::Debian, "libusb-1.0-0-dev".into()), - (LinuxDistro::Fedora, "libusb1-devel".into()), - (LinuxDistro::Gentoo, "dev-libs/libusb".into()), - (LinuxDistro::Suse, "libusb-1_0-devel".into()), - ]), - }, Dependency { name: "mesa-common-dev".into(), dep_type: DepType::Include, @@ -122,5 +83,9 @@ pub fn check_monado_deps() -> Vec { } pub fn get_missing_monado_deps() -> Vec { - check_monado_deps().filter_missing_deps() + check_monado_deps() + .iter() + .filter(|res| !res.found) + .map(|res| res.dependency.clone()) + .collect() } diff --git a/src/depcheck/openhmd_deps.rs b/src/depcheck/openhmd_deps.rs index d31d013..819324f 100644 --- a/src/depcheck/openhmd_deps.rs +++ b/src/depcheck/openhmd_deps.rs @@ -1,6 +1,6 @@ use super::{ common::{dep_gcc, dep_git, dep_gpp, dep_ninja}, - DepcheckResultGetMissing, Dependency, DependencyCheckResult, + Dependency, DependencyCheckResult, }; use crate::linux_distro::LinuxDistro; use std::collections::HashMap; @@ -31,5 +31,9 @@ pub fn check_openhmd_deps() -> Vec { } pub fn get_missing_openhmd_deps() -> Vec { - check_openhmd_deps().filter_missing_deps() + check_openhmd_deps() + .iter() + .filter(|res| !res.found) + .map(|res| res.dependency.clone()) + .collect() } diff --git a/src/depcheck/wivrn_deps.rs b/src/depcheck/wivrn_deps.rs index 9126272..ee430cb 100644 --- a/src/depcheck/wivrn_deps.rs +++ b/src/depcheck/wivrn_deps.rs @@ -4,7 +4,7 @@ use super::{ dep_libudev, dep_libx11, dep_libxcb, dep_ninja, dep_openxr, dep_vulkan_headers, dep_vulkan_icd_loader, }, - DepType, DepcheckResultGetMissing, Dependency, DependencyCheckResult, + DepType, Dependency, DependencyCheckResult, }; use crate::{ depcheck::common::{dep_libgl, dep_libxrandr}, @@ -78,15 +78,15 @@ fn wivrn_deps() -> Vec { ]), }, Dependency { - name: "libpipewire-dev".into(), + name: "libpulse-dev".into(), dep_type: DepType::Include, - filename: "pipewire-0.3/pipewire/pipewire.h".into(), + filename: "pulse/context.h".into(), packages: HashMap::from([ - (LinuxDistro::Arch, "libpipewire".into()), - (LinuxDistro::Debian, "libpipewire-0.3-dev".into()), - (LinuxDistro::Fedora, "pipewire-devel".into()), - (LinuxDistro::Gentoo, "media-video/pipewire".into()), - (LinuxDistro::Suse, "pipewire-devel".into()), + (LinuxDistro::Arch, "libpulse".into()), + (LinuxDistro::Debian, "libpulse-dev".into()), + (LinuxDistro::Fedora, "pulseaudio-libs-devel".into()), + (LinuxDistro::Gentoo, "media-libs/libpulse".into()), + (LinuxDistro::Suse, "libpulse-devel".into()), ]), }, dep_eigen(), @@ -169,10 +169,7 @@ fn wivrn_deps() -> Vec { filename: "pkgconfig/gstreamer-app-1.0.pc".into(), packages: HashMap::from([ (LinuxDistro::Arch, "gst-plugins-base-libs".into()), - ( - LinuxDistro::Debian, - "libgstreamer-plugins-base1.0-dev".into(), - ), + (LinuxDistro::Debian, "libgstreamer1.0-dev".into()), (LinuxDistro::Fedora, "gstreamer1-plugins-base-devel".into()), (LinuxDistro::Gentoo, "media-libs/gst-plugins-base".into()), (LinuxDistro::Suse, "gstreamer-plugins-base-devel".into()), @@ -238,30 +235,6 @@ fn wivrn_deps() -> Vec { (LinuxDistro::Suse, "glib2-devel".into()), ]), }, - Dependency { - name: "openssl-dev".into(), - dep_type: DepType::Include, - filename: "openssl/ssl3.h".into(), - packages: HashMap::from([ - (LinuxDistro::Arch, "openssl".into()), - (LinuxDistro::Alpine, "openssl-dev".into()), - (LinuxDistro::Debian, "libssl-dev".into()), - (LinuxDistro::Fedora, "openssl-devel".into()), - (LinuxDistro::Suse, "openssl-devel".into()), - ]), - }, - Dependency { - name: "libnotify-dev".into(), - dep_type: DepType::Include, - filename: "libnotify/notify.h".into(), - packages: HashMap::from([ - (LinuxDistro::Arch, "libnotify".into()), - (LinuxDistro::Alpine, "libnotify-dev".into()), - (LinuxDistro::Debian, "libnotify-dev".into()), - (LinuxDistro::Fedora, "libnotify-devel".into()), - (LinuxDistro::Suse, "libnotify-devel".into()), - ]), - }, ] } @@ -270,5 +243,9 @@ pub fn check_wivrn_deps() -> Vec { } pub fn get_missing_wivrn_deps() -> Vec { - check_wivrn_deps().filter_missing_deps() + check_wivrn_deps() + .iter() + .filter(|res| !res.found) + .map(|res| res.dependency.clone()) + .collect() } diff --git a/src/depcheck/xrizer_deps.rs b/src/depcheck/xrizer_deps.rs deleted file mode 100644 index 49d9354..0000000 --- a/src/depcheck/xrizer_deps.rs +++ /dev/null @@ -1,65 +0,0 @@ -use super::{DepType, DepcheckResultGetMissing, Dependency, DependencyCheckResult}; -use crate::{depcheck::common::dep_glslc, linux_distro::LinuxDistro}; -use std::collections::HashMap; - -fn xrizer_deps() -> Vec { - vec![ - dep_glslc(), - Dependency { - name: "cargo".into(), - dep_type: DepType::Executable, - filename: "cargo".into(), - packages: HashMap::from([ - (LinuxDistro::Arch, "rust".into()), - (LinuxDistro::Debian, "cargo".into()), - (LinuxDistro::Fedora, "cargo".into()), - (LinuxDistro::Alpine, "cargo".into()), - (LinuxDistro::Suse, "cargo".into()), - ]), - }, - Dependency { - name: "libxcb-glx".into(), - dep_type: DepType::Include, - filename: "xcb/glx.h".into(), - packages: HashMap::from([ - (LinuxDistro::Arch, "libxcb".into()), - (LinuxDistro::Debian, "libxcb-glx0-dev".into()), - (LinuxDistro::Fedora, "libxcb-devel".into()), - (LinuxDistro::Gentoo, "x11-libs/libxcb".into()), - (LinuxDistro::Suse, "libxcb-devel".into()), - ]), - }, - Dependency { - name: "libclang".into(), - dep_type: DepType::SharedObject, - filename: "libclang.so".into(), - packages: HashMap::from([ - (LinuxDistro::Arch, "clang".into()), - (LinuxDistro::Debian, "libclang-19-dev".into()), - (LinuxDistro::Fedora, "clang19-devel".into()), - (LinuxDistro::Gentoo, "llvm-core/clang-runtime".into()), - (LinuxDistro::Suse, "clang19-devel".into()), - ]), - }, - Dependency { - name: "wayland-dev".into(), - dep_type: DepType::Include, - filename: "wayland-client.h".into(), - packages: HashMap::from([ - (LinuxDistro::Arch, "wayland".into()), - (LinuxDistro::Debian, "libwayland-dev".into()), - (LinuxDistro::Fedora, "wayland-devel".into()), - (LinuxDistro::Gentoo, "dev-libs/wayland".into()), - (LinuxDistro::Suse, "wayland-devel".into()), - ]), - }, - ] -} - -pub fn check_xrizer_deps() -> Vec { - Dependency::check_many(xrizer_deps()) -} - -pub fn get_missing_xrizer_deps() -> Vec { - check_xrizer_deps().filter_missing_deps() -} diff --git a/src/env_var_descriptions.rs b/src/env_var_descriptions.rs index ef92101..34e646d 100644 --- a/src/env_var_descriptions.rs +++ b/src/env_var_descriptions.rs @@ -3,7 +3,7 @@ use lazy_static::lazy_static; fn env_var_descriptions() -> Vec<(&'static str, &'static str)> { vec![ ( - "XRT_COMPOSITOR_SCALE_PERCENTAGE", + "XRT_COMPOSITOR_SCALE_PECENTAGE", "Render resolution percentage. A percentage higher than the native resolution (>100) will help with antialiasing and image clarity." ), ( diff --git a/src/file_builders/active_runtime_json.rs b/src/file_builders/active_runtime_json.rs index e637385..3f5e73f 100644 --- a/src/file_builders/active_runtime_json.rs +++ b/src/file_builders/active_runtime_json.rs @@ -1,17 +1,14 @@ use crate::{ - paths::SYSTEM_PREFIX, + paths::{get_backup_dir, SYSTEM_PREFIX}, profile::Profile, - util::file_utils::{deserialize_file, get_writer, set_file_readonly}, + util::file_utils::{copy_file, deserialize_file, get_writer, set_file_readonly}, xdg::XDG, }; -use anyhow::bail; use serde::{Deserialize, Serialize}; use std::{ - fs::{create_dir_all, remove_file, rename}, - os::unix::fs::symlink, + fs::remove_file, path::{Path, PathBuf}, }; -use tracing::{debug, warn}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ActiveRuntimeInnerRuntime { @@ -37,6 +34,29 @@ fn get_active_runtime_json_path() -> PathBuf { get_openxr_conf_dir().join("1/active_runtime.json") } +pub fn is_steam(active_runtime: &ActiveRuntime) -> bool { + matches!(active_runtime.runtime.valve_runtime_is_steamvr, Some(true)) +} + +fn get_backup_steam_active_runtime_path() -> PathBuf { + get_backup_dir().join("active_runtime.json.steam.bak") +} + +fn get_backed_up_steam_active_runtime() -> Option { + get_active_runtime_from_path(&get_backup_steam_active_runtime_path()) +} + +fn backup_steam_active_runtime() { + if let Some(ar) = get_current_active_runtime() { + if is_steam(&ar) { + copy_file( + &get_active_runtime_json_path(), + &get_backup_steam_active_runtime_path(), + ); + } + } +} + fn get_active_runtime_from_path(path: &Path) -> Option { deserialize_file(path) } @@ -58,6 +78,29 @@ pub fn dump_current_active_runtime(active_runtime: &ActiveRuntime) -> anyhow::Re dump_active_runtime_to_path(active_runtime, &get_active_runtime_json_path()) } +fn build_steam_active_runtime() -> ActiveRuntime { + if let Some(backup) = get_backed_up_steam_active_runtime() { + return backup; + } + ActiveRuntime { + file_format_version: "1.0.0".into(), + runtime: ActiveRuntimeInnerRuntime { + valve_runtime_is_steamvr: Some(true), + libmonado_path: None, + library_path: XDG + .get_data_home() + .join("Steam/steamapps/common/SteamVR/bin/linux64/vrclient.so"), + name: Some("SteamVR".into()), + }, + } +} + +pub fn set_current_active_runtime_to_steam() -> anyhow::Result<()> { + set_file_readonly(&get_active_runtime_json_path(), false)?; + dump_current_active_runtime(&build_steam_active_runtime())?; + Ok(()) +} + pub fn build_profile_active_runtime(profile: &Profile) -> anyhow::Result { let Some(libopenxr_path) = profile.libopenxr_so() else { anyhow::bail!( @@ -94,67 +137,18 @@ fn relativize_active_runtime_lib_path(ar: &ActiveRuntime, path: &Path) -> Active res } -const ACTIVE_RUNTIME_BAK: &str = "active_runtime.json.envision.bak"; - pub fn set_current_active_runtime_to_profile(profile: &Profile) -> anyhow::Result<()> { let dest = get_active_runtime_json_path(); - if dest.is_dir() { - bail!("{} is a directory", dest.to_string_lossy()); + set_file_readonly(&dest, false)?; + backup_steam_active_runtime(); + let pfx = profile.clone().prefix; + let mut ar = build_profile_active_runtime(profile)?; + // hack: relativize libopenxr_monado.so path for system installs + if pfx == PathBuf::from(SYSTEM_PREFIX) { + ar = relativize_active_runtime_lib_path(&ar, &dest); } - if !dest.is_symlink() { - set_file_readonly(&dest, false)?; - } - if dest.is_file() || dest.is_symlink() { - rename(&dest, dest.parent().unwrap().join(ACTIVE_RUNTIME_BAK))?; - } else { - debug!("no active_runtime.json file to backup") - } - - let profile_openxr_json = profile.openxr_json_path(); - if profile_openxr_json.is_file() { - create_dir_all(dest.parent().unwrap())?; - symlink(profile_openxr_json, &dest)?; - } else { - warn!("profile openxr json file doesn't exist"); - // fallback: build the file from scratch - let pfx = profile.clone().prefix; - let mut ar = build_profile_active_runtime(profile)?; - // hack: relativize libopenxr_monado.so path for system installs - if pfx == PathBuf::from(SYSTEM_PREFIX) { - ar = relativize_active_runtime_lib_path(&ar, &dest); - } - dump_current_active_runtime(&ar)?; - set_file_readonly(&dest, true)?; - } - Ok(()) -} - -pub fn remove_current_active_runtime() -> anyhow::Result<()> { - let dest = get_active_runtime_json_path(); - if dest.is_dir() { - bail!("{} is a directory", dest.to_string_lossy()); - } - if !dest.exists() { - debug!("no current active_runtime.json to remove") - } - Ok(remove_file(dest)?) -} - -pub fn restore_active_runtime_backup() -> anyhow::Result<()> { - let dest = get_active_runtime_json_path(); - let bak = dest.parent().unwrap().join(ACTIVE_RUNTIME_BAK); - if bak.is_file() || bak.is_symlink() { - if dest.is_dir() { - bail!("{} is a directory", dest.to_string_lossy()); - } - if !dest.is_symlink() { - set_file_readonly(&dest, false)?; - } - rename(&bak, &dest)?; - } else { - debug!("{ACTIVE_RUNTIME_BAK} does not exist, nothing to restore"); - } - + dump_current_active_runtime(&ar)?; + set_file_readonly(&dest, true)?; Ok(()) } diff --git a/src/file_builders/mod.rs b/src/file_builders/mod.rs index 33c9829..b6078ae 100644 --- a/src/file_builders/mod.rs +++ b/src/file_builders/mod.rs @@ -1,6 +1,5 @@ pub mod active_runtime_json; pub mod monado_autorun; pub mod openvrpaths_vrpath; -pub mod wayvr_dashboard_config; pub mod wivrn_config; pub mod wivrn_encoder_presets; diff --git a/src/file_builders/openvrpaths_vrpath.rs b/src/file_builders/openvrpaths_vrpath.rs index 1b888c2..1108717 100644 --- a/src/file_builders/openvrpaths_vrpath.rs +++ b/src/file_builders/openvrpaths_vrpath.rs @@ -1,3 +1,5 @@ +use std::path::{Path, PathBuf}; + use crate::{ paths::get_backup_dir, profile::Profile, @@ -5,7 +7,6 @@ use crate::{ xdg::XDG, }; use serde::{ser::Error, Deserialize, Serialize}; -use std::path::{Path, PathBuf}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct OpenVrPaths { @@ -85,7 +86,6 @@ fn build_steam_openvrpaths() -> OpenVrPaths { } pub fn set_current_openvrpaths_to_steam() -> anyhow::Result<()> { - // removing readonly flag just in case, remove this line in the future set_file_readonly(&get_openvrpaths_vrpath_path(), false)?; dump_current_openvrpaths(&build_steam_openvrpaths())?; Ok(()) @@ -98,25 +98,26 @@ pub fn build_profile_openvrpaths(profile: &Profile) -> OpenVrPaths { external_drivers: None, jsonid: "vrpathreg".into(), log: vec![datadir.join("Steam/logs")], - runtime: vec![profile.ovr_comp.runtime_dir()], + runtime: vec![profile.opencomposite_path.join("build")], version: 1, } } pub fn set_current_openvrpaths_to_profile(profile: &Profile) -> anyhow::Result<()> { let dest = get_openvrpaths_vrpath_path(); - // removing readonly flag just in case, remove this line in the future set_file_readonly(&dest, false)?; backup_steam_openvrpaths(); dump_current_openvrpaths(&build_profile_openvrpaths(profile))?; + set_file_readonly(&dest, true)?; Ok(()) } #[cfg(test)] mod tests { - use super::{dump_openvrpaths_to_path, get_openvrpaths_from_path, OpenVrPaths}; use std::path::Path; + use super::{dump_openvrpaths_to_path, get_openvrpaths_from_path, OpenVrPaths}; + #[test] fn can_read_openvrpaths_vrpath_steamvr() { let ovrp = get_openvrpaths_from_path(Path::new("./test/files/openvrpaths.vrpath")).unwrap(); diff --git a/src/file_builders/wayvr_dashboard_config.rs b/src/file_builders/wayvr_dashboard_config.rs deleted file mode 100644 index 6e9315c..0000000 --- a/src/file_builders/wayvr_dashboard_config.rs +++ /dev/null @@ -1,13 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct WayVrDashboardConfigFragmentInner { - pub exec: String, - pub args: Option, - pub env: Option>, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct WayVrDashboardConfigFragment { - pub dashboard: WayVrDashboardConfigFragmentInner, -} diff --git a/src/linux_distro.rs b/src/linux_distro.rs index 2fcd76a..7bdfe0b 100644 --- a/src/linux_distro.rs +++ b/src/linux_distro.rs @@ -49,13 +49,13 @@ impl LinuxDistro { Ok(_) if buf.starts_with("PRETTY_NAME=\"") => { return buf .split('=') - .next_back() + .last() .map(|b| b.trim().trim_matches('"').trim().to_string()); } Ok(_) if buf.starts_with("NAME=\"") => { name = buf .split('=') - .next_back() + .last() .map(|b| b.trim().trim_matches('"').trim().to_string()); } _ => {} @@ -79,7 +79,7 @@ impl LinuxDistro { { let name = buf .split('=') - .next_back() + .last() .unwrap_or_default() .trim() .trim_matches('"') @@ -115,7 +115,6 @@ impl LinuxDistro { || s.contains("steamos") || s.contains("steam os") || s.contains("endeavour") - || s.contains("cachyos") || s.contains("garuda") { return Some(Self::Arch); @@ -151,33 +150,7 @@ impl LinuxDistro { Self::Alpine => format!("sudo apk add {}", packages.join(" ")), Self::Debian => format!("sudo apt install {}", packages.join(" ")), Self::Gentoo => format!("sudo emerge -av {}", packages.join(" ")), - Self::Suse => { - let mut opi_pkgs = Vec::new(); - let mut zypper_pkgs = Vec::new(); - for pkg in packages { - if ["OpenXR-SDK-devel"].contains(&pkg.as_str()) { - opi_pkgs.push(pkg.clone()); - } else { - zypper_pkgs.push(pkg.clone()); - } - } - [ - if opi_pkgs.is_empty() { - None - } else { - Some(format!("opi {}", opi_pkgs.join(" "))) - }, - if zypper_pkgs.is_empty() { - None - } else { - Some(format!("sudo zypper install {}", zypper_pkgs.join(" "))) - }, - ] - .iter() - .filter_map(|c| c.clone()) - .collect::>() - .join(" && ") - } + Self::Suse => format!("sudo zypper install {}", packages.join(" ")), Self::Fedora => { let mut install_rpmfusion_cmd: Option = None; let mut swap_ffmpeg_cmd: Option = None; @@ -217,10 +190,10 @@ impl LinuxDistro { #[cfg(test)] mod tests { - use super::LinuxDistro; - use crate::depcheck::common::{dep_openxr, dep_pkexec, dep_vulkan_icd_loader}; use std::path::Path; + use super::LinuxDistro; + #[test] fn can_detect_arch_linux_from_etc_os_release() { assert_eq!( @@ -230,34 +203,4 @@ mod tests { Some(LinuxDistro::Arch) ) } - - #[test] - fn can_account_for_opensuse_opi_packages() { - assert_eq!( - LinuxDistro::Suse - .install_command( - &[dep_openxr(), dep_vulkan_icd_loader()] - .iter() - .map(|dep| dep.package_name_for_distro(Some(&LinuxDistro::Suse))) - .collect::>() - ) - .as_str(), - "opi OpenXR-SDK-devel && sudo zypper install vulkan-devel" - ) - } - - #[test] - fn opensuse_opi_does_not_interfere_if_not_needed() { - assert_eq!( - LinuxDistro::Suse - .install_command( - &[dep_pkexec(), dep_vulkan_icd_loader()] - .iter() - .map(|dep| dep.package_name_for_distro(Some(&LinuxDistro::Suse))) - .collect::>() - ) - .as_str(), - "sudo zypper install polkit vulkan-devel" - ) - } } diff --git a/src/main.rs b/src/main.rs index 217434e..2185480 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,27 +1,17 @@ use anyhow::Result; use constants::{resources, APP_ID, APP_NAME, GETTEXT_PACKAGE, LOCALE_DIR, RESOURCES_BASE_PATH}; use file_builders::{ - active_runtime_json::restore_active_runtime_backup, + active_runtime_json::{get_current_active_runtime, set_current_active_runtime_to_steam}, openvrpaths_vrpath::{get_current_openvrpaths, set_current_openvrpaths_to_steam}, }; use gettextrs::LocaleCategory; -use paths::get_logs_dir; use relm4::{ adw, gtk::{self, gdk, gio, glib, prelude::*}, MessageBroker, RelmApp, }; -use std::{ - env, - fs::{read_dir, remove_file}, - os::unix::fs::MetadataExt, - path::{Path, PathBuf}, -}; +use std::env; use steam_linux_runtime_injector::restore_runtime_entrypoint; -use tracing::{error, warn}; -use tracing_subscriber::{ - filter::LevelFilter, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer, -}; use ui::{ app::{App, AppInit, Msg}, cmdline_opts::CmdLineOpts, @@ -32,7 +22,6 @@ pub mod build_tools; pub mod builders; pub mod cmd_runner; pub mod config; -#[rustfmt::skip] pub mod constants; pub mod depcheck; pub mod device_prober; @@ -57,91 +46,32 @@ pub mod xdg; pub mod xr_devices; fn restore_steam_xr_files() { + let active_runtime = get_current_active_runtime(); let openvrpaths = get_current_openvrpaths(); - if let Err(e) = restore_active_runtime_backup() { - warn!("failed to restore active runtime to steam: {e}"); + if let Some(ar) = active_runtime { + if !file_builders::active_runtime_json::is_steam(&ar) { + match set_current_active_runtime_to_steam() { + Ok(_) => {} + Err(e) => eprintln!("Warning: failed to restore active runtime to steam: {e}"), + }; + } } if let Some(ovrp) = openvrpaths { if !file_builders::openvrpaths_vrpath::is_steam(&ovrp) { - if let Err(e) = set_current_openvrpaths_to_steam() { - warn!("failed to restore openvrpaths to steam: {e}"); - } + match set_current_openvrpaths_to_steam() { + Ok(_) => {} + Err(e) => eprintln!("Warning: failed to restore openvrpaths to steam: {e}"), + }; } } restore_runtime_entrypoint(); } -const LOGS_MAX_SIZE_BYTES: u64 = 1000000000; // 1GB - -fn remove_old_logs(dir: &Path, log_files: Option>) -> anyhow::Result<()> { - let log_files: Vec = log_files - .map::>, _>(Ok) - .unwrap_or_else(|| { - let mut files: Vec = read_dir(dir)? - .filter_map(|de| { - let p = de.ok()?.path(); - if p.is_file() && !p.is_symlink() { - Some(p) - } else { - None - } - }) - .collect(); - files.sort_unstable(); - Ok(files) - })?; - let total_size = log_files - .iter() - .filter_map(|p| Some(p.metadata().ok()?.size())) - .reduce(u64::saturating_add) - .unwrap_or(0); - // if size is under threshold, finish - if total_size < LOGS_MAX_SIZE_BYTES { - return Ok(()); - } - // keep a minimum of 3 logs - if log_files.len() <= 3 { - return Ok(()); - } - - remove_file(log_files.first().ok_or_else(|| - anyhow::Error::msg( - "Could not get first item in log files list, but they should be more than 3! This is a bug!" - ) - )?)?; - - remove_old_logs(dir, Some(log_files)) -} - fn main() -> Result<()> { if env::var("USER").unwrap_or_else(|_| env::var("USERNAME").unwrap_or_default()) == "root" { panic!("{APP_NAME} cannot run as root"); } restore_steam_xr_files(); - // deferring error logging for this since tracing isn't initialized yet - let old_logs_removal_res = remove_old_logs(&get_logs_dir(), None); - - let rolling_log_writer = tracing_appender::rolling::daily(get_logs_dir(), "log"); - let (non_blocking_appender, _appender_guard) = - tracing_appender::non_blocking(rolling_log_writer); - tracing_subscriber::registry() - .with( - tracing_subscriber::fmt::layer().pretty().with_filter( - EnvFilter::builder() - .with_default_directive(LevelFilter::INFO.into()) - .from_env_lossy(), - ), - ) - .with( - tracing_subscriber::fmt::layer() - .json() - .with_writer(non_blocking_appender), - ) - .init(); - - if let Err(e) = old_logs_removal_res { - error!("Failed to remove old log files: {e}"); - } // Prepare i18n gettextrs::setlocale(LocaleCategory::LcAll, ""); diff --git a/src/meson.build b/src/meson.build index 9d4cce9..133b83f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -3,7 +3,7 @@ config = configure_file( output: 'constants.rs', configuration: global_conf ) -# Copy the constants.rs output to the source directory. +# Copy the config.rs output to the source directory. run_command( 'cp', meson.project_build_root() / 'src' / 'constants.rs', @@ -43,24 +43,3 @@ cargo_build = custom_target( 'cp', 'src' / rust_target / meson.project_name(), '@OUTPUT@', ] ) - -test( - 'cargo-fmt-check', - cargo, - args: ['fmt', '--all', '--check'] -) - -test( - 'cargo-clippy', - cargo, - env: ['RUSTFLAGS=-Dwarnings'], - args: ['clippy', '--all-targets', '--all-features'], - timeout: 0, -) - -test( - 'cargo-test', - cargo, - args: ['test'], - timeout: 0, -) diff --git a/src/paths.rs b/src/paths.rs index 6384380..06b7575 100644 --- a/src/paths.rs +++ b/src/paths.rs @@ -1,6 +1,4 @@ -use anyhow::bail; - -use crate::{constants::CMD_NAME, util::steam_library_folder::SteamLibraryFolder, xdg::XDG}; +use crate::{constants::CMD_NAME, xdg::XDG}; use std::{ env, fs::create_dir_all, @@ -56,10 +54,6 @@ pub fn get_cache_dir() -> PathBuf { XDG.get_cache_home().join(CMD_NAME) } -pub fn get_logs_dir() -> PathBuf { - get_cache_dir().join("logs") -} - pub fn get_backup_dir() -> PathBuf { let p = get_data_dir().join("backups"); if !p.is_dir() { @@ -89,26 +83,7 @@ pub fn get_exec_prefix() -> PathBuf { .into() } -const STEAMVR_STEAM_APPID: u32 = 250820; - -fn get_steamvr_base_dir() -> anyhow::Result { - SteamLibraryFolder::get_folders()? - .into_iter() - .find(|(_, lf)| lf.apps.contains_key(&STEAMVR_STEAM_APPID)) - .map(|(_, lf)| PathBuf::from(lf.path).join("steamapps/common/SteamVR")) - .ok_or(anyhow::Error::msg( - "Could not find SteamVR in Steam libraryfolders.vdf", - )) -} - -pub fn get_steamvr_bin_dir_path() -> anyhow::Result { - let res = get_steamvr_base_dir()?.join("bin/linux64"); - if !res.is_dir() { - bail!("SteamVR bin dir `{}` does not exist", res.to_string_lossy()); - } - Ok(res) -} - -pub fn get_plugins_dir() -> PathBuf { - get_data_dir().join("plugins") +pub fn get_steamvr_bin_dir_path() -> PathBuf { + XDG.get_data_home() + .join("Steam/steamapps/common/SteamVR/bin/linux64") } diff --git a/src/profile.rs b/src/profile.rs index 2666291..a0a3903 100644 --- a/src/profile.rs +++ b/src/profile.rs @@ -2,12 +2,10 @@ use crate::{ depcheck::{ basalt_deps::get_missing_basalt_deps, libsurvive_deps::get_missing_libsurvive_deps, mercury_deps::get_missing_mercury_deps, monado_deps::get_missing_monado_deps, - openhmd_deps::get_missing_openhmd_deps, wivrn_deps::get_missing_wivrn_deps, - xrizer_deps::get_missing_xrizer_deps, Dependency, + openhmd_deps::get_missing_openhmd_deps, wivrn_deps::get_missing_wivrn_deps, Dependency, }, - file_builders::active_runtime_json::ActiveRuntime, paths::{get_data_dir, BWRAP_SYSTEM_PREFIX, SYSTEM_PREFIX}, - util::file_utils::{deserialize_file, get_writer}, + util::file_utils::get_writer, xdg::XDG, }; use nix::NixPath; @@ -15,11 +13,10 @@ use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, fmt::Display, - fs::{remove_dir_all, File}, + fs::File, io::BufReader, path::{Path, PathBuf}, slice::Iter, - str::FromStr, }; use uuid::Uuid; @@ -46,14 +43,7 @@ impl XRServiceType { pub fn libmonado_path(&self) -> &'static str { match self { Self::Monado => "libmonado.so", - Self::Wivrn => "wivrn/libmonado_wivrn.so", - } - } - - pub fn openxr_json_rel_path(&self) -> &'static str { - match self { - Self::Monado => "share/openxr/1/openxr_monado.json", - Self::Wivrn => "share/openxr/1/openxr_wivrn.json", + Self::Wivrn => "wivrn/libmonado.so", } } @@ -261,105 +251,6 @@ impl Display for LighthouseDriver { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] -pub enum OvrCompatibilityModuleType { - #[default] - Opencomposite, - Xrizer, - Vapor, -} - -impl Display for OvrCompatibilityModuleType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - Self::Opencomposite => "OpenComposite", - Self::Xrizer => "xrizer", - Self::Vapor => "VapoR", - }) - } -} - -impl OvrCompatibilityModuleType { - pub fn iter() -> Iter<'static, Self> { - [Self::Opencomposite, Self::Xrizer, Self::Vapor].iter() - } - - pub fn get_missing_deps(&self) -> Vec { - match self { - OvrCompatibilityModuleType::Xrizer => get_missing_xrizer_deps(), - OvrCompatibilityModuleType::Opencomposite | OvrCompatibilityModuleType::Vapor => { - Vec::default() - } - } - } -} - -impl FromStr for OvrCompatibilityModuleType { - type Err = String; - - fn from_str(s: &str) -> Result { - match s.to_lowercase().trim() { - "opencomposite" => Ok(Self::Opencomposite), - "xrizer" => Ok(Self::Xrizer), - "vapor" => Ok(Self::Vapor), - _ => Err(format!("no match for ovr compatibility module `{s}`")), - } - } -} - -impl From for OvrCompatibilityModuleType { - fn from(value: u32) -> Self { - match value { - 0 => Self::Opencomposite, - 1 => Self::Xrizer, - 2 => Self::Vapor, - _ => panic!("OvrCompatibilityModuleType index out of bounds"), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct ProfileOvrCompatibilityModule { - pub mod_type: OvrCompatibilityModuleType, - pub repo: Option, - pub branch: Option, - pub path: PathBuf, -} - -impl ProfileOvrCompatibilityModule { - pub fn default_for_uuid(uuid: &str) -> Self { - let mod_type = OvrCompatibilityModuleType::default(); - Self { - mod_type, - repo: None, - branch: None, - path: get_data_dir().join(uuid).join(mod_type.to_string()), - } - } - - /// get the directory corresponding to the openvr runtime. - /// this should correspond to the build output directory - pub fn runtime_dir(&self) -> PathBuf { - match self.mod_type { - OvrCompatibilityModuleType::Opencomposite => self.path.join("build"), - OvrCompatibilityModuleType::Vapor => self.path.join("build/install_pfx/lib/VapoR"), - OvrCompatibilityModuleType::Xrizer => self.path.join("target/release"), - } - } -} - -impl Default for ProfileOvrCompatibilityModule { - fn default() -> Self { - let mod_type = OvrCompatibilityModuleType::default(); - Self { - mod_type, - repo: None, - branch: None, - path: get_data_dir().join("__envision__fallbackovrcomp"), - } - } -} - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Profile { pub uuid: String, @@ -370,15 +261,9 @@ pub struct Profile { pub xrservice_branch: Option, #[serde(default = "HashMap::::default")] pub xrservice_cmake_flags: HashMap, - #[deprecated] - #[serde(default)] pub opencomposite_path: PathBuf, - #[deprecated] pub opencomposite_repo: Option, - #[deprecated] pub opencomposite_branch: Option, - #[serde(default)] - pub ovr_comp: ProfileOvrCompatibilityModule, pub features: ProfileFeatures, pub environment: HashMap, /// Install prefix @@ -391,6 +276,7 @@ pub struct Profile { pub lighthouse_driver: LighthouseDriver, #[serde(default = "String::default")] pub xrservice_launch_options: String, + pub autostart_command: Option, #[serde(default)] pub skip_dependency_check: bool, } @@ -402,7 +288,6 @@ impl Display for Profile { } impl Default for Profile { - #[allow(deprecated)] fn default() -> Self { let uuid = Self::new_uuid(); let profile_dir = get_data_dir().join(&uuid); @@ -438,50 +323,23 @@ impl Default for Profile { mercury_enabled: false, }, environment: HashMap::new(), - prefix: Self::default_prefix_path(&uuid), + prefix: get_data_dir().join("prefixes").join(&uuid), can_be_built: true, pull_on_build: true, opencomposite_path: profile_dir.join("opencomposite"), opencomposite_repo: None, opencomposite_branch: None, - ovr_comp: ProfileOvrCompatibilityModule::default_for_uuid(&uuid), editable: true, lighthouse_driver: LighthouseDriver::default(), xrservice_launch_options: String::default(), uuid, + autostart_command: None, skip_dependency_check: false, } } } impl Profile { - fn default_prefix_path(uuid: &str) -> PathBuf { - get_data_dir().join("prefixes").join(uuid) - } - - /// deletes files and folders associated to this profile (mostly repo clones) - pub fn delete_files(&self) -> Vec> { - [ - Some(&self.xrservice_path), - Some(&self.ovr_comp.path), - self.features.libsurvive.path.as_ref(), - self.features.basalt.path.as_ref(), - self.features.openhmd.path.as_ref(), - ] - .iter() - .map(|dir| match dir { - Some(dir) => { - if dir.try_exists().unwrap_or_default() { - remove_dir_all(dir) - } else { - Ok(()) - } - } - None => Ok(()), - }) - .collect() - } - pub fn xr_runtime_json_env_var(&self) -> String { format!( "XR_RUNTIME_JSON=\"{prefix}/share/openxr/1/openxr_{runtime}.json\"", @@ -500,8 +358,8 @@ impl Profile { pub fn env_vars_full(&self) -> Vec { vec![ // format!( - // "VR_OVERRIDE={}", - // self.ovr_comp.runtime_dir(), + // "VR_OVERRIDE={opencomp}/build", + // opencomp = self.opencomposite_path, // ), self.xr_runtime_json_env_var(), format!( @@ -559,8 +417,8 @@ impl Profile { } let uuid = Self::new_uuid(); let profile_dir = get_data_dir().join(&uuid); - #[allow(deprecated)] let mut dup = Self { + uuid, name: format!("Duplicate of {}", self.name), xrservice_type: self.xrservice_type.clone(), xrservice_repo: self.xrservice_repo.clone(), @@ -592,6 +450,7 @@ impl Profile { mercury_enabled: self.features.mercury_enabled, }, environment: self.environment.clone(), + autostart_command: self.autostart_command.clone(), pull_on_build: self.pull_on_build, lighthouse_driver: self.lighthouse_driver, opencomposite_repo: self.opencomposite_repo.clone(), @@ -599,16 +458,7 @@ impl Profile { opencomposite_path: profile_dir.join("opencomposite"), skip_dependency_check: self.skip_dependency_check, xrservice_launch_options: self.xrservice_launch_options.clone(), - prefix: Self::default_prefix_path(&uuid), - ovr_comp: ProfileOvrCompatibilityModule { - mod_type: self.ovr_comp.mod_type, - repo: self.ovr_comp.repo.clone(), - branch: self.ovr_comp.branch.clone(), - path: profile_dir.join(self.ovr_comp.mod_type.to_string()), - }, - can_be_built: self.can_be_built, - editable: true, - uuid, + ..Default::default() }; if dup.environment.contains_key("LD_LIBRARY_PATH") { dup.environment.insert( @@ -694,37 +544,21 @@ impl Profile { } /// absolute path to a given shared object in the profile prefix - pub fn find_so>(&self, rel_path: P) -> Option { + pub fn find_so(&self, rel_path: &str) -> Option { ["lib", "lib64"] .into_iter() - .map(|lib| self.prefix.join(lib).join(rel_path.as_ref())) + .map(|lib| self.prefix.join(lib).join(rel_path)) .find(|path| path.is_file()) } /// absolute path to the libmonado shared object pub fn libmonado_so(&self) -> Option { - // try by reading the openxr json file - self.openxr_config() - .and_then(|conf| conf.runtime.libmonado_path) - .and_then(|libmonado_path| self.find_so(&libmonado_path)) - .or_else(|| - // try with the hardcoded paths - self.find_so(self.xrservice_type.libmonado_path())) - } - - fn openxr_config(&self) -> Option { - deserialize_file(&self.openxr_json_path()) + self.find_so(self.xrservice_type.libmonado_path()) } /// absolute path to the libopenxr shared object pub fn libopenxr_so(&self) -> Option { - // try by reading the openxr json file - self.openxr_config() - .map(|conf| conf.runtime.library_path) - .and_then(|libmonado_path| self.find_so(&libmonado_path)) - .or_else(|| - // try with the hardcoded paths - self.find_so(self.xrservice_type.libopenxr_path())) + self.find_so(self.xrservice_type.libopenxr_path()) } pub fn missing_dependencies(&self) -> Vec { @@ -746,18 +580,12 @@ impl Profile { if self.features.mercury_enabled { missing_deps.extend(get_missing_mercury_deps()); } - missing_deps.extend(self.ovr_comp.mod_type.get_missing_deps()); + // no listed deps for opencomp } missing_deps.sort_unstable(); missing_deps.dedup(); // dedup only works if sorted, hence the above missing_deps } - - /// the file that will become active_runtime.json, as installed in the - /// prefix - pub fn openxr_json_path(&self) -> PathBuf { - self.prefix.join(self.xrservice_type.openxr_json_rel_path()) - } } pub fn prepare_ld_library_path(prefix: &Path) -> String { @@ -771,10 +599,7 @@ mod tests { path::{Path, PathBuf}, }; - use crate::profile::{ - OvrCompatibilityModuleType, ProfileFeature, ProfileFeatureType, ProfileFeatures, - ProfileOvrCompatibilityModule, XRServiceType, - }; + use crate::profile::{ProfileFeature, ProfileFeatureType, ProfileFeatures, XRServiceType}; use super::Profile; @@ -784,7 +609,7 @@ mod tests { assert_eq!(profile.name, "Demo profile"); assert_eq!(profile.xrservice_path, PathBuf::from("/home/user/monado")); assert_eq!( - profile.ovr_comp.path, + profile.opencomposite_path, PathBuf::from("/home/user/opencomposite") ); assert_eq!(profile.prefix, PathBuf::from("/home/user/envisionprefix")); @@ -815,12 +640,7 @@ mod tests { name: "Demo profile".into(), xrservice_path: PathBuf::from("/home/user/monado"), xrservice_type: XRServiceType::Monado, - ovr_comp: ProfileOvrCompatibilityModule { - path: PathBuf::from("/home/user/opencomposite"), - repo: None, - branch: None, - mod_type: OvrCompatibilityModuleType::default(), - }, + opencomposite_path: PathBuf::from("/home/user/opencomposite"), features: ProfileFeatures { libsurvive: ProfileFeature { feature_type: ProfileFeatureType::Libsurvive, diff --git a/src/profiles/lighthouse.rs b/src/profiles/lighthouse.rs index 97f3a1f..794ee3e 100644 --- a/src/profiles/lighthouse.rs +++ b/src/profiles/lighthouse.rs @@ -1,10 +1,7 @@ use crate::{ constants::APP_NAME, paths::{data_monado_path, data_opencomposite_path, get_data_dir}, - profile::{ - prepare_ld_library_path, LighthouseDriver, Profile, ProfileFeatures, - ProfileOvrCompatibilityModule, XRServiceType, - }, + profile::{prepare_ld_library_path, LighthouseDriver, Profile, ProfileFeatures, XRServiceType}, }; use std::collections::HashMap; @@ -24,10 +21,7 @@ pub fn lighthouse_profile() -> Profile { name: format!("Lighthouse Driver - {name} Default", name = APP_NAME), xrservice_path: data_monado_path(), xrservice_type: XRServiceType::Monado, - ovr_comp: ProfileOvrCompatibilityModule { - path: data_opencomposite_path(), - ..Default::default() - }, + opencomposite_path: data_opencomposite_path(), features: ProfileFeatures::default(), environment, prefix, diff --git a/src/profiles/openhmd.rs b/src/profiles/openhmd.rs index 8709725..b045c1e 100644 --- a/src/profiles/openhmd.rs +++ b/src/profiles/openhmd.rs @@ -3,7 +3,7 @@ use crate::{ paths::{data_monado_path, data_opencomposite_path, data_openhmd_path, get_data_dir}, profile::{ prepare_ld_library_path, LighthouseDriver, Profile, ProfileFeature, ProfileFeatureType, - ProfileFeatures, ProfileOvrCompatibilityModule, XRServiceType, + ProfileFeatures, XRServiceType, }, }; use std::collections::HashMap; @@ -24,10 +24,7 @@ pub fn openhmd_profile() -> Profile { name: format!("OpenHMD - {name} Default", name = APP_NAME), xrservice_path: data_monado_path(), xrservice_type: XRServiceType::Monado, - ovr_comp: ProfileOvrCompatibilityModule { - path: data_opencomposite_path(), - ..Default::default() - }, + opencomposite_path: data_opencomposite_path(), features: ProfileFeatures { openhmd: ProfileFeature { feature_type: ProfileFeatureType::OpenHmd, diff --git a/src/profiles/simulated.rs b/src/profiles/simulated.rs index a39a66b..2283caf 100644 --- a/src/profiles/simulated.rs +++ b/src/profiles/simulated.rs @@ -1,7 +1,7 @@ use crate::{ constants::APP_NAME, paths::{data_monado_path, data_opencomposite_path, get_data_dir}, - profile::{Profile, ProfileFeatures, ProfileOvrCompatibilityModule, XRServiceType}, + profile::{Profile, ProfileFeatures, XRServiceType}, }; use std::collections::HashMap; @@ -25,10 +25,7 @@ pub fn simulated_profile() -> Profile { name: format!("Simulated Driver - {name} Default", name = APP_NAME), xrservice_path: data_monado_path(), xrservice_type: XRServiceType::Monado, - ovr_comp: ProfileOvrCompatibilityModule { - path: data_opencomposite_path(), - ..Default::default() - }, + opencomposite_path: data_opencomposite_path(), features: ProfileFeatures::default(), environment, prefix, diff --git a/src/profiles/survive.rs b/src/profiles/survive.rs index d3e8ff6..481f624 100644 --- a/src/profiles/survive.rs +++ b/src/profiles/survive.rs @@ -3,7 +3,7 @@ use crate::{ paths::{data_libsurvive_path, data_monado_path, data_opencomposite_path, get_data_dir}, profile::{ prepare_ld_library_path, LighthouseDriver, Profile, ProfileFeature, ProfileFeatureType, - ProfileFeatures, ProfileOvrCompatibilityModule, XRServiceType, + ProfileFeatures, XRServiceType, }, }; use std::collections::HashMap; @@ -26,10 +26,7 @@ pub fn survive_profile() -> Profile { name: format!("Survive - {name} Default", name = APP_NAME), xrservice_path: data_monado_path(), xrservice_type: XRServiceType::Monado, - ovr_comp: ProfileOvrCompatibilityModule { - path: data_opencomposite_path(), - ..Default::default() - }, + opencomposite_path: data_opencomposite_path(), features: ProfileFeatures { libsurvive: ProfileFeature { feature_type: ProfileFeatureType::Libsurvive, diff --git a/src/profiles/wivrn.rs b/src/profiles/wivrn.rs index 980e1ed..dd58da9 100644 --- a/src/profiles/wivrn.rs +++ b/src/profiles/wivrn.rs @@ -1,11 +1,7 @@ use crate::{ constants::APP_NAME, paths::{data_opencomposite_path, data_wivrn_path, get_data_dir}, - profile::{ - prepare_ld_library_path, Profile, ProfileFeatures, ProfileOvrCompatibilityModule, - XRServiceType, - }, - ui::job_worker::internal_worker::LAUNCH_OPTS_CMD_PLACEHOLDER, + profile::{prepare_ld_library_path, Profile, ProfileFeatures, XRServiceType}, }; use std::collections::HashMap; @@ -22,16 +18,10 @@ pub fn wivrn_profile() -> Profile { name: format!("WiVRn - {name} Default", name = APP_NAME), xrservice_path: data_wivrn_path(), xrservice_type: XRServiceType::Wivrn, - ovr_comp: ProfileOvrCompatibilityModule { - path: data_opencomposite_path(), - ..Default::default() - }, + opencomposite_path: data_opencomposite_path(), features: ProfileFeatures { ..Default::default() }, - xrservice_launch_options: format!( - "{LAUNCH_OPTS_CMD_PLACEHOLDER} --no-instructions --no-manage-active-runtime" - ), environment, prefix, can_be_built: true, diff --git a/src/profiles/wmr.rs b/src/profiles/wmr.rs index c332f6a..23e238d 100644 --- a/src/profiles/wmr.rs +++ b/src/profiles/wmr.rs @@ -3,7 +3,7 @@ use crate::{ paths::{data_basalt_path, data_monado_path, data_opencomposite_path, get_data_dir}, profile::{ prepare_ld_library_path, LighthouseDriver, Profile, ProfileFeature, ProfileFeatureType, - ProfileFeatures, ProfileOvrCompatibilityModule, XRServiceType, + ProfileFeatures, XRServiceType, }, }; use std::collections::HashMap; @@ -24,10 +24,7 @@ pub fn wmr_profile() -> Profile { name: format!("WMR - {name} Default", name = APP_NAME), xrservice_path: data_monado_path(), xrservice_type: XRServiceType::Monado, - ovr_comp: ProfileOvrCompatibilityModule { - path: data_opencomposite_path(), - ..Default::default() - }, + opencomposite_path: data_opencomposite_path(), features: ProfileFeatures { basalt: ProfileFeature { feature_type: ProfileFeatureType::Basalt, diff --git a/src/steam_linux_runtime_injector.rs b/src/steam_linux_runtime_injector.rs index 97f50c5..6dad1ec 100644 --- a/src/steam_linux_runtime_injector.rs +++ b/src/steam_linux_runtime_injector.rs @@ -1,33 +1,75 @@ use crate::{ - paths::get_backup_dir, + paths::{get_backup_dir, get_home_dir}, profile::Profile, - util::{ - file_utils::{copy_file, get_writer}, - steam_library_folder::SteamLibraryFolder, - }, + util::file_utils::{copy_file, get_writer}, }; use anyhow::bail; use lazy_static::lazy_static; +use serde::Deserialize; use std::{ + collections::HashMap, fs::read_to_string, io::Write, path::{Path, PathBuf}, }; -use tracing::error; + +#[derive(Deserialize)] +struct LibraryFolder { + pub path: String, + pub apps: HashMap, +} pub const PRESSURE_VESSEL_STEAM_APPID: u32 = 1628350; +fn get_steam_main_dir_path() -> anyhow::Result { + let steam_root: PathBuf = get_home_dir().join(".steam/root"); + + if steam_root.is_symlink() { + Ok(steam_root.read_link()?) + } else if steam_root.is_dir() { + Ok(steam_root) + } else { + bail!( + "Canonical steam root '{}' is not a dir nor a symlink!", + steam_root.to_string_lossy() + ) + } +} + +fn parse_steam_libraryfolders_vdf(path: &Path) -> anyhow::Result> { + Ok(keyvalues_serde::from_str(read_to_string(path)?.as_str())?) +} + fn get_runtime_entrypoint_path() -> Option { - match SteamLibraryFolder::get_folders() { - Ok(libraryfolders) => libraryfolders - .iter() - .find(|(_, folder)| folder.apps.contains_key(&PRESSURE_VESSEL_STEAM_APPID)) - .map(|(_, folder)| { - PathBuf::from(&folder.path) - .join("steamapps/common/SteamLinuxRuntime_sniper/_v2-entry-point") - }), + match get_steam_main_dir_path() { + Ok(steam_root) => { + let steam_libraryfolders_path = steam_root.join("steamapps/libraryfolders.vdf"); + + if !steam_libraryfolders_path.is_file() { + eprintln!( + "Steam libraryfolders.vdf does not exist in its canonical location {}", + steam_libraryfolders_path.to_string_lossy() + ); + return None; + } + + let libraryfolders: HashMap = + parse_steam_libraryfolders_vdf(&steam_libraryfolders_path).ok()?; + + libraryfolders + .iter() + .find(|(_, libraryfolder)| { + libraryfolder + .apps + .contains_key(&PRESSURE_VESSEL_STEAM_APPID) + }) + .map(|(_, libraryfolder)| { + PathBuf::from(&libraryfolder.path) + .join("steamapps/common/SteamLinuxRuntime_sniper/_v2-entry-point") + }) + } Err(e) => { - error!("unable to get runtime entrypoint path: {e}"); + eprintln!("Error getting steam root path: {e}"); None } } @@ -83,3 +125,22 @@ pub fn set_runtime_entrypoint_launch_opts_from_profile(profile: &Profile) -> any } bail!("Could not find valid runtime entrypoint"); } + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::parse_steam_libraryfolders_vdf; + + #[test] + fn deserialize_steam_libraryfolders_vdf() { + let lf = parse_steam_libraryfolders_vdf(Path::new("./test/files/steam_libraryfolders.vdf")) + .unwrap(); + assert_eq!(lf.len(), 1); + let first = lf.get(&0).unwrap(); + assert_eq!(first.path, "/home/gabmus/.local/share/Steam"); + assert_eq!(first.apps.len(), 10); + assert_eq!(first.apps.get(&228980).unwrap(), &29212173); + assert_eq!(first.apps.get(&632360).unwrap(), &0); + } +} diff --git a/src/ui/about_dialog.rs b/src/ui/about_dialog.rs index 0e28534..4e10574 100644 --- a/src/ui/about_dialog.rs +++ b/src/ui/about_dialog.rs @@ -1,7 +1,7 @@ use crate::{ constants::{ - get_artists, APP_ID, APP_NAME, BUILD_DATETIME, ISSUES_URL, REPO_URL, SINGLE_DEVELOPER, - VERSION, + get_artists, get_developers, APP_ID, APP_NAME, BUILD_DATETIME, ISSUES_URL, REPO_URL, + SINGLE_DEVELOPER, VERSION, }, device_prober::PhysicalXRDevice, linux_distro::LinuxDistro, @@ -20,20 +20,13 @@ pub fn create_about_dialog() -> adw::AboutDialog { .website(REPO_URL) .issue_url(ISSUES_URL) .developer_name(SINGLE_DEVELOPER) - .developers( - env!("CARGO_PKG_AUTHORS") - .split(':') - .map(|s| s.to_string()) - .collect::>(), - ) + .developers(get_developers()) .artists(get_artists()) .build() } -const UNKNOWN: &str = "UNKNOWN"; - pub fn populate_debug_info(dialog: &adw::AboutDialog, vkinfo: Option<&VulkanInfo>) { - if !dialog.debug_info().is_empty() { + if dialog.debug_info().len() > 0 { return; } let distro_family = LinuxDistro::get(); @@ -44,10 +37,10 @@ pub fn populate_debug_info(dialog: &adw::AboutDialog, vkinfo: Option<&VulkanInfo format!("Build time: {BUILD_DATETIME}"), format!( "Operating system: {d} ({f})", - d = distro.unwrap_or(UNKNOWN.into()), + d = distro.unwrap_or("unknown".into()), f = distro_family .map(|f| f.to_string()) - .unwrap_or(UNKNOWN.into()) + .unwrap_or("unknown".into()) ), format!( "Kernel: {}", @@ -57,29 +50,23 @@ pub fn populate_debug_info(dialog: &adw::AboutDialog, vkinfo: Option<&VulkanInfo ), format!( "Session type: {}", - env::var("XDG_SESSION_TYPE").unwrap_or(UNKNOWN.into()) + env::var("XDG_SESSION_TYPE").unwrap_or("unknown".into()) ), format!( "Desktop: {}", - env::var("XDG_CURRENT_DESKTOP").unwrap_or(UNKNOWN.into()) - ), - format!( - "CPU: {}", - read_to_string("/proc/cpuinfo") - .ok() - .and_then(|s| { - s.split("\n") - .find(|line| line.starts_with("model name")) - .map(|line| line.split(':').next_back().map(|s| s.trim().to_string())) - }) - .flatten() - .unwrap_or(UNKNOWN.into()) + env::var("XDG_CURRENT_DESKTOP").unwrap_or("unknown".into()) ), format!( "GPUs: {}", vkinfo .map(|i| i.gpu_names.join(", ")) - .unwrap_or(UNKNOWN.into()) + .unwrap_or("unknown".into()) + ), + format!( + "Monado Vulkan Layers: {}", + vkinfo + .map(|i| i.has_monado_vulkan_layers.to_string()) + .unwrap_or("unknown".into()) ), format!("Detected XR Devices: {}", { let devs = PhysicalXRDevice::from_usb(); diff --git a/src/ui/app.rs b/src/ui/app.rs index 6201648..1598ecb 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -11,7 +11,6 @@ use super::{ }, libsurvive_setup_window::{LibsurviveSetupMsg, LibsurviveSetupWindow}, main_view::{MainView, MainViewInit, MainViewMsg, MainViewOutMsg}, - plugins::store::{PluginStore, PluginStoreInit, PluginStoreMsg, PluginStoreOutMsg}, util::{copiable_code_snippet, copy_text, open_with_default_handler}, wivrn_conf_editor::{WivrnConfEditor, WivrnConfEditorInit, WivrnConfEditorMsg}, }; @@ -20,16 +19,14 @@ use crate::{ build_basalt::get_build_basalt_jobs, build_libsurvive::get_build_libsurvive_jobs, build_mercury::get_build_mercury_jobs, build_monado::get_build_monado_jobs, build_opencomposite::get_build_opencomposite_jobs, build_openhmd::get_build_openhmd_jobs, - build_vapor::get_build_vapor_jobs, build_wivrn::get_build_wivrn_jobs, - build_xrizer::get_build_xrizer_jobs, + build_wivrn::get_build_wivrn_jobs, }, - config::{Config, PluginConfig}, + config::Config, constants::APP_NAME, depcheck::common::dep_pkexec, file_builders::{ active_runtime_json::{ - remove_current_active_runtime, restore_active_runtime_backup, - set_current_active_runtime_to_profile, + set_current_active_runtime_to_profile, set_current_active_runtime_to_steam, }, openvrpaths_vrpath::{ set_current_openvrpaths_to_profile, set_current_openvrpaths_to_steam, @@ -38,21 +35,17 @@ use crate::{ linux_distro::LinuxDistro, openxr_prober::is_openxr_ready, paths::get_data_dir, - profile::{OvrCompatibilityModuleType, Profile, XRServiceType}, + profile::{Profile, XRServiceType}, stateless_action, steam_linux_runtime_injector::{ restore_runtime_entrypoint, set_runtime_entrypoint_launch_opts_from_profile, }, - termcolor::TermColor, - util::file_utils::{ - setcap_cap_sys_nice_eip, setcap_cap_sys_nice_eip_cmd, verify_cap_sys_nice_eip, - }, + util::file_utils::{setcap_cap_sys_nice_eip, setcap_cap_sys_nice_eip_cmd}, vulkaninfo::VulkanInfo, wivrn_dbus, xr_devices::XRDevice, }; use adw::{prelude::*, ResponseAppearance}; -use delicious_adwaita::{theme::Theme, ThemeEngine}; use gtk::glib::{self, clone}; use notify_rust::NotificationHandle; use relm4::{ @@ -60,12 +53,7 @@ use relm4::{ new_action_group, new_stateful_action, new_stateless_action, prelude::*, }; -use std::{ - collections::{HashMap, VecDeque}, - fs::remove_file, - time::Duration, -}; -use tracing::error; +use std::{collections::VecDeque, fs::remove_file, time::Duration}; pub struct App { application: adw::Application, @@ -82,7 +70,7 @@ pub struct App { config: Config, xrservice_worker: Option, - plugins_worker: Option, + autostart_worker: Option, restart_xrservice: bool, build_worker: Option, profiles: Vec, @@ -97,16 +85,13 @@ pub struct App { vkinfo: Option, inhibit_fail_notif: Option, - pluginstore: Option>, - - theme_engine: ThemeEngine, } #[derive(Debug)] pub enum Msg { OnServiceLog(Vec), OnServiceExit(i32), - OnPluginsExit(i32), + OnAutostartExit(i32), OnBuildLog(Vec), OnBuildExit(i32), ClockTicking, @@ -117,8 +102,7 @@ pub enum Msg { StartWithDebug, RestartXRService, ProfileSelected(Profile), - /// bool param: delete files - DeleteProfile(bool), + DeleteProfile, SaveProfile(Profile), RunSetCap, OpenLibsurviveSetup, @@ -132,10 +116,6 @@ pub enum Msg { StartProber, OnProberExit(bool), WivrnCheckPairMode, - OpenPluginStore, - UpdateConfigPlugins(HashMap), - ShowThemeManager, - SaveThemeConfig, NoOp, } @@ -167,7 +147,7 @@ impl App { } { Ok(n) => Some(n), Err(e) => { - error!("failed to send desktop notification: {e:?}"); + eprintln!("Failed to send desktop notification: {e:?}"); None } } @@ -183,7 +163,57 @@ impl App { pub fn start_xrservice(&mut self, sender: AsyncComponentSender, debug: bool) { self.xrservice_ready = false; let prof = self.get_selected_profile(); - if !prof.can_start() { + if prof.can_start() { + if let Err(e) = set_current_active_runtime_to_profile(&prof) { + alert( + "Failed to start XR Service", + Some(&format!( + "Error setting current active runtime to profile: {e}" + )), + Some(&self.app_win.clone().upcast::()), + ); + return; + } + if let Err(e) = set_current_openvrpaths_to_profile(&prof) { + alert( + "Failed to start XR Service", + Some(&format!( + "Error setting current openvrpaths file to profile: {e}" + )), + Some(&self.app_win.clone().upcast::()), + ); + return; + }; + self.debug_view.sender().emit(DebugViewMsg::ClearLog); + self.xr_devices = vec![]; + remove_file(prof.xrservice_type.ipc_file_path()) + .is_err() + .then(|| println!("Failed to remove xrservice IPC file")); + let worker = JobWorker::xrservice_worker_wrap_from_profile( + &prof, + sender.input_sender(), + |msg| match msg { + JobWorkerOut::Log(rows) => Msg::OnServiceLog(rows), + JobWorkerOut::Exit(code) => Msg::OnServiceExit(code), + }, + debug, + ); + worker.start(); + self.xrservice_worker = Some(worker); + self.main_view + .sender() + .emit(MainViewMsg::XRServiceActiveChanged( + true, + Some(self.get_selected_profile()), + // show launch opts only if setting the runtime entrypoint fails + set_runtime_entrypoint_launch_opts_from_profile(&prof).is_err(), + )); + self.debug_view + .sender() + .emit(DebugViewMsg::XRServiceActiveChanged(true)); + self.set_inhibit_session(true); + sender.input(Msg::StartProber); + } else { alert( "Failed to start profile", Some(concat!( @@ -192,129 +222,32 @@ impl App { )), Some(&self.app_win.clone().upcast::()), ); - return; } - if let Err(e) = set_current_active_runtime_to_profile(&prof) { - alert( - "Failed to start XR Service", - Some(&format!( - "Error setting current active runtime to profile: {e}" - )), - Some(&self.app_win.clone().upcast::()), - ); - return; - } - if let Err(e) = set_current_openvrpaths_to_profile(&prof) { - alert( - "Failed to start XR Service", - Some(&format!( - "Error setting current openvrpaths file to profile: {e}" - )), - Some(&self.app_win.clone().upcast::()), - ); - return; - }; - self.debug_view.sender().emit(DebugViewMsg::ClearLog); - self.xr_devices = vec![]; - { - let ipc_file = prof.xrservice_type.ipc_file_path(); - if ipc_file.exists() { - remove_file(ipc_file) - .unwrap_or_else(|e| error!("failed to remove xrservice IPC file: {e}")); - }; - } - let worker = JobWorker::xrservice_worker_wrap_from_profile( - &prof, - sender.input_sender(), - |msg| match msg { - JobWorkerOut::Log(rows) => Msg::OnServiceLog(rows), - JobWorkerOut::Exit(code) => Msg::OnServiceExit(code), - }, - debug, - ); - worker.start(); - self.xrservice_worker = Some(worker); - self.main_view - .sender() - .emit(MainViewMsg::XRServiceActiveChanged( - true, - Some(self.get_selected_profile()), - // show launch opts only if setting the runtime entrypoint fails - set_runtime_entrypoint_launch_opts_from_profile(&prof).is_err(), - )); - self.debug_view - .sender() - .emit(DebugViewMsg::XRServiceActiveChanged(true)); - self.set_inhibit_session(true); - sender.input(Msg::StartProber); } pub fn run_autostart(&mut self, sender: AsyncComponentSender) { let prof = self.get_selected_profile(); - let plugins_cmd = self - .config - .plugins - .values() - .filter_map(|cp| { - if cp.enabled && cp.plugin.validate() { - if let Err(e) = cp.plugin.mark_as_executable() { - error!( - "failed to mark plugin {} as executable: {e}", - cp.plugin.appid - ); - None - } else if !cp.plugin.plugin_type.launches_directly() { - None - } else { - Some({ - let mut cmd_parts = vec![cp - .plugin - .executable() - .unwrap() - .to_string_lossy() - .to_string()]; - cmd_parts.extend(cp.plugin.args.clone().unwrap_or_default()); - cmd_parts - .iter() - .map(|part| format!("'{part}'")) - .collect::>() - .join(" ") - }) - } - } else { - None - } - }) - .collect::>() - .join(" & "); - if !plugins_cmd.is_empty() { + if let Some(autostart_cmd) = &prof.autostart_command { let mut jobs = VecDeque::new(); jobs.push_back(WorkerJob::new_cmd( Some(prof.environment.clone()), "sh".into(), - Some(vec!["-c".into(), plugins_cmd]), + Some(vec!["-c".into(), autostart_cmd.clone()]), )); - let plugins_worker = JobWorker::new(jobs, sender.input_sender(), |msg| match msg { + let autostart_worker = JobWorker::new(jobs, sender.input_sender(), |msg| match msg { JobWorkerOut::Log(rows) => Msg::OnServiceLog(rows), - JobWorkerOut::Exit(code) => Msg::OnPluginsExit(code), + JobWorkerOut::Exit(code) => Msg::OnAutostartExit(code), }); - plugins_worker.start(); - self.plugins_worker = Some(plugins_worker); + autostart_worker.start(); + self.autostart_worker = Some(autostart_worker); } } pub fn restore_openxr_openvr_files(&self) { restore_runtime_entrypoint(); - if let Err(e) = remove_current_active_runtime() { + if let Err(e) = set_current_active_runtime_to_steam() { alert( - "Could not remove profile active runtime", - Some(&format!("{e}")), - Some(&self.app_win.clone().upcast::()), - ); - } - if let Err(e) = restore_active_runtime_backup() { - alert( - "Could not restore previous active runtime", + "Could not restore Steam active runtime", Some(&format!("{e}")), Some(&self.app_win.clone().upcast::()), ); @@ -329,17 +262,27 @@ impl App { } pub fn shutdown_xrservice(&mut self) { - if let Some(w) = self.plugins_worker.as_ref() { - w.stop(); + if let Some(worker) = self.autostart_worker.as_ref() { + worker.stop(); } + self.xrservice_ready = false; if let Some(w) = self.openxr_prober_worker.as_ref() { w.stop(); // this can cause threads to remain hanging... self.openxr_prober_worker = None; } - if let Some(w) = self.xrservice_worker.as_ref() { - w.stop(); + self.set_inhibit_session(false); + if let Some(worker) = self.xrservice_worker.as_ref() { + worker.stop(); } + self.libmonado = None; + self.main_view + .sender() + .emit(MainViewMsg::XRServiceActiveChanged(false, None, false)); + self.debug_view + .sender() + .emit(DebugViewMsg::XRServiceActiveChanged(false)); + self.xr_devices = vec![]; } } @@ -373,7 +316,7 @@ impl AsyncComponent for App { set_content: Some(&adw::NavigationPage::new(model.debug_view.widget(), "Debug View")), set_show_content: false, set_collapsed: !model.config.debug_view_enabled, - }, + } }, connect_close_request[sender] => move |win| { sender.input(Msg::SaveWinSize(win.width(), win.height())); @@ -397,27 +340,6 @@ impl AsyncComponent for App { ) { match message { Msg::NoOp => {} - Msg::ShowThemeManager => { - let dialog = self - .theme_engine - .theme_chooser_dialog(Theme::default_themes().as_ref()); - dialog.set_content_height(2000); - dialog.present(Some(&self.app_win)); - dialog.connect_closed(clone!( - #[strong] - sender, - move |_| { - sender.input(Msg::SaveThemeConfig); - } - )); - } - Msg::SaveThemeConfig => { - let name = self.theme_engine.current_theme_name(); - if self.config.theme_name != name { - self.config.theme_name = name; - self.config.save(); - } - } Msg::OnServiceLog(rows) => { if !rows.is_empty() { self.debug_view @@ -426,8 +348,6 @@ impl AsyncComponent for App { } } Msg::OnServiceExit(code) => { - self.set_inhibit_session(false); - self.xrservice_ready = false; self.restore_openxr_openvr_files(); self.main_view .sender() @@ -435,8 +355,6 @@ impl AsyncComponent for App { self.debug_view .sender() .emit(DebugViewMsg::XRServiceActiveChanged(false)); - self.libmonado = None; - self.xr_devices = vec![]; if code != 0 && code != 15 { // 15 is SIGTERM sender.input(Msg::OnServiceLog(vec![format!( @@ -451,7 +369,7 @@ impl AsyncComponent for App { self.start_xrservice(sender, false); } } - Msg::OnPluginsExit(_) => self.plugins_worker = None, + Msg::OnAutostartExit(_) => self.autostart_worker = None, Msg::ClockTicking => { self.main_view.sender().emit(MainViewMsg::ClockTicking); let xrservice_worker_is_alive = self @@ -491,7 +409,7 @@ impl AsyncComponent for App { .emit(MainViewMsg::SetWivrnSupportsPairing(true)); } Err(e) => { - error!("failed to get wivrn pairing mode: {e:?}"); + eprintln!("Error: failed to get wivrn pairing mode: {e:?}"); self.main_view .sender() .emit(MainViewMsg::SetWivrnSupportsPairing(false)); @@ -555,17 +473,7 @@ impl AsyncComponent for App { XRServiceType::Wivrn => get_build_wivrn_jobs(&profile, clean_build), }); } - jobs.extend(match profile.ovr_comp.mod_type { - OvrCompatibilityModuleType::Opencomposite => { - get_build_opencomposite_jobs(&profile, clean_build) - } - OvrCompatibilityModuleType::Xrizer => { - get_build_xrizer_jobs(&profile, clean_build) - } - OvrCompatibilityModuleType::Vapor => { - get_build_vapor_jobs(&profile, clean_build) - } - }); + jobs.extend(get_build_opencomposite_jobs(&profile, clean_build)); let missing_deps = profile.missing_dependencies(); if !(self.skip_depcheck || profile.skip_dependency_check || missing_deps.is_empty()) { @@ -654,10 +562,6 @@ impl AsyncComponent for App { if dep_pkexec().check() { self.setcap_confirm_dialog.present(Some(&self.app_win)); } else { - self.build_window - .sender() - .emit(BuildWindowMsg::UpdateContent(vec![TermColor::Red - .colorize("pkexec not found, cannot set capabilities\n")])); alert_w_widget( "pkexec not found", Some(&format!( @@ -690,16 +594,9 @@ impl AsyncComponent for App { w.stop(); } } - Msg::DeleteProfile(delete_files) => { + Msg::DeleteProfile => { let todel = self.get_selected_profile(); if todel.editable { - if delete_files { - for res in todel.delete_files() { - if let Err(e) = res { - error!("Error deleting profile directory: {e}"); - } - } - } self.config.user_profiles.retain(|p| p.uuid != todel.uuid); self.config.save(); self.profiles = self.config.profiles(); @@ -733,51 +630,13 @@ impl AsyncComponent for App { self.debug_view .sender() .emit(DebugViewMsg::UpdateSelectedProfile(prof.clone())); - self.main_view - .sender() - .emit(MainViewMsg::QueryProfileRebuild); } Msg::RunSetCap => { if !dep_pkexec().check() { - // there's a precheck ahead of this, this should likely never happen - error!("pkexec not found, skipping setcap"); + println!("pkexec not found, skipping setcap"); } else { let profile = self.get_selected_profile(); - let setcap_failed_dialog = || { - alert_w_widget( - "Setcap failed to run", - Some("Setting the capabilities automatically failed, you can still try manually using the command below." - ), - Some(&copiable_code_snippet( - &format!("sudo {}", setcap_cap_sys_nice_eip_cmd(&profile).join(" ")) - )), - Some(&self.app_win.clone().upcast()) - ); - }; - if let Err(e) = setcap_cap_sys_nice_eip(&profile).await { - setcap_failed_dialog(); - error!("failed running setcap: {e}"); - self.build_window - .sender() - .emit(BuildWindowMsg::UpdateContent(vec![ - TermColor::Red.colorize("Setting capabilities failed\n") - ])); - } else if !verify_cap_sys_nice_eip(&profile).await { - setcap_failed_dialog(); - error!("setcap succeeded but capabilities were reset"); - self.build_window - .sender() - .emit(BuildWindowMsg::UpdateContent(vec![TermColor::Red - .colorize( - "Setting capabilities succeeded, but capabilities have been reset\n", - )])); - } else { - self.build_window - .sender() - .emit(BuildWindowMsg::UpdateContent(vec![ - TermColor::Green.colorize("Capabilities set correctly\n") - ])); - } + setcap_cap_sys_nice_eip(&profile).await; } } Msg::ProfileSelected(prof) => { @@ -897,21 +756,6 @@ impl AsyncComponent for App { } } } - Msg::OpenPluginStore => { - let pluginstore = PluginStore::builder() - .launch(PluginStoreInit { - config_plugins: self.config.plugins.clone(), - }) - .forward(sender.input_sender(), move |msg| match msg { - PluginStoreOutMsg::UpdateConfigPlugins(cp) => Msg::UpdateConfigPlugins(cp), - }); - pluginstore.sender().emit(PluginStoreMsg::Present); - self.pluginstore = Some(pluginstore); - } - Msg::UpdateConfigPlugins(cp) => { - self.config.plugins = cp; - self.config.save(); - } } } @@ -1013,28 +857,6 @@ impl AsyncComponent for App { } ) ); - stateless_action!( - actions, - PluginStoreAction, - clone!( - #[strong] - sender, - move |_| { - sender.input(Msg::OpenPluginStore); - } - ) - ); - stateless_action!( - actions, - ThemeManagerAction, - clone!( - #[strong] - sender, - move |_| { - sender.input(Msg::ShowThemeManager); - } - ) - ); // this bypasses the macro because I need the underlying gio action // to enable/disable it in update() let configure_wivrn_action = { @@ -1056,7 +878,7 @@ impl AsyncComponent for App { match VulkanInfo::get() { Ok(info) => Some(info), Err(e) => { - error!("failed to get Vulkan info: {e:#?}"); + eprintln!("Failed to get Vulkan info: {e:#?}"); None } } @@ -1071,15 +893,15 @@ impl AsyncComponent for App { config: config.clone(), selected_profile: selected_profile.clone(), root_win: root.clone().into(), + vkinfo: vkinfo.clone(), }) .forward(sender.input_sender(), |message| match message { MainViewOutMsg::DoStartStopXRService => Msg::DoStartStopXRService, MainViewOutMsg::RestartXRService => Msg::RestartXRService, MainViewOutMsg::ProfileSelected(uuid) => Msg::ProfileSelected(uuid), - MainViewOutMsg::DeleteProfile(delete_files) => Msg::DeleteProfile(delete_files), + MainViewOutMsg::DeleteProfile => Msg::DeleteProfile, MainViewOutMsg::SaveProfile(p) => Msg::SaveProfile(p), MainViewOutMsg::OpenLibsurviveSetup => Msg::OpenLibsurviveSetup, - MainViewOutMsg::BuildProfile(clean) => Msg::BuildProfile(clean), }), vkinfo, debug_view: DebugView::builder() @@ -1103,21 +925,10 @@ impl AsyncComponent for App { .detach(), split_view: None, setcap_confirm_dialog, - theme_engine: ThemeEngine::new_with_theme(&{ - if config.theme_name == "Follow system" { - Theme::default() - } else { - Theme::default_themes() - .into_iter() - .find(|t| t.name == config.theme_name) - .unwrap_or_default() - } - }) - .unwrap(), config, profiles, xrservice_worker: None, - plugins_worker: None, + autostart_worker: None, build_worker: None, xr_devices: vec![], restart_xrservice: false, @@ -1128,7 +939,6 @@ impl AsyncComponent for App { openxr_prober_worker: None, xrservice_ready: false, inhibit_fail_notif: None, - pluginstore: None, }; let widgets = view_output!(); @@ -1201,8 +1011,6 @@ new_stateless_action!(pub BuildProfileCleanAction, AppActionGroup, "buildprofile new_stateless_action!(pub QuitAction, AppActionGroup, "quit"); new_stateful_action!(pub DebugViewToggleAction, AppActionGroup, "debugviewtoggle", (), bool); new_stateless_action!(pub ConfigureWivrnAction, AppActionGroup, "configurewivrn"); -new_stateless_action!(pub PluginStoreAction, AppActionGroup, "store"); -new_stateless_action!(pub ThemeManagerAction, AppActionGroup, "thememanager"); new_stateless_action!(pub DebugOpenDataAction, AppActionGroup, "debugopendata"); new_stateless_action!(pub DebugOpenPrefixAction, AppActionGroup, "debugopenprefix"); diff --git a/src/ui/build_window.rs b/src/ui/build_window.rs index f6f8b21..759af9b 100644 --- a/src/ui/build_window.rs +++ b/src/ui/build_window.rs @@ -1,5 +1,3 @@ -use crate::termcolor::TermColor; - use super::{term_widget::TermWidget, SENDER_IO_ERR_MSG}; use adw::prelude::*; use relm4::prelude::*; @@ -90,54 +88,43 @@ impl SimpleComponent for BuildWindow { gtk::Label { #[track = "model.changed(BuildWindow::build_status())"] set_markup: match &model.build_status { - BuildStatus::Building => String::default(), - BuildStatus::Done => "Build done, you can close this window".into(), + BuildStatus::Building => "Build in progress...".to_string(), + BuildStatus::Done => "Build done, you can close this window".to_string(), BuildStatus::Error(code) => { format!("Build failed: \"{c}\"", c = code) } }.as_str(), - #[track = "model.changed(BuildWindow::build_status())"] - set_visible: match &model.build_status { - BuildStatus::Building => false, - BuildStatus::Done | BuildStatus::Error(_) => true, - }, add_css_class: "title-2", set_wrap: true, set_wrap_mode: gtk::pango::WrapMode::Word, set_justify: gtk::Justification::Center, }, + gtk::Button { + #[track = "model.changed(BuildWindow::build_status())"] + set_visible: matches!(&model.build_status, BuildStatus::Building), + add_css_class: "destructive-action", + add_css_class: "circular", + set_icon_name: "window-close-symbolic", + set_tooltip_text: Some("Cancel build"), + connect_clicked[sender] => move |_| { + sender.output(Self::Output::CancelBuild).expect(SENDER_IO_ERR_MSG); + } + }, }, model.term.container.clone(), }, - add_bottom_bar: bottom_bar = >k::Box { - set_orientation: gtk::Orientation::Horizontal, + add_bottom_bar: bottom_bar = >k::Button { + add_css_class: "pill", set_halign: gtk::Align::Center, - set_hexpand: true, - set_margin_bottom: 24, - set_spacing: 12, - gtk::Button { - add_css_class: "pill", - set_halign: gtk::Align::Center, - set_label: "Close", - #[track = "model.changed(BuildWindow::can_close())"] - set_visible: model.can_close, - connect_clicked[win] => move |_| { - win.close(); - }, + set_label: "Close", + set_margin_all: 12, + #[track = "model.changed(BuildWindow::can_close())"] + set_sensitive: model.can_close, + connect_clicked[win] => move |_| { + + win.close(); }, - // this - gtk::Button { - #[track = "model.changed(BuildWindow::build_status())"] - set_visible: matches!(&model.build_status, BuildStatus::Building), - add_css_class: "destructive-action", - add_css_class: "pill", - set_label: "Cancel build", - connect_clicked[sender] => move |_| { - sender.output(Self::Output::CancelBuild).expect(SENDER_IO_ERR_MSG); - } - }, - // ^^^ - }, + } } } } @@ -166,18 +153,8 @@ impl SimpleComponent for BuildWindow { label.remove_css_class("success"); label.remove_css_class("error"); match status { - BuildStatus::Done => { - label.add_css_class("success"); - sender.input(BuildWindowMsg::UpdateContent(vec![ - TermColor::Blue.colorize("Build completed!\n") - ])); - } - BuildStatus::Error(_) => { - label.add_css_class("error"); - sender.input(BuildWindowMsg::UpdateContent(vec![ - TermColor::Blue.colorize("Build failed!\n") - ])); - } + BuildStatus::Done => label.add_css_class("success"), + BuildStatus::Error(_) => label.add_css_class("error"), _ => {} } if status != BuildStatus::Building { diff --git a/src/ui/cmdline_opts.rs b/src/ui/cmdline_opts.rs index 087ee74..ca4e14d 100644 --- a/src/ui/cmdline_opts.rs +++ b/src/ui/cmdline_opts.rs @@ -1,7 +1,4 @@ -use crate::{ - config::Config, - constants::{APP_NAME, VERSION}, -}; +use crate::config::Config; use gtk::{ gio::{ prelude::{ApplicationCommandLineExt, ApplicationExt}, @@ -9,11 +6,9 @@ use gtk::{ }, glib::{self, prelude::IsA}, }; -use tracing::error; #[derive(Debug, Clone)] pub struct CmdLineOpts { - pub version: bool, pub start: bool, pub list_profiles: bool, pub profile_uuid: Option, @@ -22,7 +17,6 @@ pub struct CmdLineOpts { } impl CmdLineOpts { - const OPT_VERSION: (&'static str, char) = ("version", 'v'); const OPT_START: (&'static str, char) = ("start", 'S'); const OPT_LIST_PROFILES: (&'static str, char) = ("list-profiles", 'l'); const OPT_PROFILE: (&'static str, char) = ("profile", 'p'); @@ -30,14 +24,6 @@ impl CmdLineOpts { const OPT_CHECK_DEPS_FOR: (&'static str, char) = ("check-deps-for", 'c'); pub fn init(app: &impl IsA) { - app.add_main_option( - Self::OPT_VERSION.0, - glib::Char::try_from(Self::OPT_VERSION.1).unwrap(), - glib::OptionFlags::IN_MAIN, - glib::OptionArg::None, - "Print the version information", - None, - ); app.add_main_option( Self::OPT_START.0, glib::Char::try_from(Self::OPT_START.1).unwrap(), @@ -82,10 +68,6 @@ impl CmdLineOpts { /// returns an exit code if the application should quit immediately pub fn handle_non_activating_opts(&self) -> Option { - if self.version { - println!("{APP_NAME} {VERSION}"); - return Some(0); - } if self.list_profiles { println!("Available profiles\nUUID: \"name\""); let profiles = Config::get_config().profiles(); @@ -106,7 +88,7 @@ impl CmdLineOpts { } return Some(1); } else { - error!("No profile found for uuid: `{prof_id}`"); + eprintln!("No profile found for uuid: `{prof_id}`"); return Some(404); } } @@ -116,7 +98,6 @@ impl CmdLineOpts { pub fn from_cmdline(cmdline: &ApplicationCommandLine) -> Self { let opts = cmdline.options_dict(); Self { - version: opts.contains(Self::OPT_VERSION.0), start: opts.contains(Self::OPT_START.0), list_profiles: opts.contains(Self::OPT_LIST_PROFILES.0), profile_uuid: opts diff --git a/src/ui/devices_box.rs b/src/ui/devices_box.rs index 64496b1..78b2abe 100644 --- a/src/ui/devices_box.rs +++ b/src/ui/devices_box.rs @@ -61,19 +61,9 @@ impl SimpleComponent for DevicesBox { } if !has_left && dev.roles.contains(&XRDeviceRole::Left) { has_left = true; - if ["Qwerty Left Controller"].contains(&dev.name.as_str()) { - row_model.state = Some(DeviceRowState::Warning); - row_model.subtitle = - Some(format!("No left controller detected ({})", dev.name)); - } } if !has_right && dev.roles.contains(&XRDeviceRole::Right) { has_right = true; - if ["Qwerty Right Controller"].contains(&dev.name.as_str()) { - row_model.state = Some(DeviceRowState::Warning); - row_model.subtitle = - Some(format!("No right controller detected ({})", dev.name)); - } } models.push(row_model); } diff --git a/src/ui/install_wivrn_box.rs b/src/ui/install_wivrn_box.rs index 769e012..cf63f7c 100644 --- a/src/ui/install_wivrn_box.rs +++ b/src/ui/install_wivrn_box.rs @@ -8,7 +8,6 @@ use crate::{ use gtk::prelude::*; use relm4::{new_action_group, new_stateless_action, prelude::*}; use std::fs::remove_file; -use tracing::error; const WIVRN_LATEST_RELEASE_APK_URL: &str = "https://github.com/WiVRn/WiVRn/releases/latest/download/WiVRn-standard-release.apk"; @@ -114,7 +113,7 @@ impl AsyncComponent for InstallWivrnBox { add_css_class: "dim-label", set_hexpand: true, set_label: concat!( - "Install the WiVRn APK on your standalone Android headset. ", + "Install the WiVRn APK on your standalong Android headset. ", "You will need to enable Developer Mode on your headset, ", "then press the \"Install WiVRn\" button." ), @@ -173,7 +172,7 @@ impl AsyncComponent for InstallWivrnBox { match get_wivrn_apk_ref(&self.selected_profile) { Err(GetWivrnApkRefErr::NotWivrn) => { - error!("this is not a WiVRn profile, how did you get here?"); + eprintln!("This is not a WiVRn profile, how did you get here?"); } Err(GetWivrnApkRefErr::RepoDirNotFound) => { self.set_install_wivrn_status(InstallWivrnStatus::Done(Some( @@ -181,11 +180,14 @@ impl AsyncComponent for InstallWivrnBox { ))); } Err(GetWivrnApkRefErr::RepoManipulationFailed(giterr)) => { - error!("failed to manipulate WiVRn repo: {giterr}, falling back to latest release APK"); + eprintln!("Error: failed to manipulate WiVRn repo: {giterr}, falling back to latest release APK"); let existing = cache_file_path(WIVRN_LATEST_RELEASE_APK_URL, Some("apk")); if existing.is_file() { if let Err(e) = remove_file(&existing) { - error!("failed to remove file {}: {e}", existing.to_string_lossy()); + eprintln!( + "Failed to remove file {}: {e}", + existing.to_string_lossy() + ); } } sender.input(Self::Input::DoInstall(WIVRN_LATEST_RELEASE_APK_URL.into())); @@ -206,7 +208,7 @@ impl AsyncComponent for InstallWivrnBox { // TODO: we gonna cache or just download async every time? match cache_file(&url, Some("apk")).await { Err(e) => { - error!("failed to download apk: {e}"); + eprintln!("Failed to download apk: {e}"); self.set_install_wivrn_status(InstallWivrnStatus::Done(Some( "Error downloading WiVRn client APK".into(), ))); @@ -234,14 +236,14 @@ impl AsyncComponent for InstallWivrnBox { .into(), )) } else { - error!("ADB failed with code {}.\nstdout:\n{}\n======\nstderr:\n{}", out.exit_code, out.stdout, out.stderr); + eprintln!("Error: ADB failed with code {}.\nstdout:\n{}\n======\nstderr:\n{}", out.exit_code, out.stdout, out.stderr); InstallWivrnStatus::Done(Some( format!("ADB exited with code \"{}\"", out.exit_code) )) } } Err(e) => { - error!("failed to run ADB: {e}"); + eprintln!("Error: failed to run ADB: {e}"); InstallWivrnStatus::Done(Some( "Failed to run ADB".into() )) diff --git a/src/ui/job_worker/internal_worker.rs b/src/ui/job_worker/internal_worker.rs index 437e6a9..25c40a2 100644 --- a/src/ui/job_worker/internal_worker.rs +++ b/src/ui/job_worker/internal_worker.rs @@ -155,7 +155,7 @@ impl Worker for InternalJobWorker { } } -pub const LAUNCH_OPTS_CMD_PLACEHOLDER: &str = "%command%"; +const LAUNCH_OPTS_CMD_PLACEHOLDER: &str = "%command%"; impl InternalJobWorker { pub fn xrservice_worker_from_profile( @@ -193,6 +193,9 @@ impl InternalJobWorker { } else { launch_opts }; + if !launch_opts.contains(" --no-instructions") { + launch_opts.push_str(" --no-instructions"); + } let (command, args) = match launch_opts.is_empty() { false => ( "sh".into(), diff --git a/src/ui/job_worker/mod.rs b/src/ui/job_worker/mod.rs index c50ab21..4de1c78 100644 --- a/src/ui/job_worker/mod.rs +++ b/src/ui/job_worker/mod.rs @@ -15,7 +15,6 @@ use std::{ thread::{self, sleep}, time::Duration, }; -use tracing::{error, warn}; pub mod internal_worker; pub mod job; @@ -98,7 +97,7 @@ impl JobWorker { self.state.lock().unwrap().stop_requested = true; if let Some(pid) = self.state.lock().unwrap().current_pid { if let Err(e) = kill(pid, SIGTERM) { - error!("Failed to send SIGTERM: {e}"); + eprintln!("Failed to send SIGTERM: {e:#?}"); } let state = self.state.clone(); thread::spawn(move || { @@ -106,9 +105,9 @@ impl JobWorker { if let Ok(s) = state.lock() { if !s.exited { // process is still alive - warn!("process is still alive 2 seconds after SIGTERM, proceeding to send SIGKILL..."); + eprintln!("Process is still alive 2 seconds after SIGTERM, proceeding to send SIGKILL..."); if let Err(e) = kill(pid, SIGKILL) { - error!("failed to send SIGKILL: {e}"); + eprintln!("Failed to send SIGKILL: {e:#?}"); }; } } diff --git a/src/ui/main_view.rs b/src/ui/main_view.rs index 8a2c145..1800a38 100644 --- a/src/ui/main_view.rs +++ b/src/ui/main_view.rs @@ -2,7 +2,7 @@ use super::{ alert::alert, app::{ AboutAction, BuildProfileAction, BuildProfileCleanAction, ConfigureWivrnAction, - DebugViewToggleAction, PluginStoreAction, + DebugViewToggleAction, }, devices_box::{DevicesBox, DevicesBoxMsg}, install_wivrn_box::{InstallWivrnBox, InstallWivrnBoxInit, InstallWivrnBoxMsg}, @@ -12,7 +12,6 @@ use super::{ steamvr_calibration_box::{SteamVrCalibrationBox, SteamVrCalibrationBoxMsg}, util::{limit_dropdown_width, warning_heading}, wivrn_wired_start_box::{WivrnWiredStartBox, WivrnWiredStartBoxInit, WivrnWiredStartBoxMsg}, - SENDER_IO_ERR_MSG, }; use crate::{ config::Config, @@ -21,11 +20,11 @@ use crate::{ paths::{get_data_dir, get_home_dir}, profile::{LighthouseDriver, Profile, XRServiceType}, stateless_action, - ui::app::ThemeManagerAction, util::{ file_utils::{get_writer, mount_has_nosuid}, steamvr_utils::chaperone_info_exists, }, + vulkaninfo::VulkanInfo, wivrn_dbus, xr_devices::XRDevice, }; @@ -37,7 +36,6 @@ use relm4::{ prelude::*, }; use std::{fs::read_to_string, io::Write}; -use tracing::{error, warn}; #[tracker::track] pub struct MainView { @@ -61,8 +59,6 @@ pub struct MainView { #[tracker::do_not_track] profile_delete_confirm_dialog: adw::AlertDialog, #[tracker::do_not_track] - query_profile_rebuild_dialog: adw::AlertDialog, - #[tracker::do_not_track] profile_editor: Option>, #[tracker::do_not_track] steamvr_calibration_box: Controller, @@ -75,6 +71,8 @@ pub struct MainView { #[tracker::do_not_track] profile_export_action: gtk::gio::SimpleAction, xrservice_ready: bool, + #[tracker::do_not_track] + vkinfo: Option, wivrn_pairing_mode: bool, wivrn_pin: Option, wivrn_supports_pairing: bool, @@ -105,7 +103,6 @@ pub enum MainViewMsg { SetWivrnPairingMode(bool), StopWivrnPairingMode, StartWivrnPairingMode, - QueryProfileRebuild, } #[derive(Debug)] @@ -113,18 +110,16 @@ pub enum MainViewOutMsg { DoStartStopXRService, RestartXRService, ProfileSelected(Profile), - /// bool param: delete files - DeleteProfile(bool), + DeleteProfile, SaveProfile(Profile), OpenLibsurviveSetup, - /// params: clean - BuildProfile(bool), } pub struct MainViewInit { pub config: Config, pub selected_profile: Profile, pub root_win: gtk::Window, + pub vkinfo: Option, } impl MainView { @@ -152,7 +147,6 @@ impl AsyncComponent for MainView { menu! { app_menu: { section! { - "Plugin_s" => PluginStoreAction, // value inside action is ignored "_Debug View" => DebugViewToggleAction, "_Build Profile" => BuildProfileAction, @@ -160,7 +154,6 @@ impl AsyncComponent for MainView { "Configure _WiVRn" => ConfigureWivrnAction, }, section! { - "Change _Theme" => ThemeManagerAction, "_About" => AboutAction, }, }, @@ -396,8 +389,8 @@ impl AsyncComponent for MainView { set_visible: match mount_has_nosuid(&model.selected_profile.prefix) { Ok(b) => b, Err(_) => { - warn!( - "nosuid detection: could not get stat on path {}", + eprintln!( + "Warning (nosuid detection): could not get stat on path {}", model.selected_profile.prefix.to_string_lossy()); false }, @@ -452,7 +445,35 @@ impl AsyncComponent for MainView { set_label: concat!( "SteamVR room configuration not found.\n", "To use the SteamVR lighthouse driver, you ", - "will need to run SteamVR Quick Calibration.", + "will need to run SteamVR and perform the room setup.", + ), + add_css_class: "warning", + set_xalign: 0.0, + set_wrap: true, + set_wrap_mode: gtk::pango::WrapMode::Word, + } + }, + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_hexpand: true, + set_vexpand: false, + set_spacing: 12, + add_css_class: "card", + add_css_class: "padded", + set_visible: model + .vkinfo + .as_ref() + .is_some_and( + |i| i.has_nvidia_gpu && !i.has_monado_vulkan_layers + ), + warning_heading(), + gtk::Label { + set_label: concat!( + "An Nvidia GPU has been detected, but it ", + "seems you don't have the Monado Vulkan Layers ", + "installed on your system.\n\nInstall the ", + "Monado Vulkan Layers or your XR session will ", + "crash." ), add_css_class: "warning", set_xalign: 0.0, @@ -606,7 +627,7 @@ impl AsyncComponent for MainView { self.set_wivrn_pin(Some(pin)); } Err(e) => { - error!("failed to get wivrn pairing pin: {e}"); + eprintln!("Error: failed to get wivrn pairing pin: {e:?}"); } }; } else { @@ -616,12 +637,12 @@ impl AsyncComponent for MainView { } Self::Input::StopWivrnPairingMode => { if let Err(e) = wivrn_dbus::disable_pairing().await { - error!("failed to stop wivrn pairing mode: {e}"); + eprintln!("Error: failed to stop wivrn pairing mode: {e:?}"); } } Self::Input::StartWivrnPairingMode => { if let Err(e) = wivrn_dbus::enable_pairing().await { - error!("failed to start wivrn pairing mode: {e}"); + eprintln!("Error: failed to start wivrn pairing mode: {e:?}"); } } Self::Input::StartStopClicked => { @@ -699,10 +720,6 @@ impl AsyncComponent for MainView { } })); } - Self::Input::QueryProfileRebuild => { - self.query_profile_rebuild_dialog - .present(Some(&self.root_win)); - } Self::Input::SetSelectedProfile(index) => { self.profiles_dropdown .as_ref() @@ -740,7 +757,7 @@ impl AsyncComponent for MainView { Self::Input::SaveProfile(prof) => { sender .output(Self::Output::SaveProfile(prof)) - .expect(SENDER_IO_ERR_MSG); + .expect("Sender output failed"); } Self::Input::DuplicateProfile => { if self.selected_profile.can_be_built { @@ -909,38 +926,8 @@ impl AsyncComponent for MainView { ), ); - let query_profile_rebuild_dialog = adw::AlertDialog::builder() - .heading("Do you want to build this profile now?") - .body("This will trigger a clean build") - .build(); - query_profile_rebuild_dialog.add_response("no", "_No"); - query_profile_rebuild_dialog.add_response("yes", "_Yes"); - query_profile_rebuild_dialog.set_response_appearance("yes", ResponseAppearance::Suggested); - - query_profile_rebuild_dialog.connect_response( - None, - clone!( - #[strong] - sender, - move |_, res| { - if res == "yes" { - sender - .output(Self::Output::BuildProfile(true)) - .expect(SENDER_IO_ERR_MSG); - } - } - ), - ); - let profile_delete_confirm_dialog = adw::AlertDialog::builder() .heading("Are you sure you want to delete this profile?") - .extra_child( - >k::CheckButton::builder() - .label("Delete all files and folders associated with profile") - .halign(gtk::Align::Center) - .hexpand(true) - .build(), - ) .build(); profile_delete_confirm_dialog.add_response("no", "_No"); profile_delete_confirm_dialog.add_response("yes", "_Yes"); @@ -952,19 +939,10 @@ impl AsyncComponent for MainView { clone!( #[strong] sender, - move |dialog, res| { - let delete_files_checkbox = dialog - .extra_child() - .and_then(|child| child.downcast::().ok()); - let delete_files = delete_files_checkbox - .as_ref() - .is_some_and(|c| c.is_active()); - if let Some(check) = delete_files_checkbox { - check.set_active(false); - } + move |_, res| { if res == "yes" { sender - .output(Self::Output::DeleteProfile(delete_files)) + .output(Self::Output::DeleteProfile) .expect("Sender output failed"); } } @@ -1082,7 +1060,6 @@ impl AsyncComponent for MainView { selected_profile: init.selected_profile.clone(), profile_not_editable_dialog, profile_delete_confirm_dialog, - query_profile_rebuild_dialog, root_win: init.root_win.clone(), steamvr_calibration_box, openhmd_calibration_box, @@ -1090,6 +1067,7 @@ impl AsyncComponent for MainView { xrservice_ready: false, profile_delete_action, profile_export_action, + vkinfo: init.vkinfo, wivrn_pairing_mode: false, wivrn_supports_pairing: false, wivrn_pin: None, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index f68333c..2a5dd01 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -13,7 +13,6 @@ mod libsurvive_setup_window; mod macros; mod main_view; mod openhmd_calibration_box; -pub mod plugins; mod preference_rows; mod profile_editor; mod steam_launch_options_box; diff --git a/src/ui/openhmd_calibration_box.rs b/src/ui/openhmd_calibration_box.rs index 6258ad1..c8da5dc 100644 --- a/src/ui/openhmd_calibration_box.rs +++ b/src/ui/openhmd_calibration_box.rs @@ -3,7 +3,6 @@ use relm4::{ gtk::{self, prelude::*}, ComponentParts, ComponentSender, SimpleComponent, }; -use tracing::{debug, error}; #[tracker::track] pub struct OpenHmdCalibrationBox { @@ -60,10 +59,10 @@ impl SimpleComponent for OpenHmdCalibrationBox { let target = XDG.get_config_home().join("openhmd/rift-room-config.json"); if target.is_file() { if let Err(e) = std::fs::remove_file(target) { - error!("Failed to remove openhmd config: {e}"); + eprintln!("Failed to remove openhmd config: {e}"); } } else { - debug!("trying to delete openhmd calibration config, but file is missing") + println!("info: trying to delete openhmd calibration config, but file is missing") } } }, diff --git a/src/ui/plugins/add_custom_plugin_win.rs b/src/ui/plugins/add_custom_plugin_win.rs deleted file mode 100644 index c06f0c8..0000000 --- a/src/ui/plugins/add_custom_plugin_win.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::path::PathBuf; - -use crate::{ - constants::APP_ID, - ui::{ - preference_rows::{entry_row, file_row}, - SENDER_IO_ERR_MSG, - }, -}; - -use super::Plugin; -use adw::prelude::*; -use gtk::glib::clone; -use relm4::prelude::*; - -#[tracker::track] -pub struct AddCustomPluginWin { - #[tracker::do_not_track] - parent: gtk::Window, - #[tracker::do_not_track] - win: Option, - /// this is true when enough fields are populated, allowing the creation - /// of the plugin object to add - can_add: bool, - #[tracker::do_not_track] - plugin: Plugin, -} - -#[derive(Debug)] -pub enum AddCustomPluginWinMsg { - Present, - Close, - OnNameChange(String), - OnExecPathChange(Option), - Add, -} - -#[derive(Debug)] -pub enum AddCustomPluginWinOutMsg { - Add(Plugin), -} - -#[derive(Debug)] -pub struct AddCustomPluginWinInit { - pub parent: gtk::Window, -} - -#[relm4::component(pub)] -impl SimpleComponent for AddCustomPluginWin { - type Init = AddCustomPluginWinInit; - type Input = AddCustomPluginWinMsg; - type Output = AddCustomPluginWinOutMsg; - - view! { - #[name(win)] - adw::Dialog { - set_can_close: true, - #[wrap(Some)] - set_child: inner = &adw::ToolbarView { - set_top_bar_style: adw::ToolbarStyle::Flat, - set_bottom_bar_style: adw::ToolbarStyle::Flat, - set_vexpand: true, - set_hexpand: true, - add_top_bar: top_bar = &adw::HeaderBar { - set_show_end_title_buttons: false, - set_show_start_title_buttons: false, - pack_start: cancel_btn = >k::Button { - set_label: "Cancel", - add_css_class: "destructive-action", - connect_clicked[sender] => move |_| { - sender.input(Self::Input::Close) - }, - }, - pack_end: add_btn = >k::Button { - set_label: "Add", - add_css_class: "suggested-action", - #[track = "model.changed(AddCustomPluginWin::can_add())"] - set_sensitive: model.can_add, - connect_clicked[sender] => move |_| { - sender.input(Self::Input::Add) - }, - }, - #[wrap(Some)] - set_title_widget: title_label = &adw::WindowTitle { - set_title: "Add Custom Plugin", - }, - }, - #[wrap(Some)] - set_content: content = &adw::PreferencesPage { - set_hexpand: true, - set_vexpand: true, - add: grp = &adw::PreferencesGroup { - add: &entry_row( - "Plugin Name", - "", - clone!( - #[strong] sender, - move |row| sender.input(Self::Input::OnNameChange(row.text().to_string())) - ) - ), - add: &file_row( - "Plugin Executable", - None, - None, - Some(model.parent.clone()), - clone!( - #[strong] sender, - move |path_s| sender.input(Self::Input::OnExecPathChange(path_s)) - ) - ) - }, - }, - }, - } - } - - fn update(&mut self, message: Self::Input, sender: ComponentSender) { - self.reset(); - - match message { - Self::Input::Present => self.win.as_ref().unwrap().present(Some(&self.parent)), - Self::Input::Close => { - self.win.as_ref().unwrap().close(); - } - Self::Input::Add => { - if self.plugin.validate() { - sender - .output(Self::Output::Add(self.plugin.clone())) - .expect(SENDER_IO_ERR_MSG); - self.win.as_ref().unwrap().close(); - } - } - Self::Input::OnNameChange(name) => { - self.plugin.appid = if !name.is_empty() { - format!("{APP_ID}.customPlugin.{name}") - } else { - String::default() - }; - self.plugin.name = name; - self.set_can_add(self.plugin.validate()); - } - Self::Input::OnExecPathChange(ep) => { - self.plugin.exec_path = ep.map(PathBuf::from); - self.set_can_add(self.plugin.validate()); - } - } - } - - fn init( - init: Self::Init, - root: Self::Root, - sender: ComponentSender, - ) -> ComponentParts { - let mut model = Self { - tracker: 0, - win: None, - parent: init.parent, - can_add: false, - plugin: Plugin { - short_description: Some("Custom Plugin".into()), - ..Default::default() - }, - }; - let widgets = view_output!(); - model.win = Some(widgets.win.clone()); - - ComponentParts { model, widgets } - } -} diff --git a/src/ui/plugins/mod.rs b/src/ui/plugins/mod.rs deleted file mode 100644 index cb18440..0000000 --- a/src/ui/plugins/mod.rs +++ /dev/null @@ -1,189 +0,0 @@ -pub mod add_custom_plugin_win; -pub mod store; -mod store_detail; -mod store_row_factory; - -use crate::{ - constants::APP_ID, - downloader::{cache_file_path, download_file_async}, - file_builders::wayvr_dashboard_config::{ - WayVrDashboardConfigFragment, WayVrDashboardConfigFragmentInner, - }, - paths::get_plugins_dir, - util::file_utils::{get_writer, mark_as_executable}, - xdg::XDG, -}; -use anyhow::bail; -use serde::{Deserialize, Serialize}; -use std::{ - collections::HashMap, - fs::{create_dir_all, remove_file}, - path::PathBuf, -}; - -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Default)] -pub enum PluginType { - #[default] - Executable, - WayVrApp, - WayVrDashboard, -} - -impl PluginType { - pub fn launches_directly(&self) -> bool { - self == &Self::Executable - } -} - -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Default)] -pub struct Plugin { - pub appid: String, - pub name: String, - pub author: Option, - pub icon_url: Option, - pub version: Option, - pub short_description: Option, - pub description: Option, - pub homepage_url: Option, - pub screenshots: Vec, - /// either one of exec_url or exec_path must be provided - pub exec_url: Option, - /// either one of exec_url or exec_path must be provided - pub exec_path: Option, - /// options and arguments that should be passed to the plugin executable - pub args: Option>, - pub env_vars: Option>, - /// defined as a list of appids of other plugins - pub dependencies: Option>, - /// defined as a list of appids of other plugins - /// all plugins of type WayVrDashboard should conflict with each other by default - pub conflicts: Option>, - #[serde(default = "PluginType::default")] - pub plugin_type: PluginType, -} - -impl Plugin { - fn wayvr_config_fragment_filename(&self) -> String { - format!("99-{APP_ID}-plugin.{}.yaml", self.appid) - } - - fn wayvr_conf_dir() -> PathBuf { - XDG.get_config_home().join("wlxoverlay/wayvr.conf.d") - } - - pub fn enable(&self) -> anyhow::Result<()> { - match self.plugin_type { - PluginType::Executable => {} - PluginType::WayVrApp => todo!(), - PluginType::WayVrDashboard => { - let wayvr_conf_dir = Self::wayvr_conf_dir(); - if !wayvr_conf_dir.exists() { - create_dir_all(&wayvr_conf_dir)?; - } else if wayvr_conf_dir.is_file() { - bail!("wayvr.conf.d is a file and not a directory") - } - let config_fragment = WayVrDashboardConfigFragment { - dashboard: WayVrDashboardConfigFragmentInner { - exec: self - .executable() - .ok_or(anyhow::Error::msg("executable missing"))? - .to_string_lossy() - .to_string(), - args: self.args.as_ref().map(|args| args.join(" ")), - env: self - .env_vars - .as_ref() - .map(|vars| vars.iter().map(|(k, v)| format!("{k}={v}")).collect()), - }, - }; - let writer = - get_writer(&wayvr_conf_dir.join(self.wayvr_config_fragment_filename()))?; - serde_yaml::to_writer(writer, &config_fragment)?; - } - } - Ok(()) - } - - pub fn disable(&self) -> anyhow::Result<()> { - match self.plugin_type { - PluginType::Executable => {} - PluginType::WayVrApp => todo!(), - PluginType::WayVrDashboard => { - let wayvr_conf_dir = Self::wayvr_conf_dir(); - remove_file(wayvr_conf_dir.join(self.wayvr_config_fragment_filename()))?; - } - }; - Ok(()) - } - - pub fn set_enabled(&self, enabled: bool) -> anyhow::Result<()> { - if enabled { - self.enable() - } else { - self.disable() - } - } - - pub fn executable(&self) -> Option { - if self.exec_path.is_some() { - self.exec_path.clone() - } else { - let canonical = self.canonical_exec_path(); - if canonical.is_file() { - Some(canonical) - } else { - None - } - } - } - - pub fn canonical_exec_path(&self) -> PathBuf { - get_plugins_dir().join(&self.appid) - } - - pub fn is_installed(&self) -> bool { - self.executable().as_ref().is_some_and(|p| p.is_file()) - } - - pub fn mark_as_executable(&self) -> anyhow::Result<()> { - if let Some(p) = self.executable().as_ref() { - mark_as_executable(p) - } else { - bail!("no executable found for plugin") - } - } - - /// validate if the plugin can be displayed correctly and run - pub fn validate(&self) -> bool { - !self.appid.is_empty() - && !self.name.is_empty() - && self.executable().as_ref().is_some_and(|p| p.is_file()) - } -} - -/// urls to manifest json files representing plugins. -/// each manifest should be json and the link should always point to the latest version -const MANIFESTS: [&str;3] = [ - "https://github.com/galister/wlx-overlay-s/raw/refs/heads/meta/com.github.galister.wlx-overlay-s.json", - "https://github.com/StardustXR/telescope/raw/refs/heads/main/envision/org.stardustxr.telescope.json", - "https://github.com/olekolek1000/wayvr-dashboard/raw/refs/heads/meta/dev.oo8.wayvr_dashboard.json", -]; - -pub async fn refresh_plugins() -> anyhow::Result>> { - let mut results = Vec::new(); - for jh in MANIFESTS - .iter() - .map(|url| -> tokio::task::JoinHandle> { - tokio::spawn(async move { - let path = cache_file_path(url, Some("json")); - download_file_async(url, &path).await?; - Ok(serde_json::from_str::( - &tokio::fs::read_to_string(path).await?, - )?) - }) - }) - { - results.push(jh.await?); - } - Ok(results) -} diff --git a/src/ui/plugins/store.rs b/src/ui/plugins/store.rs deleted file mode 100644 index 0faf99a..0000000 --- a/src/ui/plugins/store.rs +++ /dev/null @@ -1,552 +0,0 @@ -use super::{ - add_custom_plugin_win::{ - AddCustomPluginWin, AddCustomPluginWinInit, AddCustomPluginWinMsg, AddCustomPluginWinOutMsg, - }, - refresh_plugins, - store_detail::{StoreDetail, StoreDetailMsg, StoreDetailOutMsg}, - store_row_factory::{StoreRowModel, StoreRowModelInit, StoreRowModelMsg, StoreRowModelOutMsg}, - Plugin, -}; -use crate::{ - config::PluginConfig, - downloader::download_file_async, - ui::{alert::alert, SENDER_IO_ERR_MSG}, -}; -use adw::prelude::*; -use relm4::{factory::AsyncFactoryVecDeque, prelude::*}; -use std::{collections::HashMap, fs::remove_file}; -use tracing::{debug, error}; - -#[tracker::track] -pub struct PluginStore { - #[tracker::do_not_track] - win: Option, - #[tracker::do_not_track] - plugin_rows: Option>, - #[tracker::do_not_track] - details: AsyncController, - #[tracker::do_not_track] - main_stack: Option, - #[tracker::do_not_track] - config_plugins: HashMap, - refreshing: bool, - locked: bool, - plugins: Vec, - #[tracker::do_not_track] - add_custom_plugin_win: Option>, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum PluginStoreSignalSource { - Row, - Detail, -} - -#[derive(Debug)] -pub enum PluginStoreMsg { - Present, - /// sets state and calls DoRefresh - Refresh, - /// called by Refresh - DoRefresh, - Install(Plugin), - InstallDownload(Vec), - Remove(Plugin), - SetEnabled(PluginStoreSignalSource, Plugin, bool), - ShowDetails(usize), - ShowPluginList, - PresentAddCustomPluginWin, - AddCustomPlugin(Plugin), -} - -#[derive(Debug)] -pub struct PluginStoreInit { - pub config_plugins: HashMap, -} - -#[derive(Debug)] -pub enum PluginStoreOutMsg { - UpdateConfigPlugins(HashMap), -} - -impl PluginStore { - fn refresh_plugin_rows(&mut self) { - let mut guard = self.plugin_rows.as_mut().unwrap().guard(); - guard.clear(); - self.plugins.iter().for_each(|plugin| { - guard.push_back(StoreRowModelInit { - plugin: plugin.clone(), - enabled: self - .config_plugins - .get(&plugin.appid) - .is_some_and(|cp| cp.enabled), - needs_update: self - .config_plugins - .get(&plugin.appid) - .is_some_and(|cp| cp.plugin.version != plugin.version), - }); - }); - } - - fn add_plugin_to_config(&mut self, sender: &relm4::AsyncComponentSender, plugin: Plugin) { - self.config_plugins - .insert(plugin.appid.clone(), PluginConfig::from(&plugin)); - sender - .output(PluginStoreOutMsg::UpdateConfigPlugins( - self.config_plugins.clone(), - )) - .expect(SENDER_IO_ERR_MSG); - } -} - -#[relm4::component(pub async)] -impl AsyncComponent for PluginStore { - type Init = PluginStoreInit; - type Input = PluginStoreMsg; - type Output = PluginStoreOutMsg; - type CommandOutput = (); - - view! { - #[name(win)] - adw::Window { - set_title: Some("Plugins"), - #[name(main_stack)] - gtk::Stack { - add_child = &adw::ToolbarView { - set_top_bar_style: adw::ToolbarStyle::Flat, - add_top_bar: headerbar = &adw::HeaderBar { - pack_start: add_custom_plugin_btn = >k::Button { - set_icon_name: "list-add-symbolic", - set_tooltip_text: Some("Add custom plugin"), - #[track = "model.changed(PluginStore::refreshing()) || model.changed(PluginStore::locked())"] - set_sensitive: !(model.refreshing || model.locked), - connect_clicked[sender] => move |_| { - sender.input(Self::Input::PresentAddCustomPluginWin) - }, - }, - pack_end: refreshbtn = >k::Button { - set_icon_name: "view-refresh-symbolic", - set_tooltip_text: Some("Refresh"), - #[track = "model.changed(PluginStore::refreshing()) || model.changed(PluginStore::locked())"] - set_sensitive: !(model.refreshing || model.locked), - connect_clicked[sender] => move |_| { - sender.input(Self::Input::Refresh); - }, - }, - }, - #[wrap(Some)] - set_content: inner = >k::Box { - set_orientation: gtk::Orientation::Vertical, - set_hexpand: true, - set_vexpand: true, - gtk::Stack { - set_hexpand: true, - set_vexpand: true, - add_child = >k::ScrolledWindow { - set_hscrollbar_policy: gtk::PolicyType::Never, - set_hexpand: true, - set_vexpand: true, - adw::Clamp { - #[name(listbox)] - gtk::ListBox { - #[track = "model.changed(PluginStore::refreshing()) || model.changed(PluginStore::locked())"] - set_sensitive: !(model.refreshing || model.locked), - add_css_class: "boxed-list", - set_valign: gtk::Align::Start, - set_margin_all: 12, - set_selection_mode: gtk::SelectionMode::None, - connect_row_activated[sender] => move |_, row| { - sender.input( - Self::Input::ShowDetails( - row.index() as usize - ) - ); - }, - } - } - } -> { - set_name: "pluginlist" - }, - add_child = >k::Spinner { - set_hexpand: true, - set_vexpand: true, - set_width_request: 32, - set_height_request: 32, - set_valign: gtk::Align::Center, - set_halign: gtk::Align::Center, - #[track = "model.changed(PluginStore::refreshing())"] - set_spinning: model.refreshing, - } -> { - set_name: "spinner" - }, - add_child = &adw::StatusPage { - set_hexpand: true, - set_vexpand: true, - set_title: "No Plugins Found", - set_description: Some("Make sure you're connected to the internet and refresh"), - set_icon_name: Some("application-x-addon-symbolic"), - } -> { - set_name: "emptystate" - }, - #[track = "model.changed(PluginStore::refreshing()) || model.changed(PluginStore::plugins())"] - set_visible_child_name: if model.refreshing { - "spinner" - } else if model.plugins.is_empty() { - "emptystate" - } else { - "pluginlist" - }, - }, - } - } -> { - set_name: "listview" - }, - add_child = &adw::Bin { - #[track = "model.changed(PluginStore::refreshing()) || model.changed(PluginStore::locked())"] - set_sensitive: !(model.refreshing || model.locked), - set_child: Some(details_view), - } -> { - set_name: "detailsview", - }, - set_visible_child_name: "listview", - } - } - } - - async fn update( - &mut self, - message: Self::Input, - sender: AsyncComponentSender, - _root: &Self::Root, - ) { - self.reset(); - - match message { - Self::Input::Present => { - self.win.as_ref().unwrap().present(); - sender.input(Self::Input::Refresh); - } - Self::Input::AddCustomPlugin(plugin) => { - if self.config_plugins.contains_key(&plugin.appid) { - alert( - "Failed to add custom plugin", - Some("A plugin with the same name already exists"), - Some(&self.win.as_ref().unwrap().clone().upcast::()), - ); - return; - } - self.add_plugin_to_config(&sender, plugin); - sender.input(Self::Input::Refresh); - } - Self::Input::PresentAddCustomPluginWin => { - let add_win = AddCustomPluginWin::builder() - .launch(AddCustomPluginWinInit { - parent: self.win.as_ref().unwrap().clone().upcast(), - }) - .forward(sender.input_sender(), |msg| match msg { - AddCustomPluginWinOutMsg::Add(plugin) => { - Self::Input::AddCustomPlugin(plugin) - } - }); - add_win.sender().emit(AddCustomPluginWinMsg::Present); - self.add_custom_plugin_win = Some(add_win); - } - Self::Input::Refresh => { - self.set_refreshing(true); - sender.input(Self::Input::DoRefresh); - } - Self::Input::DoRefresh => { - let mut plugins = match refresh_plugins().await { - Err(e) => { - error!("failed to refresh plugins: {e}"); - Vec::new() - } - Ok(results) => results - .into_iter() - .filter_map(|res| match res { - Ok(plugin) => Some(plugin), - Err(e) => { - error!("failed to refresh single plugin manifest: {e}"); - None - } - }) - .collect(), - }; - { - let appids_from_web = plugins - .iter() - .map(|p| p.appid.clone()) - .collect::>(); - // add all plugins that are in config but not retrieved - plugins.extend(self.config_plugins.values().filter_map(|cp| { - if appids_from_web.contains(&cp.plugin.appid) { - None - } else { - Some(Plugin::from(cp)) - } - })); - } - self.set_plugins(plugins); - self.refresh_plugin_rows(); - self.set_refreshing(false); - } - Self::Input::Install(plugin) => { - self.set_locked(true); - let mut plugins = vec![plugin]; - for dep in plugins[0].dependencies.clone().unwrap_or_default() { - if let Some(dep_plugin) = self - .plugins - .iter() - .find(|plugin| plugin.appid == dep) - .cloned() - { - plugins.push(dep_plugin); - } else { - error!( - "unable to find dependency for {}: {}", - plugins[0].appid, dep - ); - alert( - "Missing dependencies", - Some(&format!( - "{} depends on unknown plugin {}", - plugins[0].name, dep - )), - Some(&self.win.clone().unwrap().upcast::()), - ); - return; - } - } - sender.input(Self::Input::InstallDownload(plugins)) - } - Self::Input::InstallDownload(plugins) => { - for plugin in plugins { - let mut plugin = plugin.clone(); - match plugin.exec_url.as_ref() { - Some(url) => { - let exec_path = plugin.canonical_exec_path(); - if let Err(e) = download_file_async(url, &exec_path).await { - alert( - "Download failed", - Some(&format!( - "Downloading {} {} failed:\n\n{e}", - plugin.name, - plugin - .version - .as_ref() - .unwrap_or(&"(no version)".to_string()) - )), - Some( - &self.win.as_ref().unwrap().clone().upcast::(), - ), - ); - } else { - plugin.exec_path = Some(exec_path); - if let Err(e) = plugin.enable() { - error!("failed to enable plugin {}: {e}", plugin.appid); - alert( - "Failed to enable plugin", - Some(&e.to_string()), - Some(&self.win.clone().unwrap().upcast::()), - ); - return; - } - self.add_plugin_to_config(&sender, plugin.clone()); - } - } - None => { - alert( - "Download failed", - Some(&format!( - "Downloading {} {} failed:\n\nNo executable url provided for this plugin, this is likely a bug!", - plugin.name, - plugin.version.as_ref().unwrap_or(&"(no version)".to_string())) - ), - Some(&self.win.as_ref().unwrap().clone().upcast::()) - ); - } - }; - self.refresh_plugin_rows(); - self.details - .emit(StoreDetailMsg::Refresh(plugin.appid, true, false)); - } - self.set_locked(false); - } - Self::Input::Remove(plugin) => { - self.set_locked(true); - if let Err(e) = plugin.disable() { - error!("failed to disable plugin {}: {e}", plugin.appid); - alert( - "Failed to disable plugin", - Some(&e.to_string()), - Some(&self.win.clone().unwrap().upcast::()), - ); - return; - } - if let Some(exec) = plugin.executable() { - // delete executable only if it's not a custom plugin - if exec.is_file() && plugin.exec_url.is_some() { - if let Err(e) = remove_file(&exec) { - alert( - "Failed removing plugin", - Some(&format!( - "Could not remove plugin executable {}:\n\n{e}", - exec.to_string_lossy() - )), - Some(&self.win.as_ref().unwrap().clone().upcast::()), - ); - } - } - } - self.config_plugins.remove(&plugin.appid); - self.set_plugins( - self.plugins - .clone() - .into_iter() - .filter(|p| !(p.appid == plugin.appid && p.exec_url.is_none())) - .collect(), - ); - sender - .output(Self::Output::UpdateConfigPlugins( - self.config_plugins.clone(), - )) - .expect(SENDER_IO_ERR_MSG); - self.refresh_plugin_rows(); - self.details - .emit(StoreDetailMsg::Refresh(plugin.appid, false, false)); - self.set_locked(false); - } - Self::Input::SetEnabled(signal_sender, plugin, enabled) => { - if let Some(cp) = self.config_plugins.get_mut(&plugin.appid) { - if let Err(e) = plugin.set_enabled(enabled) { - error!( - "failed to {} plugin {}: {e}", - if enabled { "enable" } else { "disable" }, - plugin.appid - ); - alert( - &format!( - "Failed to {} plugin", - if enabled { "enable" } else { "disable" } - ), - Some(&e.to_string()), - Some(&self.win.clone().unwrap().upcast::()), - ); - return; - } - cp.enabled = enabled; - if signal_sender == PluginStoreSignalSource::Detail { - if let Some(row) = self - .plugin_rows - .as_mut() - .unwrap() - .guard() - .iter() - .find(|row| row.is_some_and(|row| row.plugin.appid == plugin.appid)) - .flatten() - { - row.input_sender.emit(StoreRowModelMsg::Refresh( - enabled, - cp.plugin.version != plugin.version, - )); - } else { - error!("could not find corresponding listbox row") - } - } - if signal_sender == PluginStoreSignalSource::Row { - self.details.emit(StoreDetailMsg::Refresh( - plugin.appid, - enabled, - cp.plugin.version != plugin.version, - )); - } - } else { - debug!( - "failed to set plugin {} enabled: could not find in hashmap", - plugin.appid - ) - } - sender - .output(Self::Output::UpdateConfigPlugins( - self.config_plugins.clone(), - )) - .expect(SENDER_IO_ERR_MSG); - } - // we use index here because it's the listbox not the row that can - // send this signal, so I don't directly have the plugin object - Self::Input::ShowDetails(index) => { - if let Some(plugin) = self.plugins.get(index) { - self.details.sender().emit(StoreDetailMsg::SetPlugin( - plugin.clone(), - self.config_plugins - .get(&plugin.appid) - .is_some_and(|cp| cp.enabled), - self.config_plugins - .get(&plugin.appid) - .is_some_and(|cp| cp.plugin.version != plugin.version), - )); - self.main_stack - .as_ref() - .unwrap() - .set_visible_child_name("detailsview"); - } else { - error!("plugins list index out of range!") - } - } - Self::Input::ShowPluginList => { - self.main_stack - .as_ref() - .unwrap() - .set_visible_child_name("listview"); - } - } - } - - async fn init( - init: Self::Init, - root: Self::Root, - sender: AsyncComponentSender, - ) -> AsyncComponentParts { - let mut model = Self { - tracker: 0, - refreshing: false, - locked: false, - win: None, - plugins: Vec::default(), - plugin_rows: None, - details: StoreDetail::builder() - .launch(()) - .forward(sender.input_sender(), move |msg| match msg { - StoreDetailOutMsg::GoBack => Self::Input::ShowPluginList, - StoreDetailOutMsg::Install(plugin) => Self::Input::Install(plugin), - StoreDetailOutMsg::Remove(plugin) => Self::Input::Remove(plugin), - StoreDetailOutMsg::SetEnabled(plugin, enabled) => { - Self::Input::SetEnabled(PluginStoreSignalSource::Detail, plugin, enabled) - } - }), - config_plugins: init.config_plugins, - main_stack: None, - add_custom_plugin_win: None, - }; - - let details_view = model.details.widget(); - - let widgets = view_output!(); - - model.win = Some(widgets.win.clone()); - model.plugin_rows = Some( - AsyncFactoryVecDeque::builder() - .launch(widgets.listbox.clone()) - .forward(sender.input_sender(), move |msg| match msg { - StoreRowModelOutMsg::Install(appid) => Self::Input::Install(appid), - StoreRowModelOutMsg::Remove(appid) => Self::Input::Remove(appid), - StoreRowModelOutMsg::SetEnabled(plugin, enabled) => { - Self::Input::SetEnabled(PluginStoreSignalSource::Row, plugin, enabled) - } - }), - ); - model.main_stack = Some(widgets.main_stack.clone()); - - AsyncComponentParts { model, widgets } - } -} diff --git a/src/ui/plugins/store_detail.rs b/src/ui/plugins/store_detail.rs deleted file mode 100644 index 4bd9d9f..0000000 --- a/src/ui/plugins/store_detail.rs +++ /dev/null @@ -1,373 +0,0 @@ -use super::Plugin; -use crate::{downloader::cache_file, ui::SENDER_IO_ERR_MSG}; -use adw::prelude::*; -use relm4::prelude::*; -use tracing::{error, warn}; - -#[tracker::track] -pub struct StoreDetail { - plugin: Option, - enabled: bool, - #[tracker::do_not_track] - carousel: Option, - #[tracker::do_not_track] - icon: Option, - needs_update: bool, -} - -#[allow(clippy::large_enum_variant)] -#[derive(Debug)] -pub enum StoreDetailMsg { - SetPlugin(Plugin, bool, bool), - SetIcon, - SetScreenshots, - Refresh(String, bool, bool), - Install, - Remove, - SetEnabled(bool), - OpenHomepage, -} - -#[derive(Debug)] -pub enum StoreDetailOutMsg { - Install(Plugin), - Remove(Plugin), - GoBack, - SetEnabled(Plugin, bool), -} - -#[relm4::component(pub async)] -impl AsyncComponent for StoreDetail { - type Init = (); - type Input = StoreDetailMsg; - type Output = StoreDetailOutMsg; - type CommandOutput = (); - - view! { - adw::ToolbarView { - set_top_bar_style: adw::ToolbarStyle::Flat, - add_top_bar: headerbar = &adw::HeaderBar { - #[wrap(Some)] - set_title_widget: title = &adw::WindowTitle { - #[track = "model.changed(Self::plugin())"] - set_title: model - .plugin - .as_ref() - .map(|p| p.name.as_str()) - .unwrap_or_default(), - }, - pack_start: backbtn = >k::Button { - set_icon_name: "go-previous-symbolic", - set_tooltip_text: Some("Back"), - connect_clicked[sender] => move |_| { - sender.output(Self::Output::GoBack).expect(SENDER_IO_ERR_MSG); - } - }, - }, - #[wrap(Some)] - set_content: inner = >k::ScrolledWindow { - set_hscrollbar_policy: gtk::PolicyType::Never, - set_hexpand: true, - set_vexpand: true, - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_hexpand: true, - set_vexpand: true, - set_margin_top: 12, - set_margin_bottom: 48, - set_margin_start: 12, - set_margin_end: 12, - set_spacing: 24, - adw::Clamp { // icon, name, buttons - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_hexpand: true, - set_vexpand: false, - gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_hexpand: true, - #[name(icon)] - gtk::Image { - set_icon_name: Some("application-x-addon-symbolic"), - set_margin_end: 12, - set_pixel_size: 96, - }, - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_hexpand: true, - set_valign: gtk::Align::Center, - set_spacing: 6, - gtk::Label { - add_css_class: "title-2", - set_xalign: 0.0, - #[track = "model.changed(Self::plugin())"] - set_text: model - .plugin - .as_ref() - .map(|p| p.name.as_str()) - .unwrap_or_default(), - set_ellipsize: gtk::pango::EllipsizeMode::None, - set_wrap: true, - }, - gtk::Label { - add_css_class: "dim-label", - set_xalign: 0.0, - #[track = "model.changed(Self::plugin())"] - set_visible: model - .plugin - .as_ref() - .is_some_and(|p| p.author.is_some()), - #[track = "model.changed(Self::plugin())"] - set_text: model - .plugin - .as_ref() - .and_then( - |p| p.author.as_deref() - ) - .unwrap_or_default(), - set_ellipsize: gtk::pango::EllipsizeMode::None, - set_wrap: true, - }, - }, - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_halign: gtk::Align::Center, - set_valign: gtk::Align::Center, - set_spacing: 6, - gtk::Button { - #[track = "model.changed(Self::plugin())"] - set_visible: !model - .plugin - .as_ref() - .is_some_and(|p| p.is_installed()), - set_label: "Install", - add_css_class: "suggested-action", - connect_clicked[sender] => move |_| { - sender.input(Self::Input::Install); - } - }, - gtk::Button { - #[track = "model.changed(Self::plugin())"] - set_visible: model - .plugin - .as_ref() - .is_some_and(|p| p.is_installed()), - set_label: "Remove", - add_css_class: "destructive-action", - connect_clicked[sender] => move |_| { - sender.input(Self::Input::Remove); - } - }, - gtk::Button { - #[track = "model.changed(Self::plugin()) || model.changed(Self::needs_update())"] - set_visible: model - .plugin - .as_ref() - .is_some_and(|p| p.is_installed()) && model.needs_update, - add_css_class: "suggested-action", - set_label: "Update", - set_valign: gtk::Align::Center, - set_halign: gtk::Align::Center, - connect_clicked[sender] => move |_| { - sender.input(Self::Input::Install); - } - }, - gtk::Switch { - #[track = "model.changed(Self::plugin())"] - set_visible: model.plugin.as_ref() - .is_some_and(|p| p.is_installed()), - #[track = "model.changed(Self::enabled())"] - set_active: model.enabled, - set_tooltip_text: Some("Plugin enabled"), - set_valign: gtk::Align::Center, - set_halign: gtk::Align::Center, - connect_state_set[sender] => move |_, state| { - sender.input(Self::Input::SetEnabled(state)); - gtk::glib::Propagation::Proceed - } - }, - gtk::Button { - #[track = "model.changed(Self::plugin())"] - set_visible: model.plugin.as_ref() - .is_some_and(|p| p.homepage_url.is_some()), - set_label: "Homepage", - connect_clicked[sender] => move |_| { - sender.input(Self::Input::OpenHomepage); - } - } - } - } - }, - }, - gtk::Box { // screenshots - set_orientation: gtk::Orientation::Vertical, - set_spacing: 12, - #[name(carousel)] - adw::Carousel { - set_allow_mouse_drag: true, - set_allow_scroll_wheel: false, - set_spacing: 24, - }, - adw::CarouselIndicatorDots { - set_carousel: Some(&carousel), - }, - }, - adw::Clamp { // description - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - gtk::Label { - set_xalign: 0.0, - #[track = "model.changed(Self::plugin())"] - set_text: model - .plugin - .as_ref() - .and_then(|p| p - .description - .as_deref() - ).unwrap_or(""), - set_ellipsize: gtk::pango::EllipsizeMode::None, - set_wrap: true, - set_justify: gtk::Justification::Fill, - }, - }, - }, - } - } - } - } - - async fn update( - &mut self, - message: Self::Input, - sender: AsyncComponentSender, - _root: &Self::Root, - ) { - self.reset(); - - match message { - Self::Input::OpenHomepage => { - if let Some(plugin) = self.plugin.as_ref() { - if let Some(homepage) = plugin.homepage_url.as_ref() { - if let Err(e) = gtk::gio::AppInfo::launch_default_for_uri( - homepage, - gtk::gio::AppLaunchContext::NONE, - ) { - error!("opening uri {homepage}: {e}"); - }; - } - } - } - Self::Input::SetPlugin(p, enabled, needs_update) => { - self.set_plugin(Some(p)); - self.set_enabled(enabled); - self.set_needs_update(needs_update); - sender.input(Self::Input::SetIcon); - sender.input(Self::Input::SetScreenshots); - } - Self::Input::SetIcon => { - if let Some(plugin) = self.plugin.as_ref() { - if let Some(url) = plugin.icon_url.as_ref() { - match cache_file(url, None).await { - Ok(dest) => { - self.icon.as_ref().unwrap().set_from_file(Some(dest)); - } - Err(e) => { - warn!("Failed downloading icon '{url}': {e}"); - } - }; - } else { - self.icon - .as_ref() - .unwrap() - .set_icon_name(Some("application-x-addon-symbolic")); - } - } - } - Self::Input::SetScreenshots => { - let carousel = self.carousel.as_ref().unwrap().clone(); - while let Some(child) = carousel.first_child() { - carousel.remove(&child); - } - if let Some(plugin) = self.plugin.as_ref() { - carousel - .parent() - .unwrap() - .set_visible(!plugin.screenshots.is_empty()); - for url in plugin.screenshots.iter() { - match cache_file(url, None).await { - Ok(dest) => { - let pic = gtk::Picture::builder() - .height_request(300) - .css_classes(["card"]) - .overflow(gtk::Overflow::Hidden) - .valign(gtk::Align::Center) - .build(); - pic.set_filename(Some(dest)); - let clamp = adw::Clamp::builder().child(&pic).build(); - carousel.append(&clamp); - } - Err(e) => { - warn!("failed downloading screenshot '{url}': {e}"); - } - }; - } - } - } - Self::Input::Refresh(appid, enabled, needs_update) => { - if self.plugin.as_ref().is_some_and(|p| p.appid == appid) { - self.mark_all_changed(); - self.set_enabled(enabled); - self.set_needs_update(needs_update); - } - } - Self::Input::Install => { - if let Some(plugin) = self.plugin.as_ref() { - sender - .output(Self::Output::Install(plugin.clone())) - .expect(SENDER_IO_ERR_MSG); - } - } - Self::Input::Remove => { - if let Some(plugin) = self.plugin.as_ref() { - sender - .output(Self::Output::Remove(plugin.clone())) - .expect(SENDER_IO_ERR_MSG); - if plugin.exec_url.is_none() { - sender - .output(Self::Output::GoBack) - .expect(SENDER_IO_ERR_MSG); - } - } - } - Self::Input::SetEnabled(enabled) => { - self.set_enabled(enabled); - if let Some(plugin) = self.plugin.as_ref() { - sender - .output(Self::Output::SetEnabled(plugin.clone(), enabled)) - .expect(SENDER_IO_ERR_MSG); - } - } - } - } - - async fn init( - _init: Self::Init, - root: Self::Root, - sender: AsyncComponentSender, - ) -> AsyncComponentParts { - let mut model = Self { - tracker: 0, - plugin: None, - enabled: false, - carousel: None, - icon: None, - needs_update: false, - }; - let widgets = view_output!(); - - model.carousel = Some(widgets.carousel.clone()); - model.icon = Some(widgets.icon.clone()); - - AsyncComponentParts { model, widgets } - } -} diff --git a/src/ui/plugins/store_row_factory.rs b/src/ui/plugins/store_row_factory.rs deleted file mode 100644 index 72f9e02..0000000 --- a/src/ui/plugins/store_row_factory.rs +++ /dev/null @@ -1,225 +0,0 @@ -use super::Plugin; -use crate::{downloader::cache_file, ui::SENDER_IO_ERR_MSG}; -use gtk::prelude::*; -use relm4::{ - factory::AsyncFactoryComponent, prelude::DynamicIndex, AsyncFactorySender, RelmWidgetExt, -}; -use tracing::error; - -#[derive(Debug)] -#[tracker::track] -pub struct StoreRowModel { - #[no_eq] - pub plugin: Plugin, - #[tracker::do_not_track] - icon: Option, - #[tracker::do_not_track] - pub input_sender: relm4::Sender, - pub enabled: bool, - pub needs_update: bool, -} - -#[derive(Debug)] -pub struct StoreRowModelInit { - pub plugin: Plugin, - pub enabled: bool, - pub needs_update: bool, -} - -#[derive(Debug)] -pub enum StoreRowModelMsg { - LoadIcon, - /// params: enabled, needs_update - Refresh(bool, bool), - SetEnabled(bool), -} - -#[derive(Debug)] -pub enum StoreRowModelOutMsg { - Install(Plugin), - Remove(Plugin), - SetEnabled(Plugin, bool), -} - -#[relm4::factory(async pub)] -impl AsyncFactoryComponent for StoreRowModel { - type Init = StoreRowModelInit; - type Input = StoreRowModelMsg; - type Output = StoreRowModelOutMsg; - type CommandOutput = (); - type ParentWidget = gtk::ListBox; - - view! { - root = gtk::ListBoxRow { - gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_hexpand: true, - set_vexpand: false, - set_spacing: 12, - set_margin_all: 12, - #[name(icon)] - gtk::Image { - set_icon_name: Some("application-x-addon-symbolic"), - set_icon_size: gtk::IconSize::Large, - }, - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_spacing: 6, - set_hexpand: true, - set_vexpand: true, - gtk::Label { - add_css_class: "title-3", - set_hexpand: true, - set_xalign: 0.0, - set_text: &self.plugin.name, - set_ellipsize: gtk::pango::EllipsizeMode::None, - set_wrap: true, - }, - gtk::Label { - add_css_class: "dim-label", - set_hexpand: true, - set_xalign: 0.0, - set_text: self.plugin.short_description - .as_deref() - .unwrap_or(""), - set_ellipsize: gtk::pango::EllipsizeMode::None, - set_wrap: true, - }, - }, - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_spacing: 6, - set_vexpand: true, - set_valign: gtk::Align::Center, - set_halign: gtk::Align::Center, - gtk::Button { - #[track = "self.changed(StoreRowModel::plugin())"] - set_visible: !self.plugin.is_installed(), - set_icon_name: "folder-download-symbolic", - add_css_class: "suggested-action", - set_tooltip_text: Some("Install"), - set_valign: gtk::Align::Center, - set_halign: gtk::Align::Center, - connect_clicked[sender, plugin] => move |_| { - sender - .output(Self::Output::Install( - plugin.clone(), - )) - .expect(SENDER_IO_ERR_MSG); - } - }, - gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_spacing: 6, - set_valign: gtk::Align::Center, - set_halign: gtk::Align::Center, - gtk::Button { - #[track = "self.changed(StoreRowModel::plugin())"] - set_visible: self.plugin.is_installed(), - set_icon_name: "user-trash-symbolic", - add_css_class: "destructive-action", - set_tooltip_text: Some("Remove"), - set_valign: gtk::Align::Center, - set_halign: gtk::Align::Center, - connect_clicked[sender, plugin] => move |_| { - sender - .output(Self::Output::Remove( - plugin.clone(), - )) - .expect(SENDER_IO_ERR_MSG); - } - }, - gtk::Button { - #[track = "self.changed(StoreRowModel::plugin()) || self.changed(StoreRowModel::needs_update())"] - set_visible: self.plugin.is_installed() && self.needs_update, - set_icon_name: "view-refresh-symbolic", - add_css_class: "suggested-action", - set_tooltip_text: Some("Update"), - set_valign: gtk::Align::Center, - set_halign: gtk::Align::Center, - connect_clicked[sender, plugin] => move |_| { - sender - .output(Self::Output::Install( - plugin.clone(), - )) - .expect(SENDER_IO_ERR_MSG); - } - }, - }, - gtk::Switch { - #[track = "self.changed(StoreRowModel::plugin())"] - set_visible: self.plugin.is_installed(), - #[track = "self.changed(StoreRowModel::enabled())"] - set_active: self.enabled, - set_valign: gtk::Align::Center, - set_halign: gtk::Align::Center, - set_tooltip_text: Some("Plugin enabled"), - connect_state_set[sender] => move |_, state| { - sender.input(Self::Input::SetEnabled(state)); - gtk::glib::Propagation::Proceed - } - }, - }, - } - } - } - - async fn update(&mut self, message: Self::Input, sender: AsyncFactorySender) { - self.reset(); - - match message { - Self::Input::LoadIcon => { - if let Some(url) = self.plugin.icon_url.as_ref() { - match cache_file(url, None).await { - Ok(dest) => { - self.icon.as_ref().unwrap().set_from_file(Some(dest)); - } - Err(e) => { - error!("failed downloading icon '{url}': {e}"); - } - }; - } - } - Self::Input::SetEnabled(state) => { - self.set_enabled(state); - sender - .output(Self::Output::SetEnabled(self.plugin.clone(), state)) - .expect(SENDER_IO_ERR_MSG); - } - Self::Input::Refresh(enabled, needs_update) => { - self.mark_all_changed(); - self.set_enabled(enabled); - self.set_needs_update(needs_update); - } - } - } - - async fn init_model( - init: Self::Init, - _index: &DynamicIndex, - sender: AsyncFactorySender, - ) -> Self { - Self { - tracker: 0, - plugin: init.plugin, - enabled: init.enabled, - icon: None, - input_sender: sender.input_sender().clone(), - needs_update: init.needs_update, - } - } - - fn init_widgets( - &mut self, - _index: &DynamicIndex, - root: Self::Root, - _returned_widget: &::ReturnedWidget, - sender: AsyncFactorySender, - ) -> Self::Widgets { - let plugin = self.plugin.clone(); // for use in a signal handler - let widgets = view_output!(); - self.icon = Some(widgets.icon.clone()); - sender.input(Self::Input::LoadIcon); - widgets - } -} diff --git a/src/ui/preference_rows.rs b/src/ui/preference_rows.rs index 4502ebf..5159ec9 100644 --- a/src/ui/preference_rows.rs +++ b/src/ui/preference_rows.rs @@ -156,12 +156,13 @@ pub fn spin_row( row } -fn filedialog_row_base) + 'static + Clone>( +pub fn path_row) + 'static + Clone>( title: &str, description: Option<&str>, value: Option, + root_win: Option, cb: F, -) -> (adw::ActionRow, gtk::Label) { +) -> adw::ActionRow { let row = adw::ActionRow::builder() .title(title) .subtitle_lines(0) @@ -173,14 +174,14 @@ fn filedialog_row_base) + 'static + Clone>( row.set_subtitle(d); } - let path_label = gtk::Label::builder() + let path_label = >k::Label::builder() .label(match value.as_ref() { None => "(None)", Some(p) => p.as_str(), }) .wrap(true) .build(); - row.add_suffix(&path_label); + row.add_suffix(path_label); let clear_btn = gtk::Button::builder() .icon_name("edit-clear-symbolic") @@ -199,60 +200,6 @@ fn filedialog_row_base) + 'static + Clone>( cb(None) } )); - (row, path_label) -} - -pub fn file_row) + 'static + Clone>( - title: &str, - description: Option<&str>, - value: Option, - root_win: Option, - cb: F, -) -> adw::ActionRow { - let (row, path_label) = filedialog_row_base(title, description, value, cb.clone()); - - let filedialog = gtk::FileDialog::builder() - .modal(true) - .title(format!("Select {}", title)) - .build(); - - row.connect_activated(clone!( - #[weak] - path_label, - move |_| { - filedialog.open( - root_win.as_ref(), - gio::Cancellable::NONE, - clone!( - #[weak] - path_label, - #[strong] - cb, - move |res| { - if let Ok(file) = res { - if let Some(path) = file.path() { - let path_s = path.to_string_lossy().to_string(); - path_label.set_text(&path_s); - cb(Some(path_s)) - } - } - } - ), - ) - } - )); - - row -} - -pub fn path_row) + 'static + Clone>( - title: &str, - description: Option<&str>, - value: Option, - root_win: Option, - cb: F, -) -> adw::ActionRow { - let (row, path_label) = filedialog_row_base(title, description, value, cb.clone()); let filedialog = gtk::FileDialog::builder() .modal(true) .title(format!("Select Path for {}", title)) @@ -273,8 +220,8 @@ pub fn path_row) + 'static + Clone>( move |res| { if let Ok(file) = res { if let Some(path) = file.path() { - let path_s = path.to_string_lossy().to_string(); - path_label.set_text(&path_s); + let path_s = path.to_str().unwrap().to_string(); + path_label.set_text(path_s.as_str()); cb(Some(path_s)) } } diff --git a/src/ui/profile_editor.rs b/src/ui/profile_editor.rs index 99f7b7b..348df30 100644 --- a/src/ui/profile_editor.rs +++ b/src/ui/profile_editor.rs @@ -6,13 +6,12 @@ use super::{ }; use crate::{ env_var_descriptions::ENV_VAR_DESCRIPTIONS_AS_PARAGRAPH, - profile::{LighthouseDriver, OvrCompatibilityModuleType, Profile, XRServiceType}, + profile::{LighthouseDriver, Profile, XRServiceType}, }; use adw::prelude::*; use gtk::glib::{self, clone}; use relm4::{factory::AsyncFactoryVecDeque, prelude::*}; use std::{cell::RefCell, path::PathBuf, rc::Rc}; -use tracing::warn; #[tracker::track] pub struct ProfileEditor { @@ -130,6 +129,14 @@ impl SimpleComponent for ProfileEditor { prof.borrow_mut().prefix = n_path.unwrap_or_default().into(); }), ), + add: &entry_row("Autostart Command", + model.profile.borrow().autostart_command.as_ref().unwrap_or(&String::default()), + clone!(#[strong] prof, move |row| { + let txt = row.text().trim().to_string(); + prof.borrow_mut().autostart_command = + if txt.is_empty() {None} else {Some(txt)}; + }) + ), add: &switch_row("Dependency Check", Some("Warning: disabling dependency checks may result in build failures"), !model.profile.borrow().skip_dependency_check, @@ -209,43 +216,31 @@ impl SimpleComponent for ProfileEditor { ), }, add: model.xrservice_cmake_flags_rows.widget(), - add: ovr_comp_grp = &adw::PreferencesGroup { - set_title: "OpenVR Compatibility", - set_description: Some("OpenVR compatibility module, translates between OpenXR and OpenVR to run legacy OpenVR apps"), - add: &combo_row( - "OpenVR Module Type", - None, - model.profile.borrow().ovr_comp.mod_type.to_string().as_str(), - OvrCompatibilityModuleType::iter() - .map(OvrCompatibilityModuleType::to_string) - .collect::>(), - clone!(#[strong] prof, move |row| { - prof.borrow_mut().ovr_comp.mod_type = - OvrCompatibilityModuleType::from(row.selected()); - }), - ), + add: opencompgrp = &adw::PreferencesGroup { + set_title: "OpenComposite", + set_description: Some("OpenVR driver built on top of OpenXR"), add: &path_row( - "OpenVR Module Path", None, - Some(model.profile.borrow().ovr_comp.path.clone().to_string_lossy().to_string()), + "OpenComposite Path", None, + Some(model.profile.borrow().opencomposite_path.clone().to_string_lossy().to_string()), Some(init.root_win.clone()), clone!(#[strong] prof, move |n_path| { - prof.borrow_mut().ovr_comp.path = n_path.unwrap_or_default().into(); + prof.borrow_mut().opencomposite_path = n_path.unwrap_or_default().into(); }) ), add: &entry_row( - "OpenVR Compatibility Repo", - model.profile.borrow().ovr_comp.repo.clone().unwrap_or_default().as_str(), + "OpenComposite Repo", + model.profile.borrow().opencomposite_repo.clone().unwrap_or_default().as_str(), clone!(#[strong] prof, move |row| { let n_val = row.text().to_string(); - prof.borrow_mut().ovr_comp.repo = (!n_val.is_empty()).then_some(n_val); + prof.borrow_mut().opencomposite_repo = (!n_val.is_empty()).then_some(n_val); }) ), add: &entry_row( - "OpenVR Compatibility Branch", - model.profile.borrow().ovr_comp.branch.clone().unwrap_or_default().as_str(), + "OpenComposite Branch", + model.profile.borrow().opencomposite_branch.clone().unwrap_or_default().as_str(), clone!(#[strong] prof, move |row| { let n_val = row.text().to_string(); - prof.borrow_mut().ovr_comp.branch = (!n_val.is_empty()).then_some(n_val); + prof.borrow_mut().opencomposite_branch = (!n_val.is_empty()).then_some(n_val); }) ), }, @@ -504,14 +499,14 @@ impl SimpleComponent for ProfileEditor { .halign(gtk::Align::End) .build(); - let on_add = clone!( + add_btn.connect_clicked(clone!( #[strong] sender, #[weak] name_entry, #[weak] popover, - move || { + move |_| { let key_gstr = name_entry.text(); let key = key_gstr.trim(); if !key.is_empty() { @@ -520,13 +515,7 @@ impl SimpleComponent for ProfileEditor { sender.input($event(key.to_string())); } } - ); - name_entry.connect_activate(clone!( - #[strong] - on_add, - move |_| on_add() )); - add_btn.connect_clicked(move |_| on_add()); btn }}; } @@ -539,30 +528,17 @@ impl SimpleComponent for ProfileEditor { let profile = Rc::new(RefCell::new(init.profile)); let prof = profile.clone(); - let env_var_prefs_group = { - let pg = adw::PreferencesGroup::builder() - .title("Environment Variables") - .description(ENV_VAR_DESCRIPTIONS_AS_PARAGRAPH.as_str()) - .header_suffix(&add_env_var_btn) - .build(); - if let Some(desc) = pg - .first_child() - .and_then(|c| c.first_child()) - .and_then(|c| c.first_child()) - .and_then(|c| c.last_child()) - .and_downcast::() - { - desc.set_selectable(true); - } else { - warn!("failed to make env var preference group description selectable, please open a bug report"); - } - pg - }; let mut model = Self { profile, win: None, env_rows: AsyncFactoryVecDeque::builder() - .launch(env_var_prefs_group) + .launch( + adw::PreferencesGroup::builder() + .title("Environment Variables") + .description(ENV_VAR_DESCRIPTIONS_AS_PARAGRAPH.as_str()) + .header_suffix(&add_env_var_btn) + .build(), + ) .forward(sender.input_sender(), |msg| match msg { EnvVarModelOutMsg::Changed(name, value) => { ProfileEditorMsg::EnvVarChanged(name, value) diff --git a/src/ui/steamvr_calibration_box.rs b/src/ui/steamvr_calibration_box.rs index beae630..ff08b83 100644 --- a/src/ui/steamvr_calibration_box.rs +++ b/src/ui/steamvr_calibration_box.rs @@ -10,10 +10,10 @@ use relm4::{ }; use std::{ collections::{HashMap, VecDeque}, + path::Path, thread::sleep, time::Duration, }; -use tracing::error; #[tracker::track] pub struct SteamVrCalibrationBox { @@ -144,59 +144,55 @@ impl SimpleComponent for SteamVrCalibrationBox { } Self::Input::RunCalibration => { self.set_calibration_result(None); - match get_steamvr_bin_dir_path() { - Err(e) => { - error!("could not get SteamVR bin dir: {e}"); - self.set_calibration_success(false); - self.set_calibration_result(Some("SteamVR not found".into())); - } - Ok(bin_dir_p) => { - let steamvr_bin_dir = bin_dir_p.to_string_lossy().to_string(); - let mut env: HashMap = HashMap::new(); - env.insert("LD_LIBRARY_PATH".into(), steamvr_bin_dir.clone()); - let vrcmd = format!("{steamvr_bin_dir}/vrcmd"); - let server_worker = { - let mut jobs: VecDeque = VecDeque::new(); - jobs.push_back(WorkerJob::new_cmd( - Some(env.clone()), - vrcmd.clone(), - Some(vec!["--pollposes".into()]), - )); - JobWorker::new(jobs, sender.input_sender(), |msg| match msg { - JobWorkerOut::Log(_) => Self::Input::NoOp, - JobWorkerOut::Exit(code) => Self::Input::OnServerWorkerExit(code), - }) - }; - let cal_worker = { - let mut jobs: VecDeque = VecDeque::new(); - jobs.push_back(WorkerJob::new_func(Box::new(move || { - sleep(Duration::from_secs(2)); - FuncWorkerOut { - success: true, - out: vec![], - } - }))); - jobs.push_back(WorkerJob::new_cmd( - Some(env), - vrcmd, - Some(vec!["--resetroomsetup".into()]), - )); - JobWorker::new(jobs, sender.input_sender(), |msg| match msg { - JobWorkerOut::Log(_) => Self::Input::NoOp, - JobWorkerOut::Exit(code) => Self::Input::OnCalWorkerExit(code), - }) - }; - - server_worker.start(); - cal_worker.start(); - self.server_worker = Some(server_worker); - self.calibration_worker = Some(cal_worker); - } + let steamvr_bin_dir = get_steamvr_bin_dir_path().to_string_lossy().to_string(); + if !Path::new(&steamvr_bin_dir).is_dir() { + self.set_calibration_success(false); + self.set_calibration_result(Some("SteamVR not found".into())); + return; + } + let mut env: HashMap = HashMap::new(); + env.insert("LD_LIBRARY_PATH".into(), steamvr_bin_dir.clone()); + let vrcmd = format!("{steamvr_bin_dir}/vrcmd"); + let server_worker = { + let mut jobs: VecDeque = VecDeque::new(); + jobs.push_back(WorkerJob::new_cmd( + Some(env.clone()), + vrcmd.clone(), + Some(vec!["--pollposes".into()]), + )); + JobWorker::new(jobs, sender.input_sender(), |msg| match msg { + JobWorkerOut::Log(_) => Self::Input::NoOp, + JobWorkerOut::Exit(code) => Self::Input::OnServerWorkerExit(code), + }) }; + let cal_worker = { + let mut jobs: VecDeque = VecDeque::new(); + jobs.push_back(WorkerJob::new_func(Box::new(move || { + sleep(Duration::from_secs(2)); + FuncWorkerOut { + success: true, + out: vec![], + } + }))); + jobs.push_back(WorkerJob::new_cmd( + Some(env), + vrcmd, + Some(vec!["--resetroomsetup".into()]), + )); + JobWorker::new(jobs, sender.input_sender(), |msg| match msg { + JobWorkerOut::Log(_) => Self::Input::NoOp, + JobWorkerOut::Exit(code) => Self::Input::OnCalWorkerExit(code), + }) + }; + + server_worker.start(); + cal_worker.start(); + self.server_worker = Some(server_worker); + self.calibration_worker = Some(cal_worker); } Self::Input::OnServerWorkerExit(code) => { if code != 0 { - error!("calibration exited with code {code}"); + eprintln!("Calibration exited with code {code}"); } self.calibration_running = false; } diff --git a/src/ui/util.rs b/src/ui/util.rs index aa6c95f..16383c8 100644 --- a/src/ui/util.rs +++ b/src/ui/util.rs @@ -1,5 +1,4 @@ use gtk::{gdk, gio, glib::clone, prelude::*}; -use tracing::{error, warn}; pub fn limit_dropdown_width(dd: >k::DropDown) { let mut dd_child = dd @@ -47,14 +46,14 @@ pub fn warning_heading() -> gtk::Box { pub fn open_with_default_handler(uri: &str) { if let Err(e) = gio::AppInfo::launch_default_for_uri(uri, gio::AppLaunchContext::NONE) { - error!("opening uri {uri}: {e}") + eprintln!("Error opening uri {}: {}", uri, e) }; } pub fn copy_text(txt: &str) { match gdk::Display::default() { None => { - warn!("could not get default gdk display") + eprintln!("Warning: could not get default gdk display") } Some(d) => { d.clipboard().set_text(txt); diff --git a/src/ui/wivrn_conf_editor.rs b/src/ui/wivrn_conf_editor.rs index 1c6d6d4..6b02d4b 100644 --- a/src/ui/wivrn_conf_editor.rs +++ b/src/ui/wivrn_conf_editor.rs @@ -21,7 +21,6 @@ use crate::{ use adw::prelude::*; use gtk::glib::clone; use relm4::{factory::AsyncFactoryVecDeque, prelude::*}; -use tracing::error; #[tracker::track] pub struct WivrnConfEditor { @@ -256,7 +255,7 @@ impl SimpleComponent for WivrnConfEditor { if let Some(idx) = idx_opt { self.encoder_models.as_mut().unwrap().guard().remove(idx); } else { - error!("couldn't find encoder model with id {id}"); + eprintln!("Couldn't find encoder model with id {id}"); } } } diff --git a/src/ui/wivrn_wired_start_box.rs b/src/ui/wivrn_wired_start_box.rs index 503bcac..76b11e4 100644 --- a/src/ui/wivrn_wired_start_box.rs +++ b/src/ui/wivrn_wired_start_box.rs @@ -7,7 +7,6 @@ use crate::{ }; use gtk::prelude::*; use relm4::prelude::*; -use tracing::error; #[derive(PartialEq, Eq, Debug, Clone)] pub enum StartClientStatus { @@ -154,14 +153,14 @@ impl AsyncComponent for WivrnWiredStartBox { .into(), )) } else { - error!("ADB failed with code {}.\nstdout:\n{}\n======\nstderr:\n{}", out.exit_code, out.stdout, out.stderr); + eprintln!("Error: ADB failed with code {}.\nstdout:\n{}\n======\nstderr:\n{}", out.exit_code, out.stdout, out.stderr); StartClientStatus::Done(Some( format!("ADB exited with code \"{}\"", out.exit_code) )) } }, Err(e) => { - error!("failed to run ADB: {e}"); + eprintln!("Error: failed to run ADB: {e}"); StartClientStatus::Done(Some( "Failed to run ADB".into() )) diff --git a/src/util/file_utils.rs b/src/util/file_utils.rs index 04efb13..e05a7ac 100644 --- a/src/util/file_utils.rs +++ b/src/util/file_utils.rs @@ -1,4 +1,4 @@ -use crate::{async_process::async_process, depcheck::common::dep_getcap_setcap, profile::Profile}; +use crate::{async_process::async_process, profile::Profile}; use anyhow::bail; use nix::{ errno::Errno, @@ -7,10 +7,8 @@ use nix::{ use std::{ fs::{self, copy, create_dir_all, remove_dir_all, File, OpenOptions}, io::{BufReader, BufWriter}, - os::unix::fs::PermissionsExt, path::Path, }; -use tracing::{debug, error}; pub fn get_writer(path: &Path) -> anyhow::Result> { if let Some(parent) = path.parent() { @@ -38,7 +36,7 @@ pub fn get_reader(path: &Path) -> Option> { } match File::open(path) { Err(e) => { - error!("Error opening {}: {}", path.to_string_lossy(), e); + eprintln!("Error opening {}: {}", path.to_string_lossy(), e); None } Ok(fd) => Some(BufReader::new(fd)), @@ -50,7 +48,7 @@ pub fn deserialize_file(path: &Path) -> Option None, Some(reader) => match serde_json::from_reader(reader) { Err(e) => { - error!("Failed to deserialize {}: {}", path.to_string_lossy(), e); + eprintln!("Failed to deserialize {}: {}", path.to_string_lossy(), e); None } Ok(res) => Some(res), @@ -58,50 +56,21 @@ pub fn deserialize_file(path: &Path) -> Option anyhow::Result<()> { - if path.is_symlink() { - bail!( - "path {} is a symlink, trying to change its write permission will only change the original file", - path.to_string_lossy() - ); - } +pub fn set_file_readonly(path: &Path, readonly: bool) -> Result<(), std::io::Error> { if !path.is_file() { - debug!( - "trying to set readonly on a file that does not exist: {}", - path.to_string_lossy() - ); + eprintln!("WARN: trying to set readonly on a file that does not exist"); return Ok(()); } let mut perms = fs::metadata(path) .expect("Could not get metadata for file") .permissions(); perms.set_readonly(readonly); - Ok(fs::set_permissions(path, perms)?) -} - -pub fn setcap_executable() -> Option { - if dep_getcap_setcap().check() { - Some("setcap".into()) - } else if Path::new("/sbin/setcap").try_exists().unwrap_or_default() { - Some("/sbin/setcap".into()) - } else { - None - } -} - -pub fn getcap_executable() -> Option { - if dep_getcap_setcap().check() { - Some("getcap".into()) - } else if Path::new("/sbin/getcap").try_exists().unwrap_or_default() { - Some("/sbin/getcap".into()) - } else { - None - } + fs::set_permissions(path, perms) } pub fn setcap_cap_sys_nice_eip_cmd(profile: &Profile) -> Vec { vec![ - setcap_executable().unwrap_or("setcap".into()), + "setcap".into(), "CAP_SYS_NICE=eip".into(), profile .prefix @@ -111,42 +80,16 @@ pub fn setcap_cap_sys_nice_eip_cmd(profile: &Profile) -> Vec { ] } -pub async fn verify_cap_sys_nice_eip(profile: &Profile) -> bool { - let xrservice_binary = profile.xrservice_binary().to_string_lossy().to_string(); - if let Some(getcap_exec) = getcap_executable() { - match async_process(&getcap_exec, Some(&[&xrservice_binary]), None).await { - Err(e) => { - error!("failed to run `getcap {xrservice_binary}`: {e:?}"); - false - } - Ok(out) => { - debug!("getcap {xrservice_binary} stdout: {}", out.stdout); - debug!("getcap {xrservice_binary} stderr: {}", out.stderr); - if out.exit_code != 0 { - error!( - "command `getcap {xrservice_binary}` failed with status code {}", - out.exit_code - ); - false - } else { - out.stdout.to_lowercase().contains("cap_sys_nice=eip") - } - } - } - } else { - error!("getcap executable does not exist"); - false +pub async fn setcap_cap_sys_nice_eip(profile: &Profile) { + if let Err(e) = async_process("pkexec", Some(&setcap_cap_sys_nice_eip_cmd(profile)), None).await + { + eprintln!("Error: failed running setcap: {e}"); } } -pub async fn setcap_cap_sys_nice_eip(profile: &Profile) -> anyhow::Result<()> { - async_process("pkexec", Some(&setcap_cap_sys_nice_eip_cmd(profile)), None).await?; - Ok(()) -} - pub fn rm_rf(path: &Path) { if remove_dir_all(path).is_err() { - error!("failed to remove path {}", path.to_string_lossy()); + eprintln!("Failed to remove path {}", path.to_string_lossy()); } } @@ -157,13 +100,11 @@ pub fn copy_file(source: &Path, dest: &Path) { .unwrap_or_else(|_| panic!("Failed to create dir {}", parent.to_str().unwrap())); } } - if !dest.is_symlink() { - set_file_readonly(dest, false) - .unwrap_or_else(|_| panic!("Failed to set file {} as rw", dest.to_string_lossy())); - } - copy(source, dest).unwrap_or_else(|e| { + set_file_readonly(dest, false) + .unwrap_or_else(|_| panic!("Failed to set file {} as rw", dest.to_string_lossy())); + copy(source, dest).unwrap_or_else(|_| { panic!( - "Failed to copy {} to {}: {e}", + "Failed to copy {} to {}", source.to_string_lossy(), dest.to_string_lossy() ) @@ -177,17 +118,6 @@ pub fn mount_has_nosuid(path: &Path) -> Result { } } -pub fn mark_as_executable(path: &Path) -> anyhow::Result<()> { - if !path.is_file() { - bail!("Path '{}' is not a file", path.to_string_lossy()) - } else { - let mut perms = fs::metadata(path)?.permissions(); - perms.set_mode(perms.mode() | 0o111); - fs::set_permissions(path, perms)?; - Ok(()) - } -} - #[cfg(test)] mod tests { use super::mount_has_nosuid; diff --git a/src/util/mod.rs b/src/util/mod.rs index 0aded2a..10902df 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,4 +1,3 @@ pub mod file_utils; pub mod hash; -pub mod steam_library_folder; pub mod steamvr_utils; diff --git a/src/util/steam_library_folder.rs b/src/util/steam_library_folder.rs deleted file mode 100644 index 75af1e2..0000000 --- a/src/util/steam_library_folder.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::paths::get_home_dir; -use anyhow::bail; -use serde::Deserialize; -use std::{ - collections::HashMap, - fs::read_to_string, - path::{Path, PathBuf}, -}; - -#[derive(Deserialize)] -pub struct SteamLibraryFolder { - pub path: String, - pub apps: HashMap, -} - -fn get_steam_main_dir_path() -> anyhow::Result { - let steam_root: PathBuf = get_home_dir().join(".steam/root").canonicalize()?; - - if steam_root.is_dir() { - Ok(steam_root) - } else { - bail!( - "Canonical steam root '{}' is not a dir nor a symlink!", - steam_root.to_string_lossy() - ) - } -} - -impl SteamLibraryFolder { - pub fn get_folders() -> anyhow::Result> { - let libraryfolders_path = get_steam_main_dir_path()? - .join("steamapps/libraryfolders.vdf") - .canonicalize()?; - if !libraryfolders_path.is_file() { - bail!( - "Steam libraryfolders.vdf does not exist in its canonical location {}", - libraryfolders_path.to_string_lossy() - ); - } - Self::get_folders_from_path(&libraryfolders_path) - } - - /// Do not use this: use get_folders() instead as it always uses Steam's - /// canonical root path. This is intended to be directly used only for - /// unit tests - pub fn get_folders_from_path(p: &Path) -> anyhow::Result> { - Ok(keyvalues_serde::from_str(read_to_string(p)?.as_str())?) - } -} - -#[cfg(test)] -mod tests { - use super::SteamLibraryFolder; - use std::path::Path; - - #[test] - fn deserialize_steam_libraryfolders_vdf() { - let lf = SteamLibraryFolder::get_folders_from_path(Path::new( - "./test/files/steam_libraryfolders.vdf", - )) - .unwrap(); - assert_eq!(lf.len(), 1); - let first = lf.get(&0).unwrap(); - assert_eq!(first.path, "/home/gabmus/.local/share/Steam"); - assert_eq!(first.apps.len(), 10); - assert_eq!(first.apps.get(&228980).unwrap(), &29212173); - assert_eq!(first.apps.get(&632360).unwrap(), &0); - } -} diff --git a/src/vulkaninfo.rs b/src/vulkaninfo.rs index 6d76a92..a8f8110 100644 --- a/src/vulkaninfo.rs +++ b/src/vulkaninfo.rs @@ -5,10 +5,12 @@ use ash::{ #[derive(Debug, Clone)] pub struct VulkanInfo { + pub has_nvidia_gpu: bool, + pub has_monado_vulkan_layers: bool, pub gpu_names: Vec, } -// const NVIDIA_VENDOR_ID: u32 = 0x10de; +const NVIDIA_VENDOR_ID: u32 = 0x10de; impl VulkanInfo { /// # Safety @@ -23,19 +25,40 @@ impl VulkanInfo { None, ) }?; + let mut has_nvidia_gpu = false; + let mut has_monado_vulkan_layers = false; let gpu_names = unsafe { instance.enumerate_physical_devices() }? .into_iter() .filter_map(|d| { - Some( - unsafe { instance.get_physical_device_properties(d) } - .device_name_as_c_str() - .ok()? - .to_string_lossy() - .to_string(), - ) + let props = unsafe { instance.get_physical_device_properties(d) }; + if props.vendor_id == NVIDIA_VENDOR_ID { + has_nvidia_gpu = true; + } + if !has_monado_vulkan_layers { + has_monado_vulkan_layers = + unsafe { instance.enumerate_device_layer_properties(d) } + .ok() + .map(|layerprops| { + layerprops.iter().any(|lp| { + lp.layer_name_as_c_str().is_ok_and(|name| { + name.to_string_lossy() + == "VK_LAYER_MND_enable_timeline_semaphore" + }) + }) + }) + == Some(true); + } + props + .device_name_as_c_str() + .ok() + .map(|cs| cs.to_string_lossy().to_string()) }) .collect(); unsafe { instance.destroy_instance(None) }; - Ok(Self { gpu_names }) + Ok(Self { + gpu_names, + has_nvidia_gpu, + has_monado_vulkan_layers, + }) } } diff --git a/src/wivrn_dbus/mod.rs b/src/wivrn_dbus/mod.rs index dfa610c..786776b 100644 --- a/src/wivrn_dbus/mod.rs +++ b/src/wivrn_dbus/mod.rs @@ -11,6 +11,9 @@ #[allow(non_snake_case)] mod internal; +/// timeout for dbus methods in seconds +const TIMEOUT: i32 = 10; + async fn proxy<'a>() -> zbus::Result> { let connection = zbus::Connection::session().await?; let proxy = internal::ServerProxy::new(&connection).await?; @@ -22,7 +25,7 @@ pub async fn is_pairing_mode() -> zbus::Result { } pub async fn enable_pairing() -> zbus::Result { - proxy().await?.enable_pairing(0).await + proxy().await?.enable_pairing(TIMEOUT).await } pub async fn disable_pairing() -> zbus::Result<()> { diff --git a/src/xr_devices.rs b/src/xr_devices.rs index 846759f..ddd2b35 100644 --- a/src/xr_devices.rs +++ b/src/xr_devices.rs @@ -1,6 +1,5 @@ use libmonado::{self, BatteryStatus, DeviceRole}; use std::{collections::HashMap, fmt::Display, slice::Iter}; -use tracing::error; #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] pub enum XRDeviceRole { @@ -281,8 +280,8 @@ impl XRDevice { if let Some(target) = devs.get_mut(&index) { target.roles.push(role.into()); } else { - error!( - "could not find device index {index} for role {}", + eprintln!( + "Could not find device index {index} for role {}", XRDeviceRole::from(role) ) } diff --git a/test/files/profile.json b/test/files/profile.json index ab167a3..cb69c6f 100644 --- a/test/files/profile.json +++ b/test/files/profile.json @@ -5,7 +5,9 @@ "xrservice_path": "/home/user/monado", "xrservice_repo": null, "xrservice_branch": null, - "ovr_comp": { "mod_type": "Opencomposite", "path": "/home/user/opencomposite", "repo": null, "branch": null }, + "opencomposite_path": "/home/user/opencomposite", + "opencomposite_repo": null, + "opencomposite_branch": null, "features": { "libsurvive": { "feature_type": "Libsurvive", @@ -32,4 +34,4 @@ "can_be_built": true, "editable": true, "pull_on_build": true -} +} \ No newline at end of file