From 2f312afc6b628175019e668f3c812d4e242251e9 Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Mon, 8 Sep 2025 12:52:46 +0200 Subject: [PATCH 01/12] fix: squash history because of broken rebase Signed-off-by: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> --- .envrc.example | 1 + .github/workflows/build.yml | 57 -- .github/workflows/ci.yaml | 27 + .github/workflows/docker.yaml | 50 ++ .github/workflows/rust-clippy.yml | 51 -- .github/workflows/rust.yml | 22 - .gitignore | 9 +- .pre-commit-config.yaml | 9 +- .rustfmt.toml | 2 + CONTRIBUTING.md | 2 +- Cargo.lock | 989 ++++++++++++++++++++++++ Cargo.toml | 1 - README.md | 6 +- compose.yaml | 31 + scripts/install.sh | 146 ---- scripts/install_quickemu.py | 110 --- shell.nix | 37 + winapps-cli/Cargo.toml | 6 +- winapps-cli/src/main.rs | 107 +-- winapps-gui/Cargo.toml | 9 - winapps-gui/src/main.rs | 1 - winapps-image/Containerfile | 3 + winapps-image/oem/Container.reg | 4 + winapps-image/oem/ExtractPrograms.ps1 | 377 +++++++++ winapps-image/oem/NetProfileCleanup.ps1 | 34 + winapps-image/oem/RDPApps.reg | 20 + winapps-image/oem/install.bat | 3 + winapps-image/oem/install.ps1 | 60 ++ winapps/Cargo.toml | 18 +- winapps/src/backend/container.rs | 52 ++ winapps/src/backend/manual.rs | 38 + winapps/src/backend/mod.rs | 73 ++ winapps/src/command.rs | 163 ++++ winapps/src/config/apps.rs | 66 ++ winapps/src/config/mod.rs | 89 +++ winapps/src/config/operations.rs | 81 ++ winapps/src/dirs.rs | 51 ++ winapps/src/errors.rs | 222 +++--- winapps/src/freerdp.rs | 70 -- winapps/src/lib.rs | 174 +---- winapps/src/quickemu.rs | 69 -- winapps/src/remote_client/freerdp.rs | 97 +++ winapps/src/remote_client/mod.rs | 11 + 43 files changed, 2556 insertions(+), 892 deletions(-) create mode 100644 .envrc.example delete mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/ci.yaml create mode 100644 .github/workflows/docker.yaml delete mode 100644 .github/workflows/rust-clippy.yml delete mode 100644 .github/workflows/rust.yml create mode 100644 .rustfmt.toml create mode 100644 Cargo.lock create mode 100644 compose.yaml delete mode 100644 scripts/install.sh delete mode 100644 scripts/install_quickemu.py create mode 100644 shell.nix delete mode 100644 winapps-gui/Cargo.toml delete mode 100644 winapps-gui/src/main.rs create mode 100644 winapps-image/Containerfile create mode 100644 winapps-image/oem/Container.reg create mode 100644 winapps-image/oem/ExtractPrograms.ps1 create mode 100644 winapps-image/oem/NetProfileCleanup.ps1 create mode 100644 winapps-image/oem/RDPApps.reg create mode 100644 winapps-image/oem/install.bat create mode 100644 winapps-image/oem/install.ps1 create mode 100644 winapps/src/backend/container.rs create mode 100644 winapps/src/backend/manual.rs create mode 100644 winapps/src/backend/mod.rs create mode 100644 winapps/src/command.rs create mode 100644 winapps/src/config/apps.rs create mode 100644 winapps/src/config/mod.rs create mode 100644 winapps/src/config/operations.rs create mode 100644 winapps/src/dirs.rs delete mode 100644 winapps/src/freerdp.rs delete mode 100644 winapps/src/quickemu.rs create mode 100644 winapps/src/remote_client/freerdp.rs create mode 100644 winapps/src/remote_client/mod.rs diff --git a/.envrc.example b/.envrc.example new file mode 100644 index 00000000..607b752d --- /dev/null +++ b/.envrc.example @@ -0,0 +1 @@ +use nix # --arg isIdea true --arg nixpkgs '' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index d25f07ad..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: build and release - -on: - workflow_dispatch: - release: - types: [ created ] - -permissions: - contents: write - -jobs: - build: - name: ${{ matrix.platform.os_name }} with rust ${{ matrix.toolchain }} - runs-on: ${{ matrix.platform.os }} - strategy: - fail-fast: false - matrix: - platform: - - os_name: Linux-i686-musl - os: ubuntu-20.04 - target: i686-unknown-linux-musl - bin: winapps-linux-i686-musl - - os_name: Linux-i686 - os: ubuntu-20.04 - target: i686-unknown-linux-gnu - bin: winapps-linux-i686 - - os_name: Linux-x86_64-musl - os: ubuntu-20.04 - target: x84_64-unknown-linux-musl - bin: winapps-linux-amd64-musl - - os_name: Linux-x86_64 - os: ubuntu-20.04 - target: x86_64-unknown-linux-gnu - bin: winapps-linux-amd64 - toolchain: - - nightly - - steps: - - uses: actions/checkout@v4 - - name: Build binary - uses: houseabsolute/actions-rust-cross@v0 - with: - command: "build" - target: ${{ matrix.platform.target }} - toolchain: ${{ matrix.toolchain }} - args: "--release" - strip: true - - name: Rename binary (linux and macos) - run: mv target/${{ matrix.platform.target }}/release/winapps target/${{ matrix.platform.target }}/release/${{ matrix.platform.bin }} - - name: Generate SHA-256 - run: shasum -a 256 target/${{ matrix.platform.target }}/release/${{ matrix.platform.bin }} | cut -d ' ' -f 1 > target/${{ matrix.platform.target }}/release/${{ matrix.platform.bin }}.sha256 - - name: Release binary and SHA-256 checksum to GitHub - uses: softprops/action-gh-release@v1 - with: - files: | - target/${{ matrix.platform.target }}/release/${{ matrix.platform.bin }} - target/${{ matrix.platform.target }}/release/${{ matrix.platform.bin }}.sha256 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..d10a8007 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,27 @@ +name: Format and lint + +on: + push: + branches: [ "rewrite" ] + pull_request: + branches: [ "rewrite" ] + +permissions: + contents: write + pull-requests: write + checks: write + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@nightly + with: + components: clippy + + - uses: auguwu/clippy-action@1.4.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml new file mode 100644 index 00000000..4c252bba --- /dev/null +++ b/.github/workflows/docker.yaml @@ -0,0 +1,50 @@ +name: Build container image + +on: + push: + branches: [ "rewrite" ] + paths: [ "winapps-image/**" ] + schedule: + # See https://crontab.guru/monthly + - cron: 0 0 1 * * + +permissions: + packages: write + +env: + IMAGE_REGISTRY: ghcr.io + IMAGE_NAME: winapps-org/windows + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build image + id: build + uses: redhat-actions/buildah-build@v2 + with: + image: ${{ env.IMAGE_NAME }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + context: winapps-image + containerfiles: | + Containerfile + + - name: Push image to GHCR + uses: redhat-actions/push-to-registry@v2 + with: + image: ${{ steps.build.outputs.image }} + tags: ${{ steps.build.outputs.tags }} + registry: ${{ env.IMAGE_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/rust-clippy.yml b/.github/workflows/rust-clippy.yml deleted file mode 100644 index b77be5cd..00000000 --- a/.github/workflows/rust-clippy.yml +++ /dev/null @@ -1,51 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# rust-clippy is a tool that runs a bunch of lints to catch common -# mistakes in your Rust code and help improve your Rust code. -# More details at https://github.com/rust-lang/rust-clippy -# and https://rust-lang.github.io/rust-clippy/ - -name: Rust Clippy - -on: - push: - branches: [ "rewrite", "*" ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ "rewrite" ] - schedule: - - cron: '23 2 * * 5' - -env: - CARGO_TERM_COLOR: always - -jobs: - rust-clippy-analyze: - name: Run rust-clippy analyzing - runs-on: ubuntu-latest - - permissions: - contents: read - security-events: write - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Install required cargo - run: cargo install clippy-sarif sarif-fmt - - - name: Run rust-clippy - run: - cargo clippy - --all-features - --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt - continue-on-error: true - - - name: Upload analysis results to GitHub - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: rust-clippy-results.sarif - wait-for-processing: true diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml deleted file mode 100644 index 1b288a7e..00000000 --- a/.github/workflows/rust.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Rust - -on: - push: - branches: [ "rewrite" ] - pull_request: - branches: [ "rewrite" ] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose diff --git a/.gitignore b/.gitignore index c2819b03..f1e4c367 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,11 @@ # Generated files target/ - -# The library shouldn't decide about the exact versions of -# its dependencies, but let the downstream crate decide. -Cargo.lock +/result # We don't want to commit IDE configuration files. .idea/ +winapps.iml .vscode/ +.direnv +.wakatime-project +.envrc diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0237108b..44c6be17 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,8 @@ ci: skip: [clippy, cargo-check] +default_install_hook_types: [pre-commit, commit-msg] + repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 @@ -14,9 +16,10 @@ repos: - id: end-of-file-fixer - id: mixed-line-ending - id: trailing-whitespace - - repo: https://github.com/winapps-org/pre-commit-rust - rev: v1.1 + - repo: https://github.com/winapps-org/pre-commit-hooks + rev: v2.0.1 hooks: - - id: fmt + - id: rustfmt - id: clippy - id: cargo-check + - id: signoff diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 00000000..52935601 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,2 @@ +unstable_features = true +imports_granularity = "Crate" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9d4688d1..cb2d6a39 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ Thank you for contributing to winapps! Before you can contribute, we ask some things of you: - Please follow our Code of Conduct, the Contributor Covenant. You can find a copy in this repository or under https://www.contributor-covenant.org/ -- All Contributors have to sign [a CLA](https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5) for legal reasons. When opening a PR, @cla-assitant will prompt you and guide you through the process. However, if you contribute on behalf of a legal entity, we ask of you to sign [a different CLA](https://gist.github.com/oskardotglobal/75a8cc056e56a439fa6a1551129ae47f). In that case, please contact us. +- All Contributors have to sign [a CLA](https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5) for legal reasons. When opening a PR, @cla-assistant will prompt you and guide you through the process. However, if you contribute on behalf of a legal entity, we ask of you to sign [a different CLA](https://gist.github.com/oskardotglobal/75a8cc056e56a439fa6a1551129ae47f). In that case, please contact us. ## How to contribute diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..e28221eb --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,989 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + +[[package]] +name = "anyhow" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "clap" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "deranged" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive-new" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.60.2", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" + +[[package]] +name = "indexmap" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "libredox" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "backtrace", + "backtrace-ext", + "cfg-if", + "miette-derive", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[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 = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "terminal_size" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" +dependencies = [ + "rustix", + "windows-sys 0.60.2", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "toml" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +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-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "time", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "winapps" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64", + "derive-new", + "dirs", + "enum_dispatch", + "miette", + "regex", + "serde", + "thiserror", + "toml", + "tracing", +] + +[[package]] +name = "winapps-cli" +version = "0.1.0" +dependencies = [ + "clap", + "miette", + "time", + "tracing", + "tracing-subscriber", + "winapps", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" diff --git a/Cargo.toml b/Cargo.toml index 0e5b9072..bb0f2b17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,5 @@ members = [ "winapps", "winapps-cli", - "winapps-gui", ] resolver = "2" diff --git a/README.md b/README.md index cc5e8b4f..2f0d3d25 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# winapps rewrite +# WinApps Rewrite + [![Rust](https://github.com/winapps-org/winapps/actions/workflows/rust.yml/badge.svg?branch=rewrite)](https://github.com/winapps-org/winapps/actions/workflows/rust.yml) [![Rust Clippy](https://github.com/winapps-org/winapps/actions/workflows/rust-clippy.yml/badge.svg?branch=rewrite)](https://github.com/winapps-org/winapps/actions/workflows/rust-clippy.yml) [![Rust Check](https://github.com/winapps-org/winapps/actions/workflows/rust-check.yml/badge.svg?branch=rewrite)](https://github.com/winapps-org/winapps/actions/workflows/rust-check.yml) -The winapps rewrite project is a whole rewrite of the legacy winapps in rust. Its goal is to simplify the installation and provide a cleaner codebase. +The WinApps Rewrite project is a rewrite of the legacy WinApps in Rust. Its goal is to simplify the installation and +provide a cleaner codebase. diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 00000000..eac4b525 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,31 @@ +name: "winapps" + +volumes: + data: + +services: + windows: + build: + context: winapps-image + dockerfile: Containerfile + container_name: WinApps + environment: + VERSION: "11" + RAM_SIZE: "8G" + CPU_CORES: "4" + DISK_SIZE: "64G" + HOME: "${HOME}" + ports: + - "8006:8006" + - "2222:22" + - "3389:3389/tcp" + - "3389:3389/udp" + cap_add: + - NET_ADMIN + stop_grace_period: 120s + restart: on-failure + volumes: + - data:/storage + devices: + - /dev/kvm + - /dev/net/tun diff --git a/scripts/install.sh b/scripts/install.sh deleted file mode 100644 index 3954e5ef..00000000 --- a/scripts/install.sh +++ /dev/null @@ -1,146 +0,0 @@ -#!/bin/sh - -# This install script is intended to download and install the latest available -# release of the winapps rewrite.. -# -# It attempts to identify the current platform and an error will be thrown if -# the platform is not supported. -# It is based on the install script from dep (https://raw.githubusercontent.com/golang/dep/master/install.sh) -# -# Environment variables: -# - INSTALL_DIRECTORY (optional): defaults to ~/.local/bin -# - WINAPPS_RELEASE_TAG (optional): defaults to fetching the latest release -# - WINAPPS_USE_MUSL (optional): use musl instead of glibc -# - WINAPPS_ARCH (optional): use a specific value for ARCH (mostly for testing) -# -# You can install using this script: -# $ curl https://raw.githubusercontent.com/winapps-org-winapps/rewrite/scripts/install.sh | sh - -set -e - -RELEASES_URL="https://github.com/winapps-org/winapps/releases" - -downloadJSON() { - url="$2" - - echo "Fetching $url.." - if test -x "$(command -v curl)"; then - response=$(curl -s -L -w 'HTTPSTATUS:%{http_code}' -H 'Accept: application/json' "$url") - body=$(echo "$response" | sed -e 's/HTTPSTATUS\:.*//g') - code=$(echo "$response" | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') - elif test -x "$(command -v wget)"; then - temp=$(mktemp) - body=$(wget -q --header='Accept: application/json' -O - --server-response "$url" 2> "$temp") - code=$(awk '/^ HTTP/{print $2}' < "$temp" | tail -1) - rm "$temp" - else - echo "Neither curl nor wget was available to perform http requests." - exit 1 - fi - if [ "$code" != 200 ]; then - echo "Request failed with code $code" - exit 1 - fi - - eval "$1='$body'" -} - -downloadFile() { - url="$1" - destination="$2" - - echo "Fetching $url.." - if test -x "$(command -v curl)"; then - code=$(curl -s -w '%{http_code}' -L "$url" -o "$destination") - elif test -x "$(command -v wget)"; then - code=$(wget -q -O "$destination" --server-response "$url" 2>&1 | awk '/^ HTTP/{print $2}' | tail -1) - else - echo "Neither curl nor wget was available to perform http requests." - exit 1 - fi - - if [ "$code" != 200 ]; then - echo "Request failed with code $code" - exit 1 - fi -} - -initArch() { - ARCH=$(uname -m) - if [ -n "$WINAPPS_ARCH" ]; then - echo "Using WINAPPS_ARCH" - ARCH="$WINAPPS_ARCH" - fi - case $ARCH in - amd64) ARCH="amd64";; - x86_64) ARCH="amd64";; - i386) ARCH="i686";; - i686) ARCH="i686";; - *) echo "Architecture ${ARCH} is not supported by winapps"; exit 1;; - esac - echo "ARCH = $ARCH" -} - -initOS() { - OS=$(uname | tr '[:upper:]' '[:lower:]') - case "$OS" in - linux) OS='linux';; - *) echo "OS ${OS} is not supported by winapps"; exit 1;; - esac - echo "OS = $OS" -} - -# identify platform based on uname output -initArch -initOS - -# determine install directory if required -if [ -z "$INSTALL_DIRECTORY" ]; then - if [ -d "$HOME/.local/bin" ]; then - INSTALL_DIRECTORY="$HOME/.local/bin" - else - echo "Installation directory not specified and ~/.local/bin does not exist" - exit 1 - fi -fi -echo "Will install into $INSTALL_DIRECTORY" -echo "Make sure it is on your PATH" - -if [ -n "$WINAPPS_USE_MUSL" ]; then - BINARY="winapps-${OS}-${ARCH}-musl" -else - BINARY="winapps-${OS}-${ARCH}" -fi - -# if WINAPPS_RELEASE_TAG was not provided, assume latest -if [ -z "$WINAPPS_RELEASE_TAG" ]; then - downloadJSON LATEST_RELEASE "$RELEASES_URL/latest" - WINAPPS_RELEASE_TAG=$(echo "${LATEST_RELEASE}" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//' ) -fi -echo "Release Tag = $WINAPPS_RELEASE_TAG" - -# fetch the real release data to make sure it exists before we attempt a download -downloadJSON RELEASE_DATA "$RELEASES_URL/tag/$WINAPPS_RELEASE_TAG" - -BINARY_URL="$RELEASES_URL/download/$WINAPPS_RELEASE_TAG/$BINARY" -DOWNLOAD_FILE=$(mktemp) - -downloadFile "$BINARY_URL" "$DOWNLOAD_FILE" - -echo "Setting executable permissions." -chmod +x "$DOWNLOAD_FILE" - -INSTALL_NAME="winapps" - -echo "Moving executable to $INSTALL_DIRECTORY/$INSTALL_NAME" -mv "$DOWNLOAD_FILE" "$INSTALL_DIRECTORY/$INSTALL_NAME" - -if test -x "$(command -v python3)"; then - curl https://raw.githubusercontent.com/winapps-org/winapps/rewrite/scripts/install_quickemu.py | python3 -else - echo "python3 is not installed. Please install it in order to install quickemu." - echo "Once you have installed python3, run the following command to install quickemu:" - echo "curl https://raw.githubusercontent.com/winapps-org/winapps/rewrite/scripts/install_quickemu.py | python3" - echo "You may ignore this if quickemu is already installed." - exit 1 -fi diff --git a/scripts/install_quickemu.py b/scripts/install_quickemu.py deleted file mode 100644 index efaba950..00000000 --- a/scripts/install_quickemu.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python3 - -import platform -import os -import shutil -import sys - - -def _(c: str): - """Execute the command `c` and print it""" - print("> " + c) - os.system(c) - - -def clone_repo(): - if os.path.exists(os.path.expanduser("~/.local/share/quickemu")): - print("📦 quickemu is already installed. Updating...") - update_quickemu() - return - - print("📦 Cloning quickemu...") - - _("git clone --filter=blob:none https://github.com/quickemu-project/quickemu ~/.local/share/quickemu") - _("mkdir -p ~/.local/bin") - _("ln -s ~/.local/share/quickemu/quickemu ~/.local/bin/quickemu") - _("ln -s ~/.local/share/quickemu/macrecovery ~/.local/bin/macrecovery") - _("ln -s ~/.local/share/quickemu/quickget ~/.local/bin/quickget") - _("ln -s ~/.local/share/quickemu/windowskey ~/.local/bin/windowskey") - - print("Installation complete.") - print("⚠️ Make sure ~/.local/bin is in your PATH.") - - -def update_quickemu(): - print("📦 Updating quickemu...") - - _("cd ~/.local/share/quickemu") - _("git pull") - - print("Update complete.") - print("⚠️ Make sure ~/.local/bin is in your PATH.") - - -def install_fedora(): - print("📦 Installing dependencies...") - - _("sudo dnf install qemu bash coreutils edk2-tools grep jq lsb procps python3 genisoimage usbutils" - + " util-linux sed spice-gtk-tools swtpm wget xdg-user-dirs xrandr unzip socat -y") - - clone_repo() - - sys.exit(0) - - -def install_deb(): - print("📦 Installing dependencies...") - - _("sudo apt update") - _("sudo apt install qemu bash coreutils ovmf grep jq lsb-base procps python3 genisoimage usbutils" - + " util-linux sed spice-client-gtk libtss2-tcti-swtpm0 wget xdg-user-dirs zsync unzip socat -y") - - clone_repo() - - sys.exit(0) - - -def install_ubuntu(): - print("⚠️ Adding ppa...") - - _("sudo apt-add-repository ppa:flexiondotorg/quickemu") - _("sudo apt update") - _("sudo apt install quickemu -y") - - sys.exit(0) - - -if __name__ == "__main__": - print("⚠️ This script requires elevated privileges (sudo). You will be asked for your password.") - - os_release = platform.freedesktop_os_release() - - distro_id = os_release.get("ID_LIKE") - distro_id_like = os_release.get("ID") - - if not distro_id and not distro_id_like: - print("❌ Couldn't fetch distro, is os-release installed?") - - if distro_id == "ubuntu" \ - or distro_id_like == "ubuntu": - install_ubuntu() - elif distro_id == "debian" \ - or distro_id_like == "debian" \ - or shutil.which("apt"): - install_deb() - elif distro_id == "fedora" \ - or distro_id_like == "fedora" \ - or shutil.which("dnf"): - install_fedora() - else: - if distro_id: - print("❌ Unsupported distro: ", distro_id) - elif distro_id_like: - print("❌ Unsupported distro: ", distro_id_like) - else: - print("❌ Unsupported distro. Couldn't fetch data from os-release and couldn't find dnf or apt on PATH.") - - sys.exit(1) - - print("❌ Unsupported platform.") - sys.exit(1) diff --git a/shell.nix b/shell.nix new file mode 100644 index 00000000..01cf6a02 --- /dev/null +++ b/shell.nix @@ -0,0 +1,37 @@ +{ + nixpkgs ? fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-25.05.tar.gz", + pkgs ? import nixpkgs { }, + fenix ? + (import (fetchTarball "https://github.com/nix-community/fenix/archive/monthly.tar.gz") { + inherit pkgs; + }).complete, + isIdea ? false, +}: +pkgs.mkShell rec { + buildInputs = with pkgs; [ + nixfmt-rfc-style + pre-commit + + freerdp + sshpass + + openssl + pkg-config + fenix.toolchain + ]; + + RUST_BACKTRACE = 1; + RUST_SRC_PATH = "${fenix.rust-src}/lib/rustlib/src/rust/library"; + + shellHook = + let + pathFor = name: ''//component[@name="RustProjectSettings"]/option[@name="${name}"]/@value''; + xidel = pkgs.lib.getExe pkgs.xidel; + in + pkgs.lib.optionalString isIdea '' + sed -i \ + -e "s|$(${xidel} .idea/workspace.xml -e '${pathFor "explicitPathToStdlib"}')|${RUST_SRC_PATH}|" \ + -e "s|$(${xidel} .idea/workspace.xml -e '${pathFor "toolchainHomeDirectory"}')|${fenix.toolchain}/bin|" \ + .idea/workspace.xml + ''; +} diff --git a/winapps-cli/Cargo.toml b/winapps-cli/Cargo.toml index 6837cf7b..66a1d247 100644 --- a/winapps-cli/Cargo.toml +++ b/winapps-cli/Cargo.toml @@ -7,5 +7,9 @@ default-run = "winapps-cli" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = "4.3.11" +clap = "4.3" +time = "0.3" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["time", "env-filter"] } +miette = { version = "7.2.0", features = ["fancy"] } winapps = { path = "../winapps" } diff --git a/winapps-cli/src/main.rs b/winapps-cli/src/main.rs index 3fb78ee0..36950208 100644 --- a/winapps-cli/src/main.rs +++ b/winapps-cli/src/main.rs @@ -1,7 +1,8 @@ use clap::{arg, Command}; -use winapps::freerdp::freerdp_back::Freerdp; -use winapps::quickemu::{create_vm, kill_vm, start_vm}; -use winapps::{unwrap_or_panic, RemoteClient}; +use miette::{IntoDiagnostic, Result}; +use tracing::{info, Level}; +use tracing_subscriber::EnvFilter; +use winapps::{Backend, Config, Freerdp, RemoteClient}; fn cli() -> Command { Command::new("winapps-cli") @@ -9,12 +10,17 @@ fn cli() -> Command { .subcommand_required(true) .arg_required_else_help(true) .allow_external_subcommands(true) - .subcommand(Command::new("check").about("Checks remote connection")) - .subcommand(Command::new("connect").about("Connects to remote")) + .subcommand(Command::new("connect").about("Opens full session on remote")) + .subcommand(Command::new("setup").about("Create desktop files for installed Windows apps")) .subcommand( Command::new("run") - .about("Connects to app on remote") - .arg(arg!( "App to open")), + .about("Runs a configured app or an executable on the remote") + .arg(arg!( "the name of the app/the path to the executable")) + .arg( + arg!([ARGS]... "Arguments to pass to the command") + .trailing_var_arg(true) + .allow_hyphen_values(true), + ), ) .subcommand( Command::new("vm") @@ -26,66 +32,65 @@ fn cli() -> Command { .subcommand(Command::new("start").about("Start the vm")) .subcommand(Command::new("kill").about("Kill the running VM")), ) + .subcommand( + Command::new("app") + .about("Connects to app on remote") + .arg(arg!( "App to open")), + ) } -fn main() { +fn main() -> Result<()> { + tracing_subscriber::fmt() + // .with_timer(tracing_subscriber::fmt::time::uptime()) + .without_time() + .with_target(false) + .with_level(true) + .with_max_level(Level::INFO) + .with_env_filter(EnvFilter::from_default_env()) + .init(); + let cli = cli(); let matches = cli.clone().get_matches(); - let client: &dyn RemoteClient = &Freerdp {}; - let config = winapps::load_config(None); + let config = Config::load()?; - match matches.subcommand() { - Some(("check", _)) => { - println!("Checking remote connection"); + let client = Freerdp::new(config); + let backend = config.get_backend(); - client.check_depends(config); + client.check_depends()?; + backend.check_depends()?; + + match matches.subcommand() { + Some(("setup", _)) => { + info!("Running setup"); + todo!() } + Some(("connect", _)) => { - println!("Connecting to remote"); + info!("Connecting to remote"); - client.run_app(config, None); + client.run_full_session()?; + Ok(()) } - Some(("run", sub_matches)) => { - println!("Connecting to app on remote"); - client.run_app(config, sub_matches.get_one::("APP")); - } + Some(("run", sub_matches)) => { + info!("Connecting to app on remote"); - Some(("vm", command)) => { - match command.subcommand() { - Some(("create", _)) => { - println!("Creating windows 10 vm.."); - create_vm(config); - } - Some(("start", _)) => { - println!("Starting vm.."); - start_vm(config); - } + let args = sub_matches + .get_many::("args") + .map_or(Vec::new(), |args| args.map(|v| v.to_owned()).collect()); - Some(("kill", _)) => { - println!("Killing vm.."); - kill_vm(config); - } + match sub_matches.get_one::("name") { + None => panic!("App is required and should never be None here"), + Some(app) => client.run_app(app.to_owned(), args), + }?; - Some((_, _)) => { - unwrap_or_panic!( - cli.about("Command not found, try existing ones!") - .print_help(), - "Couldn't print help" - ); - } - _ => unreachable!(), - }; + Ok(()) } - Some((_, _)) => { - unwrap_or_panic!( - cli.about("Command not found, try existing ones!") - .print_help(), - "Couldn't print help" - ); - } - _ => unreachable!(), + _ => cli + .about("Command not found, try existing ones!") + .print_help() + .into_diagnostic(), } } diff --git a/winapps-gui/Cargo.toml b/winapps-gui/Cargo.toml deleted file mode 100644 index 771b90c5..00000000 --- a/winapps-gui/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "winapps-gui" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -winapps = { path = "../winapps" } diff --git a/winapps-gui/src/main.rs b/winapps-gui/src/main.rs deleted file mode 100644 index f328e4d9..00000000 --- a/winapps-gui/src/main.rs +++ /dev/null @@ -1 +0,0 @@ -fn main() {} diff --git a/winapps-image/Containerfile b/winapps-image/Containerfile new file mode 100644 index 00000000..dfa40231 --- /dev/null +++ b/winapps-image/Containerfile @@ -0,0 +1,3 @@ +FROM ghcr.io/dockur/windows:latest + +COPY oem/ /oem diff --git a/winapps-image/oem/Container.reg b/winapps-image/oem/Container.reg new file mode 100644 index 00000000..10c018a5 --- /dev/null +++ b/winapps-image/oem/Container.reg @@ -0,0 +1,4 @@ +Windows Registry Editor Version 5.00 + + [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation] + "RealTimeIsUniversal"=dword:00000001 diff --git a/winapps-image/oem/ExtractPrograms.ps1 b/winapps-image/oem/ExtractPrograms.ps1 new file mode 100644 index 00000000..caa003e3 --- /dev/null +++ b/winapps-image/oem/ExtractPrograms.ps1 @@ -0,0 +1,377 @@ +### FUNCTIONS ### +# Name: 'GetApplicationIcon' +# Role: Extract the icon from a given executable file as a base-64 PNG string. +# Args: +# - 'exePath': Provides the path to the executable file. +Function GetApplicationIcon +{ + param ( + [Parameter(Mandatory = $true)] + [string]$exePath + ) + + try + { + # Load the 'System.Drawing' assembly to access 'ExtractAssociatedIcon'. + Add-Type -AssemblyName System.Drawing + + # Extract the icon from the executable. + $exeIcon = [System.Drawing.Icon]::ExtractAssociatedIcon($exePath) + + # Create a bitmap from the icon. + $exeIconBitmap = New-Object System.Drawing.Bitmap $exeIcon.Width, $exeIcon.Height + $graphics = [System.Drawing.Graphics]::FromImage($exeIconBitmap) + $graphics.DrawIcon($exeIcon, 0, 0) + + # Save the bitmap to a 'MemoryStream' as a '.PNG' to preserve the icon colour depth. + $memoryStream = New-Object System.IO.MemoryStream + $exeIconBitmap.Save($memoryStream, [System.Drawing.Imaging.ImageFormat]::Png) + + # Convert the PNG 'MemoryStream' to a base-64 string. + $bytes = $memoryStream.ToArray() + $base64String = [Convert]::ToBase64String($bytes) + + # Clean up. + $memoryStream.Flush() + $memoryStream.Dispose() + $graphics.Dispose() + $exeIconBitmap.Dispose() + $exeIcon.Dispose() + } + catch + { + # Use a generic 32x32 PNG. + $base64String = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAASZQTFRFAAAA+vr65ubm4uLkhYmLvL7A7u7w+/r729vb4eHjFYPbFoTa5eXnGIbcG4jc+fn7Gofc7+/x7OzuF4Xb+fn54uLiC37Z5OTmEIHaIIjcEYHbDoDZFIPcJ43fHYjd9fX28PDy3d3fI4rd3d3dHojc19fXttTsJIve2dnZDX/YCn3Y09PTjL/p5+fnh7zo2traJYzfIYjdE4Pb6urrW6Tf9PT1Ioneir7otNPsCX3Zhbvn+Pj5YKfhJYfWMo7a39/gKIzeKo7eMI3ZNJDcXqbg4eHhuNTsB3zYIoncBXvZLIrXIYjbLJDgt7m6ubu+YqjiKYvYvr6+tba3rs/sz8/P1+byJonXv7/DiImLxsbGjo6Ra6reurq6io6QkJKVw8PD0tLSycnJq1DGywAAAGJ0Uk5TAP////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+BVJDaAAABY0lEQVR4nM2RaVOCUBSGr1CBgFZimppgoGnKopZSaYGmRpravq///0904IqOM9j00WeGT+9ztgtCS8Dzyh98fL6i2+HqQoaj0RPSzQNgzZc4F4wgvUuoqkr1er094MjlIeBCwRdFua9CqURQ51cty7Lykj0YCIIibnlEkS4TgCuky3nbTmSFsCKSHuso96N/Ox1aacjrlYQQ3gjNCYV7UlUJ6szCeRZyXmlkNjEZEPSuLIMAuYTreVYROQ8Y8SLTNAhlCdfzLMsaIhfHgEAT7pLtvFTH9QxTNWrmLsaEDu8558y2ZOP5LLNTNUQyiCFnHaRZnjTmzryhnR36FSdnIU9up7RGxAOuKJjOFX2vHvKU5jPiepbvxzR3BIffwROc++AAJy9qjQxQwz9rIjyGeN6tj8VACEyZCqfQn3H7F48vTvwEdlIP+aWvMNkPcl8h8DYeN5vNTqdzCNz5CIv4h7AE/AKcwUFbShJywQAAAABJRU5ErkJggg==" + } + + # Return the base-64 string. + return $base64String +} + +# Name: 'PrintCSV' +# Role: Print application names, executable paths and base-64 encoded icons as semicolon-seperated CSV ($id;$name;$path;$icon). +# Args: +# - 'Names': An array of application names. +# - 'Paths': An array of executable paths. +# - 'Source': The source of the applications (e.g. Windows Registry, Package manangers, Universal Windows Platform (UWP), etc.) +function PrintCSV +{ + param ( + [string[]]$Names, + [string[]]$Paths, + [string]$Source + ) + + # Combine the arrays into an array of objects + $NamesandPaths = @() + for ($i = 0; $i -lt $Names.Length; $i++) { + $NamesandPaths += [PSCustomObject]@{ + Name = $Names[$i] + Path = $Paths[$i] + } + } + + # Sort the combined array based on the application names. + $NamesandPaths = $NamesandPaths | Sort-Object { $_.Name } + + # Loop through the extracted executable file paths. + foreach ($Application in $NamesandPaths) + { + $Name = $Application.Name + + # Remove undesirable suffix for chocolatey shims. + if ($Source -eq "choco") + { + if ( $Name.EndsWith(" - Chocolatey Shim")) + { + $Name = $Name.Substring(0, $Name.Length - " - Chocolatey Shim".Length) + } + } + + # Remove file extensions + if ($Name.EndsWith(".dll") -or $Name.EndsWith(".exe")) + { + $Name = $Name.Substring(0, $Name.Length - 4) + } + + # Add the appropriate tag to the application name. + if ($Source -ne "winreg") + { + $Name = $Name + " [" + $Source.ToUpper() + "]" + } + + # Store the application icon as a base-64 string. + $Icon = GetApplicationIcon -exePath $Application.Path + + # Output the results as a CSV row + Write-Output "$($Application.Name.Replace(' ', '').ToLower());$Name;$($Application.Path);$Icon" + } +} + +# Name: 'GetApplicationName' +# Role: Determine the application name for a given executable file. +# Args: +# - 'exePath': The path to a given executable file. +function GetApplicationName +{ + param ( + [string]$exePath + ) + + try + { + $productName = (Get-Item $exePath).VersionInfo.FileDescription.Trim() -replace '\s+', ' ' + } + catch + { + $productName = [System.IO.Path]::GetFileNameWithoutExtension($exePath) + } + + return $productName +} + +# Name: 'GetUWPApplicationName' +# Role: Determine the application name for a given UWP application. +# Args: +# - 'exePath': The path to a given executable file. +function GetUWPApplicationName +{ + param ( + [string]$exePath + ) + + # Query the application executable for the application name. + if (Test-Path $exePath) + { + $productName = GetApplicationName -exePath $exePath + } + + # Use the 'DisplayName' (if available) if the previous method failed. + if (-not $productName -and $app.DisplayName) + { + $productName = $app.DisplayName + } + + # Use the 'Name' (if available) as a final fallback. + if (-not $productName -and $app.Name) + { + $productName = $app.Name + } + + return $productName +} + +# Name: 'GetUWPExecutablePath' +# Role: Obtain the UWP application executable path from 'AppxManifest.xml'. +# Args: +# - 'instLoc': UWP application folder path (C:\Program Files\WindowsApps\*). +function GetUWPExecutablePath +{ + param ( + [string]$instLoc + ) + + # Determine the path to 'AppxManifest.xml' for the selected application. + $manifestPath = Join-Path -Path $instLoc -ChildPath "AppxManifest.xml" + + if (Test-Path $manifestPath) + { + # Parse the XML file. + [xml]$manifest = Get-Content $manifestPath + $applications = $manifest.Package.Applications.Application + + # Return the path to the first executable specified within the XML. + foreach ($application in $applications) + { + $executable = $application.Executable + if ($executable) + { + return Join-Path -Path $instLoc -ChildPath $executable + } + } + } + + # Return 'null' if nothing was found. + return $null +} + +# Name: 'AppSearchWinReg' +# Role: Search the Windows Registry for installed applications. +function AppSearchWinReg +{ + # Initialise empty arrays. + $exeNames = @() + $exePaths = @() + $validPaths = @() + + # Query windows registry for unique installed executable files. + $exePaths = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\*" | + ForEach-Object { $_."(default)" } | # Extract the value of the (default) property + Where-Object { $_ -ne $null } | # Filter out null values + Sort-Object -Unique # Ensure uniqueness + + # Remove leading and trailing double quotes from all paths. + $exePaths = $exePaths -replace '^"*|"*$' + + # Get corresponding application names for unique installed executable files. + foreach ($exePath in $exePaths) + { + if (Test-Path -Path $exePath) + { + $validPaths += $exePath + $exeNames += GetApplicationName -exePath $exePath + } + } + + # Process extracted executable file paths. + PrintCSV -Names $exeNames -Paths $validPaths -Source "winreg" +} + +# Name: 'AppSearchUWP' +# Role: Search for 'non-system' UWP applications. +function AppSearchUWP +{ + # Initialise empty arrays. + $exeNames = @() + $exePaths = @() + + # Obtain all 'non-system' UWP applications using 'Get-AppxPackage'. + $uwpApps = Get-AppxPackage | Where-Object { + $_.IsFramework -eq $false -and + $_.IsResourcePackage -eq $false -and + $_.SignatureKind -ne 'System' + } + + # Create an array to store UWP application details. + $uwpAppDetails = @() + + # Loop through each UWP application. + foreach ($app in $uwpApps) + { + # Initialise the variable responsible for storing the UWP application name. + $productName = $null + + # Obtain the path to the UWP application executable. + $exePath = GetUWPExecutablePath -instLoc $app.InstallLocation + + # Proceed only if an executable path was identified. + if ($exePath) + { + $productName = GetUWPApplicationName -exePath $exePath + + # Ignore UWP applications with no name, or those named 'Microsoft® Windows® Operating System'. + if ($productName -ne "Microsoft® Windows® Operating System" -and [string]::IsNullOrEmpty($productName) -eq $false) + { + # Store the UWP application name and executable path. + $exeNames += $productName + $exePaths += $exePath + } + } + } + + # Process extracted executable file paths. + PrintCSV -Names $exeNames -Paths $exePaths -Source "uwp" +} + +# Name: 'AppSearchWinReg' +# Role: Search for chocolatey shims. +function AppSearchChocolatey +{ + # Initialise empty arrays. + $exeNames = @() + $exePaths = @() + + # Specify the 'chocolatey' shims directory. + $chocoDir = "C:\ProgramData\chocolatey\bin" + + # Check if the 'chocolatey' shims directory exists. + if (Test-Path -Path $chocoDir -PathType Container) + { + # Get all shim '.exe' files. + $shimExeFiles = Get-ChildItem -Path $chocoDir -Filter *.exe + + # Loop through each '.shim' file to extract the executable path. + foreach ($shimExeFile in $shimExeFiles) + { + # Resolve the shim to the actual executable path. + $exePath = (Get-Command $shimExeFile).Source + + # Proceed only if an executable path was identified. + if ($exePath) + { + $exeNames += GetApplicationName -exePath $exePath + $exePaths += $exePath + } + } + + # Process extracted executable file paths. + PrintCSV -Names $exeNames -Paths $exePaths -Source "choco" + } +} + +# Name: 'AppSearchWinReg' +# Role: Search for scoop shims. +function AppSearchScoop +{ + # Initialise empty arrays. + $exeNames = @() + $exePaths = @() + + # Specify the 'scoop' shims directory. + $scoopDir = "$HOME\scoop\shims" + + # Check if the 'scoop' shims directory exists. + if (Test-Path -Path $scoopDir -PathType Container) + { + # Get all '.shim' files. + $shimFiles = Get-ChildItem -Path $scoopDir -Filter *.shim + + # Loop through each '.shim' file to extract the executable path. + foreach ($shimFile in $shimFiles) + { + # Read the content of the '.shim' file. + $shimFileContent = Get-Content -Path $shimFile.FullName + + # Extract the path using regex, exiting the loop after the first match is found. + $exePath = "" + + foreach ($line in $shimFileContent) + { + # '^\s*path\s*=\s*"([^"]+)"' + # ^ --> Asserts the start of the line. + # \s* --> Matches any whitespace characters (zero or more times). + # path --> Matches the literal string "path". + # \s*=\s* --> Matches an equal sign = surrounded by optional whitespace characters. + # " --> Matches an initial double quote. + # ([^"]+) --> Captures one or more characters that are not ", representing the path inside the double quotes. + # " --> Matches a final double quote. + if ($line -match '^\s*path\s*=\s*"([^"]+)"') + { + $exePath = $matches[1] + break + } + } + + if ($exePath -ne "") + { + $exeNames += GetApplicationName -exePath $exePath + $exePaths += $exePath + } + } + + # Process extracted executable file paths. + PrintCSV -Names $exeNames -Paths $exePaths -Source "scoop" + } +} + + +### SEQUENTIAL LOGIC ### + +# Search for installed applications. +AppSearchWinReg # Windows Registry +if (Get-Command Get-AppxPackage -ErrorAction SilentlyContinue) +{ + AppSearchUWP # Universal Windows Platform +} +AppSearchChocolatey # Chocolatey Package Manager +AppSearchScoop # Scoop Package Manager diff --git a/winapps-image/oem/NetProfileCleanup.ps1 b/winapps-image/oem/NetProfileCleanup.ps1 new file mode 100644 index 00000000..55a1f943 --- /dev/null +++ b/winapps-image/oem/NetProfileCleanup.ps1 @@ -0,0 +1,34 @@ +# Get the current network profile name +$currentProfile = (Get-NetConnectionProfile).Name + +# Get all profiles from the registry +$profilesKey = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles" +$profiles = Get-ChildItem -Path $profilesKey + +foreach ($profile in $profiles) +{ + $profilePath = "$profilesKey\$( $profile.PSChildName )" + $profileName = (Get-ItemProperty -Path $profilePath).ProfileName + + # Remove profiles that don't match the current one + if ($profileName -ne $currentProfile) + { + Remove-Item -Path $profilePath -Recurse + Write-Host "Deleted profile: $profileName" + } +} + +# Change the current profile name to "WinApps" +$profiles = Get-ChildItem -Path $profilesKey +foreach ($profile in $profiles) +{ + $profilePath = "$profilesKey\$( $profile.PSChildName )" + $profileName = (Get-ItemProperty -Path $profilePath).ProfileName + + if ($profileName -eq $currentProfile) + { + # Update the profile name + Set-ItemProperty -Path $profilePath -Name "ProfileName" -Value "WinApps" + Write-Host "Renamed profile to: WinApps" + } +} diff --git a/winapps-image/oem/RDPApps.reg b/winapps-image/oem/RDPApps.reg new file mode 100644 index 00000000..f9e4b79a --- /dev/null +++ b/winapps-image/oem/RDPApps.reg @@ -0,0 +1,20 @@ +Windows Registry Editor Version 5.00 + + ; Disable RemoteApp allowlist so all applications can be used in Remote Desktop sessions + [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\TSAppAllowList] + "fDisabledAllowList"=dword:00000001 + + ; Allow unlisted programs to be run in Remote Desktop sessions + [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services] + "fAllowUnlistedRemotePrograms"=dword:00000001 + + ; Disable automatic administrator logon at startup + [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon] + "AutoAdminLogon"="0" + + ; Always use the server's keyboard layout + [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout] + "IgnoreRemoteKeyboardLayout"=dword:00000001 + + ; Disable "Do you want your PC to be discoverable" prompt after each host system reboot + [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Network\NewNetworkWindowOff] diff --git a/winapps-image/oem/install.bat b/winapps-image/oem/install.bat new file mode 100644 index 00000000..59c3e64d --- /dev/null +++ b/winapps-image/oem/install.bat @@ -0,0 +1,3 @@ +@echo off + +powershell.exe -ExecutionPolicy Bypass -File %~dp0\install.ps1 diff --git a/winapps-image/oem/install.ps1 b/winapps-image/oem/install.ps1 new file mode 100644 index 00000000..128beb12 --- /dev/null +++ b/winapps-image/oem/install.ps1 @@ -0,0 +1,60 @@ +$ErrorActionPreference = "Stop" + +function Install-OpenSSH +{ + # Based on https://github.com/containerd/containerd/blob/main/script/setup/enable_ssh_windows.ps1 + + Get-WindowsCapability -Online -Name OpenSSH* | Add-WindowsCapability -Online + Set-Service -Name sshd -StartupType Automatic + Start-Service sshd + + # Set PowerShell as default shell + New-ItemProperty -Force -Path "HKLM:\SOFTWARE\OpenSSH" -PropertyType String ` + -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" +} + +function Load-Registry +{ + reg import "$PSScriptRoot\RDPApps.reg" + reg import "$PSScriptRoot\Container.reg" +} + +function Install-NetworkProfileCleanup +{ + Copy-Item -Path "$PSScriptRoot\NetProfileCleanup.ps1" -Destination "$env:windir" -Force + + $taskName = "NetworkProfileCleanup" + $command = "powershell.exe -ExecutionPolicy Bypass -File `"$env:windir\NetProfileCleanup.ps1`"" + + if (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue) + { + Write-Host "Task `"$taskName`" already exists, deleting it first..." + Unregister-ScheduledTask -TaskName $taskName -Confirm:$false + } + + # Create the scheduled task to run at startup as SYSTEM with highest privileges + try + { + Register-ScheduledTask -TaskName $taskName ` + -Trigger (New-ScheduledTaskTrigger -AtStartup) ` + -Action (New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -File `"$env:windir\NetProfileCleanup.ps1`"") ` + -RunLevel Highest ` + -User "SYSTEM" ` + -Force + + Write-Host "Scheduled task `"$taskName`" created successfully." + } + catch + { + Write-Host "Failed to create scheduled task. $_" + } +} + +Set-ExecutionPolicy Unrestricted + +# Run functions +Copy-Item -Path "$PSScriptRoot\ExtractPrograms.ps1" -Destination "$env:windir" -Force + +Load-Registry +Install-NetworkProfileCleanup +Install-OpenSSH diff --git a/winapps/Cargo.toml b/winapps/Cargo.toml index 8d6d0a55..f4288c28 100644 --- a/winapps/Cargo.toml +++ b/winapps/Cargo.toml @@ -6,10 +6,14 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0.75" -derive-new = "0.5.9" -home = "0.5.5" -serde = { version = "1.0.171", features = ["derive"] } -thiserror = "1.0.49" -toml = "0.8.2" -tracing = "0.1.37" +anyhow = "1.0" +derive-new = "0.7.0" +serde = { version = "1.0", features = ["derive"] } +toml = "0.9" +tracing = "0.1" +dirs = "6.0.0" +miette = "7.2.0" +thiserror = "2.0.9" +enum_dispatch = "0.3.13" +base64 = "0.22.1" +regex = { version = "1.11.2", features = [] } diff --git a/winapps/src/backend/container.rs b/winapps/src/backend/container.rs new file mode 100644 index 00000000..2b5982de --- /dev/null +++ b/winapps/src/backend/container.rs @@ -0,0 +1,52 @@ +use crate::{command::command, ensure, Backend, Config, Error, Result}; +use std::net::{IpAddr, Ipv4Addr}; +use tracing::debug; + +#[derive(Debug, Clone)] +pub struct Container { + config: &'static Config, +} + +impl Container { + const STATE_RUNNING: &'static str = "running"; + + const DEFAULT_COMMAND: &'static str = "docker"; + const PODMAN_COMMAND: &'static str = "podman"; + + pub(crate) fn new(config: &'static Config) -> Self { + Self { config } + } +} + +impl Backend for Container { + fn check_depends(&self) -> Result<()> { + assert!(self.config.container.enable); + + ensure!( + !self.config.container.container_name.is_empty(), + Error::Config("Container name shouldn't be empty") + ); + + let command = if self.config.container.enable_podman { + Self::PODMAN_COMMAND + } else { + Self::DEFAULT_COMMAND + }; + + let state = command!( + r#"{command} ps --all --filter name={} --format {{{{.State}}}}"#, + self.config.container.container_name + )? + .with_err("Could not get container status") + .wait_with_output()?; + + debug!("{command} returned state: {state}"); + ensure!(state.trim() == Self::STATE_RUNNING, Error::VmNotRunning); + + Ok(()) + } + + fn get_host(&self) -> IpAddr { + Ipv4Addr::new(127, 0, 0, 1).into() + } +} diff --git a/winapps/src/backend/manual.rs b/winapps/src/backend/manual.rs new file mode 100644 index 00000000..a53fe12d --- /dev/null +++ b/winapps/src/backend/manual.rs @@ -0,0 +1,38 @@ +use std::{net::IpAddr, str::FromStr}; + +use crate::{ensure, Backend, Config, Error, Result}; + +#[derive(Debug, Clone)] +pub struct Manual { + config: &'static Config, +} + +impl Manual { + pub(crate) fn new(config: &'static Config) -> Self { + Self { config } + } +} + +impl Backend for Manual { + fn check_depends(&self) -> Result<()> { + assert!(self.config.manual.enable); + ensure!( + !self.config.manual.host.is_empty(), + Error::Config("Host shouldn't be empty") + ); + + ensure!( + IpAddr::from_str(&self.config.manual.host).is_ok(), + Error::Config("manual.host is not a valid IP address") + ); + + Ok(()) + } + + fn get_host(&self) -> IpAddr { + // SAFETY: When the config is read, we check that this is a valid IP + // We assume that the program will never write this field, + // so it should always be valid at this point + IpAddr::from_str(&self.config.manual.host).unwrap() + } +} diff --git a/winapps/src/backend/mod.rs b/winapps/src/backend/mod.rs new file mode 100644 index 00000000..f193b900 --- /dev/null +++ b/winapps/src/backend/mod.rs @@ -0,0 +1,73 @@ +use std::net::IpAddr; + +use enum_dispatch::enum_dispatch; + +use crate::{ + backend::{container::Container, manual::Manual}, + command::Command, + config::App, + Config, Result, +}; + +mod container; +mod manual; + +#[enum_dispatch] +pub trait Backend { + fn check_depends(&self) -> Result<()>; + + fn get_host(&self) -> IpAddr; +} + +#[enum_dispatch(Backend)] +#[derive(Debug, Clone)] +pub enum Backends { + Container, + Manual, +} + +impl Config { + pub fn get_backend(&'static self) -> &'static Backends { + self.backend.get_or_init(|| { + match ( + self.libvirt.enable, + self.container.enable, + self.manual.enable, + ) { + (true, _, _) => todo!(), + (_, true, _) => Container::new(self).into(), + (_, _, true) => Manual::new(self).into(), + _ => unreachable!(), + } + }) + } + + pub fn get_host(&'static self) -> IpAddr { + self.get_backend().get_host() + } + + #[allow(dead_code)] + fn get_installed_apps(&'static self) -> Result> { + let apps = Command::new("C:\\ExtractPrograms.ps1") + .into_remote(self) + .wait_with_output()? + .lines() + .filter_map(|line| { + let mut split = line.split(";"); + + match (split.next(), split.next(), split.next(), split.next()) { + (Some(id), Some(name), Some(path), Some(icon)) => Some(App { + id: id.to_string(), + name: name.to_string(), + win_exec: path.to_string(), + icon: Some(icon.to_string()), + icon_path: None, + }), + _ => None, + } + }) + .collect::>(); + + Ok(apps) + } +} diff --git a/winapps/src/command.rs b/winapps/src/command.rs new file mode 100644 index 00000000..d2d0625f --- /dev/null +++ b/winapps/src/command.rs @@ -0,0 +1,163 @@ +use crate::{ensure, Backend, Config, Error, IntoResult, Result}; +use std::{ + fmt::{Display, Formatter}, + process::{Child, Command as StdCommand, Stdio}, + str::FromStr, +}; +use tracing::debug; + +pub struct Command { + pub exec: String, + pub args: Vec, + error_message: String, + loud: bool, +} + +impl Display for Command { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.exec.as_str())?; + f.write_str(" ")?; + f.write_str(self.args.join(" ").as_str()) + } +} + +impl FromStr for Command { + type Err = Error; + + fn from_str(command: &str) -> std::result::Result { + ensure!(!command.is_empty(), Error::EmptyCommand); + + let (exec, args) = if command.contains(" ") { + let mut split = command.split(" "); + + ( + split + .next() + .expect("There should always be at least one element in the split if the command contains a space") + .to_string(), + split.map(|s| s.to_string()).collect::>(), + ) + } else { + (command.to_string(), Vec::new()) + }; + + Ok(Self { + exec, + args, + error_message: String::from("Error running child command"), + loud: false, + }) + } +} + +impl Command { + pub fn new(exec: &'static str) -> Self { + Self { + exec: exec.to_string(), + args: Vec::new(), + error_message: String::from("Error running child command"), + loud: false, + } + } + + pub fn into_remote(mut self, config: &'static Config) -> Self { + let prev = format!("{} {}", self.exec, self.args.join(" ")); + + self.exec = "sshpass".to_string(); + self.clear_args() + .args(["-p", &*config.auth.password]) + .args([ + "ssh", + &*format!( + "{}@{}", + config.auth.username, + config.get_backend().get_host() + ), + "-p", + &*config.auth.ssh_port.to_string(), + ]) + .arg(prev) + } + + pub fn with_err(mut self, message: &'static str) -> Self { + self.error_message = message.to_string(); + self + } + + pub fn loud(mut self, loud: bool) -> Self { + self.loud = loud; + self + } + + pub fn arg(mut self, arg: S) -> Self + where + S: ToString, + { + self.args.push(arg.to_string()); + + self + } + + pub fn args(mut self, args: I) -> Self + where + I: IntoIterator, + S: ToString, + { + let mut args = args + .into_iter() + .map(|s| s.to_string()) + .collect::>(); + + self.args.append(&mut args); + + self + } + + pub fn clear_args(mut self) -> Self { + self.args = Vec::new(); + + self + } + + pub fn spawn(&self) -> Result { + debug!("Running {self}"); + + let mut command = StdCommand::new(&self.exec); + + if !self.loud { + command.stdout(Stdio::piped()).stderr(Stdio::piped()); + } + + command.args(&self.args); + + command.spawn().map_err(|source| Error::Command { + source: source.into(), + message: self.error_message.clone(), + }) + } + + pub fn wait_with_output(&self) -> Result { + let output = self.spawn()?.wait_with_output().into_result()?; + + output.status.exit_ok().map_err(|source| Error::Command { + source: source.into(), + message: self.error_message.clone(), + })?; + + debug!( + "Child exit code is zero, returning output {} {}", + output.stdout.len(), + output.stderr.len() + ); + + Ok(format!( + "{}\n{}", + String::from_utf8(output.stdout).expect("Commands should always return valid utf-8"), + String::from_utf8(output.stderr).expect("Commands should always return valid utf-8") + )) + } +} + +pub macro command($($fmt:tt)*) { + $crate::command::Command::from_str(&format!($($fmt)*)) +} diff --git a/winapps/src/config/apps.rs b/winapps/src/config/apps.rs new file mode 100644 index 00000000..a9c79f55 --- /dev/null +++ b/winapps/src/config/apps.rs @@ -0,0 +1,66 @@ +use crate::{ + config::App, + dirs::{desktop_dir, icons_dir}, + Config, Result, +}; +use base64::{prelude::BASE64_STANDARD, Engine}; +use std::fs::write; + +impl PartialEq for App { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl App { + /// Panics: If `self.icon_path` is `None` + /// Make sure to write the icon to a file by now + /// and populate `icon_path` + /// This should be normally done by now + fn as_desktop_file(&self, exec: String) -> String { + format!( + "[Desktop Entry] +Name={} +Exec={exec} +Terminal=false +Type=Application +Icon={} +StartupWMClass={} +Comment={}", + self.name, + self.icon_path.clone().unwrap(), + self.id, + self.name + ) + } + + /// Panics: If `self.icon` is `None` and `write_icon` is `true` OR if `self.icon_path` is `None` and `write_icon` is `false` (or if both are `None`) + /// At this point in the program, that shouldn't normally be the case + pub fn link(self, config: &mut Config, exec: String, write_icon: bool) -> Result<()> { + if write_icon { + write( + icons_dir()?.join(format!("{}.png", self.id)), + BASE64_STANDARD.decode(self.icon.clone().unwrap())?, + )?; + } + + write( + desktop_dir()?.join(format!("{}.desktop", self.id)), + self.as_desktop_file(exec), + )?; + + if !config.linked_apps.contains(&self) { + config.linked_apps.push(self) + } + + config.save()?; + + Ok(()) + } +} + +impl Config { + pub fn find_linked_app(&self, id: String) -> Option<&App> { + self.linked_apps.iter().find(|app| app.id == id) + } +} diff --git a/winapps/src/config/mod.rs b/winapps/src/config/mod.rs new file mode 100644 index 00000000..b35d8235 --- /dev/null +++ b/winapps/src/config/mod.rs @@ -0,0 +1,89 @@ +use crate::Backends; +use derive_new::new; +use serde::{Deserialize, Serialize}; +use std::sync::OnceLock; + +mod apps; +mod operations; + +#[derive(new, Debug, Deserialize, Serialize, Clone)] +pub struct Config { + #[new(value = "AuthConfig::new()")] + pub auth: AuthConfig, + #[new(value = "ContainerConfig::new()")] + pub container: ContainerConfig, + #[new(value = "LibvirtConfig::new()")] + pub libvirt: LibvirtConfig, + #[new(value = "ManualConfig::new()")] + pub manual: ManualConfig, + #[new(value = "FreerdpConfig::new()")] + pub freerdp: FreerdpConfig, + #[new(value = "Vec::new()")] + pub linked_apps: Vec, + #[new(value = "false")] + pub debug: bool, + #[new(value = "OnceLock::new()")] + #[serde(skip)] + pub(crate) backend: OnceLock, +} + +#[derive(new, Debug, Deserialize, Serialize, Clone)] +pub struct AuthConfig { + #[new(value = "\"MyWindowsUser\".to_string()")] + pub username: String, + #[new(value = "\"MyWindowsPassword\".to_string()")] + pub password: String, + #[new(value = "2222")] + pub ssh_port: u32, + #[new(value = "\"\".to_string()")] + pub domain: String, +} + +#[derive(new, Debug, Deserialize, Serialize, Clone)] +pub struct ContainerConfig { + #[new(value = "true")] + pub enable: bool, + #[new(value = "false")] + pub enable_podman: bool, + #[new(value = "\"WinApps\".to_string()")] + pub container_name: String, +} + +#[derive(new, Debug, Deserialize, Serialize, Clone)] +pub struct LibvirtConfig { + #[new(value = "false")] + pub enable: bool, + #[new(value = "\"RDPWindows\".to_string()")] + pub vm_name: String, +} + +#[derive(new, Debug, Deserialize, Serialize, Clone)] +pub struct ManualConfig { + #[new(value = "false")] + pub enable: bool, + #[new(value = "\"127.0.0.1\".to_string()")] + pub host: String, +} + +#[derive(new, Debug, Deserialize, Serialize, Clone)] +pub struct FreerdpConfig { + #[new(value = r#"vec![ + "/cert:tofu".to_string(), + "/sound".to_string(), + "/microphone".to_string(), + "+auto-reconnect".to_string(), + "+home-drive".to_string(), + ]"#)] + pub extra_args: Vec, + #[new(value = "\"xfreerdp\".to_string()")] + pub executable: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct App { + pub id: String, + pub name: String, + pub win_exec: String, + pub icon: Option, + pub icon_path: Option, +} diff --git a/winapps/src/config/operations.rs b/winapps/src/config/operations.rs new file mode 100644 index 00000000..55a8b2a7 --- /dev/null +++ b/winapps/src/config/operations.rs @@ -0,0 +1,81 @@ +use std::{fs, fs::File, io::Write, net::IpAddr, path::PathBuf, str::FromStr, sync::OnceLock}; +use tracing::warn; + +use crate::{bail, dirs::path_ok, Config, Error, IntoResult, Result}; + +impl Config { + /// Reads the config from disk. + pub fn load() -> Result<&'static Config> { + static CONFIG: OnceLock = OnceLock::new(); + + CONFIG.get_or_try_init:: Result, Error>(|| { + let config = Config::new(); + let config_path = Self::get_path()?; + + let Ok(true) = config_path.try_exists() else { + warn!("Config does not exist, writing default..."); + config.save()?; + + return Ok(config); + }; + + let config_file = fs::read_to_string(config_path).into_result()?; + let config: Self = toml::from_str(config_file.as_str()).into_result()?; + + if !(config.libvirt.enable ^ config.container.enable ^ config.manual.enable) { + bail!(Error::Config("More than one backend enabled, please set only one of libvirt.enable, container.enable, and manual.enable")); + } + + if config.manual.enable && IpAddr::from_str(&config.manual.host).is_err() { + bail!(Error::Config("Please set manual.host to a valid IP address")); + } + + Ok(config) + }) + } + + fn get_path() -> Result { + let path = match dirs::config_dir() { + Some(path) => Ok(path), + None => "Could not find $XDG_CONFIG_HOME and no config path specified".into_result(), + } + .map(|path| path.join("winapps").join("config.toml"))?; + + // SAFETY: We just set the parent, so there will always be one + let parent = path.parent().unwrap(); + path_ok(parent)?; + + Ok(path) + } + + pub fn save(&self) -> Result<()> { + let config_path = Self::get_path()?; + let serialized_config = toml::to_string_pretty(&self).into_result()?; + + let mut config_file = match config_path.try_exists() { + Ok(true) => File::open(&config_path).into_result(), + Ok(false) => File::create(&config_path).into_result(), + Err(e) => Error::Io(e).into(), + }?; + + if let Err(e) = write!(config_file, "{serialized_config}") { + bail!(e); + } + + Ok(()) + } + + pub fn get_data_path() -> Result { + let path = match dirs::data_dir() { + Some(path) => Ok(path), + None => "Could not find $XDG_DATA_HOME and no data path specified".into_result(), + } + .map(|path| path.join("winapps"))?; + + // SAFETY: We just set the parent, so there will always be one + let parent = path.parent().unwrap(); + path_ok(parent)?; + + Ok(path) + } +} diff --git a/winapps/src/dirs.rs b/winapps/src/dirs.rs new file mode 100644 index 00000000..ca986628 --- /dev/null +++ b/winapps/src/dirs.rs @@ -0,0 +1,51 @@ +use crate::{bail, Result}; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +/// Check whether a directory exists and is a directory +/// If not, try creating it +pub fn path_ok(path: &Path) -> Result<()> { + if let Ok(false) = path.try_exists() { + if let Err(e) = fs::create_dir_all(path) { + bail!(e); + } + } + + if !path.is_dir() { + bail!("Config directory {:?} is not a directory", path); + } + + Ok(()) +} + +/// Get the data dir and validates it exists +pub fn data_dir() -> Result { + let Some(data_dir) = dirs::data_dir().map(|path| path.join("winapps")) else { + bail!("Could not determine $XDG_DATA_HOME") + }; + + path_ok(data_dir.as_path())?; + Ok(data_dir) +} + +/// Get the icons dir and validates it exists +pub fn icons_dir() -> Result { + let Some(data_dir) = dirs::data_dir().map(|path| path.join("icons")) else { + bail!("Could not determine $XDG_DATA_HOME") + }; + + path_ok(data_dir.as_path())?; + Ok(data_dir) +} + +/// Get the XDG applications dir and validates it exists +pub fn desktop_dir() -> Result { + let Some(data_dir) = dirs::data_dir().map(|path| path.join("applications")) else { + bail!("Could not determine $XDG_DATA_HOME") + }; + + path_ok(data_dir.as_path())?; + Ok(data_dir) +} diff --git a/winapps/src/errors.rs b/winapps/src/errors.rs index fa449db2..1aa59e8c 100644 --- a/winapps/src/errors.rs +++ b/winapps/src/errors.rs @@ -1,158 +1,124 @@ -use std::error::Error; use std::fmt::Debug; -use std::process::exit; + +use miette::Diagnostic; /// This enum represents all possible errors that can occur in this crate. +/// /// It is used as a return type for most functions should they return an error. -/// There's 2 base variants: `Message` and `WithError`. +/// There are multiple variants: /// `Message` is used for simple errors that don't have an underlying cause. -/// `WithError` is used for errors that occur from another error. -#[derive(thiserror::Error, Debug)] -pub enum WinappsError { +/// `IoError` is used for errors that occur from an `std::io::Error`. +#[derive(thiserror::Error, Diagnostic, Debug)] +pub enum Error { #[error("{0}")] + #[diagnostic(code(winapps::error))] Message(String), - #[error("{0}\n{1}")] - WithError(#[source] anyhow::Error, String), -} - -impl WinappsError { - /// This function prints the error to the console. - /// All lines are logged as seperate messages, and the source of the error is also logged if it exists. - fn error(&self) { - let messages: Vec = self.to_string().split('\n').map(|s| s.into()).collect(); - messages.iter().for_each(|s| tracing::error!("{}", s)); - - if self.source().is_some() { - tracing::error!("Caused by: {}", self.source().unwrap()); - } - } - /// This function prints the error to the console and exits the program with an exit code of 1. - pub fn exit(&self) -> ! { - self.error(); - - tracing::error!("Unrecoverable error, exiting..."); - exit(1); - } - - /// This function prints the error to the console and panics. - pub fn panic(&self) -> ! { - self.error(); + #[error("invalid config: {0}")] + #[diagnostic(code(winapps::bad_config_error))] + Config(&'static str), + + #[error(transparent)] + #[diagnostic(code(winapps::io_error))] + Io(#[from] std::io::Error), + + #[error("{message}")] + #[diagnostic(code(winapps::child_command_error))] + Command { + message: String, + source: anyhow::Error, + }, + + #[error("VM or container not running")] + #[diagnostic( + code(winapps::bad_vm_state), + help("Ensure your VM or container is started") + )] + VmNotRunning, + + #[error(transparent)] + #[diagnostic(code(winapps::toml_invalid_error))] + Deserialize(#[from] toml::de::Error), + + #[error(transparent)] + #[diagnostic(code(winapps::toml_invalid_error))] + Serialize(#[from] toml::ser::Error), + + #[error("Icon is invalid base64")] + #[diagnostic( + code(winapps::setup_error), + help("Setup returned a badly formed base64 string, is your config correct and are apps correctly installed?") + )] + InvalidIcon(#[from] base64::DecodeError), + + #[error("RDP host is unreachable")] + #[diagnostic( + code(winapps::bad_vm_state), + help("Ensure that the VM or your Firewall doesn't block ping traffic. \ + In case you're running a containerized VM, ensure the container runtime is properly configured.") + )] + HostUnreachable, + + #[error("String passed to Command::fromStr was empty")] + #[diagnostic(code(winapps::bad_string_command))] + EmptyCommand, +} - panic!("Program crashed, see log above"); +impl From<&str> for Error { + fn from(value: &str) -> Self { + Self::Message(value.to_string()) } } -/// This macro is a shortcut for creating a `WinappsError` from a string. -/// You can use normal `format!` syntax inside the macro. -#[macro_export] -macro_rules! error { - ($($fmt:tt)*) => { - $crate::errors::WinappsError::Message(format!($($fmt)*)) - }; -} +pub type Result = std::result::Result; -/// This macro is a shortcut for creating a `WinappsError` from a string. -/// The first argument is the source error. -/// You can use normal `format!` syntax inside the macro. -#[macro_export] -macro_rules! error_from { - ($err:expr, $($fmt:tt)*) => { - $crate::errors::WinappsError::WithError(anyhow::Error::new($err), format!($($fmt)*)) - }; +impl From for Result { + fn from(value: Error) -> Self { + Err(value) + } } -/// This trait serves as a generic way to convert a `Result` or `Option` into a `WinappsError`. -pub trait IntoError { - fn into_error(self, msg: String) -> Result; +pub trait IntoResult { + fn into_result(self) -> Result; } -impl IntoError for Result +impl IntoResult for std::result::Result where - T: Debug, - U: Error + Send + Sync + 'static, + Error: From, { - fn into_error(self, msg: String) -> Result { - if let Err(error) = self { - return Err(WinappsError::WithError(anyhow::Error::new(error), msg)); - } - - Ok(self.unwrap()) + fn into_result(self) -> Result { + self.map_err(|e| Error::from(e)) } } -impl IntoError for Option { - fn into_error(self, msg: String) -> Result { - if self.is_none() { - return Err(WinappsError::Message(msg)); - } - - Ok(self.unwrap()) +impl IntoResult for &str { + fn into_result(self) -> Result { + Err(Error::Message(self.to_string())) } } -/// This macro creates a `Result<_, WinappsError>` from either a `Result` or an `Option`. -/// It also works for all other types that implement `IntoError`. -/// Used internally by `winapps::unwrap_or_exit!` and `winapps::unwrap_or_panic!`. -#[macro_export] -macro_rules! into_error { - ($val:expr) => {{ - fn into_error_impl(val: U) -> std::result::Result - where - T: std::marker::Sized + std::fmt::Debug, - U: $crate::errors::IntoError, - { - val.into_error( - "Expected a value, got None / an Error. \ - See log above for more detail." - .into(), - ) +/// Return early if a condition isn't met, calling `bail!` with the second argument +/// Basically like an assertion which doesn't panic +pub macro ensure { + ($cond:expr, $err:expr) => { + if !$cond { + $crate::bail!($err); } - - into_error_impl($val) - }}; - ($val:expr, $msg:expr) => {{ - fn into_error_impl( - val: U, - msg: String, - ) -> std::result::Result - where - T: std::marker::Sized + std::fmt::Debug, - U: $crate::errors::IntoError, - { - val.into_error(msg) + }, + ($cond:expr, $err:expr, $($fmt:tt)*) => { + if !$cond { + $crate::bail!($err, $($fmt)*) } - - into_error_impl($val, $msg.into()) - }}; -} - -/// This macro unwraps a `Result` or `Option` and returns the value if it exists. -/// Should the value not exist, then the program will exit with exit code 1. -/// Optionally, a message can be passed to the function using standard `format!` syntax. -/// The result type has to implement `Debug` and `Sized`, and the source error type has to implement `Error`, `Send`, `Sync` and has to be `'static`. -/// See `winapps::unwrap_or_panic!` for a version that panics instead of exiting. -#[macro_export] -macro_rules! unwrap_or_exit { - ($expr:expr) => {{ - $crate::into_error!($expr).unwrap_or_else(|e| e.exit()) - }}; - ($expr:expr, $($fmt:tt)*) => {{ - $crate::into_error!($expr, format!($($fmt)*)).unwrap_or_else(|e| e.exit()) - }}; + } } -/// This macro unwraps a `Result` or `Option` and returns the value if it exists. -/// Should the value not exist, then the program will panic. -/// Optionally, a message can be passed to the function using standard `format!` syntax. -/// The result type has to implement `Debug` and `Sized`, and the error type has to implement `Error`, `Send`, `Sync` and has to be `'static`. -/// See `winapps::unwrap_or_exit!` for a version that exits instead of panicking. -#[macro_export] -macro_rules! unwrap_or_panic { - ($expr:expr) => {{ - $crate::into_error!($expr).unwrap_or_else(|e| e.panic()) - }}; - ($expr:expr, $($fmt:tt)*) => {{ - $crate::into_error!($expr, format!($($fmt)*)).unwrap_or_else(|e| e.panic()) - }}; +/// Return, converting the argument into an error +/// Supports `format!` syntax +pub macro bail { + ($err:expr) => { + return Err($err.into()) + }, + ($err:expr, $($fmt:tt)*) => { + return Err($crate::Error::Message(format!($err, $($fmt)*))) + } } diff --git a/winapps/src/freerdp.rs b/winapps/src/freerdp.rs deleted file mode 100644 index b88936aa..00000000 --- a/winapps/src/freerdp.rs +++ /dev/null @@ -1,70 +0,0 @@ -pub mod freerdp_back { - use std::process::{Command, Stdio}; - use tracing::{info, warn}; - - use crate::{unwrap_or_exit, Config, RemoteClient}; - - pub struct Freerdp {} - - impl RemoteClient for Freerdp { - fn check_depends(&self, config: Config) { - let mut xfreerdp = Command::new("xfreerdp"); - xfreerdp.stdout(Stdio::null()); - xfreerdp.stderr(Stdio::null()); - xfreerdp.args(["-h"]); - - unwrap_or_exit!( - xfreerdp.spawn(), - "Freerdp execution failed! It needs to be installed!", - ); - - info!("Freerdp found!"); - - info!("All dependencies found!"); - info!("Running explorer as test!"); - warn!("Check yourself if it appears correctly!"); - - self.run_app(config, Some(&"explorer.exe".to_string())); - - info!("Test finished!"); - } - - fn run_app(&self, config: Config, app: Option<&String>) { - let mut xfreerdp = Command::new("xfreerdp"); - xfreerdp.stdout(Stdio::null()); - xfreerdp.stderr(Stdio::null()); - match app { - Some(exe) => { - xfreerdp.args([ - &format!("/app:{}", exe), - &format!("/d:{}", &config.rdp.domain), - &format!("/u:{}", &config.rdp.username), - &format!("/p:{}", &config.rdp.password), - &format!("/v:{}", &config.rdp.host), - "/dynamic-resolution", - "+auto-reconnect", - "+clipboard", - "+home-drive", - ]); - } - None => { - xfreerdp.args([ - &format!("/d:{}", &config.rdp.domain), - &format!("/u:{}", &config.rdp.username), - &format!("/p:{}", &config.rdp.password), - &format!("/v:{}", &config.rdp.host), - "/dynamic-resolution", - "+auto-reconnect", - "+clipboard", - "+home-drive", - ]); - } - } - - unwrap_or_exit!( - xfreerdp.spawn(), - "Freerdp execution failed, check logs above!", - ); - } - } -} diff --git a/winapps/src/lib.rs b/winapps/src/lib.rs index 148f331e..c01fc297 100644 --- a/winapps/src/lib.rs +++ b/winapps/src/lib.rs @@ -1,162 +1,18 @@ -pub mod errors; -pub mod freerdp; -pub mod quickemu; - -use crate::errors::WinappsError; -use derive_new::new; -use home::home_dir; -use serde::{Deserialize, Serialize}; -use std::io::Write; -use std::path::PathBuf; -use std::{ - env, - fs::{self, File}, - path::Path, +#![feature(decl_macro)] +#![feature(exit_status_error)] +#![feature(once_cell_try)] + +pub use crate::{ + backend::{Backend, Backends}, + config::Config, + errors::{bail, ensure, Error, IntoResult, Result}, + remote_client::{freerdp::Freerdp, RemoteClient}, }; -use tracing::{info, warn}; - -pub trait RemoteClient { - fn check_depends(&self, config: Config); - - fn run_app(&self, config: Config, app: Option<&String>); -} - -#[derive(new, Debug, Deserialize, Serialize)] -pub struct Config { - #[new(value = "HostConfig::new()")] - host: HostConfig, - #[new(value = "RemoteConfig::new()")] - rdp: RemoteConfig, - #[new(value = "VmConfig::new()")] - vm: VmConfig, -} - -#[derive(new, Debug, Deserialize, Serialize)] -pub struct VmConfig { - #[new(value = "\"windows-10\".to_string()")] - short_name: String, - #[new(value = "\"windows-10-22H2\".to_string()")] - name: String, -} - -#[derive(new, Debug, Deserialize, Serialize)] -pub struct HostConfig { - #[new(value = "\"X11\".to_string()")] - display: String, -} - -#[derive(new, Debug, Deserialize, Serialize)] -pub struct RemoteConfig { - #[new(value = "\"127.0.0.1\".to_string()")] - host: String, - #[new(value = "\"WORKGROUP\".to_string()")] - domain: String, - #[new(value = "\"Quickemu\".to_string()")] - username: String, - #[new(value = "\"quickemu\".to_string()")] - password: String, -} - -pub fn get_config_file(path: Option<&str>) -> PathBuf { - let default = match env::var("XDG_CONFIG_HOME") { - Ok(dir) => PathBuf::from(dir).join("winapps"), - Err(_) => { - warn!("Couldn't read XDG_CONFIG_HOME, falling back to ~/.config"); - unwrap_or_panic!(home_dir(), "Couldn't find the home directory").join(".config/winapps") - } - }; - - let path = Path::new(path.unwrap_or(unwrap_or_panic!( - default.to_str(), - "Couldn't convert path {:?} to string", - default - ))); - - if !path.exists() { - info!("{:?} does not exist! Creating...", path); - fs::create_dir_all(path).expect("Failed to create directory"); - } - - if !path.is_dir() { - error!("Config directory {:?} is not a directory", path).panic(); - } - - path.join("config.toml") -} - -pub fn load_config(path: Option<&str>) -> Config { - let config = Config::new(); - let config_path = get_config_file(path); - - if !config_path.exists() { - unwrap_or_panic!( - save_config(&config, path), - "Failed to write default configuration" - ); - - return config; - } - - let config_file = unwrap_or_panic!( - fs::read_to_string(config_path), - "Failed to read configuration file" - ); - - let config: Config = unwrap_or_panic!( - toml::from_str(config_file.as_str()), - "Failed to parse configuration file", - ); - - config -} - -pub fn save_config(config: &Config, path: Option<&str>) -> Result<(), WinappsError> { - let config_path = get_config_file(path); - let serialized_config = unwrap_or_panic!( - toml::to_string(&config), - "Failed to serialize configuration" - ); - - let mut config_file = match config_path.exists() { - true => unwrap_or_panic!( - File::open(&config_path), - "Failed to open configuration file" - ), - false => unwrap_or_panic!( - File::create(&config_path), - "Failed to create configuration file" - ), - }; - - if let Err(e) = write!(config_file, "{}", serialized_config) { - return Err(error_from!(e, "Failed to write configuration file")); - } - - Ok(()) -} - -pub fn get_data_dir() -> PathBuf { - let path = match env::var("XDG_DATA_HOME") { - Ok(dir) => PathBuf::from(dir).join("winapps"), - Err(_) => { - warn!("Couldn't read XDG_DATA_HOME, falling back to ~/.local/share"); - unwrap_or_panic!(home_dir(), "Couldn't find the home directory") - .join(".local/share/winapps") - } - }; - - if !path.exists() { - let dir = path.clone(); - info!( - "Data directory {:?} does not exist! Creating...", - dir.to_str() - ); - fs::create_dir_all(dir).expect("Failed to create directory"); - } - if !path.is_dir() { - error!("Data directory {:?} is not a directory", path).panic(); - } +mod backend; +mod command; +mod errors; +mod remote_client; - path -} +pub mod config; +mod dirs; diff --git a/winapps/src/quickemu.rs b/winapps/src/quickemu.rs deleted file mode 100644 index d2cb54cd..00000000 --- a/winapps/src/quickemu.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::{get_data_dir, save_config, unwrap_or_exit, Config}; -use std::fs; -use std::process::Command; -use tracing::info; - -pub fn create_vm(mut config: Config) { - let data_dir = get_data_dir(); - - let output = unwrap_or_exit!( - Command::new("quickget") - .current_dir(data_dir) - .arg("windows") - .arg("10") - .output(), - "Failed to execute quickget: \n\ - Please make sure quickget is installed and in your PATH" - ); - - config.vm.name = "windows-10-22H2".to_string(); - config.vm.short_name = "windows-10".to_string(); - - unwrap_or_exit!(save_config(&config, None), "Failed to save config"); - - println!("{}", String::from_utf8_lossy(&output.stdout)); -} - -pub fn start_vm(config: Config) { - let data_dir = get_data_dir(); - - let command = unwrap_or_exit!( - Command::new("quickemu") - .current_dir(data_dir) - .args([ - "--ignore-msrs-always", - "--vm", - &format!("{}.conf", config.vm.name), - "--display", - "none", - ]) - .spawn(), - "Failed to execute quickemu: \n\ - Please make sure quickemu is installed and in your PATH" - ); - - let output = unwrap_or_exit!( - command.wait_with_output(), - "Failed to gather output from quickemu / stdout" - ); - - println!("{}", String::from_utf8_lossy(&output.stdout)); -} - -pub fn kill_vm(config: Config) { - let data_dir = get_data_dir(); - - let pid = unwrap_or_exit!( - fs::read_to_string( - data_dir.join(format!("{}/{}.pid", config.vm.short_name, config.vm.name)) - ), - "Failed to read PID file, is the VM running and the config correct?" - ); - - info!("Killing VM with PID {}", pid); - - unwrap_or_exit!( - Command::new("kill").arg(pid.trim()).spawn(), - "Failed to kill VM (execute kill)" - ); -} diff --git a/winapps/src/remote_client/freerdp.rs b/winapps/src/remote_client/freerdp.rs new file mode 100644 index 00000000..590d61ca --- /dev/null +++ b/winapps/src/remote_client/freerdp.rs @@ -0,0 +1,97 @@ +use crate::{bail, command::Command, Config, Error, RemoteClient, Result}; +use regex::Regex; +use std::{ + net::{SocketAddr, TcpStream}, + time::Duration, +}; +use tracing::info; + +pub struct Freerdp { + config: &'static Config, +} + +impl Freerdp { + const TIMEOUT: Duration = Duration::from_secs(5); + const RDP_PORT: u16 = 3389; + + fn get_command(&self) -> Command { + Command::new(self.config.freerdp.executable.as_str()) + .with_err("Freerdp execution failed, check logs above!") + .args(vec![ + format!("/d:{}", &self.config.auth.domain), + format!("/u:{}", &self.config.auth.username), + format!("/p:{}", &self.config.auth.password), + format!("/v:{}", &self.config.get_host()), + ]) + .args(self.config.freerdp.extra_args.iter().cloned()) + .loud(self.config.debug) + } + + pub fn new(config: &'static Config) -> Self { + Self { config } + } +} + +impl RemoteClient for Freerdp { + fn check_depends(&self) -> Result<()> { + self.get_command() + .clear_args() + .with_err("Freerdp execution failed, is `freerdp.executable` correctly set, FreeRDP properly installed and the binary on $PATH?") + .spawn()?; + + info!("Freerdp found!"); + info!("Checking whether host is reachable.."); + + let socket_address = SocketAddr::new(self.config.get_host(), Self::RDP_PORT); + + TcpStream::connect_timeout(&socket_address, Self::TIMEOUT) + .map(|_| ()) + .map_err(|_| Error::HostUnreachable)?; + + Ok(()) + } + + fn run_app(&self, app_name: String, args: Vec) -> Result<()> { + let path = self + .config + .linked_apps + .iter() + .filter_map(|app| app.id.eq(&app_name).then_some(app.win_exec.clone())) + .next() + .unwrap_or(app_name); + + let Some(home_regex) = dirs::home_dir().map(|home| { + Regex::new(&format!( + "^{}", + home.into_os_string() + .into_string() + .expect("$HOME should always be a valid string") + )) + .expect("'^$HOME' should always be a valid regex") + }) else { + bail!("Couldn't find $HOME") + }; + + self.get_command() + .arg(format!("/app:program:{path}")) + .args(args.iter().map(|arg| { + if arg.contains("/") && home_regex.is_match(arg) { + home_regex + .replace(arg, r"\\tsclient\\media") + .to_string() + .replace("/", r"\") + } else { + arg.to_owned() + } + })) + .spawn() + .map(|_| ()) + } + + fn run_full_session(&self) -> Result<()> { + self.get_command() + .arg("+dynamic-resolution".to_string()) + .spawn() + .map(|_| ()) + } +} diff --git a/winapps/src/remote_client/mod.rs b/winapps/src/remote_client/mod.rs new file mode 100644 index 00000000..ba786ab9 --- /dev/null +++ b/winapps/src/remote_client/mod.rs @@ -0,0 +1,11 @@ +use crate::Result; + +pub(crate) mod freerdp; + +pub trait RemoteClient { + fn check_depends(&self) -> Result<()>; + + fn run_app(&self, exec: String, args: Vec) -> Result<()>; + + fn run_full_session(&self) -> Result<()>; +} From bc9fc5d78c9302088a64a27fda391e2bc94ecac4 Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:23:20 +0200 Subject: [PATCH 02/12] feat: doc .envrc parameters & allow overriding toolchain Signed-off-by: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> --- .envrc.example | 8 +++++++- shell.nix | 19 +++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/.envrc.example b/.envrc.example index 607b752d..7c8f85bc 100644 --- a/.envrc.example +++ b/.envrc.example @@ -1 +1,7 @@ -use nix # --arg isIdea true --arg nixpkgs '' +# isIdea: whether to write the current rust toolchain into `.idea/workspace.xml`, defaults to false +# nixpkgs: the path to a nixpkgs checkout, defaults to a pinned version +# mkToolchain: a function taking a fenix instance and returning a toolchain, defaults to `fenix: fenix.complete` +use nix # \ + # --arg isIdea true \ + # --arg nixpkgs '' \ + # --arg mkToolchain 'fenix: fenix.beta' diff --git a/shell.nix b/shell.nix index 01cf6a02..91a75750 100644 --- a/shell.nix +++ b/shell.nix @@ -1,12 +1,15 @@ { nixpkgs ? fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-25.05.tar.gz", - pkgs ? import nixpkgs { }, - fenix ? - (import (fetchTarball "https://github.com/nix-community/fenix/archive/monthly.tar.gz") { - inherit pkgs; - }).complete, + fenix-src ? fetchTarball "https://github.com/nix-community/fenix/archive/monthly.tar.gz", + + mkToolchain ? fenix: fenix.complete, isIdea ? false, }: +let + pkgs = import nixpkgs { }; + fenix = import fenix-src { inherit pkgs; }; + toolchain = mkToolchain fenix; +in pkgs.mkShell rec { buildInputs = with pkgs; [ nixfmt-rfc-style @@ -17,11 +20,11 @@ pkgs.mkShell rec { openssl pkg-config - fenix.toolchain + toolchain.toolchain ]; RUST_BACKTRACE = 1; - RUST_SRC_PATH = "${fenix.rust-src}/lib/rustlib/src/rust/library"; + RUST_SRC_PATH = "${toolchain.rust-src}/lib/rustlib/src/rust/library"; shellHook = let @@ -31,7 +34,7 @@ pkgs.mkShell rec { pkgs.lib.optionalString isIdea '' sed -i \ -e "s|$(${xidel} .idea/workspace.xml -e '${pathFor "explicitPathToStdlib"}')|${RUST_SRC_PATH}|" \ - -e "s|$(${xidel} .idea/workspace.xml -e '${pathFor "toolchainHomeDirectory"}')|${fenix.toolchain}/bin|" \ + -e "s|$(${xidel} .idea/workspace.xml -e '${pathFor "toolchainHomeDirectory"}')|${toolchain.toolchain}/bin|" \ .idea/workspace.xml ''; } From 0740791a77ed1f4f9dc0fe488ed9f5e2a1810291 Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Mon, 8 Sep 2025 17:20:56 +0200 Subject: [PATCH 03/12] fix: typo in ci.yaml Signed-off-by: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> --- .github/workflows/ci.yaml | 9 +-------- .github/workflows/docker.yaml | 3 +-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d10a8007..726ff993 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,11 +6,6 @@ on: pull_request: branches: [ "rewrite" ] -permissions: - contents: write - pull-requests: write - checks: write - jobs: check: runs-on: ubuntu-latest @@ -22,6 +17,4 @@ jobs: with: components: clippy - - uses: auguwu/clippy-action@1.4.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} + - uses: crusty-pie/clippy@v1 diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 4c252bba..f4347b2b 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -37,8 +37,7 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} context: winapps-image - containerfiles: | - Containerfile + containerfiles: winapps-image/Containerfile - name: Push image to GHCR uses: redhat-actions/push-to-registry@v2 From 81cfe2293a42ee1c2f4a8b3022c75feb2c5de66f Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Mon, 8 Sep 2025 20:34:13 +0200 Subject: [PATCH 04/12] feat: add markdown license Signed-off-by: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> --- .../CODE_OF_CONDUCT.md | 2 +- .github/CONTRIBUTING.md | 45 ++ .github/ISSUE_TEMPLATE/1-bug.yml | 65 ++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/workflows/docker.yaml | 1 + CONTRIBUTING.md | 25 - LICENSE | 674 ------------------ LICENSE.md | 616 ++++++++++++++++ 8 files changed, 733 insertions(+), 700 deletions(-) rename CODE_OF_CONDUCT.md => .github/CODE_OF_CONDUCT.md (99%) create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/1-bug.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 CONTRIBUTING.md delete mode 100644 LICENSE create mode 100644 LICENSE.md diff --git a/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md similarity index 99% rename from CODE_OF_CONDUCT.md rename to .github/CODE_OF_CONDUCT.md index 45d257b2..bffc7b66 100644 --- a/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -61,7 +61,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -[INSERT CONTACT METHOD]. +https://matrix.to/#/#winapps:matrix.org. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..9d1b1f75 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,45 @@ +# Contribution Guidelines + +Thank you for contributing to winapps! Before you can contribute, we ask some things of you: + +- Please follow our Code of Conduct, the Contributor Covenant. You can find a copy in this repository or under https://www.contributor-covenant.org/ +- All Contributors have to sign a Developer Certificate of Origin, agreeing to license their contribution under the AGPLv3. You can find a copy of the DCO below or under https://developercertificate.org/. +- Please follow code conventions enforced by `pre-commit`. To keep down CI usage, please run it locally before committing too. + See for installation, then run `pre-commit install` inside the repository you cloned. + +## Developer Certificate of Origin + +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/.github/ISSUE_TEMPLATE/1-bug.yml b/.github/ISSUE_TEMPLATE/1-bug.yml new file mode 100644 index 00000000..82d80056 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-bug.yml @@ -0,0 +1,65 @@ +name: Bug Report +description: File a bug report. +labels: ["triage"] +body: + - type: markdown + attributes: + value: | + We cannot fix nor support all bugs caused by FreeRDP, especially on Wayland. + If you experience visual bugs, please open a discussion instead. + + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us, what did you expect to happen? + placeholder: Tell us what you see! + value: "A bug happened!" + validations: + required: true + + - type: input + id: freerdp + attributes: + label: Your FreeRDP version and where you got it from + placeholder: "FreeRDP 3.10 (Debian Backports)" + validations: + required: true + + - type: input + id: distro + attributes: + label: Your Linux distribution and version + placeholder: "Debian Trixie" + validations: + required: true + + - type: textarea + id: config + attributes: + label: Your `winapps.conf` + description: Please copy and paste your `winapps.conf`. Make sure to not include any sensible data. This will be automatically formatted into code, so no need for backticks. + render: shell + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Logs + description: Give the output of WinApps, FreeRDP etc. where / if applicable. + render: shell + + - type: checkboxes + id: terms + attributes: + label: Terms + options: + - label: I am running the latest version. + required: true + - label: To the best of my knowledge, this is a bug and not a setup nor a FreeRDP problem. + required: true + - label: I have checked for duplicate issues. + required: true + - label: I agree to follow this project's Code of Conduct. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..d265e7dc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Community Support + url: https://github.com/winapps-org/winapps/discussions + about: Get help with non-bug issues here. Please use this instead of filing bug reports. diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index f4347b2b..86e99b35 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -4,6 +4,7 @@ on: push: branches: [ "rewrite" ] paths: [ "winapps-image/**" ] + workflow_dispatch: schedule: # See https://crontab.guru/monthly - cron: 0 0 1 * * diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index cb2d6a39..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,25 +0,0 @@ -# Contribution Guidelines - -Thank you for contributing to winapps! Before you can contribute, we ask some things of you: - -- Please follow our Code of Conduct, the Contributor Covenant. You can find a copy in this repository or under https://www.contributor-covenant.org/ -- All Contributors have to sign [a CLA](https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5) for legal reasons. When opening a PR, @cla-assistant will prompt you and guide you through the process. However, if you contribute on behalf of a legal entity, we ask of you to sign [a different CLA](https://gist.github.com/oskardotglobal/75a8cc056e56a439fa6a1551129ae47f). In that case, please contact us. - -## How to contribute - -- Fork this repository -- Create a new branch with a descriptive name -- Make your changes -- Install and run `pre-commit` (see below) -- Open a Pull Request - -## Pre-commit - -pre-commit is a tool which allows to run checks before committing. -It is recommended to install it and run it before committing, since the same checks -are run through github actions on pull request. We will not merge a pull request unless all checks pass. - -Installation instructions can be found here: https://pre-commit.com/#install
-After installing, run `pre-commit install` in the repository root to install the git hooks. - -It is recommended to run `pre-commit run --all-files` before committing to make sure all checks pass. It is also recommended to use the git cli since graphical git solutions do not always play well with pre-commit. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f288702d..00000000 --- a/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..251dd271 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,616 @@ +# GNU AFFERO GENERAL PUBLIC LICENSE + +Version 3, 19 November 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +## Preamble + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains +free software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing +under this license. + +The precise terms and conditions for copying, distribution and +modification follow. + +## TERMS AND CONDITIONS + +### 0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public +License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +### 1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +### 2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in force. +You may convey covered works to others for the sole purpose of having +them make modifications exclusively for you, or provide you with +facilities for running those works, provided that you comply with the +terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for +you must do so exclusively on your behalf, under your direction and +control, on terms that prohibit them from making any copies of your +copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +- a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. +- b) The work must carry prominent notices stating that it is + released under this License and any conditions added under + section 7. This requirement modifies the requirement in section 4 + to "keep intact all notices". +- c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. +- d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +- a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. +- b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding + Source from a network server at no charge. +- c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. +- d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. +- e) Convey the object code using peer-to-peer transmission, + provided you inform other peers where the object code and + Corresponding Source of the work are being offered to the general + public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +### 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +- a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or +- b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or +- c) Prohibiting misrepresentation of the origin of that material, + or requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or +- d) Limiting the use for publicity purposes of names of licensors + or authors of the material; or +- e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or +- f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions + of it) with contractual assumptions of liability to the recipient, + for any liability that these contractual assumptions directly + impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +### 8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +### 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +### 13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your +version supports such interaction) an opportunity to receive the +Corresponding Source of your version by providing access to the +Corresponding Source from a network server at no charge, through some +standard or customary means of facilitating copying of software. This +Corresponding Source shall include the Corresponding Source for any +work covered by version 3 of the GNU General Public License that is +incorporated pursuant to the following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Affero General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever +published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS From a25980eabf08508021fdff16352f8ed7b21db49f Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Fri, 3 Oct 2025 17:54:46 +0200 Subject: [PATCH 05/12] fix(ci): use compatible toolchain action Signed-off-by: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> --- .github/workflows/ci.yaml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d10a8007..6a982180 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,11 +6,6 @@ on: pull_request: branches: [ "rewrite" ] -permissions: - contents: write - pull-requests: write - checks: write - jobs: check: runs-on: ubuntu-latest @@ -18,10 +13,11 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly + - uses: crusty-pie/toolchain@v1 with: + toolchain: nightly components: clippy - - uses: auguwu/clippy-action@1.4.0 + - uses: crusty-pie/clippy@v1 with: - token: ${{ secrets.GITHUB_TOKEN }} + args: --all-features From 0f49aa9ded6a5b32f35bbd4b653d8ace7bf49c21 Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Sat, 4 Oct 2025 18:58:05 +0200 Subject: [PATCH 06/12] feat: add setup Signed-off-by: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> --- Cargo.lock | 30 ++++++------- winapps-cli/src/main.rs | 20 +++++++-- winapps/Cargo.toml | 1 + winapps/src/backend/container.rs | 18 ++++---- winapps/src/backend/manual.rs | 21 ++++++---- winapps/src/backend/mod.rs | 16 ++++--- winapps/src/command.rs | 6 +-- winapps/src/config/apps.rs | 60 +++++++++++++++----------- winapps/src/config/mod.rs | 11 +++-- winapps/src/config/operations.rs | 63 ++++++++++++++++++---------- winapps/src/lib.rs | 1 + winapps/src/remote_client/freerdp.rs | 28 ++++++++----- 12 files changed, 171 insertions(+), 104 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01ff9293..f0cbc1a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,12 +82,6 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - [[package]] name = "backtrace" version = "0.3.75" @@ -399,11 +393,10 @@ checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -523,9 +516,9 @@ checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -533,15 +526,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.0", ] [[package]] @@ -983,6 +976,7 @@ dependencies = [ "dirs", "enum_dispatch", "miette", + "parking_lot", "regex", "serde", "thiserror", @@ -1008,6 +1002,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + [[package]] name = "windows-sys" version = "0.52.0" @@ -1057,7 +1057,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", diff --git a/winapps-cli/src/main.rs b/winapps-cli/src/main.rs index 7b0840e0..f35865ef 100644 --- a/winapps-cli/src/main.rs +++ b/winapps-cli/src/main.rs @@ -48,9 +48,10 @@ fn main() -> Result<()> { .with_env_filter(EnvFilter::from_default_env()) .init(); - let config = Config::load()?; + let config_lock = Config::try_get_lock()?; + let config = config_lock.read(); - let client = Freerdp::new(config); + let client = Freerdp::new(); let backend = config.get_backend(); client.check_depends()?; @@ -61,7 +62,20 @@ fn main() -> Result<()> { match cli.clone().get_matches().subcommand() { Some(("setup", _)) => { info!("Running setup"); - todo!() + + match inquire::MultiSelect::new("Select apps to link", config.get_available_apps()?) + .prompt_skippable() + .map_err(|e| winapps::Error::Command { + message: "Failed to display selection dialog".into(), + source: e.into(), + })? { + Some(apps) => apps + .into_iter() + .try_for_each(|app| app.link(&mut config_lock.write(), "".into()))?, + None => info!("No apps selected, skipping setup..."), + }; + + Ok(()) } Some(("connect", _)) => { diff --git a/winapps/Cargo.toml b/winapps/Cargo.toml index f4288c28..6469abf1 100644 --- a/winapps/Cargo.toml +++ b/winapps/Cargo.toml @@ -17,3 +17,4 @@ thiserror = "2.0.9" enum_dispatch = "0.3.13" base64 = "0.22.1" regex = { version = "1.11.2", features = [] } +parking_lot = "0.12.5" diff --git a/winapps/src/backend/container.rs b/winapps/src/backend/container.rs index 2b5982de..3b2947fd 100644 --- a/winapps/src/backend/container.rs +++ b/winapps/src/backend/container.rs @@ -1,10 +1,11 @@ use crate::{command::command, ensure, Backend, Config, Error, Result}; +use parking_lot::RwLock; use std::net::{IpAddr, Ipv4Addr}; use tracing::debug; #[derive(Debug, Clone)] pub struct Container { - config: &'static Config, + config: &'static RwLock, } impl Container { @@ -13,21 +14,24 @@ impl Container { const DEFAULT_COMMAND: &'static str = "docker"; const PODMAN_COMMAND: &'static str = "podman"; - pub(crate) fn new(config: &'static Config) -> Self { - Self { config } + pub(crate) fn new() -> Self { + Self { + config: Config::get_lock(), + } } } impl Backend for Container { fn check_depends(&self) -> Result<()> { - assert!(self.config.container.enable); + let config = self.config.read(); + assert!(config.container.enable); ensure!( - !self.config.container.container_name.is_empty(), + !config.container.container_name.is_empty(), Error::Config("Container name shouldn't be empty") ); - let command = if self.config.container.enable_podman { + let command = if config.container.enable_podman { Self::PODMAN_COMMAND } else { Self::DEFAULT_COMMAND @@ -35,7 +39,7 @@ impl Backend for Container { let state = command!( r#"{command} ps --all --filter name={} --format {{{{.State}}}}"#, - self.config.container.container_name + config.container.container_name )? .with_err("Could not get container status") .wait_with_output()?; diff --git a/winapps/src/backend/manual.rs b/winapps/src/backend/manual.rs index a53fe12d..c524622b 100644 --- a/winapps/src/backend/manual.rs +++ b/winapps/src/backend/manual.rs @@ -1,28 +1,33 @@ +use parking_lot::RwLock; use std::{net::IpAddr, str::FromStr}; use crate::{ensure, Backend, Config, Error, Result}; #[derive(Debug, Clone)] pub struct Manual { - config: &'static Config, + config: &'static RwLock, } impl Manual { - pub(crate) fn new(config: &'static Config) -> Self { - Self { config } + pub(crate) fn new() -> Self { + Self { + config: Config::get_lock(), + } } } impl Backend for Manual { fn check_depends(&self) -> Result<()> { - assert!(self.config.manual.enable); + let config = self.config.read(); + + assert!(config.manual.enable); ensure!( - !self.config.manual.host.is_empty(), + !config.manual.host.is_empty(), Error::Config("Host shouldn't be empty") ); ensure!( - IpAddr::from_str(&self.config.manual.host).is_ok(), + IpAddr::from_str(&config.manual.host).is_ok(), Error::Config("manual.host is not a valid IP address") ); @@ -30,9 +35,11 @@ impl Backend for Manual { } fn get_host(&self) -> IpAddr { + let config = self.config.read(); + // SAFETY: When the config is read, we check that this is a valid IP // We assume that the program will never write this field, // so it should always be valid at this point - IpAddr::from_str(&self.config.manual.host).unwrap() + IpAddr::from_str(&config.manual.host).unwrap() } } diff --git a/winapps/src/backend/mod.rs b/winapps/src/backend/mod.rs index f193b900..fd7330ca 100644 --- a/winapps/src/backend/mod.rs +++ b/winapps/src/backend/mod.rs @@ -5,7 +5,7 @@ use enum_dispatch::enum_dispatch; use crate::{ backend::{container::Container, manual::Manual}, command::Command, - config::App, + config::{App, AppKind::Detected}, Config, Result, }; @@ -27,7 +27,7 @@ pub enum Backends { } impl Config { - pub fn get_backend(&'static self) -> &'static Backends { + pub fn get_backend(&self) -> &Backends { self.backend.get_or_init(|| { match ( self.libvirt.enable, @@ -35,19 +35,18 @@ impl Config { self.manual.enable, ) { (true, _, _) => todo!(), - (_, true, _) => Container::new(self).into(), - (_, _, true) => Manual::new(self).into(), + (_, true, _) => Container::new().into(), + (_, _, true) => Manual::new().into(), _ => unreachable!(), } }) } - pub fn get_host(&'static self) -> IpAddr { + pub fn get_host(&self) -> IpAddr { self.get_backend().get_host() } - #[allow(dead_code)] - fn get_installed_apps(&'static self) -> Result> { + pub fn get_available_apps(&self) -> Result> { let apps = Command::new("C:\\ExtractPrograms.ps1") .into_remote(self) .wait_with_output()? @@ -60,8 +59,7 @@ impl Config { id: id.to_string(), name: name.to_string(), win_exec: path.to_string(), - icon: Some(icon.to_string()), - icon_path: None, + kind: Detected(icon.to_string()), }), _ => None, } diff --git a/winapps/src/command.rs b/winapps/src/command.rs index d2d0625f..e89bcbce 100644 --- a/winapps/src/command.rs +++ b/winapps/src/command.rs @@ -51,16 +51,16 @@ impl FromStr for Command { } impl Command { - pub fn new(exec: &'static str) -> Self { + pub fn new>(exec: T) -> Self { Self { - exec: exec.to_string(), + exec: exec.into(), args: Vec::new(), error_message: String::from("Error running child command"), loud: false, } } - pub fn into_remote(mut self, config: &'static Config) -> Self { + pub fn into_remote(mut self, config: &Config) -> Self { let prev = format!("{} {}", self.exec, self.args.join(" ")); self.exec = "sshpass".to_string(); diff --git a/winapps/src/config/apps.rs b/winapps/src/config/apps.rs index a9c79f55..b1828ca6 100644 --- a/winapps/src/config/apps.rs +++ b/winapps/src/config/apps.rs @@ -1,10 +1,10 @@ use crate::{ - config::App, + config::{App, AppKind}, dirs::{desktop_dir, icons_dir}, Config, Result, }; use base64::{prelude::BASE64_STANDARD, Engine}; -use std::fs::write; +use std::{fmt::Display, fs::write}; impl PartialEq for App { fn eq(&self, other: &Self) -> bool { @@ -12,14 +12,32 @@ impl PartialEq for App { } } +impl Display for App { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{} ({})", self.name, self.win_exec)) + } +} + impl App { - /// Panics: If `self.icon_path` is `None` - /// Make sure to write the icon to a file by now - /// and populate `icon_path` - /// This should be normally done by now - fn as_desktop_file(&self, exec: String) -> String { - format!( - "[Desktop Entry] + fn try_as_existing(&mut self) -> Result<&mut Self> { + match self.kind.clone() { + AppKind::Detected(base64) => { + let path = icons_dir()?.join(format!("{}.png", self.id)); + write(path.clone(), BASE64_STANDARD.decode(base64)?)?; + + self.kind = AppKind::Existing(path); + + Ok(self) + } + AppKind::Existing(_) => Ok(self), + } + } + + fn try_as_desktop_file(&mut self, exec: String) -> Result { + match &self.kind { + AppKind::Detected(_) => self.try_as_existing()?.try_as_desktop_file(exec), + AppKind::Existing(path) => Ok(format!( + "[Desktop Entry] Name={} Exec={exec} Terminal=false @@ -27,26 +45,20 @@ Type=Application Icon={} StartupWMClass={} Comment={}", - self.name, - self.icon_path.clone().unwrap(), - self.id, - self.name - ) + self.name, + path.to_string_lossy(), + self.id, + self.name + )), + } } - /// Panics: If `self.icon` is `None` and `write_icon` is `true` OR if `self.icon_path` is `None` and `write_icon` is `false` (or if both are `None`) - /// At this point in the program, that shouldn't normally be the case - pub fn link(self, config: &mut Config, exec: String, write_icon: bool) -> Result<()> { - if write_icon { - write( - icons_dir()?.join(format!("{}.png", self.id)), - BASE64_STANDARD.decode(self.icon.clone().unwrap())?, - )?; - } + pub fn link(mut self, config: &mut Config, exec: String) -> Result<()> { + self.try_as_existing()?; write( desktop_dir()?.join(format!("{}.desktop", self.id)), - self.as_desktop_file(exec), + self.try_as_desktop_file(exec)?, )?; if !config.linked_apps.contains(&self) { diff --git a/winapps/src/config/mod.rs b/winapps/src/config/mod.rs index b35d8235..86e7e38a 100644 --- a/winapps/src/config/mod.rs +++ b/winapps/src/config/mod.rs @@ -1,7 +1,7 @@ use crate::Backends; use derive_new::new; use serde::{Deserialize, Serialize}; -use std::sync::OnceLock; +use std::{path::PathBuf, sync::OnceLock}; mod apps; mod operations; @@ -79,11 +79,16 @@ pub struct FreerdpConfig { pub executable: String, } +#[derive(Debug, Deserialize, Serialize, Clone)] +pub enum AppKind { + Detected(String), + Existing(PathBuf), +} + #[derive(Debug, Deserialize, Serialize, Clone)] pub struct App { pub id: String, pub name: String, pub win_exec: String, - pub icon: Option, - pub icon_path: Option, + pub kind: AppKind, } diff --git a/winapps/src/config/operations.rs b/winapps/src/config/operations.rs index 55a8b2a7..85c8c2c3 100644 --- a/winapps/src/config/operations.rs +++ b/winapps/src/config/operations.rs @@ -1,37 +1,56 @@ -use std::{fs, fs::File, io::Write, net::IpAddr, path::PathBuf, str::FromStr, sync::OnceLock}; +use parking_lot::RwLock; +use std::{ + fs::{self, File}, + io::Write, + net::IpAddr, + path::PathBuf, + str::FromStr, + sync::OnceLock, +}; use tracing::warn; use crate::{bail, dirs::path_ok, Config, Error, IntoResult, Result}; +static CONFIG: OnceLock> = OnceLock::new(); + impl Config { - /// Reads the config from disk. - pub fn load() -> Result<&'static Config> { - static CONFIG: OnceLock = OnceLock::new(); + /// Get a lock for the config, or an error if it couldn't be read + pub fn try_get_lock() -> Result<&'static RwLock> { + CONFIG.get_or_try_init(|| Ok(RwLock::new(Self::try_new()?))) + } - CONFIG.get_or_try_init:: Result, Error>(|| { - let config = Config::new(); - let config_path = Self::get_path()?; + /// Get a lock for the config + /// Panics: if the lock is not initialized + pub fn get_lock() -> &'static RwLock { + CONFIG.get().expect("The lock is not initialized") + } - let Ok(true) = config_path.try_exists() else { - warn!("Config does not exist, writing default..."); - config.save()?; + /// Reads the config from disk. + fn try_new() -> Result { + let config = Self::new(); + let config_path = Self::get_path()?; + + let Ok(true) = config_path.try_exists() else { + warn!("Config does not exist, writing default..."); + config.save()?; - return Ok(config); - }; + return Ok(config); + }; - let config_file = fs::read_to_string(config_path).into_result()?; - let config: Self = toml::from_str(config_file.as_str()).into_result()?; + let config_file = fs::read_to_string(config_path).into_result()?; + let config: Self = toml::from_str(config_file.as_str()).into_result()?; - if !(config.libvirt.enable ^ config.container.enable ^ config.manual.enable) { - bail!(Error::Config("More than one backend enabled, please set only one of libvirt.enable, container.enable, and manual.enable")); - } + if !(config.libvirt.enable ^ config.container.enable ^ config.manual.enable) { + bail!(Error::Config("More than one backend enabled, please set only one of libvirt.enable, container.enable, and manual.enable")); + } - if config.manual.enable && IpAddr::from_str(&config.manual.host).is_err() { - bail!(Error::Config("Please set manual.host to a valid IP address")); - } + if config.manual.enable && IpAddr::from_str(&config.manual.host).is_err() { + bail!(Error::Config( + "Please set manual.host to a valid IP address" + )); + } - Ok(config) - }) + Ok(config) } fn get_path() -> Result { diff --git a/winapps/src/lib.rs b/winapps/src/lib.rs index c01fc297..2ec205ea 100644 --- a/winapps/src/lib.rs +++ b/winapps/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(clippy::new_without_default)] #![feature(decl_macro)] #![feature(exit_status_error)] #![feature(once_cell_try)] diff --git a/winapps/src/remote_client/freerdp.rs b/winapps/src/remote_client/freerdp.rs index 590d61ca..c4b59c47 100644 --- a/winapps/src/remote_client/freerdp.rs +++ b/winapps/src/remote_client/freerdp.rs @@ -1,4 +1,5 @@ use crate::{bail, command::Command, Config, Error, RemoteClient, Result}; +use parking_lot::RwLock; use regex::Regex; use std::{ net::{SocketAddr, TcpStream}, @@ -7,7 +8,7 @@ use std::{ use tracing::info; pub struct Freerdp { - config: &'static Config, + config: &'static RwLock, } impl Freerdp { @@ -15,20 +16,24 @@ impl Freerdp { const RDP_PORT: u16 = 3389; fn get_command(&self) -> Command { - Command::new(self.config.freerdp.executable.as_str()) + let config = self.config.read(); + + Command::new(config.freerdp.executable.to_owned()) .with_err("Freerdp execution failed, check logs above!") .args(vec![ - format!("/d:{}", &self.config.auth.domain), - format!("/u:{}", &self.config.auth.username), - format!("/p:{}", &self.config.auth.password), - format!("/v:{}", &self.config.get_host()), + format!("/d:{}", &config.auth.domain), + format!("/u:{}", &config.auth.username), + format!("/p:{}", &config.auth.password), + format!("/v:{}", &config.get_host()), ]) - .args(self.config.freerdp.extra_args.iter().cloned()) - .loud(self.config.debug) + .args(config.freerdp.extra_args.iter().cloned()) + .loud(config.debug) } - pub fn new(config: &'static Config) -> Self { - Self { config } + pub fn new() -> Self { + Self { + config: Config::get_lock(), + } } } @@ -42,7 +47,7 @@ impl RemoteClient for Freerdp { info!("Freerdp found!"); info!("Checking whether host is reachable.."); - let socket_address = SocketAddr::new(self.config.get_host(), Self::RDP_PORT); + let socket_address = SocketAddr::new(self.config.read().get_host(), Self::RDP_PORT); TcpStream::connect_timeout(&socket_address, Self::TIMEOUT) .map(|_| ()) @@ -54,6 +59,7 @@ impl RemoteClient for Freerdp { fn run_app(&self, app_name: String, args: Vec) -> Result<()> { let path = self .config + .read() .linked_apps .iter() .filter_map(|app| app.id.eq(&app_name).then_some(app.win_exec.clone())) From 858f790ca96722542f790397ef6d48160293fe8c Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Sat, 4 Oct 2025 19:04:17 +0200 Subject: [PATCH 07/12] feat: pin foreign action SHA Signed-off-by: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> --- .github/workflows/ci.yaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6a982180..547b59f3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,6 +6,9 @@ on: pull_request: branches: [ "rewrite" ] +permissions: + checks: write + jobs: check: runs-on: ubuntu-latest @@ -13,11 +16,10 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: crusty-pie/toolchain@v1 + - uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly components: clippy - - uses: crusty-pie/clippy@v1 + - uses: auguwu/clippy-action@1.4.0 with: - args: --all-features + token: ${{ secrets.GITHUB_TOKEN }} From bc49e92e5bb132e9c87c935e1550412f0a382bf4 Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Sun, 5 Oct 2025 11:43:34 +0200 Subject: [PATCH 08/12] fix: drop quickemu subcommand Signed-off-by: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> --- winapps-cli/src/main.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/winapps-cli/src/main.rs b/winapps-cli/src/main.rs index f35865ef..9e340356 100644 --- a/winapps-cli/src/main.rs +++ b/winapps-cli/src/main.rs @@ -22,21 +22,6 @@ fn cli() -> Command { .allow_hyphen_values(true), ), ) - .subcommand( - Command::new("vm") - .about("Manage a windows 10 vm using quickemu") - .subcommand_required(true) - .arg_required_else_help(true) - .allow_external_subcommands(true) - .subcommand(Command::new("create").about("Create a windows 10 vm using quickget")) - .subcommand(Command::new("start").about("Start the vm")) - .subcommand(Command::new("kill").about("Kill the running VM")), - ) - .subcommand( - Command::new("app") - .about("Connects to app on remote") - .arg(arg!( "App to open")), - ) } fn main() -> Result<()> { From a3781a7bbd36f996e2afdb6a8f3554ba4727bc7a Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Sun, 5 Oct 2025 15:39:46 +0200 Subject: [PATCH 09/12] fix: don't deadlock on setup Signed-off-by: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> --- winapps-cli/src/main.rs | 27 ++++++++------ winapps-image/oem/install.ps1 | 2 - winapps/src/backend/mod.rs | 4 +- winapps/src/command.rs | 1 + winapps/src/config/apps.rs | 47 +++++++++++++++--------- winapps/src/config/mod.rs | 10 +++-- winapps/src/config/operations.rs | 55 ++++------------------------ winapps/src/dirs.rs | 10 +++++ winapps/src/remote_client/freerdp.rs | 2 +- 9 files changed, 73 insertions(+), 85 deletions(-) diff --git a/winapps-cli/src/main.rs b/winapps-cli/src/main.rs index 9e340356..8837f56c 100644 --- a/winapps-cli/src/main.rs +++ b/winapps-cli/src/main.rs @@ -34,13 +34,7 @@ fn main() -> Result<()> { .init(); let config_lock = Config::try_get_lock()?; - let config = config_lock.read(); - - let client = Freerdp::new(); - let backend = config.get_backend(); - - client.check_depends()?; - backend.check_depends()?; + config_lock.read().get_backend().check_depends()?; let cli = cli(); @@ -48,7 +42,11 @@ fn main() -> Result<()> { Some(("setup", _)) => { info!("Running setup"); - match inquire::MultiSelect::new("Select apps to link", config.get_available_apps()?) + let apps = config_lock.read().get_available_apps()?; + + // TODO: Allow deleting apps, maybe pass installed apps + // so they can be deselected? + match inquire::MultiSelect::new("Select apps to link", apps) .prompt_skippable() .map_err(|e| winapps::Error::Command { message: "Failed to display selection dialog".into(), @@ -56,7 +54,7 @@ fn main() -> Result<()> { })? { Some(apps) => apps .into_iter() - .try_for_each(|app| app.link(&mut config_lock.write(), "".into()))?, + .try_for_each(|app| app.link(&mut config_lock.write()))?, None => info!("No apps selected, skipping setup..."), }; @@ -66,6 +64,9 @@ fn main() -> Result<()> { Some(("connect", _)) => { info!("Connecting to remote"); + let client = Freerdp::new(); + + client.check_depends()?; client.run_full_session()?; Ok(()) } @@ -73,11 +74,15 @@ fn main() -> Result<()> { Some(("run", sub_matches)) => { info!("Connecting to app on remote"); + let client = Freerdp::new(); + + client.check_depends()?; + let args = sub_matches - .get_many::("args") + .get_many::("ARGS") .map_or(Vec::new(), |args| args.map(|v| v.to_owned()).collect()); - match sub_matches.get_one::("name") { + match sub_matches.get_one::("NAME") { None => panic!("App is required and should never be None here"), Some(app) => client.run_app(app.to_owned(), args), }?; diff --git a/winapps-image/oem/install.ps1 b/winapps-image/oem/install.ps1 index 128beb12..f96e93ae 100644 --- a/winapps-image/oem/install.ps1 +++ b/winapps-image/oem/install.ps1 @@ -50,8 +50,6 @@ function Install-NetworkProfileCleanup } } -Set-ExecutionPolicy Unrestricted - # Run functions Copy-Item -Path "$PSScriptRoot\ExtractPrograms.ps1" -Destination "$env:windir" -Force diff --git a/winapps/src/backend/mod.rs b/winapps/src/backend/mod.rs index fd7330ca..34787f51 100644 --- a/winapps/src/backend/mod.rs +++ b/winapps/src/backend/mod.rs @@ -5,7 +5,7 @@ use enum_dispatch::enum_dispatch; use crate::{ backend::{container::Container, manual::Manual}, command::Command, - config::{App, AppKind::Detected}, + config::{App, AppKind}, Config, Result, }; @@ -59,7 +59,7 @@ impl Config { id: id.to_string(), name: name.to_string(), win_exec: path.to_string(), - kind: Detected(icon.to_string()), + kind: AppKind::FromBase64(icon.to_string()), }), _ => None, } diff --git a/winapps/src/command.rs b/winapps/src/command.rs index e89bcbce..f37c10ad 100644 --- a/winapps/src/command.rs +++ b/winapps/src/command.rs @@ -73,6 +73,7 @@ impl Command { config.auth.username, config.get_backend().get_host() ), + "-oStrictHostKeyChecking=accept-new", "-p", &*config.auth.ssh_port.to_string(), ]) diff --git a/winapps/src/config/apps.rs b/winapps/src/config/apps.rs index b1828ca6..e0fbde42 100644 --- a/winapps/src/config/apps.rs +++ b/winapps/src/config/apps.rs @@ -4,7 +4,12 @@ use crate::{ Config, Result, }; use base64::{prelude::BASE64_STANDARD, Engine}; -use std::{fmt::Display, fs::write}; +use std::{ + fmt::Display, + fs::{set_permissions, write, Permissions}, + os::unix::fs::PermissionsExt, +}; +use tracing::debug; impl PartialEq for App { fn eq(&self, other: &Self) -> bool { @@ -21,51 +26,57 @@ impl Display for App { impl App { fn try_as_existing(&mut self) -> Result<&mut Self> { match self.kind.clone() { - AppKind::Detected(base64) => { + AppKind::FromBase64(base64) => { let path = icons_dir()?.join(format!("{}.png", self.id)); write(path.clone(), BASE64_STANDARD.decode(base64)?)?; - self.kind = AppKind::Existing(path); + self.kind = AppKind::Existing; Ok(self) } - AppKind::Existing(_) => Ok(self), + AppKind::Existing => Ok(self), } } - fn try_as_desktop_file(&mut self, exec: String) -> Result { + fn try_as_desktop_file(&mut self) -> Result { + debug!("Writing desktop icon for {}", self.id); + match &self.kind { - AppKind::Detected(_) => self.try_as_existing()?.try_as_desktop_file(exec), - AppKind::Existing(path) => Ok(format!( + AppKind::FromBase64(_) => self.try_as_existing()?.try_as_desktop_file(), + AppKind::Existing => Ok(format!( "[Desktop Entry] Name={} -Exec={exec} +Exec=winapps run {} Terminal=false Type=Application Icon={} StartupWMClass={} -Comment={}", +Comment={} (WinApps)", self.name, - path.to_string_lossy(), + self.id, + icons_dir()? + .join(format!("{}.png", self.id)) + .to_string_lossy(), self.id, self.name )), } } - pub fn link(mut self, config: &mut Config, exec: String) -> Result<()> { + pub fn link(mut self, config: &mut Config) -> Result<()> { self.try_as_existing()?; - write( - desktop_dir()?.join(format!("{}.desktop", self.id)), - self.try_as_desktop_file(exec)?, - )?; + let path = desktop_dir()?.join(format!("{}.desktop", self.id)); + + write(&path, self.try_as_desktop_file()?)?; + set_permissions(&path, Permissions::from_mode(0o750))?; if !config.linked_apps.contains(&self) { - config.linked_apps.push(self) - } + debug!("Writing app {} to config", self.id); - config.save()?; + config.linked_apps.push(self); + config.save()?; + } Ok(()) } diff --git a/winapps/src/config/mod.rs b/winapps/src/config/mod.rs index 86e7e38a..755828cb 100644 --- a/winapps/src/config/mod.rs +++ b/winapps/src/config/mod.rs @@ -1,7 +1,7 @@ use crate::Backends; use derive_new::new; use serde::{Deserialize, Serialize}; -use std::{path::PathBuf, sync::OnceLock}; +use std::sync::OnceLock; mod apps; mod operations; @@ -79,10 +79,11 @@ pub struct FreerdpConfig { pub executable: String, } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Default, Deserialize, Serialize, Clone)] pub enum AppKind { - Detected(String), - Existing(PathBuf), + FromBase64(String), + #[default] + Existing, } #[derive(Debug, Deserialize, Serialize, Clone)] @@ -90,5 +91,6 @@ pub struct App { pub id: String, pub name: String, pub win_exec: String, + #[serde(skip)] pub kind: AppKind, } diff --git a/winapps/src/config/operations.rs b/winapps/src/config/operations.rs index 85c8c2c3..7876ef3f 100644 --- a/winapps/src/config/operations.rs +++ b/winapps/src/config/operations.rs @@ -1,15 +1,13 @@ use parking_lot::RwLock; use std::{ - fs::{self, File}, - io::Write, + fs::{self, write}, net::IpAddr, - path::PathBuf, str::FromStr, sync::OnceLock, }; use tracing::warn; -use crate::{bail, dirs::path_ok, Config, Error, IntoResult, Result}; +use crate::{bail, dirs::config_dir, Config, Error, IntoResult, Result}; static CONFIG: OnceLock> = OnceLock::new(); @@ -28,7 +26,7 @@ impl Config { /// Reads the config from disk. fn try_new() -> Result { let config = Self::new(); - let config_path = Self::get_path()?; + let config_path = config_dir()?.join("config.toml"); let Ok(true) = config_path.try_exists() else { warn!("Config does not exist, writing default..."); @@ -53,48 +51,11 @@ impl Config { Ok(config) } - fn get_path() -> Result { - let path = match dirs::config_dir() { - Some(path) => Ok(path), - None => "Could not find $XDG_CONFIG_HOME and no config path specified".into_result(), - } - .map(|path| path.join("winapps").join("config.toml"))?; - - // SAFETY: We just set the parent, so there will always be one - let parent = path.parent().unwrap(); - path_ok(parent)?; - - Ok(path) - } - pub fn save(&self) -> Result<()> { - let config_path = Self::get_path()?; - let serialized_config = toml::to_string_pretty(&self).into_result()?; - - let mut config_file = match config_path.try_exists() { - Ok(true) => File::open(&config_path).into_result(), - Ok(false) => File::create(&config_path).into_result(), - Err(e) => Error::Io(e).into(), - }?; - - if let Err(e) = write!(config_file, "{serialized_config}") { - bail!(e); - } - - Ok(()) - } - - pub fn get_data_path() -> Result { - let path = match dirs::data_dir() { - Some(path) => Ok(path), - None => "Could not find $XDG_DATA_HOME and no data path specified".into_result(), - } - .map(|path| path.join("winapps"))?; - - // SAFETY: We just set the parent, so there will always be one - let parent = path.parent().unwrap(); - path_ok(parent)?; - - Ok(path) + write( + config_dir()?.join("config.toml"), + toml::to_string_pretty(&self).into_result()?, + ) + .into_result() } } diff --git a/winapps/src/dirs.rs b/winapps/src/dirs.rs index ca986628..e5ad29aa 100644 --- a/winapps/src/dirs.rs +++ b/winapps/src/dirs.rs @@ -30,6 +30,16 @@ pub fn data_dir() -> Result { Ok(data_dir) } +/// Get the data dir and validates it exists +pub fn config_dir() -> Result { + let Some(config_dir) = dirs::config_dir().map(|path| path.join("winapps")) else { + bail!("Could not determine $XDG_CONFIG_HOME") + }; + + path_ok(config_dir.as_path())?; + Ok(config_dir) +} + /// Get the icons dir and validates it exists pub fn icons_dir() -> Result { let Some(data_dir) = dirs::data_dir().map(|path| path.join("icons")) else { diff --git a/winapps/src/remote_client/freerdp.rs b/winapps/src/remote_client/freerdp.rs index c4b59c47..496780d2 100644 --- a/winapps/src/remote_client/freerdp.rs +++ b/winapps/src/remote_client/freerdp.rs @@ -79,7 +79,7 @@ impl RemoteClient for Freerdp { }; self.get_command() - .arg(format!("/app:program:{path}")) + .arg(format!("/app:program:{path},hidef:on")) .args(args.iter().map(|arg| { if arg.contains("/") && home_regex.is_match(arg) { home_regex From 5e7cc1a16710685fef36fceb8e8afb72534428d8 Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Sun, 12 Oct 2025 17:23:46 +0200 Subject: [PATCH 10/12] chore: address most coderabbit lints Signed-off-by: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> --- .github/CODE_OF_CONDUCT.md | 2 +- .github/CONTRIBUTING.md | 4 ++-- .github/ISSUE_TEMPLATE/1-bug.yml | 2 +- .github/workflows/ci.yaml | 4 ++-- .github/workflows/docker.yaml | 2 +- shell.nix | 10 ++++++---- winapps/src/backend/manual.rs | 6 +++++- winapps/src/backend/mod.rs | 2 +- winapps/src/config/apps.rs | 18 ++++++++++-------- winapps/src/config/operations.rs | 21 ++++++++++++--------- winapps/src/dirs.rs | 2 +- winapps/src/remote_client/freerdp.rs | 8 +++++--- 12 files changed, 47 insertions(+), 34 deletions(-) diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index bffc7b66..f2263141 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -61,7 +61,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -https://matrix.to/#/#winapps:matrix.org. +. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 9d1b1f75..cdd98b71 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -2,8 +2,8 @@ Thank you for contributing to winapps! Before you can contribute, we ask some things of you: -- Please follow our Code of Conduct, the Contributor Covenant. You can find a copy in this repository or under https://www.contributor-covenant.org/ -- All Contributors have to sign a Developer Certificate of Origin, agreeing to license their contribution under the AGPLv3. You can find a copy of the DCO below or under https://developercertificate.org/. +- Please follow our Code of Conduct, the Contributor Covenant. You can find a copy in this repository or under +- All Contributors have to sign a Developer Certificate of Origin, agreeing to license their contribution under the AGPLv3. You can find a copy of the DCO below or under . - Please follow code conventions enforced by `pre-commit`. To keep down CI usage, please run it locally before committing too. See for installation, then run `pre-commit install` inside the repository you cloned. diff --git a/.github/ISSUE_TEMPLATE/1-bug.yml b/.github/ISSUE_TEMPLATE/1-bug.yml index 82d80056..07d551a6 100644 --- a/.github/ISSUE_TEMPLATE/1-bug.yml +++ b/.github/ISSUE_TEMPLATE/1-bug.yml @@ -38,7 +38,7 @@ body: id: config attributes: label: Your `winapps.conf` - description: Please copy and paste your `winapps.conf`. Make sure to not include any sensible data. This will be automatically formatted into code, so no need for backticks. + description: Please copy and paste your `winapps.conf`. Make sure to not include any sensitive data. This will be automatically formatted into code, so no need for backticks. render: shell validations: required: true diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 547b59f3..16d33af9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,9 +2,9 @@ name: Format and lint on: push: - branches: [ "rewrite" ] + branches: ["rewrite"] pull_request: - branches: [ "rewrite" ] + branches: ["rewrite"] permissions: checks: write diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 86e99b35..c375f431 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -26,7 +26,7 @@ jobs: - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }} diff --git a/shell.nix b/shell.nix index 91a75750..ea4ddfbf 100644 --- a/shell.nix +++ b/shell.nix @@ -32,9 +32,11 @@ pkgs.mkShell rec { xidel = pkgs.lib.getExe pkgs.xidel; in pkgs.lib.optionalString isIdea '' - sed -i \ - -e "s|$(${xidel} .idea/workspace.xml -e '${pathFor "explicitPathToStdlib"}')|${RUST_SRC_PATH}|" \ - -e "s|$(${xidel} .idea/workspace.xml -e '${pathFor "toolchainHomeDirectory"}')|${toolchain.toolchain}/bin|" \ - .idea/workspace.xml + if [ -f .idea/workspace.xml ]; then + sed -i \ + -e "s|$(${xidel} .idea/workspace.xml -e '${pathFor "explicitPathToStdlib"}')|${RUST_SRC_PATH}|" \ + -e "s|$(${xidel} .idea/workspace.xml -e '${pathFor "toolchainHomeDirectory"}')|${toolchain.toolchain}/bin|" \ + .idea/workspace.xml + fi ''; } diff --git a/winapps/src/backend/manual.rs b/winapps/src/backend/manual.rs index c524622b..b96b6843 100644 --- a/winapps/src/backend/manual.rs +++ b/winapps/src/backend/manual.rs @@ -20,7 +20,11 @@ impl Backend for Manual { fn check_depends(&self) -> Result<()> { let config = self.config.read(); - assert!(config.manual.enable); + ensure!( + config.manual.enable, + Error::Config("Manual backend is not enabled") + ); + ensure!( !config.manual.host.is_empty(), Error::Config("Host shouldn't be empty") diff --git a/winapps/src/backend/mod.rs b/winapps/src/backend/mod.rs index 34787f51..18dfc14a 100644 --- a/winapps/src/backend/mod.rs +++ b/winapps/src/backend/mod.rs @@ -52,7 +52,7 @@ impl Config { .wait_with_output()? .lines() .filter_map(|line| { - let mut split = line.split(";"); + let mut split = line.split(";").map(|part| part.trim()); match (split.next(), split.next(), split.next(), split.next()) { (Some(id), Some(name), Some(path), Some(icon)) => Some(App { diff --git a/winapps/src/config/apps.rs b/winapps/src/config/apps.rs index e0fbde42..fa89c3fd 100644 --- a/winapps/src/config/apps.rs +++ b/winapps/src/config/apps.rs @@ -1,14 +1,10 @@ use crate::{ config::{App, AppKind}, dirs::{desktop_dir, icons_dir}, - Config, Result, + ensure, Config, Error, Result, }; use base64::{prelude::BASE64_STANDARD, Engine}; -use std::{ - fmt::Display, - fs::{set_permissions, write, Permissions}, - os::unix::fs::PermissionsExt, -}; +use std::{fmt::Display, fs::write}; use tracing::debug; impl PartialEq for App { @@ -25,7 +21,14 @@ impl Display for App { impl App { fn try_as_existing(&mut self) -> Result<&mut Self> { - match self.kind.clone() { + ensure!( + self.id + .chars() + .all(|c| c.is_alphanumeric() || c == '-' || c == '_'), + Error::Message(format!("Invalid app ID: {}", self.id)) + ); + + match &self.kind { AppKind::FromBase64(base64) => { let path = icons_dir()?.join(format!("{}.png", self.id)); write(path.clone(), BASE64_STANDARD.decode(base64)?)?; @@ -69,7 +72,6 @@ Comment={} (WinApps)", let path = desktop_dir()?.join(format!("{}.desktop", self.id)); write(&path, self.try_as_desktop_file()?)?; - set_permissions(&path, Permissions::from_mode(0o750))?; if !config.linked_apps.contains(&self) { debug!("Writing app {} to config", self.id); diff --git a/winapps/src/config/operations.rs b/winapps/src/config/operations.rs index 7876ef3f..3996aa5b 100644 --- a/winapps/src/config/operations.rs +++ b/winapps/src/config/operations.rs @@ -7,7 +7,7 @@ use std::{ }; use tracing::warn; -use crate::{bail, dirs::config_dir, Config, Error, IntoResult, Result}; +use crate::{dirs::config_dir, ensure, Config, Error, IntoResult, Result}; static CONFIG: OnceLock> = OnceLock::new(); @@ -38,15 +38,18 @@ impl Config { let config_file = fs::read_to_string(config_path).into_result()?; let config: Self = toml::from_str(config_file.as_str()).into_result()?; - if !(config.libvirt.enable ^ config.container.enable ^ config.manual.enable) { - bail!(Error::Config("More than one backend enabled, please set only one of libvirt.enable, container.enable, and manual.enable")); - } + ensure!( + [config.libvirt.enable, config.container.enable, config.manual.enable] + .into_iter() + .filter(|enabled| *enabled) + .count() == 1, + Error::Config("More than one backend enabled, please set only one of libvirt.enable, container.enable, and manual.enable") + ); - if config.manual.enable && IpAddr::from_str(&config.manual.host).is_err() { - bail!(Error::Config( - "Please set manual.host to a valid IP address" - )); - } + ensure!( + config.manual.enable && IpAddr::from_str(&config.manual.host).is_err(), + Error::Config("Please set manual.host to a valid IP address") + ); Ok(config) } diff --git a/winapps/src/dirs.rs b/winapps/src/dirs.rs index e5ad29aa..d45fb1fb 100644 --- a/winapps/src/dirs.rs +++ b/winapps/src/dirs.rs @@ -30,7 +30,7 @@ pub fn data_dir() -> Result { Ok(data_dir) } -/// Get the data dir and validates it exists +/// Get the config dir and validates it exists pub fn config_dir() -> Result { let Some(config_dir) = dirs::config_dir().map(|path| path.join("winapps")) else { bail!("Could not determine $XDG_CONFIG_HOME") diff --git a/winapps/src/remote_client/freerdp.rs b/winapps/src/remote_client/freerdp.rs index 496780d2..2d1f3f70 100644 --- a/winapps/src/remote_client/freerdp.rs +++ b/winapps/src/remote_client/freerdp.rs @@ -69,9 +69,11 @@ impl RemoteClient for Freerdp { let Some(home_regex) = dirs::home_dir().map(|home| { Regex::new(&format!( "^{}", - home.into_os_string() - .into_string() - .expect("$HOME should always be a valid string") + regex::escape( + home.as_os_str() + .to_str() + .expect("$HOME should always be valid UTF-8") + ) )) .expect("'^$HOME' should always be a valid regex") }) else { From a33c5e38e500b5cf4fc32bd3a4fc97e634948a7e Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Sun, 12 Oct 2025 17:29:28 +0200 Subject: [PATCH 11/12] chore: address more comments Signed-off-by: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> --- winapps-image/oem/ExtractPrograms.ps1 | 9 +++++---- winapps/src/backend/manual.rs | 6 ++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/winapps-image/oem/ExtractPrograms.ps1 b/winapps-image/oem/ExtractPrograms.ps1 index caa003e3..46ede850 100644 --- a/winapps-image/oem/ExtractPrograms.ps1 +++ b/winapps-image/oem/ExtractPrograms.ps1 @@ -137,7 +137,8 @@ function GetApplicationName function GetUWPApplicationName { param ( - [string]$exePath + [string]$exePath, + $app ) # Query the application executable for the application name. @@ -257,7 +258,7 @@ function AppSearchUWP # Proceed only if an executable path was identified. if ($exePath) { - $productName = GetUWPApplicationName -exePath $exePath + $productName = GetUWPApplicationName -exePath $exePath -app $app # Ignore UWP applications with no name, or those named 'Microsoft® Windows® Operating System'. if ($productName -ne "Microsoft® Windows® Operating System" -and [string]::IsNullOrEmpty($productName) -eq $false) @@ -273,7 +274,7 @@ function AppSearchUWP PrintCSV -Names $exeNames -Paths $exePaths -Source "uwp" } -# Name: 'AppSearchWinReg' +# Name: 'AppSearchChocolatey' # Role: Search for chocolatey shims. function AppSearchChocolatey { @@ -309,7 +310,7 @@ function AppSearchChocolatey } } -# Name: 'AppSearchWinReg' +# Name: 'AppSearchScoop' # Role: Search for scoop shims. function AppSearchScoop { diff --git a/winapps/src/backend/manual.rs b/winapps/src/backend/manual.rs index b96b6843..742c34aa 100644 --- a/winapps/src/backend/manual.rs +++ b/winapps/src/backend/manual.rs @@ -41,9 +41,7 @@ impl Backend for Manual { fn get_host(&self) -> IpAddr { let config = self.config.read(); - // SAFETY: When the config is read, we check that this is a valid IP - // We assume that the program will never write this field, - // so it should always be valid at this point - IpAddr::from_str(&config.manual.host).unwrap() + IpAddr::from_str(&config.manual.host) + .expect("Manual host should be validated in check_depends") } } From c0e6538d8151ca2cb0c8832a8e365c465844ecc2 Mon Sep 17 00:00:00 2001 From: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> Date: Sun, 12 Oct 2025 19:35:58 +0200 Subject: [PATCH 12/12] feat: upgrade packages & switch to 2024 Signed-off-by: Oskar Manhart <52569953+oskardotglobal@users.noreply.github.com> --- Cargo.lock | 252 ++++++++++++++------------- Cargo.toml | 2 +- winapps-cli/Cargo.toml | 4 +- winapps-cli/src/main.rs | 4 +- winapps/Cargo.toml | 4 +- winapps/src/backend/container.rs | 2 +- winapps/src/backend/manual.rs | 2 +- winapps/src/backend/mod.rs | 2 +- winapps/src/command.rs | 2 +- winapps/src/config/apps.rs | 5 +- winapps/src/config/operations.rs | 19 +- winapps/src/dirs.rs | 2 +- winapps/src/errors.rs | 10 +- winapps/src/lib.rs | 4 +- winapps/src/remote_client/freerdp.rs | 2 +- 15 files changed, 164 insertions(+), 152 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0cbc1a4..4aa0a492 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -78,15 +78,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -94,7 +94,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -126,18 +126,18 @@ checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "clap" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstream", "anstyle", @@ -243,7 +243,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -281,12 +281,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -311,21 +311,21 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "indexmap" -version = "2.11.0" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown", @@ -342,7 +342,7 @@ dependencies = [ "dyn-clone", "fuzzy-matcher", "unicode-segmentation", - "unicode-width 0.2.1", + "unicode-width 0.2.2", ] [[package]] @@ -365,15 +365,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags", "libc", @@ -381,9 +381,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litrs" @@ -417,9 +417,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "miette" @@ -474,18 +474,18 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.50.1" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -510,9 +510,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "owo-colors" -version = "4.2.2" +version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" [[package]] name = "parking_lot" @@ -534,7 +534,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -554,18 +554,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] @@ -583,9 +583,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.2" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433" dependencies = [ "aho-corasick", "memchr", @@ -595,9 +595,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6" dependencies = [ "aho-corasick", "memchr", @@ -606,9 +606,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298" [[package]] name = "rustc-demangle" @@ -618,15 +618,15 @@ checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -637,18 +637,28 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -657,11 +667,11 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -764,23 +774,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ "unicode-linebreak", - "unicode-width 0.2.1", + "unicode-width 0.2.2", ] [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -798,12 +808,12 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.5" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ "indexmap", - "serde", + "serde_core", "serde_spanned", "toml_datetime", "toml_parser", @@ -813,27 +823,27 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" [[package]] name = "tracing" @@ -898,9 +908,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-linebreak" @@ -922,9 +932,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "utf8parse" @@ -998,41 +1008,35 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.53.5", ] [[package]] name = "windows-sys" -version = "0.60.2" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets 0.53.3", + "windows-link", ] [[package]] @@ -1053,19 +1057,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -1076,9 +1080,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -1088,9 +1092,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -1100,9 +1104,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -1112,9 +1116,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -1124,9 +1128,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -1136,9 +1140,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -1148,9 +1152,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -1160,9 +1164,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" diff --git a/Cargo.toml b/Cargo.toml index bb0f2b17..d3f93b3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,4 @@ members = [ "winapps", "winapps-cli", ] -resolver = "2" +resolver = "3" diff --git a/winapps-cli/Cargo.toml b/winapps-cli/Cargo.toml index 506cdcdd..590e048b 100644 --- a/winapps-cli/Cargo.toml +++ b/winapps-cli/Cargo.toml @@ -1,11 +1,9 @@ [package] name = "winapps-cli" version = "0.1.0" -edition = "2021" +edition = "2024" default-run = "winapps-cli" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] clap = "4.3" inquire = "0.9" diff --git a/winapps-cli/src/main.rs b/winapps-cli/src/main.rs index 8837f56c..b1bbb2f8 100644 --- a/winapps-cli/src/main.rs +++ b/winapps-cli/src/main.rs @@ -1,6 +1,6 @@ -use clap::{arg, Command}; +use clap::{Command, arg}; use miette::{IntoDiagnostic, Result}; -use tracing::{info, Level}; +use tracing::{Level, info}; use tracing_subscriber::EnvFilter; use winapps::{Backend, Config, Freerdp, RemoteClient}; diff --git a/winapps/Cargo.toml b/winapps/Cargo.toml index 6469abf1..cb380a27 100644 --- a/winapps/Cargo.toml +++ b/winapps/Cargo.toml @@ -1,9 +1,7 @@ [package] name = "winapps" version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +edition = "2024" [dependencies] anyhow = "1.0" diff --git a/winapps/src/backend/container.rs b/winapps/src/backend/container.rs index 3b2947fd..bb5a6d5e 100644 --- a/winapps/src/backend/container.rs +++ b/winapps/src/backend/container.rs @@ -1,4 +1,4 @@ -use crate::{command::command, ensure, Backend, Config, Error, Result}; +use crate::{Backend, Config, Error, Result, command::command, ensure}; use parking_lot::RwLock; use std::net::{IpAddr, Ipv4Addr}; use tracing::debug; diff --git a/winapps/src/backend/manual.rs b/winapps/src/backend/manual.rs index 742c34aa..f870897b 100644 --- a/winapps/src/backend/manual.rs +++ b/winapps/src/backend/manual.rs @@ -1,7 +1,7 @@ use parking_lot::RwLock; use std::{net::IpAddr, str::FromStr}; -use crate::{ensure, Backend, Config, Error, Result}; +use crate::{Backend, Config, Error, Result, ensure}; #[derive(Debug, Clone)] pub struct Manual { diff --git a/winapps/src/backend/mod.rs b/winapps/src/backend/mod.rs index 18dfc14a..803d8096 100644 --- a/winapps/src/backend/mod.rs +++ b/winapps/src/backend/mod.rs @@ -3,10 +3,10 @@ use std::net::IpAddr; use enum_dispatch::enum_dispatch; use crate::{ + Config, Result, backend::{container::Container, manual::Manual}, command::Command, config::{App, AppKind}, - Config, Result, }; mod container; diff --git a/winapps/src/command.rs b/winapps/src/command.rs index f37c10ad..c38fa5cc 100644 --- a/winapps/src/command.rs +++ b/winapps/src/command.rs @@ -1,4 +1,4 @@ -use crate::{ensure, Backend, Config, Error, IntoResult, Result}; +use crate::{Backend, Config, Error, IntoResult, Result, ensure}; use std::{ fmt::{Display, Formatter}, process::{Child, Command as StdCommand, Stdio}, diff --git a/winapps/src/config/apps.rs b/winapps/src/config/apps.rs index fa89c3fd..28b44f4f 100644 --- a/winapps/src/config/apps.rs +++ b/winapps/src/config/apps.rs @@ -1,9 +1,10 @@ use crate::{ + Config, Error, Result, config::{App, AppKind}, dirs::{desktop_dir, icons_dir}, - ensure, Config, Error, Result, + ensure, }; -use base64::{prelude::BASE64_STANDARD, Engine}; +use base64::{Engine, prelude::BASE64_STANDARD}; use std::{fmt::Display, fs::write}; use tracing::debug; diff --git a/winapps/src/config/operations.rs b/winapps/src/config/operations.rs index 3996aa5b..3f899f4a 100644 --- a/winapps/src/config/operations.rs +++ b/winapps/src/config/operations.rs @@ -7,7 +7,7 @@ use std::{ }; use tracing::warn; -use crate::{dirs::config_dir, ensure, Config, Error, IntoResult, Result}; +use crate::{Config, Error, IntoResult, Result, dirs::config_dir, ensure}; static CONFIG: OnceLock> = OnceLock::new(); @@ -39,11 +39,18 @@ impl Config { let config: Self = toml::from_str(config_file.as_str()).into_result()?; ensure!( - [config.libvirt.enable, config.container.enable, config.manual.enable] - .into_iter() - .filter(|enabled| *enabled) - .count() == 1, - Error::Config("More than one backend enabled, please set only one of libvirt.enable, container.enable, and manual.enable") + [ + config.libvirt.enable, + config.container.enable, + config.manual.enable + ] + .into_iter() + .filter(|enabled| *enabled) + .count() + == 1, + Error::Config( + "More than one backend enabled, please set only one of libvirt.enable, container.enable, and manual.enable" + ) ); ensure!( diff --git a/winapps/src/dirs.rs b/winapps/src/dirs.rs index d45fb1fb..b5bf43cd 100644 --- a/winapps/src/dirs.rs +++ b/winapps/src/dirs.rs @@ -1,4 +1,4 @@ -use crate::{bail, Result}; +use crate::{Result, bail}; use std::{ fs, path::{Path, PathBuf}, diff --git a/winapps/src/errors.rs b/winapps/src/errors.rs index 1aa59e8c..5dfbeaa9 100644 --- a/winapps/src/errors.rs +++ b/winapps/src/errors.rs @@ -47,15 +47,19 @@ pub enum Error { #[error("Icon is invalid base64")] #[diagnostic( code(winapps::setup_error), - help("Setup returned a badly formed base64 string, is your config correct and are apps correctly installed?") + help( + "Setup returned a badly formed base64 string, is your config correct and are apps correctly installed?" + ) )] InvalidIcon(#[from] base64::DecodeError), #[error("RDP host is unreachable")] #[diagnostic( code(winapps::bad_vm_state), - help("Ensure that the VM or your Firewall doesn't block ping traffic. \ - In case you're running a containerized VM, ensure the container runtime is properly configured.") + help( + "Ensure that the VM or your Firewall doesn't block ping traffic. \ + In case you're running a containerized VM, ensure the container runtime is properly configured." + ) )] HostUnreachable, diff --git a/winapps/src/lib.rs b/winapps/src/lib.rs index 2ec205ea..413a7c62 100644 --- a/winapps/src/lib.rs +++ b/winapps/src/lib.rs @@ -6,8 +6,8 @@ pub use crate::{ backend::{Backend, Backends}, config::Config, - errors::{bail, ensure, Error, IntoResult, Result}, - remote_client::{freerdp::Freerdp, RemoteClient}, + errors::{Error, IntoResult, Result, bail, ensure}, + remote_client::{RemoteClient, freerdp::Freerdp}, }; mod backend; diff --git a/winapps/src/remote_client/freerdp.rs b/winapps/src/remote_client/freerdp.rs index 2d1f3f70..783cf800 100644 --- a/winapps/src/remote_client/freerdp.rs +++ b/winapps/src/remote_client/freerdp.rs @@ -1,4 +1,4 @@ -use crate::{bail, command::Command, Config, Error, RemoteClient, Result}; +use crate::{Config, Error, RemoteClient, Result, bail, command::Command}; use parking_lot::RwLock; use regex::Regex; use std::{