Metadata-Version: 2.4
Name: global-shortcut-portal
Version: 0.1.2
Summary: Python library for the Wayland Global Shortcut Portal (xdg-desktop-portal)
Author-email: marvin1099 <marvin1099@noreply.codeberg.org>
License-Expression: AGPL-3.0-only
Project-URL: Homepage, https://codeberg.org/marvin1099/python-global-shortcut-portal
Project-URL: Repository, https://codeberg.org/marvin1099/python-global-shortcut-portal
Keywords: wayland,global-shortcuts,xdg-desktop-portal,hotkeys
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: dbus-next>=0.2.3
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.25; extra == "dev"
Requires-Dist: ruff>=0.5; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Dynamic: license-file

# global-shortcut-portal

A pure-Python library for the Wayland **Global Shortcut Portal** (`org.freedesktop.portal.GlobalShortcuts`).  
Lets any application register and receive global keyboard shortcuts on Wayland, without X11 key grabbing.

AI was used heavily during development, with human review and testing of all code.  
This is a personal library I wanted and I'm sharing it in case it's useful to others.

## Requirements

- Python >= 3.10
- `dbus-next` (pure Python, no C extensions)
- A Wayland compositor with a Global Shortcuts portal backend
  (KDE Plasma 6+, GNOME 48+, Hyprland, etc.)

## Installation

```bash
pip install global-shortcut-portal
```

> On systems with an externally managed environment (e.g. recent Debian/Ubuntu,
> Fedora, Arch Linux with system Python) use `pip install --user` or a virtual
> environment. Alternatively, install with `uv`:
>
> ```bash
> uv pip install global-shortcut-portal
> ```
>
> For development, clone the repo and run:
> ```bash
> uv sync --group dev
> ```

## Reference Example

The repository includes a fully-commented reference app at
[`examples/reference_example_app.py`](https://codeberg.org/marvin1099/python-global-shortcut-portal/src/branch/main/examples/reference_example_app.py) that demonstrates the
complete session lifecycle with interactive controls:

| Key | Action |
|-----|--------|
| `b` | Bind example shortcuts with default triggers |
| `a` | Grow the list: bind a third shortcut (resets session) |
| `f` | Force empty: two reset+bind rounds that remove shortcuts |
| `e` | Register shortcuts without triggers |
| `l` | List bound shortcuts |
| `c` | Open the native config dialog |
| `r` | Reset the session (needed before re-binding) |
| `q` | Quit |

```bash
python examples/reference_example_app.py
```

## Documentation

- [docs/overview.md](docs/overview.md) — the Global Shortcut Portal and this library
- [docs/usage.md](docs/usage.md) — full API guide with code examples
- [examples/reference_example_app.py](https://codeberg.org/marvin1099/python-global-shortcut-portal/src/branch/main/examples/reference_example_app.py) — interactive reference app

## Quick Start

```python
import asyncio
from global_shortcut_portal import (
    GlobalShortcutsSession,
    Portal,
    Shortcut,
    SessionCallback,
)


class MyCallback(SessionCallback):
    def on_activated(self, event):
        print(f"Shortcut activated: {event.shortcut_id}")

    def on_deactivated(self, event):
        print(f"Shortcut deactivated: {event.shortcut_id}")


async def main():
    portal = Portal()
    await portal.connect()

    session = GlobalShortcutsSession(
        portal,
        app_id="org.example.MyApp",
        callback=MyCallback(),
    )
    await session.connect()

    shortcuts = [
        Shortcut(
            id="toggle-overlay",
            description="Toggle overlay window",
            preferred_trigger="CTRL+ALT+SPACE",
        ),
    ]
    bound = await session.bind(shortcuts)
    for s in bound:
        print(f"Bound: {s.id} -> {s.trigger_description}")

    await asyncio.Event().wait()


asyncio.run(main())
```

## Features

- Async API via `dbus-next` (pure Python asyncio D-Bus library)
- Session life-cycle management (create, bind, list, configure, close)
- Supports `Registry.Register` for xdg-desktop-portal >= 1.20
- Full signal handling (Activated, Deactivated, ShortcutsChanged)
- Shortcut trigger parsing and formatting (XDG shortcuts specification)
- Version 2 portal features (ConfigureShortcuts)

## Notes

- **BindShortcuts is only allowed once per session.** There is no portal method
  to unbind or update a bound shortcut; use the native config dialog or create
  a new session to change the set.
- **Desktop environment persistence**: Some DEs (notably Plasma/KDE) persist
  shortcut triggers per `app_id`. A reset session + rebind works per spec: the
  new bind set replaces the old one, so a shortcut missing from the new set is
  removed. But a shortcut that is still bound (same ID) keeps its stored
  trigger — to change one, first bind a set without it, then reset again and
  rebind the full set with the new trigger.
