- Add AppDiscovery provider for running app enumeration - Implement AppDropdownView with auto-launch functionality - Create SignalAction models for 40+ yabai commands - Build ActionBuilderView with nested parameter controls - Add LiveShellPreview for real-time shell command generation - Implement ActionValidator for conflict detection - Add migration parser for existing raw action strings - Include feature flag for safe rollout - Maintain full backward compatibility
137 lines
5.0 KiB
Swift
137 lines
5.0 KiB
Swift
//
|
|
// YabaiScriptingAdditionManager.swift
|
|
// YabaiPro
|
|
//
|
|
// Created by Jake Shore
|
|
// Copyright © 2024 Jake Shore. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
class YabaiScriptingAdditionManager {
|
|
static let shared = YabaiScriptingAdditionManager()
|
|
|
|
private let commandRunner = YabaiCommandRunner()
|
|
|
|
// Check if yabai scripting addition is loaded by testing a command that requires it
|
|
func isScriptingAdditionLoaded() async -> Bool {
|
|
do {
|
|
// Test with window_opacity command - this requires scripting addition
|
|
// Use timeout to avoid hanging if yabai is unresponsive
|
|
try await commandRunner.run(command: "timeout 5 yabai -m config window_opacity on")
|
|
return true
|
|
} catch {
|
|
// If it fails, scripting addition is likely not loaded
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Load the yabai scripting addition (requires sudo)
|
|
func loadScriptingAddition() async throws {
|
|
print("YabaiScriptingAdditionManager: Loading scripting addition...")
|
|
|
|
// Try multiple approaches for loading the scripting addition
|
|
let loadCommands = [
|
|
// Method 1: Use osascript with clear prompt (most user-friendly)
|
|
"""
|
|
osascript -e '
|
|
tell application "Terminal"
|
|
activate
|
|
do script "echo Loading yabai scripting addition... && sudo yabai --load-sa"
|
|
end tell'
|
|
""",
|
|
|
|
// Method 2: Direct osascript with admin privileges
|
|
"""
|
|
osascript -e 'do shell script "yabai --load-sa" with administrator privileges'
|
|
""",
|
|
|
|
// Method 3: Manual instruction (fallback)
|
|
"echo 'Please run this manually in Terminal: sudo yabai --load-sa'"
|
|
]
|
|
|
|
var lastError: Error?
|
|
|
|
for (index, command) in loadCommands.enumerated() {
|
|
do {
|
|
print("YabaiScriptingAdditionManager: Trying loading method \(index + 1)")
|
|
try await commandRunner.run(command: command)
|
|
|
|
// Give it time to load
|
|
try await Task.sleep(nanoseconds: 3_000_000_000) // 3 seconds
|
|
|
|
// Check if it worked
|
|
if await isScriptingAdditionLoaded() {
|
|
print("YabaiScriptingAdditionManager: Scripting addition loaded successfully using method \(index + 1)")
|
|
return
|
|
} else {
|
|
print("YabaiScriptingAdditionManager: Method \(index + 1) completed but verification failed")
|
|
}
|
|
} catch {
|
|
print("YabaiScriptingAdditionManager: Method \(index + 1) failed: \(error)")
|
|
lastError = error
|
|
}
|
|
}
|
|
|
|
// If all methods failed, provide clear error
|
|
throw ScriptingAdditionError.loadFailed("All loading methods failed. Last error: \(lastError?.localizedDescription ?? "Unknown")")
|
|
}
|
|
|
|
// Ensure scripting addition is loaded, loading it if necessary
|
|
func ensureScriptingAdditionLoaded() async throws {
|
|
if await isScriptingAdditionLoaded() {
|
|
print("YabaiScriptingAdditionManager: Scripting addition already loaded")
|
|
return
|
|
}
|
|
|
|
print("YabaiScriptingAdditionManager: Scripting addition not loaded, attempting to load...")
|
|
try await loadScriptingAddition()
|
|
|
|
// Final verification
|
|
if await isScriptingAdditionLoaded() {
|
|
print("YabaiScriptingAdditionManager: Verification successful - scripting addition is now loaded")
|
|
} else {
|
|
throw ScriptingAdditionError.verificationFailed
|
|
}
|
|
}
|
|
|
|
// Check if SIP is disabled (required for scripting addition)
|
|
func isSIPDisabled() async -> Bool {
|
|
do {
|
|
// Check SIP status using csrutil
|
|
try await commandRunner.run(command: "csrutil status | grep -q 'disabled'")
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
enum ScriptingAdditionError: Error, LocalizedError {
|
|
case loadFailed(String)
|
|
case verificationFailed
|
|
case sipEnabled
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .loadFailed(let details):
|
|
return "Failed to load yabai scripting addition: \(details)"
|
|
case .verificationFailed:
|
|
return "Scripting addition loaded but verification failed. Try restarting yabai."
|
|
case .sipEnabled:
|
|
return "SIP must be disabled to use window appearance features. Run 'csrutil disable' in Recovery Mode."
|
|
}
|
|
}
|
|
|
|
var recoverySuggestion: String? {
|
|
switch self {
|
|
case .loadFailed:
|
|
return "Try running 'sudo yabai --load-sa' manually in Terminal, then restart YabaiPro."
|
|
case .verificationFailed:
|
|
return "Restart yabai with 'brew services restart yabai', then restart YabaiPro."
|
|
case .sipEnabled:
|
|
return "Boot into Recovery Mode (Command+R), run 'csrutil disable', restart, then run 'sudo yabai --load-sa'."
|
|
}
|
|
}
|
|
}
|