Nucleus
OS integration

Global hotkeys

Register OS-wide keyboard shortcuts that fire even when your app does not have focus — for media players, screenshots, accessibility tools.

A global hotkey is a key combination the OS routes to your app regardless of which window has focus. Screen capture tools, music players, screen rulers — they all need it. global-hotkey exposes it as a single Kotlin API across macOS, Windows, and Linux.

TL;DR

  • Register key + modifier combinations or dedicated media keys.
  • One callback per binding, returns a handle for unregistering.
  • Native bridges per OS — no AWT, no key-event polling.
  • On macOS, the user must grant Input Monitoring / Accessibility permission for some bindings.

Install

dependencies {
    implementation("dev.nucleusframework:nucleus.global-hotkey:<version>")
}

Quickstart

import dev.nucleusframework.globalhotkey.GlobalHotKeyManager
import dev.nucleusframework.globalhotkey.HotKeyModifier
import java.awt.event.KeyEvent

GlobalHotKeyManager.initialize()

val handle = GlobalHotKeyManager.register(
    modifiers = setOf(HotKeyModifier.CTRL, HotKeyModifier.SHIFT),
    keyCode = KeyEvent.VK_F12,
) { _ ->
    println("Hotkey pressed")
}

// later
GlobalHotKeyManager.unregister(handle)
GlobalHotKeyManager.shutdown()

How it works

GlobalHotKeyManager opens an OS event tap on initialize() (CGEventTap on macOS, RegisterHotKey on Windows, an X11/Wayland keygrabber on Linux). Each register(...) call returns a Long handle. Listeners run on a Nucleus-owned dispatcher thread — marshal back to your UI thread as needed.

Use DisposableEffect to scope a hotkey to a Composable's lifetime:

DisposableEffect(Unit) {
    GlobalHotKeyManager.initialize()
    val handle = GlobalHotKeyManager.register(
        modifiers = setOf(HotKeyModifier.META),
        keyCode = KeyEvent.VK_K,
    ) { showQuickSwitcher() }

    onDispose {
        GlobalHotKeyManager.unregister(handle)
    }
}

Reference

Modifiers

HotKeyModifier.CTRL, ALT, SHIFT, META. On macOS, META is Command; on Linux/Windows, the Win/Super key.

Media keys

GlobalHotKeyManager.register(MediaKey.Play) { player.togglePlayPause() }
GlobalHotKeyManager.register(MediaKey.Next) { player.skipNext() }

MediaKey covers Play, Pause, Next, Prev, VolumeUp, VolumeDown, Mute.

Capability check

if (!GlobalHotKeyManager.isAvailable) {
    // OS support unavailable (rare; usually a Linux compositor that bans grabs).
}

Conflict detection

register(...) returns 0L if the system rejects the binding (already grabbed by another app or the desktop environment). Always check the return value.

Notes

macOS permissions

The first time you register a hotkey on macOS, the user is prompted to grant Input Monitoring (and sometimes Accessibility) for your app under System Settings → Privacy & Security. Until they accept, callbacks never fire. Surface a friendly prompt and link to x-apple.systempreferences:com.apple.preference.security?Privacy_ListenEvent.

  • Linux Wayland compositors generally disallow global key grabs from arbitrary apps. The library falls back to the org.freedesktop.portal.GlobalShortcuts portal where available; on others, the binding may simply not fire.
  • Call shutdown() on app exit to release the event tap cleanly.