diff --git a/AS1/AS1.xcodeproj/project.pbxproj b/AS1/AS1.xcodeproj/project.pbxproj new file mode 100644 index 00000000..75c85182 --- /dev/null +++ b/AS1/AS1.xcodeproj/project.pbxproj @@ -0,0 +1,326 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXFileReference section */ + 63C4A66B2E41AB5C00467C2C /* AS1.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AS1.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 63C4A66D2E41AB5C00467C2C /* AS1 */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = AS1; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 63C4A6682E41AB5C00467C2C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 63C4A6622E41AB5B00467C2C = { + isa = PBXGroup; + children = ( + 63C4A66D2E41AB5C00467C2C /* AS1 */, + 63C4A66C2E41AB5C00467C2C /* Products */, + ); + sourceTree = ""; + }; + 63C4A66C2E41AB5C00467C2C /* Products */ = { + isa = PBXGroup; + children = ( + 63C4A66B2E41AB5C00467C2C /* AS1.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 63C4A66A2E41AB5C00467C2C /* AS1 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 63C4A6772E41AB5D00467C2C /* Build configuration list for PBXNativeTarget "AS1" */; + buildPhases = ( + 63C4A6672E41AB5C00467C2C /* Sources */, + 63C4A6682E41AB5C00467C2C /* Frameworks */, + 63C4A6692E41AB5C00467C2C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 63C4A66D2E41AB5C00467C2C /* AS1 */, + ); + name = AS1; + packageProductDependencies = ( + ); + productName = AS1; + productReference = 63C4A66B2E41AB5C00467C2C /* AS1.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 63C4A6632E41AB5B00467C2C /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1640; + LastUpgradeCheck = 1640; + TargetAttributes = { + 63C4A66A2E41AB5C00467C2C = { + CreatedOnToolsVersion = 16.4; + }; + }; + }; + buildConfigurationList = 63C4A6662E41AB5B00467C2C /* Build configuration list for PBXProject "AS1" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 63C4A6622E41AB5B00467C2C; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 63C4A66C2E41AB5C00467C2C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 63C4A66A2E41AB5C00467C2C /* AS1 */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 63C4A6692E41AB5C00467C2C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 63C4A6672E41AB5C00467C2C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 63C4A6752E41AB5D00467C2C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 8WK25CU37Q; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.5; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 63C4A6762E41AB5D00467C2C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 8WK25CU37Q; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + 63C4A6782E41AB5D00467C2C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = AS1/AS1.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8WK25CU37Q; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.rzsoftware.AS1; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 63C4A6792E41AB5D00467C2C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = AS1/AS1.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8WK25CU37Q; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.rzsoftware.AS1; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 63C4A6662E41AB5B00467C2C /* Build configuration list for PBXProject "AS1" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 63C4A6752E41AB5D00467C2C /* Debug */, + 63C4A6762E41AB5D00467C2C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 63C4A6772E41AB5D00467C2C /* Build configuration list for PBXNativeTarget "AS1" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 63C4A6782E41AB5D00467C2C /* Debug */, + 63C4A6792E41AB5D00467C2C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 63C4A6632E41AB5B00467C2C /* Project object */; +} diff --git a/AS1/AS1.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/AS1/AS1.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/AS1/AS1.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/AS1/AS1/AS1.entitlements b/AS1/AS1/AS1.entitlements new file mode 100644 index 00000000..18aff0ce --- /dev/null +++ b/AS1/AS1/AS1.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/AS1/AS1/AS1App.swift b/AS1/AS1/AS1App.swift new file mode 100644 index 00000000..04760027 --- /dev/null +++ b/AS1/AS1/AS1App.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct AS1App: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/AS1/AS1/Assets.xcassets/AccentColor.colorset/Contents.json b/AS1/AS1/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/AS1/AS1/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AS1/AS1/Assets.xcassets/AppIcon.appiconset/Contents.json b/AS1/AS1/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..3f00db43 --- /dev/null +++ b/AS1/AS1/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AS1/AS1/Assets.xcassets/Contents.json b/AS1/AS1/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/AS1/AS1/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AS1/AS1/ContentView.swift b/AS1/AS1/ContentView.swift new file mode 100644 index 00000000..b000a7e4 --- /dev/null +++ b/AS1/AS1/ContentView.swift @@ -0,0 +1,17 @@ +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/AS2/AS2.xcodeproj/project.pbxproj b/AS2/AS2.xcodeproj/project.pbxproj new file mode 100644 index 00000000..1a018415 --- /dev/null +++ b/AS2/AS2.xcodeproj/project.pbxproj @@ -0,0 +1,326 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXFileReference section */ + 63652E162E651D9000A5256C /* AS2.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AS2.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 63652E182E651D9000A5256C /* AS2 */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = AS2; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 63652E132E651D9000A5256C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 63652E0D2E651D9000A5256C = { + isa = PBXGroup; + children = ( + 63652E182E651D9000A5256C /* AS2 */, + 63652E172E651D9000A5256C /* Products */, + ); + sourceTree = ""; + }; + 63652E172E651D9000A5256C /* Products */ = { + isa = PBXGroup; + children = ( + 63652E162E651D9000A5256C /* AS2.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 63652E152E651D9000A5256C /* AS2 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 63652E222E651D9200A5256C /* Build configuration list for PBXNativeTarget "AS2" */; + buildPhases = ( + 63652E122E651D9000A5256C /* Sources */, + 63652E132E651D9000A5256C /* Frameworks */, + 63652E142E651D9000A5256C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 63652E182E651D9000A5256C /* AS2 */, + ); + name = AS2; + packageProductDependencies = ( + ); + productName = AS2; + productReference = 63652E162E651D9000A5256C /* AS2.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 63652E0E2E651D9000A5256C /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1640; + LastUpgradeCheck = 1640; + TargetAttributes = { + 63652E152E651D9000A5256C = { + CreatedOnToolsVersion = 16.4; + }; + }; + }; + buildConfigurationList = 63652E112E651D9000A5256C /* Build configuration list for PBXProject "AS2" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 63652E0D2E651D9000A5256C; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 63652E172E651D9000A5256C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 63652E152E651D9000A5256C /* AS2 */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 63652E142E651D9000A5256C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 63652E122E651D9000A5256C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 63652E202E651D9200A5256C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 8WK25CU37Q; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.5; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 63652E212E651D9200A5256C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 8WK25CU37Q; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + 63652E232E651D9200A5256C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = AS2/AS2.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8WK25CU37Q; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.rzsoftware.AS2; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 63652E242E651D9200A5256C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = AS2/AS2.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8WK25CU37Q; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.rzsoftware.AS2; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 63652E112E651D9000A5256C /* Build configuration list for PBXProject "AS2" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 63652E202E651D9200A5256C /* Debug */, + 63652E212E651D9200A5256C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 63652E222E651D9200A5256C /* Build configuration list for PBXNativeTarget "AS2" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 63652E232E651D9200A5256C /* Debug */, + 63652E242E651D9200A5256C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 63652E0E2E651D9000A5256C /* Project object */; +} diff --git a/AS2/AS2.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/AS2/AS2.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/AS2/AS2.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/AS2/AS2/AS2.entitlements b/AS2/AS2/AS2.entitlements new file mode 100644 index 00000000..e89b7f32 --- /dev/null +++ b/AS2/AS2/AS2.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/AS2/AS2/AS2App.swift b/AS2/AS2/AS2App.swift new file mode 100644 index 00000000..c6e875f4 --- /dev/null +++ b/AS2/AS2/AS2App.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct AS2App: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/AS2/AS2/Assets.xcassets/AccentColor.colorset/Contents.json b/AS2/AS2/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/AS2/AS2/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AS2/AS2/Assets.xcassets/AppIcon.appiconset/Contents.json b/AS2/AS2/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..3f00db43 --- /dev/null +++ b/AS2/AS2/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AS2/AS2/Assets.xcassets/Contents.json b/AS2/AS2/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/AS2/AS2/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AS2/AS2/ContentView.swift b/AS2/AS2/ContentView.swift new file mode 100644 index 00000000..4a99a8b1 --- /dev/null +++ b/AS2/AS2/ContentView.swift @@ -0,0 +1,114 @@ +import SwiftUI + +struct ContentView: View { + @StateObject private var xcodeMonitor = XcodeMonitor() + + // Generic app monitor for multi-editor support (future use) + // @StateObject private var appMonitor = AppMonitor(strategies: [XcodeStrategy(), VSCodeStrategy()]) + + var body: some View { + VStack(spacing: 20) { + Text("🔍 Xcode Monitor") + .font(.title) + + Group { + Text("Xcode Instances: \(xcodeMonitor.xcodeInstances.count)") + + HStack { + Text("Accessibility:") + Text(xcodeMonitor.accessibilityPermissionGranted ? "✅ Granted" : "❌ Not Granted") + .foregroundColor(xcodeMonitor.accessibilityPermissionGranted ? .green : .red) + } + + if let activeXcode = xcodeMonitor.activeXcode { + Text("✅ Active Xcode: PID \(activeXcode.processIdentifier)") + .foregroundColor(.green) + } else if let lastActive = xcodeMonitor.lastActiveXcode { + Text("🔄 Last Active: PID \(lastActive.processIdentifier)") + .foregroundColor(.orange) + } else { + Text("❌ No Xcode Found") + .foregroundColor(.red) + } + + Button("🔄 Refresh State") { + xcodeMonitor.refreshActiveState() + } + .buttonStyle(.bordered) + } + .font(.headline) + + // Phase 2: File and workspace info + if xcodeMonitor.accessibilityPermissionGranted { + Divider() + + VStack(spacing: 10) { + Text("📄 Current File & Workspace") + .font(.title2) + .bold() + + Group { + if let docURL = xcodeMonitor.currentDocumentURL { + HStack { + Text("📄 Document:") + Text(docURL.lastPathComponent) + .foregroundColor(.blue) + .font(.monospaced(.caption)()) + } + } else { + Text("📄 No document detected") + .foregroundColor(.secondary) + } + + if let workspaceURL = xcodeMonitor.currentWorkspaceURL { + HStack { + Text("📦 Workspace:") + Text(workspaceURL.lastPathComponent) + .foregroundColor(.green) + .font(.monospaced(.caption)()) + } + } else { + Text("📦 No workspace detected") + .foregroundColor(.secondary) + } + + if let projectURL = xcodeMonitor.currentProjectURL { + HStack { + Text("🏗️ Project:") + Text(projectURL.lastPathComponent) + .foregroundColor(.purple) + .font(.monospaced(.caption)()) + } + } else { + Text("🏗️ No project detected") + .foregroundColor(.secondary) + } + } + .font(.caption) + } + } + + if !xcodeMonitor.xcodeInstances.isEmpty { + Text("All Xcode Instances:") + .font(.subheadline) + + ForEach(xcodeMonitor.xcodeInstances, id: \.processIdentifier) { xcode in + HStack { + Text("PID: \(xcode.processIdentifier)") + if xcodeMonitor.activeXcode?.processIdentifier == xcode.processIdentifier { + Text("🟢 Active") + } else { + Text("⚪ Background") + } + } + .font(.caption) + } + } + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/AS2/AS2/Core/AppInstance.swift b/AS2/AS2/Core/AppInstance.swift new file mode 100644 index 00000000..d5f134fe --- /dev/null +++ b/AS2/AS2/Core/AppInstance.swift @@ -0,0 +1,16 @@ +import Cocoa +import Foundation + +struct AppInstance { + let app: NSRunningApplication + let strategy: EditorStrategy + var windowInspector: WindowInspector? + + var displayName: String { + strategy.displayName + } + + var processId: pid_t { + app.processIdentifier + } +} \ No newline at end of file diff --git a/AS2/AS2/Core/AppMonitor.swift b/AS2/AS2/Core/AppMonitor.swift new file mode 100644 index 00000000..b795bfeb --- /dev/null +++ b/AS2/AS2/Core/AppMonitor.swift @@ -0,0 +1,215 @@ +import Cocoa +import Foundation +import ApplicationServices + +@MainActor +class AppMonitor: ObservableObject { + @Published var monitoredApps: [String: AppInstance] = [:] + @Published var activeApp: AppInstance? + @Published var accessibilityPermissionGranted: Bool = false + + private let strategies: [String: EditorStrategy] + private let workspace = NSWorkspace.shared + + init(strategies: [EditorStrategy]) { + self.strategies = Dictionary(uniqueKeysWithValues: strategies.map { ($0.bundleIdentifier, $0) }) + + checkAccessibilityPermission() + setupMonitoring() + findExistingApps() + startPeriodicCheck() + } + + private func startPeriodicCheck() { + Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { [weak self] _ in + Task { @MainActor in + self?.refreshActiveState() + } + } + } + + @MainActor + public func refreshActiveState() { + let frontmostApp = workspace.frontmostApplication + let currentlyActivePID = frontmostApp?.processIdentifier ?? -1 + + print("🔄 Periodic check - Frontmost: \(frontmostApp?.localizedName ?? "None") (PID: \(currentlyActivePID))") + + let shouldBeActive = monitoredApps.values.first { instance in + instance.processId == currentlyActivePID + } + + if shouldBeActive?.processId != activeApp?.processId { + print("🔄 State correction needed!") + print(" - Should be active: \(shouldBeActive?.processId ?? -1)") + print(" - Currently tracked as active: \(activeApp?.processId ?? -1)") + + activeApp = shouldBeActive + + if let newActive = shouldBeActive, accessibilityPermissionGranted { + monitorAppWindows(for: newActive) + } + } else if let currentActive = shouldBeActive, accessibilityPermissionGranted { + monitorAppWindows(for: currentActive) + } + } + + private func setupMonitoring() { + NotificationCenter.default.addObserver( + forName: NSWorkspace.didActivateApplicationNotification, + object: nil, + queue: .main + ) { [weak self] notification in + Task { @MainActor in + self?.handleApplicationActivated(notification) + } + } + + NotificationCenter.default.addObserver( + forName: NSWorkspace.didTerminateApplicationNotification, + object: nil, + queue: .main + ) { [weak self] notification in + Task { @MainActor in + self?.handleApplicationTerminated(notification) + } + } + } + + private func checkAccessibilityPermission() { + accessibilityPermissionGranted = AXIsProcessTrusted() + print("🔐 Accessibility Permission: \(accessibilityPermissionGranted ? "✅ Granted" : "❌ Not Granted")") + + if !accessibilityPermissionGranted { + print("💡 Request accessibility permission...") + requestAccessibilityPermission() + } + } + + private func requestAccessibilityPermission() { + let options = [kAXTrustedCheckOptionPrompt.takeUnretainedValue(): true] + AXIsProcessTrustedWithOptions(options as CFDictionary) + } + + private func findExistingApps() { + let runningApps = workspace.runningApplications + + for app in runningApps { + if let strategy = findStrategy(for: app) { + let instance = AppInstance(app: app, strategy: strategy, windowInspector: nil) + monitoredApps[makeKey(for: app)] = instance + print("📱 Found \(strategy.displayName): PID \(app.processIdentifier)") + } + } + + let frontmostApp = workspace.frontmostApplication + print("🔍 Current frontmost app: \(frontmostApp?.localizedName ?? "Unknown") (PID: \(frontmostApp?.processIdentifier ?? -1))") + + activeApp = monitoredApps.values.first { instance in + instance.processId == frontmostApp?.processIdentifier + } + + if let active = activeApp { + print("✅ Active app: \(active.displayName) PID \(active.processId)") + } + + if accessibilityPermissionGranted, let activeApp = activeApp { + Task { @MainActor in + monitorAppWindows(for: activeApp) + } + } + } + + @MainActor + private func monitorAppWindows(for appInstance: AppInstance) { + guard accessibilityPermissionGranted else { + print("❌ Cannot monitor windows - no accessibility permission") + return + } + + let axApp = AXUIElementCreateApplication(appInstance.processId) + + var focusedWindowElement: AnyObject? + let result = AXUIElementCopyAttributeValue(axApp, kAXFocusedWindowAttribute as CFString, &focusedWindowElement) + + guard result == .success, let windowElement = focusedWindowElement else { + print("❌ Could not get focused window from \(appInstance.displayName)") + return + } + + guard CFGetTypeID(windowElement) == AXUIElementGetTypeID() else { + print("❌ Focused window element is not an AXUIElement") + return + } + + let axWindowElement = windowElement as! AXUIElement + + print("🪟 Found focused window for \(appInstance.displayName), creating inspector...") + + if let windowInspector = appInstance.strategy.createWindowInspector( + processId: appInstance.processId, + windowElement: axWindowElement + ) { + if windowInspector.isMainWorkWindow { + print("✅ Found main work window for \(appInstance.displayName)") + var updatedInstance = appInstance + updatedInstance.windowInspector = windowInspector + monitoredApps[makeKey(for: appInstance.app)] = updatedInstance + + if activeApp?.processId == appInstance.processId { + activeApp = updatedInstance + } + } else { + print("📝 Found other window for \(appInstance.displayName) (not main work window)") + } + } + } + + private func findStrategy(for app: NSRunningApplication) -> EditorStrategy? { + return strategies.values.first { strategy in + strategy.shouldMonitor(app) + } + } + + private func makeKey(for app: NSRunningApplication) -> String { + return "\(app.bundleIdentifier ?? "unknown")_\(app.processIdentifier)" + } + + private func handleApplicationActivated(_ notification: Notification) { + guard let app = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication else { return } + + print("📱 App activated: \(app.localizedName ?? "Unknown") (PID: \(app.processIdentifier))") + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + if let strategy = self.findStrategy(for: app) { + print("🔄 \(strategy.displayName) activated: PID \(app.processIdentifier)") + + let key = self.makeKey(for: app) + let instance = AppInstance(app: app, strategy: strategy, windowInspector: nil) + self.monitoredApps[key] = instance + self.activeApp = instance + } else { + if let previousActive = self.activeApp { + print("📱 \(app.localizedName ?? "Unknown app") became active, \(previousActive.displayName) (PID: \(previousActive.processId)) backgrounded") + self.activeApp = nil + } + } + } + } + + private func handleApplicationTerminated(_ notification: Notification) { + guard let app = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication else { return } + + if findStrategy(for: app) != nil { + let key = makeKey(for: app) + print("❌ App terminated: \(app.localizedName ?? "Unknown") PID \(app.processIdentifier)") + monitoredApps.removeValue(forKey: key) + + if activeApp?.processId == app.processIdentifier { + activeApp = monitoredApps.values.first { $0.app.isActive } + } + } + } +} \ No newline at end of file diff --git a/AS2/AS2/Core/Protocols.swift b/AS2/AS2/Core/Protocols.swift new file mode 100644 index 00000000..d19530b6 --- /dev/null +++ b/AS2/AS2/Core/Protocols.swift @@ -0,0 +1,20 @@ +import Cocoa +import Foundation +import ApplicationServices + +protocol WindowInspector: AnyObject { + var documentURL: URL? { get } + var workspaceURL: URL? { get } + var projectURL: URL? { get } + var isMainWorkWindow: Bool { get } + + func refresh() +} + +protocol EditorStrategy { + var displayName: String { get } + var bundleIdentifier: String { get } + + func shouldMonitor(_ app: NSRunningApplication) -> Bool + func createWindowInspector(processId: pid_t, windowElement: AXUIElement) -> WindowInspector? +} \ No newline at end of file diff --git a/AS2/AS2/Editors/Xcode/XcodeMonitor.swift b/AS2/AS2/Editors/Xcode/XcodeMonitor.swift new file mode 100644 index 00000000..7dbea2a5 --- /dev/null +++ b/AS2/AS2/Editors/Xcode/XcodeMonitor.swift @@ -0,0 +1,54 @@ +import Cocoa +import Foundation +import ApplicationServices + +class XcodeMonitor: AppMonitor { + @Published var xcodeInstances: [NSRunningApplication] = [] + @Published var activeXcode: NSRunningApplication? + @Published var lastActiveXcode: NSRunningApplication? + @Published var focusedWindow: WindowInspector? + @Published var currentDocumentURL: URL? + @Published var currentWorkspaceURL: URL? + @Published var currentProjectURL: URL? + + private let workspace = NSWorkspace.shared + + override init(strategies: [EditorStrategy]) { + super.init(strategies: strategies) + setupXcodeSpecificMonitoring() + } + + convenience init() { + self.init(strategies: [XcodeStrategy()]) + } + + private func setupXcodeSpecificMonitoring() { + $monitoredApps + .map { apps in apps.values.compactMap { $0.app.isXcode ? $0.app : nil } } + .assign(to: &$xcodeInstances) + + $activeApp + .map { $0?.app.isXcode == true ? $0?.app : nil } + .assign(to: &$activeXcode) + + $activeApp + .compactMap { $0?.windowInspector } + .assign(to: &$focusedWindow) + + $activeApp + .compactMap { $0?.windowInspector?.documentURL } + .assign(to: &$currentDocumentURL) + + $activeApp + .compactMap { $0?.windowInspector?.workspaceURL } + .assign(to: &$currentWorkspaceURL) + + $activeApp + .compactMap { $0?.windowInspector?.projectURL } + .assign(to: &$currentProjectURL) + + $activeXcode + .compactMap { $0 } + .assign(to: &$lastActiveXcode) + } +} \ No newline at end of file diff --git a/AS2/AS2/Editors/Xcode/XcodeStrategy.swift b/AS2/AS2/Editors/Xcode/XcodeStrategy.swift new file mode 100644 index 00000000..ac36813e --- /dev/null +++ b/AS2/AS2/Editors/Xcode/XcodeStrategy.swift @@ -0,0 +1,16 @@ +import ApplicationServices +import AppKit +import Foundation + +struct XcodeStrategy: EditorStrategy { + let displayName = "Xcode" + let bundleIdentifier = "com.apple.dt.Xcode" + + func shouldMonitor(_ app: NSRunningApplication) -> Bool { + return app.bundleIdentifier == bundleIdentifier + } + + func createWindowInspector(processId: pid_t, windowElement: AXUIElement) -> WindowInspector? { + return XcodeWindowInspector(processIdentifier: processId, windowElement: windowElement) + } +} \ No newline at end of file diff --git a/AS2/AS2/Editors/Xcode/XcodeWindowInspector.swift b/AS2/AS2/Editors/Xcode/XcodeWindowInspector.swift new file mode 100644 index 00000000..5d75641e --- /dev/null +++ b/AS2/AS2/Editors/Xcode/XcodeWindowInspector.swift @@ -0,0 +1,129 @@ +import ApplicationServices +import AppKit +import Foundation + +class XcodeWindowInspector: WindowInspector { + let processIdentifier: pid_t + let windowElement: AXUIElement + + private(set) var documentURL: URL? + private(set) var workspaceURL: URL? + private(set) var projectURL: URL? + + init(processIdentifier: pid_t, windowElement: AXUIElement) { + self.processIdentifier = processIdentifier + self.windowElement = windowElement + refresh() + } + + func refresh() { + documentURL = Self.extractDocumentURL(from: windowElement) + workspaceURL = Self.extractWorkspaceURL(from: windowElement) + projectURL = Self.extractProjectURL(workspaceURL: workspaceURL, documentURL: documentURL) + + print("📄 Xcode window refresh:") + print(" 📁 Document: \(documentURL?.lastPathComponent ?? "None")") + print(" 📦 Workspace: \(workspaceURL?.lastPathComponent ?? "None")") + print(" 🏗️ Project: \(projectURL?.lastPathComponent ?? "None")") + } + + var isMainWorkWindow: Bool { + var identifier: AnyObject? + let result = AXUIElementCopyAttributeValue(windowElement, kAXIdentifierAttribute as CFString, &identifier) + + if result == .success, let id = identifier as? String { + return id == "Xcode.WorkspaceWindow" + } + + return false + } + + static func extractDocumentURL(from windowElement: AXUIElement) -> URL? { + var documentValue: AnyObject? + let result = AXUIElementCopyAttributeValue(windowElement, kAXDocumentAttribute as CFString, &documentValue) + + guard result == .success, let path = documentValue as? String else { + return nil + } + + guard let cleanPath = path.removingPercentEncoding else { return nil } + + let url = URL(fileURLWithPath: cleanPath.replacingOccurrences(of: "file://", with: "")) + return adjustFileURL(url) + } + + static func extractWorkspaceURL(from windowElement: AXUIElement) -> URL? { + var children: AnyObject? + let result = AXUIElementCopyAttributeValue(windowElement, kAXChildrenAttribute as CFString, &children) + + guard result == .success, let childrenArray = children as? [AXUIElement] else { + return nil + } + + for child in childrenArray { + var description: AnyObject? + let descResult = AXUIElementCopyAttributeValue(child, kAXDescriptionAttribute as CFString, &description) + + guard descResult == .success, let desc = description as? String else { continue } + + if desc.starts(with: "/"), desc.count > 1 { + let trimmedPath = desc.trimmingCharacters(in: .newlines) + return URL(fileURLWithPath: trimmedPath) + } + } + + return nil + } + + static func extractProjectURL(workspaceURL: URL?, documentURL: URL?) -> URL? { + guard var currentURL = workspaceURL ?? documentURL else { return nil } + + var firstDirectoryURL: URL? + var lastGitDirectoryURL: URL? + + while currentURL.pathComponents.count > 1 { + defer { currentURL.deleteLastPathComponent() } + + guard FileManager.default.fileExists(atPath: currentURL.path) else { continue } + + var isDirectory: ObjCBool = false + FileManager.default.fileExists(atPath: currentURL.path, isDirectory: &isDirectory) + guard isDirectory.boolValue else { continue } + + guard currentURL.pathExtension != "xcodeproj", + currentURL.pathExtension != "xcworkspace", + currentURL.pathExtension != "playground" else { continue } + + if firstDirectoryURL == nil { + firstDirectoryURL = currentURL + } + + let gitURL = currentURL.appendingPathComponent(".git") + var gitIsDirectory: ObjCBool = false + + if FileManager.default.fileExists(atPath: gitURL.path, isDirectory: &gitIsDirectory) { + if gitIsDirectory.boolValue { + lastGitDirectoryURL = currentURL + } else if let gitContent = try? String(contentsOf: gitURL, encoding: .utf8) { + if !gitContent.hasPrefix("gitdir: ../") && gitContent.contains("/.git/worktrees/") { + lastGitDirectoryURL = currentURL + } + } + } + } + + return lastGitDirectoryURL ?? firstDirectoryURL ?? workspaceURL + } + + static func adjustFileURL(_ url: URL) -> URL { + if url.pathExtension == "playground", + FileManager.default.fileExists(atPath: url.path) { + var isDirectory: ObjCBool = false + FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory) + if isDirectory.boolValue { + return url.appendingPathComponent("Contents.swift") + } + } + return url + } +} \ No newline at end of file diff --git a/AS2/AS2/Extensions/NSRunningApplication+Extensions.swift b/AS2/AS2/Extensions/NSRunningApplication+Extensions.swift new file mode 100644 index 00000000..67291315 --- /dev/null +++ b/AS2/AS2/Extensions/NSRunningApplication+Extensions.swift @@ -0,0 +1,7 @@ +import Cocoa + +extension NSRunningApplication { + var isXcode: Bool { + bundleIdentifier == "com.apple.dt.Xcode" + } +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..6ffadcce --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,108 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +GitHub Copilot for Xcode is a macOS application that provides AI-powered code completion and chat assistance for Xcode. It integrates GitHub Copilot's capabilities directly into the Xcode development environment through Xcode source editor extensions. + +## Build Commands + +### Local Development Build +```bash +cd ./Script +sh ./uninstall-app.sh # Remove any previous installation +del ../build # Clean the build directory (use del instead of rm) +sh ./localbuild-app.sh # Build a fresh copy of the app +``` + +### Server Components (Node.js/TypeScript) +```bash +cd Server +npm install # Install dependencies +npm run build # Build with webpack +``` + +### Testing +```bash +# Run all unit tests (from Xcode or command line) +xcodebuild test -scheme "Copilot for Xcode" -workspace "Copilot for Xcode.xcworkspace" + +# Tests are configured in TestPlan.xctestplan +# All new tests should be added to this test plan +``` + +### Code Formatting +- Uses SwiftFormat for Swift code formatting +- Follows Ray Wenderlich Style Guide with 4 spaces for indentation + +## Architecture Overview + +The project is organized into several key targets and packages: + +### Main Targets +- **Copilot for Xcode**: Host app containing XPCService and editor extension, provides settings UI +- **EditorExtension**: Xcode source editor extension that forwards editor content to XPCService +- **ExtensionService**: Background service where all core features are implemented +- **CommunicationBridge**: Maintains communication between host app/editor extension and ExtensionService + +### Swift Packages +- **Core**: Contains main application logic organized by feature areas + - `Service`: ExtensionService implementation + - `HostApp`: Host application implementation + - `SuggestionWidget`: UI components for code suggestions + - `ConversationTab`: Chat interface components + - `ChatService`: Chat functionality and context management + - `SuggestionService`: Code completion service + - `PromptToCodeService`: Code generation from prompts + +- **Tool**: Shared utilities and lower-level services + - `GitHubCopilotService`: Core integration with GitHub Copilot Language Server + - `Workspace`: File system and project management + - `XcodeInspector`: Xcode app monitoring and interaction + - `SuggestionBasic`: Core suggestion data types + - `Preferences`: Configuration management + - `Logger`: Logging infrastructure + +- **Server**: Node.js/TypeScript components + - Monaco editor integration for diff views + - Terminal/xterm integration + - Webpack-based build system + +### Key Service Architecture +- Uses actor-based concurrency with `@WorkspaceActor` and `@ServiceActor` +- Dependency injection via swift-dependencies +- Composable Architecture (TCA) for UI state management +- XPC communication between sandboxed and non-sandboxed components + +### Data Flow +1. Editor extension captures Xcode content via source editor APIs +2. Content forwarded to ExtensionService via CommunicationBridge XPC +3. ExtensionService processes requests through GitHubCopilotService +4. Language Server Protocol (LSP) communication with GitHub Copilot backend +5. Results returned through same XPC chain back to editor + +## Prerequisites + +- macOS 12+ +- Xcode 8+ +- Node.js and npm (symlinked to /usr/local/bin for Xcode run scripts) +- GitHub Copilot subscription + +## Key Files to Understand + +- `Core/Sources/Service/Service.swift`: Main service entry point +- `Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift`: Core Copilot integration +- `Core/Package.swift` & `Tool/Package.swift`: Swift package configurations +- `TestPlan.xctestplan`: Centralized test configuration +- `Version.xcconfig`: Version control for all targets +- `DEVELOPMENT.md`: Detailed development setup and architecture notes + +## Common Development Patterns + +- All async operations use Swift concurrency (async/await) +- UI built with SwiftUI using Composable Architecture patterns +- Preference storage via UserDefaults with type-safe property wrappers +- Logging via centralized Logger package with different log levels +- File watching and workspace management through dedicated services +- XPC communication follows request-response patterns with proper error handling \ No newline at end of file diff --git a/Docs/XCODE_INTEGRATION_COMPONENTS.md b/Docs/XCODE_INTEGRATION_COMPONENTS.md new file mode 100644 index 00000000..f62ccb4a --- /dev/null +++ b/Docs/XCODE_INTEGRATION_COMPONENTS.md @@ -0,0 +1,163 @@ +# Xcode Integration Components Guide + +This guide breaks down which components in the CopilotForXcode project are needed for Xcode interaction versus Copilot-specific features. Use this when building a parallel app that needs to monitor and communicate with Xcode without Copilot functionality. + +## **Essential Xcode Monitoring Components** + +### Core Xcode State Monitoring +These components provide the fundamental ability to monitor Xcode's state in real-time: + +#### **`Tool/Sources/XcodeInspector/`** - Central Coordinator +- **`XcodeInspector.swift`** - Main coordinator that publishes Xcode state +- **`Apps/XcodeAppInstanceInspector.swift`** - Xcode-specific window and document tracking +- **`XcodeWindowInspector.swift`** - Window-level inspection +- **`SourceEditor.swift`** - Editor content and cursor management + +**Published State Properties:** +```swift +@Published public var activeDocumentURL: URL? +@Published public var activeWorkspaceURL: URL? +@Published public var focusedWindow: XcodeWindowInspector? +@Published public var focusedEditor: SourceEditor? +@Published public var focusedElement: AXUIElement? +``` + +#### **`Tool/Sources/ActiveApplicationMonitor/`** - App State Tracking +- **`ActiveApplicationMonitor.swift`** - Tracks which applications are active/running +- Uses `NSWorkspace.didActivateApplicationNotification` for real-time updates +- Provides: `isActive`, `isXcode`, process identifiers, lifecycle events + +#### **`Tool/Sources/AXExtension/`** - Accessibility API Interface +- **`AXUIElement.swift`** - Core accessibility API extensions +- Provides UI element access: `selectedTextRange`, `value`, `isFocused`, `isSourceEditor` +- Element navigation: `parent`, `children`, `focusedElement`, `window` +- Xcode detection: `isXcodeWorkspaceWindow`, `isEditorArea` + +#### **`Tool/Sources/AXNotificationStream/`** - Real-time Events +- **`AXNotificationStream.swift`** - Real-time accessibility event monitoring +- Monitors: Focus changes, title changes, window moves/resizes, element destruction +- Features: Configurable run loops, retry with backoff, permission detection + +#### **`Tool/Sources/AXHelper/`** - Accessibility Utilities +- **`AXHelper.swift`** - Higher-level accessibility operations +- Code injection, cursor management, scroll position handling + +### Communication Infrastructure (if needed) + +#### **`Tool/Sources/XPCShared/`** - XPC Communication +- **`XPCServiceProtocol.swift`** - Service protocols +- **`XcodeInspectorData.swift`** - Xcode state data types +- **`Models.swift`** - Shared data models + +#### **`ExtensionService/XPCController.swift`** - Service Coordination +- Anonymous XPC listener for cross-process communication +- Bridge management and connection lifecycle +- Data exposure API: `getXcodeInspectorData()` + +#### **`EditorExtension/SourceEditorExtension.swift`** - Xcode Menu Integration +- Official Xcode source editor extension +- Command registration for custom menu items +- XPC service wake-up and communication + +### Supporting Infrastructure + +#### **`Tool/Sources/Workspace/`** - File System Management +- **`Workspace.swift`** - Project and workspace management +- **`WorkspacePool.swift`** - Multiple workspace handling +- **File watching components** - Monitor file system changes + +#### **`Tool/Sources/Preferences/`** - Configuration +- **`AppStorage.swift`** - UserDefaults with property wrappers +- **`Keys.swift`** - Preference key definitions +- **`UserDefaults.swift`** - Type-safe preference access + +#### **`Tool/Sources/Logger/`** - Logging +- **`Logger.swift`** - Centralized logging infrastructure +- **`FileLogger.swift`** - File-based logging + +#### **`Tool/Sources/UserDefaultsObserver/`** - Settings Observation +- **`UserDefaultsObserver.swift`** - Real-time settings changes + +## **Copilot-Specific Components (Not Needed)** + +### GitHub Copilot Integration +- **`Tool/Sources/GitHubCopilotService/`** - All Language Server integration +- **`Tool/Sources/BuiltinExtension/`** - Copilot extension provider +- **`Tool/Sources/SuggestionProvider/`** - Code suggestion services +- **`Tool/Sources/SuggestionBasic/`** - Suggestion data types +- **`Tool/Sources/ConversationServiceProvider/`** - Chat conversation handling +- **`Tool/Sources/TelemetryService/`** - Usage telemetry + +### UI Components for Copilot Features +- **`Core/Sources/SuggestionWidget/`** - Code suggestion UI +- **`Core/Sources/ConversationTab/`** - Chat interface +- **`Core/Sources/ChatService/`** - Chat functionality +- **`Core/Sources/SuggestionService/`** - Suggestion management +- **`Core/Sources/PromptToCodeService/`** - Code generation +- **`Tool/Sources/ChatAPIService/`** - Chat API integration +- **`Tool/Sources/ChatTab/`** - Chat tab management + +### Server Components +- **`Server/`** - All Node.js/TypeScript web components +- Monaco editor integration, terminal integration, webpack build + +## **Minimal Architecture for Xcode Monitoring** + +### Recommended Component Structure +``` +Tool/Sources/ +├── ActiveApplicationMonitor/ # App state tracking +├── AXExtension/ # Accessibility API +├── AXNotificationStream/ # Real-time events +├── AXHelper/ # AX utilities +├── XcodeInspector/ # Central coordinator +├── XPCShared/ # Communication (optional) +├── Logger/ # Logging +├── Preferences/ # Configuration +├── UserDefaultsObserver/ # Settings +└── Workspace/ # File management +``` + +### Core Data Flow Pattern +``` +1. ActiveApplicationMonitor → Detect Xcode launch/focus +2. AXNotificationStream → Real-time UI change events +3. XcodeInspector → Aggregate and publish state +4. XcodeAppInstanceInspector → Extract specific data +5. AXUIElement extensions → Low-level element access +6. XPC Service → Expose data to extensions (optional) +``` + +### Key Published Data +The `XcodeInspector` provides real-time awareness of: +- **Active File**: `activeDocumentURL: URL?` +- **Workspace**: `activeWorkspaceURL: URL?` +- **Editor State**: `focusedEditor: SourceEditor?` +- **Cursor Position**: Available through `SourceEditor.selectedTextRange` +- **Editor Content**: Available through `SourceEditor.getContent()` + +## **Implementation Notes** + +### Accessibility Permissions +- Requires macOS Accessibility permission +- Automatic permission detection and retry mechanisms +- Robust error handling for permission issues + +### Performance Optimizations +- **Debounced Updates**: Prevents excessive state changes +- **Selective Monitoring**: Only tracks relevant UI elements +- **Async Processing**: Non-blocking state updates +- **Memory Management**: Proper cleanup of observers + +### Error Recovery +- **Automatic Restart**: When accessibility API corrupts +- **Exponential Backoff**: For failed connections +- **State Validation**: Consistency checking +- **Graceful Degradation**: Fallback to cached data + +### Thread Safety +- Uses global actors (`@XcodeInspectorActor`) for thread safety +- All state updates happen on appropriate actors +- Proper async/await patterns throughout + +This architecture provides comprehensive Xcode monitoring without any Copilot dependencies, giving you real-time awareness of files, workspaces, cursor position, and editor content. \ No newline at end of file diff --git a/Docs/XCODE_STATUS_MONITORING.md b/Docs/XCODE_STATUS_MONITORING.md new file mode 100644 index 00000000..98032a62 --- /dev/null +++ b/Docs/XCODE_STATUS_MONITORING.md @@ -0,0 +1,189 @@ +# Copilot for Xcode: Complete Xcode Status Reading Architecture Map + +Based on a thorough analysis of the codebase, here's the comprehensive map of how this project reads Xcode status: + +## 🏗️ **Core Architecture Overview** + +The project uses a **multi-layered approach** combining: +1. **macOS Accessibility API** (primary method) +2. **Xcode Editor Extensions** (direct integration) +3. **XPC Inter-Process Communication** (service coordination) +4. **NSWorkspace monitoring** (application state tracking) + +--- + +## 📊 **Data Flow & Components** + +### **1. Application State Monitoring** +**Location**: `Tool/Sources/ActiveApplicationMonitor/ActiveApplicationMonitor.swift` +- **Purpose**: Tracks which applications are active/running +- **Key Data Captured**: + - Active application status (`isActive`, `isXcode`) + - Process identifiers + - Application lifecycle events (launch/terminate) +- **Real-time Updates**: Uses `NSWorkspace.didActivateApplicationNotification` + +### **2. Accessibility-Based Xcode Monitoring** +**Primary Components**: + +**A. AXUIElement Extensions** (`Tool/Sources/AXExtension/AXUIElement.swift:1-330`) +- **UI Element Properties**: + - `selectedTextRange` (current cursor/selection) + - `value` (editor content) + - `isFocused`, `isSourceEditor` (element state) + - `document`, `title`, `role` (element metadata) +- **Element Hierarchy Navigation**: + - `parent`, `children`, `focusedElement` + - `window`, `focusedWindow` access +- **Xcode-Specific Detection**: + - `isXcodeWorkspaceWindow` + - `isEditorArea`, `isSourceEditor` + +**B. AX Notification Streaming** (`Tool/Sources/AXNotificationStream/AXNotificationStream.swift:8-170`) +- **Real-time Event Monitoring**: + - `kAXFocusedUIElementChangedNotification` + - `kAXTitleChangedNotification` + - `kAXWindowMovedNotification`/`kAXWindowResizedNotification` + - `kAXUIElementDestroyedNotification` +- **Performance Features**: + - Configurable run loop modes + - Automatic retry with backoff + - Accessibility permission detection + +**C. AX Helper Utilities** (`Tool/Sources/AXHelper/AXHelper.swift:5-70`) +- **Code Injection**: Direct content manipulation via accessibility API +- **Cursor Management**: Selection range preservation/restoration +- **Scroll Position**: Viewport state maintenance + +### **3. Centralized Xcode Inspector** +**Location**: `Tool/Sources/XcodeInspector/XcodeInspector.swift:23-432` + +**A. Published State Properties**: +```swift +@Published public var activeProjectRootURL: URL? +@Published public var activeDocumentURL: URL? +@Published public var activeWorkspaceURL: URL? +@Published public var focusedWindow: XcodeWindowInspector? +@Published public var focusedEditor: SourceEditor? +@Published public var focusedElement: AXUIElement? +@Published public var completionPanel: AXUIElement? +``` + +**B. Real-time vs Cached Data**: +- **Cached**: `activeDocumentURL`, `activeWorkspaceURL` +- **Real-time**: `realtimeActiveDocumentURL`, `realtimeActiveWorkspaceURL` +- **Source**: Real-time data extracted directly from window titles/elements + +**C. Self-Healing Mechanisms**: +- **Malfunction Detection**: Monitors for accessibility API corruption +- **Auto-Recovery**: Automatic restart when inconsistencies detected +- **Debounced Validation**: Prevents excessive restart attempts + +### **4. Xcode App Instance Monitoring** +**Location**: `Tool/Sources/XcodeInspector/Apps/XcodeAppInstanceInspector.swift:8-200+` + +**A. Window-Level Inspection**: +- **Workspace Window Detection**: `identifier == "Xcode.WorkspaceWindow"` +- **Document URL Extraction**: From window title parsing +- **Project Structure Analysis**: Workspace vs project distinction + +**B. Real-time Data Extraction**: +```swift +public var realtimeDocumentURL: URL? // From focused window +public var realtimeWorkspaceURL: URL? // From window title +public var realtimeProjectURL: URL? // Derived from workspace/document +``` + +**C. Notification Handling**: +- **AX Event Processing**: 13 different notification types +- **Async Stream**: `AsyncPassthroughSubject` +- **Window Focus Tracking**: Automatic inspector switching + +### **5. Editor Extension Integration** +**Location**: `EditorExtension/SourceEditorExtension.swift:11-91` + +**A. Direct Xcode Integration**: +- **XCSourceEditorExtension**: Official Xcode extension API +- **Command Registration**: Built-in commands for suggestions/chat +- **XPC Service Wake-up**: Automatic service initialization + +**B. Available Commands**: +- Suggestion controls (accept/reject/navigate) +- Settings access +- Chat integration +- Real-time suggestion toggling + +### **6. XPC Communication Layer** +**Location**: `ExtensionService/XPCController.swift:5-87` + +**A. Service Coordination**: +- **Anonymous XPC Listener**: Cross-process communication +- **Bridge Management**: Connection lifecycle handling +- **Ping Mechanism**: Service health monitoring + +**B. Data Exposure**: +- **Inspector Data API**: `getXcodeInspectorData()` +- **State Serialization**: JSON encoding of current Xcode state +- **Background Permission Handling**: Automated permission requests + +--- + +## 🔄 **Real-time Data Extraction Flow** + +``` +1. NSWorkspace → Application Launch/Focus Detection +2. AXNotificationStream → UI Change Events +3. XcodeInspector → State Aggregation & Publishing +4. XcodeAppInstanceInspector → Window-Specific Analysis +5. AXUIElement Extensions → Element Property Reading +6. XPC Service → Data Exposure to Extensions +``` + +## 📍 **Key Data Points Tracked** + +| Data Point | Source | Frequency | Method | +|------------|--------|-----------|---------| +| **Active File** | Window title parsing | Real-time | `realtimeDocumentURL` | +| **Workspace** | Window title/AX tree | Real-time | `realtimeWorkspaceURL` | +| **Cursor Position** | Focused element | Real-time | `selectedTextRange` | +| **Editor Content** | AX value attribute | On-demand | `focusedEditor.getContent()` | +| **UI State** | AX notifications | Event-driven | `focusedElement`, `completionPanel` | +| **Project Root** | Workspace analysis | Cached | `activeProjectRootURL` | + +## 🛡️ **Robustness Features** + +- **Permission Monitoring**: Continuous accessibility permission validation +- **Malfunction Detection**: Element consistency checking +- **Auto-Recovery**: Intelligent restart mechanisms +- **Backoff Strategies**: Exponential retry delays +- **Thread Safety**: Global actor isolation (`@XcodeInspectorActor`) + +## 🔧 **Key Implementation Details** + +### Accessibility API Usage +The project heavily relies on macOS Accessibility APIs to monitor Xcode's UI state: +- Uses `AXUIElementCreateApplication()` to get app-level access +- Monitors specific notification types for real-time updates +- Implements robust error handling for permission issues +- Provides fallback mechanisms when accessibility API fails + +### Window Title Parsing +Real-time file and workspace detection happens through: +- Parsing Xcode workspace window titles +- Extracting file paths from window identifiers +- Distinguishing between workspace and project contexts +- Handling edge cases like unsaved files and temporary documents + +### Performance Optimizations +- **Debounced Updates**: Prevents excessive state changes +- **Selective Monitoring**: Only tracks relevant UI elements +- **Async Processing**: Non-blocking state updates +- **Memory Management**: Proper cleanup of observers and tasks + +### Error Recovery +- **Automatic Restart**: When accessibility API becomes corrupted +- **Exponential Backoff**: For failed connection attempts +- **State Validation**: Continuous consistency checking +- **Graceful Degradation**: Fallback to cached data when real-time fails + +This comprehensive architecture enables the Copilot for Xcode extension to maintain accurate, real-time awareness of Xcode's state while providing robust error handling and performance optimization. \ No newline at end of file diff --git a/Tool/Sources/BuiltinExtension/BuiltinExtensionWorkspacePlugin.swift b/Tool/Sources/BuiltinExtension/BuiltinExtensionWorkspacePlugin.swift index a03c34d1..c3c6ff5d 100644 --- a/Tool/Sources/BuiltinExtension/BuiltinExtensionWorkspacePlugin.swift +++ b/Tool/Sources/BuiltinExtension/BuiltinExtensionWorkspacePlugin.swift @@ -9,6 +9,7 @@ public final class BuiltinExtensionWorkspacePlugin: WorkspacePlugin { super.init(workspace: workspace) } + // this calls Copilot notifyOpenTextDocument override public func didOpenFilespace(_ filespace: Filespace) { notifyOpenFile(filespace: filespace) } @@ -32,6 +33,7 @@ public final class BuiltinExtensionWorkspacePlugin: WorkspacePlugin { } } + // this calls Copilot notifyOpenTextDocument public func notifyOpenFile(filespace: Filespace) { Task { guard filespace.isTextReadable else { return } diff --git a/Tool/Sources/GitHubCopilotService/GitHubCopilotExtension.swift b/Tool/Sources/GitHubCopilotService/GitHubCopilotExtension.swift index 119278ee..29f90b61 100644 --- a/Tool/Sources/GitHubCopilotService/GitHubCopilotExtension.swift +++ b/Tool/Sources/GitHubCopilotService/GitHubCopilotExtension.swift @@ -42,6 +42,7 @@ public final class GitHubCopilotExtension: BuiltinExtension { public func workspaceDidClose(_: WorkspaceInfo) {} + // this calls Copilot notifyOpenTextDocument public func workspace(_ workspace: WorkspaceInfo, didOpenDocumentAt documentURL: URL) { guard isLanguageServerInUse else { return } // check if file size is larger than 15MB, if so, return immediately diff --git a/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift b/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift index 4ea5de5c..7c46f1c0 100644 --- a/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift +++ b/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift @@ -36,6 +36,7 @@ public protocol GitHubCopilotSuggestionServiceType { func notifyShown(_ completion: CodeSuggestion) async func notifyAccepted(_ completion: CodeSuggestion, acceptedLength: Int?) async func notifyRejected(_ completions: [CodeSuggestion]) async + // tells Copilot LSP that a file has been opened func notifyOpenTextDocument(fileURL: URL, content: String) async throws func notifyChangeTextDocument(fileURL: URL, content: String, version: Int) async throws func notifyCloseTextDocument(fileURL: URL) async throws diff --git a/Tool/Sources/XcodeInspector/XcodeInspector.swift b/Tool/Sources/XcodeInspector/XcodeInspector.swift index 2b2ea1e8..f513b932 100644 --- a/Tool/Sources/XcodeInspector/XcodeInspector.swift +++ b/Tool/Sources/XcodeInspector/XcodeInspector.swift @@ -140,9 +140,11 @@ public final class XcodeInspector: ObservableObject { latestNonRootWorkspaceURL = nil } + // finds running Xcode instances let runningApplications = NSWorkspace.shared.runningApplications xcodes = runningApplications .filter { $0.isXcode } + // creates a XcodeAppInstanceInspector for each Xcode instance .map(XcodeAppInstanceInspector.init(runningApplication:)) let activeXcode = xcodes.first(where: \.isActive) latestActiveXcode = activeXcode ?? xcodes.first @@ -160,6 +162,7 @@ public final class XcodeInspector: ObservableObject { } await withThrowingTaskGroup(of: Void.self) { [weak self] group in + // activation group.addTask { [weak self] in // Did activate app let sequence = NSWorkspace.shared.notificationCenter .notifications(named: NSWorkspace.didActivateApplicationNotification) @@ -193,6 +196,7 @@ public final class XcodeInspector: ObservableObject { } } + // termination group.addTask { [weak self] in // Did terminate app let sequence = NSWorkspace.shared.notificationCenter .notifications(named: NSWorkspace.didTerminateApplicationNotification) @@ -242,6 +246,7 @@ public final class XcodeInspector: ObservableObject { } } + // accessibility API issues group.addTask { [weak self] in // malfunctioning let sequence = NotificationCenter.default .notifications(named: .accessibilityAPIMalfunctioning) @@ -267,6 +272,10 @@ public final class XcodeInspector: ObservableObject { } } + // Get the focused UI element + // Determine if it's a source editor + // Monitor for focus changes + @XcodeInspectorActor private func setActiveXcode(_ xcode: XcodeAppInstanceInspector) { previousActiveApplication = activeApplication @@ -279,9 +288,12 @@ public final class XcodeInspector: ObservableObject { activeXcode = xcode latestActiveXcode = xcode + // active document activeDocumentURL = xcode.documentURL + // focused window focusedWindow = xcode.focusedWindow completionPanel = xcode.completionPanel + // project root activeProjectRootURL = xcode.projectRootURL activeWorkspaceURL = xcode.workspaceURL focusedWindow = xcode.focusedWindow @@ -304,6 +316,7 @@ public final class XcodeInspector: ObservableObject { } focusedElement = getFocusedElementAndRecordStatus(xcode.appElement) + // focused editor if let editorElement = focusedElement, editorElement.isSourceEditor { focusedEditor = .init( runningApplication: xcode.runningApplication,