Metadata-Version: 2.4
Name: ovos-PHAL-plugin-app-launcher
Version: 0.0.1a4
Summary: PHAL plugin: desktop application launcher OS actions over the OVOS messagebus
Author-email: JarbasAi <jarbasai@mailfence.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/TigreGotico/ovos-PHAL-plugin-app-launcher
Project-URL: Repository, https://github.com/TigreGotico/ovos-PHAL-plugin-app-launcher
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: ovos-plugin-manager<3.0.0,>=2.1.0
Requires-Dist: ovos-utils<1.0.0,>=0.1.0
Requires-Dist: ovos-bus-client<3.0.0,>=0.0.8
Requires-Dist: psutil
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Requires-Dist: pytest-cov; extra == "test"

# ovos-PHAL-plugin-app-launcher

This PHAL plugin for [OpenVoiceOS](https://openvoiceos.com) manages OS-level desktop
applications for [ovos-skill-application-launcher](https://github.com/OpenVoiceOS/ovos-skill-application-launcher)
or any other bus client. It scans `.desktop` files, launches and closes processes, and
reports application state over the OVOS messagebus.

---

## Why a split architecture?

In a single-box OVOS install, the skill and the OS run on the same machine, so
`subprocess.Popen` calls from the skill work directly. A split between skill and
plugin becomes necessary in these cases:

* **ovos-core runs on a server or NUC** while the desktop runs on a different device.
* **The device OS changes** (for example X11 desktop, Wayland, or a headless kiosk).
  Only the PHAL plugin needs to change; the skill stays the same.
* **HiveMind multi-room setups**: the skill runs on the ovos-core satellite, and the
  PHAL plugin runs on the satellite device where apps must open. Bus messages cross
  HiveMind transparently.

```
┌──────────────── ovos-core machine ─────────────────┐
│  ovos-skill-application-launcher                    │
│    • understands speech / intents                   │
│    • emits ovos.phal.app_launcher.* requests        │
│    • waits for responses, speaks result / error     │
└────────────────────┬───────────────────────────────┘
                     │  messagebus  (local or HiveMind)
┌────────────────── device machine ──────────────────┐
│  ovos-PHAL-plugin-app-launcher  (this repo)         │
│    • scans .desktop files                           │
│    • launches / closes processes via subprocess     │
│    • manages windows via wmctrl (optional)          │
│    • replies success / error                        │
└────────────────────────────────────────────────────┘
```

---

## Bus event API

Each request and response pair uses the standard `message.response()` convention, so
`bus.wait_for_response` works across HiveMind sessions.

| Request event | Request payload | Response event | Response payload |
|---|---|---|---|
| `ovos.phal.app_launcher.list` | *(none)* | `.response` | `{apps: [{name, exec}]}` |
| `ovos.phal.app_launcher.launch` | `{name: str}` | `.response` | `{name, success: true}` or `{name, error: str}` |
| `ovos.phal.app_launcher.close` | `{name: str}` | `.response` | `{name, success: true}` or `{name, error: str}` |
| `ovos.phal.app_launcher.is_running` | `{name: str}` | `.response` | `{name, running: bool}` |

---

## Matching

`launch`, `close`, and `is_running` resolve the spoken `name` against the built application
list (and, for `close`/`is_running`, against open window titles) with a fuzzy match. A name
that scores below `match_threshold` is reported back as not found (`{name, error: "No
application matched '...'"}` for `launch`, or excluded from window candidates for `close`)
rather than being fuzzy-matched to some other, unrelated application.

## Response and timeout guarantees

Every request gets exactly one response. When process enumeration fails mid-scan
(a process exits while the plugin is looking at it, or access is denied), the
handler answers with `{name, error: str}` instead of staying silent, so a
`wait_for_response` caller never hangs on a race. Calls to `wmctrl` (window
listing and window close) time out after 3 seconds; a hung window manager makes
`close` fall back to the process-based path rather than blocking the handler.

## Configuration

Add to `ovos.conf` under the `PHAL` section or plugin settings:

```json
{
  "ovos-phal-plugin-app-launcher": {
    "skip_categories": ["Settings", "ConsoleOnly", "Building"],
    "skip_keywords": [],
    "target_categories": [],
    "target_keywords": [],
    "blacklist": [],
    "aliases": {"kcalc": ["calculator"]},
    "user_commands": {},
    "require_icon": true,
    "require_categories": true,
    "match_threshold": 0.85,
    "terminate_all": false,
    "disable_window_manager": false
  }
}
```

| Key | Type | Default | Description |
|---|---|---|---|
| `skip_categories` | list[str] | `["Settings","ConsoleOnly","Building"]` | Desktop categories to exclude |
| `skip_keywords` | list[str] | `[]` | Desktop keywords to exclude |
| `target_categories` | list[str] | `[]` | Only include apps in these categories (empty = all) |
| `target_keywords` | list[str] | `[]` | Only include apps with these keywords (empty = all) |
| `blacklist` | list[str] | `[]` | App names or `.desktop` filenames to skip |
| `aliases` | dict | `{"kcalc":["calculator"]}` | Map desktop-name → speech aliases |
| `user_commands` | dict | `{}` | Map voice name → shell command |
| `require_icon` | bool | `true` | Skip apps without an `Icon` field |
| `require_categories` | bool | `true` | Skip apps without a `Categories` field |
| `match_threshold` | float | `0.85` | Fuzzy-match threshold (0–1) |
| `terminate_all` | bool | `false` | Kill every matching process, not just the first |
| `disable_window_manager` | bool | `false` | Skip `wmctrl`-based window management |

---

## Installation

```bash
pip install ovos-PHAL-plugin-app-launcher
# optional: for window management
sudo apt install wmctrl
```

---

## Related projects

* [ovos-skill-application-launcher](https://github.com/OpenVoiceOS/ovos-skill-application-launcher) -
  the OVOS skill that sends launch, close, and status requests to this plugin.

---

## Credits

Developed by [TigreGótico](https://tigregotico.pt) for
[OpenVoiceOS](https://openvoiceos.org).

[![NGI0 Commons Fund](./ngi.png)](https://nlnet.nl/project/OpenVoiceOS)

This project was funded through the [NGI0 Commons Fund](https://nlnet.nl/commonsfund),
a fund established by [NLnet](https://nlnet.nl) with financial support from the
European Commission's [Next Generation Internet](https://ngi.eu) programme, under
the aegis of [DG Communications Networks, Content and Technology](https://commission.europa.eu/about-european-commission/departments-and-executive-agencies/communications-networks-content-and-technology_en)
under grant agreement No [101135429](https://cordis.europa.eu/project/id/101135429).
