├── .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 |
--------------------------------------------------------------------------------