Add URL scheme support for external trigger integration
Register snippingtool:// URL scheme so external tools (Logitech Options, Shortcuts, Stream Deck) can trigger specific capture modes directly. Adds AppDelegate for reliable URL handling in MenuBarExtra-only apps. Supported URLs: snippingtool://fullscreen snippingtool://window snippingtool://region Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -92,6 +92,35 @@ final class AppCoordinator: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - External triggers (URL scheme)
|
||||
|
||||
/// Handles `snippingtool://<mode>` URLs so external tools (Logitech
|
||||
/// Options, Shortcuts, Stream Deck, `open` from the command line, …)
|
||||
/// can trigger a specific capture mode instead of just launching the app.
|
||||
/// Accepts the mode either as the host (`snippingtool://region`) or as
|
||||
/// a query item (`snippingtool://capture?mode=region`).
|
||||
func handle(url: URL) {
|
||||
guard url.scheme?.caseInsensitiveCompare("snippingtool") == .orderedSame else { return }
|
||||
|
||||
let modeString = url.host?.lowercased()
|
||||
?? URLComponents(url: url, resolvingAgainstBaseURL: false)?
|
||||
.queryItems?
|
||||
.first(where: { $0.name.caseInsensitiveCompare("mode") == .orderedSame })?
|
||||
.value?
|
||||
.lowercased()
|
||||
|
||||
switch modeString {
|
||||
case "fullscreen", "full", "full-screen", "screen":
|
||||
capture(mode: .fullScreen)
|
||||
case "window":
|
||||
capture(mode: .window)
|
||||
case "region", "area", "selection":
|
||||
capture(mode: .region)
|
||||
default:
|
||||
NSLog("SnippingTool: unrecognized URL trigger \(url.absoluteString)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Capture
|
||||
|
||||
func capture(mode: CaptureMode) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import AppKit
|
||||
|
||||
/// MenuBarExtra-only apps (no WindowGroup) don't reliably receive SwiftUI's
|
||||
/// `.onOpenURL` scene events — SwiftUI logs "Cannot use Scene methods for
|
||||
/// URL... without using SwiftUI Lifecycle" and the handler never fires.
|
||||
/// The classic AppKit delegate callback works regardless, so URL scheme
|
||||
/// triggers (from Logitech Options, Shortcuts, Stream Deck, `open`, …) are
|
||||
/// routed through here instead.
|
||||
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
let coordinator = AppCoordinator()
|
||||
|
||||
func application(_ application: NSApplication, open urls: [URL]) {
|
||||
urls.forEach { coordinator.handle(url: $0) }
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@ import SwiftUI
|
||||
|
||||
@main
|
||||
struct SnippingToolApp: App {
|
||||
@StateObject private var coordinator = AppCoordinator()
|
||||
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
|
||||
|
||||
var body: some Scene {
|
||||
MenuBarExtra("Snipping Tool", systemImage: "camera.viewfinder") {
|
||||
MenuBarContentView()
|
||||
.environmentObject(coordinator)
|
||||
.environmentObject(appDelegate.coordinator)
|
||||
}
|
||||
.menuBarExtraStyle(.menu)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ targets:
|
||||
CFBundleName: Snipping Tool
|
||||
LSUIElement: true
|
||||
NSHumanReadableCopyright: "Copyright © 2026"
|
||||
CFBundleURLTypes:
|
||||
- CFBundleURLName: it.wende.snippingtool
|
||||
CFBundleURLSchemes:
|
||||
- snippingtool
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: it.wende.snippingtool
|
||||
|
||||
Reference in New Issue
Block a user