diff --git a/.github/workflows/update.yaml b/.github/workflows/update.yaml new file mode 100644 index 0000000..f939450 --- /dev/null +++ b/.github/workflows/update.yaml @@ -0,0 +1,25 @@ +name: Update submodules +on: + repository_dispatch: + types: update + +jobs: + update: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Update module + run: | + git submodule update --init --recursive --checkout -f + + - name: Commit and push + uses: EndBug/add-and-commit@v9 + with: + add: "WinApps-Launcher" + default_author: github_actions + message: "Update submodules" + push: true + fetch: true diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2c3ac9c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "WinApps-Launcher"] + path = WinApps-Launcher + url = https://github.com/winapps-org/WinApps-Launcher.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b5f2edb --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,50 @@ +ci: + autoupdate_branch: "rewrite" + +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 + - id: remove-crlf + - id: forbid-tabs + - id: remove-tabs + args: [ --whitespaces-count, "4" ] + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.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 + - 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.8.0-1 + hooks: + - id: shfmt + args: ["-i", "4", "-ci", "-s"] + + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.10.0.1 + hooks: + - id: shellcheck diff --git a/README.md b/README.md index 5b727f5..454666d 100644 --- a/README.md +++ b/README.md @@ -1,174 +1,481 @@ -# winapps -The winapps main project -Originally created by fmstrat https://github.com/Fmstrat/winapps/ +

+

+
-Run Windows apps such as Microsoft Office/Adobe in Linux (Ubuntu/Fedora) and GNOME/KDE as if they were a part of the native OS, including Nautilus integration for right clicking on files of specific mime types to open them. +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.

-## How it works -WinApps was created as an easy, one command way to include apps running inside a VM (or on any RDP server) directly into GNOME as if they were native applications. WinApps works by: -- Running a Windows RDP server in a background VM container -- Checking the RDP server for installed applications such as Microsoft Office -- If those programs are installed, it creates shortcuts leveraging FreeRDP for both the CLI and the GNOME tray -- Files in your home directory are accessible via the `\\tsclient\home` mount inside the VM -- You can right click on any files in your home directory to open with an application, too +## 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. -## Currently supported applications -### WinApps supports ***ANY*** installed application on your system. +## 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. -It does this by: -1. Scanning your system for offically configured applications (below) -2. Scanning your system for any other EXE files with install records in the Windows Registry +## Supported Applications +**WinApps supports *ALL* Windows applications.** -Any officially configured applications will have support for high-resolution icons and mime types for automatically detecting what files can be opened by each application. Any other detected executable files will leverage the icons pulled from the EXE. +Universal application support is achieved by: +1. Scanning Windows for any officially supported applications (list below). +2. Scanning Windows for any other `.exe` files listed within the Windows Registry. -Note: The officially configured application list below is fueled by the community, and therefore some apps may be untested by the WinApps team. +Officially supported 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 officially supported applications is community-driven. As such, some applications may not be tested and verified by the WinApps team.* + +### Officially Supported Applications - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Adobe Acrobat Pro
(X)
Adobe Acrobat Reader
(DC)
Adobe After Effects
(CC)
Adobe Audition
(CC)
Adobe Bridge
(CS6, CC)
Adobe Creative Cloud
(CC)
Adobe Illustrator
(CC)
Adobe InDesign
(CC)
Adobe Lightroom
(CC)
Command Prompt
(cmd.exe)
Explorer
(File Manager)
Internet Explorer
(11)
Microsoft Access
(2016, 2019, o365)
Microsoft Excel
(2016, 2019, o365)
Microsoft Word
(2016, 2019, o365)
Microsoft OneNote
(2016, 2019, o365)
Microsoft Outlook
(2016, 2019, o365)
Microsoft PowerPoint
(2016, 2019, o365)
Microsoft Publisher
(2016, 2019, o365)
Powershell
Windows
(Full RDP session)
  
