Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions apps-expose/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,54 @@ This tells macOS that the background service with this label prefix is managed,
## Configuration

The agent is configured via environment variables at the top of `/usr/local/bin/parallels_apps_expose_service.sh`.
Default exclusions include:

### Filter Mode

The service supports two filter modes, controlled by the `FILTER_MODE` variable:

| Mode | Variable Value | Behaviour |
|------|---------------|-----------|
| **Exclude** (default) | `FILTER_MODE=exclude` | Every app is exposed **except** those listed in `EXCLUDED_APPS`. |
| **Include** (allowlist) | `FILTER_MODE=include` | **Only** apps listed in `INCLUDED_APPS` are exposed. Everything else is hidden. |

#### Using Exclude mode (default)

Leave `FILTER_MODE` at its default value (`exclude`) and list the apps you want to **hide** in the `EXCLUDED_APPS` array:

```bash
FILTER_MODE=exclude # default, can be omitted

EXCLUDED_APPS=(
"Calculator"
"Camera"
"Notepad"
# … add more apps to hide
)
```

#### Using Include mode (allowlist)

Set `FILTER_MODE=include` and list **only** the apps you want to **expose** in the `INCLUDED_APPS` array. All other apps will be ignored:

```bash
FILTER_MODE=include

INCLUDED_APPS=(
"Power BI Desktop"
"Excel"
# … only these apps will appear in the Windows Apps folder
)
```

> [!WARNING]
> If `FILTER_MODE=include` is set but `INCLUDED_APPS` is empty, **no apps will be exposed**. The service will log a warning at startup to help catch this misconfiguration.

> [!NOTE]
> Both `EXCLUDED_APPS` and `INCLUDED_APPS` entries are treated as **regular expression** patterns anchored to the full app name (without the `.app` extension). Special characters such as `(`, `)`, and `.` must be escaped with a backslash — for example `"Microsoft 365 \(Office\)"`.

### Default Excluded Applications (Exclude mode)

The following apps are excluded by default when using `FILTER_MODE=exclude`:
- Calculator
- Camera
- Character Map
Expand Down Expand Up @@ -111,7 +158,7 @@ Default exclusions include:
- Windows PowerShell
- Windows Security

To modify exclusions, edit the `EXCLUSIONS` variable in the script.
To modify the filter mode or exclusion/inclusion lists, edit the corresponding variables in the script.

## Uninstalling

Expand Down
61 changes: 54 additions & 7 deletions apps-expose/parallels_apps_expose_service.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,20 @@ DEST_DIR="$SOURCE_ROOT/Windows Apps"
# Poll interval in seconds
: "${POLL_INTERVAL:=60}"

# Applications to exclude (grep patterns)
# Applications to exclude
# Filter mode: "exclude" (default) or "include"
# - "exclude": all apps are exposed EXCEPT those listed in EXCLUDED_APPS
# - "include": ONLY apps listed in INCLUDED_APPS are exposed
: "${FILTER_MODE:=exclude}"

# Applications to include (used only when FILTER_MODE=include)
# Add each app as a separate line in the array.
# These are treated as Regex patterns.
INCLUDED_APPS=(
# "Power BI Desktop"
# "Excel"
)

# Applications to exclude (used only when FILTER_MODE=exclude)
# Add each app as a separate line in the array
# These are treated as Regex patterns.
EXCLUDED_APPS=(
Expand Down Expand Up @@ -123,6 +135,40 @@ is_excluded() {
return 1 # False, not excluded
}

# Check if an app name matches any inclusion pattern
is_included() {
local app_name="$1"
local pattern

# Strip .app for easier matching if present
local clean_name="${app_name%.app}"

for pattern in "${INCLUDED_APPS[@]}"; do
local regex="^${pattern}$"
if [[ "$clean_name" =~ $regex ]]; then
return 0 # True, it is included
fi
done
return 1 # False, not included
}

# Returns 0 (true) if the app should be exposed based on the current FILTER_MODE
should_expose() {
local app_name="$1"
if [[ "$FILTER_MODE" == "include" ]]; then
is_included "$app_name"
else
! is_excluded "$app_name"
fi
}

# Validate configuration at startup
validate_config() {
if [[ "$FILTER_MODE" == "include" && ${#INCLUDED_APPS[@]} -eq 0 ]]; then
log "Warning: FILTER_MODE is 'include' but INCLUDED_APPS is empty. No apps will be exposed. Add entries to INCLUDED_APPS or switch to FILTER_MODE=exclude."
fi
}

# ==============================================================================
# Core Logic
# ==============================================================================
Expand All @@ -145,9 +191,9 @@ sync_apps() {
if [[ -L "$link" ]]; then
link_name=$(basename "$link")

# Check 1: Is it excluded?
if is_excluded "$link_name"; then
log "Removing excluded link: $link_name"
# Check 1: Should it no longer be exposed?
if ! should_expose "$link_name"; then
log "Removing link (not eligible in current mode): $link_name"
rm "$link"
changed=1
continue
Expand Down Expand Up @@ -176,8 +222,8 @@ sync_apps() {

app_name=$(basename "$app_path")

# Check Exclusions using helper function
if is_excluded "$app_name"; then
# Check if app should be exposed based on current filter mode
if ! should_expose "$app_name"; then
continue
fi

Expand Down Expand Up @@ -338,6 +384,7 @@ EOF
# ==============================================================================

log "Agent started. Monitoring $SOURCE_ROOT"
validate_config
ensure_dest_dir

FIRST_RUN=1
Expand Down