No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

main.mm 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Unity Technologies.
  3. * Copyright (c) Microsoft Corporation. All rights reserved.
  4. * Licensed under the MIT License. See License.txt in the project root for license information.
  5. *--------------------------------------------------------------------------------------------*/
  6. #import <Cocoa/Cocoa.h>
  7. #import <Foundation/Foundation.h>
  8. // 'FSnd' FourCC
  9. #define keyFileSender 1179872868
  10. // 16 bit aligned legacy struct - this should total 20 bytes
  11. typedef struct _SelectionRange
  12. {
  13. int16_t unused1; // 0 (not used)
  14. int16_t lineNum; // line to select (<0 to specify range)
  15. int32_t startRange; // start of selection range (if line < 0)
  16. int32_t endRange; // end of selection range (if line < 0)
  17. int32_t unused2; // 0 (not used)
  18. int32_t theDate; // modification date/time
  19. } __attribute__((packed)) SelectionRange;
  20. static NSString* MakeNSString(const char* str)
  21. {
  22. if (!str)
  23. return NULL;
  24. NSString* ret = [NSString stringWithUTF8String: str];
  25. return ret;
  26. }
  27. static UInt32 GetCreatorOfThisApp()
  28. {
  29. static UInt32 creator = 0;
  30. if (creator == 0)
  31. {
  32. UInt32 type;
  33. CFBundleGetPackageInfo(CFBundleGetMainBundle(), &type, &creator);
  34. }
  35. return creator;
  36. }
  37. static BOOL OpenFileAtLineWithAppleEvent(NSRunningApplication *runningApp, NSString* path, int line)
  38. {
  39. if (!runningApp)
  40. return NO;
  41. NSURL *pathUrl = [NSURL fileURLWithPath: path];
  42. NSAppleEventDescriptor* targetDescriptor = [NSAppleEventDescriptor
  43. descriptorWithProcessIdentifier: runningApp.processIdentifier];
  44. NSAppleEventDescriptor* appleEvent = [NSAppleEventDescriptor
  45. appleEventWithEventClass: kCoreEventClass
  46. eventID: kAEOpenDocuments
  47. targetDescriptor: targetDescriptor
  48. returnID: kAutoGenerateReturnID
  49. transactionID: kAnyTransactionID];
  50. [appleEvent
  51. setParamDescriptor: [NSAppleEventDescriptor
  52. descriptorWithDescriptorType: typeFileURL
  53. data: [[pathUrl absoluteString] dataUsingEncoding: NSUTF8StringEncoding]]
  54. forKeyword: keyDirectObject];
  55. UInt32 packageCreator = GetCreatorOfThisApp();
  56. if (packageCreator == kUnknownType) {
  57. [appleEvent
  58. setParamDescriptor: [NSAppleEventDescriptor
  59. descriptorWithDescriptorType: typeApplicationBundleID
  60. data: [[[NSBundle mainBundle] bundleIdentifier] dataUsingEncoding: NSUTF8StringEncoding]]
  61. forKeyword: keyFileSender];
  62. } else {
  63. [appleEvent
  64. setParamDescriptor: [NSAppleEventDescriptor descriptorWithTypeCode: packageCreator]
  65. forKeyword: keyFileSender];
  66. }
  67. if (line != -1) {
  68. // Add selection range to event
  69. SelectionRange range;
  70. range.unused1 = 0;
  71. range.lineNum = line - 1;
  72. range.startRange = -1;
  73. range.endRange = -1;
  74. range.unused2 = 0;
  75. range.theDate = -1;
  76. [appleEvent
  77. setParamDescriptor: [NSAppleEventDescriptor
  78. descriptorWithDescriptorType: typeChar
  79. bytes: &range
  80. length: sizeof(SelectionRange)]
  81. forKeyword: keyAEPosition];
  82. }
  83. AEDesc reply = { typeNull, NULL };
  84. OSErr err = AESendMessage(
  85. [appleEvent aeDesc],
  86. &reply,
  87. kAENoReply + kAENeverInteract,
  88. kAEDefaultTimeout);
  89. return err == noErr;
  90. }
  91. static BOOL ApplicationSupportsQueryOpenedSolution(NSString* appPath)
  92. {
  93. NSURL* appUrl = [NSURL fileURLWithPath: appPath];
  94. NSBundle* bundle = [NSBundle bundleWithURL: appUrl];
  95. if (!bundle)
  96. return NO;
  97. id versionValue = [bundle objectForInfoDictionaryKey: @"CFBundleVersion"];
  98. if (!versionValue || ![versionValue isKindOfClass: [NSString class]])
  99. return NO;
  100. NSString* version = (NSString*)versionValue;
  101. return [version compare:@"8.6" options:NSNumericSearch] != NSOrderedAscending;
  102. }
  103. static NSArray<NSRunningApplication*>* QueryRunningInstances(NSString *appPath)
  104. {
  105. NSMutableArray<NSRunningApplication*>* instances = [[NSMutableArray alloc] init];
  106. NSURL *appUrl = [NSURL fileURLWithPath: appPath];
  107. for (NSRunningApplication *runningApp in NSWorkspace.sharedWorkspace.runningApplications) {
  108. if (![runningApp isTerminated] && [runningApp.bundleURL isEqual: appUrl]) {
  109. [instances addObject: runningApp];
  110. }
  111. }
  112. return instances;
  113. }
  114. enum {
  115. kWorkspaceEventClass = 1448302419, /* 'VSWS' FourCC */
  116. kCurrentSelectedSolutionPathEventID = 1129534288 /* 'CSSP' FourCC */
  117. };
  118. static BOOL TryQueryCurrentSolutionPath(NSRunningApplication* runningApp, NSString** solutionPath)
  119. {
  120. NSAppleEventDescriptor* targetDescriptor = [NSAppleEventDescriptor
  121. descriptorWithProcessIdentifier: runningApp.processIdentifier];
  122. NSAppleEventDescriptor* appleEvent = [NSAppleEventDescriptor
  123. appleEventWithEventClass: kWorkspaceEventClass
  124. eventID: kCurrentSelectedSolutionPathEventID
  125. targetDescriptor: targetDescriptor
  126. returnID: kAutoGenerateReturnID
  127. transactionID: kAnyTransactionID];
  128. AEDesc aeReply = { 0, };
  129. OSErr sendResult = AESendMessage(
  130. [appleEvent aeDesc],
  131. &aeReply,
  132. kAEWaitReply | kAENeverInteract,
  133. kAEDefaultTimeout);
  134. if (sendResult != noErr) {
  135. return NO;
  136. }
  137. NSAppleEventDescriptor *reply = [[NSAppleEventDescriptor alloc] initWithAEDescNoCopy: &aeReply];
  138. *solutionPath = [[reply descriptorForKeyword: keyDirectObject] stringValue];
  139. return *solutionPath != NULL;
  140. }
  141. static NSRunningApplication* QueryRunningApplicationOpenedOnSolution(NSString* appPath, NSString* solutionPath)
  142. {
  143. BOOL supportsQueryOpenedSolution = ApplicationSupportsQueryOpenedSolution(appPath);
  144. for (NSRunningApplication *runningApp in QueryRunningInstances(appPath)) {
  145. // If the currently selected external editor does not support the opened solution apple event
  146. // then fallback to the previous behavior: take the first opened VSM and open the solution
  147. if (!supportsQueryOpenedSolution) {
  148. OpenFileAtLineWithAppleEvent(runningApp, solutionPath, -1);
  149. return runningApp;
  150. }
  151. NSString* currentSolutionPath;
  152. if (TryQueryCurrentSolutionPath(runningApp, &currentSolutionPath)) {
  153. if ([solutionPath isEqual:currentSolutionPath]) {
  154. return runningApp;
  155. }
  156. } else {
  157. // If VSM doesn't respond to the query opened solution event
  158. // we fallback to the previous behavior too
  159. OpenFileAtLineWithAppleEvent(runningApp, solutionPath, -1);
  160. return runningApp;
  161. }
  162. }
  163. return NULL;
  164. }
  165. static NSRunningApplication* LaunchApplicationOnSolution(NSString* appPath, NSString* solutionPath)
  166. {
  167. return [[NSWorkspace sharedWorkspace]
  168. launchApplicationAtURL: [NSURL fileURLWithPath: appPath]
  169. options: NSWorkspaceLaunchDefault | NSWorkspaceLaunchNewInstance
  170. configuration: @{
  171. NSWorkspaceLaunchConfigurationArguments: @[ solutionPath ],
  172. }
  173. error: nil];
  174. }
  175. static NSRunningApplication* QueryOrLaunchApplication(NSString* appPath, NSString* solutionPath)
  176. {
  177. NSRunningApplication* runningApp = QueryRunningApplicationOpenedOnSolution(appPath, solutionPath);
  178. if (!runningApp)
  179. runningApp = LaunchApplicationOnSolution(appPath, solutionPath);
  180. if (runningApp)
  181. [runningApp activateWithOptions: 0];
  182. return runningApp;
  183. }
  184. BOOL LaunchOrReuseApp(NSString* appPath, NSString* solutionPath, NSRunningApplication** outApp)
  185. {
  186. NSRunningApplication* app = QueryOrLaunchApplication(appPath, solutionPath);
  187. if (outApp)
  188. *outApp = app;
  189. return app != NULL;
  190. }
  191. BOOL MonoDevelopOpenFile(NSString* appPath, NSString* solutionPath, NSString* filePath, int line)
  192. {
  193. NSRunningApplication* runningApp;
  194. if (!LaunchOrReuseApp(appPath, solutionPath, &runningApp)) {
  195. return FALSE;
  196. }
  197. if (filePath) {
  198. return OpenFileAtLineWithAppleEvent(runningApp, filePath, line);
  199. }
  200. return YES;
  201. }
  202. #if BUILD_APP
  203. int main(int argc, const char** argv)
  204. {
  205. if (argc != 5) {
  206. printf("Usage: AppleEventIntegration appPath solutionPath filePath lineNumber\n");
  207. return 1;
  208. }
  209. const char* appPath = argv[1];
  210. const char* solutionPath = argv[2];
  211. const char* filePath = argv[3];
  212. const int lineNumber = atoi(argv[4]);
  213. @autoreleasepool
  214. {
  215. MonoDevelopOpenFile(MakeNSString(appPath), MakeNSString(solutionPath), MakeNSString(filePath), lineNumber);
  216. }
  217. return 0;
  218. }
  219. #else
  220. extern "C"
  221. {
  222. BOOL OpenVisualStudio(const char* appPath, const char* solutionPath, const char* filePath, int line)
  223. {
  224. return MonoDevelopOpenFile(MakeNSString(appPath), MakeNSString(solutionPath), MakeNSString(filePath), line);
  225. }
  226. }
  227. #endif