+ + + 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)
+ 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. +
+ + + 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. -### Step 1: Set up a Windows Virtual Machine -The best solution for running a VM as a subsystem for WinApps would be KVM. KVM is a CPU and memory-efficient virtualization engine bundled with most major Linux distributions. To set up the VM for WinApps, follow this guide: +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) -- [Creating a Virtual Machine in KVM](docs/KVM.md) +If you already have a Windows VM or server you wish to use with WinApps, you will need to merge `install/RDPApps.reg` into the Windows Registry manually. -If you already have a Virtual Machine or server you wish to use with WinApps, you will need to merge `install/RDPApps.reg` into the VM's Windows Registry. If this VM is in KVM and you want to use auto-IP detection, you will need to name the machine `RDPWindows`. Directions for both of these can be found in the guide linked above. +### Step 2: Clone WinApps Repository and Dependencies +1. Clone the WinApps GitHub repository. + ```bash + git clone --recurse-submodules --remote-submodules https://github.com/winapps-org/winapps.git && cd winapps + ``` -### Step 2: Download the repo and prerequisites -To get things going, use: -``` bash -sudo apt install -y freerdp2-x11 -git clone https://github.com/winapps-org/winapps.git -cd winapps +2. Install the required dependencies. + - Debian/Ubuntu: + ```bash + sudo apt install -y dialog freerdp3-x11 iproute2 libnotify-bin netcat + ``` + - Fedora/RHEL: + ```bash + sudo dnf install -y dialog freerdp iproute libnotify nmap-ncat + ``` + - Arch Linux: + ```bash + sudo pacman -Syu --needed -y dialog freerdp iproute2 libnotify gnu-netcat + ``` + - Gentoo Linux: + ```bash + sudo emerge --ask=n sys-libs/dialog net-misc/freerdp:3 net-misc/iproute2 x11-libs/libnotify net-analyzer/netcat + ``` + +- `Docker Engine`: https://docs.docker.com/engine/install/ +- `Podman`: https://podman.io/docs/installation +- `podman-compose`: https://github.com/containers/podman-compose + +> [!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` ``` -### Step 3: Creating your WinApps configuration file -You will need to create a `~/.config/winapps/winapps.conf` configuration file with the following information in it: -``` bash + +### 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] RDP_PASS="MyWindowsPassword" -#RDP_DOMAIN="MYDOMAIN" -#RDP_IP="192.168.123.111" -#RDP_SCALE=100 -#RDP_FLAGS="" -#MULTIMON="true" -#DEBUG="true" + +# [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="" + +# [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" + +# [ADDITIONAL FREERDP FLAGS & ARGUMENTS] +# DEFAULT VALUE: '' (BLANK) +# VALID VALUES: See https://github.com/awakecoding/FreeRDP-Manuals/blob/master/User/FreeRDP-User-Manual.markdown +RDP_FLAGS="" + +# [MULTIPLE MONITORS] +# NOTES: +# - If enabled, a FreeRDP bug *might* produce a black screen. +# DEFAULT VALUE: 'false' +# VALID VALUES: +# - 'true' +# - 'false' +MULTIMON="false" + +# [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 'docker' and 'manual'. +# - See https://github.com/dockur/windows/issues/674 +# 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="" ``` -The username and password should be a full user account and password, such as the one created when setting up Windows or a domain user. It cannot be a user/PIN combination as those are not valid for RDP access. -Options: -- When using a pre-existing non-KVM RDP server, you can use the `RDP_IP` to specify it's location -- If you are running a VM in KVM with NAT enabled, leave `RDP_IP` commented out and WinApps will auto-detect the right local IP -- 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 [100|140|160|180] -- To add flags to the FreeRDP call, such as `/audio-mode:1` to pass in a mic, use the `RDP_FLAGS` configuration option -- For multi-monitor setups, you can try enabling `MULTIMON`, however if you get a black screen (FreeRDP bug) you will need to revert back -- If you enable `DEBUG`, a log will be created on each application start in `~/.local/share/winapps/winapps.log` +> [!NOTE] +> `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. -### Step 4: Run the WinApps installer -Lastly, check that FreeRDP can connect with: -``` -bin/winapps check -``` -You will see output from FreeRDP, as well as potentially have to accept the initial certificate. After that, a Windows Explorer window should pop up. You can close this window and press `Ctrl-C` to cancel out of FreeRDP. +> [!NOTE] +> If you wish to use an alternative WinApps backend (other than `Docker`), uncomment and change `WAFLAVOR="docker"` to `WAFLAVOR="podman"` or `WAFLAVOR="libvirt"`. -If this step fails, try restarting the VM, or your problem could be related to: -- You need to accept the security cert the first time you connect (with 'check') -- Not enabling RDP in the Windows VM -- Not being able to connect to the IP of the VM -- Incorrect user credentials in `~/.config/winapps/winapps.conf` -- Not merging `install/RDPApps.reg` into the VM +#### 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 enabling `MULTIMON`. A FreeRDP bug may result in a black screen however, in which case you should revert this change. +- 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`. -Then the final step is to run the installer which will prompt you for a system or user install: -``` bash +### Step 4: Run the WinApps Installer +Run the WinApps installer. +```bash ./installer.sh ``` -This will take you through the following process: - +A list of supported additional arguments can be accessed by running `./installer.sh --help`. +WinApps Installer Animation. -## Adding pre-defined applications -Adding applications with custom icons and mime types to the installer is easy. Simply copy one of the application configurations in the `apps` folder, and: -- Edit the variables for the application -- Replace the `icon.svg` with an SVG for the application (appropriately licensed) -- Re-run the installer -- Submit a Pull Request to add it to WinApps officially +## 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 an officially supported application once you have tested and verified your configuration (optional, but encouraged). -When running the installer, it will check for if any configured apps are installed, and if they are it will create the appropriate shortcuts on the host OS. +## 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. -## Running applications manually -WinApps offers a manual mode for running applications that are not configured. This is completed with the `manual` flag. Executables that are in the path do not require full path definition. -``` bash +```bash ./bin/winapps manual "C:\my\directory\executableNotInPath.exe" ./bin/winapps manual executableInPath.exe ``` -## Checking for new application support -The installer can be run multiple times, so simply run the below again and it will remove any current installations and update for the latest applications. -``` bash -./installer.sh -``` +## 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. -## Optional installer command line arguments -The following optional commands can be used to manage your application configurations without prompts: -``` bash -./installer.sh --user # Configure applications for the current user -./installer.sh --system # Configure applications for the entire system -./installer.sh --user --uninstall # Remove all configured applications for the current user -./installer.sh --system --uninstall # Remove all configured applications for the entire system -``` - -## Shout outs -- Some icons pulled from - - Fluent UI React - Icons under [MIT License](https://github.com/Fmstrat/fluent-ui-react/blob/master/LICENSE.md) - - Fluent UI - Icons under [MIT License](https://github.com/Fmstrat/fluentui/blob/master/LICENSE) with [restricted use](https://static2.sharepointonline.com/files/fabric/assets/microsoft_fabric_assets_license_agreement_nov_2019.pdf) - - PKief's VSCode Material Icon Theme - Icons under [MIT License](https://github.com/Fmstrat/vscode-material-icon-theme/blob/master/LICENSE.md) - - DiemenDesign's LibreICONS - Icons under [MIT License](https://github.com/Fmstrat/LibreICONS/blob/master/LICENSE) - +## Star History + + + + + Star History Chart + + diff --git a/WinApps-Launcher b/WinApps-Launcher new file mode 160000 index 0000000..eaa660d --- /dev/null +++ b/WinApps-Launcher @@ -0,0 +1 @@ +Subproject commit eaa660d39bf3f49b136c98c87c35e3e12f118f8f diff --git a/apps/access-o365-x86/icon.svg b/apps/access-o365-x86/icon.svg index 2e7240d..3aa845c 100644 --- a/apps/access-o365-x86/icon.svg +++ b/apps/access-o365-x86/icon.svg @@ -1,3 +1,18 @@ - - + + + + + + + + + + + + + + + + + diff --git a/apps/access-o365/icon.svg b/apps/access-o365/icon.svg index 2e7240d..3aa845c 100644 --- a/apps/access-o365/icon.svg +++ b/apps/access-o365/icon.svg @@ -1,3 +1,18 @@ - - + + + + + + + + + + + + + + + + + diff --git a/apps/access-x86/icon.svg b/apps/access-x86/icon.svg index 2e7240d..3aa845c 100644 --- a/apps/access-x86/icon.svg +++ b/apps/access-x86/icon.svg @@ -1,3 +1,18 @@ - - + + + + + + + + + + + + + + + + + diff --git a/apps/access/icon.svg b/apps/access/icon.svg index 2e7240d..3aa845c 100644 --- a/apps/access/icon.svg +++ b/apps/access/icon.svg @@ -1,3 +1,18 @@ - - + + + + + + + + + + + + + + + + + diff --git a/apps/acrobat-x-pro/icon.svg b/apps/acrobat-x-pro/icon.svg old mode 100755 new mode 100644 index 9c971f6..69113f8 --- a/apps/acrobat-x-pro/icon.svg +++ b/apps/acrobat-x-pro/icon.svg @@ -1,3 +1 @@ - - - + diff --git a/apps/adobe-cc/icon.svg b/apps/adobe-cc/icon.svg old mode 100755 new mode 100644 index 9272652..a846274 --- a/apps/adobe-cc/icon.svg +++ b/apps/adobe-cc/icon.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/apps/adobe-cc/info b/apps/adobe-cc/info index a1a0e9f..42a62ba 100644 --- a/apps/adobe-cc/info +++ b/apps/adobe-cc/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="" + +# System Icon +ICON="AdobeUpdate" diff --git a/apps/aftereffects-cc/icon.svg b/apps/aftereffects-cc/icon.svg old mode 100755 new mode 100644 index 6b42dd7..9e7d96f --- a/apps/aftereffects-cc/icon.svg +++ b/apps/aftereffects-cc/icon.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/apps/aftereffects-cc/info b/apps/aftereffects-cc/info index f40e513..7181324 100644 --- a/apps/aftereffects-cc/info +++ b/apps/aftereffects-cc/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="application/vnd.adobe.aftereffects.project;application/vnd.adobe.aftereffects.template;" + +# System Icon +ICON="AdobeAfterEffect" diff --git a/apps/audition-cc/icon.svg b/apps/audition-cc/icon.svg old mode 100755 new mode 100644 index 01c32a6..bc765e5 --- a/apps/audition-cc/icon.svg +++ b/apps/audition-cc/icon.svg @@ -1 +1,39 @@ - \ No newline at end of file + + + + + + + + + + +]> + + + + + + + + + + + + + + + diff --git a/apps/audition-cc/info b/apps/audition-cc/info index 8551913..b3bdde4 100644 --- a/apps/audition-cc/info +++ b/apps/audition-cc/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="" + +# System Icon +ICON="AdobeAudition" diff --git a/apps/bridge-cc/icon.svg b/apps/bridge-cc/icon.svg old mode 100755 new mode 100644 index 6a84294..9a107de --- a/apps/bridge-cc/icon.svg +++ b/apps/bridge-cc/icon.svg @@ -1 +1,25 @@ - \ No newline at end of file + + + + + + + + diff --git a/apps/bridge-cc/info b/apps/bridge-cc/info index 48379a0..b4a8f09 100644 --- a/apps/bridge-cc/info +++ b/apps/bridge-cc/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="image/vnd.adobe.photoshop;" + +# System Icon +ICON="AdobeBridge" diff --git a/apps/bridge-cs6-x86/icon.svg b/apps/bridge-cs6-x86/icon.svg old mode 100755 new mode 100644 index 6a84294..9a107de --- a/apps/bridge-cs6-x86/icon.svg +++ b/apps/bridge-cs6-x86/icon.svg @@ -1 +1,25 @@ - \ No newline at end of file + + + + + + + + diff --git a/apps/bridge-cs6-x86/info b/apps/bridge-cs6-x86/info index 5f72104..1d8cf59 100644 --- a/apps/bridge-cs6-x86/info +++ b/apps/bridge-cs6-x86/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="image/vnd.adobe.photoshop;" + +# System Icon +ICON="AdobeBridge" diff --git a/apps/bridge-cs6/icon.svg b/apps/bridge-cs6/icon.svg old mode 100755 new mode 100644 index 6a84294..9a107de --- a/apps/bridge-cs6/icon.svg +++ b/apps/bridge-cs6/icon.svg @@ -1 +1,25 @@ - \ No newline at end of file + + + + + + + + diff --git a/apps/bridge-cs6/info b/apps/bridge-cs6/info index 17009f0..3fa52e8 100644 --- a/apps/bridge-cs6/info +++ b/apps/bridge-cs6/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="image/vnd.adobe.photoshop;" + +# System Icon +ICON="AdobeBridge" diff --git a/apps/cmd/icon.svg b/apps/cmd/icon.svg index db435c4..2a50f0f 100644 --- a/apps/cmd/icon.svg +++ b/apps/cmd/icon.svg @@ -1,3 +1,90 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/cmd/info b/apps/cmd/info index eda4064..08e2e87 100644 --- a/apps/cmd/info +++ b/apps/cmd/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Windows" # GNOME mimetypes MIME_TYPES="" + +# System Icon +ICON="Terminal" diff --git a/apps/excel-o365-x86/icon.svg b/apps/excel-o365-x86/icon.svg index 2992c1f..55165ee 100644 --- a/apps/excel-o365-x86/icon.svg +++ b/apps/excel-o365-x86/icon.svg @@ -1 +1,48 @@ - + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/excel-o365-x86/info b/apps/excel-o365-x86/info index a913aa0..94b8372 100644 --- a/apps/excel-o365-x86/info +++ b/apps/excel-o365-x86/info @@ -12,3 +12,6 @@ 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 diff --git a/apps/excel-o365/icon.svg b/apps/excel-o365/icon.svg index 2992c1f..55165ee 100644 --- a/apps/excel-o365/icon.svg +++ b/apps/excel-o365/icon.svg @@ -1 +1,48 @@ - + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/excel-o365/info b/apps/excel-o365/info index de6b82b..e0e67e5 100644 --- a/apps/excel-o365/info +++ b/apps/excel-o365/info @@ -12,3 +12,6 @@ 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" diff --git a/apps/excel-x86/icon.svg b/apps/excel-x86/icon.svg index 2992c1f..55165ee 100644 --- a/apps/excel-x86/icon.svg +++ b/apps/excel-x86/icon.svg @@ -1 +1,48 @@ - + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/excel-x86/info b/apps/excel-x86/info index ceb3b6c..bfdb56a 100644 --- a/apps/excel-x86/info +++ b/apps/excel-x86/info @@ -12,3 +12,6 @@ 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" diff --git a/apps/excel/icon.svg b/apps/excel/icon.svg index 2992c1f..55165ee 100644 --- a/apps/excel/icon.svg +++ b/apps/excel/icon.svg @@ -1 +1,48 @@ - + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/excel/info b/apps/excel/info index f397b76..9ef5cb4 100644 --- a/apps/excel/info +++ b/apps/excel/info @@ -12,3 +12,6 @@ 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" diff --git a/apps/explorer/icon.svg b/apps/explorer/icon.svg index 4f1252f..24299b0 100644 --- a/apps/explorer/icon.svg +++ b/apps/explorer/icon.svg @@ -1 +1,39 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/iexplorer/icon.svg b/apps/iexplorer/icon.svg index 71882d5..898b7b0 100644 --- a/apps/iexplorer/icon.svg +++ b/apps/iexplorer/icon.svg @@ -1,3 +1,16 @@ - - + + + + + + + image/svg+xml + + + + + + + + diff --git a/apps/iexplorer/info b/apps/iexplorer/info index 7de8b24..2da385e 100644 --- a/apps/iexplorer/info +++ b/apps/iexplorer/info @@ -8,7 +8,7 @@ FULL_NAME="Internet Explorer" WIN_EXECUTABLE="C:\Program Files\Internet Explorer\iexplore.exe" # GNOME categories -CATEGORIES="WinApps;Office" +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;" diff --git a/apps/illustrator-cc/icon.svg b/apps/illustrator-cc/icon.svg old mode 100755 new mode 100644 index 7ea299b..96672b7 --- a/apps/illustrator-cc/icon.svg +++ b/apps/illustrator-cc/icon.svg @@ -1 +1,5 @@ - \ No newline at end of file + + + + + diff --git a/apps/illustrator-cc/info b/apps/illustrator-cc/info index 701ab29..58af241 100644 --- a/apps/illustrator-cc/info +++ b/apps/illustrator-cc/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="application/illustrator;" + +# System Icon +ICON="AdobeIllustrator" diff --git a/apps/indesign-cc/icon.svg b/apps/indesign-cc/icon.svg old mode 100755 new mode 100644 index f26dead..1fcb3b2 --- a/apps/indesign-cc/icon.svg +++ b/apps/indesign-cc/icon.svg @@ -1 +1,16 @@ - \ No newline at end of file + + +Adobe InDesign CC logo + + + + + + + + + + diff --git a/apps/indesign-cc/info b/apps/indesign-cc/info index 7829791..481b2dc 100644 --- a/apps/indesign-cc/info +++ b/apps/indesign-cc/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="application/x-adobe-indesign-interchange;application/x-adobe-indesign;" + +# System Icon +ICON="AdobeIndesign" diff --git a/apps/lightroom-cc/icon.svg b/apps/lightroom-cc/icon.svg old mode 100755 new mode 100644 index 9a02e00..724e669 --- a/apps/lightroom-cc/icon.svg +++ b/apps/lightroom-cc/icon.svg @@ -1 +1,36 @@ - \ No newline at end of file + + + + + + + + + + +]> + + + + + + + + + + + + + + + diff --git a/apps/lightroom-cc/info b/apps/lightroom-cc/info index d4f19f3..a2cdb25 100644 --- a/apps/lightroom-cc/info +++ b/apps/lightroom-cc/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="" + +# System Icon +ICON="AdobeLightroom" diff --git a/apps/onenote-o365-x86/icon.svg b/apps/onenote-o365-x86/icon.svg index b76f325..44650aa 100644 --- a/apps/onenote-o365-x86/icon.svg +++ b/apps/onenote-o365-x86/icon.svg @@ -1,3 +1,48 @@ - - - \ No newline at end of file + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/onenote-o365-x86/info b/apps/onenote-o365-x86/info index 7a2553d..335e780 100644 --- a/apps/onenote-o365-x86/info +++ b/apps/onenote-o365-x86/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/msonenote;" + +# System Icon +ICON="ms-outlook" diff --git a/apps/onenote-o365/icon.svg b/apps/onenote-o365/icon.svg index b76f325..44650aa 100644 --- a/apps/onenote-o365/icon.svg +++ b/apps/onenote-o365/icon.svg @@ -1,3 +1,48 @@ - - - \ No newline at end of file + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/onenote-o365/info b/apps/onenote-o365/info index 3003121..6626816 100644 --- a/apps/onenote-o365/info +++ b/apps/onenote-o365/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/msonenote;" + +# System Icon +ICON="ms-outlook" diff --git a/apps/onenote-x86/icon.svg b/apps/onenote-x86/icon.svg index b76f325..44650aa 100644 --- a/apps/onenote-x86/icon.svg +++ b/apps/onenote-x86/icon.svg @@ -1,3 +1,48 @@ - - - \ No newline at end of file + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/onenote-x86/info b/apps/onenote-x86/info index fde6517..e535123 100644 --- a/apps/onenote-x86/info +++ b/apps/onenote-x86/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/msonenote;" + +# System Icon +ICON="ms-onenote" diff --git a/apps/onenote/icon.svg b/apps/onenote/icon.svg index b76f325..44650aa 100644 --- a/apps/onenote/icon.svg +++ b/apps/onenote/icon.svg @@ -1,3 +1,48 @@ - - - \ No newline at end of file + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/onenote/info b/apps/onenote/info index 1165128..908f58e 100644 --- a/apps/onenote/info +++ b/apps/onenote/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/msonenote;" + +# System Icon +ICON="ms-outlook" diff --git a/apps/outlook-o365-x86/icon.svg b/apps/outlook-o365-x86/icon.svg index a459409..5649e66 100644 --- a/apps/outlook-o365-x86/icon.svg +++ b/apps/outlook-o365-x86/icon.svg @@ -1,3 +1,35 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/outlook-o365-x86/info b/apps/outlook-o365-x86/info index 14422af..9a528f4 100644 --- a/apps/outlook-o365-x86/info +++ b/apps/outlook-o365-x86/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-outlook;application/octet-stream;" + +# System Icon +ICON="ms-outlook" diff --git a/apps/outlook-o365/icon.svg b/apps/outlook-o365/icon.svg index a459409..5649e66 100644 --- a/apps/outlook-o365/icon.svg +++ b/apps/outlook-o365/icon.svg @@ -1,3 +1,35 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/outlook-o365/info b/apps/outlook-o365/info index 9893d42..4763b83 100644 --- a/apps/outlook-o365/info +++ b/apps/outlook-o365/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-outlook;application/octet-stream;" + +# System Icon +ICON="ms-outlook" diff --git a/apps/outlook-x86/icon.svg b/apps/outlook-x86/icon.svg index a459409..5649e66 100644 --- a/apps/outlook-x86/icon.svg +++ b/apps/outlook-x86/icon.svg @@ -1,3 +1,35 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/outlook-x86/info b/apps/outlook-x86/info index 1431add..4874db3 100644 --- a/apps/outlook-x86/info +++ b/apps/outlook-x86/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-outlook;application/octet-stream;" + +# System Icon +ICON="ms-outlook" diff --git a/apps/outlook/icon.svg b/apps/outlook/icon.svg index a459409..5649e66 100644 --- a/apps/outlook/icon.svg +++ b/apps/outlook/icon.svg @@ -1,3 +1,35 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/outlook/info b/apps/outlook/info index f3a4f43..43453c6 100644 --- a/apps/outlook/info +++ b/apps/outlook/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Office" # GNOME mimetypes MIME_TYPES="application/vnd.ms-outlook;application/octet-stream;" + +# System Icon +ICON="ms-outlook" diff --git a/apps/photoshop-cc/icon.svg b/apps/photoshop-cc/icon.svg index a9d19f7..e30aee3 100644 --- a/apps/photoshop-cc/icon.svg +++ b/apps/photoshop-cc/icon.svg @@ -1 +1,37 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + diff --git a/apps/photoshop-cc/info b/apps/photoshop-cc/info index 45fc878..a7413d6 100644 --- a/apps/photoshop-cc/info +++ b/apps/photoshop-cc/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="image/vnd.adobe.photoshop;" + +# System Icon +ICON="AdobePhotoshop" diff --git a/apps/photoshop-cs6-x86/icon.svg b/apps/photoshop-cs6-x86/icon.svg index a9d19f7..e30aee3 100644 --- a/apps/photoshop-cs6-x86/icon.svg +++ b/apps/photoshop-cs6-x86/icon.svg @@ -1 +1,37 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + diff --git a/apps/photoshop-cs6-x86/info b/apps/photoshop-cs6-x86/info index d4fd734..16d8911 100644 --- a/apps/photoshop-cs6-x86/info +++ b/apps/photoshop-cs6-x86/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="image/vnd.adobe.photoshop;" + +# System Icon +ICON="AdobePhotoshop" diff --git a/apps/photoshop-cs6/icon.svg b/apps/photoshop-cs6/icon.svg index a9d19f7..e30aee3 100644 --- a/apps/photoshop-cs6/icon.svg +++ b/apps/photoshop-cs6/icon.svg @@ -1 +1,37 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + diff --git a/apps/photoshop-cs6/info b/apps/photoshop-cs6/info index a1ae9e6..24cb454 100644 --- a/apps/photoshop-cs6/info +++ b/apps/photoshop-cs6/info @@ -12,3 +12,6 @@ CATEGORIES="WinApps;Adobe" # GNOME mimetypes MIME_TYPES="image/vnd.adobe.photoshop;" + +# System Icon +ICON="AdobePhotoshop" diff --git a/apps/powerpoint-o365-x86/icon.svg b/apps/powerpoint-o365-x86/icon.svg index 4533ac3..45006ef 100644 --- a/apps/powerpoint-o365-x86/icon.svg +++ b/apps/powerpoint-o365-x86/icon.svg @@ -1,3 +1,52 @@ - - + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/powerpoint-o365-x86/info b/apps/powerpoint-o365-x86/info index 8496695..755acc6 100644 --- a/apps/powerpoint-o365-x86/info +++ b/apps/powerpoint-o365-x86/info @@ -12,3 +12,6 @@ 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" diff --git a/apps/powerpoint-o365/icon.svg b/apps/powerpoint-o365/icon.svg index 4533ac3..45006ef 100644 --- a/apps/powerpoint-o365/icon.svg +++ b/apps/powerpoint-o365/icon.svg @@ -1,3 +1,52 @@ - - + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/powerpoint-o365/info b/apps/powerpoint-o365/info index 45b7916..a0d25d2 100644 --- a/apps/powerpoint-o365/info +++ b/apps/powerpoint-o365/info @@ -12,3 +12,6 @@ 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" diff --git a/apps/powerpoint-x86/icon.svg b/apps/powerpoint-x86/icon.svg index 4533ac3..45006ef 100644 --- a/apps/powerpoint-x86/icon.svg +++ b/apps/powerpoint-x86/icon.svg @@ -1,3 +1,52 @@ - - + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/powerpoint-x86/info b/apps/powerpoint-x86/info index 1170358..21ba721 100644 --- a/apps/powerpoint-x86/info +++ b/apps/powerpoint-x86/info @@ -12,3 +12,6 @@ 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" diff --git a/apps/powerpoint/icon.svg b/apps/powerpoint/icon.svg index 4533ac3..45006ef 100644 --- a/apps/powerpoint/icon.svg +++ b/apps/powerpoint/icon.svg @@ -1,3 +1,52 @@ - - + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/powerpoint/info b/apps/powerpoint/info index 17f1cba..6910809 100644 --- a/apps/powerpoint/info +++ b/apps/powerpoint/info @@ -12,3 +12,6 @@ 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" diff --git a/apps/powershell/icon.svg b/apps/powershell/icon.svg index a423aca..6e91cf7 100644 --- a/apps/powershell/icon.svg +++ b/apps/powershell/icon.svg @@ -1 +1 @@ - + diff --git a/apps/project-x86/icon.svg b/apps/project-x86/icon.svg new file mode 100644 index 0000000..d544135 --- /dev/null +++ b/apps/project-x86/icon.svg @@ -0,0 +1,52 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/project-x86/info b/apps/project-x86/info new file mode 100644 index 0000000..ad1614e --- /dev/null +++ b/apps/project-x86/info @@ -0,0 +1,14 @@ +# GNOME shortcut name +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;" diff --git a/apps/project/icon.svg b/apps/project/icon.svg new file mode 100644 index 0000000..d544135 --- /dev/null +++ b/apps/project/icon.svg @@ -0,0 +1,52 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/project/info b/apps/project/info new file mode 100644 index 0000000..d5d904b --- /dev/null +++ b/apps/project/info @@ -0,0 +1,14 @@ +# 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;" diff --git a/apps/publisher-o365-x86/icon.svg b/apps/publisher-o365-x86/icon.svg index 6706891..aa3146c 100644 --- a/apps/publisher-o365-x86/icon.svg +++ b/apps/publisher-o365-x86/icon.svg @@ -1,3 +1,58 @@ - - + + + + + + + + + + +]> + + + + + + + + +Publisher_64x + + + + + + + + + + + + + + + + + diff --git a/apps/publisher-o365/icon.svg b/apps/publisher-o365/icon.svg index 6706891..aa3146c 100644 --- a/apps/publisher-o365/icon.svg +++ b/apps/publisher-o365/icon.svg @@ -1,3 +1,58 @@ - - + + + + + + + + + + +]> + + + + + + + + +Publisher_64x + + + + + + + + + + + + + + + + + diff --git a/apps/publisher-x86/icon.svg b/apps/publisher-x86/icon.svg index 6706891..aa3146c 100644 --- a/apps/publisher-x86/icon.svg +++ b/apps/publisher-x86/icon.svg @@ -1,3 +1,58 @@ - - + + + + + + + + + + +]> + + + + + + + + +Publisher_64x + + + + + + + + + + + + + + + + + diff --git a/apps/publisher/icon.svg b/apps/publisher/icon.svg index 6706891..aa3146c 100644 --- a/apps/publisher/icon.svg +++ b/apps/publisher/icon.svg @@ -1,3 +1,58 @@ - - + + + + + + + + + + +]> + + + + + + + + +Publisher_64x + + + + + + + + + + + + + + + + + diff --git a/apps/visio-x86/icon.svg b/apps/visio-x86/icon.svg new file mode 100644 index 0000000..7e9680f --- /dev/null +++ b/apps/visio-x86/icon.svg @@ -0,0 +1,64 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/visio-x86/info b/apps/visio-x86/info new file mode 100644 index 0000000..dc8a193 --- /dev/null +++ b/apps/visio-x86/info @@ -0,0 +1,14 @@ +# 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;" diff --git a/apps/visio/icon.svg b/apps/visio/icon.svg new file mode 100644 index 0000000..7e9680f --- /dev/null +++ b/apps/visio/icon.svg @@ -0,0 +1,64 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/visio/info b/apps/visio/info new file mode 100644 index 0000000..a7ad0be --- /dev/null +++ b/apps/visio/info @@ -0,0 +1,14 @@ +# 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;" diff --git a/apps/visual-studio-comm/icon.svg b/apps/visual-studio-comm/icon.svg new file mode 100644 index 0000000..973cc68 --- /dev/null +++ b/apps/visual-studio-comm/icon.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/visual-studio-comm/info b/apps/visual-studio-comm/info new file mode 100644 index 0000000..c99a038 --- /dev/null +++ b/apps/visual-studio-comm/info @@ -0,0 +1,14 @@ +# 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="" diff --git a/apps/visual-studio-ent/icon.svg b/apps/visual-studio-ent/icon.svg new file mode 100644 index 0000000..973cc68 --- /dev/null +++ b/apps/visual-studio-ent/icon.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/visual-studio-ent/info b/apps/visual-studio-ent/info new file mode 100644 index 0000000..81cc124 --- /dev/null +++ b/apps/visual-studio-ent/info @@ -0,0 +1,14 @@ +# 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="" diff --git a/apps/visual-studio-pro/icon.svg b/apps/visual-studio-pro/icon.svg new file mode 100644 index 0000000..973cc68 --- /dev/null +++ b/apps/visual-studio-pro/icon.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/visual-studio-pro/info b/apps/visual-studio-pro/info new file mode 100644 index 0000000..0c08861 --- /dev/null +++ b/apps/visual-studio-pro/info @@ -0,0 +1,14 @@ +# 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="" diff --git a/apps/word-o365-x86/icon.svg b/apps/word-o365-x86/icon.svg index 643aaf4..9b9eb4a 100644 --- a/apps/word-o365-x86/icon.svg +++ b/apps/word-o365-x86/icon.svg @@ -1,3 +1,50 @@ - - + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/word-o365-x86/info b/apps/word-o365-x86/info index b6a001e..b2d853d 100644 --- a/apps/word-o365-x86/info +++ b/apps/word-o365-x86/info @@ -12,3 +12,6 @@ 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" diff --git a/apps/word-o365/icon.svg b/apps/word-o365/icon.svg index 643aaf4..9b9eb4a 100644 --- a/apps/word-o365/icon.svg +++ b/apps/word-o365/icon.svg @@ -1,3 +1,50 @@ - - + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/word-o365/info b/apps/word-o365/info index 2e1a365..0632b79 100644 --- a/apps/word-o365/info +++ b/apps/word-o365/info @@ -12,3 +12,6 @@ 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" diff --git a/apps/word-x86/icon.svg b/apps/word-x86/icon.svg index 643aaf4..9b9eb4a 100644 --- a/apps/word-x86/icon.svg +++ b/apps/word-x86/icon.svg @@ -1,3 +1,50 @@ - - + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/word-x86/info b/apps/word-x86/info index a4b9c45..b35c54e 100644 --- a/apps/word-x86/info +++ b/apps/word-x86/info @@ -12,3 +12,6 @@ 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" diff --git a/apps/word/icon.svg b/apps/word/icon.svg index 643aaf4..9b9eb4a 100644 --- a/apps/word/icon.svg +++ b/apps/word/icon.svg @@ -1,3 +1,50 @@ - - + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/word/info b/apps/word/info index 4236346..a1dfd71 100644 --- a/apps/word/info +++ b/apps/word/info @@ -12,3 +12,6 @@ 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" diff --git a/bin/winapps b/bin/winapps index cecb079..1563955 100755 --- a/bin/winapps +++ b/bin/winapps @@ -1,108 +1,702 @@ #!/usr/bin/env bash -if [ ! -f "${HOME}/.config/winapps/winapps.conf" ] && [ ! -f "${HOME}/.winapps" ]; then - echo "You need to create a ~/.config/winapps/winapps.conf configuration. Exiting..." - exit -fi +### 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 -DIR="$(dirname "$(readlink -f "$0")")" -RUN="$(date)-${RANDOM}" +# 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)" -if [ ! -d "${HOME}/.local/share/winapps" ]; then - mkdir -p "${HOME}/.local/share/winapps" -fi +# OTHER +readonly VM_NAME="RDPWindows" # FOR 'libvirt' ONLY +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 RUN="$(date)-${RANDOM}" +### GLOBAL VARIABLES ### +# WINAPPS CONFIGURATION FILE +RDP_USER="" +RDP_PASS="" +RDP_DOMAIN="" +RDP_IP="" +WAFLAVOR="docker" +RDP_FLAGS="" +FREERDP_COMMAND="" RDP_SCALE=100 +AUTOPAUSE="off" +AUTOPAUSE_TIME="300" +MULTIMON="false" +DEBUG="true" +MULTI_FLAG="" -if [ -f "${HOME}/.config/winapps/winapps.conf" ]; then - . "${HOME}/.config/winapps/winapps.conf" -else - . "${HOME}/.winapps" -fi +# OTHER +FREERDP_PID=-1 -function dprint() { - if [ "${DEBUG}" = "true" ]; then - echo "[${RUN}] ${1}" >> "${HOME}/.local/share/winapps/winapps.log" - fi +### TRAPS ### +# Catch SIGINT (CTRL+C) to call 'waCleanUp'. +trap waCleanUp SIGINT + +### FUNCTIONS ### +# Name: 'waCleanUp' +# Role: Clean up remains prior to exit. +waCleanUp() { + # Kill FreeRDP. + [ "$FREERDP_PID" -gt 0 ] && kill -9 "$FREERDP_PID" &>/dev/null + + # Remove '.cproc' file. + [ -f "${APPDATA_PATH}/FreeRDP_Process_${FREERDP_PID}.cproc" ] && rm "${APPDATA_PATH}/FreeRDP_Process_${FREERDP_PID}.cproc" &>/dev/null + + # Terminate script. + exit 1 } +# 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: WINDOWS FAILED TO IMMEDIATELY UNGRACEFULLY SHUT DOWN WINDOWS. EXITING." + notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Failed to ungracefully shut down 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 SHUT DOWN. 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 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 "[$RUN] $1" >>"$LOG_PATH" +} + +# 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 'MULTI_FLAG' based on 'MULTIMON'. + MULTI_FLAG=$([[ $MULTIMON == "true" ]] && echo "/multimon" || echo "+span") + + # Update $RDP_SCALE. + waFixScale + + # 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" + 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=$(groups "$(whoami)") + + if ! (echo "$USER_GROUPS" | grep -q -E "\blibvirt\b") || ! (echo "$USER_GROUPS" | grep -q -E "\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 | xargs | grep -wq "$VM_NAME"); then + if (virsh list --state-shutoff --name | xargs | grep -wq "$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." + virsh start "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_START + if (virsh list --state-paused --name | xargs | grep -wq "$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 | xargs | grep -wq "$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 | xargs | grep -wq "$VM_NAME"); then + if (virsh domstate "$VM_NAME" | xargs | grep -wq "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 | xargs | grep -wq "$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 + break + fi + sleep $TIME_INTERVAL + TIME_ELAPSED=$((TIME_ELAPSED + TIME_INTERVAL)) + done + elif (virsh domstate "$VM_NAME" | xargs | grep -wq "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 + fi + elif (virsh domstate "$VM_NAME" | xargs | grep -wq "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" | xargs | grep -wq "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 + fi + break + elif (virsh list --state-shutoff --name | xargs | grep -wq "$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 + break + fi + sleep $TIME_INTERVAL + TIME_ELAPSED=$((TIME_ELAPSED + TIME_INTERVAL)) + done + elif (virsh domstate "$VM_NAME" | xargs | grep -wq "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" +} + +# 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 + ;; + "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." + 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 + ;; + "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 + ;; + "unknown") + EXIT_STATUS=$EC_UNKNOWN + ;; + esac + + # Handle non-zero exit statuses. + [ "$EXIT_STATUS" -ne 0 ] && waThrowExit "$EXIT_STATUS" +} + +# Name: 'waCheckPortOpen' +# Role: Assesses whether the RDP port on Windows is open. +function waCheckPortOpen() { + # Declare variables. + local VM_MAC="" # Stores the MAC address of the Windows VM. + 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 + VM_MAC=$(virsh domiflist "$VM_NAME" | grep -oE "([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})") # VM MAC address. + + 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=$(ip neigh show | grep "$VM_MAC" | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}") # VM IP address. + [ -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" + $FREERDP_COMMAND \ + /d:"$RDP_DOMAIN" \ + /u:"$RDP_USER" \ + /p:"$RDP_PASS" \ + /scale:"$RDP_SCALE" \ + +dynamic-resolution \ + +auto-reconnect \ + +home-drive \ + /sound \ + /microphone \ + /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}" + $FREERDP_COMMAND \ + /cert:tofu \ + /d:"$RDP_DOMAIN" \ + /u:"$RDP_USER" \ + /p:"$RDP_PASS" \ + /scale:"$RDP_SCALE" \ + +auto-reconnect \ + +clipboard \ + +home-drive \ + /sound \ + /microphone \ + -wallpaper \ + +dynamic-resolution \ + "$MULTI_FLAG" \ + /app:program:"$2" \ + /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. + $FREERDP_COMMAND \ + /d:"$RDP_DOMAIN" \ + /u:"$RDP_USER" \ + /p:"$RDP_PASS" \ + /scale:"$RDP_SCALE" \ + +auto-reconnect \ + +clipboard \ + +home-drive \ + /sound \ + /microphone \ + -wallpaper \ + +dynamic-resolution \ + "$MULTI_FLAG" \ + /wm-class:"$FULL_NAME" \ + /app:program:"$WIN_EXECUTABLE",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|/|\\|g') + dprint "UNIX_FILE_PATH: ${2}" + dprint "WINDOWS_FILE_PATH: ${FILE_PATH}" + + $FREERDP_COMMAND \ + /cert:tofu \ + /d:"$RDP_DOMAIN" \ + /u:"$RDP_USER" \ + /p:"$RDP_PASS" \ + /scale:"$RDP_SCALE" \ + +auto-reconnect \ + +clipboard \ + +home-drive \ + /sound \ + /microphone \ + -wallpaper \ + +dynamic-resolution \ + "$MULTI_FLAG" \ + /wm-class:"$FULL_NAME" \ + /app:program:"$WIN_EXECUTABLE",icon:"$ICON",name:$"FULL_NAME",cmd:\""$FILE_PATH"\" \ + /v:"$RDP_IP" &>/dev/null & + + # Capture the process ID. + FREERDP_PID=$! + fi + fi + + 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 +} + +# 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" ] && [ "$WAFLAVOR" != "docker" ]; 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 +} + +### MAIN LOGIC ### +#set -x # Enable for debugging. dprint "START" +dprint "SCRIPT_DIR: ${SCRIPT_DIR_PATH}" +dprint "SCRIPT_ARGS: ${*}" +dprint "HOME_DIR: ${HOME}" +mkdir -p "$APPDATA_PATH" +waLastRun +waLoadConfig +waGetFreeRDPCommand -if [ -f "${HOME}/.local/share/winapps/run" ]; then - LAST_RAN=$(stat -t -c %Y "${HOME}/.local/share/winapps/run") - dprint "LAST_RAN:${LAST_RAN}" - touch "${HOME}/.local/share/winapps/run" - THIS_RUN=$(stat -t -c %Y "${HOME}/.local/share/winapps/run") - dprint "THIS_RUN:${THIS_RUN}" - if (( $THIS_RUN - $LAST_RAN < 2 )); then - exit - 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 - touch "${HOME}/.local/share/winapps/run" + waThrowExit "$EC_INVALID_FLAVOR" fi -if [ -z "$(which xfreerdp)" ]; then - echo "You need xfreerdp!" - echo " sudo apt-get install -y freerdp2-x11" - exit -fi +waCheckPortOpen +waRunCommand "$@" -if [ -z "${RDP_IP}" ]; then - if [ -z "$(groups |grep libvirt)" ]; then - echo "You are not a member of the libvirt group. Run the below then reboot." - echo ' sudo usermod -a -G libvirt $(whoami)' - echo ' sudo usermod -a -G kvm $(whoami)' - exit - fi - if [ -z "$(virsh list |grep RDPWindows)" ]; then - echo "RDPWindows is not running, run:" - echo " virsh start RDPWindows" - exit - fi - RDP_IP=$(virsh net-dhcp-leases default |grep RDPWindows |awk '{print $5}') - RDP_IP=${RDP_IP%%\/*} -fi - -dprint "1:${1}" -dprint "2:${2}" -dprint "@:${@}" - -MULTI_FLAG="span" -if [ "${MULTIMON}" = "true" ]; then - MULTI_FLAG="multimon" -fi - -if [ "${1}" = "windows" ]; then - xfreerdp ${RDP_FLAGS} /d:"${RDP_DOMAIN}" /u:"${RDP_USER}" /p:"${RDP_PASS}" /v:${RDP_IP} /scale:${RDP_SCALE} /dynamic-resolution +auto-reconnect +home-drive /wm-class:"Microsoft Windows" 1> /dev/null 2>&1 & -elif [ "${1}" = "check" ]; then - dprint "CHECK" - xfreerdp ${RDP_FLAGS} /d:"${RDP_DOMAIN}" /u:"${RDP_USER}" /p:"${RDP_PASS}" /v:${RDP_IP} +auto-reconnect +home-drive -wallpaper /scale:${RDP_SCALE} /dynamic-resolution /${MULTI_FLAG} /app:"explorer.exe" -elif [ "${1}" = "manual" ]; then - dprint "MANUAL:${2}" - xfreerdp ${RDP_FLAGS} /d:"${RDP_DOMAIN}" /u:"${RDP_USER}" /p:"${RDP_PASS}" /v:${RDP_IP} +auto-reconnect +home-drive -wallpaper /scale:${RDP_SCALE} /dynamic-resolution /${MULTI_FLAG} /app:"${2}" 1> /dev/null 2>&1 & -elif [ "${1}" != "install" ]; then - dprint "DIR:${DIR}" - if [ -e "${DIR}/../apps/${1}/info" ]; then - . "${DIR}/../apps/${1}/info" - ICON="${DIR}/../apps/${1}/icon.svg" - elif [ -e "${HOME}/.local/share/winapps/apps/${1}/info" ]; then - . "${HOME}/.local/share/winapps/apps/${1}/info" - ICON="${HOME}/.local/share/winapps/apps/${1}/icon.svg" - elif [ -e "/usr/local/share/winapps/apps/${1}/info" ]; then - . "/usr/local/share/winapps/apps/${1}/info" - ICON="/usr/local/share/winapps/apps/${1}/icon.svg" - else - echo "You need to run 'install.sh' first." - exit 1 - fi - if [ -n "${2}" ]; then - dprint "HOME:${HOME}" - FILE=$(echo "${2}" | sed 's|'"${HOME}"'|\\\\tsclient\\home|;s|/|\\|g;s|\\|\\\\|g') - dprint "FILE:${FILE}" - xfreerdp ${RDP_FLAGS} /d:"${RDP_DOMAIN}" /u:"${RDP_USER}" /p:"${RDP_PASS}" /v:${RDP_IP} +auto-reconnect +clipboard +home-drive -wallpaper /scale:${RDP_SCALE} /dynamic-resolution /${MULTI_FLAG} /wm-class:"${FULL_NAME}" /app:"${WIN_EXECUTABLE}" /app-icon:"${ICON}" /app-cmd:"\"${FILE}\"" 1> /dev/null 2>&1 & - else - xfreerdp ${RDP_FLAGS} /d:"${RDP_DOMAIN}" /u:"${RDP_USER}" /p:"${RDP_PASS}" /v:${RDP_IP} +auto-reconnect +clipboard +home-drive -wallpaper /scale:${RDP_SCALE} /dynamic-resolution /${MULTI_FLAG} /wm-class:"${FULL_NAME}" /app:"${WIN_EXECUTABLE}" /app-icon:"${ICON}" 1> /dev/null 2>&1 & - fi +if [[ "$AUTOPAUSE" == "on" ]]; then + waCheckIdle fi dprint "END" diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..3e77cfb --- /dev/null +++ b/compose.yaml @@ -0,0 +1,41 @@ +# 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: dockurr/windows # https://hub.docker.com/r/dockurr/windows + 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: "tiny11" + 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: "Docker" # Uncomment to set a custom Windows username. The default is 'Docker'. + #PASSWORD: "" # Uncomment to set a password for the Windows user. There is no default password. + HOME: "${HOME}" # Set path to Linux user home folder. + privileged: true # Grant the Windows VM extended privileges. + 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). + 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 mount the second hard disk 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/sdX:/disk1 # Uncomment to mount a disk directly within the Windows VM (Note: 'disk1' will be mounted as the main drive). + #- /dev/sdY:/disk2 # Uncomment to mount a disk directly within the Windows VM (Note: 'disk2' and higher will be mounted as secondary drives). diff --git a/demo/demo.gif b/demo/demo.gif deleted file mode 100644 index a27d6f5..0000000 Binary files a/demo/demo.gif and /dev/null differ diff --git a/demo/demo.png b/demo/demo.png new file mode 100644 index 0000000..84fae24 Binary files /dev/null and b/demo/demo.png differ diff --git a/demo/installer.gif b/demo/installer.gif index b152494..54aac5d 100644 Binary files a/demo/installer.gif and b/demo/installer.gif differ diff --git a/docs/KVM.md b/docs/KVM.md deleted file mode 100644 index 091e88d..0000000 --- a/docs/KVM.md +++ /dev/null @@ -1,203 +0,0 @@ -# Creating a Virtual Machine in KVM -This step-by-step guide will take you through setting up a CPU and memory efficient virtual machine to use with WinApps leveraging KVM, an open-source virtualization software contained in most linux distributions. - -## Install KVM -First up, you must install KVM and the Virtual Machine Manager. By installing `virt-manager`, you will get everything you need for your distribution: -```bash -sudo apt-get install -y virt-manager -``` - -## Download the Windows Professional and KVM VirtIO drivers -You will need Windows 10 Professional (or Enterprise or Server) to run RDP apps, Windows 10 Home will not suffice. You will also need drivers for VirtIO to ensure the best performance and lowest overhead for your system. You can download these at the following links. - -Windows 10 ISO: https://www.microsoft.com/en-us/software-download/windows10ISO - -KVM VirtIO drivers (for all distros): https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso - -## Create your virtual machine -The following guide will take you through the setup. If you are an expert user, you may wish to: -- [Define a VM from XML (may not work on all systems)](#define-a-vm-from-xml) -- [Run KVM in user mode](#run-kvm-in-user-mode) - -Otherwise, to set up the standard way, open `virt-manager` (Virtual Machines). - -![](kvm/00.png) - -Next, go to `Edit`->`Preferences`, and check `Enable XML editing`, then click the `Close` button. - -![](kvm/01.png) - -Now it is time to add a new VM by clicking the `+` button. - -![](kvm/02.png) - -Choose `Local install media` and click `Forward`. - -![](kvm/03.png) - -Now select the location of your Windows 10 ISO, and `Automatically detect` the installation. - -![](kvm/04.png) - -Set your memory and CPUs. We recommend `2` CPUs and `4096MB` for memory. We will be using a Memory Ballooning service, meaning 4096 is the maximum amount of memory the VM will ever use, but will not use this amount except when it is needed. - -![](kvm/05.png) - -Choose your virtual disk size, keep in mind this is the maximum size the disk will grow to, but it will not take up this space until it needs it. - -![](kvm/06.png) - -Next, name your machine `RDPWindows` so that WinApps can detect it, and choose to `Customize configuration before install`. - -![](kvm/07.png) - -After clicking `Finish`, ensure under CPU that `Copy host CPU configuration` is selected, and `Apply`. - -**NOTE:** Sometimes this gets turned off after Windows is installed. You should check this option after install as well. - -![](kvm/08.png) - -Next, go to the `XML` tab, and edit the `` section to contain: -```xml - - - - -``` -Then `Apply`. This will drastically reduce idle CPU usage (from ~25% to ~3%). - -![](kvm/09.png) - -Next, under Memory, lower the `Current allocation` to the minimum memory the VM should use. We recommend `1024MB`. - -![](kvm/10.png) - -Under `Boot options`, check `Start virtual machine on host boot up`. - -![](kvm/11.png) - -For SATA Disk 1, set the `Disk bus` to `VirtIO`. - -![](kvm/12.png) - -For the NIC, set the `Device model` to `virtio`. - -![](kvm/13.png) - -Click the `Add Hardware` button in the lower right, and choose `Storage`. For `Device type`, select `CDROM device` and choose the VirtIO driver ISO you downloaded earlier. This will give the Windows 10 Installer access to drivers during the install process. Now click `Finish` to add the new CDROM device. - -![](kvm/14.png) - -You are now ready to click `Begin Installation` - -![](kvm/15.png) - -Now move on to installing the virtual machine. - -## Install the virtual machine -From here out you will install Windows 10 Professional as you would on any other machine. - -![](kvm/16.png) - -Once you get to the point of selecting the location for installation, you will see there are no disks available. This is because we need to load the VirtIO driver. Select `Load driver`. - -![](kvm/17.png) - -The installer will then ask you to specify where the driver is located. Select the `E:\` drive or whichever drive the VirtIO driver ISO is located on. - -![](kvm/18.png) - -Choose the appropriate driver for the OS you have selected, which is most likely the `w10` driver for Windows 10. - -![](kvm/19.png) - -You will now see a disk you can select for the installation. - -![](kvm/20.png) - -Windows will begin to install, and you will likely need to reboot the VM a number times during this process. - -![](kvm/21.png) - -At some point, you will come to a network screen. This is because the VirtIO drivers for the network have not yet been loaded. Simply click `I don't have internet`. - -![](kvm/22.png) - -It will confirm your choice, so just choose `Continue with limited setup`. - -![](kvm/23.png) - -After you get into Windows and login with the user you created during the install. Open up `Explorer` and navigate the `E:\` drive or wherever the VirtIO driver ISO is mounted. Double click the `virt-win-gt-64.exe` file to launch the VirtIO driver installer. - -![](kvm/24.png) - -Leave everything as default and click `Next` through the installer. This will install device drivers as well as the Memory Ballooning service. - -![](kvm/25.png) - -Once you finish the driver install, you will need to make some registry changes to enable RDP Applications to run on the system. Start by downloading the `RDPApps.reg` file from the WinApps repo by visiting https://github.com/Fmstrat/winapps/blob/main/install/RDPApps.reg, right clicking on the `Raw` button, and clicking on `Save target as`. - -![](kvm/26.png) - -Once you have downloaded the registry file, right click on it, and choose `Merge`, then accept any confirmations along the way. - -![](kvm/27.png) - -Next up, we need to rename the VM so that WinApps can locate it. Go to the start menu and type `About` to bring up the `About your PC` settings. - -![](kvm/28.png) - -Scroll down and click on `Rename this PC` - -![](kvm/29.png) - -Rename to `RDPWindows`, and then `Next`, but **do not** restart. - -![](kvm/30.png) - -Lastly, scroll down to `Remote Desktop`, and toggle `Enable Remote Desktop` on, and `Confirm`. - -![](kvm/31.png) - -At this point you will need to restart and you have completed your setup. - -Rather than restart you can go right ahead and install other applications like Microsoft Office or Adobe CC that could be used through WinApps. - -You may also wish to install the [Spice Guest Tools](https://www.spice-space.org/download/windows/spice-guest-tools/spice-guest-tools-latest.exe) inside the VM which enables features like auto-desktop resize and cut-and-paste when using `virt-manager`. As WinApps uses RDP, this is not necessary if you do not plan to access the machine via `virt-manager`. - -Once you are finished, restart the VM, but do not log in. Simply close the VM viewer, and close the Virtual Machine Manager. - -## Expert installs - -### Define a VM from XML -This expert guide for XML imports is specific to Ubuntu 20.04 and may not work on all hardware platforms. - -You can refer to the [KVM](https://www.linux-kvm.org) documentation for specifics, but the first thing you need to do is set up a Virtual Machine running Windows 10 Professional (or any version that supports RDP). First, install KVM: -``` bash -sudo apt-get install -y virt-manager -``` -Now, copy your Windows ISO and VirtIO iso (links to download in the main guide) into the folder and update the `kvm/RDPWindows.xml` appropriately. - -Next, define a VM called RDPWindows from the sample XML file with: -``` bash -virsh define kvm/RDPWindows.xml -virsh autostart RDPWindows -``` -You should then open the VMs properties in `virt-manager` and ensure that under CPU `Copy host CPU configuration` is selected. - -Boot it up, install windows, and then [Install the virtual machine](#install-the-virtual-machine). - -### Run KVM in user mode -Now set up KVM to run as your user instead of root and allow it through AppArmor (for Ubuntu 20.04 and above): -``` bash -sudo sed -i "s/#user = "root"/user = "$(id -un)"/g" /etc/libvirt/qemu.conf -sudo sed -i "s/#group = "root"/group = "$(id -gn)"/g" /etc/libvirt/qemu.conf -sudo usermod -a -G kvm $(id -un) -sudo usermod -a -G libvirt $(id -un) -sudo systemctl restart libvirtd -sudo ln -s /etc/apparmor.d/usr.sbin.libvirtd /etc/apparmor.d/disable/ -``` -You will likely need to reboot to ensure your current shell is added to the group. - - - diff --git a/docs/docker.md b/docs/docker.md new file mode 100644 index 0000000..7a070d0 --- /dev/null +++ b/docs/docker.md @@ -0,0 +1,150 @@ +# 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. + +## `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`](https://github.com/winapps-org/winapps/blob/main/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`. + +> [!NOTE] +> WinApps uses a stripped-down Windows installation by default. Although this is recommended, you can request a stock Windows installation by changing `VERSION` to one of the versions listed in the README of the [original GitHub repository](https://github.com/dockur/windows). + +Please refer to the [original GitHub repository](https://github.com/dockur/windows) for more information on additional configuration options. + +### 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. + +After installing Windows, comment out the following lines in the `compose.yaml` file by prepending a '#': +- `- ./oem:/oem` +- `- /path/to/windows/install/media.iso:/custom.iso` (if relevant) + +Then, copy this modified `compose.yaml` file to `~/.config/winapps/compose.yaml`. + +```bash +cp ./compose.yaml ~/.config/winapps/compose.yaml +``` + +Finally, ensure the new configuration is applied by running the following: + +```bash +docker compose --file ./compose.yaml down +docker compose --file ~/.config/winapps/compose.yaml up +``` + +### Installing WinApps +`Docker` simplifies the WinApps installation process by eliminating the need for any additional configuration of the Windows virtual machine. Once the Windows virtual machine is up and running, you can directly launch the WinApps installer, which should automatically detect and interface with Windows. + +> [!NOTE] +> Since no Windows user password is set by default, Windows may automatically log in, which may cause the WinApps installation to fail due to complications establishing an RDP connection. To avoid this issue, please use the VNC connection to ensure that the Windows user is logged out before starting the WinApps installation. + +```bash +./installer.sh +``` + +### Changing `compose.yaml` +Changes to `compose.yaml` require the Windows virtual machine to be removed and re-created. This should __NOT__ affect your data. + +```bash +# Stop and remove the existing Windows virtual machine. +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 virtual machine with the updated configuration. +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). + +> [!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. + +After installing Windows, comment out the following lines in the `compose.yaml` file by prepending a '#': +- `- ./oem:/oem` +- `- /path/to/windows/install/media.iso:/custom.iso` (if relevant) + +Then, copy this modified `compose.yaml` file to `~/.config/winapps/compose.yaml`. + +```bash +cp ./compose.yaml ~/.config/winapps/compose.yaml +``` + +Finally, ensure the new configuration is applied by running the following: + +```bash +podman-compose --file ./compose.yaml down +podman-compose --file ~/.config/winapps/compose.yaml up +``` + +### Installing WinApps +Please follow the [`docker` instructions](#installing-winapps). + +### Changing `compose.yaml` +Changes to `compose.yaml` require the Windows virtual machine to be removed and re-created. This should __NOT__ affect your data. + +```bash +# Stop and remove the existing Windows virtual machine. +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 virtual machine 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 +``` diff --git a/docs/kvm/09.png b/docs/kvm/09.png deleted file mode 100644 index 1dca565..0000000 Binary files a/docs/kvm/09.png and /dev/null differ diff --git a/docs/kvm/21.png b/docs/kvm/21.png deleted file mode 100644 index 78b8a0b..0000000 Binary files a/docs/kvm/21.png and /dev/null differ diff --git a/docs/libvirt.md b/docs/libvirt.md new file mode 100644 index 0000000..3865044 --- /dev/null +++ b/docs/libvirt.md @@ -0,0 +1,748 @@ +# Creating a `libvirt` Windows VM +## Understanding The Virtualisation Stack +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. + +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 + ``` + +4. Install `QEMU Guest Agent`. + ```bash + sudo apt install qemu-guest-agent # Debian/Ubuntu + sudo dnf install qemu-guest-agent # Fedora/RHEL + sudo pacman -S qemu-guest-agent # Arch Linux + sudo emerge app-emulation/qemu-guest-agent # Gentoo Linux + sudo systemctl enable qemu-guest-agent + sudo systemctl start qemu-guest-agent + ``` + +> [!NOTE] +> `QEMU Guest Agent` is a helper daemon used to exchange information and commands between host and guest operating systems. +> You can read more about `QEMU Guest Agent` [here](https://pve.proxmox.com/wiki/Qemu-guest-agent). + +5. 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: 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. + +6. 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. + +7. 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. + +8. 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`. + +

+ +

+ +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. (Optional) Assign specific physical CPU cores to the virtual machine. This 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. + 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 isolatng 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). + +11. 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 + + + + + + + + ``` + +

+ +

+ +12. 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. + +13. Add the following XML snippet within the `` section to enable the GNU/Linux host to communicate with Windows using `QEMU Guest Agent`. + + ```xml + + + +
+ + ``` + +14. 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`. + +

+ +

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

+ +

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

+ +

+ +17. Navigate to 'NIC' and set the `Device model` type to `virtio` to enable paravirtualised networking. + +

+ +

+ +18. 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. + +

+ +

+ +19. Click `Begin Installation` in the top left. + +

+ +

+ +### Example `.XML` File +Below is an example `.XML` file that describes a Windows 11 virtual machine. + +```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 + + + + +
+ + + + + +
+ + +
+ + + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + +
+ + +
+ + + + + +
+ + + + + + + + + + + +
+ + + + +
+ + +
+ + + + + + + + + + + +
+ +