mirror of
https://github.com/winapps-org/winapps.git
synced 2025-06-03 21:57:18 +02:00
Merge pull request #139 from KernelGhost/improve-install-script
[Code Refactor] Improved Installation Script
This commit is contained in:
commit
b811acea64
@ -12,7 +12,7 @@ repos:
|
|||||||
- id: remove-crlf
|
- id: remove-crlf
|
||||||
- id: forbid-tabs
|
- id: forbid-tabs
|
||||||
- id: remove-tabs
|
- id: remove-tabs
|
||||||
args: [ --whitespaces-count, "2" ]
|
args: [ --whitespaces-count, "4" ]
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.6.0
|
rev: v4.6.0
|
||||||
@ -43,7 +43,8 @@ repos:
|
|||||||
- repo: https://github.com/scop/pre-commit-shfmt
|
- repo: https://github.com/scop/pre-commit-shfmt
|
||||||
rev: v3.8.0-1
|
rev: v3.8.0-1
|
||||||
hooks:
|
hooks:
|
||||||
- id: shfmt
|
- id: shfmt
|
||||||
|
args: ["-i", "4", "-ci", "-s"]
|
||||||
|
|
||||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||||
rev: v0.10.0.1
|
rev: v0.10.0.1
|
||||||
|
284
README.md
284
README.md
@ -1,114 +1,121 @@
|
|||||||
# WinApps
|
# WinApps
|
||||||
|
*The WinApps project, forked from Fmstrat's [original repository](https://github.com/Fmstrat/winapps).*
|
||||||
|
|
||||||
The WinApps main project, [originally created by Fmstrat](https://github.com/Fmstrat/winapps)
|
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` or `GNOME`, integrated seamlessly as if they were native to the OS.
|
||||||
|
|
||||||
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,
|
<img src="demo/demo.gif" width=1000 alt="WinApps Demonstration Animation.">
|
||||||
including Nautilus integration for right-clicking on files of specific mime types to open them.
|
|
||||||
|
|
||||||
<img src="demo/demo.gif" width=1000>
|
## Underlying Mechanism
|
||||||
|
WinApps works by:
|
||||||
|
1. Running Windows in a `Docker` or `libvirt + KVM/QEMU` virtual machine (deprecated).
|
||||||
|
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.
|
||||||
|
|
||||||
## How it works
|
## 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.
|
||||||
|
|
||||||
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:
|
## Supported Applications
|
||||||
|
**WinApps supports <u>*ALL*</u> Windows applications.**
|
||||||
|
|
||||||
- Running a Windows RDP server in a background VM container
|
Universal application support is achieved by:
|
||||||
- Checking the RDP server for installed applications such as Microsoft Office
|
1. Scanning Windows for any officially supported applications (list below).
|
||||||
- If those programs are installed, it creates shortcuts leveraging FreeRDP for both the CLI and the GNOME tray
|
2. Scanning Windows for any other `.exe` files listed within the Windows Registry.
|
||||||
- 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
|
|
||||||
|
|
||||||
## Currently supported applications
|
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.
|
||||||
|
|
||||||
### WinApps supports **_ANY_** installed application on your system.
|
Contributing to the list of supported applications is encouraged through submission of pull requests! Please help us grow the WinApps community.
|
||||||
|
|
||||||
It does this by:
|
*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.*
|
||||||
|
|
||||||
1. Scanning your system for the officially configured applications (below)
|
|
||||||
2. Scanning your system for any other EXE files with install records in the Windows Registry
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
||||||
<table cellpadding="10" cellspacing="0" border="0">
|
<table cellpadding="10" cellspacing="0" border="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="apps/acrobat-x-pro/icon.svg" width="100"></td><td>Adobe Acrobat Pro<br>(X)</td>
|
<td><img src="apps/acrobat-x-pro/icon.svg" width="100"></td><td>Adobe Acrobat Pro<br>(X)</td>
|
||||||
<td><img src="apps/acrobat-reader-dc/icon.svg" width="100"></td><td>Adobe Acrobat Reader<br>(DC)</td>
|
<td><img src="apps/aftereffects-cc/icon.svg" width="100"></td><td>Adobe After Effects<br>(CC)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="apps/aftereffects-cc/icon.svg" width="100"></td><td>Adobe After Effects<br>(CC)</td>
|
<td><img src="apps/audition-cc/icon.svg" width="100"></td><td>Adobe Audition<br>(CC)</td>
|
||||||
<td><img src="apps/audition-cc/icon.svg" width="100"></td><td>Adobe Audition<br>(CC)</td>
|
<td><img src="apps/bridge-cs6/icon.svg" width="100"></td><td>Adobe Bridge<br>(CS6, CC)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="apps/bridge-cs6/icon.svg" width="100"></td><td>Adobe Bridge<br>(CS6, CC)</td>
|
<td><img src="apps/adobe-cc/icon.svg" width="100"></td><td>Adobe Creative Cloud<br>(CC)</td>
|
||||||
<td><img src="apps/adobe-cc/icon.svg" width="100"></td><td>Adobe Creative Cloud<br>(CC)</td>
|
<td><img src="apps/illustrator-cc/icon.svg" width="100"></td><td>Adobe Illustrator<br>(CC)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="apps/illustrator-cc/icon.svg" width="100"></td><td>Adobe Illustrator<br>(CC)</td>
|
<td><img src="apps/indesign-cc/icon.svg" width="100"></td><td>Adobe InDesign<br>(CC)</td>
|
||||||
<td><img src="apps/indesign-cc/icon.svg" width="100"></td><td>Adobe InDesign<br>(CC)</td>
|
<td><img src="apps/lightroom-cc/icon.svg" width="100"></td><td>Adobe Lightroom<br>(CC)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="apps/lightroom-cc/icon.svg" width="100"></td><td>Adobe Lightroom<br>(CC)</td>
|
<td><img src="apps/cmd/icon.svg" width="100"></td><td>Command Prompt<br>(cmd.exe)</td>
|
||||||
<td><img src="apps/cmd/icon.svg" width="100"></td><td>Command Prompt<br>(cmd.exe)</td>
|
<td><img src="apps/explorer/icon.svg" width="100"></td><td>Explorer<br>(File Manager)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="apps/explorer/icon.svg" width="100"></td><td>Explorer<br>(File Manager)</td>
|
<td><img src="apps/iexplorer/icon.svg" width="100"></td><td>Internet Explorer<br>(11)</td>
|
||||||
<td><img src="apps/iexplorer/icon.svg" width="100"></td><td>Internet Explorer<br>(11)</td>
|
<td><img src="apps/access/icon.svg" width="100"></td><td>Microsoft Access<br>(2016, 2019, o365)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="apps/access/icon.svg" width="100"></td><td>Microsoft Access<br>(2016, 2019, o365)</td>
|
<td><img src="apps/excel/icon.svg" width="100"></td><td>Microsoft Excel<br>(2016, 2019, o365)</td>
|
||||||
<td><img src="apps/excel/icon.svg" width="100"></td><td>Microsoft Excel<br>(2016, 2019, o365)</td>
|
<td><img src="apps/word/icon.svg" width="100"></td><td>Microsoft Word<br>(2016, 2019, o365)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="apps/word/icon.svg" width="100"></td><td>Microsoft Word<br>(2016, 2019, o365)</td>
|
<td><img src="apps/onenote/icon.svg" width="100"></td><td>Microsoft OneNote<br>(2016, 2019, o365)</td>
|
||||||
<td><img src="apps/onenote/icon.svg" width="100"></td><td>Microsoft OneNote<br>(2016, 2019, o365)</td>
|
<td><img src="apps/outlook/icon.svg" width="100"></td><td>Microsoft Outlook<br>(2016, 2019, o365)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="apps/outlook/icon.svg" width="100"></td><td>Microsoft Outlook<br>(2016, 2019, o365)</td>
|
<td><img src="apps/powerpoint/icon.svg" width="100"></td><td>Microsoft PowerPoint<br>(2016, 2019, o365)</td>
|
||||||
<td><img src="apps/powerpoint/icon.svg" width="100"></td><td>Microsoft PowerPoint<br>(2016, 2019, o365)</td>
|
<td><img src="apps/publisher/icon.svg" width="100"></td><td>Microsoft Publisher<br>(2016, 2019, o365)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="apps/publisher/icon.svg" width="100"></td><td>Microsoft Publisher<br>(2016, 2019, o365)</td>
|
<td><img src="apps/powershell/icon.svg" width="100"></td><td>PowerShell</td>
|
||||||
<td><img src="apps/powershell/icon.svg" width="100"></td><td>PowerShell</td>
|
<td><img src="icons/windows.svg" width="100"></td><td>Windows<br>(Full RDP session)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td><img src="icons/windows.svg" width="100"></td><td>Windows<br>(Full RDP session)</td>
|
|
||||||
<td> </td><td> </td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
### Step 1: Configure a Windows VM
|
||||||
|
The optimal choice for running a Windows VM as a subsystem for WinApps is `Docker`. `Docker` facilitates automated installation processes while leveraging a `KVM/QEMU` backend. Despite continuing to provide documentation for configuring a Windows VM using `libvirt` and `virt-manager`, this method is now considered deprecated.
|
||||||
|
|
||||||
### Step 1: Set up a Windows virtual machine
|
The following guides are available:
|
||||||
|
- [Creating a Windows VM with `Docker`](docs/docker.md)
|
||||||
|
- [Creating a Windows VM with `virt-manager`](docs/KVM.md) (Deprecated)
|
||||||
|
|
||||||
The best solution for running a VM as a subsystem for WinApps would be Docker.
|
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.
|
||||||
Docker allows automizing the installation process and still uses KVM/QEMU under the hood.
|
|
||||||
We still provide the outdated KVM install instructions.
|
|
||||||
To set up the VM for WinApps, follow this guide:
|
|
||||||
|
|
||||||
- [Creating a virtual machine with Docker](docs/docker.md)
|
### Step 2: Clone WinApps Repository and Dependencies
|
||||||
- [Creating a virtual machine in KVM (outdated)](docs/KVM.md)
|
1. Clone the WinApps GitHub repository.
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/winapps-org/winapps.git && cd winapps
|
||||||
|
```
|
||||||
|
|
||||||
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.
|
2. Install the required dependencies.
|
||||||
|
- Debian/Ubuntu:
|
||||||
|
```bash
|
||||||
|
sudo apt install -y dialog freerdp3-x11
|
||||||
|
```
|
||||||
|
- Fedora/RHEL:
|
||||||
|
```bash
|
||||||
|
sudo dnf install -y dialog freerdp
|
||||||
|
```
|
||||||
|
- Arch Linux:
|
||||||
|
```bash
|
||||||
|
sudo pacman -Syu --needed -y dialog freerdp
|
||||||
|
```
|
||||||
|
- Gentoo Linux:
|
||||||
|
```bash
|
||||||
|
sudo emerge --ask=n sys-libs/dialog net-misc/freerdp:3
|
||||||
|
```
|
||||||
|
|
||||||
### Step 2: Download the repo and prerequisites
|
Please note that 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).
|
||||||
|
|
||||||
To get things going, use:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt install -y freerdp3-x11
|
flatpak install flathub com.freerdp.FreeRDP
|
||||||
git clone https://github.com/winapps-org/winapps.git
|
sudo flatpak override --filesystem=home com.freerdp.FreeRDP # To use `+home-drive`
|
||||||
cd winapps
|
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!note]
|
### Step 3: Create a WinApps Configuration File
|
||||||
> Requires FreeRDP 3.0.0 or later.
|
Create a configuration file at `~/.config/winapps/winapps.conf` containing the following:
|
||||||
> If not included in your distribution, you can download the Flatpak from here: https://github.com/FreeRDP/FreeRDP/wiki/Prebuilds
|
|
||||||
|
|
||||||
### 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
|
```bash
|
||||||
RDP_USER="MyWindowsUser"
|
RDP_USER="MyWindowsUser"
|
||||||
RDP_PASS="MyWindowsPassword"
|
RDP_PASS="MyWindowsPassword"
|
||||||
@ -121,95 +128,52 @@ RDP_PASS="MyWindowsPassword"
|
|||||||
#FREERDP_COMMAND="xfreerdp"
|
#FREERDP_COMMAND="xfreerdp"
|
||||||
```
|
```
|
||||||
|
|
||||||
The username and password should be a full user account and password, such as the one created when setting up Windows
|
`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.
|
||||||
or a domain user. It can't be a user/PIN combination as those aren't valid for RDP access.
|
|
||||||
|
|
||||||
Options:
|
#### Configuration Options Explained
|
||||||
|
- When using a pre-existing non-KVM RDP server, you must use `RDP_IP` to specify the location of the Windows server.
|
||||||
- When using a pre-existing non-KVM RDP server, you must use the `RDP_IP` to specify its location
|
- If running a Windows VM in KVM with NAT enabled, leave `RDP_IP` commented out and WinApps will auto-detect the local IP address for the VM.
|
||||||
- If you're 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`.
|
||||||
- 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|160|180].
|
||||||
- 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 microphone, uncomment and use the `RDP_FLAGS` configuration option.
|
||||||
- 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`. A FreeRDP bug may result in a black screen however, in which case you should revert this change.
|
||||||
- For multi-monitor setups, you can try enabling `MULTIMON`, however, if you get a black screen (FreeRDP bug) you will need to revert
|
|
||||||
- If you enable `DEBUG`, a log will be created on each application start in `~/.local/share/winapps/winapps.log`
|
- If you enable `DEBUG`, a log will be created on each application start in `~/.local/share/winapps/winapps.log`
|
||||||
- If you're on a system, where the command for freerdp is not xfreerdp, change `FREERDP_COMMAND` to it.
|
- If using a system on which the FreeRDP command is not `xfreerdp`, the correct command can be specified using `FREERDP_COMMAND`.
|
||||||
|
|
||||||
### Step 4: Run the WinApps installer
|
|
||||||
|
|
||||||
Lastly, check if 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.
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
Then the final step is to run the installer which will prompt you to a system or user install:
|
|
||||||
|
|
||||||
|
### Step 4: Run the WinApps Installer
|
||||||
|
Run the WinApps installer.
|
||||||
```bash
|
```bash
|
||||||
./installer.sh
|
./installer.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
This will take you through the following process:
|
A list of supported additional arguments can be accessed by running `./installer.sh --help`.
|
||||||
|
|
||||||
<img src="demo/installer.gif" width=1000>
|
<img src="demo/installer.gif" width=1000>
|
||||||
|
|
||||||
## Adding pre-defined applications
|
## 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. (Optional, but strongly encouraged) Submit a pull request to add your application to WinApps as an officially supported application once you have tested your configuration files to verify functionality.
|
||||||
|
|
||||||
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:
|
## 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.
|
||||||
- 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
|
|
||||||
|
|
||||||
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 aren't configured. This is completed with the `manual` flag.
|
|
||||||
Executables that are in the path don't require full path definition.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./bin/winapps manual "C:\my\directory\executableNotInPath.exe"
|
./bin/winapps manual "C:\my\directory\executableNotInPath.exe"
|
||||||
./bin/winapps manual executableInPath.exe
|
./bin/winapps manual executableInPath.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
## Checking for new application support
|
## Updating WinApps
|
||||||
|
The installer can be run multiple times. To update your installation of WinApps:
|
||||||
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.
|
1. Run the WinApps installer to remove WinApps from your system.
|
||||||
|
2. Pull the latest changes from the WinApps GitHub repository.
|
||||||
```bash
|
3. Re-install WinApps using the WinApps installer.
|
||||||
./installer.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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
|
|
||||||
./installer.sh --user --setupAllOfficiallySupportedApps # Configures all officially supported applications for the current user
|
|
||||||
./installer.sh --system --setupAllOfficiallySupportedApps # Configures all officially supported applications for the entire system
|
|
||||||
```
|
|
||||||
|
|
||||||
## Shout-outs
|
## Shout-outs
|
||||||
|
Some icons used for the officially supported applications were sourced from:
|
||||||
- Some icons pulled from
|
- Fluent UI React - Icons under [MIT License](https://github.com/Fmstrat/fluent-ui-react/blob/master/LICENSE.md)
|
||||||
- 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)
|
||||||
- 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)
|
||||||
- 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)
|
||||||
- DiemenDesign's LibreICONS - Icons under [MIT License](https://github.com/Fmstrat/LibreICONS/blob/master/LICENSE)
|
|
||||||
|
510
bin/winapps
510
bin/winapps
@ -1,209 +1,329 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
if [ ! -f "$HOME/.config/winapps/winapps.conf" ] && [ ! -f "$HOME/.winapps" ]; then
|
### GLOBAL CONSTANTS ###
|
||||||
echo "You need to create a ~/.config/winapps/winapps.conf configuration. Exiting..."
|
# ANSI ESCAPE SEQUENCES
|
||||||
exit
|
readonly ERROR_TEXT="\033[1;41;37m" # Bold + White + Red Background
|
||||||
fi
|
readonly CLEAR_TEXT="\033[0m" # Clear
|
||||||
|
|
||||||
DIR="$(dirname "$(readlink -f "$0")")"
|
# ERROR CODES
|
||||||
RUN="$(date)-$RANDOM"
|
readonly EC_MISSING_CONFIG=1
|
||||||
|
readonly EC_MISSING_FREERDP=2
|
||||||
|
readonly EC_NOT_IN_GROUP=3
|
||||||
|
readonly EC_VM_NOT_RUNNING=4
|
||||||
|
readonly EC_VM_NO_IP=5
|
||||||
|
readonly EC_VM_BAD_PORT=6
|
||||||
|
readonly EC_UNSUPPORTED_APP=7
|
||||||
|
|
||||||
if [ ! -d "$HOME/.local/share/winapps" ]; then
|
# PATHS
|
||||||
mkdir -p "$HOME/.local/share/winapps"
|
readonly APPDATA_PATH="${HOME}/.local/share/winapps"
|
||||||
fi
|
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"
|
||||||
|
# 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)"
|
||||||
|
|
||||||
|
# OTHER
|
||||||
|
readonly VM_NAME="RDPWindows"
|
||||||
|
readonly RDP_PORT=3389
|
||||||
|
# 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=""
|
||||||
|
RDP_FLAGS=""
|
||||||
|
FREERDP_COMMAND=""
|
||||||
RDP_SCALE=100
|
RDP_SCALE=100
|
||||||
|
MULTIMON="false"
|
||||||
|
DEBUG="true"
|
||||||
|
MULTI_FLAG=""
|
||||||
|
|
||||||
if [ -f "$HOME/.config/winapps/winapps.conf" ]; then
|
### FUNCTIONS ###
|
||||||
# shellcheck source=/dev/null
|
# Name: 'waThrowExit'
|
||||||
. "$HOME/.config/winapps/winapps.conf"
|
# Role: Throw an error message and exit the script.
|
||||||
else
|
function waThrowExit() {
|
||||||
# shellcheck source=/dev/null
|
# Declare variables.
|
||||||
. "$HOME/.winapps"
|
local ERR_CODE="$1"
|
||||||
fi
|
|
||||||
|
|
||||||
|
# Throw error.
|
||||||
|
case "$ERR_CODE" in
|
||||||
|
"$EC_MISSING_CONFIG")
|
||||||
|
# Missing WinApps configuration file.
|
||||||
|
dprint "ERROR: MISSING WINAPPS CONFIGURATION FILE. EXITING."
|
||||||
|
echo -e "${ERROR_TEXT}ERROR: MISSING WINAPPS CONFIGURATION FILE.${CLEAR_TEXT}"
|
||||||
|
echo "Please create a WinApps configuration file at '${CONFIG_PATH}'".
|
||||||
|
;;
|
||||||
|
"$EC_MISSING_FREERDP")
|
||||||
|
dprint "ERROR: FREERDP VERSION 3 IS NOT INSTALLED. EXITING."
|
||||||
|
echo -e "${ERROR_TEXT}ERROR: FREERDP VERSION 3 IS NOT INSTALLED.${CLEAR_TEXT}"
|
||||||
|
;;
|
||||||
|
"$EC_NOT_IN_GROUP")
|
||||||
|
dprint "ERROR: USER NOT PART OF REQUIRED GROUPS. EXITING."
|
||||||
|
echo -e "${ERROR_TEXT}ERROR: USER NOT PART OF REQUIRED GROUPS.${CLEAR_TEXT}"
|
||||||
|
echo "Please run:"
|
||||||
|
echo " sudo usermod -a -G libvirt $(whoami)"
|
||||||
|
echo " sudo usermod -a -G kvm $(whoami)"
|
||||||
|
;;
|
||||||
|
"$EC_VM_NOT_RUNNING")
|
||||||
|
dprint "ERROR: VM NOT RUNNING. EXITING."
|
||||||
|
echo -e "${ERROR_TEXT}ERROR: VM NOT RUNNING.${CLEAR_TEXT}"
|
||||||
|
echo "Please ensure the Windows VM is powered on."
|
||||||
|
;;
|
||||||
|
"$EC_VM_NO_IP")
|
||||||
|
dprint "ERROR: VM UNREACHABLE. EXITING."
|
||||||
|
echo -e "${ERROR_TEXT}ERROR: VM UNREACHABLE.${CLEAR_TEXT}"
|
||||||
|
echo "Please ensure the Windows VM is assigned an IP address."
|
||||||
|
;;
|
||||||
|
"$EC_VM_BAD_PORT")
|
||||||
|
dprint "ERROR: RDP PORT CLOSED. EXITING."
|
||||||
|
echo -e "${ERROR_TEXT}ERROR: RDP PORT CLOSED.${CLEAR_TEXT}"
|
||||||
|
echo "Please ensure Remote Desktop is correctly configured on the Windows VM."
|
||||||
|
;;
|
||||||
|
"$EC_UNSUPPORTED_APP")
|
||||||
|
dprint "ERROR: APPLICATION NOT FOUND. EXITING."
|
||||||
|
echo -e "${ERROR_TEXT}ERROR: APPLICATION NOT FOUND.${CLEAR_TEXT}"
|
||||||
|
echo "Please ensure the program is correctly configured as an officially supported application."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Provide generic advice.
|
||||||
|
echo "Check the WinApps project README for more information."
|
||||||
|
|
||||||
|
# Terminate the script.
|
||||||
|
echo "Exiting with status '${ERR_CODE}'."
|
||||||
|
exit "$ERR_CODE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Name: 'dprint'
|
||||||
|
# Role: Conditionally print debug messages to a log file, creating it if it does not exist.
|
||||||
function dprint() {
|
function dprint() {
|
||||||
if [ "$DEBUG" = "true" ]; then
|
[ "$DEBUG" = "true" ] && echo "[$RUN] $1" >>"$LOG_PATH"
|
||||||
echo "[$RUN] $1" >>"$HOME/.local/share/winapps/winapps.log"
|
}
|
||||||
|
|
||||||
|
# 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")
|
||||||
|
|
||||||
|
# Append additional flags or parameters to FreeRDP.
|
||||||
|
[[ -n $RDP_FLAGS ]] && FREERDP_COMMAND="${FREERDP_COMMAND} ${RDP_FLAGS}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function waGetFreeRDPCommand() {
|
||||||
|
# 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*' | cut -d'.' -f1)
|
||||||
|
if [[ $FREERDP_MAJOR_VERSION =~ ^[0-9]+$ ]] && ((FREERDP_MAJOR_VERSION >= 3)); then
|
||||||
|
FREERDP_COMMAND="xfreerdp"
|
||||||
|
fi
|
||||||
|
# Check for 'xfreerdp3'.
|
||||||
|
elif 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*' | cut -d'.' -f1)
|
||||||
|
if [[ $FREERDP_MAJOR_VERSION =~ ^[0-9]+$ ]] && ((FREERDP_MAJOR_VERSION >= 3)); then
|
||||||
|
FREERDP_COMMAND="xfreerdp3"
|
||||||
|
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}'."
|
||||||
|
else
|
||||||
|
waThrowExit "$EC_MISSING_FREERDP"
|
||||||
fi
|
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: Throw an error if the Windows VM is not running.
|
||||||
|
function waCheckVMRunning() {
|
||||||
|
! virsh list --state-running --name | grep -q "^${VM_NAME}$" && waThrowExit "$EC_VM_NOT_RUNNING"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Name: 'waCheckVMContactable'
|
||||||
|
# Role: Assesses whether the Windows VM can be contacted.
|
||||||
|
function waCheckVMContactable() {
|
||||||
|
# Declare variables.
|
||||||
|
local VM_MAC="" # Stores the MAC address of the Windows VM.
|
||||||
|
|
||||||
|
# Obtain Windows VM IP Address
|
||||||
|
if [ -z "$RDP_IP" ]; then
|
||||||
|
VM_MAC=$(virsh domiflist "$VM_NAME" | grep -oE "([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})") # VM MAC address.
|
||||||
|
RDP_IP=$(arp -n | grep "$VM_MAC" | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}") # VM IP address.
|
||||||
|
[ -z "$RDP_IP" ] && waThrowExit "$EC_VM_NO_IP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for an open RDP port.
|
||||||
|
timeout 5 nc -z "$RDP_IP" "$RDP_PORT" &>/dev/null || waThrowExit "$EC_VM_BAD_PORT"
|
||||||
|
}
|
||||||
|
|
||||||
|
function waRunCommand() {
|
||||||
|
# Declare variables.
|
||||||
|
local ICON=""
|
||||||
|
local FILE_PATH=""
|
||||||
|
|
||||||
|
# Run option.
|
||||||
|
if [ "$1" = "windows" ]; then
|
||||||
|
# Open Windows VM.
|
||||||
|
dprint "WINDOWS"
|
||||||
|
$FREERDP_COMMAND \
|
||||||
|
/d:"$RDP_DOMAIN" \
|
||||||
|
/u:"$RDP_USER" \
|
||||||
|
/p:"$RDP_PASS" \
|
||||||
|
/scale:"$RDP_SCALE" \
|
||||||
|
+dynamic-resolution \
|
||||||
|
+auto-reconnect \
|
||||||
|
+home-drive \
|
||||||
|
/wm-class:"Microsoft Windows" \
|
||||||
|
/v:"$RDP_IP" &>/dev/null &
|
||||||
|
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 \
|
||||||
|
-wallpaper \
|
||||||
|
+dynamic-resolution \
|
||||||
|
"$MULTI_FLAG" \
|
||||||
|
/app:program:"$2" \
|
||||||
|
/v:"$RDP_IP" &>/dev/null &
|
||||||
|
else
|
||||||
|
# Script summoned from right-click menu with officially supported application name 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 \
|
||||||
|
-wallpaper \
|
||||||
|
+dynamic-resolution \
|
||||||
|
"$MULTI_FLAG" \
|
||||||
|
/wm-class:"$FULL_NAME" \
|
||||||
|
/app:program:"$WIN_EXECUTABLE",icon:"$ICON",name:"$FULL_NAME" \
|
||||||
|
/v:"$RDP_IP" &>/dev/null &
|
||||||
|
else
|
||||||
|
# Convert path from UNIX to Windows style.
|
||||||
|
FILE_PATH=$(echo "$2" | sed 's|'"${HOME}"'|\\\\tsclient\\home|;s|/|\\|g;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 \
|
||||||
|
-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 &
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
### MAIN LOGIC ###
|
||||||
|
#set -x # Enable for debugging.
|
||||||
dprint "START"
|
dprint "START"
|
||||||
|
dprint "SCRIPT_DIR: ${SCRIPT_DIR_PATH}"
|
||||||
if [ -f "$HOME/.local/share/winapps/run" ]; then
|
dprint "SCRIPT_ARGS: ${*}"
|
||||||
LAST_RAN=$(stat -t -c %Y "$HOME/.local/share/winapps/run")
|
dprint "HOME_DIR: ${HOME}"
|
||||||
dprint "LAST_RAN:${LAST_RAN}"
|
mkdir -p "$APPDATA_PATH"
|
||||||
touch "$HOME/.local/share/winapps/run"
|
waLastRun
|
||||||
THIS_RUN=$(stat -t -c %Y "$HOME/.local/share/winapps/run")
|
waLoadConfig
|
||||||
dprint "THIS_RUN:$THIS_RUN"
|
waGetFreeRDPCommand
|
||||||
if ((THIS_RUN - LAST_RAN < 2)); then
|
waCheckGroupMembership
|
||||||
exit
|
waCheckVMRunning
|
||||||
fi
|
waCheckVMContactable
|
||||||
else
|
waRunCommand "$@"
|
||||||
touch "$HOME/.local/share/winapps/run"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${FREERDP_COMMAND}" ]; then
|
|
||||||
if command -v xfreerdp &> /dev/null
|
|
||||||
then
|
|
||||||
FREERDP_COMMAND="xfreerdp"
|
|
||||||
elif command -v xfreerdp3 &> /dev/null
|
|
||||||
then
|
|
||||||
FREERDP_COMMAND="xfreerdp3"
|
|
||||||
fi
|
|
||||||
elif command -v "$FREERDP_COMMAND" &> /dev/null
|
|
||||||
then
|
|
||||||
dprint "Using custom freerdp command $FREERDP_COMMAND"
|
|
||||||
else
|
|
||||||
echo "You have supplied a custom FreeRDP command, but the command is not available."
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$RDP_IP" ]; then
|
|
||||||
if groups | grep -vq 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 ! virsh list --state-running --name | grep -q '^RDPWindows$'; then
|
|
||||||
echo "RDPWindows is not running. Please run:"
|
|
||||||
echo " virsh start RDPWindows"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
RDP_IP=$(virsh net-dhcp-leases default | grep "RDPWindows" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}')
|
|
||||||
fi
|
|
||||||
|
|
||||||
dprint "1:$1"
|
|
||||||
dprint "2:$2"
|
|
||||||
# this is just for debug logging anyways
|
|
||||||
# shellcheck disable=SC2145
|
|
||||||
dprint "@:${@}"
|
|
||||||
|
|
||||||
MULTI_FLAG="+span"
|
|
||||||
if [ "$MULTIMON" = "true" ]; then
|
|
||||||
MULTI_FLAG="/multimon"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Append additional flags or parameters to FreeRDP
|
|
||||||
if [[ -n "$RDP_FLAGS" ]]; then
|
|
||||||
FREERDP_COMMAND="$FREERDP_COMMAND $RDP_FLAGS"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$1" = "check" ]; then
|
|
||||||
# Open File Explorer
|
|
||||||
dprint "CHECK"
|
|
||||||
COMMAND=(
|
|
||||||
"${FREERDP_COMMAND}"
|
|
||||||
"/d:${RDP_DOMAIN}"
|
|
||||||
"/u:${RDP_USER}"
|
|
||||||
"/p:${RDP_PASS}"
|
|
||||||
"/scale:${RDP_SCALE}"
|
|
||||||
"+auto-reconnect"
|
|
||||||
"+home-drive"
|
|
||||||
"-wallpaper"
|
|
||||||
"+dynamic-resolution"
|
|
||||||
"${MULTI_FLAG}"
|
|
||||||
"/app:program:explorer.exe"
|
|
||||||
"/v:${RDP_IP}"
|
|
||||||
)
|
|
||||||
"${COMMAND[@]}"
|
|
||||||
elif [ "$1" = "windows" ]; then
|
|
||||||
# Open Virtual Machine
|
|
||||||
dprint "WINDOWS"
|
|
||||||
COMMAND=(
|
|
||||||
"${FREERDP_COMMAND}"
|
|
||||||
"/d:${RDP_DOMAIN}"
|
|
||||||
"/u:${RDP_USER}"
|
|
||||||
"/p:${RDP_PASS}"
|
|
||||||
"/scale:${RDP_SCALE}"
|
|
||||||
"+dynamic-resolution"
|
|
||||||
"+auto-reconnect"
|
|
||||||
"+home-drive"
|
|
||||||
"/wm-class:\"Microsoft Windows\""
|
|
||||||
"/v:${RDP_IP}"
|
|
||||||
)
|
|
||||||
# Run the command in the background, redirecting both stdout and stderr to /dev/null
|
|
||||||
"${COMMAND[@]}" 1>/dev/null 2>&1 &
|
|
||||||
elif [ "$1" = "manual" ]; then
|
|
||||||
# Open Specified Application
|
|
||||||
dprint "MANUAL:${2}"
|
|
||||||
COMMAND=(
|
|
||||||
"${FREERDP_COMMAND}"
|
|
||||||
"/d:${RDP_DOMAIN}"
|
|
||||||
"/u:${RDP_USER}"
|
|
||||||
"/p:${RDP_PASS}"
|
|
||||||
"/scale:${RDP_SCALE}"
|
|
||||||
"+auto-reconnect"
|
|
||||||
"+home-drive"
|
|
||||||
"+dynamic-resolution"
|
|
||||||
"${MULTI_FLAG}"
|
|
||||||
"/app:program:${2}"
|
|
||||||
"/v:${RDP_IP}"
|
|
||||||
)
|
|
||||||
# Run the command in the background, redirecting both stdout and stderr to /dev/null
|
|
||||||
"${COMMAND[@]}" 1>/dev/null 2>&1 &
|
|
||||||
elif [ "$1" != "install" ]; then
|
|
||||||
dprint "DIR:${DIR}"
|
|
||||||
if [ -e "${DIR}/../apps/$1/info" ]; then
|
|
||||||
# shellcheck disable=SC1090
|
|
||||||
. "${DIR}/../apps/$1/info"
|
|
||||||
ICON="${DIR}/../apps/$1/icon.svg"
|
|
||||||
elif [ -e "$HOME/.local/share/winapps/apps/$1/info" ]; then
|
|
||||||
# shellcheck disable=SC1090
|
|
||||||
. "$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
|
|
||||||
# shellcheck disable=SC1090
|
|
||||||
. "/usr/local/share/winapps/apps/$1/info"
|
|
||||||
ICON="/usr/local/share/winapps/apps/$1/icon.svg"
|
|
||||||
else
|
|
||||||
echo "You need to run 'installer.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}"
|
|
||||||
# shellcheck disable=SC2140
|
|
||||||
COMMAND=(
|
|
||||||
"${FREERDP_COMMAND}"
|
|
||||||
"/d:${RDP_DOMAIN}"
|
|
||||||
"/u:${RDP_USER}"
|
|
||||||
"/p:${RDP_PASS}"
|
|
||||||
"/scale:${RDP_SCALE}"
|
|
||||||
"+auto-reconnect"
|
|
||||||
"+clipboard"
|
|
||||||
"+home-drive"
|
|
||||||
"-wallpaper"
|
|
||||||
"+dynamic-resolution"
|
|
||||||
"${MULTI_FLAG}"
|
|
||||||
"/wm-class:${FULL_NAME}"
|
|
||||||
"/app:program:${WIN_EXECUTABLE},icon:${ICON},name:${FULL_NAME},cmd:\"${FILE}\""
|
|
||||||
"/v:${RDP_IP}"
|
|
||||||
)
|
|
||||||
# Run the command in the background, redirecting both stdout and stderr to /dev/null
|
|
||||||
echo "${COMMAND[@]}" #1>/dev/null 2>&1 &
|
|
||||||
else
|
|
||||||
COMMAND=(
|
|
||||||
"${FREERDP_COMMAND}"
|
|
||||||
"/d:${RDP_DOMAIN}"
|
|
||||||
"/u:${RDP_USER}"
|
|
||||||
"/p:${RDP_PASS}"
|
|
||||||
"/scale:${RDP_SCALE}"
|
|
||||||
"+auto-reconnect"
|
|
||||||
"+clipboard"
|
|
||||||
"+home-drive"
|
|
||||||
"-wallpaper"
|
|
||||||
"+dynamic-resolution"
|
|
||||||
"${MULTI_FLAG}"
|
|
||||||
"/wm-class:${FULL_NAME}"
|
|
||||||
"/app:program:${WIN_EXECUTABLE},icon:${ICON},name:${FULL_NAME}"
|
|
||||||
"/v:${RDP_IP}"
|
|
||||||
)
|
|
||||||
# Run the command in the background, redirecting both stdout and stderr to /dev/null
|
|
||||||
"${COMMAND[@]}" 1>/dev/null 2>&1 &
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
dprint "END"
|
dprint "END"
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 259 KiB After Width: | Height: | Size: 4.4 MiB |
@ -1,3 +1,14 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048">
|
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" width="2048" height="2048">
|
||||||
<path d="M0 268l768-107v735H0V268zM1920 0v896H896V143L1920 0zM896 1024h1024v896L896 1777v-753zm-896 0h768v735L0 1652v-628z" fill="#73A1FB"/>
|
<title>POO</title>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="g1" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-2048,-2048,2048,-2048,2048,2048)">
|
||||||
|
<stop offset="0" stop-color="#067cd6"/>
|
||||||
|
<stop offset=".4" stop-color="#0f85da"/>
|
||||||
|
<stop offset="1" stop-color="#7adcff"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<style>
|
||||||
|
.s0 { fill: url(#g1) }
|
||||||
|
</style>
|
||||||
|
<path id="Windows" fill-rule="evenodd" class="s0" d="m228 0h746v974h-974v-746c0-125.9 102.1-228 228-228zm746 2048h-746c-125.9 0-228-102.1-228-228v-746h974zm846-2048c125.9 0 228 102.1 228 228v746h-974v-974zm228 1820c0 125.9-102.1 228-228 228h-746v-974h974z"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 217 B After Width: | Height: | Size: 751 B |
@ -1,835 +1,186 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
#======== Based on (then updated) https://raw.githubusercontent.com/tanhauhau/Inquirer.sh/master/dist/inquirer.sh ========
|
### GLOBAL CONSTANTS ###
|
||||||
|
declare -r ANSI_LIGHT_BLUE="\033[1;94m" # Light blue text.
|
||||||
|
declare -r ANSI_LIGHT_GREEN="\033[92m" # Light green text.
|
||||||
|
declare -r ANSI_CLEAR_TEXT="\033[0m" # Default text.
|
||||||
|
declare -r DIALOG_HEIGHT=14 # Height of dialog window.
|
||||||
|
declare -r TEXT_WIDTH_OFFSET=4 # Offset for fitting title text.
|
||||||
|
declare -r CHK_OPTION_WIDTH_OFFSET=10 # Offset for fitting options.
|
||||||
|
declare -r MNU_OPTION_WIDTH_OFFSET=7 # Offset for fitting options.
|
||||||
|
|
||||||
# License from: https://github.com/kahkhang/Inquirer.sh/blob/master/LICENSE
|
### FUNCTIONS ###
|
||||||
|
function inqMenu() {
|
||||||
|
# DECLARE VARIABLES.
|
||||||
|
# Variables created from function arguments:
|
||||||
|
declare DIALOG_TEXT="$1" # Dialog heading.
|
||||||
|
declare INPUT_OPTIONS_VAR="$2" # Input variable name.
|
||||||
|
declare RETURN_STRING_VAR="$3" # Output variable name.
|
||||||
|
declare -n INPUT_OPTIONS="$INPUT_OPTIONS_VAR" # Input array nameref.
|
||||||
|
declare -n RETURN_STRING="$RETURN_STRING_VAR" # Output string nameref.
|
||||||
|
# Note: namerefs allow changes made through the nameref to affect the
|
||||||
|
# referenced variable, even across different scopes like function calls.
|
||||||
|
|
||||||
# The MIT License (MIT)
|
# Other variables:
|
||||||
|
declare TRIMMED_OPTIONS=() # Input array post-trimming.
|
||||||
|
declare PADDED_OPTIONS=() # Input array with extra white space.
|
||||||
|
declare DIALOG_OPTIONS=() # Input array for options dialog.
|
||||||
|
declare DIALOG_WIDTH=0 # Width of dialog window.
|
||||||
|
declare OPTION_NUMBER=0 # Number of options in dialog window.
|
||||||
|
declare SELECTED_OPTIONS_STRING="" # Output value from dialog window.
|
||||||
|
|
||||||
# Copyright (c) 2017
|
# MAIN LOGIC.
|
||||||
|
# Trim leading and trailing white space for each option.
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
for OPTION in "${INPUT_OPTIONS[@]}"; do
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
TRIMMED_OPTIONS+=("$(echo "$OPTION" | sed 's/^[ \t]*//;s/[ \t]*$//')")
|
||||||
# in the Software without restriction, including without limitation the rights
|
|
||||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
# copies of the Software, and to permit persons to whom the Software is
|
|
||||||
# furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
# The above copyright notice and this permission notice shall be included in all
|
|
||||||
# copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
# shellcheck disable=all
|
|
||||||
|
|
||||||
# store the current set options
|
|
||||||
OLD_SET=$-
|
|
||||||
set -e
|
|
||||||
|
|
||||||
arrow="$(echo -e '\xe2\x9d\xaf')"
|
|
||||||
checked="$(echo -e '\xe2\x97\x89')"
|
|
||||||
unchecked="$(echo -e '\xe2\x97\xaf')"
|
|
||||||
down_arrow=$(echo -e '\u23f7')
|
|
||||||
up_arrow=$(echo -e '\u23f6')
|
|
||||||
|
|
||||||
black="$(tput setaf 0)"
|
|
||||||
red="$(tput setaf 1)"
|
|
||||||
green="$(tput setaf 2)"
|
|
||||||
yellow="$(tput setaf 3)"
|
|
||||||
blue="$(tput setaf 4)"
|
|
||||||
magenta="$(tput setaf 5)"
|
|
||||||
cyan="$(tput setaf 6)"
|
|
||||||
white="$(tput setaf 7)"
|
|
||||||
bold="$(tput bold)"
|
|
||||||
normal="$(tput sgr0)"
|
|
||||||
dim=$'\e[2m'
|
|
||||||
|
|
||||||
print() {
|
|
||||||
echo "$1"
|
|
||||||
tput el
|
|
||||||
}
|
|
||||||
|
|
||||||
join() {
|
|
||||||
local IFS=$'\n'
|
|
||||||
local _join_list
|
|
||||||
eval _join_list=( '"${'${1}'[@]}"' )
|
|
||||||
local first=true
|
|
||||||
for item in ${_join_list[@]}; do
|
|
||||||
if [ "$first" = true ]; then
|
|
||||||
printf "%s" "$item"
|
|
||||||
first=false
|
|
||||||
else
|
|
||||||
printf "${2-, }%s" "$item"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
function gen_env_from_options() {
|
|
||||||
local IFS=$'\n'
|
|
||||||
local _indices
|
|
||||||
local _env_names
|
|
||||||
local _checkbox_selected
|
|
||||||
eval _indices=( '"${'${1}'[@]}"' )
|
|
||||||
eval _env_names=( '"${'${2}'[@]}"' )
|
|
||||||
|
|
||||||
for i in $(gen_index ${#_env_names[@]}); do
|
|
||||||
_checkbox_selected[$i]=false
|
|
||||||
done
|
done
|
||||||
|
|
||||||
for i in ${_indices[@]}; do
|
# Find the length of the longest option to set the dialog width.
|
||||||
_checkbox_selected[$i]=true
|
for OPTION in "${TRIMMED_OPTIONS[@]}"; do
|
||||||
done
|
if [ "${#OPTION}" -gt "$DIALOG_WIDTH" ]; then
|
||||||
|
DIALOG_WIDTH=${#OPTION}
|
||||||
for i in $(gen_index ${#_env_names[@]}); do
|
|
||||||
printf "%s=%s\n" "${_env_names[$i]}" "${_checkbox_selected[$i]}"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
on_default() {
|
|
||||||
true;
|
|
||||||
}
|
|
||||||
|
|
||||||
on_keypress() {
|
|
||||||
local OLD_IFS
|
|
||||||
local IFS
|
|
||||||
local key
|
|
||||||
OLD_IFS=$IFS
|
|
||||||
local on_up=${1:-on_default}
|
|
||||||
local on_down=${2:-on_default}
|
|
||||||
local on_space=${3:-on_default}
|
|
||||||
local on_enter=${4:-on_default}
|
|
||||||
local on_left=${5:-on_default}
|
|
||||||
local on_right=${6:-on_default}
|
|
||||||
local on_ascii=${7:-on_default}
|
|
||||||
local on_backspace=${8:-on_default}
|
|
||||||
_break_keypress=false
|
|
||||||
while IFS="" read -rsn1 key; do
|
|
||||||
case "$key" in
|
|
||||||
$'\x1b')
|
|
||||||
read -rsn1 key
|
|
||||||
if [[ "$key" == "[" ]]; then
|
|
||||||
read -rsn1 key
|
|
||||||
case "$key" in
|
|
||||||
'A') eval $on_up ;;
|
|
||||||
'B') eval $on_down ;;
|
|
||||||
'D') eval $on_left ;;
|
|
||||||
'C') eval $on_right ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
' ') eval $on_space ' ' ;;
|
|
||||||
[a-z0-9A-Z\!\#\$\&\+\,\-\.\/\;\=\?\@\[\]\^\_\{\}\~]) eval $on_ascii $key ;;
|
|
||||||
$'\x7f') eval $on_backspace $key ;;
|
|
||||||
'') eval $on_enter $key ;;
|
|
||||||
esac
|
|
||||||
if [ $_break_keypress = true ]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
IFS=$OLD_IFS
|
|
||||||
}
|
|
||||||
|
|
||||||
gen_index() {
|
|
||||||
local k=$1
|
|
||||||
local l=0
|
|
||||||
if [ $k -gt 0 ]; then
|
|
||||||
for l in $(seq $k)
|
|
||||||
do
|
|
||||||
echo "$l-1" | bc
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
# Reset character attributes, make cursor visible, and restore
|
|
||||||
# previous screen contents (if possible).
|
|
||||||
tput sgr0
|
|
||||||
tput cnorm
|
|
||||||
stty echo
|
|
||||||
|
|
||||||
# Restore `set e` option to its orignal value
|
|
||||||
if [[ $OLD_SET =~ e ]]
|
|
||||||
then set -e
|
|
||||||
else set +e
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
control_c() {
|
|
||||||
cleanup
|
|
||||||
exit $?
|
|
||||||
}
|
|
||||||
|
|
||||||
select_indices() {
|
|
||||||
local _select_list
|
|
||||||
local _select_indices
|
|
||||||
local _select_selected=()
|
|
||||||
eval _select_list=( '"${'${1}'[@]}"' )
|
|
||||||
eval _select_indices=( '"${'${2}'[@]}"' )
|
|
||||||
local _select_var_name=$3
|
|
||||||
eval $_select_var_name\=\(\)
|
|
||||||
for i in $(gen_index ${#_select_indices[@]}); do
|
|
||||||
eval $_select_var_name\+\=\(\""${_select_list[${_select_indices[$i]}]}"\"\)
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
print_checkbox_line_arrow() {
|
|
||||||
if [ "${_checkbox_selected[$1]}" = true ]; then
|
|
||||||
printf "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[$1]} ${normal}"
|
|
||||||
else
|
|
||||||
printf "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[$1]} ${normal}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
print_checkbox_line() {
|
|
||||||
if [ "${_checkbox_selected[$1]}" = true ]; then
|
|
||||||
printf " ${green}${checked}${normal} ${_checkbox_list[$1]} ${normal}"
|
|
||||||
else
|
|
||||||
printf " ${normal}${unchecked} ${_checkbox_list[$1]} ${normal}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# https://www.gnu.org/software/termutils/manual/termutils-2.0/html_chapter/tput_1.html
|
|
||||||
# http://linuxcommand.org/lc3_adv_tput.php
|
|
||||||
on_checkbox_input_up2() {
|
|
||||||
#remove_checkbox_instructions
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
|
|
||||||
if [ "${_checkbox_selected[$_current_index]}" = true ]; then
|
|
||||||
printf " ${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}"
|
|
||||||
else
|
|
||||||
printf " ${unchecked} ${_checkbox_list[$_current_index]} ${normal}"
|
|
||||||
fi
|
|
||||||
tput el
|
|
||||||
|
|
||||||
if [ $_current_index = 0 ]; then
|
|
||||||
_current_index=$((${#_checkbox_list[@]}-1))
|
|
||||||
tput cud $((${#_checkbox_list[@]}-1))
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
else
|
|
||||||
_current_index=$((_current_index-1))
|
|
||||||
|
|
||||||
tput cuu1 # Up one line
|
|
||||||
tput cub "$(tput cols)" # Back to beginning (does this work?)
|
|
||||||
tput el # Clear to end of line
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "${_checkbox_selected[$_current_index]}" = true ]; then
|
|
||||||
printf "${cyan}${arrow}${green}${checked}${normal} ${_checkbox_list[$_current_index]} ${normal}"
|
|
||||||
else
|
|
||||||
printf "${cyan}${arrow}${normal}${unchecked} ${_checkbox_list[$_current_index]} ${normal}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
on_checkbox_input_up() {
|
|
||||||
#remove_checkbox_instructions
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
if (( ${_current_row} > 0 )) || (( ${#_checkbox_list[@]} <= 5 )); then
|
|
||||||
print_checkbox_line $_current_index
|
|
||||||
tput el
|
|
||||||
if [ $_current_index = 0 ]; then
|
|
||||||
_current_index=$((${#_checkbox_list[@]}-1))
|
|
||||||
_current_row=4
|
|
||||||
tput cud $((${#_checkbox_list[@]}-1))
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
else
|
|
||||||
_current_index=$((_current_index-1))
|
|
||||||
_current_row=$((_current_row-1))
|
|
||||||
tput cuu1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
fi
|
|
||||||
print_checkbox_line_arrow $_current_index
|
|
||||||
else
|
|
||||||
if [ $_current_index = 0 ]; then
|
|
||||||
_current_index=$((${#_checkbox_list[@]}-1))
|
|
||||||
_current_row=4
|
|
||||||
tput cuu 1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
printf " ${cyan}${up_arrow}${normal}"
|
|
||||||
for I in 4 3 2 1; do
|
|
||||||
tput cud1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
print_checkbox_line $((_current_index-I))
|
|
||||||
done
|
|
||||||
tput cud1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
print_checkbox_line_arrow $((_current_index))
|
|
||||||
tput cud1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
printf " ${dim}${down_arrow}${normal}"
|
|
||||||
tput cuu 1
|
|
||||||
else
|
|
||||||
_current_index=$((_current_index-1))
|
|
||||||
tput cud 5
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
printf " ${cyan}${down_arrow}${normal}"
|
|
||||||
for I in 4 3 2 1; do
|
|
||||||
tput cuu1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
print_checkbox_line $((_current_index+I))
|
|
||||||
done
|
|
||||||
tput cuu1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
print_checkbox_line_arrow $((_current_index))
|
|
||||||
if [ $_current_index = 0 ]; then
|
|
||||||
tput cuu1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
printf " ${dim}${up_arrow}${normal}"
|
|
||||||
tput cud1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
on_checkbox_input_down() {
|
|
||||||
#remove_checkbox_instructions
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
if (( ${_current_row} < 4 )) || (( ${#_checkbox_list[@]} <= 5 )); then
|
|
||||||
print_checkbox_line $_current_index
|
|
||||||
tput el
|
|
||||||
if [ $_current_index = $((${#_checkbox_list[@]}-1)) ]; then
|
|
||||||
_current_index=0
|
|
||||||
_current_row=0
|
|
||||||
tput cuu $((${#_checkbox_list[@]}-1))
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
else
|
|
||||||
_current_index=$((_current_index+1))
|
|
||||||
_current_row=$((_current_row+1))
|
|
||||||
tput cud1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
fi
|
|
||||||
print_checkbox_line_arrow $_current_index
|
|
||||||
else
|
|
||||||
if [ $_current_index = $((${#_checkbox_list[@]}-1)) ]; then
|
|
||||||
_current_index=0
|
|
||||||
_current_row=0
|
|
||||||
tput cuu 5
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
printf " ${dim}${up_arrow}${normal}"
|
|
||||||
tput cud1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
print_checkbox_line_arrow $((_current_index))
|
|
||||||
for I in 1 2 3 4; do
|
|
||||||
tput cud1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
print_checkbox_line $((_current_index+I))
|
|
||||||
done
|
|
||||||
tput cud1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
printf " ${cyan}${down_arrow}${normal}"
|
|
||||||
tput cuu 5
|
|
||||||
else
|
|
||||||
_current_index=$((_current_index+1))
|
|
||||||
tput cuu 5
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
printf " ${cyan}${up_arrow}${normal}"
|
|
||||||
for I in 4 3 2 1; do
|
|
||||||
tput cud1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
print_checkbox_line $((_current_index-I))
|
|
||||||
done
|
|
||||||
tput cud1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
print_checkbox_line_arrow $((_current_index))
|
|
||||||
if [ $_current_index = $((${#_checkbox_list[@]}-1)) ]; then
|
|
||||||
tput cud1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
printf " ${dim}${down_arrow}${normal}"
|
|
||||||
tput cuu1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
on_checkbox_input_enter() {
|
|
||||||
remove_checkbox_instructions
|
|
||||||
local OLD_IFS
|
|
||||||
OLD_IFS=$IFS
|
|
||||||
_checkbox_selected_indices=()
|
|
||||||
_checkbox_selected_options=()
|
|
||||||
IFS=$'\n'
|
|
||||||
|
|
||||||
for i in $(gen_index ${#_checkbox_list[@]}); do
|
|
||||||
if [ "${_checkbox_selected[$i]}" = true ]; then
|
|
||||||
_checkbox_selected_indices+=($i)
|
|
||||||
_checkbox_selected_options+=("${_checkbox_list[$i]}")
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if (( ${#_checkbox_list[@]} <= 5 )); then
|
# Apply the offset value to the dialog width.
|
||||||
tput cud $((${#_checkbox_list[@]}-${_current_index}))
|
DIALOG_WIDTH=$((DIALOG_WIDTH + MNU_OPTION_WIDTH_OFFSET))
|
||||||
tput cub "$(tput cols)"
|
|
||||||
for i in $(seq $((${#_checkbox_list[@]}+1))); do
|
|
||||||
tput el1
|
|
||||||
tput el
|
|
||||||
tput cuu1
|
|
||||||
done
|
|
||||||
else
|
|
||||||
tput cud $((6-${_current_row}))
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
for i in $(seq 8); do
|
|
||||||
tput el1
|
|
||||||
tput el
|
|
||||||
tput cuu1
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
|
|
||||||
tput cuf $((${#prompt}+3))
|
# Adjust the dialog width again if the dialog text is longer.
|
||||||
printf "${cyan}$(join _checkbox_selected_options)${normal}"
|
if [ "$DIALOG_WIDTH" -lt $((${#DIALOG_TEXT} + TEXT_WIDTH_OFFSET)) ]; then
|
||||||
tput el
|
DIALOG_WIDTH="$((${#DIALOG_TEXT} + TEXT_WIDTH_OFFSET))"
|
||||||
|
|
||||||
tput cud1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
|
|
||||||
_break_keypress=true
|
|
||||||
IFS=$OLD_IFS
|
|
||||||
}
|
|
||||||
|
|
||||||
on_checkbox_input_space() {
|
|
||||||
#remove_checkbox_instructions
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
if [ "${_checkbox_selected[$_current_index]}" = true ]; then
|
|
||||||
_checkbox_selected[$_current_index]=false
|
|
||||||
else
|
|
||||||
_checkbox_selected[$_current_index]=true
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
print_checkbox_line_arrow $_current_index
|
# Pad option text with trailing white space to left-align all options.
|
||||||
}
|
for OPTION in "${TRIMMED_OPTIONS[@]}"; do
|
||||||
|
local PAD_LENGTH=$((DIALOG_WIDTH - MNU_OPTION_WIDTH_OFFSET - ${#OPTION}))
|
||||||
remove_checkbox_instructions() {
|
# shellcheck disable=SC2155
|
||||||
if [ $_first_keystroke = true ]; then
|
local PADDED_OPTION="${OPTION}$(printf '%*s' $PAD_LENGTH)"
|
||||||
tput cuu $((${_current_index}+1))
|
PADDED_OPTIONS+=("$PADDED_OPTION")
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput cuf $((${#prompt}+3))
|
|
||||||
tput el
|
|
||||||
tput cud $((${_current_index}+1))
|
|
||||||
_first_keystroke=false
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# for vim movements
|
|
||||||
on_checkbox_input_ascii() {
|
|
||||||
local key=$1
|
|
||||||
case $key in
|
|
||||||
"j" ) on_checkbox_input_down ;;
|
|
||||||
"k" ) on_checkbox_input_up ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
_checkbox_input() {
|
|
||||||
local i
|
|
||||||
local j
|
|
||||||
prompt=$1
|
|
||||||
eval _checkbox_list=( '"${'${2}'[@]}"' )
|
|
||||||
_current_index=0
|
|
||||||
_current_row=0
|
|
||||||
_first_keystroke=true
|
|
||||||
|
|
||||||
trap control_c SIGINT EXIT
|
|
||||||
|
|
||||||
stty -echo
|
|
||||||
tput civis
|
|
||||||
|
|
||||||
print "${normal}${green}?${normal} ${bold}${prompt}${normal} ${dim}(Press <space> to select, <enter> to finalize)${normal}"
|
|
||||||
|
|
||||||
for i in $(gen_index ${#_checkbox_list[@]}); do
|
|
||||||
_checkbox_selected[$i]=false
|
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ -n "$3" ]; then
|
# Convert options into the appropriate format for a 'dialog' menu.
|
||||||
eval _selected_indices=( '"${'${3}'[@]}"' )
|
for PADDED_OPTION in "${PADDED_OPTIONS[@]}"; do
|
||||||
for i in ${_selected_indices[@]}; do
|
DIALOG_OPTIONS+=("$PADDED_OPTION" "")
|
||||||
_checkbox_selected[$i]=true
|
done
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (( ${#_checkbox_list[@]} > 5 )); then
|
# Store the number of options.
|
||||||
tput cub "$(tput cols)"
|
OPTION_NUMBER="${#INPUT_OPTIONS[@]}"
|
||||||
print " ${dim}${up_arrow}${normal}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
for i in $(gen_index ${#_checkbox_list[@]}); do
|
# Produce checkbox.
|
||||||
tput cub "$(tput cols)"
|
# The output string contains options delimited by spaces.
|
||||||
if [ $i = 0 ]; then
|
# Each option is enclosed in double quotes within the output string.
|
||||||
print_checkbox_line_arrow $i
|
# For example: '"Option 1 " "The Second Option " " Option Number 3 "'
|
||||||
else
|
SELECTED_OPTIONS_STRING=$(dialog \
|
||||||
print_checkbox_line $i
|
--keep-tite \
|
||||||
fi
|
--clear \
|
||||||
print ""
|
--no-shadow \
|
||||||
tput el
|
--menu \
|
||||||
if (( $i > 3 )) && (( ${#_checkbox_list[@]} > 5 )); then
|
"$DIALOG_TEXT" \
|
||||||
print " ${cyan}${down_arrow}${normal}"
|
"$DIALOG_HEIGHT" \
|
||||||
break
|
"$DIALOG_WIDTH" \
|
||||||
|
"$OPTION_NUMBER" \
|
||||||
|
"${DIALOG_OPTIONS[@]}" \
|
||||||
|
2>&1 >/dev/tty) || exit 0
|
||||||
|
|
||||||
|
# Remove white space added previously.
|
||||||
|
RETURN_STRING=$(echo "$SELECTED_OPTIONS_STRING" |
|
||||||
|
sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
||||||
|
|
||||||
|
# Remove escapes (introduced by 'dialog' if options have parentheses).
|
||||||
|
RETURN_STRING="${RETURN_STRING//\\/}" # ${variable//search/replace}
|
||||||
|
|
||||||
|
# Display question and response.
|
||||||
|
echo -e "${ANSI_LIGHT_GREEN}Q) ${ANSI_CLEAR_TEXT}${ANSI_LIGHT_BLUE}${DIALOG_TEXT}${ANSI_CLEAR_TEXT} --> ${ANSI_LIGHT_GREEN}${RETURN_STRING}${ANSI_CLEAR_TEXT}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function inqChkBx() {
|
||||||
|
# DECLARE VARIABLES.
|
||||||
|
# Variables created from function arguments:
|
||||||
|
declare DIALOG_TEXT="$1" # Dialog heading.
|
||||||
|
declare INPUT_OPTIONS_VAR="$2" # Input variable name.
|
||||||
|
declare RETURN_ARRAY_VAR="$3" # Output variable name.
|
||||||
|
declare -n INPUT_OPTIONS="$INPUT_OPTIONS_VAR" # Input array nameref.
|
||||||
|
declare -n RETURN_ARRAY="$RETURN_ARRAY_VAR" # Output array nameref.
|
||||||
|
# Note: namerefs allow changes made through the nameref to affect the
|
||||||
|
# referenced variable, even across different scopes like function calls.
|
||||||
|
|
||||||
|
# Other variables:
|
||||||
|
declare TRIMMED_OPTIONS=() # Input array post-trimming.
|
||||||
|
declare PADDED_OPTIONS=() # Input array with extra white space.
|
||||||
|
declare DIALOG_OPTIONS=() # Input array for options dialog.
|
||||||
|
declare DIALOG_WIDTH=0 # Width of dialog window.
|
||||||
|
declare OPTION_NUMBER=0 # Number of options in dialog window.
|
||||||
|
declare SELECTED_OPTIONS_STRING="" # Output value from dialog window.
|
||||||
|
|
||||||
|
# MAIN LOGIC.
|
||||||
|
# Trim leading and trailing white space for each option.
|
||||||
|
for OPTION in "${INPUT_OPTIONS[@]}"; do
|
||||||
|
TRIMMED_OPTIONS+=("$(echo "$OPTION" | sed 's/^[ \t]*//;s/[ \t]*$//')")
|
||||||
|
done
|
||||||
|
|
||||||
|
# Find the length of the longest option to set the dialog width.
|
||||||
|
for OPTION in "${TRIMMED_OPTIONS[@]}"; do
|
||||||
|
if [ "${#OPTION}" -gt "$DIALOG_WIDTH" ]; then
|
||||||
|
DIALOG_WIDTH=${#OPTION}
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
for j in $(gen_index ${#_checkbox_list[@]}); do
|
# Apply the offset value to the dialog width.
|
||||||
tput cuu1
|
DIALOG_WIDTH=$((DIALOG_WIDTH + CHK_OPTION_WIDTH_OFFSET))
|
||||||
if (( $j > 4 )); then
|
|
||||||
break
|
# Adjust the dialog width again if the dialog text is longer.
|
||||||
fi
|
if [ "$DIALOG_WIDTH" -lt $((${#DIALOG_TEXT} + TEXT_WIDTH_OFFSET)) ]; then
|
||||||
|
DIALOG_WIDTH="$((${#DIALOG_TEXT} + TEXT_WIDTH_OFFSET))"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Pad option text with trailing white space to left-align all options.
|
||||||
|
for OPTION in "${TRIMMED_OPTIONS[@]}"; do
|
||||||
|
local PAD_LENGTH=$((DIALOG_WIDTH - CHK_OPTION_WIDTH_OFFSET - ${#OPTION}))
|
||||||
|
# shellcheck disable=SC2155
|
||||||
|
local PADDED_OPTION="${OPTION}$(printf '%*s' $PAD_LENGTH)"
|
||||||
|
PADDED_OPTIONS+=("$PADDED_OPTION")
|
||||||
done
|
done
|
||||||
|
|
||||||
on_keypress on_checkbox_input_up on_checkbox_input_down on_checkbox_input_space on_checkbox_input_enter on_default on_default on_checkbox_input_ascii
|
# Convert options into the appropriate format for a 'dialog' checkbox.
|
||||||
}
|
for PADDED_OPTION in "${PADDED_OPTIONS[@]}"; do
|
||||||
|
DIALOG_OPTIONS+=("$PADDED_OPTION" "" off)
|
||||||
checkbox_input() {
|
|
||||||
_checkbox_input "$1" "$2"
|
|
||||||
_checkbox_input_output_var_name=$3
|
|
||||||
select_indices _checkbox_list _checkbox_selected_indices $_checkbox_input_output_var_name
|
|
||||||
|
|
||||||
unset _checkbox_list
|
|
||||||
unset _break_keypress
|
|
||||||
unset _first_keystroke
|
|
||||||
unset _current_index
|
|
||||||
unset _checkbox_input_output_var_name
|
|
||||||
unset _checkbox_selected_indices
|
|
||||||
unset _checkbox_selected_options
|
|
||||||
|
|
||||||
cleanup
|
|
||||||
}
|
|
||||||
|
|
||||||
checkbox_input_indices() {
|
|
||||||
_checkbox_input "$1" "$2" "$3"
|
|
||||||
_checkbox_input_output_var_name=$3
|
|
||||||
|
|
||||||
eval $_checkbox_input_output_var_name\=\(\)
|
|
||||||
for i in $(gen_index ${#_checkbox_selected_indices[@]}); do
|
|
||||||
eval $_checkbox_input_output_var_name\+\=\(${_checkbox_selected_indices[$i]}\)
|
|
||||||
done
|
done
|
||||||
|
|
||||||
unset _checkbox_list
|
# Store the number of options.
|
||||||
unset _break_keypress
|
OPTION_NUMBER="${#INPUT_OPTIONS[@]}"
|
||||||
unset _first_keystroke
|
|
||||||
unset _current_index
|
|
||||||
unset _checkbox_input_output_var_name
|
|
||||||
unset _checkbox_selected_indices
|
|
||||||
unset _checkbox_selected_options
|
|
||||||
|
|
||||||
cleanup
|
# Produce checkbox.
|
||||||
}
|
# The output string contains options delimited by spaces.
|
||||||
|
# Each option is enclosed in double quotes within the output string.
|
||||||
|
# For example: '"Option 1 " "The Second Option " " Option Number 3 "'
|
||||||
|
SELECTED_OPTIONS_STRING=$(dialog \
|
||||||
|
--keep-tite \
|
||||||
|
--clear \
|
||||||
|
--no-shadow \
|
||||||
|
--checklist \
|
||||||
|
"$DIALOG_TEXT" \
|
||||||
|
"$DIALOG_HEIGHT" \
|
||||||
|
"$DIALOG_WIDTH" \
|
||||||
|
"$OPTION_NUMBER" \
|
||||||
|
"${DIALOG_OPTIONS[@]}" \
|
||||||
|
2>&1 >/dev/tty) || exit 0
|
||||||
|
|
||||||
|
# Convert the output string into an array.
|
||||||
|
# shellcheck disable=SC2001
|
||||||
|
while IFS= read -r LINE; do
|
||||||
|
LINE="${LINE/#\"/}" # Remove leading double quote.
|
||||||
|
LINE="${LINE/%\"/}" # Remove trailing double quote.
|
||||||
|
RETURN_ARRAY+=("$LINE") # Add to array.
|
||||||
|
done < <(echo "$SELECTED_OPTIONS_STRING" | sed 's/\" \"/\"\n\"/g')
|
||||||
|
|
||||||
|
# Final modifications.
|
||||||
|
for ((i = 0; i < ${#RETURN_ARRAY[@]}; i++)); do
|
||||||
|
# Remove white space added previously.
|
||||||
|
# shellcheck disable=SC2001
|
||||||
|
RETURN_ARRAY[i]=$(echo "${RETURN_ARRAY[i]}" |
|
||||||
|
sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
||||||
|
|
||||||
|
# Remove escapes (introduced by 'dialog' if options have parentheses).
|
||||||
on_list_input_up() {
|
RETURN_ARRAY[i]=${RETURN_ARRAY[i]//\\/} # ${variable//search/replace}
|
||||||
remove_list_instructions
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
|
|
||||||
printf " ${_list_options[$_list_selected_index]}"
|
|
||||||
tput el
|
|
||||||
|
|
||||||
if [ $_list_selected_index = 0 ]; then
|
|
||||||
_list_selected_index=$((${#_list_options[@]}-1))
|
|
||||||
tput cud $((${#_list_options[@]}-1))
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
else
|
|
||||||
_list_selected_index=$((_list_selected_index-1))
|
|
||||||
|
|
||||||
tput cuu1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf "${cyan}${arrow} %s ${normal}" "${_list_options[$_list_selected_index]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
on_list_input_down() {
|
|
||||||
remove_list_instructions
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
|
|
||||||
printf " ${_list_options[$_list_selected_index]}"
|
|
||||||
tput el
|
|
||||||
|
|
||||||
if [ $_list_selected_index = $((${#_list_options[@]}-1)) ]; then
|
|
||||||
_list_selected_index=0
|
|
||||||
tput cuu $((${#_list_options[@]}-1))
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
else
|
|
||||||
_list_selected_index=$((_list_selected_index+1))
|
|
||||||
tput cud1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
fi
|
|
||||||
printf "${cyan}${arrow} %s ${normal}" "${_list_options[$_list_selected_index]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
on_list_input_enter_space() {
|
|
||||||
local OLD_IFS
|
|
||||||
OLD_IFS=$IFS
|
|
||||||
IFS=$'\n'
|
|
||||||
|
|
||||||
tput cud $((${#_list_options[@]}-${_list_selected_index}))
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
|
|
||||||
for i in $(seq $((${#_list_options[@]}+1))); do
|
|
||||||
tput el1
|
|
||||||
tput el
|
|
||||||
tput cuu1
|
|
||||||
done
|
done
|
||||||
tput cub "$(tput cols)"
|
|
||||||
|
|
||||||
tput cuf $((${#prompt}+3))
|
|
||||||
printf "${cyan}${_list_options[$_list_selected_index]}${normal}"
|
|
||||||
tput el
|
|
||||||
|
|
||||||
tput cud1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
|
|
||||||
_break_keypress=true
|
|
||||||
IFS=$OLD_IFS
|
|
||||||
}
|
|
||||||
|
|
||||||
remove_list_instructions() {
|
|
||||||
if [ $_first_keystroke = true ]; then
|
|
||||||
tput cuu $((${_list_selected_index}+1))
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput cuf $((${#prompt}+3))
|
|
||||||
tput el
|
|
||||||
tput cud $((${_list_selected_index}+1))
|
|
||||||
_first_keystroke=false
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
_list_input() {
|
|
||||||
local i
|
|
||||||
local j
|
|
||||||
prompt=$1
|
|
||||||
eval _list_options=( '"${'${2}'[@]}"' )
|
|
||||||
|
|
||||||
_list_selected_index=0
|
|
||||||
_first_keystroke=true
|
|
||||||
|
|
||||||
trap control_c SIGINT EXIT
|
|
||||||
|
|
||||||
stty -echo
|
|
||||||
tput civis
|
|
||||||
|
|
||||||
print "${normal}${green}?${normal} ${bold}${prompt}${normal} ${dim}(Use arrow keys)${normal}"
|
|
||||||
|
|
||||||
for i in $(gen_index ${#_list_options[@]}); do
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
if [ $i = 0 ]; then
|
|
||||||
print "${cyan}${arrow} ${_list_options[$i]} ${normal}"
|
|
||||||
else
|
|
||||||
print " ${_list_options[$i]}"
|
|
||||||
fi
|
|
||||||
tput el
|
|
||||||
done
|
|
||||||
|
|
||||||
for j in $(gen_index ${#_list_options[@]}); do
|
|
||||||
tput cuu1
|
|
||||||
done
|
|
||||||
|
|
||||||
on_keypress on_list_input_up on_list_input_down on_list_input_enter_space on_list_input_enter_space
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
list_input() {
|
|
||||||
_list_input "$1" "$2"
|
|
||||||
local var_name=$3
|
|
||||||
eval $var_name=\'"${_list_options[$_list_selected_index]}"\'
|
|
||||||
unset _list_selected_index
|
|
||||||
unset _list_options
|
|
||||||
unset _break_keypress
|
|
||||||
unset _first_keystroke
|
|
||||||
|
|
||||||
cleanup
|
|
||||||
}
|
|
||||||
|
|
||||||
list_input_index() {
|
|
||||||
_list_input "$1" "$2"
|
|
||||||
local var_name=$3
|
|
||||||
eval $var_name=\'"$_list_selected_index"\'
|
|
||||||
unset _list_selected_index
|
|
||||||
unset _list_options
|
|
||||||
unset _break_keypress
|
|
||||||
unset _first_keystroke
|
|
||||||
|
|
||||||
cleanup
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
on_text_input_left() {
|
|
||||||
remove_regex_failed
|
|
||||||
if [ $_current_pos -gt 0 ]; then
|
|
||||||
tput cub1
|
|
||||||
_current_pos=$(($_current_pos-1))
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
on_text_input_right() {
|
|
||||||
remove_regex_failed
|
|
||||||
if [ $_current_pos -lt ${#_text_input} ]; then
|
|
||||||
tput cuf1
|
|
||||||
_current_pos=$(($_current_pos+1))
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
on_text_input_enter() {
|
|
||||||
remove_regex_failed
|
|
||||||
|
|
||||||
if [[ "$_text_input" =~ $_text_input_regex && "$(eval $_text_input_validator "$_text_input")" = true ]]; then
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput cuf $((${#_read_prompt}-19))
|
|
||||||
printf "${cyan}${_text_input}${normal}"
|
|
||||||
tput el
|
|
||||||
tput cud1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
eval $var_name=\'"${_text_input}"\'
|
|
||||||
_break_keypress=true
|
|
||||||
else
|
|
||||||
_text_input_regex_failed=true
|
|
||||||
tput civis
|
|
||||||
tput cud1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput el
|
|
||||||
printf "${red}>>${normal} $_text_input_regex_failed_msg"
|
|
||||||
tput cuu1
|
|
||||||
tput cub "$(tput cols)"
|
|
||||||
tput cuf $((${#_read_prompt}-19))
|
|
||||||
tput el
|
|
||||||
_text_input=""
|
|
||||||
_current_pos=0
|
|
||||||
tput cnorm
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
on_text_input_ascii() {
|
|
||||||
remove_regex_failed
|
|
||||||
local c=$1
|
|
||||||
|
|
||||||
if [ "$c" = '' ]; then
|
|
||||||
c=' '
|
|
||||||
fi
|
|
||||||
|
|
||||||
local rest="${_text_input:$_current_pos}"
|
|
||||||
_text_input="${_text_input:0:$_current_pos}$c$rest"
|
|
||||||
_current_pos=$(($_current_pos+1))
|
|
||||||
|
|
||||||
tput civis
|
|
||||||
printf "$c$rest"
|
|
||||||
tput el
|
|
||||||
if [ ${#rest} -gt 0 ]; then
|
|
||||||
tput cub ${#rest}
|
|
||||||
fi
|
|
||||||
tput cnorm
|
|
||||||
}
|
|
||||||
|
|
||||||
on_text_input_backspace() {
|
|
||||||
remove_regex_failed
|
|
||||||
if [ $_current_pos -gt 0 ]; then
|
|
||||||
local start="${_text_input:0:$(($_current_pos-1))}"
|
|
||||||
local rest="${_text_input:$_current_pos}"
|
|
||||||
_current_pos=$(($_current_pos-1))
|
|
||||||
tput cub 1
|
|
||||||
tput el
|
|
||||||
tput sc
|
|
||||||
printf "$rest"
|
|
||||||
tput rc
|
|
||||||
_text_input="$start$rest"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
remove_regex_failed() {
|
|
||||||
if [ $_text_input_regex_failed = true ]; then
|
|
||||||
_text_input_regex_failed=false
|
|
||||||
tput sc
|
|
||||||
tput cud1
|
|
||||||
tput el1
|
|
||||||
tput el
|
|
||||||
tput rc
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
text_input_default_validator() {
|
|
||||||
echo true;
|
|
||||||
}
|
|
||||||
|
|
||||||
text_input() {
|
|
||||||
local prompt=$1
|
|
||||||
local var_name=$2
|
|
||||||
local _text_input_regex="${3:-"\.+"}"
|
|
||||||
local _text_input_regex_failed_msg=${4:-"Input validation failed"}
|
|
||||||
local _text_input_validator=${5:-text_input_default_validator}
|
|
||||||
local _read_prompt_start=$'\e[32m?\e[39m\e[1m'
|
|
||||||
local _read_prompt_end=$'\e[22m'
|
|
||||||
local _read_prompt="$( echo "$_read_prompt_start ${prompt} $_read_prompt_end")"
|
|
||||||
local _current_pos=0
|
|
||||||
local _text_input_regex_failed=false
|
|
||||||
local _text_input=""
|
|
||||||
printf "$_read_prompt"
|
|
||||||
|
|
||||||
|
|
||||||
trap control_c SIGINT EXIT
|
|
||||||
|
|
||||||
stty -echo
|
|
||||||
tput cnorm
|
|
||||||
|
|
||||||
on_keypress on_default on_default on_text_input_ascii on_text_input_enter on_text_input_left on_text_input_right on_text_input_ascii on_text_input_backspace
|
|
||||||
eval $var_name=\'"${_text_input}"\'
|
|
||||||
|
|
||||||
cleanup
|
|
||||||
}
|
|
||||||
|
|
||||||
# =============================================================
|
|
||||||
|
|
||||||
function menuFromCmd() {
|
|
||||||
local mLOCALRESULT=$1
|
|
||||||
local mRESULT=''
|
|
||||||
read -r -a ARRAY <<< $3
|
|
||||||
list_input "$2" ARRAY mRESULT
|
|
||||||
eval $mLOCALRESULT="'$mRESULT'"
|
|
||||||
}
|
|
||||||
|
|
||||||
function menuFromArr() {
|
|
||||||
local mLOCALRESULT=$1
|
|
||||||
shift
|
|
||||||
local PROMPT=$1
|
|
||||||
shift
|
|
||||||
local ARRAY=("$@")
|
|
||||||
list_input "$PROMPT" ARRAY mRESULT
|
|
||||||
eval $mLOCALRESULT="'$mRESULT'"
|
|
||||||
}
|
|
||||||
|
|
||||||
function multiFromArr() {
|
|
||||||
local mLOCALRESULT=$1
|
|
||||||
shift
|
|
||||||
local PROMPT=$1
|
|
||||||
shift
|
|
||||||
local ARRAY=("$@")
|
|
||||||
checkbox_input "$PROMPT" ARRAY mRESULT
|
|
||||||
eval $mLOCALRESULT="'$mRESULT'"
|
|
||||||
}
|
}
|
||||||
|
1726
installer.sh
1726
installer.sh
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user