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
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 --useror a virtual environment. Alternatively, install withuv:uv pip install global-shortcut-portalFor development, clone the repo and pull group dev:
git clone https://codeberg.org/marvin1099/python-global-shortcut-portal # if not done jet cd python-global-shortcut-portal # if not done jet uv sync --group dev
Reference Example
The repository includes a fully-commented reference app at
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 |
python examples/reference_example_app.py
Flatpak
xdg-desktop-portal was built for sandboxed apps, so this library runs unchanged
inside a Flatpak sandbox, portal access is granted by default, no D-Bus
permissions needed (see docs/flatpak.md). The only difference
is the app ID: the portal attributes shortcuts to the sandbox app ID
($FLATPAK_ID). The library ships small detection helpers (is_flatpak(),
flatpak_id(), portal_app_id(), session_type()) for apps that need to know.
A ready-to-build Flatpak for the reference example lives in flatpak/:
flatpak-builder --force-clean --user --install flatpak/build \
flatpak/page.codeberg.marvin1099.GlobalShortcutPortalExample.json
flatpak run page.codeberg.marvin1099.GlobalShortcutPortalExample
The app ID
page.codeberg.marvin1099.GlobalShortcutPortalExampleonly exists
in this repo for testing, there is no such entry on Flathub.
After testing, uninstall it with:flatpak remove page.codeberg.marvin1099.GlobalShortcutPortalExample
Documentation
- docs/overview.md — the Global Shortcut Portal and this library
- docs/usage.md — full API guide with code examples
- docs/flatpak.md — running the library from a Flatpak sandbox
- examples/reference_example_app.py — interactive reference app
Quick Start
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.Registerfor 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. - Changing the set on a new session works.
Binding a reduced set on a new session replaces the old one,
so shortcuts missing from the new set are removed
(actionfuses this to clear everything).
DEs usualy persist triggers perapp_id,
so a re-bound shortcut with the 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.
Metadata
Release files for global-shortcut-portal 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| global_shortcut_portal-0.2.1.tar.gz | 28.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| global_shortcut_portal-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 53.0 kB
Release files / global_shortcut_portal-0.2.1.tar.gz
| Download URL | global_shortcut_portal-0.2.1.tar.gz |
|---|---|
| Size | 28.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0f8477b93c31cdba33fc37a1106ee61b1821caf1890f6b0d2e0745759980c4ff
|
|
BLAKE2b-256 checksum How to use checksums |
f43e373987c5e2f0a891777540f9b28a0c9aa4aaa7b97686ea639d70239277a2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / global_shortcut_portal-0.2.1-py3-none-any.whl
| Download URL | global_shortcut_portal-0.2.1-py3-none-any.whl |
|---|---|
| Size | 24.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a697747fee2acd40c2bff32d49096804980e01eb1143305145d1a955669450ba
|
|
BLAKE2b-256 checksum How to use checksums |
c6da0dad9b520156385ae358d733db7fce778a5a6e6d3460fab31d17973abad7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|