beatmatchr/Desktop/YabaiPro/Sources/YabaiScriptingAdditionManager.swift
BusyBee3333 7694d965c9 feat: Add structured signal editor with app dropdown and action builder
- 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
2025-12-31 01:44:13 -05:00

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'."
}
}
}