Skip to content

Instantly share code, notes, and snippets.

@alin23
Created May 22, 2026 13:00
Show Gist options
  • Select an option

  • Save alin23/69c0b52b03ab68e3b238ff4ef0f6fde7 to your computer and use it in GitHub Desktop.

Select an option

Save alin23/69c0b52b03ab68e3b238ff4ef0f6fde7 to your computer and use it in GitHub Desktop.
//
// KeylumeIntegration.swift
//
// Drop-in helper for integrating Keylume (https://lowtechguys.com/keylume)
// into a macOS Swift app. Handles a headless install/uninstall/reinstall
// flow, install + running-state detection, and posting the show / hide
// distributed notifications to drive a temporary keyboard layer.
//
// Single-file: no external dependencies beyond SwiftUI + AppKit, so this
// file can be copied into any macOS app target as-is.
//
// Usage:
//
// // Show a temporary keyboard with two keys bound to apps:
// KeylumeIntegration.shared.showKeyboard(bindings: [
// "q": .app(bundleID: "com.apple.Safari"),
// "w": .symbolText(symbol: "magnifyingglass", text: "Search"),
// ])
//
// // Hide it later:
// KeylumeIntegration.shared.hideKeyboard()
//
// // Render a Settings pane:
// var body: some View {
// KeylumeSettingsForm()
// }
//
import AppKit
import Combine
import SwiftUI
// MARK: - Constants
public enum Keylume {
public static let bundleID = "com.lowtechguys.Keylume"
public static let appPath = "/Applications/Keylume.app"
public static let downloadURL = URL(string: "https://files.lowtechguys.com/releases/Keylume.zip")!
/// `UserDefaults` keys used by Keylume itself. Set on the
/// `com.lowtechguys.Keylume` suite before first launch to opt the
/// companion into a fully background, no-UI install.
enum HeadlessKey {
static let menuBarIconVisible = "menuBarIconVisible"
static let layersEnabled = "layersEnabled"
static let adaptToKeyboardLayout = "adaptToKeyboardLayout"
static let showOnScreenRecording = "showOnScreenRecording"
static let enableGlobalHotkey = "enableGlobalHotkey"
static let launchAtLogin = "launchAtLogin"
static let passedProFeaturesOnboarding = "passedProFeaturesOnboarding"
static let onboardingVersion = "onboardingVersion"
static let sparkleEnableAutomaticChecks = "SUEnableAutomaticChecks"
static let sparkleAutomaticallyUpdate = "SUAutomaticallyUpdate"
static let sparkleHasLaunchedBefore = "SUHasLaunchedBefore"
static let sparkleScheduledCheckInterval = "SUScheduledCheckInterval"
}
}
public extension Notification.Name {
static let keylumeShow = Notification.Name("com.lowtechguys.Keylume.show")
static let keylumeHide = Notification.Name("com.lowtechguys.Keylume.hide")
}
// MARK: - Public types
public enum KeylumeInstallState: Equatable {
case notInstalled
case downloading(progress: Double)
case extracting
case configuring
case installed
case failed(String)
}
public enum KeylumeFillMode: String, CaseIterable, Codable {
case visible
case dim
case blank
public var label: String {
switch self {
case .visible: "Visible"
case .dim: "Dim"
case .blank: "Blank"
}
}
}
public enum KeylumePosition: String, CaseIterable {
case bottom, top, left, right, center
case topLeft, topRight, bottomLeft, bottomRight
}
/// Value bound to a Keylume keyboard key. Encodes to the value format the
/// `keylume://show` URL scheme accepts. See Keylume's REMOTE_CONTROL.md.
public enum KeylumeBinding {
case app(bundleID: String)
case symbol(name: String)
case text(String)
case symbolText(symbol: String, text: String)
case raw(String)
var encoded: String {
switch self {
case let .app(bundleID): "a:\(bundleID)"
case let .symbol(name): "s:\(name)"
case let .text(text): text
case let .symbolText(symbol, text): "symtext:\(symbol)|\(text)"
case let .raw(value): value
}
}
}
// MARK: - Manager
@MainActor
public final class KeylumeIntegration: ObservableObject {
private init() {
installState = Self.isInstalledOnDisk() ? .installed : .notInstalled
running = !NSRunningApplication.runningApplications(withBundleIdentifier: Keylume.bundleID).isEmpty
fillMode = UserDefaults.standard.string(forKey: "keylume.fillMode")
.flatMap(KeylumeFillMode.init(rawValue:)) ?? .dim
observeWorkspace()
}
deinit {
let observers = workspaceObservers
let nc = NSWorkspace.shared.notificationCenter
for o in observers {
nc.removeObserver(o)
}
}
public static let shared = KeylumeIntegration()
@Published public private(set) var installState: KeylumeInstallState = .notInstalled
@Published public private(set) var running = false
/// Persisted user preference for how Keylume renders keys without a
/// binding. The settings Form reads/writes this; pass it through to
/// `showKeyboard(fillMode:)` if you want the setting to apply.
@Published public var fillMode: KeylumeFillMode {
didSet {
UserDefaults.standard.set(fillMode.rawValue, forKey: "keylume.fillMode")
}
}
// MARK: Detection
public static func isInstalledOnDisk() -> Bool {
FileManager.default.fileExists(atPath: Keylume.appPath)
}
public func isInstalled() -> Bool { Self.isInstalledOnDisk() }
public func isRunning() -> Bool {
!NSRunningApplication.runningApplications(withBundleIdentifier: Keylume.bundleID).isEmpty
}
// MARK: Show / hide
/// Show a temporary Keylume keyboard with the given key bindings.
/// Bindings clear automatically when the keyboard hides.
public func showKeyboard(
bindings: [String: KeylumeBinding],
position: KeylumePosition = .bottom,
offset: CGPoint = CGPoint(x: 0, y: 100),
width: Double = 1000,
fillMode: KeylumeFillMode? = nil
) {
guard isInstalled() else { return }
var userInfo: [String: String] = [:]
for (key, value) in bindings {
userInfo[key] = value.encoded
}
userInfo["position"] = position.rawValue
userInfo["offset"] = "\(Int(offset.x)):\(Int(offset.y))"
userInfo["width"] = String(Int(width))
userInfo["fill"] = (fillMode ?? self.fillMode).rawValue
// Apply the fill effect to modifier keys (shift, ctrl, option, cmd,
// fn) too, so an `app-launcher`-style layer doesn't leave a noisy
// row of full-opacity modifier glyphs around the dimmed letters.
userInfo["dimBlankModifierKeys"] = "1"
DistributedNotificationCenter.default().postNotificationName(
.keylumeShow, object: nil, userInfo: userInfo, deliverImmediately: true
)
}
public func hideKeyboard() {
DistributedNotificationCenter.default().postNotificationName(
.keylumeHide, object: nil, userInfo: nil, deliverImmediately: true
)
}
// MARK: Install
/// Download Keylume.zip, extract to /Applications, write headless
/// preferences so the companion runs invisibly, and launch it. Safe to
/// call when Keylume is already installed and/or running, quits and
/// replaces in place.
public func installHeadless() {
guard !isCurrentlyInstalling else { return }
installState = .downloading(progress: 0)
let dest = URL(fileURLWithPath: Keylume.appPath)
let tmpDir = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent("keylume-install-\(UUID().uuidString)")
try? FileManager.default.createDirectory(at: tmpDir, withIntermediateDirectories: true)
let zipPath = tmpDir.appendingPathComponent("Keylume.zip")
let task = URLSession.shared.downloadTask(with: Keylume.downloadURL) { [weak self] url, _, error in
guard let self else { return }
if let error {
Task { @MainActor in self.installState = .failed("Download failed: \(error.localizedDescription)") }
return
}
guard let url else {
Task { @MainActor in self.installState = .failed("Download failed: no file") }
return
}
do {
if FileManager.default.fileExists(atPath: zipPath.path) {
try FileManager.default.removeItem(at: zipPath)
}
try FileManager.default.moveItem(at: url, to: zipPath)
} catch {
Task { @MainActor in self.installState = .failed("Move failed: \(error.localizedDescription)") }
return
}
Task { @MainActor in
self.installState = .extracting
self.unzipAndInstall(zipPath: zipPath, destination: dest, scratchDir: tmpDir)
}
}
progressObservation = task.progress.observe(\.fractionCompleted) { [weak self] progress, _ in
Task { @MainActor in
guard let self else { return }
if case .downloading = self.installState {
self.installState = .downloading(progress: progress.fractionCompleted)
}
}
}
task.resume()
}
/// Quit any running Keylume, delete the bundle, and clear the running
/// state. Leaves the user's Keylume preferences in place so a
/// subsequent install keeps their themes and layers.
public func uninstall() {
quitRunningKeylume()
try? FileManager.default.removeItem(atPath: Keylume.appPath)
installState = .notInstalled
running = false
}
private var progressObservation: NSKeyValueObservation?
private var workspaceObservers: [NSObjectProtocol] = []
private var isCurrentlyInstalling: Bool {
switch installState {
case .downloading, .extracting, .configuring: true
default: false
}
}
private func observeWorkspace() {
let center = NSWorkspace.shared.notificationCenter
let launch = center.addObserver(forName: NSWorkspace.didLaunchApplicationNotification, object: nil, queue: .main) { [weak self] note in
guard let app = note.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication,
app.bundleIdentifier == Keylume.bundleID else { return }
Task { @MainActor in self?.running = true }
}
let terminate = center.addObserver(forName: NSWorkspace.didTerminateApplicationNotification, object: nil, queue: .main) { [weak self] note in
guard let app = note.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication,
app.bundleIdentifier == Keylume.bundleID else { return }
Task { @MainActor in self?.running = self?.isRunning() ?? false }
}
workspaceObservers = [launch, terminate]
}
private func unzipAndInstall(zipPath: URL, destination: URL, scratchDir: URL) {
// `ditto -xk` preserves Apple metadata + code signatures so
// NSWorkspace.openApplication can launch the freshly extracted .app
// without Gatekeeper rejecting it.
let unzip = Process()
unzip.executableURL = URL(fileURLWithPath: "/usr/bin/ditto")
unzip.arguments = ["-xk", zipPath.path, scratchDir.path]
do {
try unzip.run()
unzip.waitUntilExit()
} catch {
installState = .failed("Unzip failed: \(error.localizedDescription)")
return
}
guard unzip.terminationStatus == 0 else {
installState = .failed("Unzip failed (exit \(unzip.terminationStatus))")
return
}
let extractedApp = scratchDir.appendingPathComponent("Keylume.app")
guard FileManager.default.fileExists(atPath: extractedApp.path) else {
installState = .failed("Keylume.app not found in archive")
return
}
// Replace in place: a running Keylume holds open file handles inside
// the bundle, which silently corrupts the replace if we don't quit
// first.
quitRunningKeylume()
if FileManager.default.fileExists(atPath: destination.path) {
try? FileManager.default.removeItem(at: destination)
}
do {
try FileManager.default.moveItem(at: extractedApp, to: destination)
} catch {
installState = .failed("Install to /Applications failed: \(error.localizedDescription)")
return
}
try? FileManager.default.removeItem(at: scratchDir)
installState = .configuring
writeHeadlessPreferences()
launchKeylume()
// Give Keylume a moment to come up before the running observer fires.
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self] in
guard let self else { return }
installState = .installed
}
}
private func writeHeadlessPreferences() {
guard let defaults = UserDefaults(suiteName: Keylume.bundleID) else { return }
defaults.set(false, forKey: Keylume.HeadlessKey.menuBarIconVisible)
defaults.set(false, forKey: Keylume.HeadlessKey.layersEnabled)
defaults.set(false, forKey: Keylume.HeadlessKey.adaptToKeyboardLayout)
defaults.set(false, forKey: Keylume.HeadlessKey.showOnScreenRecording)
defaults.set(false, forKey: Keylume.HeadlessKey.enableGlobalHotkey)
defaults.set(true, forKey: Keylume.HeadlessKey.launchAtLogin)
// Skip onboarding (current Keylume version is 2; 100 future-proofs).
defaults.set(true, forKey: Keylume.HeadlessKey.passedProFeaturesOnboarding)
defaults.set(100, forKey: Keylume.HeadlessKey.onboardingVersion)
// Sparkle: automatic + silent updates.
defaults.set(true, forKey: Keylume.HeadlessKey.sparkleEnableAutomaticChecks)
defaults.set(true, forKey: Keylume.HeadlessKey.sparkleAutomaticallyUpdate)
defaults.set(true, forKey: Keylume.HeadlessKey.sparkleHasLaunchedBefore)
defaults.set(3600, forKey: Keylume.HeadlessKey.sparkleScheduledCheckInterval)
defaults.synchronize()
}
private func launchKeylume() {
let url = URL(fileURLWithPath: Keylume.appPath)
let config = NSWorkspace.OpenConfiguration()
config.activates = false
config.addsToRecentItems = false
NSWorkspace.shared.openApplication(at: url, configuration: config) { _, _ in }
}
private func quitRunningKeylume() {
let running = NSRunningApplication.runningApplications(withBundleIdentifier: Keylume.bundleID)
guard !running.isEmpty else { return }
for app in running {
app.terminate()
}
let deadline = Date().addingTimeInterval(2.0)
while Date() < deadline,
!NSRunningApplication.runningApplications(withBundleIdentifier: Keylume.bundleID).isEmpty
{
Thread.sleep(forTimeInterval: 0.1)
}
for app in NSRunningApplication.runningApplications(withBundleIdentifier: Keylume.bundleID) {
app.forceTerminate()
}
}
}
// MARK: - SwiftUI Settings Form
/// A modestly-styled grouped `Form` with the install row, fill-mode picker,
/// and (optionally) any additional content the host app supplies. Drop in
/// as the body of a Settings scene tab.
public struct KeylumeSettingsForm<Extra: View>: View {
public init(
title: String = "Keylume integration",
subtitle: String = "On-screen keyboard companion for app launchers and shortcut overlays.",
@ViewBuilder extra: @escaping () -> Extra = { EmptyView() }
) {
self.title = title
self.subtitle = subtitle
extraContent = extra
}
public var body: some View {
Form {
Section {
installRow
} header: {
VStack(alignment: .leading, spacing: 4) {
Text(title).font(.system(size: 16, weight: .bold))
Text(subtitle)
.font(.system(size: 11))
.foregroundColor(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
.padding(.bottom, 12)
}
Section {
HStack(spacing: 10) {
VStack(alignment: .leading, spacing: 2) {
Text("Unassigned keys").font(.system(size: 13, weight: .medium))
Text("How keys without a binding appear on the Keylume keyboard.")
.font(.system(size: 10))
.foregroundColor(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
.frame(maxWidth: .infinity, alignment: .leading)
Picker("", selection: $integration.fillMode) {
ForEach(KeylumeFillMode.allCases, id: \.self) { mode in
Text(mode.label).tag(mode)
}
}
.labelsHidden()
.pickerStyle(.segmented)
.fixedSize()
}
.disabled(!isInstalled)
.opacity(isInstalled ? 1 : 0.5)
extraContent()
} header: {
Text("Behavior")
}
}
.formStyle(.grouped)
.scrollContentBackground(.hidden)
}
@ObservedObject private var integration = KeylumeIntegration.shared
private let title: String
private let subtitle: String
private let extraContent: () -> Extra
private var isInstalled: Bool {
if case .installed = integration.installState { return true }
return false
}
private var statusDotColor: Color {
if !isInstalled { return .red }
return integration.running ? .green : .orange
}
private var statusDotTooltip: String {
if !isInstalled { return "Keylume not installed" }
return integration.running ? "Keylume installed and running" : "Keylume installed but not running"
}
private var installTitle: String {
switch integration.installState {
case .notInstalled: "Install Keylume"
case .downloading: "Downloading Keylume…"
case .extracting: "Extracting…"
case .configuring: "Configuring headless mode…"
case .installed: integration.running ? "Keylume running" : "Keylume installed"
case .failed: "Installation failed"
}
}
private var installSubtitle: String {
switch integration.installState {
case .notInstalled:
"Downloads Keylume.app to /Applications and configures it for silent background use."
case let .downloading(progress):
String(format: "%.0f%% downloaded", progress * 100)
case .extracting:
"Unpacking the archive into /Applications."
case .configuring:
"Writing headless preferences and launching the companion."
case .installed:
integration.running
? "Installed at \(Keylume.appPath)."
: "Installed at \(Keylume.appPath) but not running."
case let .failed(reason):
reason
}
}
@ViewBuilder
private var installRow: some View {
HStack(alignment: .center, spacing: 12) {
Circle()
.fill(statusDotColor)
.frame(width: 10, height: 10)
.help(statusDotTooltip)
VStack(alignment: .leading, spacing: 2) {
Text(installTitle).font(.system(size: 13, weight: .medium))
Text(installSubtitle)
.font(.system(size: 10))
.foregroundColor(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
Spacer()
installButton
}
}
@ViewBuilder
private var installButton: some View {
switch integration.installState {
case .notInstalled, .failed:
Button("Install") { integration.installHeadless() }
case .downloading, .extracting, .configuring:
ProgressView().controlSize(.small)
case .installed:
HStack(spacing: 8) {
Button("Reinstall") { integration.installHeadless() }
.buttonStyle(.bordered)
Button("Uninstall") { integration.uninstall() }
.buttonStyle(.bordered)
.tint(.red)
}
}
}
}
#if DEBUG
#Preview("Keylume Settings Form") {
KeylumeSettingsForm {
Text("Sample additional settings content")
.font(.system(size: 12))
.foregroundColor(.secondary)
}
.frame(width: 620)
.padding()
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment