Nucleus
Lifecycle

Deep links and URL schemes

Register myapp:// URL handlers across macOS, Windows, and Linux from one Gradle block.

A URL is the universal way for the OS — and other apps — to talk to yours. Email a link, click a Slack message, scan a QR code: it all ends up as myapp://something?id=42 on some user's machine. Nucleus registers the protocol at packaging time and hands you the URI in your main() at runtime.

TL;DR

  • Declare protocols once in nucleus { application { nativeDistributions { protocol(...) } } }.
  • The plugin writes the platform plumbing: LSBundleURLTypes (macOS), HKCU registry entries (Windows), .desktop MimeType (Linux).
  • DeepLinkHandler.setHandler(args, onDeepLink) parses the URI whether it arrives via Apple Events or CLI args.
  • Combine with SingleInstanceManager to forward URIs from a relaunched secondary to the running primary.

Install

Bundled with core-runtime (transitively pulled in by nucleus-application).

Quickstart

1. Declare the URL scheme

nucleus.application {
    nativeDistributions {
        protocol("MyApp", "myapp")
        // protocol(name, vararg schemes) — name shows up in the OS UI,
        // schemes are the URL prefixes (without ://) you want to capture.
    }
}

2. Handle inbound URIs

import dev.nucleusframework.core.runtime.DeepLinkHandler

fun main(args: Array<String>) = nucleusApplication(args) {
    onDeepLink { uri ->
        // uri is a java.net.URI — route on uri.host / uri.path / query params
        navigateTo(uri)
    }

    DecoratedWindow(onCloseRequest = ::exitApplication) { /* ... */ }
}

Without nucleusApplication { }:

fun main(args: Array<String>) {
    DeepLinkHandler.setHandler(args) { uri -> navigateTo(uri) }
    // On macOS, also call:
    DeepLinkHandler.installAwtAppleEventHandler()

    application { /* ... */ }
}

How it works

The three OSes deliver URLs in three different shapes:

OSMechanism
macOSApple Events. Your already-running app receives GetURL events via NSAppleEventManager (or AWT's Desktop.setOpenURIHandler on JBR/OpenJDK). The plugin registers your scheme in Info.plist under CFBundleURLTypes.
WindowsRegistry entry under HKCU\Software\Classes\<scheme>. Clicking a myapp://... URL launches your .exe with the URL as the first CLI argument — a new process.
LinuxA .desktop file with MimeType=x-scheme-handler/myapp; plus xdg-mime association. Activation also passes the URL as a CLI argument.

DeepLinkHandler.setHandler(args, onDeepLink) papers over the difference: on macOS it installs the AWT Apple-event handler so the callback fires while the app is already running, and on Windows / Linux it scans args for URI-shaped entries. Combine with SingleInstanceManager to handle the very common Windows / Linux case where a second .exe is launched just to deliver the URL: the secondary writes the URI to the restore-request file, the primary reads it back.

The plugin handles every static side of this — Info.plist, registry entries, .desktop files. You only describe the protocol once.

Reference

Gradle DSL

nativeDistributions {
    protocol(name: String, vararg schemes: String)
}

DeepLinkHandler

MemberSignatureNotes
setHandler(args, onDeepLink)(Array<String>, (URI) -> Unit) -> UnitWires the handler. Call before application { }.
installAwtAppleEventHandler()() -> UnitmacOS-only; idempotent. nucleusApplication { } calls it for you.
urivar URI?The most recent URI, e.g. the cold-start one.
writeUriTo(path)(Path) -> UnitPersist the current URI to a file — pair with SingleInstanceManager.
deliver(uri)(URI) -> UnitManually inject a URI into the handler chain (useful in tests).
nucleusApplication { onDeepLink { uri -> /* ... */ } }

Shorthand for DeepLinkHandler.setHandler that defers registration until the application is running.

Notes

  • Test in dev with xdg-open myapp://test (Linux), open myapp://test (macOS), start myapp://test (Windows). Jump-list-style entries only work in packaged builds — see Launcher: Windows.
  • The cold-start URI (the one that launched the app) is also stashed in DeepLinkHandler.uri — read it after your handler is registered.
  • For file associations (open .csv with your app), use the plugin's fileAssociation(...) DSL instead.