Repository: winapps-org/winapps Branch: main Commit: d5ea5d5a0b8e Files: 119 Total size: 346.0 KB Directory structure: gitextract_m7fmf0qs/ ├── .gitattributes ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE/ │ │ ├── 1-bug.yml │ │ └── config.yml │ └── cla-signatures.csv ├── .gitignore ├── .pre-commit-config.yaml ├── COPYRIGHT.md ├── LICENSE.md ├── README.md ├── apps/ │ ├── ChemDraw/ │ │ └── info │ ├── access/ │ │ └── info │ ├── access-o365/ │ │ └── info │ ├── access-o365-x86/ │ │ └── info │ ├── access-x86/ │ │ └── info │ ├── acrobat-dc/ │ │ └── info │ ├── acrobat-x-pro/ │ │ └── info │ ├── acrobat9/ │ │ └── info │ ├── adobe-cc/ │ │ └── info │ ├── adobe-digital-editions-4.5/ │ │ └── info │ ├── afdesign/ │ │ └── info │ ├── affinity/ │ │ └── info │ ├── afphoto/ │ │ └── info │ ├── afpub/ │ │ └── info │ ├── aftereffects-2024/ │ │ └── info │ ├── aftereffects-cc/ │ │ └── info │ ├── audition-2024/ │ │ └── info │ ├── audition-cc/ │ │ └── info │ ├── bridge-cc/ │ │ └── info │ ├── bridge-cs6/ │ │ └── info │ ├── bridge-cs6-x86/ │ │ └── info │ ├── cmd/ │ │ └── info │ ├── dorico-6/ │ │ └── info │ ├── dymo-connect/ │ │ └── info │ ├── emclient/ │ │ └── info │ ├── excel/ │ │ └── info │ ├── excel-o365/ │ │ └── info │ ├── excel-o365-x86/ │ │ └── info │ ├── excel-x86/ │ │ └── info │ ├── excel-x86-2010/ │ │ └── info │ ├── explorer/ │ │ └── info │ ├── fusion-360/ │ │ └── info │ ├── iexplorer/ │ │ └── info │ ├── illustrator-2024/ │ │ └── info │ ├── illustrator-cc/ │ │ └── info │ ├── indesign-2024/ │ │ └── info │ ├── indesign-cc/ │ │ └── info │ ├── lightroom-cc/ │ │ └── info │ ├── lightroom-classic/ │ │ └── info │ ├── linqpad8/ │ │ └── info │ ├── mediaencoder-2024/ │ │ └── info │ ├── mirc/ │ │ └── info │ ├── ms-office-protocol-handler.desktop │ ├── mspaint/ │ │ └── info │ ├── onenote/ │ │ └── info │ ├── onenote-o365/ │ │ └── info │ ├── onenote-o365-x86/ │ │ └── info │ ├── onenote-x86/ │ │ └── info │ ├── outlook/ │ │ └── info │ ├── outlook-o365/ │ │ └── info │ ├── outlook-o365-x86/ │ │ └── info │ ├── outlook-x86/ │ │ └── info │ ├── paint.net/ │ │ └── info │ ├── pdfgear/ │ │ └── info │ ├── photoshop-2022/ │ │ └── info │ ├── photoshop-2024/ │ │ └── info │ ├── photoshop-2025/ │ │ └── info │ ├── photoshop-cc/ │ │ └── info │ ├── photoshop-cs6/ │ │ └── info │ ├── photoshop-cs6-x86/ │ │ └── info │ ├── powerbi/ │ │ └── info │ ├── powerbi-store/ │ │ └── info │ ├── powerpoint/ │ │ └── info │ ├── powerpoint-o365/ │ │ └── info │ ├── powerpoint-o365-x86/ │ │ └── info │ ├── powerpoint-x86/ │ │ └── info │ ├── powershell/ │ │ └── info │ ├── powershell-ide/ │ │ └── info │ ├── premierepro-2024/ │ │ └── info │ ├── project/ │ │ └── info │ ├── project-x86/ │ │ └── info │ ├── publisher/ │ │ └── info │ ├── publisher-o365/ │ │ └── info │ ├── publisher-o365-x86/ │ │ └── info │ ├── publisher-x86/ │ │ └── info │ ├── remarkable-desktop/ │ │ └── info │ ├── ssms20/ │ │ └── info │ ├── turbotax-2024/ │ │ └── info │ ├── turbotax-2025/ │ │ └── info │ ├── visio/ │ │ └── info │ ├── visio-x86/ │ │ └── info │ ├── visual-studio-comm/ │ │ └── info │ ├── visual-studio-ent/ │ │ └── info │ ├── visual-studio-pro/ │ │ └── info │ ├── word/ │ │ └── info │ ├── word-o365/ │ │ └── info │ ├── word-o365-x86/ │ │ └── info │ ├── word-x86/ │ │ └── info │ └── word-x86-2010/ │ └── info ├── bin/ │ └── winapps ├── compose.yaml ├── default.nix ├── docs/ │ ├── docker.md │ └── libvirt.md ├── flake.nix ├── install/ │ ├── ExtractPrograms.ps1 │ └── inquirer.sh ├── oem/ │ ├── Container.reg │ ├── NetProfileCleanup.ps1 │ ├── RDPApps.reg │ ├── TimeSync.ps1 │ └── install.bat ├── packages/ │ ├── winapps/ │ │ ├── default.nix │ │ └── setup.patch │ └── winapps-launcher/ │ ├── WinApps-Launcher.patch │ └── default.nix ├── renovate.json └── setup.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Ensure Windows-friendly line endings and encoding for batch and registry files *.bat text eol=crlf *.cmd text eol=crlf *.reg text eol=crlf working-tree-encoding=UTF-16LE-BOM ================================================ FILE: .github/CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at https://matrix.to/#/#winapps:matrix.org. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contribution Guidelines Thank you for contributing to winapps! Before you can contribute, we ask some things of you: - Please follow our Code of Conduct, the Contributor Covenant. You can find a copy in this repository or under https://www.contributor-covenant.org/ - All Contributors have to sign a Developer Certificate of Origin, agreeing to license their contribution under the AGPLv3. Historically, we used to require a CLA because we had to relicense the codebase from ARR to AGPLv3; however, this is being phased out. You can find a copy of the DCO below or under https://developercertificate.org/. - Please follow code conventions enforced by `pre-commit`. To keep down CI usage, please run it locally before committing too. See for installation, then run `pre-commit install` inside the `winapps` repository you cloned. ## About using Artificial Intelligence for pull requests > [!IMPORTANT] > If you are using any kind of AI assistance to contribute to WinApps, it must be disclosed in the pull request. ### AI-generated code When using AI assistance, we expect contributors to understand the code that is produced and be able to answer critical questions about it. It isn't a maintainers job to review a PR so broken that it requires significant rework to be acceptable. In a perfect world, AI assistance would produce equal or higher quality work than any human. That isn't the world we live in today, and in most cases it's generating slop. A good rule of thumb is that if another person can easily tell a pull request is AI-generated, it needs some more work. ### Other kinds of AI assistance Currently, [CodeRabbit](https://coderabbit.ai) is configured to review pull requests *on demand* when `@coderabbitai review` is commented on pull requests. However, we ask of you to not use it for PRs of which you are the authors unless asked to. Additionally, please do not AI-generate descriptions for larger pull requests or reviews by hand. This does not include things like commit messages. ### AI "Art" We do not condone AI-generated "art", including AI-written and AI-produced tutorials, as well as AI-generated icons for contributed applications. Additionally, please do not share these kinds of media on any official WinApps channel. ## Guidelines for pre-defined applications Some pre-defined applications contain a header like: ``` # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary ``` This is for historic reasons, see [LICENSE.md](../LICENSE.md) and [COPYRIGHT.md](../COPYRIGHT.md). When contributing new applications, please *do not* include such a header. ## 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. ================================================ FILE: .github/ISSUE_TEMPLATE/1-bug.yml ================================================ name: Bug Report description: File a bug report. labels: ["triage"] body: - type: markdown attributes: value: | We cannot fix nor support all bugs caused by FreeRDP, especially on Wayland. If you experience visual bugs, please open a discussion instead. - type: textarea id: what-happened attributes: label: What happened? description: Also tell us, what did you expect to happen? placeholder: Tell us what you see! value: "A bug happened!" validations: required: true - type: input id: freerdp attributes: label: Your FreeRDP version and where you got it from placeholder: "FreeRDP 3.10 (Debian Backports)" validations: required: true - type: input id: distro attributes: label: Your Linux distribution and version placeholder: "Debian Trixie" validations: required: true - type: textarea id: config attributes: label: Your `winapps.conf` description: Please copy and paste your `winapps.conf`. Make sure to not include any sensible data. This will be automatically formatted into code, so no need for backticks. render: shell validations: required: true - type: textarea id: logs attributes: label: Logs description: Give the output of WinApps, FreeRDP etc. where / if applicable. render: shell - type: checkboxes id: terms attributes: label: Terms options: - label: I am running the latest version. required: true - label: To the best of my knowledge, this is a bug and not a setup nor a FreeRDP problem. required: true - label: I have checked for duplicate issues. required: true - label: I agree to follow this project's Code of Conduct. required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ 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. ================================================ FILE: .github/cla-signatures.csv ================================================ User Name;Repository Owner;Repository Name;CLA Title;Gist URL;Gist Version;Signed At;Revoked At;Signed for Organization sparky3387;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-08-31T12:31:32.643Z;2025-09-04T11:04:01.000Z;TRUE matheusmelo18;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-08-30T15:57:01.072Z;2025-09-04T11:04:01.000Z;TRUE Libadoxon;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-08-29T09:53:59.385Z;2025-09-04T11:04:01.000Z;TRUE Dreamail;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-08-29T06:43:07.752Z;2025-09-04T11:04:01.000Z;TRUE queenkjuul;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-08-27T16:19:30.937Z;2025-09-04T11:04:01.000Z;TRUE DevZiaus;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-08-25T21:17:17.792Z;2025-09-04T11:04:01.000Z;TRUE nlogozzo;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-08-24T15:25:25.197Z;2025-09-04T11:04:01.000Z;TRUE osalbahr;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-08-24T15:09:01.053Z;2025-09-04T11:04:01.000Z;TRUE 9Morello;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-08-19T12:14:39.707Z;2025-09-04T11:04:01.000Z;TRUE Sunrongguo2008;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-08-06T15:56:33.681Z;2025-09-04T11:04:01.000Z;TRUE wovw;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-07-31T04:14:50.710Z;2025-09-04T11:04:01.000Z;TRUE denisstrizhkin;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-07-07T14:43:28.482Z;2025-09-04T11:04:01.000Z;TRUE joeshachaf;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-06-29T21:17:46.041Z;2025-09-04T11:04:01.000Z;TRUE Aldo-f;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-06-19T19:26:32.522Z;2025-09-04T11:04:01.000Z;TRUE Mr-MyDooM;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-06-10T03:01:36.680Z;2025-09-04T11:04:01.000Z;TRUE Theowulf-dev;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-06-10T00:44:09.838Z;2025-09-04T11:04:01.000Z;TRUE JoAllg;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-06-04T21:04:46.931Z;2025-09-04T11:04:01.000Z;TRUE sears-s;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-05-24T02:56:00.518Z;2025-09-04T11:04:01.000Z;TRUE egvrl;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-05-15T14:33:38.598Z;2025-09-04T11:04:01.000Z;TRUE thefiredragon;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-04-19T19:06:36.941Z;2025-09-04T11:04:01.000Z;TRUE arwarw;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-04-19T16:12:55.873Z;2025-09-04T11:04:01.000Z;TRUE linull24;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-04-11T17:32:09.319Z;2025-09-04T11:04:01.000Z;TRUE kroese;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-04-02T15:26:03.840Z;2025-09-04T11:04:01.000Z;TRUE dasinking;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-03-25T00:54:49.660Z;2025-09-04T11:04:01.000Z;TRUE tstormn3tw0rk;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-03-21T07:08:26.583Z;2025-09-04T11:04:01.000Z;TRUE limemane;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-03-10T16:38:38.276Z;2025-09-04T11:04:01.000Z;TRUE borekon;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-02-24T11:40:24.101Z;2025-09-04T11:04:01.000Z;TRUE whitewolf101;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-02-23T03:29:22.061Z;2025-09-04T11:04:01.000Z;TRUE raffaem;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-02-16T14:28:21.523Z;2025-09-04T11:04:01.000Z;TRUE mbekkomo;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-02-02T11:40:36.462Z;2025-09-04T11:04:01.000Z;TRUE toastedcrumpets;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-01-16T13:53:21.167Z;2025-09-04T11:04:01.000Z;TRUE starbr3aker;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-01-12T17:30:37.473Z;2025-09-04T11:04:01.000Z;TRUE Deluxe-7;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-01-11T01:48:33.638Z;2025-09-04T11:04:01.000Z;TRUE stceum;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2025-01-03T07:14:43.695Z;2025-09-04T11:04:01.000Z;TRUE molostovvs;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-12-13T07:48:02.950Z;2025-09-04T11:04:01.000Z;TRUE mindset-tk;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-11-25T22:30:30.062Z;2025-09-04T11:04:01.000Z;TRUE lunatic-gh;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-11-18T20:39:24.413Z;2025-09-04T11:04:01.000Z;TRUE Username404-59;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-10-08T21:06:39.926Z;2025-09-04T11:04:01.000Z;TRUE CHN-beta;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-10-05T06:12:56.133Z;2025-09-04T11:04:01.000Z;TRUE eylenburg;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-10-02T13:49:45.131Z;2025-09-04T11:04:01.000Z;TRUE tristanRW;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-09-23T14:48:15.158Z;2025-09-04T11:04:01.000Z;TRUE MopigamesYT;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-09-19T14:54:16.014Z;2025-09-04T11:04:01.000Z;TRUE queler;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-09-09T03:33:59.760Z;2025-09-04T11:04:01.000Z;TRUE C0rn3j;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-09-01T22:30:46.600Z;2025-09-04T11:04:01.000Z;TRUE FixeQyt;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-08-30T14:22:17.912Z;2025-09-04T11:04:01.000Z;TRUE RheaBarar;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-08-19T07:36:53.003Z;2025-09-04T11:04:01.000Z;TRUE Coruscant11;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-08-18T12:46:39.393Z;2025-09-04T11:04:01.000Z;TRUE escapefreeg;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-08-11T03:04:17.227Z;2025-09-04T11:04:01.000Z;TRUE itiligent;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-07-26T04:13:53.616Z;2025-09-04T11:04:01.000Z;TRUE gordoncheong;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-07-25T11:14:28.928Z;2025-09-04T11:04:01.000Z;TRUE bkanuka;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-07-08T19:31:49.299Z;2025-09-04T11:04:01.000Z;TRUE Kazevic;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-07-02T06:46:56.451Z;2025-09-04T11:04:01.000Z;TRUE KernelGhost;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-07-01T06:44:06.681Z;2025-09-04T11:04:01.000Z;TRUE MrTumnis;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-06-13T00:24:25.715Z;2025-09-04T11:04:01.000Z;TRUE Alchemi1963;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-05-30T21:48:08.692Z;2025-09-04T11:04:01.000Z;TRUE GreatNovaDragon;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2024-04-20T20:34:17.381Z;2024-09-21T21:48:55.765Z;TRUE Matt-M-3;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2023-12-04T17:31:44.383Z;2025-09-04T11:04:01.000Z;TRUE Hyperspeed1313;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2023-11-10T18:19:32.080Z;2025-09-04T11:04:01.000Z;TRUE notPlancha;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2023-09-04T18:07:58.859Z;2025-09-04T11:04:01.000Z;TRUE freechelmi;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2023-07-16T08:31:50.083Z;2025-09-04T11:04:01.000Z;TRUE LDprg;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2023-07-16T07:08:14.876Z;2025-09-04T11:04:01.000Z;TRUE fbartels;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2023-07-15T19:49:07.019Z;2025-09-04T11:04:01.000Z;TRUE oskardotglobal;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2023-07-15T15:09:49.368Z;2025-09-04T11:04:01.000Z;TRUE Leonardo-DGS;winapps-org;;fcla.md;https://gist.github.com/oskardotglobal/35f0a72eb45fcc7087e535561383dbc5;1f2d08888e405a91582607bf95176a5755363929;2023-07-15T15:07:40.778Z;2025-09-04T11:04:01.000Z;TRUE ================================================ FILE: .gitignore ================================================ /.idea /.vscode /result .DS_Store **/.DS_Store ================================================ FILE: .pre-commit-config.yaml ================================================ exclude: ^(.+)\.patch$ repos: - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.5.5 hooks: - id: chmod args: [ "775" ] files: (\.sh|winapps)$ - id: forbid-crlf exclude: '\.(bat|cmd|reg)$' - id: remove-crlf exclude: '\.(bat|cmd|reg)$' - id: forbid-tabs - id: remove-tabs args: [ --whitespaces-count, "4" ] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: check-added-large-files - id: check-case-conflict - id: check-executables-have-shebangs - id: check-json - id: check-merge-conflict - id: check-shebang-scripts-are-executable - id: check-symlinks - id: check-toml - id: check-vcs-permalinks - id: check-xml - id: check-yaml - id: destroyed-symlinks - id: detect-private-key - id: end-of-file-fixer - id: fix-byte-order-marker - id: mixed-line-ending exclude: '\.(bat|cmd|reg)$' - id: pretty-format-json args: [ "--autofix", "--no-sort-keys" ] - id: sort-simple-yaml - id: trailing-whitespace - repo: https://github.com/scop/pre-commit-shfmt rev: v3.12.0-2 hooks: - id: shfmt args: ["-i", "4", "-ci", "-s"] - repo: https://github.com/shellcheck-py/shellcheck-py rev: v0.11.0.1 hooks: - id: shellcheck ================================================ FILE: COPYRIGHT.md ================================================ Some of the files are Copyright (c) 2024 fmstrat Many files also contain contributions from third parties. In this case the original copyright of the contributions can be traced through the history of the source version control system. When that is not the case, the files contain a prominent notice stating the original copyright and applicable license, or come with their own dedicated COPYRIGHT and/or LICENSE file. ================================================ FILE: LICENSE.md ================================================ For copyright information, please see the [COPYRIGHT.md](./COPYRIGHT.md) file. This project has files licensed under different licenses. The original project by Fmstrat is not free software. Due to lack of a license, it is All Rights Reserved by the original author. We have tried contacting Fmstrat about this, but they abandoned the project and did not reply nor apply an open-source license to the project. However, almost all parts of the codebase have been rewritten and all new contributions require signing a Developer Certificate of Origin (or historically, a CLA; see [CONTRIBUTING.md](./.github/CONTRIBUTING.md)), making most parts of the codebase AGPLv3. Refer to a specific file for its respective license. # 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 ================================================ FILE: README.md ================================================


