diff --git a/.config/Code/User/keybindings.json b/.config/Code/User/keybindings.json
index 1a12b65..47b18c1 100644
--- a/.config/Code/User/keybindings.json
+++ b/.config/Code/User/keybindings.json
@@ -390,7 +390,7 @@
"command": "-workbench.action.gotoLine"
},
{
- "key": "ctrl+g",
+ "key": "alt+space",
"command": "relative-goto.goto"
},
{
diff --git a/.config/Code/User/settings.json b/.config/Code/User/settings.json
index 4e5235c..7f3d995 100644
--- a/.config/Code/User/settings.json
+++ b/.config/Code/User/settings.json
@@ -1,126 +1,132 @@
-{
- //
- // Appearance
- "workbench.colorTheme": "Monokai Pro",
- "workbench.iconTheme": "Monokai Pro Icons",
- "editor.fontFamily": "Fira Code, Menlo, Monaco, 'Courier New', monospace",
- "editor.fontSize": 18,
- "editor.fontWeight": "normal",
- "editor.fontLigatures": true,
- "editor.stickyScroll.enabled": true,
- "editor.minimap.renderCharacters": false,
- "editor.suggestSelection": "first",
- "vsintellicode.modify.editor.suggestSelection": "automaticallyOverrodeDefaultValue",
- "window.titleBarStyle": "custom",
- "workbench.sideBar.location": "right",
- //
- // Excluded files and directories
- "files.exclude": {
- "**/.classpath": true,
- "**/.project": true,
- "**/.settings": true,
- "**/.factorypath": true
- },
- "files.watcherExclude": {
- "**/.bloop": true,
- "**/.metals": true,
- "**/.ammonite": true
- },
- //
- // File associations with languages
- "files.associations": {
- "*.pgpass" : "yaml",
- },
- //
- // Settings Sync
- "settingsSync.ignoredSettings": [
- "sshfs.configs",
- "editor.fontFamily"
- ],
- //
- // Remote SSH
- "remote.SSH.remotePlatform": {
- "lab-pc07": "linux",
- "lab-pc28": "linux",
- "lab-pc01": "linux",
- "wasteside": "linux",
- "sralab": "linux"
- },
- "remote.SSH.serverInstallPath": {
- "sralab": "/srv/scratch/paul.aumann",
- "lab-pc00": "/srv/scratch/paul.aumann",
- "lab-pc44": "/srv/scratch/paul.aumann",
- },
- //
- // Python
- "python.languageServer": "Default",
- "python.createEnvironment.trigger": "off",
- "[python]": {
- "editor.formatOnSave": true,
- "editor.rulers": [
- 120
- ],
- "editor.defaultFormatter": "ms-python.black-formatter",
- "editor.codeActionsOnSave": {
- "source.organizeImports": "explicit"
- }
- },
- "black-formatter.args": [
- "--line-length",
- "120"
- ],
- //
- // Jupyter Notebooks
- "notebook.cellToolbarLocation": {
- "default": "right",
- "jupyter-notebook": "left"
- },
- "workbench.editorAssociations": {
- "*.ipynb": "jupyter-notebook"
- },
- //
- // R
- "r.lsp.diagnostics": false,
- //
- // JSON
- "[json][jsonc]": {
- "editor.defaultFormatter": "vscode.json-language-features",
- },
- "prettier.singleQuote": true,
- //
- // Webdev
- "[typescript][typescriptreact][javascript][html]": {
- "editor.defaultFormatter": "esbenp.prettier-vscode",
- "editor.formatOnSave": true,
- "editor.indentSize": "tabSize",
- "editor.tabSize": 2,
- "emmet.showAbbreviationSuggestions": false,
- "editor.linkedEditing": true,
- "editor.codeActionsOnSave": {
- // "source.organizeImports": "always"
- }
- },
- //
- // Hex Editor
- "hexeditor.columnWidth": 4,
- "hexeditor.showDecodedText": true,
- "hexeditor.defaultEndianness": "little",
- "hexeditor.inspectorType": "aside",
- //
- // Typst
- "typst-lsp.exportPdf": "onType",
- ///
- // Various settings
- "explorer.confirmDragAndDrop": false,
- "workbench.startupEditor": "none",
- "security.workspace.trust.untrustedFiles": "open",
- "emmet.showExpandedAbbreviation": "never",
- "git.openRepositoryInParentFolders": "never",
- "editor.selectionClipboard": false,
- "git.blame.editorDecoration.enabled": true,
- "python.analysis.typeCheckingMode": "basic",
- "workbench.editor.empty.hint": "hidden",
- "python.defaultInterpreterPath": "/home/paul/.pyenv/versions/3.12.5/envs/wasteside-tools",
- "editor.lineNumbers": "relative",
- "github.copilot.nextEditSuggestions.enabled": true,
+{
+ "workbench.iconTheme": "Default Light Modern Icons",
+ "editor.fontFamily": "JetBrains Mono Semibold, Fira Code, Menlo, Monaco, 'Courier New', monospace",
+ "editor.fontSize": 18,
+ "editor.fontWeight": "normal",
+ "editor.fontLigatures": true,
+ "editor.stickyScroll.enabled": true,
+ "editor.minimap.renderCharacters": false,
+ "editor.suggestSelection": "first",
+ "vsintellicode.modify.editor.suggestSelection": "automaticallyOverrodeDefaultValue",
+ "window.titleBarStyle": "custom",
+ "workbench.sideBar.location": "right",
+ //
+ // Excluded files and directories
+ "files.exclude": {
+ "**/.classpath": true,
+ "**/.project": true,
+ "**/.settings": true,
+ "**/.factorypath": true
+ },
+ "files.watcherExclude": {
+ "**/.bloop": true,
+ "**/.metals": true,
+ "**/.ammonite": true
+ },
+ //
+ // File associations with languages
+ "files.associations": {
+ "*.pgpass" : "yaml",
+ },
+ //
+ // Settings Sync
+ "settingsSync.ignoredSettings": [
+ "sshfs.configs",
+ "editor.fontFamily"
+ ],
+ //
+ // Remote SSH
+ "remote.SSH.remotePlatform": {
+ "lab-pc07": "linux",
+ "lab-pc28": "linux",
+ "lab-pc01": "linux",
+ "wasteside": "linux",
+ "sralab": "linux"
+ },
+ "remote.SSH.serverInstallPath": {
+ "sralab": "/srv/scratch/paul.aumann",
+ "lab-pc00": "/srv/scratch/paul.aumann",
+ "lab-pc44": "/srv/scratch/paul.aumann",
+ },
+ //
+ // Python
+ "python.languageServer": "Default",
+ "python.createEnvironment.trigger": "off",
+ "[python]": {
+ "editor.formatOnSave": true,
+ "editor.rulers": [
+ 120
+ ],
+ "editor.defaultFormatter": "ms-python.black-formatter",
+ "editor.codeActionsOnSave": {
+ "source.organizeImports": "explicit"
+ }
+ },
+ "black-formatter.args": [
+ "--line-length",
+ "120"
+ ],
+ //
+ // Jupyter Notebooks
+ "notebook.cellToolbarLocation": {
+ "default": "right",
+ "jupyter-notebook": "left"
+ },
+ "workbench.editorAssociations": {
+ "*.ipynb": "jupyter-notebook"
+ },
+ //
+ // R
+ "r.lsp.diagnostics": false,
+ //
+ // JSON
+ "[json][jsonc]": {
+ "editor.defaultFormatter": "vscode.json-language-features",
+ },
+ "prettier.singleQuote": true,
+ //
+ // Webdev
+ "[typescript][typescriptreact][javascript][html]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true,
+ "editor.indentSize": "tabSize",
+ "editor.tabSize": 2,
+ "emmet.showAbbreviationSuggestions": false,
+ "editor.linkedEditing": true,
+ "editor.codeActionsOnSave": {
+ // "source.organizeImports": "always"
+ }
+ },
+ //
+ // Hex Editor
+ "hexeditor.columnWidth": 4,
+ "hexeditor.showDecodedText": true,
+ "hexeditor.defaultEndianness": "little",
+ "hexeditor.inspectorType": "aside",
+ //
+ // Typst
+ "typst-lsp.exportPdf": "onType",
+ ///
+ // Various settings
+ "explorer.confirmDragAndDrop": false,
+ "workbench.startupEditor": "none",
+ "security.workspace.trust.untrustedFiles": "open",
+ "emmet.showExpandedAbbreviation": "never",
+ "git.openRepositoryInParentFolders": "never",
+ "editor.selectionClipboard": false,
+ "git.blame.editorDecoration.enabled": true,
+ "python.analysis.typeCheckingMode": "basic",
+ "workbench.editor.empty.hint": "hidden",
+ "python.defaultInterpreterPath": "/home/paul/.pyenv/versions/3.12.5/envs/wasteside-tools",
+ "editor.lineNumbers": "relative",
+ "github.copilot.nextEditSuggestions.enabled": true,
+ "json.schemaDownload.trustedDomains": {
+ "https://schemastore.azurewebsites.net/": true,
+ "https://raw.githubusercontent.com/": true,
+ "https://www.schemastore.org/": true,
+ "https://json.schemastore.org/": true,
+ "https://json-schema.org/": true,
+ "https://vicinae.com/schemas/config.json": true
+ },
+ "workbench.colorTheme": "Monokai Pro",
}
\ No newline at end of file
diff --git a/.config/_default.jsonc b/.config/_default.jsonc
new file mode 120000
index 0000000..263178a
--- /dev/null
+++ b/.config/_default.jsonc
@@ -0,0 +1 @@
+vicinae/_default.jsonc
\ No newline at end of file
diff --git a/.config/appeareance.jsonc b/.config/appeareance.jsonc
new file mode 120000
index 0000000..4876596
--- /dev/null
+++ b/.config/appeareance.jsonc
@@ -0,0 +1 @@
+vicinae/appeareance.jsonc
\ No newline at end of file
diff --git a/.config/code-flags.conf b/.config/code-flags.conf
index 51bdd86..d145ccc 100644
--- a/.config/code-flags.conf
+++ b/.config/code-flags.conf
@@ -1,2 +1,2 @@
---enable-features=UseOzonePlatform
+# --enable-features=UseOzonePlatform
--ozone-platform=wayland
diff --git a/.config/darkman/config.yaml b/.config/darkman/config.yaml
new file mode 100644
index 0000000..c87e474
--- /dev/null
+++ b/.config/darkman/config.yaml
@@ -0,0 +1,3 @@
+lat: 52.375893
+lng: 9.732010
+dbusserver: true
\ No newline at end of file
diff --git a/.config/dunst/dunstrc b/.config/dunst/dunstrc
index 0cdd1ab..64d4e48 100644
--- a/.config/dunst/dunstrc
+++ b/.config/dunst/dunstrc
@@ -4,7 +4,7 @@
### Display ###
# Which monitor should the notifications be displayed on.
- monitor = DP-2
+ monitor = DP-1
# Display notification on focused monitor. Possible modes are:
# mouse: follow mouse pointer
@@ -196,7 +196,8 @@
max_icon_size = 50
# Paths to default icons.
- icon_path = /usr/share/icons/Adwaita/16x16/mimetypes/:/usr/share/icons/Papirus-Dark/16x16/actions/
+ icon_path = "/usr/share/icons/Adwaita/symbolic/actions:/home/paul/.config/dunst/icons"
+ # enable_recursive_icon_lookup = true
default_icon = "/home/paul/.config/dunst/default.svg"
diff --git a/.config/dunst/icons/homeassistant.svg b/.config/dunst/icons/homeassistant.svg
new file mode 100644
index 0000000..efddb63
--- /dev/null
+++ b/.config/dunst/icons/homeassistant.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/.config/general.jsonc b/.config/general.jsonc
new file mode 120000
index 0000000..1dd33be
--- /dev/null
+++ b/.config/general.jsonc
@@ -0,0 +1 @@
+vicinae/general.jsonc
\ No newline at end of file
diff --git a/.config/hypr/hyprland.conf b/.config/hypr/hyprland.conf
index 9c67f90..038de00 100644
--- a/.config/hypr/hyprland.conf
+++ b/.config/hypr/hyprland.conf
@@ -16,8 +16,8 @@
################
# See https://wiki.hyprland.org/Configuring/Monitors/
-monitor=DP-2,3440x1440@144,auto,auto
-monitor=DP-3,1920x1080,-1080x-550,1,transform,1
+monitor=DP-1,3440x1440@144,auto,auto
+monitor=DP-2,1920x1080,-1080x-550,1,transform,1
monitor=HDMI-A-1,3440x1440@144,auto,auto
@@ -31,6 +31,8 @@ monitor=HDMI-A-1,3440x1440@144,auto,auto
$terminal = alacritty
$fileManager = nautilus --new-window
$browser = firefox
+$launcher = vicinae toggle
+# $launcher = tofi-drun | xargs hyprctl dispatch exec --
#################
@@ -44,14 +46,17 @@ exec-once = hyprpaper
exec-once = hypridle
exec-once = systemctl --user start hyprpolkitagent
exec-once = dunst
-exec-once = wl-paste --type text --watch cliphist store # Stores only text data
+# exec-once = wl-paste --type text --watch cliphist store # Stores only text data
exec-once = waybar
+exec-once = darkman run
+exec-once = vicinae server
# Open programs on special workspaces
exec-once = [ workspace special:term silent] $terminal
-exec-once = [ workspace special:browser silent] $browser "https://web.whatsapp.com" "https://mail.google.com/mail/u/0/\##inbox/\##wasteside\##container-wasteside"
+exec-once = [ workspace special:browser silent] $browser "ext+container:name=Meta&url=https://web.whatsapp.com" "ext+container:name=Wasteside&url=https://mail.google.com"
exec-once = [ workspace special:browser silent] gnome-calendar
exec-once = [ workspace special:music silent] $terminal -e spotify_player
+exec-once = [ workspace special:music silent] $terminal -e wiremix
exec-once = [ workspace special:todo silent] clickup
exec-once = [ workspace special:files silent] $fileManager
exec-once = [ workspace special:files silent] localsend
@@ -66,6 +71,9 @@ exec-once = ratbagctl list
exec-once = hyprpm enable Hyprspace
exec-once = hyprpm reload
+# Custom scripts
+exec-once = ~/code/ha-notifications/ha-notifications
+
#############################
### ENVIRONMENT VARIABLES ###
#############################
@@ -74,10 +82,7 @@ exec-once = hyprpm reload
env = XCURSOR_SIZE,24
env = HYPRCURSOR_SIZE,24
-env = LIBVA_DRIVER_NAME,nvidia
env = XDG_SESSION_TYPE,wayland
-env = GBM_BACKEND,nvidia-drm
-env = __GLX_VENDOR_LIBRARY_NAME,nvidia
env = HYPRSHOT_DIR,/home/paul/screenshots
cursor {
@@ -119,7 +124,7 @@ decoration {
active_opacity = 1.0
inactive_opacity = 1.0
- dim_special = 0.9
+ dim_special = 0.7
# Deprecated v0.45 ?
# drop_shadow = true
@@ -143,15 +148,16 @@ animations {
# Default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more
- bezier = myBezier, 0.05, 0.9, 0.1, 1.05
+ bezier = bz, 0.05, 0.9, 0.1, 1.05
- animation = windows, 1, 7, myBezier
- animation = windowsOut, 1, 7, default, popin 80%
+ animation = windows, 1, 4, bz
+ animation = windowsOut, 1, 4, default, popin 80%
animation = border, 1, 10, default
animation = borderangle, 1, 8, default
animation = fade, 1, 7, default
- animation = workspaces, 1, 6, default
- animation = specialWorkspaceIn, 1, 2, default
+ animation = workspaces, 1, 3, default
+
+ animation = specialWorkspace, 1, 2, default, fade
}
# See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more
@@ -169,6 +175,8 @@ master {
misc {
force_default_wallpaper = 0 # Set to 0 or 1 to disable the anime mascot wallpapers
disable_hyprland_logo = true # If true disables the random hyprland logo / anime girl background. :(
+
+ close_special_on_empty = false
}
@@ -183,14 +191,11 @@ input {
kb_model =
kb_options =
kb_rules =
+ numlock_by_default = true
follow_mouse = 2
sensitivity = 0 # -1.0 - 1.0, 0 means no modification.
-
- touchpad {
- natural_scroll = false
- }
}
# https://wiki.hyprland.org/Configuring/Variables/#gestures
@@ -205,6 +210,10 @@ device {
sensitivity = -0.5
}
+binds {
+ hide_special_on_workspace_change = true
+}
+
# cursor {
# no_warps = true
# }
@@ -220,14 +229,15 @@ $mainMod = SUPER # Sets "Windows" key as main modifier
# Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more
bind = $mainMod, T, exec, $terminal
bind = $mainMod, B, exec, $browser
-bind = $mainMod, Return, exec, tofi-drun | xargs hyprctl dispatch exec --
+bind = $mainMod, Return, exec, $launcher
bind = $mainMod, Q, killactive,
bind = $mainMod, F, fullscreen,
bind = $mainMod, M, splitratio, exact 0.6,
bind = $mainMod, mouse_down, splitratio, 0.1
bind = $mainMod, mouse_up, splitratio, -0.1
bind = $mainMod, E, exec, $fileManager
-bind = $mainMod, V, exec, cliphist list | tofi | cliphist decode | wl-copy
+# bind = $mainMod, V, exec, cliphist list | tofi | cliphist decode | wl-copy
+bind = $mainMod, V, exec, vicinae vicinae://extensions/vicinae/clipboard/history
bind = $mainMod, X, togglefloating,
bind = $mainMod, P, pin, active
bind = $mainMod, J, togglesplit, # dwindle
@@ -250,7 +260,7 @@ bind = $mainMod SHIFT, right, movewindow, r
bind = $mainMod SHIFT, up, movewindow, u
bind = $mainMod SHIFT, down, movewindow, d
-# Switch workspaces of primary monitor with mainMod + [1-9]
+# Switch workspaces of primary monitor with mainMod + [1-7]
bind = $mainMod, 1, workspace, 1
bind = $mainMod, 2, workspace, 2
bind = $mainMod, 3, workspace, 3
@@ -260,7 +270,7 @@ bind = $mainMod, 6, workspace, 6
bind = $mainMod, 7, workspace, 7
bind = $mainMod, 8, workspace, 8
-# Switch workspaces of secondary monitor with mainMod + [F1-9]
+# Switch workspaces of secondary monitor with mainMod + [F1-7]
bind = $mainMod, F1, workspace, 11
bind = $mainMod, F2, workspace, 12
bind = $mainMod, F3, workspace, 13
@@ -268,9 +278,8 @@ bind = $mainMod, F4, workspace, 14
bind = $mainMod, F5, workspace, 15
bind = $mainMod, F6, workspace, 16
bind = $mainMod, F7, workspace, 17
-bind = $mainMod, F8, workspace, 18
-# Move active window to a workspace with mainMod + SHIFT + [1-9] (primary monitor)
+# Move active window to a workspace with mainMod + SHIFT + [1-7] (primary monitor)
bind = $mainMod SHIFT, 1, movetoworkspace, 1
bind = $mainMod SHIFT, 2, movetoworkspace, 2
bind = $mainMod SHIFT, 3, movetoworkspace, 3
@@ -279,7 +288,7 @@ bind = $mainMod SHIFT, 5, movetoworkspace, 5
bind = $mainMod SHIFT, 6, movetoworkspace, 6
bind = $mainMod SHIFT, 7, movetoworkspace, 7
-# Move active window to a workspace with mainMod + SHIFT + [F1-F9] (secondary monitor)
+# Move active window to a workspace with mainMod + SHIFT + [F1-F7] (secondary monitor)
bind = $mainMod SHIFT, F1, movetoworkspace, 11
bind = $mainMod SHIFT, F2, movetoworkspace, 12
bind = $mainMod SHIFT, F3, movetoworkspace, 13
@@ -289,14 +298,20 @@ bind = $mainMod SHIFT, F6, movetoworkspace, 16
bind = $mainMod SHIFT, F7, movetoworkspace, 17
# Special workspaces (scratchpad)
+# Special workspaces should only be opened on the primary monitor.
+bind = $mainMod, F12, focusmonitor, DP-1
bind = $mainMod, F12, togglespecialworkspace, term
bind = $mainMod SHIFT, F12, movetoworkspace, special:term
+bind = $mainMod, F11, focusmonitor, DP-1
bind = $mainMod, F11, togglespecialworkspace, browser
bind = $mainMod SHIFT, F11, movetoworkspace, special:browser
+bind = $mainMod, F10, focusmonitor, DP-1
bind = $mainMod, F10, togglespecialworkspace, music
bind = $mainMod SHIFT, F10, movetoworkspace, special:music
+bind = $mainMod, F9, focusmonitor, DP-1
bind = $mainMod, F9, togglespecialworkspace, todo
bind = $mainMod SHIFT, F9, movetoworkspace, special:todo
+bind = $mainMod, F8, focusmonitor, DP-1
bind = $mainMod, F8, togglespecialworkspace, files
bind = $mainMod SHIFT, F8, movetoworkspace, special:files
@@ -314,8 +329,8 @@ bindel = ,XF86MonBrightnessDown, exec, brightnessctl s 10%-
# Requires playerctl
bindl = , XF86AudioNext, exec, playerctl next
-bindl = , XF86AudioPause, exec, playerctl play-pause
-bindl = , XF86AudioPlay, exec, playerctl play-pause
+bindl = , XF86AudioPause, exec, playerctl play-pause -a
+bindl = , XF86AudioPlay, exec, playerctl play-pause -a
bindl = , XF86AudioPrev, exec, playerctl previous
##############################
@@ -326,49 +341,103 @@ bindl = , XF86AudioPrev, exec, playerctl previous
# See https://wiki.hyprland.org/Configuring/Workspace-Rules/ for workspace rules
# Ignore maximize requests from apps. You'll probably like this.
-windowrulev2 = suppressevent maximize, class:.*
+windowrule = match:class .*, suppress_event maximize
# Fix some dragging issues with XWayland
-windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0
+# windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0
# Window rules to assign windows to workspaces
-windowrulev2 = float,title:^(.*Extension: Bitwarden.*)$
+windowrule = match:title ^(.*Extension: Bitwarden.*)$, float 1
# Always center ClickUp command bar
-windowrulev2 = center, title:ClickUp Command Bar
+windowrule = match:title ClickUp Command Bar, center 1
# Dont use floating mode for freerdp
-windowrulev2 = tile, class:xfreerdp
+windowrule = match:class ^(xfreerdp)$, tile 1
# Open audio settings in floating mode
-windowrulev2 = float, class:org.pulseaudio.pavucontrol
-windowrulev2 = size 500 1000, class:org.pulseaudio.pavucontrol
-windowrulev2 = move 2841 61, class:org.pulseaudio.pavucontrol
-windowrulev2 = pin, class:org.pulseaudio.pavucontrol
+windowrule = match:class org.pulseaudio.pavucontrol, float 1, size 500 1000, move 2841 61, pin 1
# Same for bluetooth settings
-windowrulev2 = float, initialTitle:overskride
-windowrulev2 = size 765 765, initialTitle:overskride
-windowrulev2 = move 2653 44, initialTitle:overskride
-windowrulev2 = pin, initialTitle:overskride
+windowrule = match:title overskride, float 1, size 765 765, move 2653 44, pin 1
# Change border color of pinned windows
-windowrulev2 = bordercolor rgb(CC34A4) rgb(4B2D70), pinned:1
+windowrule = match:pin 1, border_color rgb(CC34A4) rgb(4B2D70)
+
+# Share Picker is always floating
+windowrule = match:class com.paulaumann.hyprland-share-picker, float 1, center 1
+
+# Waydroid is always floating and exact size
+windowrule {
+ name = "waydroid"
+ match:initial_class = Waydroid
+
+ float = 1
+ size = 576 1024
+}
+
+# Vicinae is always floating and exact size
+windowrule {
+ name = "vicinae"
+ match:initial_class = vicinae-server
+ float = 1
+ pin = 1
+ stay_focused = on
+ dim_around = on
+ no_anim = on
+}
# Notification animation style
-layerrule = animation slide, notifications
+layerrule = match:namespace notifications, animation slide
+
+# Fixes black border around screenshots due to animation after selection
+layerrule {
+ name = no_anim_for_selection
+ match:namespace = selection
+ no_anim = on
+}
+
+# Blur behind certain layers
+layerrule {
+ name = blur_waybar
+ match:namespace = waybar
+ blur = on
+}
+
+layerrule {
+ name = blur_tofi
+ match:namespace = launcher
+ blur = on
+}
+
+# layerrule {
+# name = blur_vicinae
+# match:namespace = vicinae
+# blur = on
+# ignore_alpha = on
+# dim_around = on
+# }
+
+# layerrule {
+# name = no_animation_for_vicinae
+# match:namespace = vicinae
+# no_anim = on
+# }
# Workspace rules to assign workspaces to monitors
-workspace = 1, monitor:DP-2, default:true
-workspace = 2, monitor:DP-2
-workspace = 3, monitor:DP-2
-workspace = 4, monitor:DP-2
-workspace = 5, monitor:DP-2
-workspace = 11, monitor:DP-3, default:true
-workspace = 12, monitor:DP-3
-workspace = 13, monitor:DP-3
-workspace = 14, monitor:DP-3
-workspace = 15, monitor:DP-3
+workspace = 1, monitor:DP-1, default:true
+workspace = 2, monitor:DP-1
+workspace = 3, monitor:DP-1
+workspace = 4, monitor:DP-1
+workspace = 5, monitor:DP-1
+workspace = 11, monitor:DP-2, default:true
+workspace = 12, monitor:DP-2
+workspace = 13, monitor:DP-2
+workspace = 14, monitor:DP-2
+workspace = 15, monitor:DP-2
-workspace = special:music, gapsout:40 1800 50 50
-workspace = special:music, monitor:DP-2
+workspace = special:term, gapsout:64
+workspace = special:browser, gapsout:64
+workspace = special:music, gapsout:64
+workspace = special:todo, gapsout:64
+workspace = special:files, gapsout:64
\ No newline at end of file
diff --git a/.config/hypr/hyprpaper.conf b/.config/hypr/hyprpaper.conf
index d1f5dbb..978c1e6 100644
--- a/.config/hypr/hyprpaper.conf
+++ b/.config/hypr/hyprpaper.conf
@@ -1,2 +1,13 @@
-preload = ~/.config/hypr/img/gnome-background.webp
-wallpaper = , ~/.config/hypr/img/gnome-background.webp
\ No newline at end of file
+wallpaper {
+ monitor = DP-1
+ path = ~/.config/hypr/img/gnome-background.webp
+ fit_mode = cover
+}
+
+wallpaper {
+ monitor = DP-2
+ path = ~/.config/hypr/img/gnome-background.webp
+ fit_mode = cover
+}
+
+splash = false
\ No newline at end of file
diff --git a/.config/settings.json b/.config/settings.json
new file mode 120000
index 0000000..0f40aca
--- /dev/null
+++ b/.config/settings.json
@@ -0,0 +1 @@
+vicinae/settings.json
\ No newline at end of file
diff --git a/.config/spotify-player/app.toml b/.config/spotify-player/app.toml
new file mode 100644
index 0000000..3530ab5
--- /dev/null
+++ b/.config/spotify-player/app.toml
@@ -0,0 +1,51 @@
+theme = "dracula"
+client_id = "7fed45e8d6ec4ff1b50e5464852fe136"
+client_port = 8080
+login_redirect_uri = "http://127.0.0.1:8989/login"
+playback_format = """
+{status} {track} • {artists} {liked}
+{album}
+{metadata}"""
+notify_timeout_in_secs = 0
+tracks_playback_limit = 50
+app_refresh_duration_in_ms = 32
+playback_refresh_duration_in_ms = 0
+page_size_in_rows = 20
+play_icon = "▶"
+pause_icon = "▌▌"
+liked_icon = "♥"
+border_type = "Plain"
+progress_bar_type = "Rectangle"
+cover_img_length = 9
+cover_img_width = 5
+cover_img_scale = 1.0
+cover_img_pixels = 64
+enable_media_control = true
+enable_streaming = "Always"
+enable_notify = true
+enable_cover_image_cache = true
+default_device = "spotify-player"
+notify_streaming_only = false
+seek_duration_secs = 5
+sort_artist_albums_by_type = false
+
+[notify_format]
+summary = "{track} • {artists}"
+body = "{album}"
+
+[layout]
+playback_window_position = "Top"
+playback_window_height = 6
+
+[layout.library]
+playlist_percent = 40
+album_percent = 40
+
+[device]
+name = "spotify-player"
+device_type = "speaker"
+volume = 70
+bitrate = 320
+audio_cache = false
+normalization = false
+autoplay = false
diff --git a/.config/vicinae/.vscode/settings.json b/.config/vicinae/.vscode/settings.json
new file mode 100644
index 0000000..38b6504
--- /dev/null
+++ b/.config/vicinae/.vscode/settings.json
@@ -0,0 +1,11 @@
+{
+ "[json]": {
+ "editor.formatOnSave": true,
+ "editor.codeActionsOnSave": {
+ "source.fixAll": "explicit"
+ }
+ },
+ "[jsonc]": {
+ "editor.formatOnSave": true
+ },
+}
\ No newline at end of file
diff --git a/.config/vicinae/_default.jsonc b/.config/vicinae/_default.jsonc
new file mode 100644
index 0000000..c4bac90
--- /dev/null
+++ b/.config/vicinae/_default.jsonc
@@ -0,0 +1,186 @@
+{
+ "$schema": "https://vicinae.com/schemas/config.json",
+
+ // A list of paths pointed to separate configuration files to source during loading.
+ // Note that values defined in the main configuration file always take precedence over imported
+ // files.
+ // Relative paths are accepted, and are relative to the configuration directory in which the importing configuration file is located.
+ "imports": [],
+
+ // Whether root search should also search files directly.
+ // File search is performed asynchrounously: files may take an instant to appear after a query is entered.
+ // Turning this on results in an increase in CPU usage when searching.
+ // Note that vicinae has a built-in file search command that can be used to perform file search in isolation, accompanied with
+ // rich content preview. This should be more than enough for most users.
+ "search_files_in_root": false,
+
+ // What vicinae should do when the "escape" key is pressed
+ // Supports "navigate_back" or "close_window"
+ "escape_key_behavior": "navigate_back",
+
+ // If the layer shell protocol is used to position the window and the keyboard interactivity is
+ // set to "exclusive" (the default) then this setting will have no effect. Either switch to "on_demand" interactivity
+ // or disable layer shell.
+ "close_on_focus_loss": false,
+
+ // Whether IME preedit strings should be searched in real time.
+ "consider_preedit": false,
+
+ // Reset the navigation state every time the window is closed
+ "pop_to_root_on_close": false,
+
+ // What favicon service to use when loading favicons is needed.
+ // Available values are: 'twenty' | 'google' | 'none'
+ // If this is set to 'none', favicon loading is disabled and a placeholder icon will be used when a favicon is expected.
+ "favicon_service": "twenty",
+
+ // EXPERIMENTAL!
+ // Enable specific editing motions in the main search bar and during navigation.
+ // This will probably be changed in future updates.
+ // Supports: 'default' | 'emacs'
+ "keybinding": "default",
+
+ "font": {
+ // The font family to use for the general vicinae UI.
+ // "auto" (recommended): uses the bundled Inter font
+ // "system": uses the system/platform default font
+ // Or specify any font family name (e.g. "Fira Sans")
+ "normal": {
+ "family": "auto",
+ // The point size of the font (unlike pixel size, the point size scales with resolution)
+ "size": 10.5
+ }
+ },
+
+ // The general vicinae theme as well as the system icon theme (used for applications and file icons) can be customized
+ // according to the system appearance.
+ // When editing the current theme through the vicinae GUI, it will modify the value that maps to the current system appearance.
+ "theme": {
+ "light": {
+ "name": "vicinae-light",
+ "icon_theme": "auto"
+ },
+ "dark": {
+ "name": "vicinae-dark",
+ "icon_theme": "auto"
+ }
+ },
+
+ "launcher_window": {
+ // Control the opacity of the main window.
+ // Opacity for other surfaces are controlled by the active theme.
+ "opacity": 0.95,
+
+ // Blur the window background.
+ // Only suported on Hyprland for now.
+ // May require a server restart to disable properly.
+ "blur": {
+ "enabled": true
+ },
+
+ // Dims everything behind the vicinae window.
+ // Only supported on Hyprland for now.
+ // May require a server restart to disable properly.
+ "dim_around": true,
+
+ // You can turn client side decorations off if you want to let your compositor draw the rounded
+ // borders and such. This usually gives better results, but is more complicated to setup, if at all possible.
+ "client_side_decorations": {
+ "enabled": true,
+ "rounding": 10,
+ "border_width": 2
+ },
+
+ // In compact mode, vicinae only shows a search bar in the root search if no query is entered, only expanding to its full size when searching.
+ // WARNING: compact mode works best when vicinae is rendered as a layer surface (the default if available), as opposed to a regular floating window.
+ // That's because the part that is not rendered in compact mode is still part of the full window size, allowing the window to be gracefully expanded without having to deal
+ // with a compositor window resize which can generate a lot of visual noise. Server side borders and blur will look notably out of place.
+ "compact_mode": {
+ "enabled": false
+ },
+
+ // The vicinae UI is designed to work best at the default size. If you change this it is highly recommended
+ // that you preserve a similar aspect ratio.
+ "size": {
+ "width": 770,
+ "height": 480
+ },
+
+ // EXPERIMENTAL - X11 ONLY!
+ // The name of the screen (as provided by a tool like xrandr) on which the vicinae window
+ // needs to be shown.
+ // e.g "eDP1", "DP-1"
+ "screen": "auto",
+
+ // Only for wayland compositors that support the 'wlr-layer-shell' protocol (https://wayland.app/protocols/wlr-layer-shell-unstable-v1)
+ // NOTE: layer shell support for the cosmic compositor is explicitly disabled as it is currently broken
+ "layer_shell": {
+ "enabled": true,
+
+ // 'exclusive' | 'on_demand'
+ // WARNING: 'exclusive' is known to break mouse stuff on popups (such as the action panel) on Hyprland
+ "keyboard_interactivity": "on_demand",
+
+ // either 'overlay' or 'top'.
+ // Other layers are not supported as they are unsuitable for a launcher.
+ // 'top' is recommended as using 'overlay' will make the vicinae window appear on top of IME popovers in some scenarios.
+ "layer": "top"
+ }
+ },
+
+ // How much memory (in MB) can be used to cache small image assets in memory directly
+ // The higher this value is the more memory vicinae will use. In exchange for this, you get lower cpu usage.
+ "pixmap_cache_mb": 50,
+
+ // Keybinds are serialized using a custom format.
+ // It is recommended to edit them through the settings GUI, which will write them to this file.
+ "keybinds": {
+ // common shortcuts
+ "open-search-filter": "control+P",
+ "open-settings": "control+,",
+ "toggle-action-panel": "control+B",
+
+ // used to assign shortcuts to generic actions
+ // if an extension provides a specific kind of action, it is encouraged to use shortcuts as defined here.
+ "action.copy": "control+shift+C",
+ "action.copy-name": "control+shift+.",
+ "action.copy-path": "control+shift+,",
+ "action.dangerous-remove": "control+shift+X",
+ "action.duplicate": "control+D",
+ "action.edit": "control+E",
+ "action.edit-secondary": "control+shift+E",
+ "action.move-down": "control+shift+ARROWDOWN",
+ "action.move-up": "control+shift+ARROWUP",
+ "action.new": "control+N",
+ "action.open": "control+O",
+ "action.pin": "control+shift+P",
+ "action.refresh": "control+R",
+ "action.remove": "control+X",
+ "action.save": "control+S"
+ },
+
+ // List of entrypoints that are tagged as "favorite".
+ // They show up on the very top of the root search when no search query is active.
+ // Each value is a serialized entrypoint ID.
+ "favorites": [
+ "clipboard:history"
+ ],
+
+ // List of entrypoints to suggest as a fallback when no result matches the
+ // provided search query.
+ // Each value is a serialized entrypoint ID.
+ "fallbacks": [
+ "files:search"
+ ],
+
+ // Every item that can be activated from the vicinae root search is referred as an entrypoint, or sometimes, in the case of extensions, as a "command".
+ // Entrypoints are always grouped under a provider.
+ // e,g applications entrypoints are all grouped under the "applications" provider
+ //
+ // The best way to interact with providers and entrypoints is through the graphical interface provided by the
+ // settings window ("extensions" tab). Every time a change is made through this GUI, the relevant values will be
+ // written to the "providers" object below (password preferences excluded).
+ //
+ // The exact list of available providers depends on what extensions have been installed.
+ "providers": {}
+}
diff --git a/.config/vicinae/appeareance.jsonc b/.config/vicinae/appeareance.jsonc
new file mode 100644
index 0000000..e7b2432
--- /dev/null
+++ b/.config/vicinae/appeareance.jsonc
@@ -0,0 +1,22 @@
+{
+ "$schema": "https://vicinae.com/schemas/config.json",
+ "launcher_window": {
+ // You can turn client side decorations off if you want to let your compositor draw the rounded
+ // borders and such. This usually gives better results, but is more complicated to setup, if at all possible.
+ "client_side_decorations": {
+ "enabled": false
+ },
+ // Only for wayland compositors that support the 'wlr-layer-shell' protocol (https://wayland.app/protocols/wlr-layer-shell-unstable-v1)
+ // NOTE: layer shell support for the cosmic compositor is explicitly disabled as it is currently broken
+ "layer_shell": {
+ "enabled": false,
+ // 'exclusive' | 'on_demand'
+ // WARNING: 'exclusive' is known to break mouse stuff on popups (such as the action panel) on Hyprland
+ "keyboard_interactivity": "on_demand",
+ // either 'overlay' or 'top'.
+ // Other layers are not supported as they are unsuitable for a launcher.
+ // 'top' is recommended as using 'overlay' will make the vicinae window appear on top of IME popovers in some scenarios.
+ "layer": "top"
+ }
+ }
+}
\ No newline at end of file
diff --git a/.config/vicinae/general.jsonc b/.config/vicinae/general.jsonc
new file mode 100644
index 0000000..3f9d891
--- /dev/null
+++ b/.config/vicinae/general.jsonc
@@ -0,0 +1,7 @@
+{
+ "$schema": "https://vicinae.com/schemas/config.json",
+ // If the layer shell protocol is used to position the window and the keyboard interactivity is
+ // set to "exclusive" (the default) then this setting will have no effect. Either switch to "on_demand" interactivity
+ // or disable layer shell.
+ "close_on_focus_loss": false
+}
\ No newline at end of file
diff --git a/.config/vicinae/settings.json b/.config/vicinae/settings.json
new file mode 100644
index 0000000..73fb94d
--- /dev/null
+++ b/.config/vicinae/settings.json
@@ -0,0 +1,17 @@
+// This configuration is merged with the default vicinae configuration file, which you can obtain by running the `vicinae config default` command.
+// Every item defined in this file takes precedence over the values defined in the default config or any other imported file.
+//
+// You can make manual edits to this file, however you should keep in mind that this file may be written to by vicinae when a configuration change is made through the GUI.
+// When that happens, any custom comments or formatting will be lost.
+//
+// If you want to maintain a configuration file with your own comments and formatting, you should create a separate file and add it to the 'imports' array.
+//
+// Learn more about configuration at https://docs.vicinae.com/config
+
+{
+ "$schema": "https://vicinae.com/schemas/config.json",
+ "imports": [
+ "appeareance.jsonc",
+ "general.jsonc"
+ ]
+}
\ No newline at end of file
diff --git a/.config/waybar/config.jsonc b/.config/waybar/config.jsonc
index 97cf25f..570d31d 100644
--- a/.config/waybar/config.jsonc
+++ b/.config/waybar/config.jsonc
@@ -1,6 +1,6 @@
{
"layer": "top",
- "output": "DP-2",
+ "output": "DP-1",
"modules-left": [
"custom/power",
"hyprland/workspaces",
diff --git a/.config/winapps/compose.yaml b/.config/winapps/compose.yaml
new file mode 100644
index 0000000..21780dc
--- /dev/null
+++ b/.config/winapps/compose.yaml
@@ -0,0 +1,51 @@
+# For documentation, FAQ, additional configuration options and technical help, visit: https://github.com/dockur/windows
+
+name: "winapps" # Docker Compose Project Name.
+# volumes:
+ # Create Volume 'data'.
+ # Located @ '/var/lib/docker/volumes/winapps_data/_data' (Docker).
+ # Located @ '/var/lib/containers/storage/volumes/winapps_data/_data' or '~/.local/share/containers/storage/volumes/winapps_data/_data' (Podman).
+ # data:
+
+services:
+ windows:
+ image: ghcr.io/dockur/windows:latest
+ container_name: winapps # Created Docker VM Name.
+ environment:
+ # Version of Windows to configure. For valid options, visit:
+ # https://github.com/dockur/windows?tab=readme-ov-file#how-do-i-select-the-windows-version
+ # https://github.com/dockur/windows?tab=readme-ov-file#how-do-i-install-a-custom-image
+ VERSION: "11"
+ RAM_SIZE: "8G" # 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: "paul" # Edit here to set a custom Windows username. The default is 'MyWindowsUser'.
+ PASSWORD: "nicerdicer" # Edit here to set a password for the Windows user. The default is 'MyWindowsPassword'.
+ HOME: "${HOME}" # Set path to Linux user home folder.
+ ARGUMENTS: "-cpu host,arch_capabilities=off"
+ ports:
+ - 8006:8006 # Map '8006' on Linux host to '8006' on Windows VM --> For VNC Web Interface @ http://127.0.0.1:8006.
+ - 3389:3389/tcp # Map '3389' on Linux host to '3389' on Windows VM --> For Remote Desktop Protocol (RDP).
+ - 3389:3389/udp # Map '3389' on Linux host to '3389' on Windows VM --> For Remote Desktop Protocol (RDP).
+ cap_add:
+ - NET_ADMIN # Add network permission
+ stop_grace_period: 120s # Wait 120 seconds before sending SIGTERM when attempting to shut down the Windows VM.
+ restart: on-failure # Restart the Windows VM if the exit code indicates an error.
+ volumes:
+ - ~/.winapps:/storage # Mount local folder 'data' to use as Windows 'C:' drive.
+ - ${HOME}:/shared # Mount Linux user home directory @ '\\host.lan\Data'.
+ #- /path/to/second/hard/disk:/storage2 # Uncomment to create a virtual second hard disk and mount it within the Windows VM. Ensure 'DISK2_SIZE' is specified above.
+ - ./oem:/oem # Enables automatic post-install execution of 'oem/install.bat', applying Windows registry modifications contained within 'oem/RDPApps.reg'.
+ #- /path/to/windows/install/media.iso:/custom.iso # Uncomment to use a custom Windows ISO. If specified, 'VERSION' (e.g. 'tiny11') will be ignored.
+ devices:
+ - /dev/kvm # Enable KVM.
+ - /dev/net/tun # Enable tuntap
+ # Uncomment to mount a disk directly within the Windows VM.
+ # WARNING: /dev/sdX paths may change after reboot. Use persistent identifiers!
+ # NOTE: 'disk1' will be mounted as the main drive. THIS DISK WILL BE FORMATTED BY DOCKER.
+ # All following disks (disk2, ...) WILL NOT BE FORMATTED.
+ # - /dev/disk/by-id/:/disk1
+ # - /dev/disk/by-id/:/disk2
+ # group_add: # uncomment this line and the next one for using rootless podman containers
+ # - keep-groups # to make /dev/kvm work with podman. needs "crun" installed, "runc" will not work! Add your user to the 'kvm' group or another that can access /dev/kvm.
diff --git a/.config/winapps/setup.sh b/.config/winapps/setup.sh
new file mode 100755
index 0000000..65648b1
--- /dev/null
+++ b/.config/winapps/setup.sh
@@ -0,0 +1,1794 @@
+#!/usr/bin/env bash
+
+# shellcheck disable=SC2034 # Silence warnings regarding unused variables globally.
+
+### GLOBAL CONSTANTS ###
+# ANSI ESCAPE SEQUENCES
+readonly BOLD_TEXT="\033[1m" # Bold
+readonly CLEAR_TEXT="\033[0m" # Clear
+readonly COMMAND_TEXT="\033[0;37m" # Grey
+readonly DONE_TEXT="\033[0;32m" # Green
+readonly ERROR_TEXT="\033[1;31m" # Bold + Red
+readonly EXIT_TEXT="\033[1;41;37m" # Bold + White + Red Background
+readonly FAIL_TEXT="\033[0;91m" # Bright Red
+readonly INFO_TEXT="\033[0;33m" # Orange/Yellow
+readonly SUCCESS_TEXT="\033[1;42;37m" # Bold + White + Green Background
+readonly WARNING_TEXT="\033[1;33m" # Bold + Orange/Yellow
+
+# ERROR CODES
+readonly EC_FAILED_CD="1" # Failed to change directory to location of script.
+readonly EC_BAD_ARGUMENT="2" # Unsupported argument passed to script.
+readonly EC_EXISTING_INSTALL="3" # Existing conflicting WinApps installation.
+readonly EC_NO_CONFIG="4" # Absence of a valid WinApps configuration file.
+readonly EC_MISSING_DEPS="5" # Missing dependencies.
+readonly EC_NO_SUDO="6" # Insufficient privileges to invoke superuser access.
+readonly EC_NOT_IN_GROUP="7" # Current user not in group 'libvirt' and/or 'kvm'.
+readonly EC_VM_OFF="8" # Windows 'libvirt' VM powered off.
+readonly EC_VM_PAUSED="9" # Windows 'libvirt' VM paused.
+readonly EC_VM_ABSENT="10" # Windows 'libvirt' VM does not exist.
+readonly EC_CONTAINER_OFF="11" # Windows Docker container is not running.
+readonly EC_NO_IP="12" # Windows does not have an IP address.
+readonly EC_BAD_PORT="13" # Windows is unreachable via RDP_PORT.
+readonly EC_RDP_FAIL="14" # FreeRDP failed to establish a connection with Windows.
+readonly EC_APPQUERY_FAIL="15" # Failed to query Windows for installed applications.
+readonly EC_INVALID_FLAVOR="16" # Backend specified is not 'libvirt', 'docker' or 'podman'.
+
+# PATHS
+# 'BIN'
+readonly SYS_BIN_PATH="/usr/local/bin" # UNIX path to 'bin' directory for a '--system' WinApps installation.
+readonly USER_BIN_PATH="${HOME}/.local/bin" # UNIX path to 'bin' directory for a '--user' WinApps installation.
+readonly USER_BIN_PATH_WIN='\\tsclient\home\.local\bin' # WINDOWS path to 'bin' directory for a '--user' WinApps installation.
+# 'SOURCE'
+readonly SYS_SOURCE_PATH="${SYS_BIN_PATH}/winapps-src" # UNIX path to WinApps source directory for a '--system' WinApps installation.
+readonly USER_SOURCE_PATH="${USER_BIN_PATH}/winapps-src" # UNIX path to WinApps source directory for a '--user' WinApps installation.
+# 'APP'
+readonly SYS_APP_PATH="/usr/share/applications" # UNIX path to 'applications' directory for a '--system' WinApps installation.
+readonly USER_APP_PATH="${HOME}/.local/share/applications" # UNIX path to 'applications' directory for a '--user' WinApps installation.
+readonly USER_APP_PATH_WIN='\\tsclient\home\.local\share\applications' # WINDOWS path to 'applications' directory for a '--user' WinApps installation.
+# 'APPDATA'
+readonly SYS_APPDATA_PATH="/usr/local/share/winapps" # UNIX path to 'application data' directory for a '--system' WinApps installation.
+readonly USER_APPDATA_PATH="${HOME}/.local/share/winapps" # UNIX path to 'application data' directory for a '--user' WinApps installation.
+readonly USER_APPDATA_PATH_WIN='\\tsclient\home\.local\share\winapps' # WINDOWS path to 'application data' directory for a '--user' WinApps installation.
+# 'Installed Batch Script'
+readonly BATCH_SCRIPT_PATH="${USER_APPDATA_PATH}/installed.bat" # UNIX path to a batch script used to search Windows for applications.
+readonly BATCH_SCRIPT_PATH_WIN="${USER_APPDATA_PATH_WIN}\\installed.bat" # WINDOWS path to a batch script used to search Windows for applications.
+# 'Installed File'
+readonly TMP_INST_FILE_PATH="${USER_APPDATA_PATH}/installed.tmp" # UNIX path to a temporary file containing the names of detected officially supported applications.
+readonly TMP_INST_FILE_PATH_WIN="${USER_APPDATA_PATH_WIN}\\installed.tmp" # WINDOWS path to a temporary file containing the names of detected officially supported applications.
+readonly INST_FILE_PATH="${USER_APPDATA_PATH}/installed" # UNIX path to a file containing the names of detected officially supported applications.
+readonly INST_FILE_PATH_WIN="${USER_APPDATA_PATH_WIN}\\installed" # WINDOWS path to a file containing the names of detected officially supported applications.
+# 'PowerShell Script'
+readonly PS_SCRIPT_PATH="./install/ExtractPrograms.ps1" # UNIX path to a PowerShell script used to store the names, executable paths and icons (base64) of detected applications.
+readonly PS_SCRIPT_HOME_PATH="${USER_APPDATA_PATH}/ExtractPrograms.ps1" # UNIX path to a copy of the PowerShell script within the user's home directory to enable access by Windows.
+readonly PS_SCRIPT_HOME_PATH_WIN="${USER_APPDATA_PATH_WIN}\\ExtractPrograms.ps1" # WINDOWS path to a copy of the PowerShell script within the user's home directory to enable access by Windows.
+# 'Detected File'
+readonly DETECTED_FILE_PATH="${USER_APPDATA_PATH}/detected" # UNIX path to a file containing the output generated by the PowerShell script, formatted to define bash arrays.
+readonly DETECTED_FILE_PATH_WIN="${USER_APPDATA_PATH_WIN}\\detected" # WINDOWS path to a file containing the output generated by the PowerShell script, formatted to define bash arrays.
+# 'FreeRDP Connection Test File'
+readonly TEST_PATH="${USER_APPDATA_PATH}/FreeRDP_Connection_Test" # UNIX path to temporary file whose existence is used to confirm a successful RDP connection was established.
+readonly TEST_PATH_WIN="${USER_APPDATA_PATH_WIN}\\FreeRDP_Connection_Test" # WINDOWS path to temporary file whose existence is used to confirm a successful RDP connection was established.
+# 'WinApps Configuration File'
+readonly CONFIG_PATH="${HOME}/.config/winapps/winapps.conf" # UNIX path to the WinApps configuration file.
+# 'Inquirer Bash Script'
+readonly INQUIRER_PATH="./install/inquirer.sh" # UNIX path to the 'inquirer' script, which is used to produce selection menus.
+
+# REMOTE DESKTOP CONFIGURATION
+readonly RDP_PORT=3389 # Port used for RDP on Windows.
+readonly DOCKER_IP="127.0.0.1" # Localhost.
+
+### GLOBAL VARIABLES ###
+# USER INPUT
+OPT_SYSTEM=0 # Set to '1' if the user specifies '--system'.
+OPT_USER=0 # Set to '1' if the user specifies '--user'.
+OPT_UNINSTALL=0 # Set to '1' if the user specifies '--uninstall'.
+OPT_AOSA=0 # Set to '1' if the user specifies '--setupAllOfficiallySupportedApps'.
+
+# WINAPPS CONFIGURATION FILE
+RDP_USER="" # Imported variable.
+RDP_PASS="" # Imported variable.
+RDP_DOMAIN="" # Imported variable.
+RDP_IP="" # Imported variable.
+VM_NAME="RDPWindows" # Name of the Windows VM (FOR 'libvirt' ONLY).
+WAFLAVOR="docker" # Imported variable.
+RDP_SCALE=100 # Imported variable.
+RDP_FLAGS="" # Imported variable.
+DEBUG="true" # Imported variable.
+FREERDP_COMMAND="" # Imported variable.
+
+PORT_TIMEOUT=5 # Default port check timeout.
+RDP_TIMEOUT=30 # Default RDP connection test timeout.
+APP_SCAN_TIMEOUT=60 # Default application scan timeout.
+
+# PERMISSIONS AND DIRECTORIES
+SUDO="" # Set to "sudo" if the user specifies '--system', or "" if the user specifies '--user'.
+BIN_PATH="" # Set to $SYS_BIN_PATH if the user specifies '--system', or $USER_BIN_PATH if the user specifies '--user'.
+APP_PATH="" # Set to $SYS_APP_PATH if the user specifies '--system', or $USER_APP_PATH if the user specifies '--user'.
+APPDATA_PATH="" # Set to $SYS_APPDATA_PATH if the user specifies '--system', or $USER_APPDATA_PATH if the user specifies '--user'.
+SOURCE_PATH="" # Set to $SYS_SOURCE_PATH if the user specifies '--system', or $USER_SOURCE_PATH if the user specifies '--user'.
+
+# INSTALLATION PROCESS
+INSTALLED_EXES=() # List of executable file names of officially supported applications that have already been configured during the current installation process.
+
+### TRAPS ###
+set -o errtrace # Ensure traps are inherited by all shell functions and subshells.
+trap "waTerminateScript" ERR # Catch non-zero return values.
+
+### FUNCTIONS ###
+# Name: 'waTerminateScript'
+# Role: Terminates the script when a non-zero return value is encountered.
+# shellcheck disable=SC2317 # Silence warning regarding this function being unreachable.
+function waTerminateScript() {
+ # Store the non-zero exit status received by the trap.
+ local EXIT_STATUS=$?
+
+ # Display the exit status.
+ echo -e "${EXIT_TEXT}Exiting with status '${EXIT_STATUS}'.${CLEAR_TEXT}"
+
+ # Terminate the script.
+ exit "$EXIT_STATUS"
+}
+
+# Name: 'waUsage'
+# Role: Displays usage information for the script.
+function waUsage() {
+ echo -e "Usage:
+ ${COMMAND_TEXT} --user${CLEAR_TEXT} # Install WinApps and selected applications in ${HOME}
+ ${COMMAND_TEXT} --system${CLEAR_TEXT} # Install WinApps and selected applications in /usr
+ ${COMMAND_TEXT} --user --setupAllOfficiallySupportedApps${CLEAR_TEXT} # Install WinApps and all officially supported applications in ${HOME}
+ ${COMMAND_TEXT} --system --setupAllOfficiallySupportedApps${CLEAR_TEXT} # Install WinApps and all officially supported applications in /usr
+ ${COMMAND_TEXT} --user --uninstall${CLEAR_TEXT} # Uninstall everything in ${HOME}
+ ${COMMAND_TEXT} --system --uninstall${CLEAR_TEXT} # Uninstall everything in /usr
+ ${COMMAND_TEXT} --help${CLEAR_TEXT} # Display this usage message."
+}
+
+# Name: 'waGetSourceCode'
+# Role: Grab the WinApps source code using Git.
+function waGetSourceCode() {
+ # Declare variables.
+ local SCRIPT_DIR_PATH="" # Stores the absolute path of the directory containing the script.
+
+ # Determine the absolute path to the directory containing the script.
+ SCRIPT_DIR_PATH=$(readlink -f "$(dirname "${BASH_SOURCE[0]}")")
+
+ # Check if winapps is currently installed on $SOURCE_PATH
+ if [[ -f "$SCRIPT_DIR_PATH/winapps" && "$SCRIPT_DIR_PATH" != "$SOURCE_PATH" ]]; then
+ # Display a warning.
+ echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} You are running a WinApps installation located outside of default location '${SOURCE_PATH}'. A new installation will be created."
+ echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} You might want to remove your old installation on '${SCRIPT_DIR_PATH}'."
+ fi
+
+ if [[ ! -d "$SOURCE_PATH" ]]; then
+ $SUDO git clone --recurse-submodules --remote-submodules https://github.com/winapps-org/winapps.git "$SOURCE_PATH"
+ else
+ echo -e "${INFO_TEXT}WinApps installation already present at ${CLEAR_TEXT}${COMMAND_TEXT}${SOURCE_PATH}${CLEAR_TEXT}${INFO_TEXT}. Updating...${CLEAR_TEXT}"
+ $SUDO git -C "$SOURCE_PATH" pull --no-rebase
+ fi
+
+ # Silently change the working directory.
+ if ! cd "$SOURCE_PATH" &>/dev/null; then
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}DIRECTORY CHANGE FAILURE.${CLEAR_TEXT}"
+
+ # Display error details.
+ echo -e "${INFO_TEXT}Failed to change the working directory to ${CLEAR_TEXT}${COMMAND_TEXT}${SOURCE_PATH}${CLEAR_TEXT}${INFO_TEXT}.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Ensure:"
+ echo -e " - ${COMMAND_TEXT}${SOURCE_PATH}${CLEAR_TEXT} exists."
+ echo -e " - ${COMMAND_TEXT}${SOURCE_PATH}${CLEAR_TEXT} has been cloned and checked out properly."
+ echo -e " - The current user has sufficient permissions to access and write to ${COMMAND_TEXT}${SOURCE_PATH}${CLEAR_TEXT}."
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_FAILED_CD"
+ fi
+}
+
+# Name: 'waGetInquirer'
+# Role: Loads the inquirer script, even if the source isn't cloned yet
+function waGetInquirer() {
+ local INQUIRER=$INQUIRER_PATH
+
+ if [ -d "$SYS_SOURCE_PATH" ]; then
+ INQUIRER=$SYS_SOURCE_PATH/$INQUIRER_PATH
+ elif [ -d "$USER_SOURCE_PATH" ] ; then
+ INQUIRER=$USER_SOURCE_PATH/$INQUIRER_PATH
+ else
+ INQUIRER="/tmp/waInquirer.sh"
+ rm -f "$INQUIRER"
+
+ curl -o "$INQUIRER" "https://raw.githubusercontent.com/winapps-org/winapps/main/install/inquirer.sh"
+ fi
+
+ # shellcheck source=/dev/null # Exclude this file from being checked by ShellCheck.
+ source "$INQUIRER"
+}
+
+# Name: 'waCheckInput'
+# Role: Sanitises input and guides users through selecting appropriate options if no arguments are provided.
+function waCheckInput() {
+ # Declare variables.
+ local OPTIONS=() # Stores the options.
+ local SELECTED_OPTION # Stores the option selected by the user.
+
+ if [[ $# -gt 0 ]]; then
+ # Parse arguments.
+ for argument in "$@"; do
+ case "$argument" in
+ "--user")
+ OPT_USER=1
+ ;;
+ "--system")
+ OPT_SYSTEM=1
+ ;;
+ "--setupAllOfficiallySupportedApps")
+ OPT_AOSA=1
+ ;;
+ "--uninstall")
+ OPT_UNINSTALL=1
+ ;;
+ "--help")
+ waUsage
+ exit 0
+ ;;
+ *)
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}INVALID ARGUMENT.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}Unsupported argument${CLEAR_TEXT} ${COMMAND_TEXT}${argument}${CLEAR_TEXT}${INFO_TEXT}.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ waUsage
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_BAD_ARGUMENT"
+ ;;
+ esac
+ done
+ else
+ # Install vs. uninstall?
+ OPTIONS=("Install" "Uninstall")
+ inqMenu "Install or uninstall WinApps?" OPTIONS SELECTED_OPTION
+
+ # Set flags.
+ if [[ $SELECTED_OPTION == "Uninstall" ]]; then
+ OPT_UNINSTALL=1
+ fi
+
+ # User vs. system?
+ OPTIONS=("Current User" "System")
+ inqMenu "Configure WinApps for the current user '$(whoami)' or the whole system?" OPTIONS SELECTED_OPTION
+
+ # Set flags.
+ if [[ $SELECTED_OPTION == "Current User" ]]; then
+ OPT_USER=1
+ elif [[ $SELECTED_OPTION == "System" ]]; then
+ OPT_SYSTEM=1
+ fi
+
+ # Automatic vs. manual?
+ if [ "$OPT_UNINSTALL" -eq 0 ]; then
+ OPTIONS=("Manual (Default)" "Automatic")
+ inqMenu "Automatically install supported applications or choose manually?" OPTIONS SELECTED_OPTION
+
+ # Set flags.
+ if [[ $SELECTED_OPTION == "Automatic" ]]; then
+ OPT_AOSA=1
+ fi
+ fi
+
+ # Newline.
+ echo ""
+ fi
+
+ # Simultaneous 'User' and 'System'.
+ if [ "$OPT_SYSTEM" -eq 1 ] && [ "$OPT_USER" -eq 1 ]; then
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}CONFLICTING ARGUMENTS.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}You cannot specify both${CLEAR_TEXT} ${COMMAND_TEXT}--user${CLEAR_TEXT} ${INFO_TEXT}and${CLEAR_TEXT} ${COMMAND_TEXT}--system${CLEAR_TEXT} ${INFO_TEXT}simultaneously.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ waUsage
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_BAD_ARGUMENT"
+ fi
+
+ # Simultaneous 'Uninstall' and 'AOSA'.
+ if [ "$OPT_UNINSTALL" -eq 1 ] && [ "$OPT_AOSA" -eq 1 ]; then
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}CONFLICTING ARGUMENTS.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}You cannot specify both${CLEAR_TEXT} ${COMMAND_TEXT}--uninstall${CLEAR_TEXT} ${INFO_TEXT}and${CLEAR_TEXT} ${COMMAND_TEXT}--aosa${CLEAR_TEXT} ${INFO_TEXT}simultaneously.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ waUsage
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_BAD_ARGUMENT"
+ fi
+
+ # No 'User' or 'System'.
+ if [ "$OPT_SYSTEM" -eq 0 ] && [ "$OPT_USER" -eq 0 ]; then
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}INSUFFICIENT ARGUMENTS.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}You must specify either${CLEAR_TEXT} ${COMMAND_TEXT}--user${CLEAR_TEXT} ${INFO_TEXT}or${CLEAR_TEXT} ${COMMAND_TEXT}--system${CLEAR_TEXT} ${INFO_TEXT}to proceed.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ waUsage
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_BAD_ARGUMENT"
+ fi
+}
+
+# Name: 'waConfigurePathsAndPermissions'
+# Role: Sets paths and adjusts permissions as specified.
+function waConfigurePathsAndPermissions() {
+ if [ "$OPT_USER" -eq 1 ]; then
+ SUDO=""
+ SOURCE_PATH="$USER_SOURCE_PATH"
+ BIN_PATH="$USER_BIN_PATH"
+ APP_PATH="$USER_APP_PATH"
+ APPDATA_PATH="$USER_APPDATA_PATH"
+ elif [ "$OPT_SYSTEM" -eq 1 ]; then
+ SUDO="sudo"
+ SOURCE_PATH="$SYS_SOURCE_PATH"
+ BIN_PATH="$SYS_BIN_PATH"
+ APP_PATH="$SYS_APP_PATH"
+ APPDATA_PATH="$SYS_APPDATA_PATH"
+
+ # Preemptively obtain superuser privileges.
+ sudo -v || {
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}AUTHENTICATION FAILURE.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}Failed to gain superuser privileges.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Please check your password and try again."
+ echo "If you continue to experience issues, contact your system administrator."
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_NO_SUDO"
+ }
+ fi
+}
+
+# Name: 'waCheckExistingInstall'
+# Role: Identifies any existing WinApps installations that may conflict with the new installation.
+function waCheckExistingInstall() {
+ # Print feedback.
+ echo -n "Checking for existing conflicting WinApps installations... "
+
+ # Check for an existing 'user' installation.
+ if [[ -f "${USER_BIN_PATH}/winapps" || -d "${USER_SOURCE_PATH}/winapps" ]]; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}EXISTING 'USER' WINAPPS INSTALLATION.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}A previous WinApps installation was detected for the current user.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo -e "Please remove the existing WinApps installation using ${COMMAND_TEXT}winapps-setup --user --uninstall${CLEAR_TEXT}."
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_EXISTING_INSTALL"
+ fi
+
+ # Check for an existing 'system' installation.
+ if [[ -f "${SYS_BIN_PATH}/winapps" || -d "${SYS_SOURCE_PATH}/winapps" ]]; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}EXISTING 'SYSTEM' WINAPPS INSTALLATION.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}A previous system-wide WinApps installation was detected.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo -e "Please remove the existing WinApps installation using ${COMMAND_TEXT}winapps-setup --system --uninstall${CLEAR_TEXT}."
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_EXISTING_INSTALL"
+ fi
+
+ # Print feedback.
+ echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}"
+}
+
+# Name: 'waFixScale'
+# Role: Since FreeRDP only supports '/scale' values of 100, 140 or 180, find the closest supported argument to the user's configuration.
+function waFixScale() {
+ # Define variables.
+ local OLD_SCALE=100
+ local VALID_SCALE_1=100
+ local VALID_SCALE_2=140
+ local VALID_SCALE_3=180
+
+ # Check for an unsupported value.
+ if [ "$RDP_SCALE" != "$VALID_SCALE_1" ] && [ "$RDP_SCALE" != "$VALID_SCALE_2" ] && [ "$RDP_SCALE" != "$VALID_SCALE_3" ]; then
+ # Save the unsupported scale.
+ OLD_SCALE="$RDP_SCALE"
+
+ # Calculate the absolute differences.
+ local DIFF_1=$(( RDP_SCALE > VALID_SCALE_1 ? RDP_SCALE - VALID_SCALE_1 : VALID_SCALE_1 - RDP_SCALE ))
+ local DIFF_2=$(( RDP_SCALE > VALID_SCALE_2 ? RDP_SCALE - VALID_SCALE_2 : VALID_SCALE_2 - RDP_SCALE ))
+ local DIFF_3=$(( RDP_SCALE > VALID_SCALE_3 ? RDP_SCALE - VALID_SCALE_3 : VALID_SCALE_3 - RDP_SCALE ))
+
+ # Set the final scale to the valid scale value with the smallest absolute difference.
+ if (( DIFF_1 <= DIFF_2 && DIFF_1 <= DIFF_3 )); then
+ RDP_SCALE="$VALID_SCALE_1"
+ elif (( DIFF_2 <= DIFF_1 && DIFF_2 <= DIFF_3 )); then
+ RDP_SCALE="$VALID_SCALE_2"
+ else
+ RDP_SCALE="$VALID_SCALE_3"
+ fi
+
+ # Print feedback.
+ echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} Unsupported RDP_SCALE value '${OLD_SCALE}' detected. Defaulting to '${RDP_SCALE}'."
+ fi
+}
+
+# Name: 'waLoadConfig'
+# Role: Loads settings specified within the WinApps configuration file.
+function waLoadConfig() {
+ # Print feedback.
+ echo -n "Attempting to load WinApps configuration file... "
+
+ if [ ! -f "$CONFIG_PATH" ]; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING CONFIGURATION FILE.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}A valid WinApps configuration file was not found.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo -e "Please create a configuration file at ${COMMAND_TEXT}${CONFIG_PATH}${CLEAR_TEXT}."
+ echo -e "See https://github.com/winapps-org/winapps?tab=readme-ov-file#step-3-create-a-winapps-configuration-file"
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_NO_CONFIG"
+ else
+ # Load the WinApps configuration file.
+ # shellcheck source=/dev/null # Exclude this file from being checked by ShellCheck.
+ source "$CONFIG_PATH"
+ fi
+
+ # Print feedback.
+ echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}"
+}
+
+# Name: 'waCheckScriptDependencies'
+# Role: Terminate script if dependencies are missing.
+function waCheckScriptDependencies() {
+ # 'Git'
+ if ! command -v git &>/dev/null; then
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}Please install 'git' to proceed.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Debian/Ubuntu-based systems:"
+ echo -e " ${COMMAND_TEXT}sudo apt install git${CLEAR_TEXT}"
+ echo "Red Hat/Fedora-based systems:"
+ echo -e " ${COMMAND_TEXT}sudo dnf install git${CLEAR_TEXT}"
+ echo "Arch Linux systems:"
+ echo -e " ${COMMAND_TEXT}sudo pacman -S git${CLEAR_TEXT}"
+ echo "Gentoo Linux systems:"
+ echo -e " ${COMMAND_TEXT}sudo emerge --ask dev-vcs/git${CLEAR_TEXT}"
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_MISSING_DEPS"
+ fi
+
+ # 'curl'
+ if ! command -v curl &>/dev/null; then
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}Please install 'curl' to proceed.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Debian/Ubuntu-based systems:"
+ echo -e " ${COMMAND_TEXT}sudo apt install curl${CLEAR_TEXT}"
+ echo "Red Hat/Fedora-based systems:"
+ echo -e " ${COMMAND_TEXT}sudo dnf install curl${CLEAR_TEXT}"
+ echo "Arch Linux systems:"
+ echo -e " ${COMMAND_TEXT}sudo pacman -S curl${CLEAR_TEXT}"
+ echo "Gentoo Linux systems:"
+ echo -e " ${COMMAND_TEXT}sudo emerge --ask net-misc/curl${CLEAR_TEXT}"
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_MISSING_DEPS"
+ fi
+
+ # 'Dialog'.
+ if ! command -v dialog &>/dev/null; then
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}Please install 'dialog' to proceed.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Debian/Ubuntu-based systems:"
+ echo -e " ${COMMAND_TEXT}sudo apt install dialog${CLEAR_TEXT}"
+ echo "Red Hat/Fedora-based systems:"
+ echo -e " ${COMMAND_TEXT}sudo dnf install dialog${CLEAR_TEXT}"
+ echo "Arch Linux systems:"
+ echo -e " ${COMMAND_TEXT}sudo pacman -S dialog${CLEAR_TEXT}"
+ echo "Gentoo Linux systems:"
+ echo -e " ${COMMAND_TEXT}sudo emerge --ask dialog${CLEAR_TEXT}"
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_MISSING_DEPS"
+ fi
+}
+
+# Name: 'waCheckInstallDependencies'
+# Role: Terminate script if dependencies required to install WinApps are missing.
+function waCheckInstallDependencies() {
+ # Declare variables.
+ local FREERDP_MAJOR_VERSION="" # Stores the major version of the installed copy of FreeRDP.
+
+ # Print feedback.
+ echo -n "Checking whether dependencies are installed... "
+
+ # 'libnotify'
+ if ! command -v notify-send &>/dev/null; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}Please install 'libnotify' to proceed.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Debian/Ubuntu-based systems:"
+ echo -e " ${COMMAND_TEXT}sudo apt install libnotify-bin${CLEAR_TEXT}"
+ echo "Red Hat/Fedora-based systems:"
+ echo -e " ${COMMAND_TEXT}sudo dnf install libnotify${CLEAR_TEXT}"
+ echo "Arch Linux systems:"
+ echo -e " ${COMMAND_TEXT}sudo pacman -S libnotify${CLEAR_TEXT}"
+ echo "Gentoo Linux systems:"
+ echo -e " ${COMMAND_TEXT}sudo emerge --ask x11-libs/libnotify${CLEAR_TEXT}"
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_MISSING_DEPS"
+ fi
+
+ # 'Netcat'
+ if ! command -v nc &>/dev/null; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}Please install 'netcat' to proceed.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Debian/Ubuntu-based systems:"
+ echo -e " ${COMMAND_TEXT}sudo apt install netcat${CLEAR_TEXT}"
+ echo "Red Hat/Fedora-based systems:"
+ echo -e " ${COMMAND_TEXT}sudo dnf install nmap-ncat${CLEAR_TEXT}"
+ echo "Arch Linux systems:"
+ echo -e " ${COMMAND_TEXT}sudo pacman -S gnu-netcat${CLEAR_TEXT}"
+ echo "Gentoo Linux systems:"
+ echo -e " ${COMMAND_TEXT}sudo emerge --ask net-analyzer/netcat${CLEAR_TEXT}"
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_MISSING_DEPS"
+ fi
+
+ # 'FreeRDP' (Version 3).
+ # Attempt to set a FreeRDP command if the command variable is empty.
+ if [ -z "$FREERDP_COMMAND" ]; then
+ # Check common commands used to launch FreeRDP.
+ if command -v xfreerdp &>/dev/null; then
+ # Check FreeRDP major version is 3 or greater.
+ FREERDP_MAJOR_VERSION=$(xfreerdp --version | head -n 1 | grep -o -m 1 '\b[0-9]\S*' | head -n 1 | cut -d'.' -f1)
+ if [[ $FREERDP_MAJOR_VERSION =~ ^[0-9]+$ ]] && ((FREERDP_MAJOR_VERSION >= 3)); then
+ FREERDP_COMMAND="xfreerdp"
+ fi
+ fi
+
+ # Check for xfreerdp3 command as a fallback option.
+ if [ -z "$FREERDP_COMMAND" ]; then
+ if command -v xfreerdp3 &>/dev/null; then
+ # Check FreeRDP major version is 3 or greater.
+ FREERDP_MAJOR_VERSION=$(xfreerdp3 --version | head -n 1 | grep -o -m 1 '\b[0-9]\S*' | head -n 1 | cut -d'.' -f1)
+ if [[ $FREERDP_MAJOR_VERSION =~ ^[0-9]+$ ]] && ((FREERDP_MAJOR_VERSION >= 3)); then
+ FREERDP_COMMAND="xfreerdp3"
+ fi
+ fi
+ fi
+
+ # Check for FreeRDP flatpak as a fallback option.
+ if [ -z "$FREERDP_COMMAND" ]; then
+ if command -v flatpak &>/dev/null; then
+ if flatpak list --columns=application | grep -q "^com.freerdp.FreeRDP$"; then
+ # Check FreeRDP major version is 3 or greater.
+ FREERDP_MAJOR_VERSION=$(flatpak list --columns=application,version | grep "^com.freerdp.FreeRDP" | awk '{print $2}' | cut -d'.' -f1)
+ if [[ $FREERDP_MAJOR_VERSION =~ ^[0-9]+$ ]] && ((FREERDP_MAJOR_VERSION >= 3)); then
+ FREERDP_COMMAND="flatpak run --command=xfreerdp com.freerdp.FreeRDP"
+ fi
+ fi
+ fi
+ fi
+ fi
+
+ if ! command -v "$FREERDP_COMMAND" &>/dev/null && [ "$FREERDP_COMMAND" != "flatpak run --command=xfreerdp com.freerdp.FreeRDP" ]; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}Please install 'FreeRDP' version 3 to proceed.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Debian/Ubuntu-based systems:"
+ echo -e " ${COMMAND_TEXT}sudo apt install freerdp3-x11${CLEAR_TEXT}"
+ echo "Red Hat/Fedora-based systems:"
+ echo -e " ${COMMAND_TEXT}sudo dnf install freerdp${CLEAR_TEXT}"
+ echo "Arch Linux systems:"
+ echo -e " ${COMMAND_TEXT}sudo pacman -S freerdp${CLEAR_TEXT}"
+ echo "Gentoo Linux systems:"
+ echo -e " ${COMMAND_TEXT}sudo emerge --ask net-misc/freerdp${CLEAR_TEXT}"
+ echo ""
+ echo "You can also install FreeRDP as a Flatpak."
+ echo "Install Flatpak, add the Flathub repository and then install FreeRDP:"
+ echo -e "${COMMAND_TEXT}flatpak install flathub com.freerdp.FreeRDP${CLEAR_TEXT}"
+ echo -e "${COMMAND_TEXT}sudo flatpak override --filesystem=home com.freerdp.FreeRDP${CLEAR_TEXT}"
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_MISSING_DEPS"
+ fi
+
+ # 'libvirt'/'virt-manager' + 'iproute2'.
+ if [ "$WAFLAVOR" = "libvirt" ]; then
+ if ! command -v virsh &>/dev/null; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}Please install 'Virtual Machine Manager' to proceed.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Debian/Ubuntu-based systems:"
+ echo -e " ${COMMAND_TEXT}sudo apt install virt-manager${CLEAR_TEXT}"
+ echo "Red Hat/Fedora-based systems:"
+ echo -e " ${COMMAND_TEXT}sudo dnf install virt-manager${CLEAR_TEXT}"
+ echo "Arch Linux systems:"
+ echo -e " ${COMMAND_TEXT}sudo pacman -S virt-manager${CLEAR_TEXT}"
+ echo "Gentoo Linux systems:"
+ echo -e " ${COMMAND_TEXT}sudo emerge --ask app-emulation/virt-manager${CLEAR_TEXT}"
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_MISSING_DEPS"
+ fi
+
+ if ! command -v ip &>/dev/null; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}Please install 'iproute2' to proceed.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Debian/Ubuntu-based systems:"
+ echo -e " ${COMMAND_TEXT}sudo apt install iproute2${CLEAR_TEXT}"
+ echo "Red Hat/Fedora-based systems:"
+ echo -e " ${COMMAND_TEXT}sudo dnf install iproute${CLEAR_TEXT}"
+ echo "Arch Linux systems:"
+ echo -e " ${COMMAND_TEXT}sudo pacman -S iproute2${CLEAR_TEXT}"
+ echo "Gentoo Linux systems:"
+ echo -e " ${COMMAND_TEXT}sudo emerge --ask net-misc/iproute2${CLEAR_TEXT}"
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_MISSING_DEPS"
+ fi
+ elif [ "$WAFLAVOR" = "docker" ]; then
+ if ! command -v docker &>/dev/null; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}Please install 'Docker Engine' to proceed.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Please visit https://docs.docker.com/engine/install/ for more information."
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_MISSING_DEPS"
+ fi
+ elif [ "$WAFLAVOR" = "podman" ]; then
+ if ! command -v podman-compose &>/dev/null || ! command -v podman &>/dev/null; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}Please install 'podman' and 'podman-compose' to proceed.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Please visit https://podman.io/docs/installation for more information."
+ echo "Please visit https://github.com/containers/podman-compose for more information."
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_MISSING_DEPS"
+ fi
+ fi
+
+ # Print feedback.
+ echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}"
+}
+
+# Name: 'waCheckGroupMembership'
+# Role: Ensures the current user is part of the required groups.
+function waCheckGroupMembership() {
+ # Print feedback.
+ echo -n "Checking whether the user '$(whoami)' is part of the required groups... "
+
+ # Declare variables.
+ local USER_GROUPS="" # Stores groups the current user belongs to.
+
+ # Identify groups the current user belongs to.
+ USER_GROUPS=$(groups "$(whoami)")
+
+ if ! (echo "$USER_GROUPS" | grep -q -E "\blibvirt\b") || ! (echo "$USER_GROUPS" | grep -q -E "\bkvm\b"); then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}GROUP MEMBERSHIP CHECK ERROR.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}The current user '$(whoami)' is not part of group 'libvirt' and/or group 'kvm'.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Please run the below commands, followed by a system reboot:"
+ echo -e "${COMMAND_TEXT}sudo usermod -a -G libvirt $(whoami)${CLEAR_TEXT}"
+ echo -e "${COMMAND_TEXT}sudo usermod -a -G kvm $(whoami)${CLEAR_TEXT}"
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_NOT_IN_GROUP"
+ fi
+
+ # Print feedback.
+ echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}"
+}
+
+# Name: 'waCheckVMRunning'
+# Role: Checks the state of the Windows 'libvirt' VM to ensure it is running.
+function waCheckVMRunning() {
+ # Print feedback.
+ echo -n "Checking the status of the Windows VM... "
+
+ # Declare variables.
+ local VM_STATE="" # Stores the state of the Windows VM.
+
+ # Obtain VM Status
+ VM_PAUSED=0
+ virsh list --state-paused | grep -wq "$VM_NAME" || VM_PAUSED="$?"
+ VM_RUNNING=0
+ virsh list --state-running | grep -wq "$VM_NAME" || VM_RUNNING="$?"
+ VM_SHUTOFF=0
+ virsh list --state-shutoff | grep -wq "$VM_NAME" || VM_SHUTOFF="$?"
+
+ if [[ $VM_SHUTOFF == "0" ]]; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}WINDOWS VM NOT RUNNING.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}The Windows VM '${VM_NAME}' is powered off.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Please run the below command to start the Windows VM:"
+ echo -e "${COMMAND_TEXT}virsh start ${VM_NAME}${CLEAR_TEXT}"
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_VM_OFF"
+ elif [[ $VM_PAUSED == "0" ]]; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}WINDOWS VM NOT RUNNING.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}The Windows VM '${VM_NAME}' is paused.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Please run the below command to resume the Windows VM:"
+ echo -e "${COMMAND_TEXT}virsh resume ${VM_NAME}${CLEAR_TEXT}"
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_VM_PAUSED"
+ elif [[ $VM_RUNNING != "0" ]]; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}WINDOWS VM DOES NOT EXIST.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}The Windows VM '${VM_NAME}' could not be found.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Please ensure a Windows VM with the name '${VM_NAME}' exists."
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_VM_ABSENT"
+ fi
+
+ # Print feedback.
+ echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}"
+}
+
+# Name: 'waCheckContainerRunning'
+# Role: Throw an error if the Docker/Podman container is not running.
+function waCheckContainerRunning() {
+ # Print feedback.
+ echo -n "Checking container status... "
+
+ # Declare variables.
+ local CONTAINER_STATE=""
+ local COMPOSE_COMMAND=""
+
+ # Determine the state of the container.
+ CONTAINER_STATE=$("$WAFLAVOR" ps --all --filter name="winapps" --format '{{.Status}}')
+ CONTAINER_STATE=${CONTAINER_STATE,,} # Convert the string to lowercase.
+ CONTAINER_STATE=${CONTAINER_STATE%% *} # Extract the first word.
+
+ # Determine the compose command.
+ case "$WAFLAVOR" in
+ "docker") COMPOSE_COMMAND="docker compose" ;;
+ "podman") COMPOSE_COMMAND="podman-compose" ;;
+ esac
+
+ # Check container state.
+ if [[ "$CONTAINER_STATE" != "up" ]]; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}CONTAINER NOT RUNNING.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}Windows is not running.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Please ensure Windows is powered on:"
+ echo -e "${COMMAND_TEXT}${COMPOSE_COMMAND} --file ~/.config/winapps/compose.yaml start${CLEAR_TEXT}"
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_CONTAINER_OFF"
+ fi
+
+ # Print feedback.
+ echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}"
+}
+
+# Name: 'waCheckPortOpen'
+# Role: Assesses whether the RDP port on Windows is open.
+function waCheckPortOpen() {
+ # Print feedback.
+ echo -n "Checking for an open RDP Port on Windows... "
+
+ # Declare variables.
+ local VM_MAC="" # Stores the MAC address of the Windows VM.
+
+ # Obtain Windows VM IP Address (FOR 'libvirt' ONLY)
+ # Note: 'RDP_IP' should not be empty if 'WAFLAVOR' is 'docker', since it is set to localhost before this function is called.
+ if [ -z "$RDP_IP" ] && [ "$WAFLAVOR" = "libvirt" ]; then
+ VM_MAC=$(virsh domiflist "$VM_NAME" | grep -oE "([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})") # VM MAC address.
+ RDP_IP=$(ip neigh show | grep "$VM_MAC" | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}") # VM IP address.
+
+ if [ -z "$RDP_IP" ]; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}NETWORK CONFIGURATION ERROR.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}The IP address of the Windows VM '${VM_NAME}' could not be found.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Please ensure networking is properly configured for the Windows VM."
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_NO_IP"
+ fi
+ fi
+
+ # Check for an open RDP port.
+ if ! timeout "$PORT_TIMEOUT" nc -z "$RDP_IP" "$RDP_PORT" &>/dev/null; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}NETWORK CONFIGURATION ERROR.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}Failed to establish a connection with Windows at '${RDP_IP}:${RDP_PORT}'.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo "Please ensure Remote Desktop is configured on Windows as per the WinApps README."
+ echo -e "Then you can try increasing the ${COMMAND_TEXT}PORT_TIMEOUT${CLEAR_TEXT} in ${COMMAND_TEXT}${CONFIG_PATH}${CLEAR_TEXT}."
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_BAD_PORT"
+ fi
+
+ # Print feedback.
+ echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}"
+}
+
+# Name: 'waCheckRDPAccess'
+# Role: Tests if Windows is accessible via RDP.
+function waCheckRDPAccess() {
+ # Print feedback.
+ echo -n "Attempting to establish a Remote Desktop connection with Windows... "
+
+ # Declare variables.
+ local FREERDP_LOG="" # Stores the path of the FreeRDP log file.
+ local FREERDP_PROC="" # Stores the FreeRDP process ID.
+ local ELAPSED_TIME="" # Stores the time counter.
+
+ # Log file path.
+ FREERDP_LOG="${USER_APPDATA_PATH}/FreeRDP_Test_$(date +'%Y%m%d_%H%M_%N').log"
+
+ # Ensure the output directory exists.
+ mkdir -p "$USER_APPDATA_PATH"
+
+ # Remove existing 'FreeRDP Connection Test' file.
+ rm -f "$TEST_PATH"
+
+ # This command should create a file on the host filesystem before terminating the RDP session. This command is silently executed as a background process.
+ # If the file is created, it means Windows received the command via FreeRDP successfully and can read and write to the Linux home folder.
+ # Note: The following final line is expected within the log, indicating successful execution of the 'tsdiscon' command and termination of the RDP session.
+ # [INFO][com.freerdp.core] - [rdp_print_errinfo]: ERRINFO_LOGOFF_BY_USER (0x0000000C):The disconnection was initiated by the user logging off their session on the server.
+ # shellcheck disable=SC2140,SC2027 # Disable warnings regarding unquoted strings.
+ $FREERDP_COMMAND \
+ /cert:tofu \
+ /d:"$RDP_DOMAIN" \
+ /u:"$RDP_USER" \
+ /p:"$RDP_PASS" \
+ /scale:"$RDP_SCALE" \
+ +auto-reconnect \
+ /app:program:"C:\Windows\System32\cmd.exe",cmd:"/C type NUL > $TEST_PATH_WIN && tsdiscon" \
+ /v:"$RDP_IP" &>"$FREERDP_LOG" &
+
+ # Store the FreeRDP process ID.
+ FREERDP_PROC=$!
+
+ # Initialise the time counter.
+ ELAPSED_TIME=0
+
+ # Wait a maximum of $RDP_TIMEOUT seconds for the background process to complete.
+ while [ "$ELAPSED_TIME" -lt "$RDP_TIMEOUT" ]; do
+ # Check if the FreeRDP process is complete or if the test file exists.
+ if ! ps -p "$FREERDP_PROC" &>/dev/null || [ -f "$TEST_PATH" ]; then
+ break
+ fi
+
+ # Wait for 5 seconds.
+ sleep 5
+ ELAPSED_TIME=$((ELAPSED_TIME + 5))
+ done
+
+ # Check if FreeRDP process is not complete.
+ if ps -p "$FREERDP_PROC" &>/dev/null; then
+ # SIGKILL FreeRDP.
+ kill -9 "$FREERDP_PROC" &>/dev/null
+ fi
+
+ # Check if test file does not exist.
+ if ! [ -f "$TEST_PATH" ]; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}REMOTE DESKTOP PROTOCOL FAILURE.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}FreeRDP failed to establish a connection with Windows.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo -e "Please view the log at ${COMMAND_TEXT}${FREERDP_LOG}${CLEAR_TEXT}."
+ echo "Troubleshooting Tips:"
+ echo " - Ensure the user is logged out of Windows prior to initiating the WinApps installation."
+ echo " - Ensure the credentials within the WinApps configuration file are correct."
+ echo -e " - Utilise a new certificate by removing relevant certificate(s) in ${COMMAND_TEXT}${HOME}/.config/freerdp/server${CLEAR_TEXT}."
+ echo -e " - Try increasing the ${COMMAND_TEXT}RDP_TIMEOUT${CLEAR_TEXT} in ${COMMAND_TEXT}${CONFIG_PATH}${CLEAR_TEXT}."
+ echo " - If using 'libvirt', ensure the Windows VM is correctly named as specified within the README."
+ echo " - If using 'libvirt', ensure 'Remote Desktop' is enabled within the Windows VM."
+ echo " - If using 'libvirt', ensure you have merged 'RDPApps.reg' into the Windows VM's registry."
+ echo " - If using 'libvirt', try logging into and back out of the Windows VM within 'virt-manager' prior to initiating the WinApps installation."
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_RDP_FAIL"
+ else
+ # Remove the temporary test file.
+ rm -f "$TEST_PATH"
+ fi
+
+ # Print feedback.
+ echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}"
+}
+
+# Name: 'waFindInstalled'
+# Role: Identifies installed applications on Windows.
+function waFindInstalled() {
+ # Print feedback.
+ echo -n "Checking for installed Windows applications... "
+
+ # Declare variables.
+ local FREERDP_LOG="" # Stores the path of the FreeRDP log file.
+ local FREERDP_PROC="" # Stores the FreeRDP process ID.
+ local ELAPSED_TIME="" # Stores the time counter.
+
+ # Log file path.
+ FREERDP_LOG="${USER_APPDATA_PATH}/FreeRDP_Scan_$(date +'%Y%m%d_%H%M_%N').log"
+
+ # Make the output directory if required.
+ mkdir -p "$USER_APPDATA_PATH"
+
+ # Remove temporary files from previous WinApps installations.
+ rm -f "$BATCH_SCRIPT_PATH" "$TMP_INST_FILE_PATH" "$INST_FILE_PATH" "$PS_SCRIPT_HOME_PATH" "$DETECTED_FILE_PATH"
+
+ # Copy PowerShell script to a directory within the user's home folder.
+ # This will enable the PowerShell script to be accessed and executed by Windows.
+ cp "$PS_SCRIPT_PATH" "$PS_SCRIPT_HOME_PATH"
+
+ # Enumerate over each officially supported application.
+ for APPLICATION in ./apps/*; do
+ # Extract the name of the application from the absolute path of the folder.
+ APPLICATION="$(basename "$APPLICATION")"
+
+ if [[ "$APPLICATION" == "ms-office-protocol-handler.desktop" ]]; then
+ continue
+ fi
+
+ # Source 'Info' File Containing:
+ # - The Application Name (FULL_NAME)
+ # - The Shortcut Name (NAME)
+ # - Application Categories (CATEGORIES)
+ # - Executable Path (WIN_EXECUTABLE)
+ # - Supported MIME Types (MIME_TYPES)
+ # - Application Icon (ICON)
+ # shellcheck source=/dev/null # Exclude this file from being checked by ShellCheck.
+ source "./apps/${APPLICATION}/info"
+
+ # Append commands to batch file.
+ echo "IF EXIST \"${WIN_EXECUTABLE}\" ECHO ${APPLICATION} >> ${TMP_INST_FILE_PATH_WIN}" >>"$BATCH_SCRIPT_PATH"
+ done
+
+ # Append a command to the batch script to run the PowerShell script and store its output in the 'detected' file.
+ # shellcheck disable=SC2129 # Silence warning regarding repeated redirects.
+ echo "powershell.exe -ExecutionPolicy Bypass -File ${PS_SCRIPT_HOME_PATH_WIN} > ${DETECTED_FILE_PATH_WIN}" >>"$BATCH_SCRIPT_PATH"
+
+ # Append a command to the batch script to rename the temporary file containing the names of all detected officially supported applications.
+ echo "RENAME ${TMP_INST_FILE_PATH_WIN} installed" >>"$BATCH_SCRIPT_PATH"
+
+ # Append a command to the batch script to terminate the remote desktop session once all previous commands are complete.
+ echo "tsdiscon" >>"$BATCH_SCRIPT_PATH"
+
+ # Silently execute the batch script within Windows in the background (Log Output To File)
+ # Note: The following final line is expected within the log, indicating successful execution of the 'tsdiscon' command and termination of the RDP session.
+ # [INFO][com.freerdp.core] - [rdp_print_errinfo]: ERRINFO_LOGOFF_BY_USER (0x0000000C):The disconnection was initiated by the user logging off their session on the server.
+ # shellcheck disable=SC2140,SC2027 # Disable warnings regarding unquoted strings.
+ $FREERDP_COMMAND \
+ /cert:tofu \
+ /d:"$RDP_DOMAIN" \
+ /u:"$RDP_USER" \
+ /p:"$RDP_PASS" \
+ /scale:"$RDP_SCALE" \
+ +auto-reconnect \
+ /app:program:"C:\Windows\System32\cmd.exe",cmd:"/C "$BATCH_SCRIPT_PATH_WIN"" \
+ /v:"$RDP_IP" &>"$FREERDP_LOG" &
+
+ # Store the FreeRDP process ID.
+ FREERDP_PROC=$!
+
+ # Initialise the time counter.
+ ELAPSED_TIME=0
+
+ # Wait a maximum of $APP_SCAN_TIMEOUT seconds for the batch script to finish running.
+ while [ $ELAPSED_TIME -lt "$APP_SCAN_TIMEOUT" ]; do
+ # Check if the FreeRDP process is complete or if the 'installed' file exists.
+ if ! ps -p "$FREERDP_PROC" &>/dev/null || [ -f "$INST_FILE_PATH" ]; then
+ break
+ fi
+
+ # Wait for 5 seconds.
+ sleep 5
+ ELAPSED_TIME=$((ELAPSED_TIME + 5))
+ done
+
+ # Check if the FreeRDP process is not complete.
+ if ps -p "$FREERDP_PROC" &>/dev/null; then
+ # SIGKILL FreeRDP.
+ kill -9 "$FREERDP_PROC" &>/dev/null
+ fi
+
+ # Check if test file does not exist.
+ if ! [ -f "$INST_FILE_PATH" ]; then
+ # Complete the previous line.
+ echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n"
+
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}APPLICATION QUERY FAILURE.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}Failed to query Windows for installed applications.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo -e "Please view the log at ${COMMAND_TEXT}${FREERDP_LOG}${CLEAR_TEXT}."
+ echo -e "You can try increasing the ${COMMAND_TEXT}APP_SCAN_TIMEOUT${CLEAR_TEXT} in ${COMMAND_TEXT}${CONFIG_PATH}${CLEAR_TEXT}."
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_APPQUERY_FAIL"
+ fi
+
+ # Print feedback.
+ echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}"
+}
+
+# Name: 'waConfigureWindows'
+# Role: Create an application entry for launching Windows via Remote Desktop.
+function waConfigureWindows() {
+ # Print feedback.
+ echo -n "Creating an application entry for Windows... "
+
+ # Declare variables.
+ local WIN_BASH="" # Stores the bash script to launch a Windows RDP session.
+ local WIN_DESKTOP="" # Stores the '.desktop' file to launch a Windows RDP session.
+
+ # Populate variables.
+ WIN_BASH="\
+#!/usr/bin/env bash
+${BIN_PATH}/winapps windows"
+ WIN_DESKTOP="\
+[Desktop Entry]
+Name=Windows
+Exec=${BIN_PATH}/winapps windows %F
+Terminal=false
+Type=Application
+Icon=${APPDATA_PATH}/icons/windows.svg
+StartupWMClass=Microsoft Windows
+Comment=Microsoft Windows RDP Session"
+
+ # Copy the 'Windows' icon.
+ $SUDO cp "./icons/windows.svg" "${APPDATA_PATH}/icons/windows.svg"
+
+ # Write the desktop entry content to a file.
+ echo "$WIN_DESKTOP" | $SUDO tee "${APP_PATH}/windows.desktop" &>/dev/null
+
+ # Write the bash script to a file.
+ echo "$WIN_BASH" | $SUDO tee "${BIN_PATH}/windows" &>/dev/null
+
+ # Mark the bash script as executable.
+ $SUDO chmod a+x "${BIN_PATH}/windows"
+
+ # Print feedback.
+ echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}"
+}
+
+# Name: 'waConfigureApp'
+# Role: Create application entries for a given application installed on Windows.
+function waConfigureApp() {
+ # Declare variables.
+ local APP_ICON="" # Stores the path to the application icon.
+ local APP_BASH="" # Stores the bash script used to launch the application.
+ local APP_DESKTOP_FILE="" # Stores the '.desktop' file used to launch the application.
+
+ # Source 'Info' File Containing:
+ # - The Application Name (FULL_NAME)
+ # - The Shortcut Name (NAME)
+ # - Application Categories (CATEGORIES)
+ # - Executable Path (WIN_EXECUTABLE)
+ # - Supported MIME Types (MIME_TYPES)
+ # - Application Icon (ICON)
+ # shellcheck source=/dev/null # Exclude this file from being checked by ShellCheck.
+ source "${APPDATA_PATH}/apps/${1}/info"
+
+ # Determine path to application icon using arguments passed to function.
+ APP_ICON="${APPDATA_PATH}/apps/${1}/icon.${2}"
+
+ # Determine the content of the bash script for the application.
+ APP_BASH="\
+#!/usr/bin/env bash
+${BIN_PATH}/winapps ${1}"
+
+ # Determine the content of the '.desktop' file for the application.
+ APP_DESKTOP_FILE="\
+[Desktop Entry]
+Name=${NAME}
+Exec=${BIN_PATH}/winapps ${1} %F
+Terminal=false
+Type=Application
+Icon=${APP_ICON}
+StartupWMClass=${FULL_NAME}
+Comment=${FULL_NAME}
+Categories=${CATEGORIES}
+MimeType=${MIME_TYPES}"
+
+ # Store the '.desktop' file for the application.
+ echo "$APP_DESKTOP_FILE" | $SUDO tee "${APP_PATH}/${1}.desktop" &>/dev/null
+
+ # Store the bash script for the application.
+ echo "$APP_BASH" | $SUDO tee "${BIN_PATH}/${1}" &>/dev/null
+
+ # Mark bash script as executable.
+ $SUDO chmod a+x "${BIN_PATH}/${1}"
+}
+
+# Name: 'waConfigureOfficiallySupported'
+# Role: Create application entries for officially supported applications installed on Windows.
+function waConfigureOfficiallySupported() {
+ # Declare variables.
+ local OSA_LIST=() # Stores a list of all officially supported applications installed on Windows.
+ local OFFICE_APPS=("access" "access-o365" "access-o365-x86" "access-x86" "adobe-cc" "acrobat9" "acrobat-x-pro" "aftereffects-cc" "audition-cc" "bridge-cc" "bridge-cc-x86" "bridge-cs6" "bridge-cs6-x86" "cmd" "dymo-connect" "excel" "excel-o365" "excel-o365-x86" "excel-x86" "excel-x86-2010" "explorer" "iexplorer" "illustrator-cc" "lightroom-cc" "linqpad8" "mirc" "mspaint" "onenote" "onenote-o365" "onenote-o365-x86" "onenote-x86" "outlook" "outlook-o365" "outlook-o365-x86" "powerpoint" "powerpoint-o365" "powerpoint-o365-x86" "powerpoint-x86" "publisher" "publisher-o365" "publisher-o365-x86" "publisher-x86" "project" "project-x86" "remarkable-desktop" "ssms20" "visual-studio-comm" "visual-studio-ent" "visual-studio-pro" "visio" "visio-x86" "word" "word-o365" "word-o365-x86" "word-x86" "word-x86-2010")
+
+ # Read the list of officially supported applications that are installed on Windows into an array, returning an empty array if no such files exist.
+ readarray -t OSA_LIST < <(grep -v '^[[:space:]]*$' "$INST_FILE_PATH" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' 2>/dev/null || true)
+
+ # Create application entries for each officially supported application.
+ for OSA in "${OSA_LIST[@]}"; do
+ # Print feedback.
+ echo -n "Creating an application entry for ${OSA}... "
+
+ # Copy application icon and information.
+ $SUDO cp -r "./apps/${OSA}" "${APPDATA_PATH}/apps"
+
+ # Configure the application.
+ waConfigureApp "$OSA" svg
+
+ # Check if the application is an Office app and copy the protocol handler.
+ if [[ " ${OFFICE_APPS[*]} " == *" $OSA "* ]]; then
+ # Determine the target directory based on whether the installation is for the system or user.
+ if [[ "$OPT_SYSTEM" -eq 1 ]]; then
+ TARGET_DIR="$SYS_APP_PATH"
+ else
+ TARGET_DIR="$USER_APP_PATH"
+ fi
+
+ # Copy the protocol handler to the appropriate directory.
+ $SUDO cp "./apps/ms-office-protocol-handler.desktop" "$TARGET_DIR/ms-office-protocol-handler.desktop"
+ fi
+
+ # Print feedback.
+ echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}"
+ done
+
+ # Delete 'install' file.
+ rm -f "$INST_FILE_PATH"
+}
+
+# Name: 'waConfigureApps'
+# Role: Allow the user to select which officially supported applications to configure.
+function waConfigureApps() {
+ # Declare variables.
+ local OSA_LIST=() # Stores a list of all officially supported applications installed on Windows.
+ local APPS=() # Stores a list of both the simplified and full names of each installed officially supported application.
+ local OPTIONS=() # Stores a list of options presented to the user.
+ local APP_INSTALL="" # Stores the option selected by the user.
+ local SELECTED_APPS=() # Stores the officially supported applications selected by the user.
+ local TEMP_ARRAY=() # Temporary array used for sorting elements of an array.
+
+ # Read the list of officially supported applications that are installed on Windows into an array, returning an empty array if no such files exist.
+ # This will remove leading and trailing whitespace characters as well as ignore empty lines.
+ readarray -t OSA_LIST < <(grep -v '^[[:space:]]*$' "$INST_FILE_PATH" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' 2>/dev/null || true)
+
+ # Loop over each officially supported application installed on Windows.
+ for OSA in "${OSA_LIST[@]}"; do
+ # Source 'Info' File Containing:
+ # - The Application Name (FULL_NAME)
+ # - The Shortcut Name (NAME)
+ # - Application Categories (CATEGORIES)
+ # - Executable Path (WIN_EXECUTABLE)
+ # - Supported MIME Types (MIME_TYPES)
+ # - Application Icon (ICON)
+ # shellcheck source=/dev/null # Exclude this file from being checked by ShellCheck.
+ source "./apps/${OSA}/info"
+
+ # Add both the simplified and full name of the application to an array.
+ APPS+=("${FULL_NAME} (${OSA})")
+
+ # Extract the executable file name (e.g. 'MyApp.exe') from the absolute path.
+ WIN_EXECUTABLE="${WIN_EXECUTABLE##*\\}"
+
+ # Trim any leading or trailing whitespace characters from the executable file name.
+ read -r WIN_EXECUTABLE <<<"$(echo "$WIN_EXECUTABLE" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
+
+ # Add the executable file name (in lowercase) to the array.
+ INSTALLED_EXES+=("${WIN_EXECUTABLE,,}")
+ done
+
+ # Sort the 'APPS' array in alphabetical order.
+ IFS=$'\n'
+ # shellcheck disable=SC2207 # Silence warnings regarding preferred use of 'mapfile' or 'read -a'.
+ TEMP_ARRAY=($(sort <<<"${APPS[*]}"))
+ unset IFS
+ APPS=("${TEMP_ARRAY[@]}")
+
+ # Prompt user to select which officially supported applications to configure.
+ OPTIONS=(
+ "Set up all detected officially supported applications"
+ "Choose specific officially supported applications to set up"
+ "Skip setting up any officially supported applications"
+ )
+ inqMenu "How would you like to handle officially supported applications?" OPTIONS APP_INSTALL
+
+ # Remove unselected officially supported applications from the 'install' file.
+ if [[ $APP_INSTALL == "Choose specific officially supported applications to set up" ]]; then
+ inqChkBx "Which officially supported applications would you like to set up?" APPS SELECTED_APPS
+
+ # Clear/create the 'install' file.
+ echo "" >"$INST_FILE_PATH"
+
+ # Add each selected officially supported application back to the 'install' file.
+ for SELECTED_APP in "${SELECTED_APPS[@]}"; do
+ # Capture the substring within (but not including) the parentheses.
+ # This substring represents the officially supported application name (see above loop).
+ SELECTED_APP="${SELECTED_APP##*(}"
+ SELECTED_APP="${SELECTED_APP%%)}"
+
+ # Add the substring back to the 'install' file.
+ echo "$SELECTED_APP" >>"$INST_FILE_PATH"
+ done
+ fi
+
+ # Configure selected (or all) officially supported applications.
+ if [[ $APP_INSTALL != "Skip setting up any officially supported applications" ]]; then
+ waConfigureOfficiallySupported
+ fi
+}
+
+# Name: 'waConfigureDetectedApps'
+# Role: Allow the user to select which detected applications to configure.
+function waConfigureDetectedApps() {
+ # Declare variables.
+ local APPS=() # Stores a list of both the simplified and full names of each detected application.
+ local EXE_FILENAME="" # Stores the executable filename of a given detected application.
+ local EXE_FILENAME_NOEXT="" # Stores the executable filename without the file extension of a given detected application.
+ local EXE_FILENAME_LOWERCASE="" # Stores the executable filename of a given detected application in lowercase letters only.
+ local OPTIONS=() # Stores a list of options presented to the user.
+ local APP_INSTALL="" # Stores the option selected by the user.
+ local SELECTED_APPS=() # Detected applications selected by the user.
+ local APP_DESKTOP_FILE="" # Stores the '.desktop' file used to launch the application.
+ local TEMP_ARRAY=() # Temporary array used for sorting elements of an array.
+
+ if [ -f "$DETECTED_FILE_PATH" ]; then
+ # On UNIX systems, lines are terminated with a newline character (\n).
+ # On WINDOWS systems, lines are terminated with both a carriage return (\r) and a newline (\n) character.
+ # Remove all carriage returns (\r) within the 'detected' file, as the file was written by Windows.
+ sed -i 's/\r//g' "$DETECTED_FILE_PATH"
+
+ # Import the detected application information:
+ # - Application Names (NAMES)
+ # - Application Icons in base64 (ICONS)
+ # - Application Executable Paths (EXES)
+ # shellcheck source=/dev/null # Exclude this file from being checked by ShellCheck.
+ source "$DETECTED_FILE_PATH"
+
+ # shellcheck disable=SC2153 # Silence warnings regarding possible misspellings.
+ for INDEX in "${!NAMES[@]}"; do
+ # Extract the executable file name (e.g. 'MyApp.exe').
+ EXE_FILENAME=${EXES[$INDEX]##*\\}
+
+ # Convert the executable file name to lower-case (e.g. 'myapp.exe').
+ EXE_FILENAME_LOWERCASE="${EXE_FILENAME,,}"
+
+ # Remove the file extension (e.g. 'MyApp').
+ EXE_FILENAME_NOEXT="${EXE_FILENAME%.*}"
+
+ # Check if the executable was previously configured as part of setting up officially supported applications.
+ if [[ " ${INSTALLED_EXES[*]} " != *" ${EXE_FILENAME_LOWERCASE} "* ]]; then
+ # If not previously configured, add the application to the list of detected applications.
+ APPS+=("${NAMES[$INDEX]} (${EXE_FILENAME_NOEXT})")
+ fi
+ done
+
+ # Sort the 'APPS' array in alphabetical order.
+ IFS=$'\n'
+ # shellcheck disable=SC2207 # Silence warnings regarding preferred use of 'mapfile' or 'read -a'.
+ TEMP_ARRAY=($(sort <<<"${APPS[*]}"))
+ unset IFS
+ APPS=("${TEMP_ARRAY[@]}")
+
+ # Prompt user to select which other detected applications to configure.
+ OPTIONS=(
+ "Set up all detected applications"
+ "Select which applications to set up"
+ "Do not set up any applications"
+ )
+ inqMenu "How would you like to handle other detected applications?" OPTIONS APP_INSTALL
+
+ # Store selected detected applications.
+ if [[ $APP_INSTALL == "Select which applications to set up" ]]; then
+ inqChkBx "Which other applications would you like to set up?" APPS SELECTED_APPS
+ elif [[ $APP_INSTALL == "Set up all detected applications" ]]; then
+ for APP in "${APPS[@]}"; do
+ SELECTED_APPS+=("$APP")
+ done
+ fi
+
+ for SELECTED_APP in "${SELECTED_APPS[@]}"; do
+ # Capture the substring within (but not including) the parentheses.
+ # This substring represents the executable filename without the file extension (see above loop).
+ EXE_FILENAME_NOEXT="${SELECTED_APP##*(}"
+ EXE_FILENAME_NOEXT="${EXE_FILENAME_NOEXT%%)}"
+
+ # Capture the substring prior to the space and parentheses.
+ # This substring represents the detected application name (see above loop).
+ PROGRAM_NAME="${SELECTED_APP% (*}"
+
+ # Loop through all detected applications to find the detected application being processed.
+ for INDEX in "${!NAMES[@]}"; do
+ # Check for a matching detected application entry.
+ if [[ ${NAMES[$INDEX]} == "$PROGRAM_NAME" ]] && [[ ${EXES[$INDEX]} == *"\\$EXE_FILENAME_NOEXT"* ]]; then
+ # Print feedback.
+ echo -n "Creating an application entry for ${PROGRAM_NAME}... "
+
+ # Create directory to store application icon and information.
+ $SUDO mkdir -p "${APPDATA_PATH}/apps/${EXE_FILENAME_NOEXT}"
+
+ # Determine the content of the '.desktop' file for the application.
+ APP_DESKTOP_FILE="\
+# GNOME Shortcut Name
+NAME=\"${PROGRAM_NAME}\"
+# Used for Descriptions and Window Class
+FULL_NAME=\"${PROGRAM_NAME}\"
+# Path to executable inside Windows
+WIN_EXECUTABLE=\"${EXES[$INDEX]}\"
+# GNOME Categories
+CATEGORIES=\"WinApps\"
+# GNOME MIME Types
+MIME_TYPES=\"\""
+
+ # Store the '.desktop' file for the application.
+ echo "$APP_DESKTOP_FILE" | $SUDO tee "${APPDATA_PATH}/apps/${EXE_FILENAME_NOEXT}/info" &>/dev/null
+
+ # Write application icon to file.
+ echo "${ICONS[$INDEX]}" | base64 -d | $SUDO tee "${APPDATA_PATH}/apps/${EXE_FILENAME_NOEXT}/icon.png" &>/dev/null
+
+ # Configure the application.
+ waConfigureApp "$EXE_FILENAME_NOEXT" png
+
+ # Print feedback.
+ echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}"
+ fi
+ done
+ done
+ fi
+}
+
+# Name: 'waInstall'
+# Role: Installs WinApps.
+function waInstall() {
+ # Print feedback.
+ echo -e "${BOLD_TEXT}Installing WinApps.${CLEAR_TEXT}"
+
+ # Check for existing conflicting WinApps installations.
+ waCheckExistingInstall
+
+ # Load the WinApps configuration file.
+ waLoadConfig
+
+ # Check for missing dependencies.
+ waCheckInstallDependencies
+
+ # Update $RDP_SCALE.
+ waFixScale
+
+ # Append additional FreeRDP flags if required.
+ if [[ -n $RDP_FLAGS ]]; then
+ FREERDP_COMMAND="${FREERDP_COMMAND} ${RDP_FLAGS}"
+ fi
+
+ # If using 'docker' or 'podman', set RDP_IP to localhost.
+ if [ "$WAFLAVOR" = "docker" ] || [ "$WAFLAVOR" = "podman" ]; then
+ RDP_IP="$DOCKER_IP"
+ fi
+
+ # If using podman backend, modify the FreeRDP command to enter a new namespace.
+ if [ "$WAFLAVOR" = "podman" ]; then
+ FREERDP_COMMAND="podman unshare --rootless-netns ${FREERDP_COMMAND}"
+ fi
+
+ if [ "$WAFLAVOR" = "docker" ] || [ "$WAFLAVOR" = "podman" ]; then
+ # Check if Windows is powered on.
+ waCheckContainerRunning
+ elif [ "$WAFLAVOR" = "libvirt" ]; then
+ # Verify the current user's group membership.
+ waCheckGroupMembership
+
+ # Check if the Windows VM is powered on.
+ waCheckVMRunning
+ elif [ "$WAFLAVOR" = "manual" ]; then
+ waCheckPortOpen
+ else
+ # Display the error type.
+ echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}INVALID WINAPPS BACKEND.${CLEAR_TEXT}"
+
+ # Display the error details.
+ echo -e "${INFO_TEXT}An invalid WinApps backend '${WAFLAVOR}' was specified.${CLEAR_TEXT}"
+
+ # Display the suggested action(s).
+ echo "--------------------------------------------------------------------------------"
+ echo -e "Please ensure 'WAFLAVOR' is set to 'docker', 'podman' or 'libvirt' in ${COMMAND_TEXT}${CONFIG_PATH}${CLEAR_TEXT}."
+ echo "--------------------------------------------------------------------------------"
+
+ # Terminate the script.
+ return "$EC_INVALID_FLAVOR"
+ fi
+
+ # Check if the RDP port on Windows is open.
+ waCheckPortOpen
+
+ # Test RDP access to Windows.
+ waCheckRDPAccess
+
+ # Create required directories.
+ $SUDO mkdir -p "$BIN_PATH"
+ $SUDO mkdir -p "$APP_PATH"
+ $SUDO mkdir -p "$APPDATA_PATH/apps"
+ $SUDO mkdir -p "$APPDATA_PATH/icons"
+
+ # Check for installed applications.
+ waFindInstalled
+
+ # Install the WinApps bash scripts.
+ $SUDO ln -sf "${SOURCE_PATH}/bin/winapps" "${BIN_PATH}/winapps"
+ $SUDO ln -sf "${SOURCE_PATH}/setup.sh" "${BIN_PATH}/winapps-setup"
+
+ # Configure the Windows RDP session application launcher.
+ waConfigureWindows
+
+ if [ "$OPT_AOSA" -eq 1 ]; then
+ # Automatically configure all officially supported applications.
+ waConfigureOfficiallySupported
+ else
+ # Configure officially supported applications.
+ waConfigureApps
+
+ # Configure other detected applications.
+ waConfigureDetectedApps
+ fi
+
+ # Ensure BIN_PATH is on PATH
+ waEnsureOnPath
+
+ # Print feedback.
+ echo -e "${SUCCESS_TEXT}INSTALLATION COMPLETE.${CLEAR_TEXT}"
+}
+
+# Name: 'waEnsureOnPath'
+# Role: Ensures that $BIN_PATH is on $PATH.
+function waEnsureOnPath() {
+ if [[ ":$PATH:" != *":$BIN_PATH:"* ]]; then
+ echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} It seems like '${BIN_PATH}' is not on PATH."
+ echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} You can add it by running:"
+ # shellcheck disable=SC2086
+ echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} - For Bash: ${COMMAND_TEXT}echo 'export PATH="${BIN_PATH}:\$PATH"' >> ~/.bashrc && source ~/.bashrc${CLEAR_TEXT}"
+ # shellcheck disable=SC2086
+ echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} - For ZSH: ${COMMAND_TEXT}echo 'export PATH="${BIN_PATH}:\$PATH"' >> ~/.zshrc && source ~/.zshrc${CLEAR_TEXT}"
+ echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} Make sure to restart your Terminal afterwards.\n"
+ fi
+}
+
+# Name: 'waUninstall'
+# Role: Uninstalls WinApps.
+function waUninstall() {
+
+ # Print feedback.
+ [ "$OPT_SYSTEM" -eq 1 ] && echo -e "${BOLD_TEXT}REMOVING SYSTEM INSTALLATION.${CLEAR_TEXT}"
+ [ "$OPT_USER" -eq 1 ] && echo -e "${BOLD_TEXT}REMOVING USER INSTALLATION.${CLEAR_TEXT}"
+
+ # Determine the target directory for the protocol handler based on the installation type.
+ if [[ "$OPT_SYSTEM" -eq 1 ]]; then
+ TARGET_DIR="$SYS_APP_PATH"
+ else
+ TARGET_DIR="$USER_APP_PATH"
+ fi
+
+ # Remove the 'ms-office-protocol-handler.desktop' file if it exists.
+ $SUDO rm -f "$TARGET_DIR/ms-office-protocol-handler.desktop"
+
+ # Declare variables.
+ local WINAPPS_DESKTOP_FILES=() # Stores a list of '.desktop' file paths.
+ local WINAPPS_APP_BASH_SCRIPTS=() # Stores a list of bash script paths.
+ local DESKTOP_FILE_NAME="" # Stores the name of the '.desktop' file for the application.
+ local BASH_SCRIPT_NAME="" # Stores the name of the application.
+
+ # Remove the 'WinApps' bash scripts.
+ $SUDO rm -f "${BIN_PATH}/winapps"
+ $SUDO rm -f "${BIN_PATH}/winapps-setup"
+
+ # Remove WinApps configuration data, temporary files and logs.
+ rm -rf "$USER_APPDATA_PATH"
+
+ # Remove application icons and shortcuts.
+ $SUDO rm -rf "$APPDATA_PATH"
+
+ # Store '.desktop' files containing "${BIN_PATH}/winapps" in an array, returning an empty array if no such files exist.
+ readarray -t WINAPPS_DESKTOP_FILES < <(grep -l -d skip "${BIN_PATH}/winapps" "${APP_PATH}/"* 2>/dev/null || true)
+
+ # Remove each '.desktop' file.
+ for DESKTOP_FILE_PATH in "${WINAPPS_DESKTOP_FILES[@]}"; do
+ # Trim leading and trailing whitespace from '.desktop' file path.
+ DESKTOP_FILE_PATH=$(echo "$DESKTOP_FILE_PATH" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
+
+ # Extract the file name.
+ DESKTOP_FILE_NAME=$(basename "$DESKTOP_FILE_PATH" | sed 's/\.[^.]*$//')
+
+ # Print feedback.
+ echo -n "Removing '.desktop' file for '${DESKTOP_FILE_NAME}'... "
+
+ # Delete the file.
+ $SUDO rm "$DESKTOP_FILE_PATH"
+
+ # Print feedback.
+ echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}"
+ done
+
+ # Store the paths of bash scripts calling 'WinApps' to launch specific applications in an array, returning an empty array if no such files exist.
+ readarray -t WINAPPS_APP_BASH_SCRIPTS < <(grep -l -d skip "${BIN_PATH}/winapps" "${BIN_PATH}/"* 2>/dev/null || true)
+
+ # Remove each bash script.
+ for BASH_SCRIPT_PATH in "${WINAPPS_APP_BASH_SCRIPTS[@]}"; do
+ # Trim leading and trailing whitespace from bash script path.
+ BASH_SCRIPT_PATH=$(echo "$BASH_SCRIPT_PATH" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
+
+ # Extract the file name.
+ BASH_SCRIPT_NAME=$(basename "$BASH_SCRIPT_PATH" | sed 's/\.[^.]*$//')
+
+ # Print feedback.
+ echo -n "Removing bash script for '${BASH_SCRIPT_NAME}'... "
+
+ # Delete the file.
+ $SUDO rm "$BASH_SCRIPT_PATH"
+
+ # Print feedback.
+ echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}"
+ done
+
+ # Print caveats.
+ echo -e "\n${INFO_TEXT}Please note that your WinApps configuration and the WinApps source code were not removed.${CLEAR_TEXT}"
+ echo -e "${INFO_TEXT}You can remove these manually by running:${CLEAR_TEXT}"
+ echo -e "${COMMAND_TEXT}rm -r $(dirname "$CONFIG_PATH")${CLEAR_TEXT}"
+ echo -e "${COMMAND_TEXT}rm -r ${SOURCE_PATH}${CLEAR_TEXT}\n"
+
+ # Print feedback.
+ echo -e "${SUCCESS_TEXT}UNINSTALLATION COMPLETE.${CLEAR_TEXT}"
+}
+
+### SEQUENTIAL LOGIC ###
+# Welcome the user.
+echo -e "${BOLD_TEXT}\
+################################################################################
+# #
+# WinApps Install Wizard #
+# #
+################################################################################
+${CLEAR_TEXT}"
+
+# Check dependencies for the script.
+waCheckScriptDependencies
+
+# Source the contents of 'inquirer.sh'.
+waGetInquirer
+
+# Sanitise and parse the user input.
+waCheckInput "$@"
+
+# Configure paths and permissions.
+waConfigurePathsAndPermissions
+
+# Get the source code
+waGetSourceCode
+
+# Install or uninstall WinApps.
+if [ "$OPT_UNINSTALL" -eq 1 ]; then
+ waUninstall
+else
+ waInstall
+fi
+
+exit 0
diff --git a/.config/winapps/winapps.conf b/.config/winapps/winapps.conf
new file mode 100644
index 0000000..617d16a
--- /dev/null
+++ b/.config/winapps/winapps.conf
@@ -0,0 +1,139 @@
+##################################
+# 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="paul"
+
+# [WINDOWS PASSWORD]
+# NOTES:
+# - If using FreeRDP v3.9.0 or greater, you *have* to set a password
+RDP_PASS="nicerdicer"
+
+# [WINDOWS DOMAIN]
+# DEFAULT VALUE: '' (BLANK)
+RDP_DOMAIN=""
+
+# [WINDOWS IPV4 ADDRESS]
+# NOTES:
+# - If using 'libvirt', 'RDP_IP' will be determined by WinApps at runtime if left unspecified.
+# DEFAULT VALUE:
+# - 'docker': '127.0.0.1'
+# - 'podman': '127.0.0.1'
+# - 'libvirt': '' (BLANK)
+RDP_IP="127.0.0.1"
+
+# [VM NAME]
+# NOTES:
+# - Only applicable when using 'libvirt'
+# - The libvirt VM name must match so that WinApps can determine VM IP, start the VM, etc.
+# DEFAULT VALUE: 'RDPWindows'
+VM_NAME="RDPWindows"
+
+# [WINAPPS BACKEND]
+# DEFAULT VALUE: 'docker'
+# VALID VALUES:
+# - 'docker'
+# - 'podman'
+# - 'libvirt'
+# - 'manual'
+WAFLAVOR="docker"
+
+# [DISPLAY SCALING FACTOR]
+# NOTES:
+# - If an unsupported value is specified, a warning will be displayed.
+# - If an unsupported value is specified, WinApps will use the closest supported value.
+# DEFAULT VALUE: '100'
+# VALID VALUES:
+# - '100'
+# - '140'
+# - '180'
+RDP_SCALE="100"
+
+# [MOUNTING REMOVABLE PATHS FOR FILES]
+# NOTES:
+# - By default, `udisks` (which you most likely have installed) uses /run/media for mounting removable devices.
+# This improves compatibility with most desktop environments (DEs).
+# ATTENTION: The Filesystem Hierarchy Standard (FHS) recommends /media instead. Verify your system's configuration.
+# - To manually mount devices, you may optionally use /mnt.
+# REFERENCE: https://wiki.archlinux.org/title/Udisks#Mount_to_/media
+REMOVABLE_MEDIA="/run/media"
+
+# [ADDITIONAL FREERDP FLAGS & ARGUMENTS]
+# NOTES:
+# - You can try adding /network:lan to these flags in order to increase performance, however, some users have faced issues with this.
+# DEFAULT VALUE: '/cert:tofu /sound /microphone +home-drive'
+# VALID VALUES: See https://github.com/awakecoding/FreeRDP-Manuals/blob/master/User/FreeRDP-User-Manual.markdown
+RDP_FLAGS="/cert:tofu /sound /microphone +home-drive"
+
+# [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=""
+
+# [TIMEOUTS]
+# NOTES:
+# - These settings control various timeout durations within the WinApps setup.
+# - Increasing the timeouts is only necessary if the corresponding errors occur.
+# - Ensure you have followed all the Troubleshooting Tips in the error message first.
+
+# PORT CHECK
+# - The maximum time (in seconds) to wait when checking if the RDP port on Windows is open.
+# - Corresponding error: "NETWORK CONFIGURATION ERROR" (exit status 13).
+# DEFAULT VALUE: '5'
+PORT_TIMEOUT="5"
+
+# RDP CONNECTION TEST
+# - The maximum time (in seconds) to wait when testing the initial RDP connection to Windows.
+# - Corresponding error: "REMOTE DESKTOP PROTOCOL FAILURE" (exit status 14).
+# DEFAULT VALUE: '30'
+RDP_TIMEOUT="30"
+
+# APPLICATION SCAN
+# - The maximum time (in seconds) to wait for the script that scans for installed applications on Windows to complete.
+# - Corresponding error: "APPLICATION QUERY FAILURE" (exit status 15).
+# DEFAULT VALUE: '60'
+APP_SCAN_TIMEOUT="60"
+
+# WINDOWS BOOT
+# - The maximum time (in seconds) to wait for the Windows VM to boot if it is not running, before attempting to launch an application.
+# DEFAULT VALUE: '120'
+BOOT_TIMEOUT="120"
\ No newline at end of file
diff --git a/.local/share/darkman/gtk-theme.py b/.local/share/darkman/gtk-theme.py
new file mode 100755
index 0000000..6e22a5a
--- /dev/null
+++ b/.local/share/darkman/gtk-theme.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+
+import subprocess
+from argparse import ArgumentParser
+
+if __name__ == "__main__":
+ parser = ArgumentParser(description="GTK theme switcher")
+ parser.add_argument(
+ "mode",
+ help="The mode to switch the GTK theme to (e.g., 'dark' or 'light')",
+ )
+ args = parser.parse_args()
+
+ if args.mode not in ["dark", "light"]:
+ print("Invalid mode. Please choose 'dark' or 'light'.")
+ exit(1)
+
+ gtk_theme = "Adwaita-dark" if args.mode == "dark" else "Adwaita"
+ color_scheme = "prefer-dark" if args.mode == "dark" else "prefer-light"
+
+ # Set the GTK theme using gsettings
+ subprocess.run(
+ ["gsettings", "set", "org.gnome.desktop.interface", "gtk-theme", gtk_theme],
+ check=True,
+ )
+ subprocess.run(
+ ["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", color_scheme],
+ check=True,
+ )
+
diff --git a/.local/share/darkman/vscode-theme.py b/.local/share/darkman/vscode-theme.py
new file mode 100755
index 0000000..b9326e2
--- /dev/null
+++ b/.local/share/darkman/vscode-theme.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+
+import re
+from argparse import ArgumentParser
+from pathlib import Path
+
+if __name__ == "__main__":
+ parser = ArgumentParser(description="VS Code theme switcher")
+ parser.add_argument(
+ "mode",
+ help="The mode to switch the VS Code theme to (e.g., 'dark' or 'light')",
+ )
+ args = parser.parse_args()
+
+ if args.mode not in ["dark", "light"]:
+ print("Invalid mode. Please choose 'dark' or 'light'.")
+ exit(1)
+
+ settings = Path.home() / ".config/Code/User/settings.json"
+
+ if not settings.exists():
+ print(f"VS Code settings file not found at {settings}")
+ exit(1)
+
+ # We just replace the theme name in the settings file using regex,
+ # this way we don't have to worry about jsonc parsing.
+ theme = "Monokai Pro" if args.mode == "dark" else "Monokai Pro Light"
+
+ with settings.open("r") as f:
+ content = f.read()
+
+ new_content = re.sub(
+ r'"workbench\.colorTheme"\s*:\s*".*?"',
+ f'"workbench.colorTheme": "{theme}"',
+ content,
+ )
+
+ with settings.open("w") as f:
+ f.write(new_content)
\ No newline at end of file
diff --git a/.zshrc b/.zshrc
index dedfebe..a3535f5 100644
--- a/.zshrc
+++ b/.zshrc
@@ -142,13 +142,14 @@ alias open="xdg-open"
alias lg="lazygit"
alias ccat="pygmentize -g -O style=monokai,linenos=1"
alias rm="echo Use the full path i.e. '/bin/rm', consider using: trash"
-alias explain="gh copilot explain"
+alias explain="copilot explain"
alias csv="csvlens"
alias wstmux="cd ~/code/wasteside/app && tmux new -A -s wasteside"
alias R="R --no-save"
+
# Python tkinter stuff
export PATH="/usr/local/opt/tcl-tk/bin:$PATH"
@@ -158,6 +159,8 @@ export PATH="/usr/local/opt/tcl-tk/bin:$PATH"
# ws shortcuts
alias wsproddb="psql -h ep-shiny-brook-a23l5gzv-pooler.eu-central-1.aws.neon.tech -d neondb -U neondb_owner"
alias wsdevdb="psql -h ep-bitter-queen-a2jgwcaj-pooler.eu-central-1.aws.neon.tech -d verceldb -U default"
+alias wsrecupdb="psql -h ep-icy-dew-aggbrr4l-pooler.c-2.eu-central-1.aws.neon.tech -d neondb -U neondb_owner"
+alias wsrecupdevdb="psql -h ep-broad-base-ag8vy0hl-pooler.c-2.eu-central-1.aws.neon.tech -d neondb -U neondb_owner"
# Fix ls colors for synology directories
LS_COLORS+=':ow=01;33'
@@ -170,6 +173,8 @@ HISTFILE=~/.histfile
HISTSIZE=10000
SAVEHIST=10000
+export XDG_DATA_DIRS="$HOME/.local/share:$XDG_DATA_DIRS"
+
# Android tools
export ANDROID_SDK_ROOT="$HOME/.local/opt/android-sdk"
export ANDROID_HOME="$ANDROID_SDK_ROOT"