├── .gitignore ├── Info.plist ├── OSAX.xcconfigs ├── OSAX_Debug_TotalFinder.xcconfig ├── OSAX_Debug_TotalFinder_generated.xcconfig ├── OSAX_ReleaseLogging_TotalFinder.xcconfig ├── OSAX_ReleaseLogging_TotalFinder_generated.xcconfig ├── OSAX_Release_TotalFinder.xcconfig └── OSAX_Release_TotalFinder_generated.xcconfig ├── OSAX.xcodeproj └── project.pbxproj ├── TFStandardVersionComparator.h ├── TFStandardVersionComparator.mm ├── TFVersionComparisonProtocol.h ├── TotalFinder.pch ├── TotalFinderInjector.mm ├── TotalFinderInjector.sdef ├── license.txt └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | xcuserdata 3 | bin/ -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.binaryage.totalfinder.injector 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | TotalFinderInjector 15 | CFBundlePackageType 16 | osax 17 | CFBundleShortVersionString 18 | ##VERSION## 19 | CFBundleSignature 20 | BAGE 21 | CFBundleVersion 22 | ##VERSION## 23 | OSAScriptingDefinition 24 | TotalFinderInjector.sdef 25 | OSAXHandlers 26 | 27 | Events 28 | 29 | BATFchck 30 | 31 | Context 32 | Process 33 | Handler 34 | HandleCheckEvent 35 | ThreadSafe 36 | 37 | 38 | BATFcrsh 39 | 40 | Context 41 | Process 42 | Handler 43 | HandleCrashEvent 44 | ThreadSafe 45 | 46 | 47 | BATFinit 48 | 49 | Context 50 | Process 51 | Handler 52 | HandleInitEvent 53 | ThreadSafe 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /OSAX.xcconfigs/OSAX_Debug_TotalFinder.xcconfig: -------------------------------------------------------------------------------- 1 | //!bagen generate binaryage OSAX Debug TotalFinder 2 | // 3 | // this file should be set as Debug configuration for target TotalFinder in OSAX.xcodeproj 4 | 5 | // following included file can be regenerated by running 'badev regen_xcconfigs' 6 | #include "OSAX_Debug_TotalFinder_generated.xcconfig" 7 | 8 | // here you can follow with your custom settings overrides if needed 9 | -------------------------------------------------------------------------------- /OSAX.xcconfigs/OSAX_Debug_TotalFinder_generated.xcconfig: -------------------------------------------------------------------------------- 1 | // A GENERATED FILE by bagen utility 2 | // more info: https://github.com/binaryage/badev 3 | // 4 | // !!! DO NOT EDIT IT BY HAND !!! 5 | // 6 | 7 | // 8 | // Common settings that should be enabled for every project 9 | // 10 | CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES 11 | CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES 12 | CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES 13 | CLANG_ENABLE_OBJC_ARC = YES 14 | CLANG_WARN_BOOL_CONVERSION = YES 15 | CLANG_WARN_CONSTANT_CONVERSION = YES 16 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES 17 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 18 | CLANG_WARN_EMPTY_BODY = YES 19 | CLANG_WARN_ENUM_CONVERSION = YES 20 | CLANG_WARN_INT_CONVERSION = YES 21 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 22 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 23 | CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES 24 | GCC_WARN_UNINITIALIZED_AUTOS = YES 25 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym 26 | GCC_C_LANGUAGE_STANDARD = gnu99 27 | GCC_PRECOMPILE_PREFIX_HEADER = YES 28 | GCC_THREADSAFE_STATICS = NO 29 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0 30 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES 31 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES 32 | GCC_WARN_ABOUT_RETURN_TYPE = YES 33 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES 34 | GCC_WARN_UNINITIALIZED_AUTOS = YES 35 | GCC_WARN_UNUSED_FUNCTION = YES 36 | GCC_WARN_UNUSED_LABEL = YES 37 | GCC_WARN_UNUSED_VARIABLE = YES 38 | GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES 39 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 40 | CLANG_WARN_INFINITE_RECURSION = YES; 41 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 42 | CLANG_WARN_UNREACHABLE_CODE = YES; 43 | ENABLE_STRICT_OBJC_MSGSEND = YES; 44 | GCC_WARN_UNUSED_VARIABLE = YES; 45 | ALWAYS_SEARCH_USER_PATHS = NO 46 | PRODUCT_NAME = TotalFinder 47 | // 48 | // Base configuration for a Debug build of any project 49 | // 50 | COPY_PHASE_STRIP = NO 51 | GCC_OPTIMIZATION_LEVEL = 0 52 | LLVM_LTO = NO 53 | ONLY_ACTIVE_ARCH = YES 54 | STRIP_INSTALLED_PRODUCT = NO 55 | OTHER_CODE_SIGN_FLAGS = --timestamp=none 56 | // 57 | // Base configuration for all TotalFinder projects 58 | // 59 | MACOSX_DEPLOYMENT_TARGET = 10.9 60 | SDKROOT = macosx 61 | CLANG_CXX_LIBRARY = libc++ 62 | CLANG_CXX_LANGUAGE_STANDARD = c++17 63 | CLANG_ENABLE_MODULES = YES 64 | ENABLE_TESTABILITY = NO // this would expose some internal symbols 65 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 66 | CLANG_WARN_BOOL_CONVERSION = YES; 67 | CLANG_WARN_CONSTANT_CONVERSION = YES; 68 | CLANG_WARN_EMPTY_BODY = YES; 69 | CLANG_WARN_ENUM_CONVERSION = YES; 70 | CLANG_WARN_INFINITE_RECURSION = YES; 71 | CLANG_WARN_INT_CONVERSION = YES; 72 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 73 | CLANG_WARN_UNREACHABLE_CODE = YES; 74 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 75 | GCC_NO_COMMON_BLOCKS = YES; 76 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 77 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 78 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 79 | GCC_WARN_UNUSED_FUNCTION = YES; 80 | GCC_WARN_UNUSED_VARIABLE = YES; 81 | ENABLE_STRICT_OBJC_MSGSEND = YES; 82 | COMBINE_HIDPI_IMAGES = YES 83 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym 84 | INFOPLIST_FILE = Info.plist 85 | GCC_PREFIX_HEADER = TotalFinder.pch 86 | // 87 | // Debug mode additions to TotalFinder configs 88 | // 89 | // we don't want OSAX subsystem to treat our intermediate build folder as installed osax in the system 90 | // they use SpotLight or some similar search mechanism to look for all .osax in the system 91 | WRAPPER_EXTENSION = xaso 92 | GCC_SYMBOLS_PRIVATE_EXTERN = YES 93 | // 94 | // Dynamic configuration collected from all sections above 95 | // 96 | WARNING_CFLAGS = -Wextra -Wself-assign -Wno-gcc-compat -Wno-unused-parameter -Wstrict-prototypes -Wunreachable-code -Wunused-member-function -Wuninitialized -Wsuper-class-method-mismatch -Warc -Warc-retain-cycles -Warc-unsafe-retained-assign -Warc-non-pod-memaccess -Wbind-to-temporary-copy -Wthread-safety -Wthread-safety-beta 97 | GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1 LOGGING_SUPPORT=1 98 | OTHER_CPLUSPLUSFLAGS = -ftrapv -fsanitize=undefined-trap -fsanitize-undefined-trap-on-error 99 | OTHER_LDFLAGS = -undefined dynamic_lookup -headerpad_max_install_names -------------------------------------------------------------------------------- /OSAX.xcconfigs/OSAX_ReleaseLogging_TotalFinder.xcconfig: -------------------------------------------------------------------------------- 1 | //!bagen generate binaryage OSAX ReleaseLogging TotalFinder 2 | // 3 | // this file should be set as ReleaseLogging configuration for target TotalFinder in OSAX.xcodeproj 4 | 5 | // include the previously set xcconfig 6 | #include "OSAX_Release_TotalFinder.xcconfig" 7 | 8 | // following included file can be regenerated by running 'badev regen_xcconfigs' 9 | #include "OSAX_ReleaseLogging_TotalFinder_generated.xcconfig" 10 | 11 | // here you can follow with your custom settings overrides if needed 12 | -------------------------------------------------------------------------------- /OSAX.xcconfigs/OSAX_ReleaseLogging_TotalFinder_generated.xcconfig: -------------------------------------------------------------------------------- 1 | // A GENERATED FILE by bagen utility 2 | // more info: https://github.com/binaryage/badev 3 | // 4 | // !!! DO NOT EDIT IT BY HAND !!! 5 | // 6 | 7 | // 8 | // Common settings that should be enabled for every project 9 | // 10 | CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES 11 | CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES 12 | CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES 13 | CLANG_ENABLE_OBJC_ARC = YES 14 | CLANG_WARN_BOOL_CONVERSION = YES 15 | CLANG_WARN_CONSTANT_CONVERSION = YES 16 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES 17 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 18 | CLANG_WARN_EMPTY_BODY = YES 19 | CLANG_WARN_ENUM_CONVERSION = YES 20 | CLANG_WARN_INT_CONVERSION = YES 21 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 22 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 23 | CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES 24 | GCC_WARN_UNINITIALIZED_AUTOS = YES 25 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym 26 | GCC_C_LANGUAGE_STANDARD = gnu99 27 | GCC_PRECOMPILE_PREFIX_HEADER = YES 28 | GCC_THREADSAFE_STATICS = NO 29 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0 30 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES 31 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES 32 | GCC_WARN_ABOUT_RETURN_TYPE = YES 33 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES 34 | GCC_WARN_UNINITIALIZED_AUTOS = YES 35 | GCC_WARN_UNUSED_FUNCTION = YES 36 | GCC_WARN_UNUSED_LABEL = YES 37 | GCC_WARN_UNUSED_VARIABLE = YES 38 | GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES 39 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 40 | CLANG_WARN_INFINITE_RECURSION = YES; 41 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 42 | CLANG_WARN_UNREACHABLE_CODE = YES; 43 | ENABLE_STRICT_OBJC_MSGSEND = YES; 44 | GCC_WARN_UNUSED_VARIABLE = YES; 45 | ALWAYS_SEARCH_USER_PATHS = NO 46 | PRODUCT_NAME = TotalFinder 47 | // 48 | // Base configuration for a ReleaseLogging build of any project 49 | // 50 | // 51 | // Base configuration for a Release build of any project 52 | // 53 | COPY_PHASE_STRIP = YES 54 | GCC_OPTIMIZATION_LEVEL = fast 55 | LLVM_LTO = YES 56 | ONLY_ACTIVE_ARCH = NO 57 | STRIP_INSTALLED_PRODUCT = YES 58 | // 59 | // Base configuration for all TotalFinder projects 60 | // 61 | MACOSX_DEPLOYMENT_TARGET = 10.9 62 | SDKROOT = macosx 63 | CLANG_CXX_LIBRARY = libc++ 64 | CLANG_CXX_LANGUAGE_STANDARD = c++17 65 | CLANG_ENABLE_MODULES = YES 66 | ENABLE_TESTABILITY = NO // this would expose some internal symbols 67 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 68 | CLANG_WARN_BOOL_CONVERSION = YES; 69 | CLANG_WARN_CONSTANT_CONVERSION = YES; 70 | CLANG_WARN_EMPTY_BODY = YES; 71 | CLANG_WARN_ENUM_CONVERSION = YES; 72 | CLANG_WARN_INFINITE_RECURSION = YES; 73 | CLANG_WARN_INT_CONVERSION = YES; 74 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 75 | CLANG_WARN_UNREACHABLE_CODE = YES; 76 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 77 | GCC_NO_COMMON_BLOCKS = YES; 78 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 79 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 80 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 81 | GCC_WARN_UNUSED_FUNCTION = YES; 82 | GCC_WARN_UNUSED_VARIABLE = YES; 83 | ENABLE_STRICT_OBJC_MSGSEND = YES; 84 | COMBINE_HIDPI_IMAGES = YES 85 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym 86 | INFOPLIST_FILE = Info.plist 87 | GCC_PREFIX_HEADER = TotalFinder.pch 88 | // we don't want OSAX subsystem to treat our intermediate build folder as installed osax in the system 89 | // they use SpotLight or some similar search mechanism to look for all .osax in the system 90 | WRAPPER_EXTENSION = xaso 91 | GCC_SYMBOLS_PRIVATE_EXTERN = YES 92 | // 93 | // Dynamic configuration collected from all sections above 94 | // 95 | WARNING_CFLAGS = -Wextra -Wself-assign -Wno-gcc-compat -Wno-unused-parameter -Wstrict-prototypes -Wunreachable-code -Wunused-member-function -Wuninitialized -Wsuper-class-method-mismatch -Warc -Warc-retain-cycles -Warc-unsafe-retained-assign -Warc-non-pod-memaccess -Wbind-to-temporary-copy -Wno-unused-variable -Wthread-safety -Wthread-safety-beta 96 | GCC_PREPROCESSOR_DEFINITIONS = NDEBUG=1 NS_BLOCK_ASSERTIONS=1 LOGGING_SUPPORT=1 LOGGING_TO_FILE=1 97 | OTHER_LDFLAGS = -undefined dynamic_lookup -headerpad_max_install_names -------------------------------------------------------------------------------- /OSAX.xcconfigs/OSAX_Release_TotalFinder.xcconfig: -------------------------------------------------------------------------------- 1 | //!bagen generate binaryage OSAX Release TotalFinder 2 | // 3 | // this file should be set as Release configuration for target TotalFinder in OSAX.xcodeproj 4 | 5 | // following included file can be regenerated by running 'badev regen_xcconfigs' 6 | #include "OSAX_Release_TotalFinder_generated.xcconfig" 7 | 8 | // here you can follow with your custom settings overrides if needed 9 | -------------------------------------------------------------------------------- /OSAX.xcconfigs/OSAX_Release_TotalFinder_generated.xcconfig: -------------------------------------------------------------------------------- 1 | // A GENERATED FILE by bagen utility 2 | // more info: https://github.com/binaryage/badev 3 | // 4 | // !!! DO NOT EDIT IT BY HAND !!! 5 | // 6 | 7 | // 8 | // Common settings that should be enabled for every project 9 | // 10 | CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES 11 | CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES 12 | CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES 13 | CLANG_ENABLE_OBJC_ARC = YES 14 | CLANG_WARN_BOOL_CONVERSION = YES 15 | CLANG_WARN_CONSTANT_CONVERSION = YES 16 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES 17 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 18 | CLANG_WARN_EMPTY_BODY = YES 19 | CLANG_WARN_ENUM_CONVERSION = YES 20 | CLANG_WARN_INT_CONVERSION = YES 21 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 22 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 23 | CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES 24 | GCC_WARN_UNINITIALIZED_AUTOS = YES 25 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym 26 | GCC_C_LANGUAGE_STANDARD = gnu99 27 | GCC_PRECOMPILE_PREFIX_HEADER = YES 28 | GCC_THREADSAFE_STATICS = NO 29 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0 30 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES 31 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES 32 | GCC_WARN_ABOUT_RETURN_TYPE = YES 33 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES 34 | GCC_WARN_UNINITIALIZED_AUTOS = YES 35 | GCC_WARN_UNUSED_FUNCTION = YES 36 | GCC_WARN_UNUSED_LABEL = YES 37 | GCC_WARN_UNUSED_VARIABLE = YES 38 | GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES 39 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 40 | CLANG_WARN_INFINITE_RECURSION = YES; 41 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 42 | CLANG_WARN_UNREACHABLE_CODE = YES; 43 | ENABLE_STRICT_OBJC_MSGSEND = YES; 44 | GCC_WARN_UNUSED_VARIABLE = YES; 45 | ALWAYS_SEARCH_USER_PATHS = NO 46 | PRODUCT_NAME = TotalFinder 47 | // 48 | // Base configuration for a Release build of any project 49 | // 50 | COPY_PHASE_STRIP = YES 51 | GCC_OPTIMIZATION_LEVEL = fast 52 | LLVM_LTO = YES 53 | ONLY_ACTIVE_ARCH = NO 54 | STRIP_INSTALLED_PRODUCT = YES 55 | // 56 | // Base configuration for all TotalFinder projects 57 | // 58 | MACOSX_DEPLOYMENT_TARGET = 10.9 59 | SDKROOT = macosx 60 | CLANG_CXX_LIBRARY = libc++ 61 | CLANG_CXX_LANGUAGE_STANDARD = c++17 62 | CLANG_ENABLE_MODULES = YES 63 | ENABLE_TESTABILITY = NO // this would expose some internal symbols 64 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 65 | CLANG_WARN_BOOL_CONVERSION = YES; 66 | CLANG_WARN_CONSTANT_CONVERSION = YES; 67 | CLANG_WARN_EMPTY_BODY = YES; 68 | CLANG_WARN_ENUM_CONVERSION = YES; 69 | CLANG_WARN_INFINITE_RECURSION = YES; 70 | CLANG_WARN_INT_CONVERSION = YES; 71 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 72 | CLANG_WARN_UNREACHABLE_CODE = YES; 73 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 74 | GCC_NO_COMMON_BLOCKS = YES; 75 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 76 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 77 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 78 | GCC_WARN_UNUSED_FUNCTION = YES; 79 | GCC_WARN_UNUSED_VARIABLE = YES; 80 | ENABLE_STRICT_OBJC_MSGSEND = YES; 81 | COMBINE_HIDPI_IMAGES = YES 82 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym 83 | INFOPLIST_FILE = Info.plist 84 | GCC_PREFIX_HEADER = TotalFinder.pch 85 | // 86 | // Release mode additions to TotalFinder configs 87 | // 88 | // we don't want OSAX subsystem to treat our intermediate build folder as installed osax in the system 89 | // they use SpotLight or some similar search mechanism to look for all .osax in the system 90 | WRAPPER_EXTENSION = xaso 91 | GCC_SYMBOLS_PRIVATE_EXTERN = YES 92 | // 93 | // Dynamic configuration collected from all sections above 94 | // 95 | WARNING_CFLAGS = -Wextra -Wself-assign -Wno-gcc-compat -Wno-unused-parameter -Wstrict-prototypes -Wunreachable-code -Wunused-member-function -Wuninitialized -Wsuper-class-method-mismatch -Warc -Warc-retain-cycles -Warc-unsafe-retained-assign -Warc-non-pod-memaccess -Wbind-to-temporary-copy -Wno-unused-variable -Wthread-safety -Wthread-safety-beta 96 | GCC_PREPROCESSOR_DEFINITIONS = NDEBUG=1 NS_BLOCK_ASSERTIONS=1 97 | OTHER_LDFLAGS = -undefined dynamic_lookup -headerpad_max_install_names -------------------------------------------------------------------------------- /OSAX.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D6ACBEA2117B7D5600F6691C /* TotalFinderInjector.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6ACBE9E117B7D5600F6691C /* TotalFinderInjector.mm */; }; 11 | D6ACBEA3117B7D5600F6691C /* TFStandardVersionComparator.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6ACBEA0117B7D5600F6691C /* TFStandardVersionComparator.mm */; }; 12 | D6ACBEA5117B7D6100F6691C /* TotalFinderInjector.sdef in Resources */ = {isa = PBXBuildFile; fileRef = D6ACBEA4117B7D6100F6691C /* TotalFinderInjector.sdef */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXFileReference section */ 16 | 8D576317048677EA00EA77CD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 17 | D60A992314CE37030061AD6D /* TotalFinder.xaso */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TotalFinder.xaso; sourceTree = BUILT_PRODUCTS_DIR; }; 18 | D618B015195996BD004F2F20 /* OSAX_Debug_TotalFinder.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = OSAX_Debug_TotalFinder.xcconfig; path = OSAX.xcconfigs/OSAX_Debug_TotalFinder.xcconfig; sourceTree = ""; }; 19 | D618B016195996BD004F2F20 /* OSAX_Release_TotalFinder.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = OSAX_Release_TotalFinder.xcconfig; path = OSAX.xcconfigs/OSAX_Release_TotalFinder.xcconfig; sourceTree = ""; }; 20 | D64491D61EFDC95C00CCF8EC /* TotalFinder.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TotalFinder.pch; sourceTree = ""; }; 21 | D6ACBE9E117B7D5600F6691C /* TotalFinderInjector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TotalFinderInjector.mm; sourceTree = ""; }; 22 | D6ACBE9F117B7D5600F6691C /* TFVersionComparisonProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TFVersionComparisonProtocol.h; sourceTree = ""; }; 23 | D6ACBEA0117B7D5600F6691C /* TFStandardVersionComparator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TFStandardVersionComparator.mm; sourceTree = ""; }; 24 | D6ACBEA1117B7D5600F6691C /* TFStandardVersionComparator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TFStandardVersionComparator.h; sourceTree = ""; }; 25 | D6ACBEA4117B7D6100F6691C /* TotalFinderInjector.sdef */ = {isa = PBXFileReference; fileEncoding = 1; lastKnownFileType = text.xml; path = TotalFinderInjector.sdef; sourceTree = ""; }; 26 | D6D55BAA1B0C4D1700227B08 /* OSAX_ReleaseLogging_TotalFinder.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = OSAX_ReleaseLogging_TotalFinder.xcconfig; path = OSAX.xcconfigs/OSAX_ReleaseLogging_TotalFinder.xcconfig; sourceTree = ""; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFrameworksBuildPhase section */ 30 | 8D576313048677EA00EA77CD /* Frameworks */ = { 31 | isa = PBXFrameworksBuildPhase; 32 | buildActionMask = 2147483647; 33 | files = ( 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXFrameworksBuildPhase section */ 38 | 39 | /* Begin PBXGroup section */ 40 | 089C166AFE841209C02AAC07 /* OSAX */ = { 41 | isa = PBXGroup; 42 | children = ( 43 | D672258517FA6D3900D3ACEA /* Configs */, 44 | 08FB77AFFE84173DC02AAC07 /* Source */, 45 | 089C167CFE841241C02AAC07 /* Resources */, 46 | D60A992414CE37030061AD6D /* Products */, 47 | D64491D61EFDC95C00CCF8EC /* TotalFinder.pch */, 48 | 8D576317048677EA00EA77CD /* Info.plist */, 49 | ); 50 | indentWidth = 2; 51 | name = OSAX; 52 | sourceTree = ""; 53 | tabWidth = 2; 54 | usesTabs = 0; 55 | }; 56 | 089C167CFE841241C02AAC07 /* Resources */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | D6ACBEA4117B7D6100F6691C /* TotalFinderInjector.sdef */, 60 | ); 61 | name = Resources; 62 | sourceTree = ""; 63 | }; 64 | 08FB77AFFE84173DC02AAC07 /* Source */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | D6ACBE9E117B7D5600F6691C /* TotalFinderInjector.mm */, 68 | D6ACBE9F117B7D5600F6691C /* TFVersionComparisonProtocol.h */, 69 | D6ACBEA0117B7D5600F6691C /* TFStandardVersionComparator.mm */, 70 | D6ACBEA1117B7D5600F6691C /* TFStandardVersionComparator.h */, 71 | ); 72 | name = Source; 73 | sourceTree = ""; 74 | }; 75 | D60A992414CE37030061AD6D /* Products */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | D60A992314CE37030061AD6D /* TotalFinder.xaso */, 79 | ); 80 | name = Products; 81 | sourceTree = ""; 82 | }; 83 | D672258517FA6D3900D3ACEA /* Configs */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | D6D55BAA1B0C4D1700227B08 /* OSAX_ReleaseLogging_TotalFinder.xcconfig */, 87 | D618B015195996BD004F2F20 /* OSAX_Debug_TotalFinder.xcconfig */, 88 | D618B016195996BD004F2F20 /* OSAX_Release_TotalFinder.xcconfig */, 89 | ); 90 | name = Configs; 91 | sourceTree = ""; 92 | }; 93 | /* End PBXGroup section */ 94 | 95 | /* Begin PBXNativeTarget section */ 96 | 8D57630D048677EA00EA77CD /* TotalFinder */ = { 97 | isa = PBXNativeTarget; 98 | buildConfigurationList = 1DEB911A08733D790010E9CD /* Build configuration list for PBXNativeTarget "TotalFinder" */; 99 | buildPhases = ( 100 | 8D57630F048677EA00EA77CD /* Resources */, 101 | 8D576311048677EA00EA77CD /* Sources */, 102 | 8D576313048677EA00EA77CD /* Frameworks */, 103 | ); 104 | buildRules = ( 105 | ); 106 | dependencies = ( 107 | ); 108 | name = TotalFinder; 109 | productInstallPath = "$(HOME)/Library/Bundles"; 110 | productName = OSAX; 111 | productReference = D60A992314CE37030061AD6D /* TotalFinder.xaso */; 112 | productType = "com.apple.product-type.bundle"; 113 | }; 114 | /* End PBXNativeTarget section */ 115 | 116 | /* Begin PBXProject section */ 117 | 089C1669FE841209C02AAC07 /* Project object */ = { 118 | isa = PBXProject; 119 | attributes = { 120 | BuildIndependentTargetsInParallel = YES; 121 | LastUpgradeCheck = 0900; 122 | ORGANIZATIONNAME = BinaryAge; 123 | }; 124 | buildConfigurationList = 1DEB911E08733D790010E9CD /* Build configuration list for PBXProject "OSAX" */; 125 | compatibilityVersion = "Xcode 8.0"; 126 | developmentRegion = English; 127 | hasScannedForEncodings = 1; 128 | knownRegions = ( 129 | en, 130 | English, 131 | ); 132 | mainGroup = 089C166AFE841209C02AAC07 /* OSAX */; 133 | productRefGroup = D60A992414CE37030061AD6D /* Products */; 134 | projectDirPath = ""; 135 | projectRoot = ""; 136 | targets = ( 137 | 8D57630D048677EA00EA77CD /* TotalFinder */, 138 | ); 139 | }; 140 | /* End PBXProject section */ 141 | 142 | /* Begin PBXResourcesBuildPhase section */ 143 | 8D57630F048677EA00EA77CD /* Resources */ = { 144 | isa = PBXResourcesBuildPhase; 145 | buildActionMask = 2147483647; 146 | files = ( 147 | D6ACBEA5117B7D6100F6691C /* TotalFinderInjector.sdef in Resources */, 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | /* End PBXResourcesBuildPhase section */ 152 | 153 | /* Begin PBXSourcesBuildPhase section */ 154 | 8D576311048677EA00EA77CD /* Sources */ = { 155 | isa = PBXSourcesBuildPhase; 156 | buildActionMask = 2147483647; 157 | files = ( 158 | D6ACBEA2117B7D5600F6691C /* TotalFinderInjector.mm in Sources */, 159 | D6ACBEA3117B7D5600F6691C /* TFStandardVersionComparator.mm in Sources */, 160 | ); 161 | runOnlyForDeploymentPostprocessing = 0; 162 | }; 163 | /* End PBXSourcesBuildPhase section */ 164 | 165 | /* Begin XCBuildConfiguration section */ 166 | 1DEB911B08733D790010E9CD /* Debug */ = { 167 | isa = XCBuildConfiguration; 168 | baseConfigurationReference = D618B015195996BD004F2F20 /* OSAX_Debug_TotalFinder.xcconfig */; 169 | buildSettings = { 170 | }; 171 | name = Debug; 172 | }; 173 | 1DEB911C08733D790010E9CD /* Release */ = { 174 | isa = XCBuildConfiguration; 175 | baseConfigurationReference = D618B016195996BD004F2F20 /* OSAX_Release_TotalFinder.xcconfig */; 176 | buildSettings = { 177 | }; 178 | name = Release; 179 | }; 180 | 1DEB911F08733D790010E9CD /* Debug */ = { 181 | isa = XCBuildConfiguration; 182 | buildSettings = { 183 | ENABLE_TESTABILITY = YES; 184 | ONLY_ACTIVE_ARCH = YES; 185 | }; 186 | name = Debug; 187 | }; 188 | 1DEB912008733D790010E9CD /* Release */ = { 189 | isa = XCBuildConfiguration; 190 | buildSettings = { 191 | }; 192 | name = Release; 193 | }; 194 | D6D55B661B0C3D2E00227B08 /* ReleaseLogging */ = { 195 | isa = XCBuildConfiguration; 196 | buildSettings = { 197 | }; 198 | name = ReleaseLogging; 199 | }; 200 | D6D55B671B0C3D2E00227B08 /* ReleaseLogging */ = { 201 | isa = XCBuildConfiguration; 202 | baseConfigurationReference = D6D55BAA1B0C4D1700227B08 /* OSAX_ReleaseLogging_TotalFinder.xcconfig */; 203 | buildSettings = { 204 | }; 205 | name = ReleaseLogging; 206 | }; 207 | /* End XCBuildConfiguration section */ 208 | 209 | /* Begin XCConfigurationList section */ 210 | 1DEB911A08733D790010E9CD /* Build configuration list for PBXNativeTarget "TotalFinder" */ = { 211 | isa = XCConfigurationList; 212 | buildConfigurations = ( 213 | 1DEB911B08733D790010E9CD /* Debug */, 214 | 1DEB911C08733D790010E9CD /* Release */, 215 | D6D55B671B0C3D2E00227B08 /* ReleaseLogging */, 216 | ); 217 | defaultConfigurationIsVisible = 0; 218 | defaultConfigurationName = Release; 219 | }; 220 | 1DEB911E08733D790010E9CD /* Build configuration list for PBXProject "OSAX" */ = { 221 | isa = XCConfigurationList; 222 | buildConfigurations = ( 223 | 1DEB911F08733D790010E9CD /* Debug */, 224 | 1DEB912008733D790010E9CD /* Release */, 225 | D6D55B661B0C3D2E00227B08 /* ReleaseLogging */, 226 | ); 227 | defaultConfigurationIsVisible = 0; 228 | defaultConfigurationName = Release; 229 | }; 230 | /* End XCConfigurationList section */ 231 | }; 232 | rootObject = 089C1669FE841209C02AAC07 /* Project object */; 233 | } 234 | -------------------------------------------------------------------------------- /TFStandardVersionComparator.h: -------------------------------------------------------------------------------- 1 | // 2 | // TFStandardVersionComparator.h 3 | // Sparkle 4 | // 5 | // Created by Andy Matuschak on 12/21/07. 6 | // Copyright 2007 Andy Matuschak. All rights reserved. 7 | // 8 | 9 | #ifndef TFSTANDARDVERSIONCOMPARATOR_H 10 | #define TFSTANDARDVERSIONCOMPARATOR_H 11 | 12 | #import "TFVersionComparisonProtocol.h" 13 | 14 | /*! 15 | @class 16 | @abstract Sparkle's default version comparator. 17 | @discussion This comparator is adapted from MacPAD, by Kevin Ballard. It's "dumb" in that it does essentially string comparison, in components split by 18 | character type. 19 | */ 20 | @interface TFStandardVersionComparator : NSObject { 21 | } 22 | 23 | /*! 24 | @method 25 | @abstract Returns a singleton instance of the comparator. 26 | */ 27 | + (TFStandardVersionComparator*)defaultComparator; 28 | 29 | /*! 30 | @method 31 | @abstract Compares version strings through textual analysis. 32 | @discussion See the implementation for more details. 33 | */ 34 | - (NSComparisonResult)compareVersion:(NSString*)versionA toVersion:(NSString*)versionB; 35 | @end 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /TFStandardVersionComparator.mm: -------------------------------------------------------------------------------- 1 | // 2 | // TFStandardVersionComparator.m 3 | // Sparkle 4 | // 5 | // Created by Andy Matuschak on 12/21/07. 6 | // Copyright 2007 Andy Matuschak. All rights reserved. 7 | // 8 | 9 | #import "TFStandardVersionComparator.h" 10 | 11 | @implementation TFStandardVersionComparator 12 | 13 | + (TFStandardVersionComparator*)defaultComparator { 14 | static TFStandardVersionComparator* defaultComparator = nil; 15 | 16 | if (defaultComparator == nil) 17 | defaultComparator = [[TFStandardVersionComparator alloc] init]; 18 | return defaultComparator; 19 | } 20 | 21 | typedef NS_ENUM(NSInteger, SUCharacterType) { kNumberType, kStringType, kPeriodType }; 22 | 23 | - (SUCharacterType)typeOfCharacter:(NSString*)character { 24 | if ([character isEqualToString:@"."]) { 25 | return kPeriodType; 26 | } else if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:[character characterAtIndex:0]]) { 27 | return kNumberType; 28 | } else { 29 | return kStringType; 30 | } 31 | } 32 | 33 | - (NSArray*)splitVersionString:(NSString*)version { 34 | NSString* character; 35 | NSMutableString* s; 36 | NSUInteger i, n; 37 | SUCharacterType oldType, newType; 38 | NSMutableArray* parts = [NSMutableArray array]; 39 | 40 | if ([version length] == 0) { 41 | // Nothing to do here 42 | return parts; 43 | } 44 | s = [[version substringToIndex:1] mutableCopy]; 45 | oldType = [self typeOfCharacter:s]; 46 | n = [version length] - 1; 47 | for (i = 1; i <= n; ++i) { 48 | character = [version substringWithRange:NSMakeRange(i, 1)]; 49 | newType = [self typeOfCharacter:character]; 50 | if ((oldType != newType) || (oldType == kPeriodType)) { 51 | // We've reached a new segment 52 | NSString* aPart = [[NSString alloc] initWithString:s]; 53 | [parts addObject:aPart]; 54 | [s setString:character]; 55 | } else { 56 | // Add character to string and continue 57 | [s appendString:character]; 58 | } 59 | oldType = newType; 60 | } 61 | 62 | // Add the last part onto the array 63 | [parts addObject:[NSString stringWithString:s]]; 64 | return parts; 65 | } 66 | 67 | - (NSComparisonResult)compareVersion:(NSString*)versionA toVersion:(NSString*)versionB { 68 | NSArray* partsA = [self splitVersionString:versionA]; 69 | NSArray* partsB = [self splitVersionString:versionB]; 70 | 71 | NSString *partA, *partB; 72 | NSUInteger i, n; 73 | int intA, intB; 74 | SUCharacterType typeA, typeB; 75 | 76 | n = MIN([partsA count], [partsB count]); 77 | for (i = 0; i < n; ++i) { 78 | partA = partsA[i]; 79 | partB = partsB[i]; 80 | 81 | typeA = [self typeOfCharacter:partA]; 82 | typeB = [self typeOfCharacter:partB]; 83 | 84 | // Compare types 85 | if (typeA == typeB) { 86 | // Same type; we can compare 87 | if (typeA == kNumberType) { 88 | intA = [partA intValue]; 89 | intB = [partB intValue]; 90 | if (intA > intB) { 91 | return NSOrderedDescending; 92 | } else if (intA < intB) { 93 | return NSOrderedAscending; 94 | } 95 | } else if (typeA == kStringType) { 96 | NSComparisonResult result = [partA compare:partB]; 97 | if (result != NSOrderedSame) { 98 | return result; 99 | } 100 | } 101 | } else { 102 | // Not the same type? Now we have to do some validity checking 103 | if ((typeA != kStringType) && (typeB == kStringType)) { 104 | // typeA wins 105 | return NSOrderedDescending; 106 | } else if ((typeA == kStringType) && (typeB != kStringType)) { 107 | // typeB wins 108 | return NSOrderedAscending; 109 | } else { 110 | // One is a number and the other is a period. The period is invalid 111 | if (typeA == kNumberType) { 112 | return NSOrderedDescending; 113 | } else { 114 | return NSOrderedAscending; 115 | } 116 | } 117 | } 118 | } 119 | // The versions are equal up to the point where they both still have parts 120 | // Lets check to see if one is larger than the other 121 | if ([partsA count] != [partsB count]) { 122 | // Yep. Lets get the next part of the larger 123 | // n holds the index of the part we want. 124 | NSString* missingPart; 125 | SUCharacterType missingType; 126 | NSComparisonResult shorterResult, largerResult; 127 | 128 | if ([partsA count] > [partsB count]) { 129 | missingPart = partsA[n]; 130 | shorterResult = NSOrderedAscending; 131 | largerResult = NSOrderedDescending; 132 | } else { 133 | missingPart = partsB[n]; 134 | shorterResult = NSOrderedDescending; 135 | largerResult = NSOrderedAscending; 136 | } 137 | 138 | missingType = [self typeOfCharacter:missingPart]; 139 | // Check the type 140 | if (missingType == kStringType) { 141 | // It's a string. Shorter version wins 142 | return shorterResult; 143 | } else { 144 | // It's a number/period. Larger version wins 145 | return largerResult; 146 | } 147 | } 148 | 149 | // The 2 strings are identical 150 | return NSOrderedSame; 151 | } 152 | 153 | @end 154 | -------------------------------------------------------------------------------- /TFVersionComparisonProtocol.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @protocol TFVersionComparison 4 | 5 | - (NSComparisonResult)compareVersion:(NSString*)versionA toVersion:(NSString*)versionB; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /TotalFinder.pch: -------------------------------------------------------------------------------- 1 | #import 2 | -------------------------------------------------------------------------------- /TotalFinderInjector.mm: -------------------------------------------------------------------------------- 1 | #import "TFStandardVersionComparator.h" 2 | 3 | #if !defined(DEBUG) 4 | #define CHECK_SIGNATURE 1 5 | #endif 6 | 7 | #define EXPORT extern "C" __attribute__((visibility("default"))) __attribute__((used)) 8 | 9 | #define TOTALFINDER_INSTALL_LOCATION_CONFIG_PATH "~/.totalfinder-install-location" 10 | #define TOTALFINDER_STANDARD_BUNDLE_LOCATION "/Applications/TotalFinder.app/Contents/Resources/TotalFinder.bundle" 11 | #define TOTALFINDER_DEV_BUNDLE_LOCATION "~/Applications/TotalFinder.app/Contents/Resources/TotalFinder.bundle" 12 | #define TOTALFINDER_OSAX_BUNDLE_LOCATION "/Library/ScriptingAdditions/TotalFinder.osax/Contents/Resources/TotalFinder.bundle" 13 | #define TOTALFINDER_SYSTEM_OSAX_BUNDLE_LOCATION "/System/Library/ScriptingAdditions/TotalFinder.osax/Contents/Resources/TotalFinder.bundle" 14 | #define TOTALFINDER_USER_OSAX_BUNDLE_LOCATION "~/Library/ScriptingAdditions/TotalFinder.osax/Contents/Resources/TotalFinder.bundle" 15 | #define TOTALFINDER_INJECTED_NOTIFICATION @"TotalFinderInjectedNotification" 16 | #define TOTALFINDER_FAILED_INJECTION_NOTIFICATION @"TotalFinderFailedInjectionNotification" 17 | 18 | static NSString* globalLock = @"I'm the global lock to prevent concurrent handler executions"; 19 | static bool totalFinderAlreadyLoaded = false; 20 | static Class gPrincipalClass = nil; 21 | 22 | // Imagine this code: 23 | // 24 | // NSString* source = @"tell application \"Finder\" to «event BATFinit»"; 25 | // NSAppleScript* appleScript = [[NSAppleScript alloc] initWithSource:source]; 26 | // [appleScript executeAndReturnError:nil]; 27 | // 28 | // Force-quit Finder.app, wait for plain Finder.app to be relaunched by launchd, execute this code... 29 | // 30 | // On my machine (OS X 10.8.4-12E55) it sends following 4 events to the Finder process: 31 | // 32 | // aevt('BATF'\'init' transactionID=0 returnID=29128 sourcePSN=[0x0,202202 "Finder"] timeout=7200 eventSource=3 { &'subj':null(), &'csig':magn(65536) }) 33 | // aevt('ascr'\'gdut' transactionID=0 returnID=23693 sourcePSN=[0x0,202202 "Finder"] timeout=7200 eventSource=3 { }) 34 | // aevt('BATF'\'init' transactionID=0 returnID=29128 sourcePSN=[0x0,202202 "Finder"] timeout=7200 eventSource=3 { &'subj':null(), &'csig':magn(65536) }) 35 | // aevt('BATF'\'init' transactionID=0 returnID=29128 sourcePSN=[0x0,202202 "Finder"] timeout=7200 eventSource=3 { &'subj':null(), &'csig':magn(65536), 36 | // &'autx':autx('autx'(368CEB26DFB7FE807CA5860100000000000000000000000000000000000000000036)) }) 37 | // 38 | // 39 | // My explanation (pure speculation): 40 | // 41 | // 1. First, it naively fails (-1708) 42 | // 2. Then it tries to load dynamic additions (http://developer.apple.com/library/mac/#qa/qa1070/_index.html) 43 | // 3. Then it tries again but fails because the Finder requires "signature" (-10004) 44 | // 4. Finally it signs the event, sends it again and it succeeds 45 | // 46 | // Ok, this works, so why do we need a better solution? 47 | // 48 | // quite some people have had troubles injecting TotalFinder during startup using applescript. 49 | // I don't know what is wrong with their machines or applescript subsystem, but they were getting: 50 | // "Connection is Invalid -609" or "The operation could not be completed -1708" or some other mysterious applescript error codes. 51 | // 52 | // Here are several possible scenarios: 53 | // 54 | // 1. system is busy, Finder process is busy or applescriptd is busy => timeout 55 | // 2. Finder crashed during startup, got (potentially) restarted, but applescript subsystem caches handle and is unable to deliver events 56 | // 3. our script is too fast and finished launching before Finder.app itself entered main loop => unexpected timing errors 57 | // 4. some other similar issue 58 | // 59 | // A more robust solution? 60 | // 61 | // 1. Don't use high-level applescript. Send raw events using lowest level API available (AESendMessage). 62 | // 2. Don't deal with timeouts, don't wait for replies and don't process errors. 63 | // 3. Wait for Finder.app to fully launch. 64 | // 4. Try multiple times. 65 | // 5. Enable excessive debug logging for troubleshooting 66 | // 67 | 68 | static void broadcastNotification(NSString* notification) { 69 | pid_t pid = [[NSProcessInfo processInfo] processIdentifier]; 70 | 71 | [[NSDistributedNotificationCenter defaultCenter] postNotificationName:notification 72 | object:[[NSBundle mainBundle] bundleIdentifier] 73 | userInfo:@{ 74 | @"pid" : @(pid) 75 | } 76 | deliverImmediately:YES]; 77 | } 78 | 79 | static void broadcastSuccessfulInjection() { broadcastNotification(TOTALFINDER_INJECTED_NOTIFICATION); } 80 | 81 | static void broadcastUnsuccessfulInjection() { broadcastNotification(TOTALFINDER_FAILED_INJECTION_NOTIFICATION); } 82 | 83 | // SIMBL-compatible interface 84 | @interface TotalFinder : NSObject { 85 | } 86 | + (void)install; 87 | @end 88 | 89 | // just a dummy class for locating our bundle 90 | @interface TotalFinderInjector : NSObject { 91 | } 92 | @end 93 | 94 | @implementation TotalFinderInjector 95 | @end 96 | 97 | static OSErr AEPutParamString(AppleEvent* event, AEKeyword keyword, NSString* string) { 98 | UInt8* textBuf; 99 | size_t maxBytes; 100 | CFIndex length, actualBytes; 101 | 102 | length = CFStringGetLength((__bridge CFStringRef)string); 103 | maxBytes = (size_t)CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8); 104 | 105 | textBuf = (UInt8*)malloc(maxBytes); 106 | if (!textBuf) { 107 | return memFullErr; 108 | } 109 | 110 | CFStringGetBytes((__bridge CFStringRef)string, CFRangeMake(0, length), kCFStringEncodingUTF8, 0, true, textBuf, maxBytes, &actualBytes); 111 | OSErr err = AEPutParamPtr(event, keyword, typeUTF8Text, textBuf, actualBytes); 112 | free(textBuf); 113 | return err; 114 | } 115 | 116 | static void reportError(AppleEvent* reply, NSString* msg) { 117 | NSLog(@"TotalFinderInjector: %@", msg); 118 | AEPutParamString(reply, keyErrorString, msg); 119 | } 120 | 121 | // this is just a sanity checking to catch missing methods early 122 | static int performSelfCheck() { 123 | if (!gPrincipalClass) { 124 | return 1; 125 | } 126 | 127 | if (![gPrincipalClass respondsToSelector:@selector(sharedInstance)]) { 128 | return 2; 129 | } 130 | 131 | TotalFinder* instance = [gPrincipalClass sharedInstance]; 132 | if (!instance) { 133 | return 3; 134 | } 135 | 136 | return 0; 137 | } 138 | 139 | #if defined(CHECK_SIGNATURE) 140 | static NSString* checkSignature(CFURLRef bundleURL, CFStringRef requirementString) { 141 | CFErrorRef error = NULL; 142 | SecStaticCodeRef staticCode = NULL; 143 | SecStaticCodeCreateWithPath(bundleURL, kSecCSDefaultFlags, &staticCode); 144 | 145 | if (!staticCode) { 146 | return @"SecStaticCodeCreateWithPath returned no staticCode"; 147 | } 148 | 149 | SecRequirementRef requirementRef = NULL; 150 | OSStatus requirementCreateStatus = SecRequirementCreateWithStringAndErrors(requirementString, kSecCSDefaultFlags, &error, &requirementRef); 151 | if (error) { 152 | if (requirementRef) { 153 | CFRelease(requirementRef); 154 | } 155 | NSString* result = [NSString stringWithFormat:@"SecRequirementCreateWithStringAndErrors reported %@", error]; 156 | CFRelease(error); 157 | return result; 158 | } 159 | 160 | if (requirementCreateStatus != errSecSuccess) { 161 | if (requirementRef) { 162 | CFRelease(requirementRef); 163 | } 164 | return [NSString stringWithFormat:@"SecRequirementCreateWithString returned %d)", requirementCreateStatus]; 165 | } 166 | 167 | SecCSFlags flags = (SecCSFlags)(kSecCSDefaultFlags | kSecCSCheckAllArchitectures | kSecCSCheckNestedCode); 168 | OSStatus signatureCheckResult = SecStaticCodeCheckValidityWithErrors(staticCode, flags, requirementRef, &error); 169 | CFRelease(requirementRef); 170 | CFRelease(staticCode); 171 | 172 | if (error) { 173 | NSString* result = [NSString stringWithFormat:@"SecStaticCodeCheckValidityWithErrors reported %@", error]; 174 | CFRelease(error); 175 | return result; 176 | } 177 | 178 | if (signatureCheckResult != errSecSuccess) { 179 | return [NSString stringWithFormat:@"SecStaticCodeCheckValidityWithErrors returned %d", signatureCheckResult]; 180 | } 181 | 182 | return nil; 183 | } 184 | 185 | @interface CorruptionNotificationDelegate : NSObject { 186 | } 187 | @end 188 | 189 | @implementation CorruptionNotificationDelegate 190 | 191 | - (BOOL)userNotificationCenter:(NSUserNotificationCenter*)center shouldPresentNotification:(NSUserNotification*)notification { 192 | return YES; 193 | } 194 | 195 | - (void)userNotificationCenter:(NSUserNotificationCenter*)center didActivateNotification:(NSUserNotification*)notification { 196 | [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://totalfinder.binaryage.com"]]; 197 | } 198 | 199 | @end 200 | 201 | void displayCorruptionNotificationIfNeeded() { 202 | static bool alreadyPresented = false; 203 | static id delegate = nil; 204 | if (alreadyPresented) { 205 | return; 206 | } 207 | alreadyPresented = true; 208 | NSUserNotification* notification = [[NSUserNotification alloc] init]; 209 | notification.title = @"TotalFinder is corrupted"; 210 | notification.informativeText = @"A code signature check failed.\nPlease reinstall TotalFinder."; 211 | notification.hasActionButton = YES; 212 | notification.actionButtonTitle = @"Download"; 213 | NSUserNotificationCenter* notificationCenter = [NSUserNotificationCenter defaultUserNotificationCenter]; 214 | if (!delegate) { 215 | delegate = [[CorruptionNotificationDelegate alloc] init]; 216 | } 217 | notificationCenter.delegate = delegate; 218 | [notificationCenter deliverNotification:notification]; 219 | } 220 | #endif 221 | 222 | static bool checkExistenceOfTotalFinderBundleAtPath(NSString* path) { 223 | NSFileManager* fileManager = [NSFileManager defaultManager]; 224 | BOOL dir = FALSE; 225 | if ([fileManager fileExistsAtPath:path isDirectory:&dir]) { 226 | if (!dir) { 227 | NSLog(@"TotalFinderInjector: unexpected situation, filesystem path exists but it is not a directory: %@", path); 228 | return false; 229 | } 230 | if (![fileManager isReadableFileAtPath:path]) { 231 | NSLog(@"TotalFinderInjector: unexpected situation, filesystem path exists but it is not readable: %@", path); 232 | return false; 233 | } 234 | return true; 235 | } 236 | return false; 237 | } 238 | 239 | static NSString* determineTotalFinderBundlePath() { 240 | // config file can override standard installation location 241 | NSFileManager* fileManager = [NSFileManager defaultManager]; 242 | NSString* installLocationConfigPath = [@TOTALFINDER_INSTALL_LOCATION_CONFIG_PATH stringByStandardizingPath]; 243 | if ([fileManager fileExistsAtPath:installLocationConfigPath]) { 244 | NSData* configData = [fileManager contentsAtPath:installLocationConfigPath]; 245 | if (configData) { 246 | NSString* content = [[NSString alloc] initWithData:configData encoding:NSUTF8StringEncoding]; 247 | if (content && [content length]) { 248 | if (checkExistenceOfTotalFinderBundleAtPath(content)) { 249 | return content; 250 | } else { 251 | NSLog(@"TotalFinderInjector: install location specified path which does not point to existing TotalFinder.bundle\nconfig file:%@\nspecified bundle " 252 | @"path:%@", 253 | installLocationConfigPath, content); 254 | } 255 | } else { 256 | NSLog(@"TotalFinderInjector: unable to read content of %@", installLocationConfigPath); 257 | } 258 | } else { 259 | NSLog(@"TotalFinderInjector: unable to read installation location from %@", installLocationConfigPath); 260 | } 261 | } 262 | 263 | NSString* path; 264 | 265 | #if defined(DEBUG) 266 | // this is used during development 267 | path = [@TOTALFINDER_DEV_BUNDLE_LOCATION stringByStandardizingPath]; 268 | if (checkExistenceOfTotalFinderBundleAtPath(path)) { 269 | return path; 270 | } 271 | #endif 272 | 273 | // this location is standard since TotalFinder 1.7.13, TotalFinder.bundle is located in TotalFinder.app's resources 274 | path = [@TOTALFINDER_STANDARD_BUNDLE_LOCATION stringByStandardizingPath]; 275 | if (checkExistenceOfTotalFinderBundleAtPath(path)) { 276 | return path; 277 | } 278 | 279 | // prior TotalFinder 1.7.13, budle was included in the OSAX 280 | path = [@TOTALFINDER_OSAX_BUNDLE_LOCATION stringByStandardizingPath]; 281 | if (checkExistenceOfTotalFinderBundleAtPath(path)) { 282 | return path; 283 | } 284 | 285 | // this is a special case if someone decided to move TotalFinder.bundle under system osax location for some reason 286 | path = [@TOTALFINDER_SYSTEM_OSAX_BUNDLE_LOCATION stringByStandardizingPath]; 287 | if (checkExistenceOfTotalFinderBundleAtPath(path)) { 288 | return path; 289 | } 290 | 291 | // this is a special case if someone decided to move TotalFinder.bundle under user osax location for some reason (we use this during development) 292 | path = [@TOTALFINDER_USER_OSAX_BUNDLE_LOCATION stringByStandardizingPath]; 293 | if (checkExistenceOfTotalFinderBundleAtPath(path)) { 294 | return path; 295 | } 296 | 297 | return nil; 298 | } 299 | 300 | EXPORT OSErr HandleInitEvent(const AppleEvent* __unused ev, AppleEvent* reply, long __unused refcon) { 301 | @synchronized(globalLock) { 302 | @autoreleasepool { 303 | NSString* targetAppName = @"Finder"; 304 | NSString* bundleName = @"TotalFinder"; 305 | TFStandardVersionComparator* comparator = [TFStandardVersionComparator defaultComparator]; 306 | NSBundle* injectorBundle = [NSBundle bundleForClass:[TotalFinderInjector class]]; 307 | id injectorVersion = [injectorBundle objectForInfoDictionaryKey:@"CFBundleVersion"]; 308 | 309 | if (!injectorVersion || ![injectorVersion isKindOfClass:[NSString class]]) { 310 | reportError(reply, [NSString stringWithFormat:@"Unable to determine TotalFinderInjector version!"]); 311 | return 11; 312 | } 313 | 314 | NSString* injectorBundlePath = [injectorBundle bundlePath]; 315 | NSLog(@"TotalFinderInjector v%@ received init event (%@)", injectorVersion, injectorBundlePath); 316 | 317 | if (totalFinderAlreadyLoaded) { 318 | NSLog(@"TotalFinderInjector: %@ has been already loaded. Ignoring this request.", bundleName); 319 | broadcastSuccessfulInjection(); // prevent continuous injection 320 | return noErr; 321 | } 322 | 323 | NSString* totalFinderBundlePath = determineTotalFinderBundlePath(); 324 | if (!totalFinderBundlePath) { 325 | NSLog(@"TotalFinderInjector: unable to determine location of TotalFinder.bundle (likely a corrupted TotalFinder installation)."); 326 | return 12; 327 | } 328 | 329 | @try { 330 | 331 | #if !defined(CHECK_SIGNATURE) 332 | NSLog(@"TotalFinderInjector: skipped signature check because compiled without CHECK_SIGNATURE"); 333 | #else 334 | NSURL* totalFinderBundleURL = [NSURL fileURLWithPath:totalFinderBundlePath]; 335 | static CFStringRef injectorRequirement = CFSTR( 336 | "anchor apple generic and identifier com.binaryage.totalfinder and certificate leaf[subject.O] = \"BinaryAge Limited\""); 337 | NSString* signatureError = checkSignature((__bridge CFURLRef)totalFinderBundleURL, injectorRequirement); 338 | if (signatureError) { 339 | displayCorruptionNotificationIfNeeded(); 340 | reportError(reply, [NSString stringWithFormat:@"Invalid code signature of '%@'.\n%@", totalFinderBundlePath, signatureError]); 341 | return 14; 342 | } 343 | #endif 344 | 345 | NSBundle* totalFinderBundle = [NSBundle bundleWithPath:totalFinderBundlePath]; 346 | if (!totalFinderBundle) { 347 | reportError(reply, [NSString stringWithFormat:@"Unable to create bundle from path: %@", totalFinderBundlePath]); 348 | return 2; 349 | } 350 | 351 | id maxTestedVersion = [totalFinderBundle objectForInfoDictionaryKey:@"FinderMaxTestedVersion"]; 352 | if (!maxTestedVersion || ![maxTestedVersion isKindOfClass:[NSString class]]) { 353 | maxTestedVersion = nil; 354 | } 355 | 356 | id minTestedVersion = [totalFinderBundle objectForInfoDictionaryKey:@"FinderMinTestedVersion"]; 357 | if (!minTestedVersion || ![minTestedVersion isKindOfClass:[NSString class]]) { 358 | minTestedVersion = nil; 359 | } 360 | 361 | id unsupportedVersion = [totalFinderBundle objectForInfoDictionaryKey:@"FinderUnsupportedVersion"]; 362 | if (!unsupportedVersion || ![unsupportedVersion isKindOfClass:[NSString class]]) { 363 | unsupportedVersion = nil; 364 | } 365 | 366 | NSBundle* mainBundle = [NSBundle mainBundle]; 367 | if (!mainBundle) { 368 | reportError(reply, [NSString stringWithFormat:@"Unable to locate main %@ bundle!", targetAppName]); 369 | return 4; 370 | } 371 | 372 | id mainVersion = [mainBundle objectForInfoDictionaryKey:@"CFBundleVersion"]; 373 | if (!mainVersion || ![mainVersion isKindOfClass:[NSString class]]) { 374 | reportError(reply, [NSString stringWithFormat:@"Unable to determine %@ version!", targetAppName]); 375 | return 5; 376 | } 377 | 378 | // future versions from some point can be explicitely unsupported 379 | if (unsupportedVersion) { 380 | NSComparisonResult comparatorResult = [comparator compareVersion:mainVersion toVersion:unsupportedVersion]; 381 | if (comparatorResult == NSOrderedDescending || comparatorResult == NSOrderedSame) { 382 | NSLog(@"TotalFinderInjector: You have %@ version %@. But %@ was marked as unsupported with %@ since version %@.", targetAppName, mainVersion, 383 | bundleName, targetAppName, unsupportedVersion); 384 | 385 | // TODO: maybe we want to use a system notification to inform the user here 386 | return 13; 387 | } 388 | } 389 | 390 | // warn about non-tested minor versions into the log only 391 | BOOL maxTestFailed = maxTestedVersion && [comparator compareVersion:mainVersion toVersion:maxTestedVersion] == NSOrderedDescending; 392 | BOOL minTestFailed = minTestedVersion && [comparator compareVersion:mainVersion toVersion:minTestedVersion] == NSOrderedAscending; 393 | if (maxTestFailed || minTestFailed) { 394 | NSLog(@"TotalFinderInjector: You have %@ version %@. But %@ was properly tested only with %@ versions in range %@ - %@.", targetAppName, mainVersion, 395 | bundleName, targetAppName, minTestedVersion ? minTestedVersion : @"*", maxTestedVersion ? maxTestedVersion : @"*"); 396 | } 397 | 398 | NSLog(@"TotalFinderInjector: Installing TotalFinder from %@", totalFinderBundlePath); 399 | NSError* error; 400 | if (![totalFinderBundle loadAndReturnError:&error]) { 401 | reportError(reply, [NSString stringWithFormat:@"Unable to load bundle from path: %@ error: %@ [code=%ld]", totalFinderBundlePath, 402 | [error localizedDescription], (long)[error code]]); 403 | return 6; 404 | } 405 | gPrincipalClass = [totalFinderBundle principalClass]; 406 | if (!gPrincipalClass) { 407 | reportError(reply, [NSString stringWithFormat:@"Unable to retrieve principalClass for bundle: %@", totalFinderBundle]); 408 | return 3; 409 | } 410 | 411 | if (![gPrincipalClass respondsToSelector:@selector(install)]) { 412 | reportError(reply, [NSString stringWithFormat:@"TotalFinder's principal class does not implement 'install' method!"]); 413 | return 7; 414 | } 415 | 416 | [gPrincipalClass install]; 417 | 418 | int selfCheckCode = performSelfCheck(); 419 | if (selfCheckCode) { 420 | reportError(reply, [NSString stringWithFormat:@"Self-check failed with code %d", selfCheckCode]); 421 | return 10; 422 | } 423 | 424 | totalFinderAlreadyLoaded = true; 425 | broadcastSuccessfulInjection(); 426 | 427 | return noErr; 428 | } @catch (NSException* exception) { 429 | reportError(reply, [NSString stringWithFormat:@"Failed to load %@ with exception: %@", bundleName, exception]); 430 | broadcastUnsuccessfulInjection(); // stops subsequent attempts 431 | } 432 | 433 | return 1; 434 | } 435 | } 436 | } 437 | 438 | EXPORT OSErr HandleCheckEvent(const AppleEvent* __unused ev, AppleEvent* reply, long __unused refcon) { 439 | @synchronized(globalLock) { 440 | @autoreleasepool { 441 | if (totalFinderAlreadyLoaded) { 442 | return noErr; 443 | } 444 | 445 | reportError(reply, @"TotalFinder not loaded"); 446 | return 1; 447 | } 448 | } 449 | } 450 | 451 | // debug command to emulate a crash in our code 452 | EXPORT OSErr HandleCrashEvent(const AppleEvent* __unused ev, AppleEvent* reply, long __unused refcon) { 453 | @synchronized(globalLock) { 454 | @autoreleasepool { 455 | if (!totalFinderAlreadyLoaded) { 456 | return 1; 457 | } 458 | 459 | TotalFinder* shell = [gPrincipalClass sharedInstance]; 460 | if (!shell) { 461 | reportError(reply, [NSString stringWithFormat:@"Unable to retrieve shell class"]); 462 | return 3; 463 | } 464 | 465 | abort(); 466 | } 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /TotalFinderInjector.sdef: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2013, BinaryAge Limited 2 | Contributors: https://github.com/binaryage/totalfinder-osax/contributors 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of Antonin Hildebrand nor the 13 | names of other contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY BINARYAGE LIMITED ``AS IS'' AND ANY 17 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL Antonin Hildebrand BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # TotalFinder.osax 2 | 3 | This source code implements scripting additions used by [TotalFinder](http://totalfinder.binaryage.com). 4 | 5 | **TotalFinder** is a plugin for Apples's Finder.app which brings tabs, dual panels and more! 6 | 7 | 8 | 9 | ## Is this a replacement for SIMBL? 10 | 11 | Yes, this is SIMBL-lite tailored specifically for TotalFinder. 12 | 13 | You might want to read this article about my motivations: 14 | [http://blog.binaryage.com/totalfinder-without-simbl](http://blog.binaryage.com/totalfinder-without-simbl) 15 | 16 | ## BATFinit event 17 | 18 | Installs TotalFinder.bundle into running Finder.app (/Applications/TotalFinder.app is just a wrapper app for this script) 19 | 20 | ```AppleScript 21 | tell application "Finder" 22 | -- give Finder some time to launch if it wasn't running (rare case) 23 | delay 1 -- this delay is important to prevent random "Connection is Invalid -609" AppleScript errors 24 | try 25 | «event BATFinit» 26 | on error msg number num 27 | display dialog "Unable to launch TotalFinder." & msg & " (" & (num as text) & ")" 28 | end try 29 | end tell 30 | ``` 31 | 32 | ## BATFchck event 33 | 34 | Check if TotalFinder is present in running Finder image. 35 | 36 | ```AppleScript 37 | tell application "Finder" 38 | -- give Finder some time to launch if it wasn't running (rare case) 39 | delay 1 -- this delay is important to prevent random "Connection is Invalid -609" AppleScript errors 40 | try 41 | «event BATFchck» 42 | set res to "present" 43 | on error msg number num 44 | set res to "not present" 45 | end try 46 | res 47 | end tell 48 | ``` 49 | --------------------------------------------------------------------------------