Skip to content

naviapps/app-runtime-kit

Repository files navigation

AppRuntimeKit

CI License Swift versions Supported platforms

AppRuntimeKit is a Swift package for building launcher apps, menu bar utilities, automation tools, companion apps, and duplicate-process activation requests with a clean split between provider-neutral runtime values and live AppKit integration.

The package provides two products:

  • AppRuntimeKit for provider-neutral process snapshots, runtime events, and runtime request result values.
  • AppRuntimeKitAppKit for NSWorkspace, NSRunningApplication, URL/file/application opening, and distributed-notification backed runtime operations.

Common Use Cases

  • launcher applications
  • menu bar utilities
  • automation tools
  • companion applications
  • duplicate-process activation requests
  • runtime-aware diagnostics and support tools

Core Types

ApplicationProcessSnapshot

Store immutable process identifiers, bundle identifiers, display names, and termination state without keeping a live process handle.

ApplicationRuntimeEvent

Represent launch, termination, activation, and deactivation notifications with provider-neutral snapshot values.

Runtime Result Values

Use ApplicationRequestResult for compact request outcomes, specialized with ApplicationOpeningError or ApplicationRuntimeRequestError for validated error cases. Use ApplicationTerminationMode to choose graceful or forced termination requests.

ApplicationRuntime

Query, open, activate, terminate, observe, and wait for macOS applications through the AppRuntimeKitAppKit product.

ApplicationDuplicateActivationCoordinator

Coordinate activation requests from duplicate processes with distributed notifications.

Runtime Behavior

Asynchronous open methods require an explicit NSWorkspace.OpenConfiguration so call sites do not become ambiguous with the synchronous open methods.

NSWorkspace and NSRunningApplication backed APIs live in AppRuntimeKitAppKit and are main-actor isolated.

Responsibility Boundary

AppRuntimeKit owns reusable process snapshots, lifecycle events, typed request outcomes, and validation errors. AppRuntimeKitAppKit owns the AppKit-backed runtime adapter and duplicate-process activation request coordination.

Host apps remain responsible for product-specific lifecycle policy, URL/file/application exposure policy, UI, persistence, permissions onboarding, analytics, telemetry, privacy disclosures, and release packaging.

Inputs are validated before use, and result types distinguish invalid input, missing applications, stale process identity, and rejected runtime requests. Process-targeted activation and termination overloads can validate an expected bundle identifier before sending a request. See the API documentation for detailed behavior.

Requirements

  • macOS 14 or later
  • Swift 6.0 or later

Installation

Add this package to your Swift Package dependencies:

.package(url: "https://github.com/naviapps/app-runtime-kit.git", from: "0.2.0")

Then add the product that matches the boundary you need:

.product(name: "AppRuntimeKit", package: "app-runtime-kit"),
.product(name: "AppRuntimeKitAppKit", package: "app-runtime-kit")

Documentation

Practical Example

Open an application only when it is not already running:

import AppRuntimeKit
import AppRuntimeKitAppKit

@MainActor
func openSafariIfNeeded() -> ApplicationRequestResult<ApplicationOpeningError>? {
  let runtime = ApplicationRuntime()
  guard runtime.runningApplications(bundleIdentifier: "com.apple.Safari").isEmpty else {
    return nil
  }
  return runtime.openApplication(bundleIdentifier: "com.apple.Safari")
}

Basic Usage

Use ApplicationProcessSnapshot for explicit process snapshot values: the explicit initializer records the values you pass and leaves live-process validation to runtime adapters.

import AppKit
import AppRuntimeKit
import AppRuntimeKitAppKit

let snapshot = ApplicationProcessSnapshot(
  processIdentifier: 123,
  bundleIdentifier: "com.example.app",
  displayName: "Example"
)

@MainActor
func currentApplicationSnapshot() -> ApplicationProcessSnapshot {
  ApplicationProcessSnapshot(runningApplication: NSRunningApplication.current)
}

Use ApplicationRuntime for live macOS runtime operations:

import AppRuntimeKit
import AppRuntimeKitAppKit

@MainActor
func runningExampleApplications() -> [ApplicationProcessSnapshot] {
  let runtime = ApplicationRuntime()
  return runtime.runningApplications(bundleIdentifier: "com.example.app")
}

@MainActor
func openExampleApplication() -> ApplicationRequestResult<ApplicationOpeningError> {
  let runtime = ApplicationRuntime()
  return runtime.openApplication(bundleIdentifier: "com.example.app")
}

Keep event observation tasks alive while the app should receive events. Cancel the task when the observation is no longer needed.

import AppRuntimeKit
import AppRuntimeKitAppKit

@MainActor
final class RuntimeEventObserver {
  private let runtime = ApplicationRuntime()
  private let handleEvent: @MainActor (ApplicationRuntimeEvent) -> Void
  private var task: Task<Void, Never>?

  init(handleEvent: @escaping @MainActor (ApplicationRuntimeEvent) -> Void) {
    self.handleEvent = handleEvent
  }

  func start() {
    stop()
    let runtime = runtime
    let handleEvent = handleEvent
    task = Task { @MainActor in
      for await event in runtime.events() {
        handleEvent(event)
      }
    }
  }

  func stop() {
    task?.cancel()
    task = nil
  }

  deinit {
    task?.cancel()
  }
}

Keep ApplicationDuplicateActivationCoordinator alive while the app should receive activation requests from duplicate processes. Calling stop() or releasing the coordinator removes the observer.

import AppRuntimeKitAppKit

@MainActor
final class DuplicateActivationCoordinatorStore {
  private var coordinator: ApplicationDuplicateActivationCoordinator?

  func start() throws -> ApplicationDuplicateActivationCoordinator.CoordinationResult {
    stop()
    let coordinator = try ApplicationDuplicateActivationCoordinator(
      bundleIdentifier: "com.example.app"
    )
    let result = coordinator.coordinateActivatingCurrentApplication()
    if result == .unique {
      self.coordinator = coordinator
    }
    return result
  }

  func stop() {
    coordinator?.stop()
    coordinator = nil
  }
}

Development

Run the package check with:

make check

GitHub Actions runs the same check on pull requests and pushes to main.

Contributing

See CONTRIBUTING.md. Release notes are in CHANGELOG.md.

Security

Report vulnerabilities privately. See SECURITY.md.

License

AppRuntimeKit is released under the MIT License. See LICENSE.

About

Reusable Swift package for macOS application lifecycle, process queries, and duplicate-activation coordination.

Topics

Resources

License

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors