diff --git a/.envrc.example b/.envrc.example new file mode 100644 index 00000000..7c8f85bc --- /dev/null +++ b/.envrc.example @@ -0,0 +1,7 @@ +# 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/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..f2263141 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]. +. 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..cdd98b71 --- /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 +- 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. + +## 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..07d551a6 --- /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 sensitive 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/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..16d33af9 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,25 @@ +name: Format and lint + +on: + push: + branches: ["rewrite"] + pull_request: + branches: ["rewrite"] + +permissions: + 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..c375f431 --- /dev/null +++ b/.github/workflows/docker.yaml @@ -0,0 +1,50 @@ +name: Build container image + +on: + push: + branches: [ "rewrite" ] + paths: [ "winapps-image/**" ] + workflow_dispatch: + 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@v5 + 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: winapps-image/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 deleted file mode 100644 index 9d4688d1..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-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. - -## 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/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..4aa0a492 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1175 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +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.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[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.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[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.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +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 = "convert_case" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[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 = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "convert_case", + "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.61.2", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[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.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + +[[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.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "indexmap" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inquire" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2628910d0114e9139056161d8644a2026be7b117f8498943f9437748b04c9e0a" +dependencies = [ + "bitflags", + "crossterm", + "dyn-clone", + "fuzzy-matcher", + "unicode-segmentation", + "unicode-width 0.2.2", +] + +[[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.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litrs" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[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 = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.59.0", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[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.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[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.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d8d02cacdb176ef4678de6c052efb4b3da14b78e4db683a4252762be5433" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "722166aa0d7438abbaa4d5cc2c649dac844e8c56d82fb3d33e9c34b5cd268fc6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3160422bbd54dd5ecfdca71e5fd59b7b8fe2b1697ab2baf64f6d05dcc66d298" + +[[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.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +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.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + +[[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 = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[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.2", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +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 = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + +[[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", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[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 = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winapps" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64", + "derive-new", + "dirs", + "enum_dispatch", + "miette", + "parking_lot", + "regex", + "serde", + "thiserror", + "toml", + "tracing", +] + +[[package]] +name = "winapps-cli" +version = "0.1.0" +dependencies = [ + "clap", + "inquire", + "miette", + "tracing", + "tracing-subscriber", + "winapps", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +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.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "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]] +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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[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..d3f93b3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,5 @@ members = [ "winapps", "winapps-cli", - "winapps-gui", ] -resolver = "2" +resolver = "3" 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 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..ea4ddfbf --- /dev/null +++ b/shell.nix @@ -0,0 +1,42 @@ +{ + nixpkgs ? fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-25.05.tar.gz", + 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 + pre-commit + + freerdp + sshpass + + openssl + pkg-config + toolchain.toolchain + ]; + + RUST_BACKTRACE = 1; + RUST_SRC_PATH = "${toolchain.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 '' + 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-cli/Cargo.toml b/winapps-cli/Cargo.toml index 6837cf7b..590e048b 100644 --- a/winapps-cli/Cargo.toml +++ b/winapps-cli/Cargo.toml @@ -1,11 +1,13 @@ [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.11" +clap = "4.3" +inquire = "0.9" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["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..b1bbb2f8 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 clap::{Command, arg}; +use miette::{IntoDiagnostic, Result}; +use tracing::{Level, info}; +use tracing_subscriber::EnvFilter; +use winapps::{Backend, Config, Freerdp, RemoteClient}; fn cli() -> Command { Command::new("winapps-cli") @@ -9,83 +10,89 @@ 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")), - ) - .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")), + .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), + ), ) } -fn main() { +fn main() -> Result<()> { + tracing_subscriber::fmt() + .without_time() + .with_target(false) + .with_level(true) + .with_max_level(Level::INFO) + .with_env_filter(EnvFilter::from_default_env()) + .init(); + + let config_lock = Config::try_get_lock()?; + config_lock.read().get_backend().check_depends()?; + let cli = cli(); - let matches = cli.clone().get_matches(); - let client: &dyn RemoteClient = &Freerdp {}; - let config = winapps::load_config(None); + match cli.clone().get_matches().subcommand() { + Some(("setup", _)) => { + info!("Running setup"); - match matches.subcommand() { - Some(("check", _)) => { - println!("Checking remote connection"); + let apps = config_lock.read().get_available_apps()?; - client.check_depends(config); + // 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(), + source: e.into(), + })? { + Some(apps) => apps + .into_iter() + .try_for_each(|app| app.link(&mut config_lock.write()))?, + None => info!("No apps selected, skipping setup..."), + }; + + Ok(()) } + Some(("connect", _)) => { - println!("Connecting to remote"); + info!("Connecting to remote"); + + let client = Freerdp::new(); - client.run_app(config, None); + client.check_depends()?; + client.run_full_session()?; + Ok(()) } + Some(("run", sub_matches)) => { - println!("Connecting to app on remote"); + info!("Connecting to app on remote"); - client.run_app(config, sub_matches.get_one::("APP")); - } + let client = Freerdp::new(); - Some(("vm", command)) => { - match command.subcommand() { - Some(("create", _)) => { - println!("Creating windows 10 vm.."); - create_vm(config); - } - Some(("start", _)) => { - println!("Starting vm.."); - start_vm(config); - } - - Some(("kill", _)) => { - println!("Killing vm.."); - kill_vm(config); - } - - Some((_, _)) => { - unwrap_or_panic!( - cli.about("Command not found, try existing ones!") - .print_help(), - "Couldn't print help" - ); - } - _ => unreachable!(), - }; - } + client.check_depends()?; - Some((_, _)) => { - unwrap_or_panic!( - cli.about("Command not found, try existing ones!") - .print_help(), - "Couldn't print help" - ); + let args = sub_matches + .get_many::("ARGS") + .map_or(Vec::new(), |args| args.map(|v| v.to_owned()).collect()); + + 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), + }?; + + Ok(()) } - _ => 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..46ede850 --- /dev/null +++ b/winapps-image/oem/ExtractPrograms.ps1 @@ -0,0 +1,378 @@ +### 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, + $app + ) + + # 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 -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) + { + # 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: 'AppSearchChocolatey' +# 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: 'AppSearchScoop' +# 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..f96e93ae --- /dev/null +++ b/winapps-image/oem/install.ps1 @@ -0,0 +1,58 @@ +$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. $_" + } +} + +# 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..cb380a27 100644 --- a/winapps/Cargo.toml +++ b/winapps/Cargo.toml @@ -1,15 +1,18 @@ [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.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 = [] } +parking_lot = "0.12.5" diff --git a/winapps/src/backend/container.rs b/winapps/src/backend/container.rs new file mode 100644 index 00000000..bb5a6d5e --- /dev/null +++ b/winapps/src/backend/container.rs @@ -0,0 +1,56 @@ +use crate::{Backend, Config, Error, Result, command::command, ensure}; +use parking_lot::RwLock; +use std::net::{IpAddr, Ipv4Addr}; +use tracing::debug; + +#[derive(Debug, Clone)] +pub struct Container { + config: &'static RwLock, +} + +impl Container { + const STATE_RUNNING: &'static str = "running"; + + const DEFAULT_COMMAND: &'static str = "docker"; + const PODMAN_COMMAND: &'static str = "podman"; + + pub(crate) fn new() -> Self { + Self { + config: Config::get_lock(), + } + } +} + +impl Backend for Container { + fn check_depends(&self) -> Result<()> { + let config = self.config.read(); + assert!(config.container.enable); + + ensure!( + !config.container.container_name.is_empty(), + Error::Config("Container name shouldn't be empty") + ); + + let command = if config.container.enable_podman { + Self::PODMAN_COMMAND + } else { + Self::DEFAULT_COMMAND + }; + + let state = command!( + r#"{command} ps --all --filter name={} --format {{{{.State}}}}"#, + 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..f870897b --- /dev/null +++ b/winapps/src/backend/manual.rs @@ -0,0 +1,47 @@ +use parking_lot::RwLock; +use std::{net::IpAddr, str::FromStr}; + +use crate::{Backend, Config, Error, Result, ensure}; + +#[derive(Debug, Clone)] +pub struct Manual { + config: &'static RwLock, +} + +impl Manual { + pub(crate) fn new() -> Self { + Self { + config: Config::get_lock(), + } + } +} + +impl Backend for Manual { + fn check_depends(&self) -> Result<()> { + let config = self.config.read(); + + ensure!( + config.manual.enable, + Error::Config("Manual backend is not enabled") + ); + + ensure!( + !config.manual.host.is_empty(), + Error::Config("Host shouldn't be empty") + ); + + ensure!( + IpAddr::from_str(&config.manual.host).is_ok(), + Error::Config("manual.host is not a valid IP address") + ); + + Ok(()) + } + + fn get_host(&self) -> IpAddr { + let config = self.config.read(); + + IpAddr::from_str(&config.manual.host) + .expect("Manual host should be validated in check_depends") + } +} diff --git a/winapps/src/backend/mod.rs b/winapps/src/backend/mod.rs new file mode 100644 index 00000000..803d8096 --- /dev/null +++ b/winapps/src/backend/mod.rs @@ -0,0 +1,71 @@ +use std::net::IpAddr; + +use enum_dispatch::enum_dispatch; + +use crate::{ + Config, Result, + backend::{container::Container, manual::Manual}, + command::Command, + config::{App, AppKind}, +}; + +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(&self) -> &Backends { + self.backend.get_or_init(|| { + match ( + self.libvirt.enable, + self.container.enable, + self.manual.enable, + ) { + (true, _, _) => todo!(), + (_, true, _) => Container::new().into(), + (_, _, true) => Manual::new().into(), + _ => unreachable!(), + } + }) + } + + pub fn get_host(&self) -> IpAddr { + self.get_backend().get_host() + } + + pub fn get_available_apps(&self) -> Result> { + let apps = Command::new("C:\\ExtractPrograms.ps1") + .into_remote(self) + .wait_with_output()? + .lines() + .filter_map(|line| { + 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 { + id: id.to_string(), + name: name.to_string(), + win_exec: path.to_string(), + kind: AppKind::FromBase64(icon.to_string()), + }), + _ => None, + } + }) + .collect::>(); + + Ok(apps) + } +} diff --git a/winapps/src/command.rs b/winapps/src/command.rs new file mode 100644 index 00000000..c38fa5cc --- /dev/null +++ b/winapps/src/command.rs @@ -0,0 +1,164 @@ +use crate::{Backend, Config, Error, IntoResult, Result, ensure}; +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: T) -> Self { + Self { + exec: exec.into(), + args: Vec::new(), + error_message: String::from("Error running child command"), + loud: false, + } + } + + pub fn into_remote(mut self, config: &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() + ), + "-oStrictHostKeyChecking=accept-new", + "-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..28b44f4f --- /dev/null +++ b/winapps/src/config/apps.rs @@ -0,0 +1,92 @@ +use crate::{ + Config, Error, Result, + config::{App, AppKind}, + dirs::{desktop_dir, icons_dir}, + ensure, +}; +use base64::{Engine, prelude::BASE64_STANDARD}; +use std::{fmt::Display, fs::write}; +use tracing::debug; + +impl PartialEq for App { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +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 { + fn try_as_existing(&mut self) -> Result<&mut Self> { + 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)?)?; + + self.kind = AppKind::Existing; + + Ok(self) + } + AppKind::Existing => Ok(self), + } + } + + fn try_as_desktop_file(&mut self) -> Result { + debug!("Writing desktop icon for {}", self.id); + + match &self.kind { + AppKind::FromBase64(_) => self.try_as_existing()?.try_as_desktop_file(), + AppKind::Existing => Ok(format!( + "[Desktop Entry] +Name={} +Exec=winapps run {} +Terminal=false +Type=Application +Icon={} +StartupWMClass={} +Comment={} (WinApps)", + self.name, + self.id, + icons_dir()? + .join(format!("{}.png", self.id)) + .to_string_lossy(), + self.id, + self.name + )), + } + } + + pub fn link(mut self, config: &mut Config) -> Result<()> { + self.try_as_existing()?; + + let path = desktop_dir()?.join(format!("{}.desktop", self.id)); + + write(&path, self.try_as_desktop_file()?)?; + + if !config.linked_apps.contains(&self) { + debug!("Writing app {} to config", self.id); + + 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..755828cb --- /dev/null +++ b/winapps/src/config/mod.rs @@ -0,0 +1,96 @@ +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, Default, Deserialize, Serialize, Clone)] +pub enum AppKind { + FromBase64(String), + #[default] + Existing, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +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 new file mode 100644 index 00000000..3f899f4a --- /dev/null +++ b/winapps/src/config/operations.rs @@ -0,0 +1,71 @@ +use parking_lot::RwLock; +use std::{ + fs::{self, write}, + net::IpAddr, + str::FromStr, + sync::OnceLock, +}; +use tracing::warn; + +use crate::{Config, Error, IntoResult, Result, dirs::config_dir, ensure}; + +static CONFIG: OnceLock> = OnceLock::new(); + +impl Config { + /// 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()?))) + } + + /// 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") + } + + /// Reads the config from disk. + fn try_new() -> Result { + let config = Self::new(); + let config_path = config_dir()?.join("config.toml"); + + 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()?; + + 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" + ) + ); + + 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) + } + + pub fn save(&self) -> Result<()> { + 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 new file mode 100644 index 00000000..b5bf43cd --- /dev/null +++ b/winapps/src/dirs.rs @@ -0,0 +1,61 @@ +use crate::{Result, bail}; +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 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") + }; + + 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 { + 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..5dfbeaa9 100644 --- a/winapps/src/errors.rs +++ b/winapps/src/errors.rs @@ -1,158 +1,128 @@ -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..413a7c62 100644 --- a/winapps/src/lib.rs +++ b/winapps/src/lib.rs @@ -1,162 +1,19 @@ -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, +#![allow(clippy::new_without_default)] +#![feature(decl_macro)] +#![feature(exit_status_error)] +#![feature(once_cell_try)] + +pub use crate::{ + backend::{Backend, Backends}, + config::Config, + errors::{Error, IntoResult, Result, bail, ensure}, + remote_client::{RemoteClient, freerdp::Freerdp}, }; -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..783cf800 --- /dev/null +++ b/winapps/src/remote_client/freerdp.rs @@ -0,0 +1,105 @@ +use crate::{Config, Error, RemoteClient, Result, bail, command::Command}; +use parking_lot::RwLock; +use regex::Regex; +use std::{ + net::{SocketAddr, TcpStream}, + time::Duration, +}; +use tracing::info; + +pub struct Freerdp { + config: &'static RwLock, +} + +impl Freerdp { + const TIMEOUT: Duration = Duration::from_secs(5); + const RDP_PORT: u16 = 3389; + + fn get_command(&self) -> Command { + let config = self.config.read(); + + Command::new(config.freerdp.executable.to_owned()) + .with_err("Freerdp execution failed, check logs above!") + .args(vec![ + format!("/d:{}", &config.auth.domain), + format!("/u:{}", &config.auth.username), + format!("/p:{}", &config.auth.password), + format!("/v:{}", &config.get_host()), + ]) + .args(config.freerdp.extra_args.iter().cloned()) + .loud(config.debug) + } + + pub fn new() -> Self { + Self { + config: Config::get_lock(), + } + } +} + +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.read().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 + .read() + .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!( + "^{}", + 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 { + bail!("Couldn't find $HOME") + }; + + self.get_command() + .arg(format!("/app:program:{path},hidef:on")) + .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<()>; +}