Run Windows applications (including [Microsoft 365](https://www.microsoft365.com/) and [Adobe Creative Cloud](https://www.adobe.com/creativecloud.html)) on GNU/Linux with `KDE Plasma`, `GNOME` or `XFCE`, integrated seamlessly as if they were native to the OS.

WinApps Demonstration.

## Underlying Mechanism WinApps works by: 1. Running Windows in a `Docker`, `Podman` or `libvirt` virtual machine. 2. Querying Windows for all installed applications. 3. Creating shortcuts to selected Windows applications on the host GNU/Linux OS. 4. Using [`FreeRDP`](https://www.freerdp.com/) as a backend to seamlessly render Windows applications alongside GNU/Linux applications. ## Additional Features - The GNU/Linux `/home` directory is accessible within Windows via the `\\tsclient\home` mount. - Integration with `Nautilus`, allowing you to right-click files to open them with specific Windows applications based on the file MIME type. - The [official taskbar widget](https://github.com/winapps-org/WinApps-Launcher) enables seamless administration of the Windows subsystem and offers an easy way to launch Windows applications. - Microsoft Office links (e.g. ms-word://) from the host system are automatically opened in the Windows subsystem. (Note: You may need to use a [User Agent Switcher](https://github.com/ray-lothian/UserAgent-Switcher/) browser extension and set the User-Agent to Windows, as the Office webapps typically hide the "Open in Desktop App" option for Linux users.) ## Supported Applications **WinApps supports *ALL* Windows applications.** Support does not, however, extend to kernel-level anti-cheat systems (e.g. Riot Vanguard). Universal application support is achieved by: 1. Scanning Windows for any community tested applications (list below). 2. Scanning Windows for any other `.exe` files listed within the Windows Registry. Community tested applications benefit from high-resolution icons and pre-populated MIME types. This enables file managers to determine which Windows applications should open files based on file extensions. Icons for other detected applications are pulled from `.exe` files. Contributing to the list of supported applications is encouraged through submission of pull requests! Please help us grow the WinApps community. *Please note that the provided list of community tested applications is community-driven. As such, some applications may not be tested and verified by the WinApps team.* ### Community Tested Applications
Adobe Acrobat Pro
(X)
Icon in the Public Domain.
Adobe After Effects
(CC)
Icon in the Public Domain.
Adobe Audition
(CC)
Icon in the Public Domain.
Adobe Bridge
(CS6, CC)
Icon in the Public Domain.
Adobe Creative Cloud
(CC)
Icon under MIT license.
Adobe Illustrator
(CC)
Icon in the Public Domain.
Adobe InDesign
(CC)
Icon in the Public Domain.
Adobe Lightroom
(CC)
Icon in the Public Domain.
Adobe Photoshop
(CS6, CC, 2022)
Icon in the Public Domain.
Affinity Designer 2
Icon under CC-BY-SA 4.0 via Serif Ltd.
Affinity Photo 2
Icon under CC-BY-SA 4.0 via Serif Ltd.
Affinity Publisher 2
Icon under CC-BY-SA 4.0 via Serif Ltd.
Affinity by Canva (v3)
Icon in the Public Domain.
Command Prompt
(cmd.exe)
Icon under MIT license.
File Explorer
(Windows Explorer)
Icon in the Public Domain.
Internet Explorer
(11)
Icon in the Public Domain.
Microsoft Access
(2016, 2019, o365)
Icon in the Public Domain.
Microsoft Excel
(2016, 2019, o365)
Icon in the Public Domain.
Microsoft Word
(2016, 2019, o365)
Icon in the Public Domain.
Microsoft OneNote
(2016, 2019, o365)
Icon in the Public Domain.
Microsoft Outlook
(2016, 2019, o365)
Icon in the Public Domain.
Microsoft PowerPoint
(2016, 2019, o365)
Icon in the Public Domain.
Microsoft Publisher
(2016, 2019, o365)
Icon in the Public Domain.
Microsoft Visio
(Standard/Pro. 2021, Plan 2)
Icon in the Public Domain.
Microsoft Project
(Standard/Pro. 2021, Plan 3/5)
Icon in the Public Domain.
Microsoft Visual Studio
(Comm./Pro./Ent. 2022)
Icon in the Public Domain.
Autodesk Fusion 360
Icon in the Public Domain.
mIRC
Icon in the Public Domain.
PowerShell
Icon under MIT license.
Windows
(Full RDP Session)
Icon in the Public Domain.
## Installation ### Step 1: Configure a Windows VM Both `Docker` and `Podman` are recommended backends for running the Windows virtual machine, as they facilitate an automated Windows installation process. WinApps is also compatible with `libvirt`. While this method requires considerably more manual configuration, it also provides greater virtual machine customisation options. All three methods leverage the `KVM` hypervisor, ensuring excellent virtual machine performance. Ultimately, the choice of backend depends on your specific use case. The following guides are available: - [Creating a Windows VM with `Docker` or `Podman`](docs/docker.md) - [Creating a Windows VM with `libvirt`](docs/libvirt.md) If you already have a Windows VM or server you wish to use with WinApps, you will still have to follow the [final steps described in the `libvirt` documentation](docs/libvirt.md#final-configuration-steps). ### Step 2: Install Dependencies Install the required dependencies. - Debian/Ubuntu: ```bash sudo apt install -y curl dialog freerdp3-x11 git iproute2 libnotify-bin netcat-openbsd ``` > [!NOTE] > On Debian 12 (_"bookworm"_), you need to enable the `backports` repository for the `freerdp3-x11` package to become available. > For instructions, see https://backports.debian.org/Instructions. - Fedora/RHEL: ```bash sudo dnf install -y curl dialog freerdp git iproute libnotify nmap-ncat ``` - Arch Linux: ```bash sudo pacman -Syu --needed -y curl dialog freerdp git iproute2 libnotify openbsd-netcat ``` - openSUSE: ```bash sudo zypper install -y curl dialog freerdp git iproute2 libnotify-tools netcat-openbsd ``` - Gentoo Linux: ```bash sudo emerge --ask=n net-misc/curl dev-util/dialog net-misc/freerdp:3 dev-vcs/git sys-apps/iproute2 x11-libs/libnotify net-analyzer/openbsd-netcat ``` > [!NOTE] > WinApps requires `FreeRDP` version 3 or later. If not available for your distribution through your package manager, you can install the [Flatpak](https://flathub.org/apps/com.freerdp.FreeRDP): > ```bash > flatpak install flathub com.freerdp.FreeRDP > sudo flatpak override --filesystem=home com.freerdp.FreeRDP # To use `+home-drive` > ``` > However, if you have weird issues like [#233](https://github.com/winapps-org/winapps/issues/233) when running Flatpak, please compile FreeRDP from source according to [this guide](https://github.com/FreeRDP/FreeRDP/wiki/Compilation). ### Step 3: Create a WinApps Configuration File Create a configuration file at `~/.config/winapps/winapps.conf` containing the following: ```bash ################################## # WINAPPS CONFIGURATION FILE # ################################## # INSTRUCTIONS # - Leading and trailing whitespace are ignored. # - Empty lines are ignored. # - Lines starting with '#' are ignored. # - All characters following a '#' are ignored. # [WINDOWS USERNAME] RDP_USER="MyWindowsUser" # [WINDOWS PASSWORD] # NOTES: # - If using FreeRDP v3.9.0 or greater, you *have* to set a password # - RDP_ASKPASS is provided as a more secure option to RDP_PASS: # - Calls an external command and uses its stdout as the password # - The password is not passed on the command line to freerdp, keeping it out of logs # - If specified, takes precedence over RDP_PASS # - Examples to use this: # - RDP_ASKPASS="~/some-custom-command" # - RDP_ASKPASS="bash -c 'cat ~/.some-secret-file'" # - RDP_ASKPASS="bash -c 'kwallet-query --folder winapps --read-password rdp kdewallet'" # RDP_PASS="MyWindowsPassword" RDP_ASKPASS="" # [WINDOWS DOMAIN] # DEFAULT VALUE: '' (BLANK) RDP_DOMAIN="" # [WINDOWS IPV4 ADDRESS] # NOTES: # - If using 'libvirt', 'RDP_IP' will be determined by WinApps at runtime if left unspecified. # DEFAULT VALUE: # - 'docker': '127.0.0.1' # - 'podman': '127.0.0.1' # - 'libvirt': '' (BLANK) RDP_IP="127.0.0.1" # [VM NAME] # NOTES: # - Only applicable when using 'libvirt' # - The libvirt VM name must match so that WinApps can determine VM IP, start the VM, etc. # DEFAULT VALUE: 'RDPWindows' VM_NAME="RDPWindows" # [WINAPPS BACKEND] # DEFAULT VALUE: 'docker' # VALID VALUES: # - 'docker' # - 'podman' # - 'libvirt' # - 'manual' WAFLAVOR="docker" # [DISPLAY SCALING FACTOR] # NOTES: # - If an unsupported value is specified, a warning will be displayed. # - If an unsupported value is specified, WinApps will use the closest supported value. # DEFAULT VALUE: '100' # VALID VALUES: # - '100' # - '140' # - '180' RDP_SCALE="100" # [MOUNTING REMOVABLE PATHS FOR FILES] # NOTES: # - By default, `udisks` (which you most likely have installed) uses /run/media for mounting removable devices. # This improves compatibility with most desktop environments (DEs). # ATTENTION: The Filesystem Hierarchy Standard (FHS) recommends /media instead. Verify your system's configuration. # - To manually mount devices, you may optionally use /mnt. # REFERENCE: https://wiki.archlinux.org/title/Udisks#Mount_to_/media REMOVABLE_MEDIA="/run/media" # [ADDITIONAL FREERDP FLAGS & ARGUMENTS] # NOTES: # - You can try adding /network:lan to these flags in order to increase performance, however, some users have faced issues with this. # If this does not work or if it does not work without the flag, you can try adding /nsc and /gfx. # DEFAULT VALUE: '/cert:tofu /sound /microphone +home-drive' # VALID VALUES: See https://github.com/awakecoding/FreeRDP-Manuals/blob/master/User/FreeRDP-User-Manual.markdown RDP_FLAGS="/cert:tofu /sound /microphone +home-drive" # [NON FULL WINDOWS RDP FLAGS] # NOTES: # - Use these flags to pass specific flags to the freerdp command when you are starting a non-full RDP session (any other command than winapps windows) # DEFAULT_VALUES: '' # VALID_VALUES: See https://github.com/awakecoding/FreeRDP-Manuals/blob/master/User/FreeRDP-User-Manual.markdown RDP_FLAGS_NON_WINDOWS="" # [FULL WINDOWS RDP FLAGS] # NOTES: # - Use these flags to pass specific flags to the freerdp command when you are starting a full RDP session (winapps windows) # DEFAULT_VALUES: '' # VALID_VALUES: See https://github.com/awakecoding/FreeRDP-Manuals/blob/master/User/FreeRDP-User-Manual.markdown RDP_FLAGS_WINDOWS="" # [DEBUG WINAPPS] # NOTES: # - Creates and appends to ~/.local/share/winapps/winapps.log when running WinApps. # DEFAULT VALUE: 'true' # VALID VALUES: # - 'true' # - 'false' DEBUG="true" # [AUTOMATICALLY PAUSE WINDOWS] # NOTES: # - This is currently INCOMPATIBLE with 'manual'. # DEFAULT VALUE: 'off' # VALID VALUES: # - 'on' # - 'off' AUTOPAUSE="off" # [AUTOMATICALLY PAUSE WINDOWS TIMEOUT] # NOTES: # - This setting determines the duration of inactivity to tolerate before Windows is automatically paused. # - This setting is ignored if 'AUTOPAUSE' is set to 'off'. # - The value must be specified in seconds (to the nearest 10 seconds e.g., '30', '40', '50', etc.). # - For RemoteApp RDP sessions, there is a mandatory 20-second delay, so the minimum value that can be specified here is '20'. # - Source: https://techcommunity.microsoft.com/t5/security-compliance-and-identity/terminal-services-remoteapp-8482-session-termination-logic/ba-p/246566 # DEFAULT VALUE: '300' # VALID VALUES: >=20 AUTOPAUSE_TIME="300" # [FREERDP COMMAND] # NOTES: # - WinApps will attempt to automatically detect the correct command to use for your system. # DEFAULT VALUE: '' (BLANK) # VALID VALUES: The command required to run FreeRDPv3 on your system (e.g., 'xfreerdp', 'xfreerdp3', etc.). FREERDP_COMMAND="" # [TIMEOUTS] # NOTES: # - These settings control various timeout durations within the WinApps setup. # - Increasing the timeouts is only necessary if the corresponding errors occur. # - Ensure you have followed all the Troubleshooting Tips in the error message first. # PORT CHECK # - The maximum time (in seconds) to wait when checking if the RDP port on Windows is open. # - Corresponding error: "NETWORK CONFIGURATION ERROR" (exit status 13). # DEFAULT VALUE: '5' PORT_TIMEOUT="5" # RDP CONNECTION TEST # - The maximum time (in seconds) to wait when testing the initial RDP connection to Windows. # - Corresponding error: "REMOTE DESKTOP PROTOCOL FAILURE" (exit status 14). # DEFAULT VALUE: '30' RDP_TIMEOUT="30" # APPLICATION SCAN # - The maximum time (in seconds) to wait for the script that scans for installed applications on Windows to complete. # - Corresponding error: "APPLICATION QUERY FAILURE" (exit status 15). # DEFAULT VALUE: '60' APP_SCAN_TIMEOUT="60" # WINDOWS BOOT # - The maximum time (in seconds) to wait for the Windows VM to boot if it is not running, before attempting to launch an application. # DEFAULT VALUE: '120' BOOT_TIMEOUT="120" # FREERDP RAIL HIDEF # - This option controls the value of the `hidef` option passed to the /app parameter of the FreeRDP command. # - Setting this option to 'off' may resolve window misalignment issues related to maximized windows. # DEFAULT VALUE: 'on' HIDEF="on" ``` > [!IMPORTANT] > To safeguard your Windows password, ensure `~/.config/winapps/winapps.conf` is accessible only by your user account. > ```bash > chown $(whoami):$(whoami) ~/.config/winapps/winapps.conf > chmod 600 ~/.config/winapps/winapps.conf > ``` > [!IMPORTANT] > `RDP_USER` and `RDP_PASS` must correspond to a complete Windows user account and password, such as those created during Windows setup or for a domain user. User/PIN combinations are not valid for RDP access. > [!IMPORTANT] > If you wish to use an alternative WinApps backend (other than `Docker`), uncomment and change `WAFLAVOR="docker"` to `WAFLAVOR="podman"` or `WAFLAVOR="libvirt"`. #### Configuration Options Explained - If using a pre-existing Windows RDP server on your LAN, you must use `RDP_IP` to specify the location of the Windows server. You may also wish to configure a static IP address for this server. - If running a Windows VM using `libvirt` with NAT enabled, leave `RDP_IP` commented out and WinApps will auto-detect the local IP address for the VM. - For domain users, you can uncomment and change `RDP_DOMAIN`. - On high-resolution (UHD) displays, you can set `RDP_SCALE` to the scale you would like to use (100, 140 or 180). - To add additional flags to the FreeRDP call (e.g. `/prevent-session-lock 120`), uncomment and use the `RDP_FLAGS` configuration option. - For multi-monitor setups, you can try adding `/multimon` to `RDP_FLAGS`. A FreeRDP bug may result in a black screen however, in which case you should revert this change. - To enable non-English input and seamless language switching, you can try adding `/kbd:unicode` to `RDP_FLAGS`. This ensures client inputs are sent as Unicode sequences. - If you enable `DEBUG`, a log will be created on each application start in `~/.local/share/winapps/winapps.log`. - If using a system on which the FreeRDP command is not `xfreerdp` or `xfreerdp3`, the correct command can be specified using `FREERDP_COMMAND`. ### Step 4: Test FreeRDP 1. Test establishing an RDP session by running the following command, replacing the `/u:`, `/p:`, and `/v:` values with the correct values specified in `~/.config/winapps/winapps.conf`. ```bash xfreerdp3 /u:"MyWindowsUser" /p:"MyWindowsPassword" /v:127.0.0.1 /cert:tofu # Or, if you are using Podman podman unshare --rootless-netns xfreerdp3 /u:"MyWindowsUser" /p:"MyWindowsPassword" /v:127.0.0.1 /cert:tofu # Or, if you installed FreeRDP using Flatpak flatpak run --command=xfreerdp com.freerdp.FreeRDP /u:"MyWindowsUser" /p:"MyWindowsPassword" /v:127.0.0.1 /cert:tofu ``` - Please note that the correct `FreeRDP` command may vary depending on your system (e.g. `xfreerdp`, `xfreerdp3`, etc.). - Ensure you use the correct IP address for your Windows instance in the above command. - If prompted within the terminal window, choose to accept the certificate permanently. If the Windows desktop appears in a `FreeRDP` window, the configuration was successful and the correct RDP TLS certificate was enrolled on the Linux host. Disconnect from the RDP session and skip the following debugging step. 2. [DEBUGGING STEP] If an outdated or expired certificate is detected, the `FreeRDP` command will display output resembling the following. In this case, the old certificate will need to be removed and a new RDP TLS certificate installed. ``` @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: CERTIFICATE NAME MISMATCH! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ The hostname used for this connection (192.168.122.2:3389) does not match the name given in the certificate: Common Name (CN): RDPWindows A valid certificate for the wrong name should NOT be trusted! The host key for 192.168.122.2:3389 has changed @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-middle attack)! It is also possible that a host key has just been changed. The fingerprint for the host key sent by the remote host is 8e:b4:d2:8e:4e:14:e7:4e:82:9b:07:5b:e1:68:40:18:bc:db:5f:bc:29:0d:91:83:f9:17:f9:13:e6:51:dc:36 Please contact your system administrator. Add correct host key in /home/rohanbarar/.config/freerdp/server/192.168.122.2_3389.pem to get rid of this message. ``` If you experience the above error, delete any old or outdated RDP TLS certificates associated with Windows, as they can prevent `FreeRDP` from establishing a connection. These certificates are located within `~/.config/freerdp/server/` and follow the naming format `_.pem` (e.g., `192.168.122.2_3389.pem`, `127.0.0.1_3389.pem`, etc.). If you use FreeRDP for purposes other than WinApps, ensure you only remove certificates related to the relevant Windows VM. If no relevant certificates are found, no action is needed. Following deletion, re-attempt establishing an RDP session. ### Step 5: Run the WinApps Installer With Windows still powered on, run the WinApps installer. ```bash bash <(curl https://raw.githubusercontent.com/winapps-org/winapps/main/setup.sh) ``` Once WinApps is installed, a list of additional arguments can be accessed by running `winapps-setup --help`. WinApps Installer Animation. ## Adding Additional Pre-defined Applications Adding your own applications with custom icons and MIME types to the installer is easy. Simply copy one of the application configurations in the `apps` folder located within the WinApps repository, and: 1. Modify the name and variables to reflect the appropriate/desired values for your application. 2. Replace `icon.svg` with an SVG for your application (ensuring the icon is appropriately licensed). 3. Remove and reinstall WinApps. 4. Submit a pull request to add your application to WinApps as a community tested application once you have tested and verified your configuration (optional, but encouraged). ## Running Applications Manually WinApps offers a manual mode for running applications that were not configured by the WinApps installer. This is completed with the `manual` flag. Executables that are in the Windows PATH do not require full path definition. ```bash winapps manual "C:\my\directory\executableNotInPath.exe" winapps manual executableInPath.exe ``` ## Updating WinApps The installer can be run multiple times. To update your installation of WinApps: 1. Run the WinApps installer to remove WinApps from your system. 2. Pull the latest changes from the WinApps GitHub repository. 3. Re-install WinApps using the WinApps installer by running `winapps-setup`. ## WinApps Launcher (Optional) The [WinApps Launcher](https://github.com/winapps-org/winapps-launcher) provides a simple system tray menu that makes it easy to launch your installed Windows applications, open a full desktop RDP session, and control your Windows VM or container. You can start, stop, pause, reboot or hibernate Windows, as well as access your installed applications from a convenient list. This lightweight, optional tool helps streamline your overall WinApps experience. WinApps Launcher Animation. ## Installation using Nix First, follow Step 1 of the normal installation guide to create your VM. Then, install WinApps according to the following instructions. After installation, it will be available under `winapps`, with the installer being available under `winapps-setup` and the optional launcher being available under `winapps-launcher.` ### Using standalone Nix First, make sure Flakes and the `nix` command are enabled. In your `~/.config/nix/nix.conf`: ``` experimental-features = nix-command flakes ``` ```bash nix profile install github:winapps-org/winapps#winapps nix profile install github:winapps-org/winapps#winapps-launcher # optional ``` ### On NixOS using Flakes ```nix # flake.nix { description = "My configuration"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; winapps = { url = "github:winapps-org/winapps"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = inputs@{ nixpkgs, winapps, ... }: { nixosConfigurations.hostname = nixpkgs.lib.nixosSystem rec { system = "x86_64-linux"; specialArgs = { inherit inputs system; }; modules = [ ./configuration.nix ( { pkgs, system ? pkgs.system, ... }: { environment.systemPackages = [ winapps.packages."${system}".winapps winapps.packages."${system}".winapps-launcher # optional ]; } ) ]; }; }; } ``` ### On NixOS without Flakes [Flakes aren't real and they can't hurt you.](https://jade.fyi/blog/flakes-arent-real/). However, if you still don't want to use flakes, you can use WinApps with flake-compat like: ```nix # configuration.nix { pkgs, system ? pkgs.system, ... }: { # set up binary cache (optional) nix.settings = { substituters = [ "https://winapps.cachix.org/" ]; trusted-public-keys = [ "winapps.cachix.org-1:HI82jWrXZsQRar/PChgIx1unmuEsiQMQq+zt05CD36g=" ]; trusted-users = [ "" ]; # replace with your username }; environment.systemPackages = let winapps = (import (builtins.fetchTarball "https://github.com/winapps-org/winapps/archive/main.tar.gz")) .packages."${system}"; in [ winapps.winapps winapps.winapps-launcher # optional ]; } ``` ## Star History Star History Chart ================================================ FILE: apps/ChemDraw/info ================================================ # GNOME shortcut name NAME="ChemDraw" # Used for descriptions and window class FULL_NAME="ChemDraw Prime" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\RevvitySignalsSoftware\ChemDrawApplications_x64\ChemDraw.exe" # GNOME categories CATEGORIES="WinApps;Science" # GNOME mimetypes; file types: .cdxml .cdx .chm MIME_TYPES="chemical/x-cdx;application/vnd.chemdraw+xml;chemical/x-chemdraw;" ================================================ FILE: apps/access/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Access" # Used for descriptions and window class FULL_NAME="Microsoft Access" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Office\root\Office16\MSACCESS.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/x-msaccess;" ================================================ FILE: apps/access-o365/info ================================================ # Copyright (c) 2024 Jon Champagne # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Access" # Used for descriptions and window class FULL_NAME="Microsoft Access" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Office\Office16\MSACCESS.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/x-msaccess;" ================================================ FILE: apps/access-o365-x86/info ================================================ # GNOME shortcut name NAME="Access" # Used for descriptions and window class FULL_NAME="Microsoft Access" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\Office16\MSACCESS.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/x-msaccess;" ================================================ FILE: apps/access-x86/info ================================================ # GNOME shortcut name NAME="Access" # Used for descriptions and window class FULL_NAME="Microsoft Access" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\root\Office16\MSACCESS.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/x-msaccess;" ================================================ FILE: apps/acrobat-dc/info ================================================ # GNOME shortcut name NAME="Adobe Acrobat DC" # Used for descriptions and window class FULL_NAME="Adobe Acrobat DC" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Acrobat DC\Acrobat\Acrobat.exe" # GNOME categories CATEGORIES="WinApps;Documents" # GNOME mimetypes MIME_TYPES="application/pdf;" ================================================ FILE: apps/acrobat-x-pro/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Acrobat X Pro" # Used for descriptions and window class FULL_NAME="Acrobat X Pro" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Adobe\Acrobat 10.0\Acrobat\Acrobat.exe" # GNOME categories CATEGORIES="WinApps;Documents" # GNOME mimetypes MIME_TYPES="application/pdf;" ================================================ FILE: apps/acrobat9/info ================================================ # GNOME shortcut name NAME="Acrobat 9" # Used for descriptions and window class FULL_NAME="Acrobat 9" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Adobe\Acrobat 9.0\Acrobat\Acrobat.exe" # GNOME categories CATEGORIES="WinApps;Documents" # GNOME mimetypes MIME_TYPES="application/pdf;" ================================================ FILE: apps/adobe-cc/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Creative Cloud" # Used for descriptions and window class FULL_NAME="Adobe Creative Cloud" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe Creative Cloud\ACC\Creative Cloud.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="" # System Icon ICON="AdobeUpdate" ================================================ FILE: apps/adobe-digital-editions-4.5/info ================================================ # GNOME shortcut name NAME="Adobe Digital Editions" # Used for descriptions and window class FULL_NAME="Adobe Digital Editions" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Adobe\Adobe Digital Editions 4.5\DigitalEditions.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="application/epub+zip;application/vnd.adobe.adept+xml;" # System Icon ICON="AdobeDigitalEditions" ================================================ FILE: apps/afdesign/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Designer" # Used for descriptions and window class FULL_NAME="Affinity Designer" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Affinity\Designer 2\Designer.exe" # GNOME categories CATEGORIES="WinApps;Affinity" # GNOME mimetypes MIME_TYPES="" # System Icon ICON="AffinityDesigner" ================================================ FILE: apps/affinity/info ================================================ # GNOME shortcut name NAME="Affinity" # Used for descriptions and window class FULL_NAME="Affinity by Canva" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Affinity\Affinity\Affinity.exe" # GNOME categories CATEGORIES="WinApps;Affinity" # GNOME mimetypes MIME_TYPES="" # System Icon ICON="Affinity" ================================================ FILE: apps/afphoto/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Photo" # Used for descriptions and window class FULL_NAME="Affinity Photo" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Affinity\Photo 2\Photo.exe" # GNOME categories CATEGORIES="WinApps;Affinity" # GNOME mimetypes MIME_TYPES="" # System Icon ICON="AffinityPhoto" ================================================ FILE: apps/afpub/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Publisher" # Used for descriptions and window class FULL_NAME="Affinity Publisher" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Affinity\Publisher 2\Publisher.exe" # GNOME categories CATEGORIES="WinApps;Affinity" # GNOME mimetypes MIME_TYPES="" # System Icon ICON="AffinityPublisher" ================================================ FILE: apps/aftereffects-2024/info ================================================ # GNOME shortcut name NAME="After Effects 2024" # Used for descriptions and window class FULL_NAME="Adobe After Effects 2024" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe After Effects 2024\Support Files\AfterFX.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="application/vnd.adobe.aftereffects.project;application/vnd.adobe.aftereffects.template;" # System Icon ICON="AdobeAfterEffects" ================================================ FILE: apps/aftereffects-cc/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="After Effects CC" # Used for descriptions and window class FULL_NAME="Adobe After Effects CC" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe After Effects 2020\Support Files\AfterFX.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="application/vnd.adobe.aftereffects.project;application/vnd.adobe.aftereffects.template;" # System Icon ICON="AdobeAfterEffect" ================================================ FILE: apps/audition-2024/info ================================================ # GNOME shortcut name NAME="Audition 2024" # Used for descriptions and window class FULL_NAME="Adobe Audition 2024" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe Audition 2024\Adobe Audition.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="" # System Icon ICON="AdobeAudition" ================================================ FILE: apps/audition-cc/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Audition CC" # Used for descriptions and window class FULL_NAME="Adobe Audition CC" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe Audition 2020\Adobe Audition.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="" # System Icon ICON="AdobeAudition" ================================================ FILE: apps/bridge-cc/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Bridge CS6" # Used for descriptions and window class FULL_NAME="Adobe Bridge CS6" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe Bridge CS6 (64 Bit)\Bridge.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="image/vnd.adobe.photoshop;" # System Icon ICON="AdobeBridge" ================================================ FILE: apps/bridge-cs6/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Bridge CS6" # Used for descriptions and window class FULL_NAME="Adobe Bridge CS6" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe Bridge 2020\Bridge.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="image/vnd.adobe.photoshop;" # System Icon ICON="AdobeBridge" ================================================ FILE: apps/bridge-cs6-x86/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Bridge CS6" # Used for descriptions and window class FULL_NAME="Adobe Bridge CS6" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Adobe\Adobe Bridge CS6\Bridge.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="image/vnd.adobe.photoshop;" # System Icon ICON="AdobeBridge" ================================================ FILE: apps/cmd/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Cmd" # Used for descriptions and window class FULL_NAME="Command Prompt" # The executable inside windows WIN_EXECUTABLE="C:\Windows\System32\cmd.exe" # GNOME categories CATEGORIES="WinApps;Windows" # GNOME mimetypes MIME_TYPES="" # System Icon ICON="Terminal" ================================================ FILE: apps/dorico-6/info ================================================ # GNOME shortcut name NAME="Dorico 6" # Used for descriptions and window class FULL_NAME="Steinberg Dorico 6" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Steinberg\Dorico6\Dorico6.exe" # GNOME categories CATEGORIES="WinApps;Audio;Graphics;Publishing;Music;" # GNOME mimetypes MIME_TYPES="application/vnd.steinberg.dorico-project;" # System Icon ICON="Dorico" ================================================ FILE: apps/dymo-connect/info ================================================ # GNOME shortcut name NAME="DYMO Connect" # Used for descriptions and window class FULL_NAME="Software for DYMO label printers" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\DYMO\DYMO Connect\DYMOConnect.exe" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="" # System Icon ICON="DYMOConnect" ================================================ FILE: apps/emclient/info ================================================ # GNOME shortcut name NAME="eM Client" # Used for descriptions and window class FULL_NAME="eM Client" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\eM Client\mailclient.exe" # GNOME categories CATEGORIES="WinApps;Network;Office;" # GNOME mimetypes MIME_TYPES="" # System Icon ICON="eM Client" ================================================ FILE: apps/excel/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Excel" # Used for descriptions and window class FULL_NAME="Microsoft Excel" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Office\Office16\EXCEL.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-excel;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;application/vnd.openxmlformats-officedocument.spreadsheetml.template;application/vnd.ms-excel.sheet.macroEnabled.12;application/vnd.ms-excel.template.macroEnabled.12;application/vnd.ms-excel.addin.macroEnabled.12;application/vnd.ms-excel.sheet.binary.macroEnabled.12;" # System Icon ICON="ms-excel" ================================================ FILE: apps/excel-o365/info ================================================ # Copyright (c) 2024 Jon Champagne # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Excel" # Used for descriptions and window class FULL_NAME="Microsoft Excel" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-excel;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;application/vnd.openxmlformats-officedocument.spreadsheetml.template;application/vnd.ms-excel.sheet.macroEnabled.12;application/vnd.ms-excel.template.macroEnabled.12;application/vnd.ms-excel.addin.macroEnabled.12;application/vnd.ms-excel.sheet.binary.macroEnabled.12;" # System Icon ICON="ms-excel" ================================================ FILE: apps/excel-o365-x86/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Excel" # Used for descriptions and window class FULL_NAME="Microsoft Excel" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\root\Office16\EXCEL.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-excel;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;application/vnd.openxmlformats-officedocument.spreadsheetml.template;application/vnd.ms-excel.sheet.macroEnabled.12;application/vnd.ms-excel.template.macroEnabled.12;application/vnd.ms-excel.addin.macroEnabled.12;application/vnd.ms-excel.sheet.binary.macroEnabled.12;" # System Icon ICON="ms-excel"s ================================================ FILE: apps/excel-x86/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Excel" # Used for descriptions and window class FULL_NAME="Microsoft Excel" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\Office16\EXCEL.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-excel;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;application/vnd.openxmlformats-officedocument.spreadsheetml.template;application/vnd.ms-excel.sheet.macroEnabled.12;application/vnd.ms-excel.template.macroEnabled.12;application/vnd.ms-excel.addin.macroEnabled.12;application/vnd.ms-excel.sheet.binary.macroEnabled.12;" # System Icon ICON="ms-excel" ================================================ FILE: apps/excel-x86-2010/info ================================================ # GNOME shortcut name NAME="Excel" # Used for descriptions and window class FULL_NAME="Microsoft Excel" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\Office14\EXCEL.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-excel;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;application/vnd.openxmlformats-officedocument.spreadsheetml.template;application/vnd.ms-excel.sheet.macroEnabled.12;application/vnd.ms-excel.template.macroEnabled.12;application/vnd.ms-excel.addin.macroEnabled.12;application/vnd.ms-excel.sheet.binary.macroEnabled.12;" ================================================ FILE: apps/explorer/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Explorer" # Used for descriptions and window class FULL_NAME="Microsoft Explorer" # The executable inside windows WIN_EXECUTABLE="C:\Windows\explorer.exe" # GNOME categories CATEGORIES="WinApps;Windows" # GNOME mimetypes MIME_TYPES="" ================================================ FILE: apps/fusion-360/info ================================================ # GNOME shortcut name NAME="Fusion 360" # Used for descriptions and window class FULL_NAME="Autodesk Fusion 360" # The executable inside windows WIN_EXECUTABLE="C:\Users\%USERNAME%\AppData\Local\Autodesk\webdeploy\production\6a0c9611291d45bb9226980209917c3d\FusionLauncher.exe" # GNOME categories CATEGORIES="WinApps;Graphics;3DGraphics" # GNOME mimetypes MIME_TYPES="application/fusion" ================================================ FILE: apps/iexplorer/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Internet Explorer" # Used for descriptions and window class FULL_NAME="Internet Explorer" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Internet Explorer\iexplore.exe" # GNOME categories CATEGORIES="WinApps;Network;WebBrowser;" # GNOME mimetypes MIME_TYPES="text/html;text/xml;application/xhtml+xml;application/xml;application/rss+xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp;x-scheme-handler/chrome;video/webm;application/x-xpinstall;" ================================================ FILE: apps/illustrator-2024/info ================================================ # GNOME shortcut name NAME="Illustrator 2024" # Used for descriptions and window class FULL_NAME="Adobe Illustrator 2024" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe Illustrator 2024\Support Files\Contents\Windows\Illustrator.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="application/illustrator;" # System Icon ICON="AdobeIllustrator" ================================================ FILE: apps/illustrator-cc/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Illustrator CC" # Used for descriptions and window class FULL_NAME="Adobe Illustrator CC" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe Illustrator 2021\Support Files\Contents\Windows\Illustrator.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="application/illustrator;" # System Icon ICON="AdobeIllustrator" ================================================ FILE: apps/indesign-2024/info ================================================ # GNOME shortcut name NAME="InDesign 2024" # Used for descriptions and window class FULL_NAME="Adobe InDesign 2024" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe InDesign 2024\InDesign.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="application/x-adobe-indesign-interchange;application/x-adobe-indesign;" # System Icon ICON="AdobeIndesign" ================================================ FILE: apps/indesign-cc/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="InDesign CC" # Used for descriptions and window class FULL_NAME="Adobe InDesign CC" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe InDesign 2021\InDesign.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="application/x-adobe-indesign-interchange;application/x-adobe-indesign;" # System Icon ICON="AdobeIndesign" ================================================ FILE: apps/lightroom-cc/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Lightroom CC" # Used for descriptions and window class FULL_NAME="Adobe Lightroom CC" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe Lightroom CC\lightroom.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="" # System Icon ICON="AdobeLightroom" ================================================ FILE: apps/lightroom-classic/info ================================================ # GNOME shortcut name NAME="Lightroom Classic" # Used for descriptions and window class FULL_NAME="Adobe Lightroom Classic" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe Lightroom Classic\Lightroom.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="" # System Icon ICON="AdobeLightroom" ================================================ FILE: apps/linqpad8/info ================================================ # GNOME shortcut name NAME="LINQPad8" # Used for descriptions and window class FULL_NAME="LINQPad 8" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\LINQPad8\LINQPad8.exe" # GNOME categories CATEGORIES="WinApps;Development" # GNOME mimetypes MIME_TYPES="text/cs" ================================================ FILE: apps/mediaencoder-2024/info ================================================ # GNOME shortcut name NAME="Media Encoder 2024" # Used for descriptions and window class FULL_NAME="Adobe Media Encoder 2024" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe Media Encoder 2024\Adobe Media Encoder.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="" # System Icon ICON="AdobeMediaEncoder" ================================================ FILE: apps/mirc/info ================================================ # GNOME shortcut name NAME="mIRC" # Used for descriptions and window class FULL_NAME="mIRC" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\mIRC\mirc.exe" # GNOME categories CATEGORIES="WinApps;Network;IRCclient;" # GNOME mimetypes MIME_TYPES="" # System Icon ICON="mIRC" ================================================ FILE: apps/ms-office-protocol-handler.desktop ================================================ [Desktop Entry] Name=Microsoft Office Protocol Handler Comment=Handle Microsoft Office URI schemes via WinApps Exec=winapps manual %u Terminal=false Type=Application MimeType=x-scheme-handler/ms-word;x-scheme-handler/ms-excel;x-scheme-handler/ms-powerpoint;x-scheme-handler/ms-outlook;x-scheme-handler/ms-access;x-scheme-handler/ms-visio;x-scheme-handler/ms-project;x-scheme-handler/ms-teams;x-scheme-handler/ms-whiteboard;x-scheme-handler/ms-officeapp; NoDisplay=true Categories=Office;Utility; ================================================ FILE: apps/mspaint/info ================================================ # GNOME shortcut name NAME="Paint" # Used for descriptions and window class FULL_NAME="Microsoft Paint" # The executable inside windows WIN_EXECUTABLE="C:\windows\system32\mspaint.exe" # GNOME categories CATEGORIES="WinApps;Graphics" # GNOME mimetypes MIME_TYPES="image/png;image/bmp;image/jpeg;image/jpe;image/jpeg;image/tiff;image/tif;image/x-ms-bmp;image/dib;image/vnd.microsoft.icon;image/x-icon;image/ico;image/icon;text/ico;application/ico" ================================================ FILE: apps/onenote/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="OneNote" # Used for descriptions and window class FULL_NAME="Microsoft OneNote" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Office\Office16\ONENOTE.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/msonenote;" # System Icon ICON="ms-outlook" ================================================ FILE: apps/onenote-o365/info ================================================ # Copyright (c) 2024 Jon Champagne # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="OneNote" # Used for descriptions and window class FULL_NAME="Microsoft OneNote" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Office\root\Office16\ONENOTE.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/msonenote;" # System Icon ICON="ms-outlook" ================================================ FILE: apps/onenote-o365-x86/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="OneNote" # Used for descriptions and window class FULL_NAME="Microsoft OneNote" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\root\Office16\ONENOTE.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/msonenote;" # System Icon ICON="ms-outlook" ================================================ FILE: apps/onenote-x86/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="OneNote" # Used for descriptions and window class FULL_NAME="Microsoft OneNote" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\Office16\ONENOTE.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/msonenote;" # System Icon ICON="ms-onenote" ================================================ FILE: apps/outlook/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Outlook" # Used for descriptions and window class FULL_NAME="Microsoft Outlook" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Office\Office16\OUTLOOK.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-outlook;application/octet-stream;" # System Icon ICON="ms-outlook" ================================================ FILE: apps/outlook-o365/info ================================================ # Copyright (c) 2024 Jon Champagne # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Outlook" # Used for descriptions and window class FULL_NAME="Microsoft Outlook" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Office\root\Office16\OUTLOOK.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-outlook;application/octet-stream;" # System Icon ICON="ms-outlook" ================================================ FILE: apps/outlook-o365-x86/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Outlook" # Used for descriptions and window class FULL_NAME="Microsoft Outlook" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\root\Office16\OUTLOOK.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-outlook;application/octet-stream;" # System Icon ICON="ms-outlook" ================================================ FILE: apps/outlook-x86/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Outlook" # Used for descriptions and window class FULL_NAME="Microsoft Outlook" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\Office16\OUTLOOK.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-outlook;application/octet-stream;" # System Icon ICON="ms-outlook" ================================================ FILE: apps/paint.net/info ================================================ # GNOME shortcut name NAME="Paint.NET" # Used for descriptions and window class FULL_NAME="Paint.NET" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Paint.NET\paintdotnet.exe" # GNOME categories CATEGORIES="WinApps;Graphic;" # GNOME mimetypes MIME_TYPES="" # System Icon ICON="Paint.Net" ================================================ FILE: apps/pdfgear/info ================================================ # GNOME shortcut name NAME="pdfgear" # Used for descriptions and window class FULL_NAME="PDF Gear" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\PDFgear\PDFLauncher.exe" # GNOME categories CATEGORIES="Office;Viewer;Graphics;pdf" # GNOME mimetypes MIME_TYPES="application/pdf;" ================================================ FILE: apps/photoshop-2022/info ================================================ # GNOME shortcut name NAME="Photoshop 2022" # Used for descriptions and window class FULL_NAME="Adobe Photoshop 2022" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe Photoshop 2022\Photoshop.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="image/vnd.adobe.photoshop;" # System Icon ICON="AdobePhotoshop" ================================================ FILE: apps/photoshop-2024/info ================================================ # GNOME shortcut name NAME="Photoshop 2024" # Used for descriptions and window class FULL_NAME="Adobe Photoshop 2024" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe Photoshop 2024\Photoshop.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="image/vnd.adobe.photoshop;" # System Icon ICON="AdobePhotoshop" ================================================ FILE: apps/photoshop-2025/info ================================================ # GNOME shortcut name NAME="Photoshop 2025" # Used for descriptions and window class FULL_NAME="Adobe Photoshop 2025" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe Photoshop 2025\Photoshop.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="image/vnd.adobe.photoshop;" # System Icon ICON="AdobePhotoshop" ================================================ FILE: apps/photoshop-cc/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Photoshop CC" # Used for descriptions and window class FULL_NAME="Adobe Photoshop CC" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe Photoshop 2020\Photoshop.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="image/vnd.adobe.photoshop;" # System Icon ICON="AdobePhotoshop" ================================================ FILE: apps/photoshop-cs6/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Photoshop CS6" # Used for descriptions and window class FULL_NAME="Adobe Photoshop CS6" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe Photoshop CS6 (64 Bit)\Photoshop.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="image/vnd.adobe.photoshop;" # System Icon ICON="AdobePhotoshop" ================================================ FILE: apps/photoshop-cs6-x86/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Photoshop CS6" # Used for descriptions and window class FULL_NAME="Adobe Photoshop CS6" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Adobe\Adobe Photoshop CS6\Photoshop.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="image/vnd.adobe.photoshop;" # System Icon ICON="AdobePhotoshop" ================================================ FILE: apps/powerbi/info ================================================ # GNOME shortcut name NAME="Power BI Desktop (Traditional)" # Used for descriptions and window class FULL_NAME="Microsoft Power BI Desktop" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Power BI Desktop\bin\PBIDesktop.exe" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/x-powerbi;application/vnd.ms-powerbi;application/powerbi;application/x-pbix;application/vnd.powerbi.pbix" # System Icon ICON="ms-powerbi" ================================================ FILE: apps/powerbi-store/info ================================================ # GNOME shortcut name NAME="Power BI Desktop (Microsoft Store)" # Used for descriptions and window class FULL_NAME="Microsoft Power BI Desktop (Store Version)" # The executable inside windows WIN_EXECUTABLE="C:\Users\%USERNAME%\AppData\Local\Microsoft\WindowsApps\PBIDesktop.exe" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/x-powerbi;application/vnd.ms-powerbi;application/powerbi;application/x-pbix;application/vnd.powerbi.pbix" # System Icon ICON="ms-powerbi" ================================================ FILE: apps/powerpoint/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="PowerPoint" # Used for descriptions and window class FULL_NAME="Microsoft PowerPoint" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Office\Office16\POWERPNT.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-powerpoint;application/vnd.openxmlformats-officedocument.presentationml.presentation;application/vnd.openxmlformats-officedocument.presentationml.template;application/vnd.openxmlformats-officedocument.presentationml.slideshow;application/vnd.ms-powerpoint.addin.macroEnabled.12;application/vnd.ms-powerpoint.presentation.macroEnabled.12;application/vnd.ms-powerpoint.template.macroEnabled.12;application/vnd.ms-powerpoint.slideshow.macroEnabled.12;" # System Icon ICON="ms-powerpoint" ================================================ FILE: apps/powerpoint-o365/info ================================================ # Copyright (c) 2024 Jon Champagne # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="PowerPoint" # Used for descriptions and window class FULL_NAME="Microsoft PowerPoint" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Office\root\Office16\POWERPNT.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-powerpoint;application/vnd.openxmlformats-officedocument.presentationml.presentation;application/vnd.openxmlformats-officedocument.presentationml.template;application/vnd.openxmlformats-officedocument.presentationml.slideshow;application/vnd.ms-powerpoint.addin.macroEnabled.12;application/vnd.ms-powerpoint.presentation.macroEnabled.12;application/vnd.ms-powerpoint.template.macroEnabled.12;application/vnd.ms-powerpoint.slideshow.macroEnabled.12;" # System Icon ICON="ms-powerpoint" ================================================ FILE: apps/powerpoint-o365-x86/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="PowerPoint" # Used for descriptions and window class FULL_NAME="Microsoft PowerPoint" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\root\Office16\POWERPNT.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-powerpoint;application/vnd.openxmlformats-officedocument.presentationml.presentation;application/vnd.openxmlformats-officedocument.presentationml.template;application/vnd.openxmlformats-officedocument.presentationml.slideshow;application/vnd.ms-powerpoint.addin.macroEnabled.12;application/vnd.ms-powerpoint.presentation.macroEnabled.12;application/vnd.ms-powerpoint.template.macroEnabled.12;application/vnd.ms-powerpoint.slideshow.macroEnabled.12;" # System Icon ICON="ms-powerpoint" ================================================ FILE: apps/powerpoint-x86/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="PowerPoint" # Used for descriptions and window class FULL_NAME="Microsoft PowerPoint" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\Office16\POWERPNT.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-powerpoint;application/vnd.openxmlformats-officedocument.presentationml.presentation;application/vnd.openxmlformats-officedocument.presentationml.template;application/vnd.openxmlformats-officedocument.presentationml.slideshow;application/vnd.ms-powerpoint.addin.macroEnabled.12;application/vnd.ms-powerpoint.presentation.macroEnabled.12;application/vnd.ms-powerpoint.template.macroEnabled.12;application/vnd.ms-powerpoint.slideshow.macroEnabled.12;" # System Icon ICON="ms-powerpoint" ================================================ FILE: apps/powershell/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Powershell" # Used for descriptions and window class FULL_NAME="Microsoft Powershell" # The executable inside windows WIN_EXECUTABLE="C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe" # GNOME categories CATEGORIES="WinApps;Windows" # GNOME mimetypes MIME_TYPES="" ================================================ FILE: apps/powershell-ide/info ================================================ # GNOME shortcut name NAME="Powershell" # Used for descriptions and window class FULL_NAME="Microsoft Powershell" # The executable inside windows WIN_EXECUTABLE="C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe" # GNOME categories CATEGORIES="WinApps;Windows" # GNOME mimetypes MIME_TYPES="" ================================================ FILE: apps/premierepro-2024/info ================================================ # GNOME shortcut name NAME="Premiere Pro 2024" # Used for descriptions and window class FULL_NAME="Adobe Premiere Pro 2024" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Adobe\Adobe Premiere Pro 2024\Adobe Premiere Pro.exe" # GNOME categories CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="image/vnd.adobe.premierepro.project;" # System Icon ICON="AdobePremierePro" ================================================ FILE: apps/project/info ================================================ # GNOME shortcut name NAME="Project" # Used for descriptions and window class FULL_NAME="Microsoft Project" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Office\root\Office16\WINPROJ.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-project;" ================================================ FILE: apps/project-x86/info ================================================ NAME="Project" # Used for descriptions and window class FULL_NAME="Microsoft Project" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\root\Office16\WINPROJ.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-project;" ================================================ FILE: apps/publisher/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Publisher" # Used for descriptions and window class FULL_NAME="Microsoft Publisher" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Office\Office16\MSPUB.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-publisher;" ================================================ FILE: apps/publisher-o365/info ================================================ # Copyright (c) 2024 Jon Champagne # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Publisher" # Used for descriptions and window class FULL_NAME="Microsoft Publisher" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Office\root\Office16\MSPUB.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-publisher;" ================================================ FILE: apps/publisher-o365-x86/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Publisher" # Used for descriptions and window class FULL_NAME="Microsoft Publisher" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\root\Office16\MSPUB.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-publisher;" ================================================ FILE: apps/publisher-x86/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Publisher" # Used for descriptions and window class FULL_NAME="Microsoft Publisher" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\Office16\MSPUB.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-publisher;" ================================================ FILE: apps/remarkable-desktop/info ================================================ # GNOME shortcut name NAME="reMarkable" # Used for descriptions and window class FULL_NAME="reMarkable Desktop App" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\reMarkable\reMarkable.exe" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="" # System Icon ICON="reMarkable" ================================================ FILE: apps/ssms20/info ================================================ # GNOME shortcut name NAME="SQL Server Management Studio" # Used for descriptions and window class FULL_NAME="SQL Server Management Studio" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft SQL Server Management Studio 20\Common7\IDE\Ssms.exe" # GNOME categories CATEGORIES="Development" # GNOME mimetypes MIME_TYPES="text/sql" ================================================ FILE: apps/turbotax-2024/info ================================================ # GNOME shortcut name NAME="TurboTax 2024" # Used for descriptions and window class FULL_NAME="Intuit TurboTax 2024" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\TurboTax\Individual 2024\64bit\TurboTax.exe" # GNOME categories CATEGORIES="WinApps;Office;Finance;" # GNOME mimetypes MIME_TYPES="application/x-turbotax;" # System Icon ICON="TurboTax" ================================================ FILE: apps/turbotax-2025/info ================================================ # GNOME shortcut name NAME="TurboTax 2025" # Used for descriptions and window class FULL_NAME="Intuit TurboTax 2025" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\TurboTax\Individual 2025\64bit\TurboTax.exe" # GNOME categories CATEGORIES="WinApps;Office;Finance;" # GNOME mimetypes MIME_TYPES="application/x-turbotax;" # System Icon ICON="TurboTax" ================================================ FILE: apps/visio/info ================================================ # GNOME shortcut name NAME="Visio" # Used for descriptions and window class FULL_NAME="Microsoft Visio" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Office\root\Office16\VISIO.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-visio.template.main+xml;application/vnd.ms-visio.stencil.main+xml;application/vnd.ms-visio.drawing.main+xml;application/vnd.ms-visio.stencil.macroEnabled.main+xml;application/vnd.ms-visio.drawing.macroEnabled.main+xml;application/vnd.ms-visio.template.macroEnabled.main+xml;" ================================================ FILE: apps/visio-x86/info ================================================ # GNOME shortcut name NAME="Visio" # Used for descriptions and window class FULL_NAME="Microsoft Visio" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\root\Office16\VISIO.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-visio.template.main+xml;application/vnd.ms-visio.stencil.main+xml;application/vnd.ms-visio.drawing.main+xml;application/vnd.ms-visio.stencil.macroEnabled.main+xml;application/vnd.ms-visio.drawing.macroEnabled.main+xml;application/vnd.ms-visio.template.macroEnabled.main+xml;" ================================================ FILE: apps/visual-studio-comm/info ================================================ # GNOME shortcut name NAME="Visual Studio Community" # Used for descriptions and window class FULL_NAME="Microsoft Visual Studio - Community Version" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\devenv.exe" # GNOME categories CATEGORIES="WinApps;Development" # GNOME mimetypes MIME_TYPES="" ================================================ FILE: apps/visual-studio-ent/info ================================================ # GNOME shortcut name NAME="Visual Studio Enterprise" # Used for descriptions and window class FULL_NAME="Microsoft Visual Studio - Enterprise Version" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\devenv.exe" # GNOME categories CATEGORIES="WinApps;Development" # GNOME mimetypes MIME_TYPES="" ================================================ FILE: apps/visual-studio-pro/info ================================================ # GNOME shortcut name NAME="Visual Studio Professional" # Used for descriptions and window class FULL_NAME="Microsoft Visual Studio - Professional Version" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\IDE\devenv.exe" # GNOME categories CATEGORIES="WinApps;Development" # GNOME mimetypes MIME_TYPES="" ================================================ FILE: apps/word/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Word" # Used for descriptions and window class FULL_NAME="Microsoft Word" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Office\Office16\WINWORD.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/msword;application/vnd.openxmlformats-officedocument.wordprocessingml.document;application/vnd.openxmlformats-officedocument.wordprocessingml.template;application/vnd.ms-word.document.macroEnabled.12;application/vnd.ms-word.template.macroEnabled.12;" # System Icon ICON="ms-word" ================================================ FILE: apps/word-o365/info ================================================ # Copyright (c) 2024 Jon Champagne # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Word" # Used for descriptions and window class FULL_NAME="Microsoft Word" # The executable inside windows WIN_EXECUTABLE="C:\Program Files\Microsoft Office\root\Office16\WINWORD.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/msword;application/vnd.openxmlformats-officedocument.wordprocessingml.document;application/vnd.openxmlformats-officedocument.wordprocessingml.template;application/vnd.ms-word.document.macroEnabled.12;application/vnd.ms-word.template.macroEnabled.12;" # System Icon ICON="ms-word" ================================================ FILE: apps/word-o365-x86/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Word" # Used for descriptions and window class FULL_NAME="Microsoft Word" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/msword;application/vnd.openxmlformats-officedocument.wordprocessingml.document;application/vnd.openxmlformats-officedocument.wordprocessingml.template;application/vnd.ms-word.document.macroEnabled.12;application/vnd.ms-word.template.macroEnabled.12;" # System Icon ICON="ms-word" ================================================ FILE: apps/word-x86/info ================================================ # Copyright (c) 2024 Fmstrat # All rights reserved. # # SPDX-License-Identifier: Proprietary # GNOME shortcut name NAME="Word" # Used for descriptions and window class FULL_NAME="Microsoft Word" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\Office16\WINWORD.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/msword;application/vnd.openxmlformats-officedocument.wordprocessingml.document;application/vnd.openxmlformats-officedocument.wordprocessingml.template;application/vnd.ms-word.document.macroEnabled.12;application/vnd.ms-word.template.macroEnabled.12;" # System Icon ICON="ms-word" ================================================ FILE: apps/word-x86-2010/info ================================================ # GNOME shortcut name NAME="Word" # Used for descriptions and window class FULL_NAME="Microsoft Word" # The executable inside windows WIN_EXECUTABLE="C:\Program Files (x86)\Microsoft Office\Office14\WINWORD.EXE" # GNOME categories CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/msword;application/vnd.openxmlformats-officedocument.wordprocessingml.document;application/vnd.openxmlformats-officedocument.wordprocessingml.template;application/vnd.ms-word.document.macroEnabled.12;application/vnd.ms-word.template.macroEnabled.12;" # System Icon ICON="ms-word" ================================================ FILE: bin/winapps ================================================ #!/usr/bin/env bash ### GLOBAL CONSTANTS ### # ERROR CODES readonly EC_MISSING_CONFIG=1 readonly EC_MISSING_FREERDP=2 readonly EC_NOT_IN_GROUP=3 readonly EC_FAIL_START=4 readonly EC_FAIL_RESUME=5 readonly EC_FAIL_DESTROY=6 readonly EC_SD_TIMEOUT=7 readonly EC_DIE_TIMEOUT=8 readonly EC_RESTART_TIMEOUT=9 readonly EC_NOT_EXIST=10 readonly EC_UNKNOWN=11 readonly EC_NO_IP=12 readonly EC_BAD_PORT=13 readonly EC_UNSUPPORTED_APP=14 readonly EC_INVALID_FLAVOR=15 # PATHS readonly APPDATA_PATH="${HOME}/.local/share/winapps" readonly SYS_APP_PATH="/usr/local/share/winapps" readonly LASTRUN_PATH="${APPDATA_PATH}/lastrun" readonly LOG_PATH="${APPDATA_PATH}/winapps.log" readonly CONFIG_PATH="${HOME}/.config/winapps/winapps.conf" readonly COMPOSE_PATH="${HOME}/.config/winapps/compose.yaml" # shellcheck disable=SC2155 # Silence warnings regarding masking return values through simultaneous declaration and assignment. readonly SCRIPT_DIR_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" readonly SLEEP_DETECT_PATH="${APPDATA_PATH}/last_activity" readonly SLEEP_MARKER="${APPDATA_PATH}/sleep_marker" # OTHER readonly CONTAINER_NAME="WinApps" # FOR 'docker' AND 'podman' ONLY readonly RDP_PORT=3389 readonly DOCKER_IP="127.0.0.1" # shellcheck disable=SC2155 # Silence warnings regarding masking return values through simultaneous declaration and assignment. readonly RUNID="${RANDOM}" ### GLOBAL VARIABLES ### # WINAPPS CONFIGURATION FILE RDP_USER="" RDP_PASS="" RDP_ASKPASS="" RDP_DOMAIN="" RDP_IP="" VM_NAME="RDPWindows" # FOR 'libvirt' ONLY WAFLAVOR="docker" RDP_FLAGS="" FREERDP_COMMAND="" REMOVABLE_MEDIA="" RDP_SCALE=100 AUTOPAUSE="off" AUTOPAUSE_TIME="300" DEBUG="true" BOOT_TIMEOUT=120 HIDEF="on" RDP_FLATPAK=0 RDP_FLAGS_WINDOWS="" RDP_FLAGS_NON_WINDOWS="" # OTHER FREERDP_PID=-1 NEEDED_BOOT=false ### TRAPS ### # Catch SIGINT (CTRL+C) and SIGTERM to call 'waKillCleanExit'. trap waKillCleanExit SIGINT SIGTERM ### FUNCTIONS ### # Name: 'waCleanFreeRDP' # Role: Clean up remains prior to exit. function waCleanFreeRDP() { for proc_file in "${APPDATA_PATH}"/FreeRDP_Process_*.cproc; do # Protect against glob non-expansion. [[ -f "$proc_file" ]] || break # Extract the file name from the path. cproc=$(basename "$proc_file") # Remove the 'FreeRDP_Process_' prefix. cproc="${cproc#FreeRDP_Process_}" # Remove the '.cproc' file extension. cproc="${cproc%.cproc}" if [[ "$cproc" =~ ^[0-9]+$ ]]; then # Proceed if process dir missing OR comm unreadable OR name doesn’t match. if [[ ! -d "/proc/$cproc" ]] || [[ ! -r "/proc/$cproc/comm" ]] || [[ $(<"/proc/$cproc/comm") != *freerdp* ]]; then # Delete the file. rm -- "$proc_file" &>/dev/null fi elif [[ "$cproc" == "Flatpak" ]]; then if command -v flatpak > /dev/null 2>&1; then # Proceed if the flatpak is not running. if ! flatpak ps --columns=application | grep -q "^com.freerdp.FreeRDP$"; then # Delete the file. rm -- "$proc_file" &>/dev/null fi fi fi done } # Name: 'waKillFreeRDP' # Role: Kill WinApps FreeRDP sessions. function waKillFreeRDP() { # Declare variables. local TERMINATED_PROCESS_IDS=() # Loop through each matching file and add to the array. for FREERDP_PROCESS_FILE in "${APPDATA_PATH}/FreeRDP_Process_"*.cproc; do # Ensure the pattern is not treated as a literal string if no files match. [[ -f "$FREERDP_PROCESS_FILE" ]] || break # Extract the file name from the path. FREERDP_PROCESS_FILE=$(basename "$FREERDP_PROCESS_FILE") # Remove the 'FreeRDP_Process_' prefix. FREERDP_PROCESS_FILE="${FREERDP_PROCESS_FILE#FreeRDP_Process_}" # Remove the '.cproc' file extension. FREERDP_PROCESS_FILE="${FREERDP_PROCESS_FILE%.cproc}" # Track termination action. KILLED=false # Terminate processes. if [[ "$FREERDP_PROCESS_FILE" =~ ^[0-9]+$ ]] && \ [[ -d "/proc/$FREERDP_PROCESS_FILE" ]] && \ [[ -r "/proc/$FREERDP_PROCESS_FILE/comm" ]] && \ [[ $(<"/proc/$FREERDP_PROCESS_FILE/comm") == *freerdp* ]]; then # SIGTERM kill -15 "$FREERDP_PROCESS_FILE" &>/dev/null # Wait up to 5 seconds. for _ in {1..5}; do sleep 1 [[ ! -d "/proc/$FREERDP_PROCESS_FILE" ]] && break done # SIGKILL if [[ -d "/proc/$FREERDP_PROCESS_FILE" ]] && \ [[ -r "/proc/$FREERDP_PROCESS_FILE/comm" ]] && \ [[ $(<"/proc/$FREERDP_PROCESS_FILE/comm") == *freerdp* ]]; then kill -9 "$FREERDP_PROCESS_FILE" &>/dev/null fi # Track termination action. KILLED=true elif [[ "$FREERDP_PROCESS_FILE" == "Flatpak" ]] && \ command -v flatpak >/dev/null 2>&1 && \ flatpak ps --columns=application | grep -q "^com.freerdp.FreeRDP$"; then # Terminate the process. flatpak kill com.freerdp.FreeRDP &>/dev/null # Track termination action. KILLED=true fi # Delete FreeRDP process tracking file. # NOTE: Better practice to call 'waCleanFreeRDP' to handle this in case FreeRDP process(es) still not killed. #rm -- "${APPDATA_PATH}/FreeRDP_Process_${FREERDP_PROCESS_FILE}.cproc" &>/dev/null if [[ "$KILLED" == true ]]; then # Add the process ID to the list of terminated processes. TERMINATED_PROCESS_IDS+=("$FREERDP_PROCESS_FILE") fi done # Display feedback if any processes were terminated. if [ ${#TERMINATED_PROCESS_IDS[@]} -ne 0 ]; then dprint "KILLED FREERDP PROCESSES: $( IFS=', ' printf '%s' "${TERMINATED_PROCESS_IDS[*]}" )." echo "Killed FreeRDP process(es):" printf '%s\n' "${TERMINATED_PROCESS_IDS[@]}" fi } # Name: 'waKillCleanExit' # Role: Kill FreeRDP processes and clean process tracking files when WinApps is forcefully terminated. function waKillCleanExit() { # Kill FreeRDP processes. waKillFreeRDP # Clean orphaned files. waCleanFreeRDP # Terminate script. exit 1 } # Name: 'waEarlyDispatch' # Role: Handle lightweight subcommands. function waEarlyDispatch() { if [[ -z "${1:-}" ]] || [[ "$1" == "help" ]]; then waHelp exit 0 elif [[ "$1" == "killrdp" ]]; then waKillFreeRDP waCleanFreeRDP exit 0 elif [[ "$1" == "cleanrdp" ]]; then waCleanFreeRDP exit 0 fi } # Name: 'waThrowExit' # Role: Throw an error message and exit the script. function waThrowExit() { # Declare variables. local ERR_CODE="$1" # Throw error. case "$ERR_CODE" in "$EC_MISSING_CONFIG") # Missing WinApps configuration file. dprint "ERROR: MISSING WINAPPS CONFIGURATION FILE. EXITING." notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "The WinApps configuration file is missing.\nPlease create a WinApps configuration file at '${CONFIG_PATH}'." ;; "$EC_MISSING_FREERDP") dprint "ERROR: FREERDP VERSION 3 IS NOT INSTALLED. EXITING." notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "FreeRDP version 3 is not installed." ;; "$EC_NOT_IN_GROUP") dprint "ERROR: USER NOT PART OF REQUIRED GROUPS. EXITING." notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "The user $(whoami) is not part of the required groups. Please run: sudo usermod -a -G libvirt $(whoami) sudo usermod -a -G kvm $(whoami)" ;; "$EC_FAIL_START") dprint "ERROR: WINDOWS FAILED TO START. EXITING." notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Windows failed to start." ;; "$EC_FAIL_RESUME") dprint "ERROR: WINDOWS FAILED TO RESUME. EXITING." notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Windows failed to resume." ;; "$EC_FAIL_DESTROY") dprint "ERROR: FAILED TO FORCE STOP WINDOWS. EXITING." notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Failed to forcibly stop Windows." ;; "$EC_SD_TIMEOUT") dprint "ERROR: WINDOWS TOOK TOO LONG TO SHUT DOWN. EXITING." notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Windows took too long to shut down." ;; "$EC_DIE_TIMEOUT") dprint "ERROR: WINDOWS TOOK TOO LONG TO DIE. EXITING." notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Windows took too long to die." ;; "$EC_RESTART_TIMEOUT") dprint "ERROR: WINDOWS TOOK TOO LONG TO RESTART. EXITING." notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Windows took too long to restart." ;; "$EC_NOT_EXIST") dprint "ERROR: WINDOWS NONEXISTENT. EXITING." notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Windows VM named '${VM_NAME}' does not exist." ;; "$EC_UNKNOWN") dprint "ERROR: UNKNOWN CONTAINER ERROR. EXITING." notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Unknown Windows container error." ;; "$EC_NO_IP") dprint "ERROR: WINDOWS UNREACHABLE. EXITING." notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Windows is unreachable.\nPlease ensure Windows is assigned an IP address." ;; "$EC_BAD_PORT") dprint "ERROR: RDP PORT CLOSED. EXITING." notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "The Windows RDP port '${RDP_PORT}' is closed.\nPlease ensure Remote Desktop is correctly configured on Windows." ;; "$EC_UNSUPPORTED_APP") dprint "ERROR: APPLICATION NOT FOUND. EXITING." notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Application not found.\nPlease ensure the program is correctly configured as an officially supported application." ;; "$EC_INVALID_FLAVOR") dprint "ERROR: INVALID FLAVOR. EXITING." notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Invalid WinApps flavor.\nPlease ensure 'docker', 'podman' or 'libvirt' are specified as the flavor in the WinApps configuration file." ;; esac # Terminate the script. exit "$ERR_CODE" } # Name: 'dprint' # Role: Conditionally print debug messages to a log file, creating it if it does not exist. function dprint() { [ "$DEBUG" = "true" ] && echo "[$(date)-$RUNID] $1" >>"$LOG_PATH" } # Name: 'waFixRemovableMedia' # Role: If REMOVABLE_MEDIA is empty, default to /run/media (udisks default) and show a warning. function waFixRemovableMedia() { if [ -z "$REMOVABLE_MEDIA" ]; then REMOVABLE_MEDIA="/run/media" # Default for udisks dprint "NOTICE: Using default REMOVABLE_MEDIA: $REMOVABLE_MEDIA" notify-send --expire-time=3000 --icon="drive-removable-media" \ "WinApps Notice" "Using default removable media path: $REMOVABLE_MEDIA" fi } # Name: 'waHelp' # Role: Print usage information. function waHelp() { local script_name script_name="$(basename "$0")" echo "Usage:" echo " ${script_name} help" echo " ${script_name} windows" echo " ${script_name} manual " echo " ${script_name} [file]" echo " ${script_name} killrdp" echo " ${script_name} cleanrdp" echo echo "Commands:" echo " help --> Show this help message." echo " windows --> Start a full Windows desktop RDP session." echo " manual --> Start a RemoteApp session for an arbitrary executable." echo " [file] --> Start a community-tested application using a preconfigured definition. Optionally open a file." echo " killrdp --> Terminate any running WinApps FreeRDP sessions." echo " cleanrdp --> Remove orphaned FreeRDP process tracking files." } # Name: 'waFixScale' # Role: Since FreeRDP only supports '/scale' values of 100, 140 or 180, find the closest supported argument to the user's configuration. function waFixScale() { # Define variables. local OLD_SCALE=100 local VALID_SCALE_1=100 local VALID_SCALE_2=140 local VALID_SCALE_3=180 # Check for an unsupported value. if [ "$RDP_SCALE" != "$VALID_SCALE_1" ] && [ "$RDP_SCALE" != "$VALID_SCALE_2" ] && [ "$RDP_SCALE" != "$VALID_SCALE_3" ]; then # Save the unsupported scale. OLD_SCALE="$RDP_SCALE" # Calculate the absolute differences. local DIFF_1=$(( RDP_SCALE > VALID_SCALE_1 ? RDP_SCALE - VALID_SCALE_1 : VALID_SCALE_1 - RDP_SCALE )) local DIFF_2=$(( RDP_SCALE > VALID_SCALE_2 ? RDP_SCALE - VALID_SCALE_2 : VALID_SCALE_2 - RDP_SCALE )) local DIFF_3=$(( RDP_SCALE > VALID_SCALE_3 ? RDP_SCALE - VALID_SCALE_3 : VALID_SCALE_3 - RDP_SCALE )) # Set the final scale to the valid scale value with the smallest absolute difference. if (( DIFF_1 <= DIFF_2 && DIFF_1 <= DIFF_3 )); then RDP_SCALE="$VALID_SCALE_1" elif (( DIFF_2 <= DIFF_1 && DIFF_2 <= DIFF_3 )); then RDP_SCALE="$VALID_SCALE_2" else RDP_SCALE="$VALID_SCALE_3" fi # Print feedback. dprint "WARNING: Unsupported RDP_SCALE value '${OLD_SCALE}'. Defaulting to '${RDP_SCALE}'." notify-send --expire-time=4000 --icon="dialog-warning" --app-name="WinApps" --urgency="low" "WinApps" "Unsupported RDP_SCALE value '${OLD_SCALE}'.\nDefaulting to '${RDP_SCALE}'." fi } # Name: 'waLoadConfig' # Role: Load the variables within the WinApps configuration file. function waLoadConfig() { # Load WinApps configuration file. if [ -f "$CONFIG_PATH" ]; then # shellcheck source=/dev/null # Exclude WinApps configuration file from being checked by ShellCheck. source "$CONFIG_PATH" else waThrowExit $EC_MISSING_CONFIG fi # Update $RDP_SCALE. waFixScale # Update when $REMOVABLE_MEDIA is null waFixRemovableMedia # Update $AUTOPAUSE_TIME. # RemoteApp RDP sessions take, at minimum, 20 seconds to be terminated by the Windows server. # Hence, subtract 20 from the timeout specified by the user, as a 'built in' timeout of 20 seconds will occur. # Source: https://techcommunity.microsoft.com/t5/security-compliance-and-identity/terminal-services-remoteapp-8482-session-termination-logic/ba-p/246566 AUTOPAUSE_TIME=$((AUTOPAUSE_TIME - 20)) AUTOPAUSE_TIME=$((AUTOPAUSE_TIME < 0 ? 0 : AUTOPAUSE_TIME)) } # Name: 'waLastRun' # Role: Determine the last time this script was run. function waLastRun() { # Declare variables. local LAST_RUN_UNIX_TIME=0 local CURR_RUN_UNIX_TIME=0 # Store the time this script was run last as a unix timestamp. if [ -f "$LASTRUN_PATH" ]; then LAST_RUN_UNIX_TIME=$(stat -t -c %Y "$LASTRUN_PATH") dprint "LAST_RUN: ${LAST_RUN_UNIX_TIME}" fi # Update the file modification time with the current time. touch -- "$LASTRUN_PATH" CURR_RUN_UNIX_TIME=$(stat -t -c %Y "$LASTRUN_PATH") dprint "THIS_RUN: ${CURR_RUN_UNIX_TIME}" } # Name: 'waGetFreeRDPCommand' # Role: Determine the correct FreeRDP command to use. function waGetFreeRDPCommand() { # Declare variables. local FREERDP_MAJOR_VERSION="" # Stores the major version of the installed copy of FreeRDP. # Attempt to set a FreeRDP command if the command variable is empty. if [ -z "$FREERDP_COMMAND" ]; then # Check for 'xfreerdp'. if command -v xfreerdp &>/dev/null; then # Check FreeRDP major version is 3 or greater. FREERDP_MAJOR_VERSION=$(xfreerdp --version | head -n 1 | grep -o -m 1 '\b[0-9]\S*' | head -n 1 | cut -d'.' -f1) if [[ $FREERDP_MAJOR_VERSION =~ ^[0-9]+$ ]] && ((FREERDP_MAJOR_VERSION >= 3)); then FREERDP_COMMAND="xfreerdp" fi fi # Check for 'xfreerdp3' command as a fallback option. if [ -z "$FREERDP_COMMAND" ]; then if command -v xfreerdp3 &>/dev/null; then # Check FreeRDP major version is 3 or greater. FREERDP_MAJOR_VERSION=$(xfreerdp3 --version | head -n 1 | grep -o -m 1 '\b[0-9]\S*' | head -n 1 | cut -d'.' -f1) if [[ $FREERDP_MAJOR_VERSION =~ ^[0-9]+$ ]] && ((FREERDP_MAJOR_VERSION >= 3)); then FREERDP_COMMAND="xfreerdp3" fi fi fi # Check for FreeRDP Flatpak (fallback option). if [ -z "$FREERDP_COMMAND" ]; then if command -v flatpak &>/dev/null; then if flatpak list --columns=application | grep -q "^com.freerdp.FreeRDP$"; then # Check FreeRDP major version is 3 or greater. FREERDP_MAJOR_VERSION=$(flatpak list --columns=application,version | grep "^com.freerdp.FreeRDP" | awk '{print $2}' | cut -d'.' -f1) if [[ $FREERDP_MAJOR_VERSION =~ ^[0-9]+$ ]] && ((FREERDP_MAJOR_VERSION >= 3)); then FREERDP_COMMAND="flatpak run --command=xfreerdp com.freerdp.FreeRDP" RDP_FLATPAK=1 fi fi fi fi fi if command -v "$FREERDP_COMMAND" &>/dev/null || [ "$FREERDP_COMMAND" = "flatpak run --command=xfreerdp com.freerdp.FreeRDP" ]; then dprint "Using FreeRDP command '${FREERDP_COMMAND}'." # Append additional flags or parameters to FreeRDP. # These additional flags are loaded prior in 'waLoadConfig'. [[ -n $RDP_FLAGS ]] && FREERDP_COMMAND="${FREERDP_COMMAND} ${RDP_FLAGS}" else waThrowExit "$EC_MISSING_FREERDP" fi } # Name: 'waCheckGroupMembership' # Role: Ensures the current user is part of the required groups. function waCheckGroupMembership() { # Identify groups the current user belongs to. # shellcheck disable=SC2155 # Silence warnings regarding masking return values through simultaneous declaration and assignment. local USER_GROUPS=$(id -nG "$(whoami)") if ! echo "$USER_GROUPS" | grep -qE '\b(libvirt|libvirtd)\b' || \ ! echo "$USER_GROUPS" | grep -qE '\bkvm\b'; then waThrowExit "$EC_NOT_IN_GROUP" fi } # Name: 'waCheckVMRunning' # Role: Check if the Windows 'libvirt' VM is running, and attempt to start it if it is not. function waCheckVMRunning() { # Declare exit status variable. local EXIT_STATUS=0 # Declare timer variables. local TIME_ELAPSED=0 local TIME_LIMIT=60 local TIME_INTERVAL=5 # Attempt to run the Windows virtual machine. # Note: States 'running' and 'idle' do not require intervention, and are not checked for. if (virsh list --all --name | grep -Fxq -- "$VM_NAME"); then if (virsh list --state-shutoff --name | grep -Fxq -- "$VM_NAME"); then dprint "WINDOWS SHUT OFF. BOOTING WINDOWS." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Booting Windows." NEEDED_BOOT=true virsh start "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_START if (virsh list --state-paused --name | grep -Fxq -- "$VM_NAME"); then dprint "WINDOWS PAUSED. RESUMING WINDOWS." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Resuming Windows." virsh resume "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_RESUME fi elif (virsh list --state-paused --name | grep -Fxq -- "$VM_NAME"); then dprint "WINDOWS PAUSED. RESUMING WINDOWS." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Resuming Windows." virsh resume "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_RESUME elif (virsh list --state-other --name | grep -Fxq -- "$VM_NAME"); then if (virsh domstate "$VM_NAME" | grep -Fxq "in shutdown"); then dprint "WINDOWS SHUTTING DOWN. WAITING." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Windows is currently shutting down.\nIt will automatically restart once the shutdown process is complete." EXIT_STATUS=$EC_SD_TIMEOUT while (( TIME_ELAPSED < TIME_LIMIT )); do if (virsh list --state-shutoff --name | grep -Fxq -- "$VM_NAME"); then EXIT_STATUS=0 dprint "WINDOWS SHUT OFF. BOOTING WINDOWS." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Booting Windows." virsh start "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_START NEEDED_BOOT=true break fi sleep $TIME_INTERVAL TIME_ELAPSED=$((TIME_ELAPSED + TIME_INTERVAL)) done elif (virsh domstate "$VM_NAME" | grep -Fxq "crashed"); then dprint "WINDOWS CRASHED. DESTROYING WINDOWS." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Windows experienced an unexpected crash.\nAttempting to restart Windows." virsh destroy "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_DESTROY if [ "$EXIT_STATUS" -eq 0 ]; then dprint "WINDOWS DESTROYED. BOOTING WINDOWS." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Booting Windows." virsh start "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_START NEEDED_BOOT=true fi elif (virsh domstate "$VM_NAME" | grep -Fxq "dying"); then dprint "WINDOWS DYING. WAITING." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Windows is currently shutting down unexpectedly.\nIt will try to restart once the shutdown process finishes." EXIT_STATUS=$EC_DIE_TIMEOUT while (( TIME_ELAPSED < TIME_LIMIT )); do if (virsh domstate "$VM_NAME" | grep -Fxq "crashed"); then EXIT_STATUS=0 dprint "WINDOWS CRASHED. DESTROYING WINDOWS." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Windows experienced an unexpected crash.\nAttempting to restart Windows." virsh destroy "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_DESTROY if [ "$EXIT_STATUS" -eq 0 ]; then dprint "WINDOWS DESTROYED. BOOTING WINDOWS." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Booting Windows." virsh start "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_START NEEDED_BOOT=true fi break elif (virsh list --state-shutoff --name | grep -Fxq -- "$VM_NAME"); then EXIT_STATUS=0 dprint "WINDOWS SHUT OFF. BOOTING WINDOWS." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Booting Windows." virsh start "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_START NEEDED_BOOT=true break fi sleep $TIME_INTERVAL TIME_ELAPSED=$((TIME_ELAPSED + TIME_INTERVAL)) done elif (virsh domstate "$VM_NAME" | grep -Fxq "pmsuspended" ); then dprint "WINDOWS SUSPENDED. RESUMING WINDOWS." virsh resume "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_RESUME fi fi else EXIT_STATUS=$EC_NOT_EXIST fi # Handle non-zero exit statuses. [ "$EXIT_STATUS" -ne 0 ] && waThrowExit "$EXIT_STATUS" # Wait for VM to be fully ready if [[ "$NEEDED_BOOT" == "true" ]]; then dprint "WAITING FOR VM TO BE FULLY READY..." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Waiting for Windows to be ready..." TIME_ELAPSED=0 while (( TIME_ELAPSED < BOOT_TIMEOUT )); do # libvirt users need to find the IP address dynamically if [[ -z "$RDP_IP" ]]; then RDP_IP=$(waFindVMIP) fi # Check if VM is running if (virsh list --state-running --name | grep -Fxq -- "$VM_NAME"); then # Try to connect to RDP port to verify it's ready if timeout 1 bash -c ">/dev/tcp/$RDP_IP/$RDP_PORT" 2>/dev/null; then dprint "VM IS READY" notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Windows is ready." # Add a delay after Windows is ready if [ "$NEEDED_BOOT" = "true" ]; then sleep 10 fi break fi fi sleep 5 TIME_ELAPSED=$((TIME_ELAPSED + 5)) # Show progress every 30 seconds if (( TIME_ELAPSED % 30 == 0 )); then notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Still waiting for Windows to be ready... ($TIME_ELAPSED seconds elapsed)" fi done # If we timed out waiting for the VM if (( TIME_ELAPSED >= BOOT_TIMEOUT )); then dprint "TIMEOUT WAITING FOR VM TO BE READY" notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Timeout waiting for Windows to be ready. Please try again." waThrowExit $EC_FAIL_START fi fi } # Name: 'waCheckContainerRunning' # Role: Throw an error if the Docker container is not running. function waCheckContainerRunning() { # Declare variables. local EXIT_STATUS=0 local CONTAINER_STATE="" local COMPOSE_COMMAND="" local TIME_ELAPSED=0 local TIME_LIMIT=60 local TIME_INTERVAL=5 # Determine the state of the container. CONTAINER_STATE=$("$WAFLAVOR" inspect --format='{{.State.Status}}' "$CONTAINER_NAME") # Determine the compose command. case "$WAFLAVOR" in "docker") COMPOSE_COMMAND="docker compose" ;; "podman") COMPOSE_COMMAND="podman-compose" ;; esac # Check container state. # Note: Errors DO NOT result in non-zero exit statuses. # Docker: 'created', 'restarting', 'running', 'removing', 'paused', 'exited' or 'dead'. # Podman: 'created', 'running', 'paused', 'exited' or 'unknown'. case "$CONTAINER_STATE" in "created") dprint "WINDOWS CREATED. BOOTING WINDOWS." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Booting Windows." $COMPOSE_COMMAND --file "$COMPOSE_PATH" start &>/dev/null NEEDED_BOOT=true ;; "restarting") dprint "WINDOWS RESTARTING. WAITING." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Windows is currently restarting. Please wait." EXIT_STATUS=$EC_RESTART_TIMEOUT while (( TIME_ELAPSED < TIME_LIMIT )); do if [[ $("$WAFLAVOR" inspect --format='{{.State.Status}}' "$CONTAINER_NAME") == "running" ]]; then EXIT_STATUS=0 dprint "WINDOWS RESTARTED." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Restarted Windows." NEEDED_BOOT=true break fi sleep $TIME_INTERVAL TIME_ELAPSED=$((TIME_ELAPSED + TIME_INTERVAL)) done ;; "paused") dprint "WINDOWS PAUSED. RESUMING WINDOWS." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Resuming Windows." $COMPOSE_COMMAND --file "$COMPOSE_PATH" unpause &>/dev/null ;; "exited") dprint "WINDOWS SHUT OFF. BOOTING WINDOWS." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Booting Windows." $COMPOSE_COMMAND --file "$COMPOSE_PATH" start &>/dev/null NEEDED_BOOT=true ;; "dead") dprint "WINDOWS DEAD. RECREATING WINDOWS CONTAINER." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Re-creating and booting Windows." $COMPOSE_COMMAND --file "$COMPOSE_PATH" down &>/dev/null && $COMPOSE_COMMAND --file "$COMPOSE_PATH" up -d &>/dev/null NEEDED_BOOT=true ;; "unknown") EXIT_STATUS=$EC_UNKNOWN ;; esac # Handle non-zero exit statuses. [ "$EXIT_STATUS" -ne 0 ] && waThrowExit "$EXIT_STATUS" # Wait for container to be fully ready if [[ "$CONTAINER_STATE" == "created" || "$CONTAINER_STATE" == "exited" || "$CONTAINER_STATE" == "dead" || "$CONTAINER_STATE" == "restarting" ]]; then dprint "WAITING FOR CONTAINER TO BE FULLY READY..." notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Waiting for Windows to be ready..." TIME_ELAPSED=0 while (( TIME_ELAPSED < BOOT_TIMEOUT )); do # Check if container is running if [[ $("$WAFLAVOR" inspect --format='{{.State.Status}}' "$CONTAINER_NAME") == "running" ]]; then # Try to connect to RDP port to verify it's ready if timeout 1 bash -c ">/dev/tcp/$RDP_IP/$RDP_PORT" 2>/dev/null; then dprint "CONTAINER IS READY" notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Windows is ready." # Add a delay after Windows is ready if [ "$NEEDED_BOOT" = "true" ]; then sleep 10 fi break fi fi sleep 5 TIME_ELAPSED=$((TIME_ELAPSED + 5)) # Show progress every 30 seconds if (( TIME_ELAPSED % 30 == 0 )); then notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Still waiting for Windows to be ready... ($TIME_ELAPSED seconds elapsed)" fi done # If we timed out waiting for the container if (( TIME_ELAPSED >= BOOT_TIMEOUT )); then dprint "TIMEOUT WAITING FOR CONTAINER TO BE READY" notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Timeout waiting for Windows to be ready. Please try again." waThrowExit $EC_FAIL_START fi fi } # Name: 'waCheckPortOpen' # Role: Assesses whether the RDP port on Windows is open. function waCheckPortOpen() { # Declare variables. local TIME_ELAPSED=0 local TIME_LIMIT=30 local TIME_INTERVAL=5 # Obtain Windows VM IP Address ('libvirt' ONLY) # Note: 'RDP_IP' should not be empty if 'WAFLAVOR' is 'docker', since it is set to localhost before this function is called. if [ -z "$RDP_IP" ] && [ "$WAFLAVOR" = "libvirt" ]; then while (( TIME_ELAPSED < TIME_LIMIT )); do if [ "$TIME_ELAPSED" -eq "$TIME_INTERVAL" ]; then notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Requesting Windows IP address..." fi RDP_IP=$(waFindVMIP) [ -n "$RDP_IP" ] && break sleep $TIME_INTERVAL TIME_ELAPSED=$((TIME_ELAPSED + TIME_INTERVAL)) done [ -z "$RDP_IP" ] && waThrowExit "$EC_NO_IP" fi # Check for an open RDP port. timeout 10 nc -z "$RDP_IP" "$RDP_PORT" &>/dev/null || waThrowExit "$EC_BAD_PORT" } # Name: 'waRunCommand' # Role: Run the requested WinApps command. function waRunCommand() { # Declare variables. local ICON="" local FILE_PATH="" # Run option. if [ "$1" = "windows" ]; then # Update timeout (since there is no 'in-built' 20 second delay for full RDP sessions post-logout). AUTOPAUSE_TIME=$((AUTOPAUSE_TIME + 20)) # Open Windows RDP session. dprint "WINDOWS" # shellcheck disable=SC2086 #Disable warning for globbing and word splitting $FREERDP_COMMAND \ $RDP_FLAGS_WINDOWS \ /d:"$RDP_DOMAIN" \ /u:"$RDP_USER" \ ${RDP_PASSWORD_ARG:+"$RDP_PASSWORD_ARG"} \ /scale:"$RDP_SCALE" \ +auto-reconnect \ +dynamic-resolution \ /wm-class:"Microsoft Windows" \ /t:"Windows RDP Session [$RDP_IP]" \ /v:"$RDP_IP" &>/dev/null & # Capture the process ID. FREERDP_PID=$! elif [ "$1" = "manual" ]; then # Open specified application. dprint "MANUAL: ${2}" # shellcheck disable=SC2086 #Disable warning for globbing and word splitting $FREERDP_COMMAND \ $RDP_FLAGS_NON_WINDOWS \ /d:"$RDP_DOMAIN" \ /u:"$RDP_USER" \ ${RDP_PASSWORD_ARG:+"$RDP_PASSWORD_ARG"} \ /scale:"$RDP_SCALE" \ +auto-reconnect \ /app:program:"$2",hidef:"$HIDEF" \ /v:"$RDP_IP" &>/dev/null & # Capture the process ID. FREERDP_PID=$! else # Script summoned from right-click menu or application icon (plus/minus a file path). if [ -e "${SCRIPT_DIR_PATH}/../apps/${1}/info" ]; then # shellcheck source=/dev/null # Exclude this file from being checked by ShellCheck. source "${SCRIPT_DIR_PATH}/../apps/${1}/info" ICON="${SCRIPT_DIR_PATH}/../apps/${1}/icon.svg" elif [ -e "${APPDATA_PATH}/apps/${1}/info" ]; then # shellcheck source=/dev/null # Exclude this file from being checked by ShellCheck. source "${APPDATA_PATH}/apps/${1}/info" ICON="${APPDATA_PATH}/apps/${1}/icon.svg" elif [ -e "${SYS_APP_PATH}/apps/${1}/info" ]; then # shellcheck source=/dev/null # Exclude this file from being checked by ShellCheck. source "${SYS_APP_PATH}/apps/${1}/info" ICON="${SYS_APP_PATH}/apps/${1}/icon.svg" else waThrowExit "$EC_UNSUPPORTED_APP" fi # Check if a file path was specified, and pass this to the application. if [ -z "$2" ]; then # No file path specified. # shellcheck disable=SC2086 #Disable warning for globbing and word splitting $FREERDP_COMMAND \ $RDP_FLAGS_NON_WINDOWS \ /d:"$RDP_DOMAIN" \ /u:"$RDP_USER" \ ${RDP_PASSWORD_ARG:+"$RDP_PASSWORD_ARG"} \ /scale:"$RDP_SCALE" \ +auto-reconnect \ /wm-class:"$FULL_NAME" \ /app:program:"$WIN_EXECUTABLE",hidef:"$HIDEF",icon:"$ICON",name:"$FULL_NAME" \ /v:"$RDP_IP" &>/dev/null & # Capture the process ID. FREERDP_PID=$! else # Convert path from UNIX to Windows style. FILE_PATH=$(echo "$2" | sed \ -e 's|^'"${HOME}"'|\\\\tsclient\\home|' \ -e 's|^'"${REMOVABLE_MEDIA}"'|\\\\tsclient\\media|' \ -e 's|/|\\|g') dprint "UNIX_FILE_PATH: ${2}" dprint "WINDOWS_FILE_PATH: ${FILE_PATH}" # shellcheck disable=SC2086 #Disable warning for globbing and word splitting $FREERDP_COMMAND \ $RDP_FLAGS_NON_WINDOWS \ /d:"$RDP_DOMAIN" \ /u:"$RDP_USER" \ ${RDP_PASSWORD_ARG:+"$RDP_PASSWORD_ARG"} \ /scale:"$RDP_SCALE" \ +auto-reconnect \ /drive:media,"$REMOVABLE_MEDIA" \ /wm-class:"$FULL_NAME" \ /app:program:"$WIN_EXECUTABLE",hidef:"$HIDEF",icon:"$ICON",name:"$FULL_NAME",cmd:\""$FILE_PATH"\" \ /v:"$RDP_IP" &>/dev/null & # Capture the process ID. FREERDP_PID=$! fi fi if [[ "$RDP_FLATPAK" == "0" ]]; then if [ "$FREERDP_PID" -ne -1 ]; then # Create a file with the process ID. touch -- "${APPDATA_PATH}/FreeRDP_Process_${FREERDP_PID}.cproc" # Wait for the process to terminate. wait "$FREERDP_PID" # Remove the file with the process ID. rm -- "${APPDATA_PATH}/FreeRDP_Process_${FREERDP_PID}.cproc" &>/dev/null fi else # Create a file. touch -- "${APPDATA_PATH}/FreeRDP_Process_Flatpak.cproc" # Wait for the process to terminate. while flatpak ps --columns=application | grep -q "^com.freerdp.FreeRDP$"; do sleep 5 done # Remove the file. rm -- "${APPDATA_PATH}/FreeRDP_Process_Flatpak.cproc" &>/dev/null fi } # Name: 'waCheckIdle' # Role: Suspend Windows if idle. function waCheckIdle() { # Declare variables local TIME_INTERVAL=10 local TIME_ELAPSED=0 local SUSPEND_WINDOWS=0 # Prevent 'autopause' functionality with unsupported Windows backends. if [ "$WAFLAVOR" != "manual" ]; then # Check if there are no WinApps-related FreeRDP processes running. if ! ls "$APPDATA_PATH"/FreeRDP_Process_*.cproc &>/dev/null; then SUSPEND_WINDOWS=1 while (( TIME_ELAPSED < AUTOPAUSE_TIME )); do if ls "$APPDATA_PATH"/FreeRDP_Process_*.cproc &>/dev/null; then SUSPEND_WINDOWS=0 break fi sleep $TIME_INTERVAL TIME_ELAPSED=$((TIME_ELAPSED + TIME_INTERVAL)) done fi # Hibernate/Pause Windows. if [ "$SUSPEND_WINDOWS" -eq 1 ]; then dprint "IDLE FOR ${AUTOPAUSE_TIME} SECONDS. SUSPENDING WINDOWS." notify-send --expire-time=8000 --icon="info" --app-name="WinApps" --urgency="low" "WinApps" "Pausing Windows due to inactivity." if [ "$WAFLAVOR" = "docker" ]; then docker compose --file "$COMPOSE_PATH" pause &>/dev/null elif [ "$WAFLAVOR" = "podman" ]; then podman-compose --file "$COMPOSE_PATH" pause &>/dev/null elif [ "$WAFLAVOR" = "libvirt" ]; then virsh suspend "$VM_NAME" &>/dev/null fi fi fi } # Name: 'waTimeSync' # Role: Detect if system went to sleep by comparing uptime progression, then sync time in Windows VM function waTimeSync() { local CURRENT_TIME local CURRENT_UPTIME local STORED_TIME=0 local STORED_UPTIME=0 local EXPECTED_UPTIME=0 local UPTIME_DIFF=0 CURRENT_TIME=$(date +%s) CURRENT_UPTIME=$(awk '{print int($1)}' /proc/uptime) # Read stored values if file exists if [ -f "$SLEEP_DETECT_PATH" ]; then STORED_TIME=$(head -n1 "$SLEEP_DETECT_PATH" 2>/dev/null || echo 0) STORED_UPTIME=$(tail -n1 "$SLEEP_DETECT_PATH" 2>/dev/null || echo 0) fi if [ "$STORED_TIME" -gt 0 ] && [ "$STORED_UPTIME" -gt 0 ]; then # Calculate what uptime should be now EXPECTED_UPTIME=$((STORED_UPTIME + CURRENT_TIME - STORED_TIME)) UPTIME_DIFF=$((EXPECTED_UPTIME - CURRENT_UPTIME)) dprint "UPTIME_DIFF: ${UPTIME_DIFF} seconds" # If uptime is significantly less than expected, system likely slept if [[ "$UPTIME_DIFF" -gt 30 && ! -f "$SLEEP_MARKER" ]]; then dprint "DETECTED SLEEP/WAKE CYCLE (uptime gap: ${UPTIME_DIFF}s). CREATING SLEEP MARKER TO SYNC WINDOWS TIME." # Create sleep marker which will be monitored by Windows VM to trigger time sync touch -- "$SLEEP_MARKER" dprint "CREATED SLEEP MARKER" fi fi # Store current values { echo "$CURRENT_TIME" echo "$CURRENT_UPTIME" } > "$SLEEP_DETECT_PATH" } function waFindVMIP() { local VM_MAC="" VM_MAC=$(virsh domiflist "$VM_NAME" | grep -oE "([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})") # VM MAC address. # VM IP address ip neigh show | grep -F -- "$VM_MAC" | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}" } ### MAIN LOGIC ### #set -x # Enable for debugging. mkdir -p "$APPDATA_PATH" dprint "START" dprint "SCRIPT_DIR: ${SCRIPT_DIR_PATH}" dprint "SCRIPT_ARGS: ${*}" dprint "HOME_DIR: ${HOME}" waEarlyDispatch "$@" waLastRun waLoadConfig waGetFreeRDPCommand waCleanFreeRDP # Send password on the command line if a command to retrieve the password from is not given # Otherwise, set FREERDP_ASKPASS which freerdp will read the stdout of to use as the password RDP_PASSWORD_ARG="/p:$RDP_PASS" if [[ ! -z "$RDP_ASKPASS" ]]; then export FREERDP_ASKPASS="$RDP_ASKPASS" unset RDP_PASSWORD_ARG fi # If using podman backend, modify the FreeRDP command to enter a new namespace. if [ "$WAFLAVOR" = "podman" ]; then FREERDP_COMMAND="podman unshare --rootless-netns ${FREERDP_COMMAND}" fi if [ "$WAFLAVOR" = "docker" ] || [ "$WAFLAVOR" = "podman" ]; then RDP_IP="$DOCKER_IP" waCheckContainerRunning elif [ "$WAFLAVOR" = "libvirt" ]; then waCheckGroupMembership waCheckVMRunning elif [ "$WAFLAVOR" = "manual" ]; then waCheckPortOpen else waThrowExit "$EC_INVALID_FLAVOR" fi waCheckPortOpen waTimeSync waRunCommand "$@" if [[ "$AUTOPAUSE" == "on" ]]; then waCheckIdle fi dprint "END" ================================================ FILE: compose.yaml ================================================ # For documentation, FAQ, additional configuration options and technical help, visit: https://github.com/dockur/windows name: "winapps" # Docker Compose Project Name. volumes: # Create Volume 'data'. # Located @ '/var/lib/docker/volumes/winapps_data/_data' (Docker). # Located @ '/var/lib/containers/storage/volumes/winapps_data/_data' or '~/.local/share/containers/storage/volumes/winapps_data/_data' (Podman). data: services: windows: image: ghcr.io/dockur/windows:latest container_name: WinApps # Created Docker VM Name. environment: # Version of Windows to configure. For valid options, visit: # https://github.com/dockur/windows?tab=readme-ov-file#how-do-i-select-the-windows-version # https://github.com/dockur/windows?tab=readme-ov-file#how-do-i-install-a-custom-image VERSION: "11" RAM_SIZE: "4G" # RAM allocated to the Windows VM. CPU_CORES: "4" # CPU cores allocated to the Windows VM. DISK_SIZE: "64G" # Size of the primary hard disk. # DISK2_SIZE: "32G" # Uncomment to add an additional hard disk to the Windows VM. Ensure it is mounted as a volume below. USERNAME: "MyWindowsUser" # Edit here to set a custom Windows username. The default is 'MyWindowsUser'. PASSWORD: "MyWindowsPassword" # Edit here to set a password for the Windows user. The default is 'MyWindowsPassword'. HOME: "${HOME}" # Set path to Linux user home folder. ports: - 8006:8006 # Map '8006' on Linux host to '8006' on Windows VM --> For VNC Web Interface @ http://127.0.0.1:8006. - 3389:3389/tcp # Map '3389' on Linux host to '3389' on Windows VM --> For Remote Desktop Protocol (RDP). - 3389:3389/udp # Map '3389' on Linux host to '3389' on Windows VM --> For Remote Desktop Protocol (RDP). cap_add: - NET_ADMIN # Add network permission stop_grace_period: 120s # Wait 120 seconds before sending SIGTERM when attempting to shut down the Windows VM. restart: on-failure # Restart the Windows VM if the exit code indicates an error. volumes: - data:/storage # Mount volume 'data' to use as Windows 'C:' drive. - ${HOME}:/shared # Mount Linux user home directory @ '\\host.lan\Data'. #- /path/to/second/hard/disk:/storage2 # Uncomment to create a virtual second hard disk and mount it within the Windows VM. Ensure 'DISK2_SIZE' is specified above. - ./oem:/oem # Enables automatic post-install execution of 'oem/install.bat', applying Windows registry modifications contained within 'oem/RDPApps.reg'. #- /path/to/windows/install/media.iso:/custom.iso # Uncomment to use a custom Windows ISO. If specified, 'VERSION' (e.g. 'tiny11') will be ignored. devices: - /dev/kvm # Enable KVM. - /dev/net/tun # Enable tuntap # Uncomment to mount a disk directly within the Windows VM. # WARNING: /dev/sdX paths may change after reboot. Use persistent identifiers! # NOTE: 'disk1' will be mounted as the main drive. THIS DISK WILL BE FORMATTED BY DOCKER. # All following disks (disk2, ...) WILL NOT BE FORMATTED. # - /dev/disk/by-id/:/disk1 # - /dev/disk/by-id/:/disk2 # group_add: # uncomment this line and the next one for using rootless podman containers # - keep-groups # to make /dev/kvm work with podman. needs "crun" installed, "runc" will not work! Add your user to the 'kvm' group or another that can access /dev/kvm. ================================================ FILE: default.nix ================================================ (import ( let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in fetchTarball { url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; sha256 = lock.nodes.flake-compat.locked.narHash; } ) { src = ./.; }).defaultNix ================================================ FILE: docs/docker.md ================================================ # Creating a Windows VM in `Docker` or `Podman` Although WinApps supports using `QEMU+KVM+libvirt` as a backend for running Windows virtual machines, it is recommended to use `Docker` or `Podman`. These backends automate the setup process, eliminating the need for manual configuration and optimisation of the Windows virtual machine. > [!IMPORTANT] > Running a Windows virtual machine using `Docker` or `Podman` as a backend is only possible on GNU/Linux systems. This is due to the necessity of kernel interfaces, such as the KVM hypervisor, for achieving acceptable performance. The performance of the virtual machine can vary based on the version of the Linux kernel, with newer releases generally offering better performance. > [!IMPORTANT] > WinApps does __NOT__ officially support versions of Windows prior to Windows 10. Despite this, it may be possible to achieve a successful installation with some additional experimentation. If you find a way to achieve this, please share your solution through a pull request for the benefit of other users. > Possible setup instructions for Windows 10: > - 'Professional', 'Enterprise' or 'Server' editions of Windows are required to run RDP applications. Windows 'Home' will __NOT__ suffice. > - It is recommended to edit the initial `compose.yaml` file to keep your required username and password from the beginning. > - It is recommended to not use `sudo` to force commands to run. Add your user to the relevant permissions group wherever possible. > [!IMPORTANT] > The iptables kernel module must be loaded for folder sharing with the host to work. > Check that the output of `lsmod | grep ip_tables` and `lsmod | grep iptable_nat` is non-empty. > If the output of one of the previous commands is empty, run `echo -e "ip_tables\niptable_nat" | sudo tee /etc/modules-load.d/iptables.conf` and reboot. ## `Docker` ### Installation You can find a guide for installing `Docker Engine` [here](https://docs.docker.com/engine/install/). ### Setup `Docker` Container WinApps utilises `docker compose` to configure Windows VMs. A template [`compose.yaml`](../compose.yaml) is provided. Prior to installing Windows, you can modify the RAM and number of CPU cores available to the Windows VM by changing `RAM_SIZE` and `CPU_CORES` within `compose.yaml`. It is also possible to specify the version of Windows you wish to install within `compose.yaml` by modifying `VERSION`. Please refer to the [original GitHub repository](https://github.com/dockur/windows) for more information on additional configuration options. > [!NOTE] > If you want to undo all your changes and start from scratch, run the following. For `podman`, replace `docker compose` with `podman-compose`. > ```bash > docker compose down --rmi=all --volumes > ``` ### Installing Windows You can initiate the Windows installation using `docker compose`. ```bash cd winapps docker compose --file ./compose.yaml up ``` You can then access the Windows virtual machine via a VNC connection to complete the Windows setup by navigating to http://127.0.0.1:8006 in your web browser. ### Changing `compose.yaml` Changes to `compose.yaml` require the container to be removed and re-created. This should __NOT__ affect your data. ```bash # Stop and remove the existing container. docker compose --file ~/.config/winapps/compose.yaml down # Remove the existing FreeRDP certificate (if required). # Note: A new certificate will be created when connecting via RDP for the first time. rm ~/.config/freerdp/server/127.0.0.1_3389.pem # Re-create the container with the updated configuration. # Add the -d flag at the end to run the container in the background. docker compose --file ~/.config/winapps/compose.yaml up ``` ### Subsequent Use ```bash docker compose --file ~/.config/winapps/compose.yaml start # Power on the Windows VM docker compose --file ~/.config/winapps/compose.yaml pause # Pause the Windows VM docker compose --file ~/.config/winapps/compose.yaml unpause # Resume the Windows VM docker compose --file ~/.config/winapps/compose.yaml restart # Restart the Windows VM docker compose --file ~/.config/winapps/compose.yaml stop # Gracefully shut down the Windows VM docker compose --file ~/.config/winapps/compose.yaml kill # Force shut down the Windows VM ``` ## `Podman` ### Installation 1. Install `Podman` using [this guide](https://podman.io/docs/installation). 2. Install `podman-compose` using [this guide](https://github.com/containers/podman-compose?tab=readme-ov-file#installation). ### Setup `Podman` Container Please follow the [`docker` instructions](#setup-docker-container). > [!NOTE] > #### Rootless `podman` containers > If you are invoking podman as a user, your container will be "rootless". This can be desirable as a security feature. However, you may encounter an error about missing permissions to /dev/kvm as a consequence. > > For rootless podman to work, you need to add your user to the `kvm` group (depending on your distribution) to be able to access `/dev/kvm`. Make sure that you are using `crun` as your container runtime, not `runc`. Usually this is done by stopping all containers and (de-)installing the corresponding packages. Then either invoke podman-compose as `podman-compose --file ./compose.yaml --podman-create-args '--group-add keep-groups' up`. Or edit `compose.yaml` and uncomment the `group_add:` section at the end, and add `[]`. > [!IMPORTANT] > Ensure `WAFLAVOR` is set to `"podman"` in `~/.config/winapps/winapps.conf`. ### Installing Windows You can initiate the Windows installation using `podman-compose`. ```bash cd winapps podman-compose --file ./compose.yaml up ``` You can then access the Windows virtual machine via a VNC connection to complete the Windows setup by navigating to http://127.0.0.1:8006 in your web browser. ### Changing `compose.yaml` Changes to `compose.yaml` require the container to be removed and re-created. This should __NOT__ affect your data. ```bash # Stop and remove the existing container. podman-compose --file ~/.config/winapps/compose.yaml down # Remove the existing FreeRDP certificate (if required). # Note: A new certificate will be created when connecting via RDP for the first time. rm ~/.config/freerdp/server/127.0.0.1_3389.pem # Re-create the container with the updated configuration. podman-compose --file ~/.config/winapps/compose.yaml up ``` ### Subsequent Use ```bash podman-compose --file ~/.config/winapps/compose.yaml start # Power on the Windows VM podman-compose --file ~/.config/winapps/compose.yaml pause # Pause the Windows VM podman-compose --file ~/.config/winapps/compose.yaml unpause # Resume the Windows VM podman-compose --file ~/.config/winapps/compose.yaml restart # Restart the Windows VM podman-compose --file ~/.config/winapps/compose.yaml stop # Gracefully shut down the Windows VM podman-compose --file ~/.config/winapps/compose.yaml kill # Force shut down the Windows VM ``` ================================================ FILE: docs/libvirt.md ================================================ # Creating a `libvirt` Windows VM This method of configuring a Windows virtual machine for use with WinApps is significantly more involved than utilising `Docker` or `Podman`. Nevertheless, expert users may prefer this method due to its greater flexibility and wider range of customisation options (e.g. GPU passthrough).
Understanding The Virtualisation Stack Before beginning, it is important to have a basic understanding of the various components involved in this particular method. 1. `QEMU` is a FOSS emulator that performs hardware virtualisation, enabling operating systems and applications designed for one architecture (e.g., aarch64) to run on systems with differing architectures (e.g., amd64). When used in conjunction with `KVM`, it can run virtual machines at near-native speed (provided the guest virtual machine matches the host architecture) by utilising hardware extensions like Intel VT-x or AMD-V. 2. `KVM` is a Linux kernel module that enables the kernel to function as a type-1 hypervisor. `KVM` runs directly on the underlying hardware (as opposed to on top of the GNU/Linux host OS). For many workloads, the performance overhead is minimal, often in the range of 2-5%. `KVM` requires a CPU with hardware virtualisation extensions. 3. `libvirt` is an open-source API, daemon, and management tool for orchestrating platform virtualisation. It provides a consistent and stable interface for managing various virtualisation technologies, including `KVM` and `QEMU` (as well as others). `libvirt` offers a wide range of functionality to control the lifecycle of virtual machines, storage, networks, and interfaces, making it easier to interact with virtualisation capabilities programmatically or via command-line tools. 4. `virt-manager` (Virtual Machine Manager) is a GUI desktop application that provides an easy-to-use interface for creating, configuring and controlling virtual machines. `virt-manager` utilises `libvirt` as a backend. Together, these components form a powerful and flexible virtualization stack, with `KVM` providing low-level kernel-based virtualisation capabilities, `QEMU` providing high-level userspace-based virtualisation functionality, `libvirt` managing the resources and `virt-manager` offering an intuitive graphical management interface.

## Prerequisites 1. Ensure your CPU supports hardware virtualisation extensions by [reading this article](https://wiki.archlinux.org/title/KVM). 2. Install all dependencies by installing `virt-manager`. This will ensure that your package manager automatically installs all the necessary components. ```bash sudo apt install virt-manager # Debian/Ubuntu sudo dnf install virt-manager # Fedora/RHEL sudo pacman -S virt-manager # Arch Linux sudo emerge app-emulation/virt-manager # Gentoo Linux ``` 3. Configure `libvirt` to use the 'system' URI by adding the line `LIBVIRT_DEFAULT_URI="qemu:///system"` to your preferred shell profile file (e.g., `.bashrc`, `.zshrc`, etc.). ```bash echo 'export LIBVIRT_DEFAULT_URI="qemu:///system"' >> ~/.bashrc ``` > [!NOTE] > WinApps may not read your shell's configuration. If you're having issues getting the installer to detect your VM, try adding > `LIBVIRT_DEFAULT_URI="qemu:///system"` to your `/etc/environment` like: > ```bash > echo 'LIBVIRT_DEFAULT_URI="qemu:///system"' | sudo tee -a /etc/environment > ``` > Thanks to imoize for pointing this out: https://github.com/winapps-org/winapps/issues/310#issuecomment-2505348088 4. Configure rootless `libvirt` and `kvm` by adding your user to groups of the same name. ``` bash sudo usermod -a -G kvm $(id -un) # Add the user to the 'kvm' group. sudo usermod -a -G libvirt $(id -un) # Add the user to the 'libvirt' group. sudo reboot # Reboot the system to ensure the user is added to the relevant groups. ``` Note: On NixOS, the group name for libvirt is `libvirtd` and not `libvirt`. In addition, user and group management on NixOS is handled through the Nix configuration files and not via traditional tools like `usermod`. Please see "Adding User to a group" on [this NixOS Wiki page](https://wiki.nixos.org/wiki/User_management). Note: Due to a known bug in `rpm-ostree`, which affects various distributions such as Silverblue, Bazzite, Bluefin, Kinoite, Aurora, UCore, and others, the commands provided earlier may not properly add your user to all required groups. If the `groups $USER` command does not show your user as being part of the necessary groups, you'll need to manually add these groups to `/etc/group` if they are present in `/usr/lib/group`. To resolve this: 1. Identify which groups are missing from the output of `groups $USER`. 2. Use the following snippet to add each missing group to `/etc/group`. Ensure you replace "kvm" with the name of the missing group. ```bash grep -E '^kvm:' /usr/lib/group | sudo tee -a /etc/group sudo usermod -aG kvm $USER ``` 3. Reboot your system to ensure that the user is correctly added to the relevant groups. 5. If relevant to your distribution, disable `AppArmor` for the `libvirt` daemon. ``` bash sudo ln -s /etc/apparmor.d/usr.sbin.libvirtd /etc/apparmor.d/disable/ # Disable AppArmor for the libvirt daemon by creating a symbolic link. ``` > [!NOTE] > Systems with `SELinux` may also require security policy adjustments if virtual machine images are stored outside the default `/var/lib/libvirt/images` directory. Read [this guide](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/5/html/virtualization/sect-virtualization-security_for_virtualization-selinux_and_virtualization#sect-Virtualization-Security_for_virtualization-SELinux_and_virtualization) for more information. 6. Download a [Windows 10](https://www.microsoft.com/software-download/windows10ISO) or [Windows 11](https://www.microsoft.com/software-download/windows11) installation `.ISO` image. > [!IMPORTANT] > 'Professional', 'Enterprise' or 'Server' editions of Windows are required to run RDP applications. Windows 'Home' will NOT suffice. 7. Download [VirtIO drivers](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/latest-virtio/virtio-win.iso) for the Windows virtual machine. > [!NOTE] > VirtIO drivers enhance system performance and minimize overhead by enabling the Windows virtual machine to use specialised network and disk device drivers. These drivers are aware that they are operating inside a virtual machine, and cooperate with the hypervisor. This approach eliminates the need for the hypervisor to emulate physical hardware devices, which is a computationally expensive process. This setup allows guests to achieve high-performance network and disk operations, leveraging the benefits of paravirtualisation. > The above link contains the latest release of the `VirtIO` drivers for Windows, compiled and signed by Red Hat. Older versions of the `VirtIO` drivers can be downloaded [here](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/?C=M;O=D). > You can read more about `VirtIO` [here](https://wiki.libvirt.org/Virtio.html) and [here](https://developer.ibm.com/articles/l-virtio/). ## Creating a Windows VM 1. Open `virt-manager`. > [!NOTE] > The name given to the application can vary between GNU/Linux distributions (e.g., 'Virtual Machines', 'Virtual Machine Manager', etc.)

2. Navigate to `Edit`→`Preferences`. Ensure `Enable XML editing` is enabled, then click the `Close` button.

3. Create a new virtual machine by clicking the `+` button.

Creating a new virtual machine in 'virt-manager'

4. Choose `Local install media` and click `Forward`.

5. Select the location of your Windows 10 or 11 `.ISO` by clicking `Browse...` and `Browse Local`. Ensure `Automatically detect from the installation media / source` is enabled.

6. Configure the RAM and CPU cores allocated to the Windows virtual machine. We recommend `2` CPUs and `4096MB` of RAM. We will use the `VirtIO` Memory Ballooning service, which means the virtual machine can use up to `4096MB` of memory, but it will only consume this amount if necessary.

7. Configure the virtual disk by setting its maximum size. While this size represents the largest it can grow to, the disk will only use this space as needed.

8. Name your virtual machine `RDPWindows` to ensure it is recognized by WinApps, and select the option to `Customize configuration before installation`.

> [!NOTE] > A name other than `RDPWindows` can be used if `VM_NAME` is set in `~/.config/winapps/winapps.conf`. 9. After clicking `Finish`, select `Copy host CPU configuration` under 'CPUs', and then click `Apply`. > [!NOTE] > Sometimes this feature gets disabled after installing Windows. Make sure to check and re-enable this option after the installation is complete.

10. Navigate to the `XML` tab, and edit the `` section to disable all timers except for the hypervclock, thereby drastically reducing idle CPU usage. Once changed, click `Apply`. ```xml ```

11. Enable Hyper-V enlightenments by adding the following to the `` section. Once changed, click `Apply`. ```xml ``` > [!NOTE] > Hyper-V enlightenments make Windows (and other Hyper-V guests) think they are running on top of a Hyper-V compatible hypervisor. This enables use of Hyper-V specific features, allowing `KVM` to implement paravirtualised interfaces for improved virtual machine performance. 12. Add the following XML snippet within the `` section to enable the GNU/Linux host to communicate with Windows using `QEMU Guest Agent`. ```xml
``` 13. In the 'Memory' section, set the `Current allocation` to the minimum amount of memory you want the virtual machine to use, with a recommended value of `1024MB`. > [!NOTE] > Depending upon certain apps (like OneDrive), the balloon driver cannot keep up. If you are having segfaults (random reboots), turn off memory ballooning. ``

14. (Optional) Under `Boot Options`, enable `Start virtual machine on host boot up`.

15. Navigate to 'SATA Disk 1' and set the `Disk bus` type to `VirtIO`. This allows disk access to be paravirtualised, improving virtual machine performance.

16. Navigate to 'NIC' and set the `Device model` type to `virtio` to enable paravirtualised networking. > [!NOTE] > `virtio` is the recommended device model and should be used for normal operation. If you specifically want internet access during the Windows installation (for example, to sign in with a Microsoft account), you may temporarily set the device model to `e1000e`. After installation and VirtIO driver setup are complete, switch the NIC back to `virtio` for best performance.

17. Click the `Add Hardware` button in the lower left, and choose `Storage`. For `Device type`, select `CDROM device` and choose the VirtIO driver `.ISO` you downloaded earlier. Click `Finish` to add the new CD-ROM device. > [!IMPORTANT] > If you skip this step, the Windows installer will fail to recognise and list the virtual hard drive you created earlier.

(Optional) Assign Specific Physical CPU Cores Assigning specific physical CPU cores to the virtual machine can improve performance by reducing context switching and ensuring that the virtual machine's workload consistently uses the same cores, leading to better CPU cache utilisation. This is an optional step. 1. Run `lscpu -e` to determine which L1, L2 and L3 caches are associated with which CPU cores. Example 1 (Intel 11th Gen Core i7-1185G7): ``` CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE MAXMHZ MINMHZ 0 0 0 0 0:0:0:0 yes 4800.0000 400.0000 1 0 0 1 1:1:1:0 yes 4800.0000 400.0000 2 0 0 2 2:2:2:0 yes 4800.0000 400.0000 3 0 0 3 3:3:3:0 yes 4800.0000 400.0000 4 0 0 0 0:0:0:0 yes 4800.0000 400.0000 5 0 0 1 1:1:1:0 yes 4800.0000 400.0000 6 0 0 2 2:2:2:0 yes 4800.0000 400.0000 7 0 0 3 3:3:3:0 yes 4800.0000 400.0000 ``` - C0 = T0+T4 → L10+L20+L30 - C1 = T1+T5 → L11+L21+L30 - C2 = T2+T6 → L12+L22+L30 - C3 = T3+T7 → L13+L23+L30 Example 2 (AMD Ryzen 5 1600): ``` CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE MAXMHZ MINMHZ 0 0 0 0 0:0:0:0 yes 3800.0000 1550.0000 1 0 0 0 0:0:0:0 yes 3800.0000 1550.0000 2 0 0 1 1:1:1:0 yes 3800.0000 1550.0000 3 0 0 1 1:1:1:0 yes 3800.0000 1550.0000 4 0 0 2 2:2:2:0 yes 3800.0000 1550.0000 5 0 0 2 2:2:2:0 yes 3800.0000 1550.0000 6 0 0 3 3:3:3:1 yes 3800.0000 1550.0000 7 0 0 3 3:3:3:1 yes 3800.0000 1550.0000 8 0 0 4 4:4:4:1 yes 3800.0000 1550.0000 9 0 0 4 4:4:4:1 yes 3800.0000 1550.0000 10 0 0 5 5:5:5:1 yes 3800.0000 1550.0000 11 0 0 5 5:5:5:1 yes 3800.0000 1550.0000 ``` - C0 = T0+T1 → L10+L20+L30 - C1 = T2+T3 → L11+L21+L30 - C2 = T4+T5 → L12+L22+L30 - C3 = T6+T7 → L13+L23+L31 - C4 = T8+T9 → L14+L24+L31 - C5 = T10+T11 → L15+L25+L31 2. Select which CPU cores to 'pin'. You should aim to select a combination of CPU cores that minimises sharing of caches between Windows and GNU/Linux. Example 1: - CPU cores share the same singular L3 cache, so this cannot be optimised. - CPU cores utilise different L1 and L2 caches, so isolating corresponding thread pairs will help improve performance. - Thus, if limiting the virtual machine to a maximum of 4 threads, there are 10 possible optimal configurations: - T0+T4 - T1+T5 - T2+T6 - T3+T7 - T0+T4+T1+T5 - T0+T4+T2+T6 - T0+T4+T3+T7 - T1+T5+T2+T6 - T1+T5+T3+T7 - T2+T6+T3+T7 Example 2: - Threads 0-5 utilise one L3 cache whereas threads 6-11 utilise a different L3 cache. Thus, one of these two sets of threads should be pinned to the virtual machine. - Pinning and isolating fewer than these (e.g. threads 8-11) would result in the host system making use of the L3 cache in threads 6 and 7, resulting in cache evictions and therefore bad performance. - Thus, there are only two possible optimal configurations: - T0+T1+T2+T3+T4+T5 - T6+T7+T8+T9+T10+T11 3. Prepare and add/modify the following to the ``, `` and `` sections, adjusting the values to match your selected threads. Example 1: The following selects 'T2+T6+T3+T7'. ```xml 4 ``` Example 2: The following selects 'T6+T7+T8+T9+T10+T11'. ```xml 6 ``` > [!NOTE] > More information on configuring CPU pinning can be found in [this excellent guide](https://wiki.archlinux.org/title/PCI_passthrough_via_OVMF#CPU_pinning).
Below is an example `.XML` file that describes a Windows 11 virtual machine.
Example .XML File ```xml RDPWindows 4d76e36e-c632-43e0-83c0-dc9f36c2823a 8388608 8388608 4 hvm /usr/share/edk2/ovmf/OVMF_CODE_4M.secboot.qcow2 /var/lib/libvirt/qemu/nvram/RDPWindows_VARS.qcow2 destroy restart destroy /usr/bin/qemu-system-x86_64
## Install Windows Click `Begin Installation` in the top left.

Once you get to the point of selecting the location for installation, you will see there are no disks available. This is because the `VirtIO driver` needs to be specified manually. 1. Select `Load driver`.

2. The installer will then ask you to specify where the driver is located. Select the drive the `VirtIO` driver `.ISO` is mounted on.

3. Choose the appropriate driver for the operating system you've selected, which is likely either the `w10` or `w11` drivers.

4. The virtual hard disk should now be visible and available for selection.

The next hurdle will be bypassing the network selection screen. As the `VirtIO` drivers for networking have not yet been loaded, the virtual machine will not be able to be connected to the internet. - For Windows 11: When prompted to select your country or region, press "Shift + F10" to open the command prompt. Enter `OOBE\BYPASSNRO` and press Enter. The system will restart, allowing you to select "I don't have internet" later on. It is crucial to run this command as soon as possible, as doing so later in the installation process will not work, and you may be required to create a Microsoft account despite not having an internet connection.

- For Windows 10: Simply click "I don't have internet".

Following the above, choose to "Continue with limited setup".

## Final Configuration Steps Open `File Explorer` and navigate to the drive where the "virtio-win" `.iso` is mounted. Run `virtio-win-guest-tools.exe` to install all necessary drivers as well as `QEMU Guest Agent`. Leave everything as default and click `Next` through the installer.

Confirm `QEMU Guest Agent` was successfully installed by running `Get-Service QEMU-GA` within a PowerShell window. The output should resemble: ``` Status Name DisplayName ------ ---- ----------- Running QEMU-GA QEMU Guest Agent ``` You can then test whether the host GNU/Linux system can communicate with Windows via `QEMU Guest Agent` by running `virsh qemu-agent-command RDPWindows '{"execute":"guest-get-osinfo"}' --pretty`. The output should resemble: ```json { "return": { "name": "Microsoft Windows", "kernel-release": "26100", "version": "Microsoft Windows 11", "variant": "client", "pretty-name": "Windows 10 Pro", "version-id": "11", "variant-id": "client", "kernel-version": "10.0", "machine": "x86_64", "id": "mswindows" } } ``` Next, you will need to make some registry changes to enable RDP Applications to run on the system. Start by downloading the [RDPApps.reg](../oem/RDPApps.reg) file, right-clicking on the `Raw` button, and clicking on `Save target as`. Repeat the same thing for the [install.bat](../oem/install.bat), the [TimeSync.ps1](../oem/TimeSync.ps1) and the [NetProfileCleanup.ps1](../oem/NetProfileCleanup.ps1). **Do not download 'Container.reg'** - this file is only required for users using docker or podman.

Once you have downloaded all three files, right-click the install.bat and select "Run as administrator".

Once this is complete, restart the Windows virtual machine.
(Optional) Configuring a Fallback Shared Folder When connecting to Windows through FreeRDP, your home folder will be shared automatically. However, this sharing setup does not apply when using Windows via virt-manager. To configure a fallback shared folder, follow these steps: 1. Navigate to "Virtual Hardware Details", then "Memory" and then check the box for "Enable shared memory". 2. Add filesystem hardware by going to "Virtual Hardware Details" and selecting "Add Hardware" followed by "Filesystem". Choose `virtiofs` as the driver, enter the path to the shared folder, and provide a name for the shared folder in the target path (e.g., "Windows Shared Folder"). 3. Install [`WinFSP`](https://github.com/winfsp/winfsp/releases/) on Windows. 4. Enable and start a 'VirtIO Filesystem' service within Windows by running the following commands within a PowerShell prompt. ```PowerShell sc.exe create VirtioFsSvc binpath= "C:\Program Files\Virtio-Win\VioFS\virtiofs.exe" start=auto depend="WinFsp.Launcher/VirtioFsDrv" DisplayName="Virtio Filesystem Service" sc.exe start VirtioFsSvc ``` 5. Reboot Windows.
(Optional) Configuring a Static IP Address 1. Identify the Windows MAC address. ```bash virsh dumpxml "RDPWindows" | grep "mac address" ``` 2. Edit the virtual network configuration. 1. Identify the correct network name. ```bash virsh net-list # Will likely return "default" ``` 2. Edit the configuration file. ```bash virsh net-edit "default" # Replace "default" with the appropriate network name if different ``` 3. Update the `` section in the configuration file using the MAC address you obtained earlier. In the below example, "RDPWindows" has MAC address "df:87:4c:75:e5:fb" and is assigned the static IP address "192.168.122.2". ```xml ``` 4. Restart the virtual network. ```bash virsh net-destroy "default" # Replace with the correct name on your system virsh net-start "default" # Replace with the correct name on your system ``` 5. Reboot Windows.
(Optional) Installing Spice Guest Tools You may also wish to install [Spice Guest Tools](https://www.spice-space.org/download/windows/spice-guest-tools/spice-guest-tools-latest.exe) inside the virtual machine, which enables features like auto-desktop resize and cut-and-paste when accessing the virtual machine through `virt-manager`. Since WinApps uses RDP, however, this is unnecessary if you don't plan to access the virtual machine via `virt-manager`.
## Installing Windows Software and Configuring WinApps You may now proceed to install other applications like 'Microsoft 365', 'Adobe Creative Cloud' or any other applications you would like to use through WinApps. > [!IMPORTANT] > Ensure `WAFLAVOR` is set to `"libvirt"` in your `~/.config/winapps/winapps.conf` to prevent WinApps looking for a `Docker` installation instead. Finally, restart the virtual machine, but **DO NOT** log in. Close the virtual machine viewer and proceed to run the WinApps installation. ```bash bash <(curl https://raw.githubusercontent.com/winapps-org/winapps/main/setup.sh) ``` ================================================ FILE: flake.nix ================================================ { description = "WinApps Nix packages"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"; flake-utils.url = "github:numtide/flake-utils"; nix-filter.url = "github:numtide/nix-filter"; }; nixConfig = { extra-substituters = [ "https://cache.garnix.io" ]; extra-trusted-public-keys = [ "cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g=" ]; }; outputs = { nixpkgs, flake-utils, nix-filter, ... }: flake-utils.lib.eachDefaultSystem ( system: let pkgs = import nixpkgs { inherit system; }; in rec { formatter = pkgs.nixfmt-rfc-style; packages.winapps = pkgs.callPackage ./packages/winapps { inherit nix-filter; }; packages.winapps-launcher = pkgs.callPackage ./packages/winapps-launcher { inherit (packages) winapps; }; } ); } ================================================ FILE: install/ExtractPrograms.ps1 ================================================ ### FUNCTIONS ### # Name: 'GetApplicationIcon' # Role: Extract the icon from a given executable file as a base-64 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: 'PrintArrayData' # Role: Print application names, executable paths and base-64 encoded icons in a format suitable for importing into bash arrays. # Args: # - 'Names': An array of application names. # - 'Paths': An array of executable paths. # - 'Source': The source of the applications (e.g. Windows Registry, Package managers, Universal Windows Platform (UWP), etc.) function PrintArrayData { 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) { # Remove undesirable suffix for chocolatey shims. if ($Source -eq "choco") { if ($Application.Name.EndsWith(" - Chocolatey Shim")) { $Application.Name = $Application.Name.Substring(0, $Application.Name.Length - " - Chocolatey Shim".Length) } } # Add the appropriate tag to the application name. if ($Source -ne "winreg") { $Application.Name = $Application.Name + " [" + $Source.ToUpper() + "]" } # Store the application icon as a base-64 string. $Icon = GetApplicationIcon -exePath $Application.Path # Output the results as bash commands that append the results to several bash arrays. Write-Output ('NAMES+=("' + $Application.Name + '")') Write-Output ('EXES+=("' + $Application.Path + '")') Write-Output ('ICONS+=("' + $Icon + '")') } } # Name: 'GetApplicationName' # Role: Determine the application name for a given executable file. # Args: # - 'exePath': The path to a given executable file. function GetApplicationName { param ( [string]$exePath ) try { $productName = (Get-Item $exePath).VersionInfo.FileDescription.Trim() -replace '\s+', ' ' } catch { $productName = [System.IO.Path]::GetFileNameWithoutExtension($exePath) } return $productName } # Name: 'GetUWPApplicationName' # Role: Determine the application name for a given UWP application. # Args: # - 'exePath': The path to a given executable file. function GetUWPApplicationName { param ( [string]$exePath ) # Query the application executable for the application name. if (Test-Path $exePath) { $productName = GetApplicationName -exePath $exePath } # Use the 'DisplayName' (if available) if the previous method failed. if (-not $productName -and $app.DisplayName) { $productName = $app.DisplayName } # Use the 'Name' (if available) as a final fallback. if (-not $productName -and $app.Name) { $productName = $app.Name } return $productName } # Name: 'GetUWPExecutablePath' # Role: Obtain the UWP application executable path from 'AppxManifest.xml'. # Args: # - 'instLoc': UWP application folder path (C:\Program Files\WindowsApps\*). function GetUWPExecutablePath { param ( [string]$instLoc ) # Determine the path to 'AppxManifest.xml' for the selected application. $manifestPath = Join-Path -Path $instLoc -ChildPath "AppxManifest.xml" if (Test-Path $manifestPath) { # Parse the XML file. [xml]$manifest = Get-Content $manifestPath $applications = $manifest.Package.Applications.Application # Return the path to the first executable specified within the XML. foreach ($application in $applications) { $executable = $application.Executable if ($executable) { return Join-Path -Path $instLoc -ChildPath $executable } } } # Return 'null' if nothing was found. return $null } # Name: 'AppSearchWinReg' # Role: Search the Windows Registry for installed applications. function AppSearchWinReg { # Initialise empty arrays. $exeNames = @() $exePaths = @() $validPaths = @() # Query windows registry for unique installed executable files. $exePaths = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\*" | ForEach-Object { $_."(default)" } | # Extract the value of the (default) property Where-Object { $_ -ne $null } | # Filter out null values Sort-Object -Unique # Ensure uniqueness # Remove leading and trailing double quotes from all paths. $exePaths = $exePaths -replace '^"*|"*$' # Get corresponding application names for unique installed executable files. foreach ($exePath in $exePaths) { if (Test-Path -Path $exePath) { $validPaths += $exePath $exeNames += GetApplicationName -exePath $exePath } } # Process extracted executable file paths. PrintArrayData -Names $exeNames -Paths $validPaths -Source "winreg" } # Name: 'AppSearchUWP' # Role: Search for 'non-system' UWP applications. function AppSearchUWP { # Initialise empty arrays. $exeNames = @() $exePaths = @() # Obtain all 'non-system' UWP applications using 'Get-AppxPackage'. $uwpApps = Get-AppxPackage | Where-Object { $_.IsFramework -eq $false -and $_.IsResourcePackage -eq $false -and $_.SignatureKind -ne 'System' } # Create an array to store UWP application details. $uwpAppDetails = @() # Loop through each UWP application. foreach ($app in $uwpApps) { # Initialise the variable responsible for storing the UWP application name. $productName = $null # Obtain the path to the UWP application executable. $exePath = GetUWPExecutablePath -instLoc $app.InstallLocation # Proceed only if an executable path was identified. if ($exePath) { $productName = GetUWPApplicationName -exePath $exePath # Ignore UWP applications with no name, or those named 'Microsoft® Windows® Operating System'. if ($productName -ne "Microsoft® Windows® Operating System" -and [string]::IsNullOrEmpty($productName) -eq $false) { # Store the UWP application name and executable path. $exeNames += $productName $exePaths += $exePath } } } # Process extracted executable file paths. PrintArrayData -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. PrintArrayData -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. PrintArrayData -Names $exeNames -Paths $exePaths -Source "scoop" } } ### SEQUENTIAL LOGIC ### # Print bash commands to define three new arrays. Write-Output 'NAMES=()' Write-Output 'EXES=()' Write-Output 'ICONS=()' # 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 ================================================ FILE: install/inquirer.sh ================================================ #!/usr/bin/env bash # Copyright (c) 2024 kahkhang # All rights reserved. # # SPDX-License-Identifier: MIT # For original source, see https://github.com/kahkhang/Inquirer.sh ### GLOBAL CONSTANTS ### declare -r ANSI_LIGHT_BLUE="\033[1;94m" # Light blue text. declare -r ANSI_LIGHT_GREEN="\033[92m" # Light green text. declare -r ANSI_CLEAR_TEXT="\033[0m" # Default text. declare -r DIALOG_HEIGHT=14 # Height of dialog window. declare -r TEXT_WIDTH_OFFSET=4 # Offset for fitting title text. declare -r CHK_OPTION_WIDTH_OFFSET=10 # Offset for fitting options. declare -r MNU_OPTION_WIDTH_OFFSET=7 # Offset for fitting options. ### FUNCTIONS ### function inqMenu() { # DECLARE VARIABLES. # Variables created from function arguments: declare DIALOG_TEXT="$1" # Dialog heading. declare INPUT_OPTIONS_VAR="$2" # Input variable name. declare RETURN_STRING_VAR="$3" # Output variable name. declare -n INPUT_OPTIONS="$INPUT_OPTIONS_VAR" # Input array nameref. declare -n RETURN_STRING="$RETURN_STRING_VAR" # Output string nameref. # Note: namerefs allow changes made through the nameref to affect the # referenced variable, even across different scopes like function calls. # Other variables: declare TRIMMED_OPTIONS=() # Input array post-trimming. declare PADDED_OPTIONS=() # Input array with extra white space. declare DIALOG_OPTIONS=() # Input array for options dialog. declare DIALOG_WIDTH=0 # Width of dialog window. declare OPTION_NUMBER=0 # Number of options in dialog window. declare SELECTED_OPTIONS_STRING="" # Output value from dialog window. # MAIN LOGIC. # Trim leading and trailing white space for each option. for OPTION in "${INPUT_OPTIONS[@]}"; do TRIMMED_OPTIONS+=("$(echo "$OPTION" | sed 's/^[ \t]*//;s/[ \t]*$//')") done # Find the length of the longest option to set the dialog width. for OPTION in "${TRIMMED_OPTIONS[@]}"; do if [ "${#OPTION}" -gt "$DIALOG_WIDTH" ]; then DIALOG_WIDTH=${#OPTION} fi done # Apply the offset value to the dialog width. DIALOG_WIDTH=$((DIALOG_WIDTH + MNU_OPTION_WIDTH_OFFSET)) # Adjust the dialog width again if the dialog text is longer. if [ "$DIALOG_WIDTH" -lt $((${#DIALOG_TEXT} + TEXT_WIDTH_OFFSET)) ]; then DIALOG_WIDTH="$((${#DIALOG_TEXT} + TEXT_WIDTH_OFFSET))" fi # Pad option text with trailing white space to left-align all options. for OPTION in "${TRIMMED_OPTIONS[@]}"; do local PAD_LENGTH=$((DIALOG_WIDTH - MNU_OPTION_WIDTH_OFFSET - ${#OPTION})) # shellcheck disable=SC2155 local PADDED_OPTION="${OPTION}$(printf '%*s' $PAD_LENGTH)" PADDED_OPTIONS+=("$PADDED_OPTION") done # Convert options into the appropriate format for a 'dialog' menu. for PADDED_OPTION in "${PADDED_OPTIONS[@]}"; do DIALOG_OPTIONS+=("$PADDED_OPTION" "") done # Store the number of options. OPTION_NUMBER="${#INPUT_OPTIONS[@]}" # Produce checkbox. # The output string contains options delimited by spaces. # Each option is enclosed in double quotes within the output string. # For example: '"Option 1 " "The Second Option " " Option Number 3 "' SELECTED_OPTIONS_STRING=$(dialog \ --keep-tite \ --clear \ --no-shadow \ --menu \ "$DIALOG_TEXT" \ "$DIALOG_HEIGHT" \ "$DIALOG_WIDTH" \ "$OPTION_NUMBER" \ "${DIALOG_OPTIONS[@]}" \ 2>&1 >/dev/tty) || exit 0 # Remove white space added previously. RETURN_STRING=$(echo "$SELECTED_OPTIONS_STRING" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') # Remove escapes (introduced by 'dialog' if options have parentheses). RETURN_STRING="${RETURN_STRING//\\/}" # ${variable//search/replace} # Display question and response. echo -e "${ANSI_LIGHT_GREEN}Q) ${ANSI_CLEAR_TEXT}${ANSI_LIGHT_BLUE}${DIALOG_TEXT}${ANSI_CLEAR_TEXT} --> ${ANSI_LIGHT_GREEN}${RETURN_STRING}${ANSI_CLEAR_TEXT}" } function inqChkBx() { # DECLARE VARIABLES. # Variables created from function arguments: declare DIALOG_TEXT="$1" # Dialog heading. declare INPUT_OPTIONS_VAR="$2" # Input variable name. declare RETURN_ARRAY_VAR="$3" # Output variable name. declare -n INPUT_OPTIONS="$INPUT_OPTIONS_VAR" # Input array nameref. declare -n RETURN_ARRAY="$RETURN_ARRAY_VAR" # Output array nameref. # Note: namerefs allow changes made through the nameref to affect the # referenced variable, even across different scopes like function calls. # Other variables: declare TRIMMED_OPTIONS=() # Input array post-trimming. declare PADDED_OPTIONS=() # Input array with extra white space. declare DIALOG_OPTIONS=() # Input array for options dialog. declare DIALOG_WIDTH=0 # Width of dialog window. declare OPTION_NUMBER=0 # Number of options in dialog window. declare SELECTED_OPTIONS_STRING="" # Output value from dialog window. # MAIN LOGIC. # Trim leading and trailing white space for each option. for OPTION in "${INPUT_OPTIONS[@]}"; do TRIMMED_OPTIONS+=("$(echo "$OPTION" | sed 's/^[ \t]*//;s/[ \t]*$//')") done # Find the length of the longest option to set the dialog width. for OPTION in "${TRIMMED_OPTIONS[@]}"; do if [ "${#OPTION}" -gt "$DIALOG_WIDTH" ]; then DIALOG_WIDTH=${#OPTION} fi done # Apply the offset value to the dialog width. DIALOG_WIDTH=$((DIALOG_WIDTH + CHK_OPTION_WIDTH_OFFSET)) # Adjust the dialog width again if the dialog text is longer. if [ "$DIALOG_WIDTH" -lt $((${#DIALOG_TEXT} + TEXT_WIDTH_OFFSET)) ]; then DIALOG_WIDTH="$((${#DIALOG_TEXT} + TEXT_WIDTH_OFFSET))" fi # Pad option text with trailing white space to left-align all options. for OPTION in "${TRIMMED_OPTIONS[@]}"; do local PAD_LENGTH=$((DIALOG_WIDTH - CHK_OPTION_WIDTH_OFFSET - ${#OPTION})) # shellcheck disable=SC2155 local PADDED_OPTION="${OPTION}$(printf '%*s' $PAD_LENGTH)" PADDED_OPTIONS+=("$PADDED_OPTION") done # Convert options into the appropriate format for a 'dialog' checkbox. for PADDED_OPTION in "${PADDED_OPTIONS[@]}"; do DIALOG_OPTIONS+=("$PADDED_OPTION" "" off) done # Store the number of options. OPTION_NUMBER="${#INPUT_OPTIONS[@]}" # Produce checkbox. # The output string contains options delimited by spaces. # Each option is enclosed in double quotes within the output string. # For example: '"Option 1 " "The Second Option " " Option Number 3 "' SELECTED_OPTIONS_STRING=$(dialog \ --keep-tite \ --clear \ --no-shadow \ --checklist \ "$DIALOG_TEXT" \ "$DIALOG_HEIGHT" \ "$DIALOG_WIDTH" \ "$OPTION_NUMBER" \ "${DIALOG_OPTIONS[@]}" \ 2>&1 >/dev/tty) || exit 0 # Convert the output string into an array. # shellcheck disable=SC2001 while IFS= read -r LINE; do LINE="${LINE/#\"/}" # Remove leading double quote. LINE="${LINE/%\"/}" # Remove trailing double quote. RETURN_ARRAY+=("$LINE") # Add to array. done < <(echo "$SELECTED_OPTIONS_STRING" | sed 's/\" \"/\"\n\"/g') # Final modifications. for ((i = 0; i < ${#RETURN_ARRAY[@]}; i++)); do # Remove white space added previously. # shellcheck disable=SC2001 RETURN_ARRAY[i]=$(echo "${RETURN_ARRAY[i]}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') # Remove escapes (introduced by 'dialog' if options have parentheses). RETURN_ARRAY[i]=${RETURN_ARRAY[i]//\\/} # ${variable//search/replace} done } ================================================ FILE: oem/NetProfileCleanup.ps1 ================================================ # 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" } } ================================================ FILE: oem/TimeSync.ps1 ================================================ # Script to monitor if there is a sleep_marker created by WinApps (indicating the Linux host was suspended) in order to trigger a time sync as the time in the Windows VM will otherwise drift while Linux is suspended. # Define the path to monitor. Make sure this matches the location for the sleep_marker in the Winapps script (need to match the APPDATA path). $filePath = "\\tsclient\home\.local\share\winapps\sleep_marker" $networkPath = "\\tsclient\home" # Function to check and handle file function Monitor-File { while ($true) { # Check if network location is available try { $null = Test-Path -Path $networkPath -ErrorAction Stop # Check if file exists if (Test-Path -Path $filePath) { # Run time resync silently w32tm /resync /quiet # Remove the file Remove-Item -Path $filePath -Force } } catch { # Network location not available, continue monitoring silently } # Wait 5 minutes before next check Start-Sleep -Seconds 3000 } } # Start monitoring silently Monitor-File ================================================ FILE: oem/install.bat ================================================ @echo off title WinApps Setup Wizard :: Check for administrative privileges fltmc >nul 2>&1 || ( echo [INFO] Script not running as administrator. Attempting to relaunch with elevation... powershell -Command "Start-Process '%~f0' -Verb runAs" exit /b ) echo ============================================ echo WinApps Setup Wizard echo ============================================ echo. echo [INFO] Starting setup... :: Apply RDP and system configuration tweaks echo [INFO] Importing "RDPApps.reg"... if exist "%~dp0RDPApps.reg" ( reg import "%~dp0RDPApps.reg" >nul 2>&1 if %ERRORLEVEL% equ 0 ( echo [SUCCESS] Imported "RDPApps.reg". ) else ( echo [ERROR] Failed to import "RDPApps.reg". ) ) else ( echo [ERROR] "RDPApps.reg" not found. Skipping... ) :: Allow Remote Desktop connections through the firewall echo [INFO] Allowing Remote Desktop connections through the firewall... powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass ^ -Command "if (Get-Command Enable-NetFirewallRule -ErrorAction SilentlyContinue) { try { Enable-NetFirewallRule -DisplayGroup 'Remote Desktop' -ErrorAction Stop; exit 0 } catch { exit 1 } } else { exit 2 }" >nul 2>&1 if %ERRORLEVEL% equ 0 ( echo [SUCCESS] Firewall changes applied successfully. ) else ( :: Fallback to using 'netsh' to make the firewall modification netsh advfirewall firewall set rule group="remote desktop" new enable=Yes >nul 2>&1 if %ERRORLEVEL% equ 0 ( echo [SUCCESS] Firewall changes applied successfully. ) else ( echo [ERROR] Failed to apply firewall changes. echo Please manually enable Remote Desktop via 'Settings --> System --> Remote Desktop'. ) ) :: Configure the system clock to use UTC instead of local time if exist "%~dp0Container.reg" ( echo [INFO] Importing "Container.reg"... reg import "%~dp0Container.reg" >nul 2>&1 if %ERRORLEVEL% equ 0 ( echo [SUCCESS] Imported "Container.reg". ) else ( echo [ERROR] Failed to import "Container.reg". ) ) else ( echo [WARNING] "Container.reg" not found. Skipping... ) :: Create a startup task to clean up stale network profiles echo [INFO] Creating network profile cleanup task... :: Initialise values required to create the startup task set "scriptpath=%windir%\NetProfileCleanup.ps1" set "taskname=WinApps_NetworkProfileCleanup" set "command=powershell.exe -ExecutionPolicy Bypass -File ""%scriptpath%""" :: Copy the script to the Windows directory copy /Y "%~dp0NetProfileCleanup.ps1" "%scriptpath%" >nul if %ERRORLEVEL% neq 0 ( echo [ERROR] Failed to copy "NetProfileCleanup.ps1" to "%windir%". ) else ( schtasks /create /tn "%taskname%" /tr "%command%" /sc onstart /ru "SYSTEM" /rl HIGHEST /f >nul 2>&1 if %ERRORLEVEL% equ 0 ( echo [SUCCESS] Created scheduled task "%taskname%". ) else ( echo [ERROR] Failed to create scheduled task "%taskname%". ) ) REM Create time sync task to be run by the user at login copy %~dp0\TimeSync.ps1 %windir% set "taskname2=TimeSync" set "command2=powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -File \"%windir%\TimeSync.ps1\"" schtasks /query /tn "%taskname2%" >nul if %ERRORLEVEL% equ 0 ( echo %DATE% %TIME% Task "%taskname2%" already exists, skipping creation. ) else ( schtasks /create /tn "%taskname2%" /tr "%command2%" /sc onlogon /rl HIGHEST /f if %ERRORLEVEL% equ 0 ( echo %DATE% %TIME% Scheduled task "%taskname2%" created successfully. ) else ( echo %DATE% %TIME% Failed to create scheduled task %taskname2%. ) ) ================================================ FILE: packages/winapps/default.nix ================================================ { stdenv, lib, makeWrapper, freerdp, dialog, libnotify, netcat, iproute2, nix-filter ? throw "Pass github:numtide/nix-filter as an argument!", ... }: stdenv.mkDerivation rec { pname = "winapps"; version = "0-unstable-2025-07-02"; src = nix-filter { root = ./../..; include = [ "apps" "install" "bin" "icons" "LICENSE.md" "COPYRIGHT.md" "setup.sh" ]; }; nativeBuildInputs = [ makeWrapper ]; buildInputs = [ freerdp libnotify dialog netcat iproute2 ]; patches = [ ./setup.patch ]; postPatch = '' substituteAllInPlace bin/winapps substituteAllInPlace setup.sh patchShebangs install/inquirer.sh ''; installPhase = '' runHook preInstall mkdir -p $out mkdir -p $out/src cp -r ./ $out/src/ install -m755 -D bin/winapps $out/bin/winapps install -m755 -D setup.sh $out/bin/winapps-setup for f in winapps-setup winapps; do wrapProgram $out/bin/$f \ --set LIBVIRT_DEFAULT_URI "qemu:///system" \ --prefix PATH : "${lib.makeBinPath buildInputs}" done runHook postInstall ''; meta = with lib; { homepage = "https://github.com/winapps-org/winapps"; description = "Run Windows applications (including Microsoft 365 and Adobe Creative Cloud) on GNU/Linux with KDE, GNOME or XFCE, integrated seamlessly as if they were native to the OS. Wayland is currently unsupported."; mainProgram = "winapps"; platforms = platforms.linux; license = licenses.agpl3Plus; }; } ================================================ FILE: packages/winapps/setup.patch ================================================ diff --git a/setup.sh b/setup.sh index 3a871c8..71a8fa0 100755 --- a/setup.sh +++ b/setup.sh @@ -39,8 +39,8 @@ readonly SYS_BIN_PATH="/usr/local/bin" # UNIX path to 'bin' dir readonly USER_BIN_PATH="${HOME}/.local/bin" # UNIX path to 'bin' directory for a '--user' WinApps installation. readonly USER_BIN_PATH_WIN='\\tsclient\home\.local\bin' # WINDOWS path to 'bin' directory for a '--user' WinApps installation. # 'SOURCE' -readonly SYS_SOURCE_PATH="${SYS_BIN_PATH}/winapps-src" # UNIX path to WinApps source directory for a '--system' WinApps installation. -readonly USER_SOURCE_PATH="${USER_BIN_PATH}/winapps-src" # UNIX path to WinApps source directory for a '--user' WinApps installation. +readonly SYS_SOURCE_PATH="@out@/src" # UNIX path to WinApps source directory for a '--system' WinApps installation. +readonly USER_SOURCE_PATH="@out@/src" # UNIX path to WinApps source directory for a '--user' WinApps installation. # 'APP' readonly SYS_APP_PATH="/usr/share/applications" # UNIX path to 'applications' directory for a '--system' WinApps installation. readonly USER_APP_PATH="${HOME}/.local/share/applications" # UNIX path to 'applications' directory for a '--user' WinApps installation. @@ -70,7 +70,7 @@ readonly TEST_PATH_WIN="${USER_APPDATA_PATH_WIN}\\FreeRDP_Connection_Test" # WIN # 'WinApps Configuration File' readonly CONFIG_PATH="${HOME}/.config/winapps/winapps.conf" # UNIX path to the WinApps configuration file. # 'Inquirer Bash Script' -readonly INQUIRER_PATH="./install/inquirer.sh" # UNIX path to the 'inquirer' script, which is used to produce selection menus. +readonly INQUIRER_PATH="@out@/src/install/inquirer.sh" # UNIX path to the 'inquirer' script, which is used to produce selection menus. # REMOTE DESKTOP CONFIGURATION readonly RDP_PORT=3389 # Port used for RDP on Windows. @@ -157,13 +157,6 @@ function waGetSourceCode() { echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} You might want to remove your old installation on '${SCRIPT_DIR_PATH}'." fi - if [[ ! -d "$SOURCE_PATH" ]]; then - $SUDO git clone --recurse-submodules --remote-submodules https://github.com/winapps-org/winapps.git "$SOURCE_PATH" - else - echo -e "${INFO_TEXT}WinApps installation already present at ${CLEAR_TEXT}${COMMAND_TEXT}${SOURCE_PATH}${CLEAR_TEXT}${INFO_TEXT}. Updating...${CLEAR_TEXT}" - $SUDO git -C "$SOURCE_PATH" pull --no-rebase - fi - # Silently change the working directory. if ! cd "$SOURCE_PATH" &>/dev/null; then # Display the error type. @@ -188,21 +181,8 @@ function waGetSourceCode() { # Name: 'waGetInquirer' # Role: Loads the inquirer script, even if the source isn't cloned yet function waGetInquirer() { - local INQUIRER=$INQUIRER_PATH - - if [ -d "$SYS_SOURCE_PATH" ]; then - INQUIRER=$SYS_SOURCE_PATH/$INQUIRER_PATH - elif [ -d "$USER_SOURCE_PATH" ] ; then - INQUIRER=$USER_SOURCE_PATH/$INQUIRER_PATH - else - INQUIRER="/tmp/waInquirer.sh" - rm -f "$INQUIRER" - - curl -o "$INQUIRER" "https://raw.githubusercontent.com/winapps-org/winapps/main/install/inquirer.sh" - fi - # shellcheck source=/dev/null # Exclude this file from being checked by ShellCheck. - source "$INQUIRER" + source "$INQUIRER_PATH" } # Name: 'waCheckInput' @@ -807,7 +787,7 @@ function waCheckGroupMembership() { # Identify groups the current user belongs to. USER_GROUPS=$(groups "$(whoami)") - if ! (echo "$USER_GROUPS" | grep -q -E "\blibvirt\b") || ! (echo "$USER_GROUPS" | grep -q -E "\bkvm\b"); then + if ! (echo "$USER_GROUPS" | grep -q -E "\blibvirtd\b") || ! (echo "$USER_GROUPS" | grep -q -E "\bkvm\b"); then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" @@ -1239,11 +1219,11 @@ function waConfigureWindows() { # Populate variables. WIN_BASH="\ #!/usr/bin/env bash -${BIN_PATH}/winapps windows" +@out@/bin/winapps windows" WIN_DESKTOP="\ [Desktop Entry] Name=Windows -Exec=${BIN_PATH}/winapps windows %F +Exec=@out@/bin/winapps windows %F Terminal=false Type=Application Icon=${APPDATA_PATH}/icons/windows.svg @@ -1290,13 +1270,13 @@ function waConfigureApp() { # Determine the content of the bash script for the application. APP_BASH="\ #!/usr/bin/env bash -${BIN_PATH}/winapps ${1}" +@out@/bin/winapps ${1}" # Determine the content of the '.desktop' file for the application. APP_DESKTOP_FILE="\ [Desktop Entry] Name=${NAME} -Exec=${BIN_PATH}/winapps ${1} %F +Exec=@out@/bin/winapps ${1} %F Terminal=false Type=Application Icon=${APP_ICON} @@ -1364,7 +1344,9 @@ function waConfigureOfficiallySupported() { fi # Copy the protocol handler to the appropriate directory. + # Fix permissions for nix. $SUDO cp "./apps/ms-office-protocol-handler.desktop" "$TARGET_DIR/ms-office-protocol-handler.desktop" + $SUDO chmod u+w "$TARGET_DIR/ms-office-protocol-handler.desktop" fi # Print feedback. @@ -1596,7 +1578,7 @@ function waInstall() { echo -e "${BOLD_TEXT}Installing WinApps.${CLEAR_TEXT}" # Check for existing conflicting WinApps installations. - waCheckExistingInstall + # waCheckExistingInstall # Load the WinApps configuration file. waLoadConfig @@ -1664,9 +1646,8 @@ function waInstall() { # Check for installed applications. waFindInstalled - # Install the WinApps bash scripts. - $SUDO ln -sf "${SOURCE_PATH}/bin/winapps" "${BIN_PATH}/winapps" - $SUDO ln -sf "${SOURCE_PATH}/setup.sh" "${BIN_PATH}/winapps-setup" + # Fix permissions for nix. + $SUDO chmod u+w -R "${APPDATA_PATH}" # Configure the Windows RDP session application launcher. waConfigureWindows @@ -1727,18 +1708,15 @@ function waUninstall() { local DESKTOP_FILE_NAME="" # Stores the name of the '.desktop' file for the application. local BASH_SCRIPT_NAME="" # Stores the name of the application. - # Remove the 'WinApps' bash scripts. - $SUDO rm -f "${BIN_PATH}/winapps" - $SUDO rm -f "${BIN_PATH}/winapps-setup" - # Remove WinApps configuration data, temporary files and logs. + chmod -R +rw "$USER_APPDATA_PATH" rm -rf "$USER_APPDATA_PATH" # Remove application icons and shortcuts. $SUDO rm -rf "$APPDATA_PATH" # Store '.desktop' files containing "${BIN_PATH}/winapps" in an array, returning an empty array if no such files exist. - readarray -t WINAPPS_DESKTOP_FILES < <(grep -l -d skip "${BIN_PATH}/winapps" "${APP_PATH}/"* 2>/dev/null || true) + readarray -t WINAPPS_DESKTOP_FILES < <(grep -l -d skip "@out@/bin/winapps" "${APP_PATH}/"* 2>/dev/null || true) # Remove each '.desktop' file. for DESKTOP_FILE_PATH in "${WINAPPS_DESKTOP_FILES[@]}"; do @@ -1759,7 +1737,7 @@ function waUninstall() { done # Store the paths of bash scripts calling 'WinApps' to launch specific applications in an array, returning an empty array if no such files exist. - readarray -t WINAPPS_APP_BASH_SCRIPTS < <(grep -l -d skip "${BIN_PATH}/winapps" "${BIN_PATH}/"* 2>/dev/null || true) + readarray -t WINAPPS_APP_BASH_SCRIPTS < <(grep -l -d skip "@out@/bin/winapps" "${BIN_PATH}/"* 2>/dev/null || true) # Remove each bash script. for BASH_SCRIPT_PATH in "${WINAPPS_APP_BASH_SCRIPTS[@]}"; do @@ -1780,10 +1758,9 @@ function waUninstall() { done # Print caveats. - echo -e "\n${INFO_TEXT}Please note that your WinApps configuration and the WinApps source code were not removed.${CLEAR_TEXT}" - echo -e "${INFO_TEXT}You can remove these manually by running:${CLEAR_TEXT}" + echo -e "\n${INFO_TEXT}Please note that your WinApps configuration and the WinApps package were not removed.${CLEAR_TEXT}" + echo -e "${INFO_TEXT}You can remove your config manually by running:${CLEAR_TEXT}" echo -e "${COMMAND_TEXT}rm -r $(dirname "$CONFIG_PATH")${CLEAR_TEXT}" - echo -e "${COMMAND_TEXT}rm -r ${SOURCE_PATH}${CLEAR_TEXT}\n" # Print feedback. echo -e "${SUCCESS_TEXT}UNINSTALLATION COMPLETE.${CLEAR_TEXT}" ================================================ FILE: packages/winapps-launcher/WinApps-Launcher.patch ================================================ diff --git a/WinApps-Launcher.sh b/WinApps-Launcher.sh index 1d3a929..a5d7d4c 100755 --- a/WinApps-Launcher.sh +++ b/WinApps-Launcher.sh @@ -19,7 +19,7 @@ declare -rx EC_WIN_NOT_SPEC=6 declare -rx EC_NO_WIN_FOUND=7 # Paths -declare -rx ICONS_PATH="./Icons" +declare -rx ICONS_PATH="@out@/Icons" declare -rx APPDATA_PATH="${XDG_DATA_HOME:-$HOME/.local/share}/winapps" declare -rx CONFIG_PATH="${XDG_CONFIG_HOME:-$HOME/.config}/winapps" declare -rx CONFIG_FILE="${CONFIG_PATH}/winapps.conf" ================================================ FILE: packages/winapps-launcher/default.nix ================================================ { stdenv, lib, fetchFromGitHub, makeWrapper, makeDesktopItem, yad, winapps ? throw "Pass in the winapps package", ... }: let rev = "87f92a80c7e421ab7d1b8801e647dcbfaaa6ee34"; hash = "sha256-aZ8uusg5yQOD1xYfaX2IQCbcPdHuVA0tiy1NDkdGCCs="; in stdenv.mkDerivation rec { pname = "winapps-launcher"; version = "0-unstable-2025-09-01"; src = fetchFromGitHub { owner = "winapps-org"; repo = "WinApps-Launcher"; inherit rev hash; }; nativeBuildInputs = [ makeWrapper ]; buildInputs = [ yad winapps ]; patches = [ ./WinApps-Launcher.patch ]; postPatch = '' substituteAllInPlace WinApps-Launcher.sh ''; installPhase = '' runHook preInstall mkdir -p $out cp -r ./Icons $out/Icons install -m755 -D WinApps-Launcher.sh $out/bin/winapps-launcher install -Dm444 -T Icons/AppIcon.svg $out/share/pixmaps/winapps.svg wrapProgram $out/bin/winapps-launcher \ --set LIBVIRT_DEFAULT_URI "qemu:///system" \ --prefix PATH : "${lib.makeBinPath buildInputs}" runHook postInstall ''; desktopItems = [ (makeDesktopItem { name = "winapps"; exec = "winapps-launcher"; icon = "winapps"; comment = meta.description; desktopName = "WinApps"; categories = [ "Utility" ]; }) ]; meta = with lib; { homepage = "https://github.com/winapps-org/WinApps-Launcher"; description = "Graphical launcher for WinApps. Run Windows applications (including Microsoft 365 and Adobe Creative Cloud) on GNU/Linux with KDE, GNOME or XFCE, integrated seamlessly as if they were native to the OS. Wayland is currently unsupported."; mainProgram = "winapps-launcher"; platforms = platforms.linux; license = licenses.gpl3; }; } ================================================ FILE: renovate.json ================================================ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:best-practices", "group:all", "schedule:monthly" ] } ================================================ FILE: setup.sh ================================================ #!/usr/bin/env bash # shellcheck disable=SC2034 # Silence warnings regarding unused variables globally. ### GLOBAL CONSTANTS ### # ANSI ESCAPE SEQUENCES readonly BOLD_TEXT="\033[1m" # Bold readonly CLEAR_TEXT="\033[0m" # Clear readonly COMMAND_TEXT="\033[0;37m" # Grey readonly DONE_TEXT="\033[0;32m" # Green readonly ERROR_TEXT="\033[1;31m" # Bold + Red readonly EXIT_TEXT="\033[1;41;37m" # Bold + White + Red Background readonly FAIL_TEXT="\033[0;91m" # Bright Red readonly INFO_TEXT="\033[0;33m" # Orange/Yellow readonly SUCCESS_TEXT="\033[1;42;37m" # Bold + White + Green Background readonly WARNING_TEXT="\033[1;33m" # Bold + Orange/Yellow # ERROR CODES readonly EC_FAILED_CD="1" # Failed to change directory to location of script. readonly EC_BAD_ARGUMENT="2" # Unsupported argument passed to script. readonly EC_EXISTING_INSTALL="3" # Existing conflicting WinApps installation. readonly EC_NO_CONFIG="4" # Absence of a valid WinApps configuration file. readonly EC_MISSING_DEPS="5" # Missing dependencies. readonly EC_NO_SUDO="6" # Insufficient privileges to invoke superuser access. readonly EC_NOT_IN_GROUP="7" # Current user not in group 'libvirt' and/or 'kvm'. readonly EC_VM_OFF="8" # Windows 'libvirt' VM powered off. readonly EC_VM_PAUSED="9" # Windows 'libvirt' VM paused. readonly EC_VM_ABSENT="10" # Windows 'libvirt' VM does not exist. readonly EC_CONTAINER_OFF="11" # Windows Docker container is not running. readonly EC_NO_IP="12" # Windows does not have an IP address. readonly EC_BAD_PORT="13" # Windows is unreachable via RDP_PORT. readonly EC_RDP_FAIL="14" # FreeRDP failed to establish a connection with Windows. readonly EC_APPQUERY_FAIL="15" # Failed to query Windows for installed applications. readonly EC_INVALID_FLAVOR="16" # Backend specified is not 'libvirt', 'docker' or 'podman'. # PATHS # 'BIN' readonly SYS_BIN_PATH="/usr/local/bin" # UNIX path to 'bin' directory for a '--system' WinApps installation. readonly USER_BIN_PATH="${HOME}/.local/bin" # UNIX path to 'bin' directory for a '--user' WinApps installation. readonly USER_BIN_PATH_WIN='\\tsclient\home\.local\bin' # WINDOWS path to 'bin' directory for a '--user' WinApps installation. # 'SOURCE' readonly SYS_SOURCE_PATH="${SYS_BIN_PATH}/winapps-src" # UNIX path to WinApps source directory for a '--system' WinApps installation. readonly USER_SOURCE_PATH="${USER_BIN_PATH}/winapps-src" # UNIX path to WinApps source directory for a '--user' WinApps installation. # 'APP' readonly SYS_APP_PATH="/usr/share/applications" # UNIX path to 'applications' directory for a '--system' WinApps installation. readonly USER_APP_PATH="${HOME}/.local/share/applications" # UNIX path to 'applications' directory for a '--user' WinApps installation. readonly USER_APP_PATH_WIN='\\tsclient\home\.local\share\applications' # WINDOWS path to 'applications' directory for a '--user' WinApps installation. # 'APPDATA' readonly SYS_APPDATA_PATH="/usr/local/share/winapps" # UNIX path to 'application data' directory for a '--system' WinApps installation. readonly USER_APPDATA_PATH="${HOME}/.local/share/winapps" # UNIX path to 'application data' directory for a '--user' WinApps installation. readonly USER_APPDATA_PATH_WIN='\\tsclient\home\.local\share\winapps' # WINDOWS path to 'application data' directory for a '--user' WinApps installation. # 'Installed Batch Script' readonly BATCH_SCRIPT_PATH="${USER_APPDATA_PATH}/installed.bat" # UNIX path to a batch script used to search Windows for applications. readonly BATCH_SCRIPT_PATH_WIN="${USER_APPDATA_PATH_WIN}\\installed.bat" # WINDOWS path to a batch script used to search Windows for applications. # 'Installed File' readonly TMP_INST_FILE_PATH="${USER_APPDATA_PATH}/installed.tmp" # UNIX path to a temporary file containing the names of detected officially supported applications. readonly TMP_INST_FILE_PATH_WIN="${USER_APPDATA_PATH_WIN}\\installed.tmp" # WINDOWS path to a temporary file containing the names of detected officially supported applications. readonly INST_FILE_PATH="${USER_APPDATA_PATH}/installed" # UNIX path to a file containing the names of detected officially supported applications. readonly INST_FILE_PATH_WIN="${USER_APPDATA_PATH_WIN}\\installed" # WINDOWS path to a file containing the names of detected officially supported applications. # 'PowerShell Script' readonly PS_SCRIPT_PATH="./install/ExtractPrograms.ps1" # UNIX path to a PowerShell script used to store the names, executable paths and icons (base64) of detected applications. readonly PS_SCRIPT_HOME_PATH="${USER_APPDATA_PATH}/ExtractPrograms.ps1" # UNIX path to a copy of the PowerShell script within the user's home directory to enable access by Windows. readonly PS_SCRIPT_HOME_PATH_WIN="${USER_APPDATA_PATH_WIN}\\ExtractPrograms.ps1" # WINDOWS path to a copy of the PowerShell script within the user's home directory to enable access by Windows. # 'Detected File' readonly DETECTED_FILE_PATH="${USER_APPDATA_PATH}/detected" # UNIX path to a file containing the output generated by the PowerShell script, formatted to define bash arrays. readonly DETECTED_FILE_PATH_WIN="${USER_APPDATA_PATH_WIN}\\detected" # WINDOWS path to a file containing the output generated by the PowerShell script, formatted to define bash arrays. # 'FreeRDP Connection Test File' readonly TEST_PATH="${USER_APPDATA_PATH}/FreeRDP_Connection_Test" # UNIX path to temporary file whose existence is used to confirm a successful RDP connection was established. readonly TEST_PATH_WIN="${USER_APPDATA_PATH_WIN}\\FreeRDP_Connection_Test" # WINDOWS path to temporary file whose existence is used to confirm a successful RDP connection was established. # 'WinApps Configuration File' readonly CONFIG_PATH="${HOME}/.config/winapps/winapps.conf" # UNIX path to the WinApps configuration file. # 'Inquirer Bash Script' readonly INQUIRER_PATH="./install/inquirer.sh" # UNIX path to the 'inquirer' script, which is used to produce selection menus. # REMOTE DESKTOP CONFIGURATION readonly RDP_PORT=3389 # Port used for RDP on Windows. readonly DOCKER_IP="127.0.0.1" # Localhost. ### GLOBAL VARIABLES ### # USER INPUT OPT_SYSTEM=0 # Set to '1' if the user specifies '--system'. OPT_USER=0 # Set to '1' if the user specifies '--user'. OPT_UNINSTALL=0 # Set to '1' if the user specifies '--uninstall'. OPT_AOSA=0 # Set to '1' if the user specifies '--setupAllOfficiallySupportedApps'. OPT_ADD_APPS=0 # Set to '1' if the user specifies '--add-apps'. # WINAPPS CONFIGURATION FILE RDP_USER="" # Imported variable. RDP_PASS="" # Imported variable. RDP_ASKPASS="" # Imported variable. RDP_DOMAIN="" # Imported variable. RDP_IP="" # Imported variable. VM_NAME="RDPWindows" # Name of the Windows VM (FOR 'libvirt' ONLY). WAFLAVOR="docker" # Imported variable. RDP_SCALE=100 # Imported variable. RDP_FLAGS="" # Imported variable. DEBUG="true" # Imported variable. FREERDP_COMMAND="" # Imported variable. PORT_TIMEOUT=5 # Default port check timeout. RDP_TIMEOUT=30 # Default RDP connection test timeout. APP_SCAN_TIMEOUT=60 # Default application scan timeout. # PERMISSIONS AND DIRECTORIES SUDO="" # Set to "sudo" if the user specifies '--system', or "" if the user specifies '--user'. BIN_PATH="" # Set to $SYS_BIN_PATH if the user specifies '--system', or $USER_BIN_PATH if the user specifies '--user'. APP_PATH="" # Set to $SYS_APP_PATH if the user specifies '--system', or $USER_APP_PATH if the user specifies '--user'. APPDATA_PATH="" # Set to $SYS_APPDATA_PATH if the user specifies '--system', or $USER_APPDATA_PATH if the user specifies '--user'. SOURCE_PATH="" # Set to $SYS_SOURCE_PATH if the user specifies '--system', or $USER_SOURCE_PATH if the user specifies '--user'. # INSTALLATION PROCESS INSTALLED_EXES=() # List of executable file names of officially supported applications that have already been configured during the current installation process. ### TRAPS ### set -o errtrace # Ensure traps are inherited by all shell functions and subshells. trap "waTerminateScript" ERR # Catch non-zero return values. ### FUNCTIONS ### # Name: 'waTerminateScript' # Role: Terminates the script when a non-zero return value is encountered. # shellcheck disable=SC2329 # Silence warning regarding this function never being invoked (shellCheck is currently bad at figuring out functions that are invoked via trap). function waTerminateScript() { # Store the non-zero exit status received by the trap. local EXIT_STATUS=$? # Display the exit status. echo -e "${EXIT_TEXT}Exiting with status '${EXIT_STATUS}'.${CLEAR_TEXT}" # Terminate the script. exit "$EXIT_STATUS" } # Name: 'waUsage' # Role: Displays usage information for the script. function waUsage() { echo -e "Usage: ${COMMAND_TEXT} --user${CLEAR_TEXT} # Install WinApps and selected applications in ${HOME} ${COMMAND_TEXT} --system${CLEAR_TEXT} # Install WinApps and selected applications in /usr ${COMMAND_TEXT} --user --setupAllOfficiallySupportedApps${CLEAR_TEXT} # Install WinApps and all officially supported applications in ${HOME} ${COMMAND_TEXT} --system --setupAllOfficiallySupportedApps${CLEAR_TEXT} # Install WinApps and all officially supported applications in /usr ${COMMAND_TEXT} --user --uninstall${CLEAR_TEXT} # Uninstall everything in ${HOME} ${COMMAND_TEXT} --system --uninstall${CLEAR_TEXT} # Uninstall everything in /usr ${COMMAND_TEXT} --user --add-apps${CLEAR_TEXT} # Add new applications to existing installation in ${HOME} ${COMMAND_TEXT} --system --add-apps${CLEAR_TEXT} # Add new applications to existing installation in /usr ${COMMAND_TEXT} --help${CLEAR_TEXT} # Display this usage message." } # Name: 'waGetSourceCode' # Role: Grab the WinApps source code using Git. function waGetSourceCode() { # Declare variables. local SCRIPT_DIR_PATH="" # Stores the absolute path of the directory containing the script. # Determine the absolute path to the directory containing the script. SCRIPT_DIR_PATH=$(readlink -f "$(dirname "${BASH_SOURCE[0]}")") # Check if winapps is currently installed on $SOURCE_PATH if [[ -f "$SCRIPT_DIR_PATH/winapps" && "$SCRIPT_DIR_PATH" != "$SOURCE_PATH" ]]; then # Display a warning. echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} You are running a WinApps installation located outside of default location '${SOURCE_PATH}'. A new installation will be created." echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} You might want to remove your old installation on '${SCRIPT_DIR_PATH}'." fi if [[ ! -d "$SOURCE_PATH" ]]; then $SUDO git clone --recurse-submodules --remote-submodules https://github.com/winapps-org/winapps.git "$SOURCE_PATH" else echo -e "${INFO_TEXT}WinApps installation already present at ${CLEAR_TEXT}${COMMAND_TEXT}${SOURCE_PATH}${CLEAR_TEXT}${INFO_TEXT}. Updating...${CLEAR_TEXT}" $SUDO git -C "$SOURCE_PATH" pull --no-rebase fi # Silently change the working directory. if ! cd "$SOURCE_PATH" &>/dev/null; then # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}DIRECTORY CHANGE FAILURE.${CLEAR_TEXT}" # Display error details. echo -e "${INFO_TEXT}Failed to change the working directory to ${CLEAR_TEXT}${COMMAND_TEXT}${SOURCE_PATH}${CLEAR_TEXT}${INFO_TEXT}.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Ensure:" echo -e " - ${COMMAND_TEXT}${SOURCE_PATH}${CLEAR_TEXT} exists." echo -e " - ${COMMAND_TEXT}${SOURCE_PATH}${CLEAR_TEXT} has been cloned and checked out properly." echo -e " - The current user has sufficient permissions to access and write to ${COMMAND_TEXT}${SOURCE_PATH}${CLEAR_TEXT}." echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_FAILED_CD" fi } # Name: 'waGetInquirer' # Role: Loads the inquirer script, even if the source isn't cloned yet function waGetInquirer() { local INQUIRER=$INQUIRER_PATH if [ -d "$SYS_SOURCE_PATH" ]; then INQUIRER=$SYS_SOURCE_PATH/$INQUIRER_PATH elif [ -d "$USER_SOURCE_PATH" ] ; then INQUIRER=$USER_SOURCE_PATH/$INQUIRER_PATH else INQUIRER="/tmp/waInquirer.sh" rm -f "$INQUIRER" curl -o "$INQUIRER" "https://raw.githubusercontent.com/winapps-org/winapps/main/install/inquirer.sh" fi # shellcheck source=/dev/null # Exclude this file from being checked by ShellCheck. source "$INQUIRER" } # Name: 'waCheckInput' # Role: Sanitises input and guides users through selecting appropriate options if no arguments are provided. function waCheckInput() { # Declare variables. local OPTIONS=() # Stores the options. local SELECTED_OPTION # Stores the option selected by the user. if [[ $# -gt 0 ]]; then # Parse arguments. for argument in "$@"; do case "$argument" in "--user") OPT_USER=1 ;; "--system") OPT_SYSTEM=1 ;; "--setupAllOfficiallySupportedApps") OPT_AOSA=1 ;; "--uninstall") OPT_UNINSTALL=1 ;; "--add-apps") OPT_ADD_APPS=1 ;; "--help") waUsage exit 0 ;; *) # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}INVALID ARGUMENT.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}Unsupported argument${CLEAR_TEXT} ${COMMAND_TEXT}${argument}${CLEAR_TEXT}${INFO_TEXT}.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" waUsage echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_BAD_ARGUMENT" ;; esac done else # Install vs. uninstall? OPTIONS=("Install" "Uninstall") inqMenu "Install or uninstall WinApps?" OPTIONS SELECTED_OPTION # Set flags. if [[ $SELECTED_OPTION == "Uninstall" ]]; then OPT_UNINSTALL=1 fi # User vs. system? OPTIONS=("Current User" "System") inqMenu "Configure WinApps for the current user '$(whoami)' or the whole system?" OPTIONS SELECTED_OPTION # Set flags. if [[ $SELECTED_OPTION == "Current User" ]]; then OPT_USER=1 elif [[ $SELECTED_OPTION == "System" ]]; then OPT_SYSTEM=1 fi # Automatic vs. manual? if [ "$OPT_UNINSTALL" -eq 0 ]; then OPTIONS=("Manual (Default)" "Automatic") inqMenu "Automatically install supported applications or choose manually?" OPTIONS SELECTED_OPTION # Set flags. if [[ $SELECTED_OPTION == "Automatic" ]]; then OPT_AOSA=1 fi fi # Newline. echo "" fi # Simultaneous 'User' and 'System'. if [ "$OPT_SYSTEM" -eq 1 ] && [ "$OPT_USER" -eq 1 ]; then # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}CONFLICTING ARGUMENTS.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}You cannot specify both${CLEAR_TEXT} ${COMMAND_TEXT}--user${CLEAR_TEXT} ${INFO_TEXT}and${CLEAR_TEXT} ${COMMAND_TEXT}--system${CLEAR_TEXT} ${INFO_TEXT}simultaneously.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" waUsage echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_BAD_ARGUMENT" fi # Simultaneous 'Uninstall' and 'AOSA'. if [ "$OPT_UNINSTALL" -eq 1 ] && [ "$OPT_AOSA" -eq 1 ]; then # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}CONFLICTING ARGUMENTS.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}You cannot specify both${CLEAR_TEXT} ${COMMAND_TEXT}--uninstall${CLEAR_TEXT} ${INFO_TEXT}and${CLEAR_TEXT} ${COMMAND_TEXT}--aosa${CLEAR_TEXT} ${INFO_TEXT}simultaneously.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" waUsage echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_BAD_ARGUMENT" fi # Simultaneous 'Uninstall' and 'Add Apps'. if [ "$OPT_UNINSTALL" -eq 1 ] && [ "$OPT_ADD_APPS" -eq 1 ]; then # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}CONFLICTING ARGUMENTS.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}You cannot specify both${CLEAR_TEXT} ${COMMAND_TEXT}--uninstall${CLEAR_TEXT} ${INFO_TEXT}and${CLEAR_TEXT} ${COMMAND_TEXT}--add-apps${CLEAR_TEXT} ${INFO_TEXT}simultaneously.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" waUsage echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_BAD_ARGUMENT" fi # Simultaneous 'AOSA' and 'Add Apps'. if [ "$OPT_AOSA" -eq 1 ] && [ "$OPT_ADD_APPS" -eq 1 ]; then # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}CONFLICTING ARGUMENTS.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}You cannot specify both${CLEAR_TEXT} ${COMMAND_TEXT}--setupAllOfficiallySupportedApps${CLEAR_TEXT} ${INFO_TEXT}and${CLEAR_TEXT} ${COMMAND_TEXT}--add-apps${CLEAR_TEXT} ${INFO_TEXT}simultaneously.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" waUsage echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_BAD_ARGUMENT" fi # No 'User' or 'System'. if [ "$OPT_SYSTEM" -eq 0 ] && [ "$OPT_USER" -eq 0 ]; then # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}INSUFFICIENT ARGUMENTS.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}You must specify either${CLEAR_TEXT} ${COMMAND_TEXT}--user${CLEAR_TEXT} ${INFO_TEXT}or${CLEAR_TEXT} ${COMMAND_TEXT}--system${CLEAR_TEXT} ${INFO_TEXT}to proceed.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" waUsage echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_BAD_ARGUMENT" fi } # Name: 'waConfigurePathsAndPermissions' # Role: Sets paths and adjusts permissions as specified. function waConfigurePathsAndPermissions() { if [ "$OPT_USER" -eq 1 ]; then SUDO="" SOURCE_PATH="$USER_SOURCE_PATH" BIN_PATH="$USER_BIN_PATH" APP_PATH="$USER_APP_PATH" APPDATA_PATH="$USER_APPDATA_PATH" elif [ "$OPT_SYSTEM" -eq 1 ]; then SUDO="sudo" SOURCE_PATH="$SYS_SOURCE_PATH" BIN_PATH="$SYS_BIN_PATH" APP_PATH="$SYS_APP_PATH" APPDATA_PATH="$SYS_APPDATA_PATH" # Preemptively obtain superuser privileges. sudo -v || { # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}AUTHENTICATION FAILURE.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}Failed to gain superuser privileges.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Please check your password and try again." echo "If you continue to experience issues, contact your system administrator." echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_NO_SUDO" } fi } # Name: 'waCheckExistingInstall' # Role: Identifies any existing WinApps installations that may conflict with the new installation. function waCheckExistingInstall() { # Print feedback. echo -n "Checking for existing conflicting WinApps installations... " # If --add-apps is specified, we don't want to fail if an installation exists if [ "$OPT_ADD_APPS" -eq 1 ]; then # Check for an existing 'user' installation. if [[ -f "${USER_BIN_PATH}/winapps" && -d "${USER_SOURCE_PATH}/winapps" ]]; then # Complete the previous line. echo -e "${DONE_TEXT}Found!${CLEAR_TEXT}" echo -e "${INFO_TEXT}Adding new applications to existing user installation.${CLEAR_TEXT}" return 0 fi # Check for an existing 'system' installation. if [[ -f "${SYS_BIN_PATH}/winapps" && -d "${SYS_SOURCE_PATH}/winapps" ]]; then # Complete the previous line. echo -e "${DONE_TEXT}Found!${CLEAR_TEXT}" echo -e "${INFO_TEXT}Adding new applications to existing system installation.${CLEAR_TEXT}" return 0 fi # If we're adding apps but no installation exists, that's an error echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}NO EXISTING WINAPPS INSTALLATION.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}No existing WinApps installation was detected.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo -e "Please install WinApps first using ${COMMAND_TEXT}winapps-setup --user${CLEAR_TEXT} or ${COMMAND_TEXT}winapps-setup --system${CLEAR_TEXT}." echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_EXISTING_INSTALL" fi # Check for an existing 'user' installation. if [[ -f "${USER_BIN_PATH}/winapps" || -d "${USER_SOURCE_PATH}/winapps" ]]; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}EXISTING 'USER' WINAPPS INSTALLATION.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}A previous WinApps installation was detected for the current user.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo -e "Please remove the existing WinApps installation using ${COMMAND_TEXT}winapps-setup --user --uninstall${CLEAR_TEXT}." echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_EXISTING_INSTALL" fi # Check for an existing 'system' installation. if [[ -f "${SYS_BIN_PATH}/winapps" || -d "${SYS_SOURCE_PATH}/winapps" ]]; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}EXISTING 'SYSTEM' WINAPPS INSTALLATION.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}A previous system-wide WinApps installation was detected.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo -e "Please remove the existing WinApps installation using ${COMMAND_TEXT}winapps-setup --system --uninstall${CLEAR_TEXT}." echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_EXISTING_INSTALL" fi # Print feedback. echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}" } # Name: 'waFixScale' # Role: Since FreeRDP only supports '/scale' values of 100, 140 or 180, find the closest supported argument to the user's configuration. function waFixScale() { # Define variables. local OLD_SCALE=100 local VALID_SCALE_1=100 local VALID_SCALE_2=140 local VALID_SCALE_3=180 # Check for an unsupported value. if [ "$RDP_SCALE" != "$VALID_SCALE_1" ] && [ "$RDP_SCALE" != "$VALID_SCALE_2" ] && [ "$RDP_SCALE" != "$VALID_SCALE_3" ]; then # Save the unsupported scale. OLD_SCALE="$RDP_SCALE" # Calculate the absolute differences. local DIFF_1=$(( RDP_SCALE > VALID_SCALE_1 ? RDP_SCALE - VALID_SCALE_1 : VALID_SCALE_1 - RDP_SCALE )) local DIFF_2=$(( RDP_SCALE > VALID_SCALE_2 ? RDP_SCALE - VALID_SCALE_2 : VALID_SCALE_2 - RDP_SCALE )) local DIFF_3=$(( RDP_SCALE > VALID_SCALE_3 ? RDP_SCALE - VALID_SCALE_3 : VALID_SCALE_3 - RDP_SCALE )) # Set the final scale to the valid scale value with the smallest absolute difference. if (( DIFF_1 <= DIFF_2 && DIFF_1 <= DIFF_3 )); then RDP_SCALE="$VALID_SCALE_1" elif (( DIFF_2 <= DIFF_1 && DIFF_2 <= DIFF_3 )); then RDP_SCALE="$VALID_SCALE_2" else RDP_SCALE="$VALID_SCALE_3" fi # Print feedback. echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} Unsupported RDP_SCALE value '${OLD_SCALE}' detected. Defaulting to '${RDP_SCALE}'." fi } # Name: 'waLoadConfig' # Role: Loads settings specified within the WinApps configuration file. function waLoadConfig() { # Print feedback. echo -n "Attempting to load WinApps configuration file... " if [ ! -f "$CONFIG_PATH" ]; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING CONFIGURATION FILE.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}A valid WinApps configuration file was not found.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo -e "Please create a configuration file at ${COMMAND_TEXT}${CONFIG_PATH}${CLEAR_TEXT}." echo -e "See https://github.com/winapps-org/winapps?tab=readme-ov-file#step-3-create-a-winapps-configuration-file" echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_NO_CONFIG" else # Load the WinApps configuration file. # shellcheck source=/dev/null # Exclude this file from being checked by ShellCheck. source "$CONFIG_PATH" # Send password on the command line if a command to retrieve the password from is not given # Otherwise, set FREERDP_ASKPASS which freerdp will read the stdout of to use as the password RDP_PASSWORD_ARG="/p:$RDP_PASS" if [[ ! -z "$RDP_ASKPASS" ]]; then export FREERDP_ASKPASS="$RDP_ASKPASS" unset RDP_PASSWORD_ARG fi fi # Print feedback. echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}" } # Name: 'waCheckScriptDependencies' # Role: Terminate script if dependencies are missing. function waCheckScriptDependencies() { # 'Git' if ! command -v git &>/dev/null; then # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}Please install 'git' to proceed.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Debian/Ubuntu-based systems:" echo -e " ${COMMAND_TEXT}sudo apt install git${CLEAR_TEXT}" echo "Red Hat/Fedora-based systems:" echo -e " ${COMMAND_TEXT}sudo dnf install git${CLEAR_TEXT}" echo "Arch Linux systems:" echo -e " ${COMMAND_TEXT}sudo pacman -S git${CLEAR_TEXT}" echo "Gentoo Linux systems:" echo -e " ${COMMAND_TEXT}sudo emerge --ask dev-vcs/git${CLEAR_TEXT}" echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_MISSING_DEPS" fi # 'curl' if ! command -v curl &>/dev/null; then # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}Please install 'curl' to proceed.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Debian/Ubuntu-based systems:" echo -e " ${COMMAND_TEXT}sudo apt install curl${CLEAR_TEXT}" echo "Red Hat/Fedora-based systems:" echo -e " ${COMMAND_TEXT}sudo dnf install curl${CLEAR_TEXT}" echo "Arch Linux systems:" echo -e " ${COMMAND_TEXT}sudo pacman -S curl${CLEAR_TEXT}" echo "Gentoo Linux systems:" echo -e " ${COMMAND_TEXT}sudo emerge --ask net-misc/curl${CLEAR_TEXT}" echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_MISSING_DEPS" fi # 'Dialog'. if ! command -v dialog &>/dev/null; then # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}Please install 'dialog' to proceed.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Debian/Ubuntu-based systems:" echo -e " ${COMMAND_TEXT}sudo apt install dialog${CLEAR_TEXT}" echo "Red Hat/Fedora-based systems:" echo -e " ${COMMAND_TEXT}sudo dnf install dialog${CLEAR_TEXT}" echo "Arch Linux systems:" echo -e " ${COMMAND_TEXT}sudo pacman -S dialog${CLEAR_TEXT}" echo "Gentoo Linux systems:" echo -e " ${COMMAND_TEXT}sudo emerge --ask dialog${CLEAR_TEXT}" echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_MISSING_DEPS" fi } # Name: 'waCheckInstallDependencies' # Role: Terminate script if dependencies required to install WinApps are missing. function waCheckInstallDependencies() { # Declare variables. local FREERDP_MAJOR_VERSION="" # Stores the major version of the installed copy of FreeRDP. # Print feedback. echo -n "Checking whether dependencies are installed... " # 'libnotify' if ! command -v notify-send &>/dev/null; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}Please install 'libnotify' to proceed.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Debian/Ubuntu-based systems:" echo -e " ${COMMAND_TEXT}sudo apt install libnotify-bin${CLEAR_TEXT}" echo "Red Hat/Fedora-based systems:" echo -e " ${COMMAND_TEXT}sudo dnf install libnotify${CLEAR_TEXT}" echo "Arch Linux systems:" echo -e " ${COMMAND_TEXT}sudo pacman -S libnotify${CLEAR_TEXT}" echo "Gentoo Linux systems:" echo -e " ${COMMAND_TEXT}sudo emerge --ask x11-libs/libnotify${CLEAR_TEXT}" echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_MISSING_DEPS" fi # 'Netcat' if ! command -v nc &>/dev/null; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}Please install 'netcat' to proceed.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Debian/Ubuntu-based systems:" echo -e " ${COMMAND_TEXT}sudo apt install netcat${CLEAR_TEXT}" echo "Red Hat/Fedora-based systems:" echo -e " ${COMMAND_TEXT}sudo dnf install nmap-ncat${CLEAR_TEXT}" echo "Arch Linux systems:" echo -e " ${COMMAND_TEXT}sudo pacman -S openbsd-netcat${CLEAR_TEXT}" echo "Gentoo Linux systems:" echo -e " ${COMMAND_TEXT}sudo emerge --ask net-analyzer/netcat${CLEAR_TEXT}" echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_MISSING_DEPS" fi # 'FreeRDP' (Version 3). # Attempt to set a FreeRDP command if the command variable is empty. if [ -z "$FREERDP_COMMAND" ]; then # Check common commands used to launch FreeRDP. if command -v xfreerdp &>/dev/null; then # Check FreeRDP major version is 3 or greater. FREERDP_MAJOR_VERSION=$(xfreerdp --version | head -n 1 | grep -o -m 1 '\b[0-9]\S*' | head -n 1 | cut -d'.' -f1) if [[ $FREERDP_MAJOR_VERSION =~ ^[0-9]+$ ]] && ((FREERDP_MAJOR_VERSION >= 3)); then FREERDP_COMMAND="xfreerdp" fi fi # Check for xfreerdp3 command as a fallback option. if [ -z "$FREERDP_COMMAND" ]; then if command -v xfreerdp3 &>/dev/null; then # Check FreeRDP major version is 3 or greater. FREERDP_MAJOR_VERSION=$(xfreerdp3 --version | head -n 1 | grep -o -m 1 '\b[0-9]\S*' | head -n 1 | cut -d'.' -f1) if [[ $FREERDP_MAJOR_VERSION =~ ^[0-9]+$ ]] && ((FREERDP_MAJOR_VERSION >= 3)); then FREERDP_COMMAND="xfreerdp3" fi fi fi # Check for FreeRDP flatpak as a fallback option. if [ -z "$FREERDP_COMMAND" ]; then if command -v flatpak &>/dev/null; then if flatpak list --columns=application | grep -q "^com.freerdp.FreeRDP$"; then # Check FreeRDP major version is 3 or greater. FREERDP_MAJOR_VERSION=$(flatpak list --columns=application,version | grep "^com.freerdp.FreeRDP" | awk '{print $2}' | cut -d'.' -f1) if [[ $FREERDP_MAJOR_VERSION =~ ^[0-9]+$ ]] && ((FREERDP_MAJOR_VERSION >= 3)); then FREERDP_COMMAND="flatpak run --command=xfreerdp com.freerdp.FreeRDP" fi fi fi fi fi if ! command -v "$FREERDP_COMMAND" &>/dev/null && [ "$FREERDP_COMMAND" != "flatpak run --command=xfreerdp com.freerdp.FreeRDP" ]; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}Please install 'FreeRDP' version 3 to proceed.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Debian/Ubuntu-based systems:" echo -e " ${COMMAND_TEXT}sudo apt install freerdp3-x11${CLEAR_TEXT}" echo "Red Hat/Fedora-based systems:" echo -e " ${COMMAND_TEXT}sudo dnf install freerdp${CLEAR_TEXT}" echo "Arch Linux systems:" echo -e " ${COMMAND_TEXT}sudo pacman -S freerdp${CLEAR_TEXT}" echo "Gentoo Linux systems:" echo -e " ${COMMAND_TEXT}sudo emerge --ask net-misc/freerdp${CLEAR_TEXT}" echo "" echo "You can also install FreeRDP as a Flatpak." echo "Install Flatpak, add the Flathub repository and then install FreeRDP:" echo -e "${COMMAND_TEXT}flatpak install flathub com.freerdp.FreeRDP${CLEAR_TEXT}" echo -e "${COMMAND_TEXT}sudo flatpak override --filesystem=home com.freerdp.FreeRDP${CLEAR_TEXT}" echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_MISSING_DEPS" fi # 'libvirt'/'virt-manager' + 'iproute2'. if [ "$WAFLAVOR" = "libvirt" ]; then if ! command -v virsh &>/dev/null; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}Please install 'Virtual Machine Manager' to proceed.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Debian/Ubuntu-based systems:" echo -e " ${COMMAND_TEXT}sudo apt install virt-manager${CLEAR_TEXT}" echo "Red Hat/Fedora-based systems:" echo -e " ${COMMAND_TEXT}sudo dnf install virt-manager${CLEAR_TEXT}" echo "Arch Linux systems:" echo -e " ${COMMAND_TEXT}sudo pacman -S virt-manager${CLEAR_TEXT}" echo "Gentoo Linux systems:" echo -e " ${COMMAND_TEXT}sudo emerge --ask app-emulation/virt-manager${CLEAR_TEXT}" echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_MISSING_DEPS" fi if ! command -v ip &>/dev/null; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}Please install 'iproute2' to proceed.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Debian/Ubuntu-based systems:" echo -e " ${COMMAND_TEXT}sudo apt install iproute2${CLEAR_TEXT}" echo "Red Hat/Fedora-based systems:" echo -e " ${COMMAND_TEXT}sudo dnf install iproute${CLEAR_TEXT}" echo "Arch Linux systems:" echo -e " ${COMMAND_TEXT}sudo pacman -S iproute2${CLEAR_TEXT}" echo "Gentoo Linux systems:" echo -e " ${COMMAND_TEXT}sudo emerge --ask net-misc/iproute2${CLEAR_TEXT}" echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_MISSING_DEPS" fi elif [ "$WAFLAVOR" = "docker" ]; then if ! command -v docker &>/dev/null; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}Please install 'Docker Engine' to proceed.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Please visit https://docs.docker.com/engine/install/ for more information." echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_MISSING_DEPS" fi elif [ "$WAFLAVOR" = "podman" ]; then if ! command -v podman-compose &>/dev/null || ! command -v podman &>/dev/null; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}Please install 'podman' and 'podman-compose' to proceed.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Please visit https://podman.io/docs/installation for more information." echo "Please visit https://github.com/containers/podman-compose for more information." echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_MISSING_DEPS" fi fi # Print feedback. echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}" } # Name: 'waCheckGroupMembership' # Role: Ensures the current user is part of the required groups. function waCheckGroupMembership() { # Print feedback. echo -n "Checking whether the user '$(whoami)' is part of the required groups... " # Declare variables. local USER_GROUPS="" # Stores groups the current user belongs to. # Identify groups the current user belongs to. USER_GROUPS=$(groups "$(whoami)") if ! (echo "$USER_GROUPS" | grep -q -E "\blibvirt\b") || ! (echo "$USER_GROUPS" | grep -q -E "\bkvm\b"); then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}GROUP MEMBERSHIP CHECK ERROR.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}The current user '$(whoami)' is not part of group 'libvirt' and/or group 'kvm'.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Please run the below commands, followed by a system reboot:" echo -e "${COMMAND_TEXT}sudo usermod -a -G libvirt $(whoami)${CLEAR_TEXT}" echo -e "${COMMAND_TEXT}sudo usermod -a -G kvm $(whoami)${CLEAR_TEXT}" echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_NOT_IN_GROUP" fi # Print feedback. echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}" } # Name: 'waCheckVMRunning' # Role: Checks the state of the Windows 'libvirt' VM to ensure it is running. function waCheckVMRunning() { # Print feedback. echo -n "Checking the status of the Windows VM... " # Obtain VM Status VM_PAUSED=0 virsh list --state-paused --name | grep -Fxq -- "$VM_NAME" || VM_PAUSED="$?" VM_RUNNING=0 virsh list --state-running --name | grep -Fxq -- "$VM_NAME" || VM_RUNNING="$?" VM_SHUTOFF=0 virsh list --state-shutoff --name | grep -Fxq -- "$VM_NAME" || VM_SHUTOFF="$?" if [[ $VM_SHUTOFF == "0" ]]; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}WINDOWS VM NOT RUNNING.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}The Windows VM '${VM_NAME}' is powered off.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Please run the below command to start the Windows VM:" echo -e "${COMMAND_TEXT}virsh start ${VM_NAME}${CLEAR_TEXT}" echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_VM_OFF" elif [[ $VM_PAUSED == "0" ]]; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}WINDOWS VM NOT RUNNING.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}The Windows VM '${VM_NAME}' is paused.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Please run the below command to resume the Windows VM:" echo -e "${COMMAND_TEXT}virsh resume ${VM_NAME}${CLEAR_TEXT}" echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_VM_PAUSED" elif [[ $VM_RUNNING != "0" ]]; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}WINDOWS VM DOES NOT EXIST.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}The Windows VM '${VM_NAME}' could not be found.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Please ensure a Windows VM with the name '${VM_NAME}' exists." echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_VM_ABSENT" fi # Print feedback. echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}" } # Name: 'waCheckContainerRunning' # Role: Throw an error if the Docker/Podman container is not running. function waCheckContainerRunning() { # Print feedback. echo -n "Checking container status... " # Declare variables. local CONTAINER_STATE="" local COMPOSE_COMMAND="" # Determine the state of the container. CONTAINER_STATE=$("$WAFLAVOR" ps --all --filter name="WinApps" --format '{{.Status}}') CONTAINER_STATE=${CONTAINER_STATE,,} # Convert the string to lowercase. CONTAINER_STATE=${CONTAINER_STATE%% *} # Extract the first word. # Determine the compose command. case "$WAFLAVOR" in "docker") COMPOSE_COMMAND="docker compose" ;; "podman") COMPOSE_COMMAND="podman-compose" ;; esac # Check container state. if [[ "$CONTAINER_STATE" != "up" ]]; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}CONTAINER NOT RUNNING.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}Windows is not running.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Please ensure Windows is powered on:" echo -e "${COMMAND_TEXT}${COMPOSE_COMMAND} --file ~/.config/winapps/compose.yaml start${CLEAR_TEXT}" echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_CONTAINER_OFF" fi # Print feedback. echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}" } # Name: 'waCheckPortOpen' # Role: Assesses whether the RDP port on Windows is open. function waCheckPortOpen() { # Print feedback. echo -n "Checking for an open RDP Port on Windows... " # Declare variables. local VM_MAC="" # Stores the MAC address of the Windows VM. # Obtain Windows VM IP Address (FOR 'libvirt' ONLY) # Note: 'RDP_IP' should not be empty if 'WAFLAVOR' is 'docker', since it is set to localhost before this function is called. if [ -z "$RDP_IP" ] && [ "$WAFLAVOR" = "libvirt" ]; then VM_MAC=$(virsh domiflist "$VM_NAME" | grep -oE "([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})") # VM MAC address. RDP_IP=$(ip neigh show | grep "$VM_MAC" | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}") # VM IP address. if [ -z "$RDP_IP" ]; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}NETWORK CONFIGURATION ERROR.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}The IP address of the Windows VM '${VM_NAME}' could not be found.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Please ensure networking is properly configured for the Windows VM." echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_NO_IP" fi fi # Check for an open RDP port. if ! timeout "$PORT_TIMEOUT" nc -z "$RDP_IP" "$RDP_PORT" &>/dev/null; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}NETWORK CONFIGURATION ERROR.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}Failed to establish a connection with Windows at '${RDP_IP}:${RDP_PORT}'.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Please ensure Remote Desktop is configured on Windows as per the WinApps README." echo -e "Then you can try increasing the ${COMMAND_TEXT}PORT_TIMEOUT${CLEAR_TEXT} in ${COMMAND_TEXT}${CONFIG_PATH}${CLEAR_TEXT}." echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_BAD_PORT" fi # Print feedback. echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}" } # Name: 'waCheckRDPAccess' # Role: Tests if Windows is accessible via RDP. function waCheckRDPAccess() { # Print feedback. echo -n "Attempting to establish a Remote Desktop connection with Windows... " # Declare variables. local FREERDP_LOG="" # Stores the path of the FreeRDP log file. local FREERDP_PROC="" # Stores the FreeRDP process ID. local ELAPSED_TIME="" # Stores the time counter. # Log file path. FREERDP_LOG="${USER_APPDATA_PATH}/FreeRDP_Test_$(date +'%Y%m%d_%H%M_%N').log" # Ensure the output directory exists. mkdir -p "$USER_APPDATA_PATH" # Remove existing 'FreeRDP Connection Test' file. rm -f "$TEST_PATH" # This command should create a file on the host filesystem before terminating the RDP session. This command is silently executed as a background process. # If the file is created, it means Windows received the command via FreeRDP successfully and can read and write to the Linux home folder. # Note: The following final line is expected within the log, indicating successful execution of the 'tsdiscon' command and termination of the RDP session. # [INFO][com.freerdp.core] - [rdp_print_errinfo]: ERRINFO_LOGOFF_BY_USER (0x0000000C):The disconnection was initiated by the user logging off their session on the server. # shellcheck disable=SC2140,SC2027,SC2086 # Disable warnings regarding unquoted strings. $FREERDP_COMMAND \ $RDP_FLAGS_NON_WINDOWS \ /cert:tofu \ /d:"$RDP_DOMAIN" \ /u:"$RDP_USER" \ ${RDP_PASSWORD_ARG:+"$RDP_PASSWORD_ARG"} \ /scale:"$RDP_SCALE" \ +auto-reconnect \ +home-drive \ /app:program:"C:\Windows\System32\cmd.exe",cmd:"/C type NUL > $TEST_PATH_WIN && tsdiscon" \ /v:"$RDP_IP" &>"$FREERDP_LOG" & # Store the FreeRDP process ID. FREERDP_PROC=$! # Initialise the time counter. ELAPSED_TIME=0 # Wait a maximum of $RDP_TIMEOUT seconds for the background process to complete. while [ "$ELAPSED_TIME" -lt "$RDP_TIMEOUT" ]; do # Check if the FreeRDP process is complete or if the test file exists. if ! ps -p "$FREERDP_PROC" &>/dev/null || [ -f "$TEST_PATH" ]; then break fi # Wait for 5 seconds. sleep 5 ELAPSED_TIME=$((ELAPSED_TIME + 5)) done # Check if FreeRDP process is not complete. if ps -p "$FREERDP_PROC" &>/dev/null; then # SIGKILL FreeRDP. kill -9 "$FREERDP_PROC" &>/dev/null fi # Check if test file does not exist. if ! [ -f "$TEST_PATH" ]; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}REMOTE DESKTOP PROTOCOL FAILURE.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}FreeRDP failed to establish a connection with Windows.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo -e "Please view the log at ${COMMAND_TEXT}${FREERDP_LOG}${CLEAR_TEXT}." echo "Troubleshooting Tips:" echo " - Ensure the user is logged out of Windows prior to initiating the WinApps installation." echo " - Ensure the credentials within the WinApps configuration file are correct." echo -e " - Utilise a new certificate by removing relevant certificate(s) in ${COMMAND_TEXT}${HOME}/.config/freerdp/server${CLEAR_TEXT}." echo -e " - Try increasing the ${COMMAND_TEXT}RDP_TIMEOUT${CLEAR_TEXT} in ${COMMAND_TEXT}${CONFIG_PATH}${CLEAR_TEXT}." echo " - If using 'libvirt', ensure the Windows VM is correctly named as specified within the README." echo " - If using 'libvirt', ensure 'Remote Desktop' is enabled within the Windows VM." echo " - If using 'libvirt', ensure you have merged 'RDPApps.reg' into the Windows VM's registry." echo " - If using 'libvirt', try logging into and back out of the Windows VM within 'virt-manager' prior to initiating the WinApps installation." echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_RDP_FAIL" else # Remove the temporary test file. rm -f "$TEST_PATH" fi # Print feedback. echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}" } # Name: 'waFindInstalled' # Role: Identifies installed applications on Windows. function waFindInstalled() { # Print feedback. echo -n "Checking for installed Windows applications... " # Declare variables. local FREERDP_LOG="" # Stores the path of the FreeRDP log file. local FREERDP_PROC="" # Stores the FreeRDP process ID. local ELAPSED_TIME="" # Stores the time counter. # Log file path. FREERDP_LOG="${USER_APPDATA_PATH}/FreeRDP_Scan_$(date +'%Y%m%d_%H%M_%N').log" # Make the output directory if required. mkdir -p "$USER_APPDATA_PATH" # Remove temporary files from previous WinApps installations. rm -f "$BATCH_SCRIPT_PATH" "$TMP_INST_FILE_PATH" "$INST_FILE_PATH" "$PS_SCRIPT_HOME_PATH" "$DETECTED_FILE_PATH" # Copy PowerShell script to a directory within the user's home folder. # This will enable the PowerShell script to be accessed and executed by Windows. cp "$PS_SCRIPT_PATH" "$PS_SCRIPT_HOME_PATH" # Enumerate over each officially supported application. for APPLICATION in ./apps/*; do # Extract the name of the application from the absolute path of the folder. APPLICATION="$(basename "$APPLICATION")" if [[ "$APPLICATION" == "ms-office-protocol-handler.desktop" ]]; then continue fi # Source 'Info' File Containing: # - The Application Name (FULL_NAME) # - The Shortcut Name (NAME) # - Application Categories (CATEGORIES) # - Executable Path (WIN_EXECUTABLE) # - Supported MIME Types (MIME_TYPES) # - Application Icon (ICON) # shellcheck source=/dev/null # Exclude this file from being checked by ShellCheck. source "./apps/${APPLICATION}/info" # Append commands to batch file. echo "IF EXIST \"${WIN_EXECUTABLE}\" ECHO ${APPLICATION}^|^|^|${WIN_EXECUTABLE} >> ${TMP_INST_FILE_PATH_WIN}" >>"$BATCH_SCRIPT_PATH" done # Append a command to the batch script to run the PowerShell script and store its output in the 'detected' file. # shellcheck disable=SC2129 # Silence warning regarding repeated redirects. echo "powershell.exe -ExecutionPolicy Bypass -File ${PS_SCRIPT_HOME_PATH_WIN} > ${DETECTED_FILE_PATH_WIN}" >>"$BATCH_SCRIPT_PATH" # Append a command to the batch script to rename the temporary file containing the names of all detected officially supported applications. echo "RENAME ${TMP_INST_FILE_PATH_WIN} installed" >>"$BATCH_SCRIPT_PATH" # Append a command to the batch script to terminate the remote desktop session once all previous commands are complete. echo "tsdiscon" >>"$BATCH_SCRIPT_PATH" # Silently execute the batch script within Windows in the background (Log Output To File) # Note: The following final line is expected within the log, indicating successful execution of the 'tsdiscon' command and termination of the RDP session. # [INFO][com.freerdp.core] - [rdp_print_errinfo]: ERRINFO_LOGOFF_BY_USER (0x0000000C):The disconnection was initiated by the user logging off their session on the server. # shellcheck disable=SC2140,SC2027,SC2086 # Disable warnings regarding unquoted strings. $FREERDP_COMMAND \ $RDP_FLAGS_NON_WINDOWS \ /cert:tofu \ /d:"$RDP_DOMAIN" \ /u:"$RDP_USER" \ ${RDP_PASSWORD_ARG:+"$RDP_PASSWORD_ARG"} \ /scale:"$RDP_SCALE" \ +auto-reconnect \ +home-drive \ /app:program:"C:\Windows\System32\cmd.exe",cmd:"/C "$BATCH_SCRIPT_PATH_WIN"" \ /v:"$RDP_IP" &>"$FREERDP_LOG" & # Store the FreeRDP process ID. FREERDP_PROC=$! # Initialise the time counter. ELAPSED_TIME=0 # Wait a maximum of $APP_SCAN_TIMEOUT seconds for the batch script to finish running. while [ $ELAPSED_TIME -lt "$APP_SCAN_TIMEOUT" ]; do # Check if the FreeRDP process is complete or if the 'installed' file exists. if ! ps -p "$FREERDP_PROC" &>/dev/null || [ -f "$INST_FILE_PATH" ]; then break fi # Wait for 5 seconds. sleep 5 ELAPSED_TIME=$((ELAPSED_TIME + 5)) done # Check if the FreeRDP process is not complete. if ps -p "$FREERDP_PROC" &>/dev/null; then # SIGKILL FreeRDP. kill -9 "$FREERDP_PROC" &>/dev/null fi # Check if test file does not exist. if ! [ -f "$INST_FILE_PATH" ]; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}APPLICATION QUERY FAILURE.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}Failed to query Windows for installed applications.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo -e "Please view the log at ${COMMAND_TEXT}${FREERDP_LOG}${CLEAR_TEXT}." echo -e "You can try increasing the ${COMMAND_TEXT}APP_SCAN_TIMEOUT${CLEAR_TEXT} in ${COMMAND_TEXT}${CONFIG_PATH}${CLEAR_TEXT}." echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_APPQUERY_FAIL" fi # Print feedback. echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}" } # Name: 'waConfigureWindows' # Role: Create an application entry for launching Windows via Remote Desktop. function waConfigureWindows() { # Print feedback. echo -n "Creating an application entry for Windows... " # Declare variables. local WIN_BASH="" # Stores the bash script to launch a Windows RDP session. local WIN_DESKTOP="" # Stores the '.desktop' file to launch a Windows RDP session. # Populate variables. WIN_BASH="\ #!/usr/bin/env bash ${BIN_PATH}/winapps windows" WIN_DESKTOP="\ [Desktop Entry] Name=Windows Exec=${BIN_PATH}/winapps windows %F Terminal=false Type=Application Icon=${APPDATA_PATH}/icons/windows.svg StartupWMClass=Microsoft Windows Comment=Microsoft Windows RDP Session" # Copy the 'Windows' icon. $SUDO cp "./install/windows.svg" "${APPDATA_PATH}/icons/windows.svg" # Write the desktop entry content to a file. echo "$WIN_DESKTOP" | $SUDO tee "${APP_PATH}/windows.desktop" &>/dev/null # Write the bash script to a file. echo "$WIN_BASH" | $SUDO tee "${BIN_PATH}/windows" &>/dev/null # Mark the bash script as executable. $SUDO chmod a+x "${BIN_PATH}/windows" # Print feedback. echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}" } # Name: 'waConfigureApp' # Role: Create application entries for a given application installed on Windows. function waConfigureApp() { # Declare variables. local APP_ICON="" # Stores the path to the application icon. local APP_BASH="" # Stores the bash script used to launch the application. local APP_DESKTOP_FILE="" # Stores the '.desktop' file used to launch the application. # Source 'Info' File Containing: # - The Application Name (FULL_NAME) # - The Shortcut Name (NAME) # - Application Categories (CATEGORIES) # - Executable Path (WIN_EXECUTABLE) # - Supported MIME Types (MIME_TYPES) # - Application Icon (ICON) # shellcheck source=/dev/null # Exclude this file from being checked by ShellCheck. source "${APPDATA_PATH}/apps/${1}/info" # Determine path to application icon using arguments passed to function. APP_ICON="${APPDATA_PATH}/apps/${1}/icon.${2}" # Determine the content of the bash script for the application. APP_BASH="\ #!/usr/bin/env bash ${BIN_PATH}/winapps ${1}" # Determine the content of the '.desktop' file for the application. APP_DESKTOP_FILE="\ [Desktop Entry] Name=${NAME} Exec=${BIN_PATH}/winapps ${1} %F Terminal=false Type=Application Icon=${APP_ICON} StartupWMClass=${FULL_NAME} Comment=${FULL_NAME} Categories=${CATEGORIES} MimeType=${MIME_TYPES}" # Store the '.desktop' file for the application. echo "$APP_DESKTOP_FILE" | $SUDO tee "${APP_PATH}/${1}.desktop" &>/dev/null # Store the bash script for the application. echo "$APP_BASH" | $SUDO tee "${BIN_PATH}/${1}" &>/dev/null # Mark bash script as executable. $SUDO chmod a+x "${BIN_PATH}/${1}" } # Name: 'waConfigureOfficiallySupported' # Role: Create application entries for officially supported applications installed on Windows. function waConfigureOfficiallySupported() { # Declare variables. local OSA_LIST=() # Stores a list of all officially supported applications installed on Windows. local OFFICE_APPS=("access" "access-o365" "access-o365-x86" "access-x86" "adobe-cc" "acrobat9" "acrobat-x-pro" "aftereffects-cc" "audition-cc" "bridge-cc" "bridge-cc-x86" "bridge-cs6" "bridge-cs6-x86" "cmd" "dymo-connect" "excel" "excel-o365" "excel-o365-x86" "excel-x86" "excel-x86-2010" "explorer" "iexplorer" "illustrator-cc" "lightroom-cc" "linqpad8" "mirc" "mspaint" "onenote" "onenote-o365" "onenote-o365-x86" "onenote-x86" "outlook" "outlook-o365" "outlook-o365-x86" "powerbi" "powerbi-store" "powerpoint" "powerpoint-o365" "powerpoint-o365-x86" "powerpoint-x86" "publisher" "publisher-o365" "publisher-o365-x86" "publisher-x86" "project" "project-x86" "remarkable-desktop" "ssms20" "visual-studio-comm" "visual-studio-ent" "visual-studio-pro" "visio" "visio-x86" "word" "word-o365" "word-o365-x86" "word-x86" "word-x86-2010") # Read the list of officially supported applications that are installed on Windows into an array, returning an empty array if no such files exist. readarray -t OSA_LIST < <(grep -v '^[[:space:]]*$' "$INST_FILE_PATH" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' 2>/dev/null || true) # Create application entries for each officially supported application. for OSA in "${OSA_LIST[@]}"; do # Split the line by the '|||' delimiter local APP_NAME="${OSA%%|||*}" local ACTUAL_WIN_EXECUTABLE="${OSA##*|||}" # If splitting failed for some reason, skip this line to be safe. if [[ -z "$APP_NAME" || -z "$ACTUAL_WIN_EXECUTABLE" ]]; then continue fi # Print feedback using the clean application name. echo -n "Creating an application entry for ${APP_NAME}... " # Copy the original, unmodified application assets. # --no-preserve=mode is needed to avoid missing write permissions when copying from Nix store. $SUDO cp -r --no-preserve=mode "./apps/${APP_NAME}" "${APPDATA_PATH}/apps" local DESTINATION_INFO_FILE="${APPDATA_PATH}/apps/${APP_NAME}/info" # Sanitize the string using pure Bash. This is fast and safe. local SED_SAFE_PATH="${ACTUAL_WIN_EXECUTABLE//&/\\&}" SED_SAFE_PATH="${SED_SAFE_PATH//\\/\\\\}" # Use the sanitized string to safely edit the file. $SUDO sed -i "s|^WIN_EXECUTABLE=.*|WIN_EXECUTABLE=\"${SED_SAFE_PATH}\"|" "$DESTINATION_INFO_FILE" # Configure the application using the clean name. waConfigureApp "$APP_NAME" svg # Check if the application is an Office app and copy the protocol handler. if [[ " ${OFFICE_APPS[*]} " == *" $APP_NAME "* ]]; then # Determine the target directory based on whether the installation is for the system or user. if [[ "$OPT_SYSTEM" -eq 1 ]]; then TARGET_DIR="$SYS_APP_PATH" else TARGET_DIR="$USER_APP_PATH" fi # Copy the protocol handler to the appropriate directory. $SUDO cp "./apps/ms-office-protocol-handler.desktop" "$TARGET_DIR/ms-office-protocol-handler.desktop" fi # Print feedback. echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}" done # Delete 'install' file. rm -f "$INST_FILE_PATH" } # Name: 'waConfigureApps' # Role: Allow the user to select which officially supported applications to configure. function waConfigureApps() { # Declare variables. local OSA_LIST=() # Stores a list of all officially supported applications installed on Windows. local APPS=() # Stores a list of both the simplified and full names of each installed officially supported application. local OPTIONS=() # Stores a list of options presented to the user. local APP_INSTALL="" # Stores the option selected by the user. local SELECTED_APPS=() # Stores the officially supported applications selected by the user. local TEMP_ARRAY=() # Temporary array used for sorting elements of an array. declare -A APP_DATA_MAP # Associative array to map short names back to their full data line. # Read the list of officially supported applications that are installed on Windows into an array, returning an empty array if no such files exist. # This will remove leading and trailing whitespace characters as well as ignore empty lines. readarray -t OSA_LIST < <(grep -v '^[[:space:]]*$' "$INST_FILE_PATH" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' 2>/dev/null || true) # Loop over each officially supported application installed on Windows. for OSA in "${OSA_LIST[@]}"; do # Source 'Info' File Containing: # - The Application Name (FULL_NAME) # - The Shortcut Name (NAME) # - Application Categories (CATEGORIES) # - Executable Path (WIN_EXECUTABLE) # - Supported MIME Types (MIME_TYPES) # - Application Icon (ICON) # Split the line to get the clean application name local APP_NAME="${OSA%%|||*}" local ACTUAL_WIN_EXECUTABLE="${OSA##*|||*}" # If splitting failed, skip this entry. if [[ -z "$APP_NAME" ]]; then continue fi # Use the clean APP_NAME to source the info file # shellcheck source=/dev/null # Exclude this file from being checked by ShellCheck. source "./apps/${APP_NAME}/info" # Add both the simplified and full name of the application to an array. APPS+=("${FULL_NAME} (${APP_NAME})") # Store the original data line in our map so we can retrieve it later. APP_DATA_MAP["$APP_NAME"]="$OSA" # Extract the executable file name (e.g. 'MyApp.exe') from the absolute path. WIN_EXECUTABLE="${ACTUAL_WIN_EXECUTABLE##*\\}" # Trim any leading or trailing whitespace characters from the executable file name. read -r WIN_EXECUTABLE <<<"$(echo "$WIN_EXECUTABLE" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" # Add the executable file name (in lowercase) to the array. INSTALLED_EXES+=("${WIN_EXECUTABLE,,}") done # Sort the 'APPS' array in alphabetical order. IFS=$'\n' # shellcheck disable=SC2207 # Silence warnings regarding preferred use of 'mapfile' or 'read -a'. TEMP_ARRAY=($(sort <<<"${APPS[*]}")) unset IFS APPS=("${TEMP_ARRAY[@]}") # Prompt user to select which officially supported applications to configure. OPTIONS=( "Set up all detected officially supported applications" "Choose specific officially supported applications to set up" "Skip setting up any officially supported applications" ) inqMenu "How would you like to handle officially supported applications?" OPTIONS APP_INSTALL # Remove unselected officially supported applications from the 'install' file. if [[ $APP_INSTALL == "Choose specific officially supported applications to set up" ]]; then inqChkBx "Which officially supported applications would you like to set up?" APPS SELECTED_APPS # Clear/create the 'install' file. echo "" >"$INST_FILE_PATH" # Add each selected officially supported application back to the 'install' file. for SELECTED_APP in "${SELECTED_APPS[@]}"; do # Capture the substring within (but not including) the parentheses. # This substring represents the officially supported application name (see above loop). local SHORT_NAME="${SELECTED_APP##*(}" SHORT_NAME="${SHORT_NAME%%)}" # Use the map to find the original data line (e.g., "word|||C:\...") and write it back. echo "${APP_DATA_MAP[$SHORT_NAME]}" >>"$INST_FILE_PATH" done fi # Configure selected (or all) officially supported applications. if [[ $APP_INSTALL != "Skip setting up any officially supported applications" ]]; then waConfigureOfficiallySupported fi } # Name: 'waConfigureDetectedApps' # Role: Allow the user to select which detected applications to configure. function waConfigureDetectedApps() { # Declare variables. local APPS=() # Stores a list of both the simplified and full names of each detected application. local EXE_FILENAME="" # Stores the executable filename of a given detected application. local EXE_FILENAME_NOEXT="" # Stores the executable filename without the file extension of a given detected application. local EXE_FILENAME_LOWERCASE="" # Stores the executable filename of a given detected application in lowercase letters only. local OPTIONS=() # Stores a list of options presented to the user. local APP_INSTALL="" # Stores the option selected by the user. local SELECTED_APPS=() # Detected applications selected by the user. local APP_DESKTOP_FILE="" # Stores the '.desktop' file used to launch the application. local TEMP_ARRAY=() # Temporary array used for sorting elements of an array. if [ -f "$DETECTED_FILE_PATH" ]; then # On UNIX systems, lines are terminated with a newline character (\n). # On WINDOWS systems, lines are terminated with both a carriage return (\r) and a newline (\n) character. # Remove all carriage returns (\r) within the 'detected' file, as the file was written by Windows. sed -i 's/\r//g' "$DETECTED_FILE_PATH" # Import the detected application information: # - Application Names (NAMES) # - Application Icons in base64 (ICONS) # - Application Executable Paths (EXES) # shellcheck source=/dev/null # Exclude this file from being checked by ShellCheck. source "$DETECTED_FILE_PATH" # shellcheck disable=SC2153 # Silence warnings regarding possible misspellings. for INDEX in "${!NAMES[@]}"; do # Extract the executable file name (e.g. 'MyApp.exe'). EXE_FILENAME=${EXES[$INDEX]##*\\} # Convert the executable file name to lower-case (e.g. 'myapp.exe'). EXE_FILENAME_LOWERCASE="${EXE_FILENAME,,}" # Remove the file extension (e.g. 'MyApp'). EXE_FILENAME_NOEXT="${EXE_FILENAME%.*}" # Check if the executable was previously configured as part of setting up officially supported applications. if [[ " ${INSTALLED_EXES[*]} " != *" ${EXE_FILENAME_LOWERCASE} "* ]]; then # If not previously configured, add the application to the list of detected applications. APPS+=("${NAMES[$INDEX]} (${EXE_FILENAME_NOEXT})") fi done # Sort the 'APPS' array in alphabetical order. IFS=$'\n' # shellcheck disable=SC2207 # Silence warnings regarding preferred use of 'mapfile' or 'read -a'. TEMP_ARRAY=($(sort <<<"${APPS[*]}")) unset IFS APPS=("${TEMP_ARRAY[@]}") # Prompt user to select which other detected applications to configure. OPTIONS=( "Set up all detected applications" "Select which applications to set up" "Do not set up any applications" ) inqMenu "How would you like to handle other detected applications?" OPTIONS APP_INSTALL # Store selected detected applications. if [[ $APP_INSTALL == "Select which applications to set up" ]]; then inqChkBx "Which other applications would you like to set up?" APPS SELECTED_APPS elif [[ $APP_INSTALL == "Set up all detected applications" ]]; then for APP in "${APPS[@]}"; do SELECTED_APPS+=("$APP") done fi for SELECTED_APP in "${SELECTED_APPS[@]}"; do # Capture the substring within (but not including) the parentheses. # This substring represents the executable filename without the file extension (see above loop). EXE_FILENAME_NOEXT="${SELECTED_APP##*(}" EXE_FILENAME_NOEXT="${EXE_FILENAME_NOEXT%%)}" # Capture the substring prior to the space and parentheses. # This substring represents the detected application name (see above loop). PROGRAM_NAME="${SELECTED_APP% (*}" # Loop through all detected applications to find the detected application being processed. for INDEX in "${!NAMES[@]}"; do # Check for a matching detected application entry. if [[ ${NAMES[$INDEX]} == "$PROGRAM_NAME" ]] && [[ ${EXES[$INDEX]} == *"\\$EXE_FILENAME_NOEXT"* ]]; then # Print feedback. echo -n "Creating an application entry for ${PROGRAM_NAME}... " # Create directory to store application icon and information. $SUDO mkdir -p "${APPDATA_PATH}/apps/${EXE_FILENAME_NOEXT}" # Determine the content of the '.desktop' file for the application. APP_DESKTOP_FILE="\ # GNOME Shortcut Name NAME=\"${PROGRAM_NAME}\" # Used for Descriptions and Window Class FULL_NAME=\"${PROGRAM_NAME}\" # Path to executable inside Windows WIN_EXECUTABLE=\"${EXES[$INDEX]}\" # GNOME Categories CATEGORIES=\"WinApps\" # GNOME MIME Types MIME_TYPES=\"\"" # Store the '.desktop' file for the application. echo "$APP_DESKTOP_FILE" | $SUDO tee "${APPDATA_PATH}/apps/${EXE_FILENAME_NOEXT}/info" &>/dev/null # Write application icon to file. echo "${ICONS[$INDEX]}" | base64 -d | $SUDO tee "${APPDATA_PATH}/apps/${EXE_FILENAME_NOEXT}/icon.png" &>/dev/null # Configure the application. waConfigureApp "$EXE_FILENAME_NOEXT" png # Print feedback. echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}" fi done done fi } # Name: 'waInstall' # Role: Installs WinApps. function waInstall() { # Print feedback. echo -e "${BOLD_TEXT}Installing WinApps.${CLEAR_TEXT}" # Check for existing conflicting WinApps installations. waCheckExistingInstall # Load the WinApps configuration file. waLoadConfig # Check for missing dependencies. waCheckInstallDependencies # Update $RDP_SCALE. waFixScale # Append additional FreeRDP flags if required. if [[ -n $RDP_FLAGS ]]; then FREERDP_COMMAND="${FREERDP_COMMAND} ${RDP_FLAGS}" fi # If using 'docker' or 'podman', set RDP_IP to localhost. if [ "$WAFLAVOR" = "docker" ] || [ "$WAFLAVOR" = "podman" ]; then RDP_IP="$DOCKER_IP" fi # If using podman backend, modify the FreeRDP command to enter a new namespace. if [ "$WAFLAVOR" = "podman" ]; then FREERDP_COMMAND="podman unshare --rootless-netns ${FREERDP_COMMAND}" fi if [ "$WAFLAVOR" = "docker" ] || [ "$WAFLAVOR" = "podman" ]; then # Check if Windows is powered on. waCheckContainerRunning elif [ "$WAFLAVOR" = "libvirt" ]; then # Verify the current user's group membership. waCheckGroupMembership # Check if the Windows VM is powered on. waCheckVMRunning elif [ "$WAFLAVOR" = "manual" ]; then waCheckPortOpen else # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}INVALID WINAPPS BACKEND.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}An invalid WinApps backend '${WAFLAVOR}' was specified.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo -e "Please ensure 'WAFLAVOR' is set to 'docker', 'podman' or 'libvirt' in ${COMMAND_TEXT}${CONFIG_PATH}${CLEAR_TEXT}." echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_INVALID_FLAVOR" fi # Check if the RDP port on Windows is open. waCheckPortOpen # Test RDP access to Windows. waCheckRDPAccess # Create required directories. $SUDO mkdir -p "$BIN_PATH" $SUDO mkdir -p "$APP_PATH" $SUDO mkdir -p "$APPDATA_PATH/apps" $SUDO mkdir -p "$APPDATA_PATH/icons" # Check for installed applications. waFindInstalled # Install the WinApps bash scripts. $SUDO ln -sf "${SOURCE_PATH}/bin/winapps" "${BIN_PATH}/winapps" $SUDO ln -sf "${SOURCE_PATH}/setup.sh" "${BIN_PATH}/winapps-setup" # Configure the Windows RDP session application launcher. waConfigureWindows if [ "$OPT_AOSA" -eq 1 ]; then # Automatically configure all officially supported applications. waConfigureOfficiallySupported else # Configure officially supported applications. waConfigureApps # Configure other detected applications. waConfigureDetectedApps fi # Ensure BIN_PATH is on PATH waEnsureOnPath # Print feedback. echo -e "${SUCCESS_TEXT}INSTALLATION COMPLETE.${CLEAR_TEXT}" } # Name: 'waEnsureOnPath' # Role: Ensures that $BIN_PATH is on $PATH. function waEnsureOnPath() { if [[ ":$PATH:" != *":$BIN_PATH:"* ]]; then echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} It seems like '${BIN_PATH}' is not on PATH." echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} You can add it by running:" # shellcheck disable=SC2086 echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} - For Bash: ${COMMAND_TEXT}echo 'export PATH="${BIN_PATH}:\$PATH"' >> ~/.bashrc && source ~/.bashrc${CLEAR_TEXT}" # shellcheck disable=SC2086 echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} - For ZSH: ${COMMAND_TEXT}echo 'export PATH="${BIN_PATH}:\$PATH"' >> ~/.zshrc && source ~/.zshrc${CLEAR_TEXT}" echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} Make sure to restart your Terminal afterwards.\n" fi } # Name: 'waUninstall' # Role: Uninstalls WinApps. function waUninstall() { # Print feedback. [ "$OPT_SYSTEM" -eq 1 ] && echo -e "${BOLD_TEXT}REMOVING SYSTEM INSTALLATION.${CLEAR_TEXT}" [ "$OPT_USER" -eq 1 ] && echo -e "${BOLD_TEXT}REMOVING USER INSTALLATION.${CLEAR_TEXT}" # Determine the target directory for the protocol handler based on the installation type. if [[ "$OPT_SYSTEM" -eq 1 ]]; then TARGET_DIR="$SYS_APP_PATH" else TARGET_DIR="$USER_APP_PATH" fi # Remove the 'ms-office-protocol-handler.desktop' file if it exists. $SUDO rm -f "$TARGET_DIR/ms-office-protocol-handler.desktop" # Declare variables. local WINAPPS_DESKTOP_FILES=() # Stores a list of '.desktop' file paths. local WINAPPS_APP_BASH_SCRIPTS=() # Stores a list of bash script paths. local DESKTOP_FILE_NAME="" # Stores the name of the '.desktop' file for the application. local BASH_SCRIPT_NAME="" # Stores the name of the application. # Remove the 'WinApps' bash scripts. $SUDO rm -f "${BIN_PATH}/winapps" $SUDO rm -f "${BIN_PATH}/winapps-setup" # Remove WinApps configuration data, temporary files and logs. rm -rf "$USER_APPDATA_PATH" # Remove application icons and shortcuts. $SUDO rm -rf "$APPDATA_PATH" # Store '.desktop' files containing "${BIN_PATH}/winapps" in an array, returning an empty array if no such files exist. readarray -t WINAPPS_DESKTOP_FILES < <(grep -l -d skip "${BIN_PATH}/winapps" "${APP_PATH}/"* 2>/dev/null || true) # Remove each '.desktop' file. for DESKTOP_FILE_PATH in "${WINAPPS_DESKTOP_FILES[@]}"; do # Trim leading and trailing whitespace from '.desktop' file path. DESKTOP_FILE_PATH=$(echo "$DESKTOP_FILE_PATH" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') # Extract the file name. DESKTOP_FILE_NAME=$(basename "$DESKTOP_FILE_PATH" | sed 's/\.[^.]*$//') # Print feedback. echo -n "Removing '.desktop' file for '${DESKTOP_FILE_NAME}'... " # Delete the file. $SUDO rm "$DESKTOP_FILE_PATH" # Print feedback. echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}" done # Store the paths of bash scripts calling 'WinApps' to launch specific applications in an array, returning an empty array if no such files exist. readarray -t WINAPPS_APP_BASH_SCRIPTS < <(grep -l -d skip "${BIN_PATH}/winapps" "${BIN_PATH}/"* 2>/dev/null || true) # Remove each bash script. for BASH_SCRIPT_PATH in "${WINAPPS_APP_BASH_SCRIPTS[@]}"; do # Trim leading and trailing whitespace from bash script path. BASH_SCRIPT_PATH=$(echo "$BASH_SCRIPT_PATH" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') # Extract the file name. BASH_SCRIPT_NAME=$(basename "$BASH_SCRIPT_PATH" | sed 's/\.[^.]*$//') # Print feedback. echo -n "Removing bash script for '${BASH_SCRIPT_NAME}'... " # Delete the file. $SUDO rm "$BASH_SCRIPT_PATH" # Print feedback. echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}" done # Print caveats. echo -e "\n${INFO_TEXT}Please note that your WinApps configuration and the WinApps source code were not removed.${CLEAR_TEXT}" echo -e "${INFO_TEXT}You can remove these manually by running:${CLEAR_TEXT}" echo -e "${COMMAND_TEXT}rm -r $(dirname "$CONFIG_PATH")${CLEAR_TEXT}" echo -e "${COMMAND_TEXT}rm -r ${SOURCE_PATH}${CLEAR_TEXT}\n" # Print feedback. echo -e "${SUCCESS_TEXT}UNINSTALLATION COMPLETE.${CLEAR_TEXT}" } # Name: 'waAddApps' # Role: Adds new applications to an existing WinApps installation. function waAddApps() { # Print feedback. echo -e "${BOLD_TEXT}Adding new applications to existing WinApps installation.${CLEAR_TEXT}" # Load the WinApps configuration file. waLoadConfig # Check for missing dependencies. waCheckInstallDependencies # Update $RDP_SCALE. waFixScale # Append additional FreeRDP flags if required. if [[ -n $RDP_FLAGS ]]; then FREERDP_COMMAND="${FREERDP_COMMAND} ${RDP_FLAGS}" fi # If using 'docker' or 'podman', set RDP_IP to localhost. if [ "$WAFLAVOR" = "docker" ] || [ "$WAFLAVOR" = "podman" ]; then RDP_IP="$DOCKER_IP" fi # If using podman backend, modify the FreeRDP command to enter a new namespace. if [ "$WAFLAVOR" = "podman" ]; then FREERDP_COMMAND="podman unshare --rootless-netns ${FREERDP_COMMAND}" fi if [ "$WAFLAVOR" = "docker" ] || [ "$WAFLAVOR" = "podman" ]; then # Check if Windows is powered on. waCheckContainerRunning elif [ "$WAFLAVOR" = "libvirt" ]; then # Verify the current user's group membership. waCheckGroupMembership # Check if the Windows VM is powered on. waCheckVMRunning elif [ "$WAFLAVOR" = "manual" ]; then waCheckPortOpen else # Display the error type. echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}INVALID WINAPPS BACKEND.${CLEAR_TEXT}" # Display the error details. echo -e "${INFO_TEXT}An invalid WinApps backend '${WAFLAVOR}' was specified.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo -e "Please ensure 'WAFLAVOR' is set to 'docker', 'podman' or 'libvirt' in ${COMMAND_TEXT}${CONFIG_PATH}${CLEAR_TEXT}." echo "--------------------------------------------------------------------------------" # Terminate the script. return "$EC_INVALID_FLAVOR" fi # Check if the RDP port on Windows is open. waCheckPortOpen # Test RDP access to Windows. waCheckRDPAccess # Check for installed applications. waFindInstalled # Configure officially supported applications. waConfigureApps # Configure other detected applications. waConfigureDetectedApps # Print feedback. echo -e "${SUCCESS_TEXT}ADDING NEW APPS COMPLETE.${CLEAR_TEXT}" } ### SEQUENTIAL LOGIC ### # Welcome the user. echo -e "${BOLD_TEXT}\ ################################################################################ # # # WinApps Install Wizard # # # ################################################################################ ${CLEAR_TEXT}" # Check dependencies for the script. waCheckScriptDependencies # Source the contents of 'inquirer.sh'. waGetInquirer # Sanitise and parse the user input. waCheckInput "$@" # Configure paths and permissions. waConfigurePathsAndPermissions # Get the source code waGetSourceCode # Install or uninstall WinApps. if [ "$OPT_UNINSTALL" -eq 1 ]; then waUninstall elif [ "$OPT_ADD_APPS" -eq 1 ]; then waAddApps else waInstall fi exit 0