MacOpenFileDelegate.pas 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. {
  2. Модуль содержит обработчик сообщения открытия файла для MAC OS GUI приложедля.
  3. При открытии файла приложением на MAC OS имя файла передается не через
  4. командную строку, как это делается в Windows, а посылается сообщение application:openFile:
  5. объектом NSApplication.
  6. Для того что бы обработать данное сообщение, нужно назначить делегат объекту
  7. NSApplication приложения, в котором и произвести всю работу.
  8. Подробнее https://delphihaven.wordpress.com/2012/08/14/associating-a-file-type-on-osx-part3/
  9. code by: Victor Fedorenkov
  10. mail: victor.fedorenkov[at]gmail.com
  11. }
  12. unit MacOpenFileDelegate;
  13. interface
  14. type
  15. TOpenURLEvent = reference to procedure(const AURL: string);
  16. TOpenFileEvent = reference to procedure(const AFileName: string);
  17. procedure InstallApplicationOpenFileDelegate(AOnOpenFile: TOpenFileEvent;
  18. AOpenURLEvent: TOpenURLEvent = nil);
  19. implementation
  20. uses
  21. System.SysUtils, System.RTLConsts, System.Messaging, System.Classes,
  22. Macapi.ObjectiveC, Macapi.CoreFoundation, Macapi.CocoaTypes, Macapi.AppKit, Macapi.Foundation, FMX.Forms,
  23. Macapi.ObjCRuntime,
  24. FMX.Platform, FMX.Platform.Mac, FMX.Helpers.Mac; //,
  25. // suStringUtilsUnit;
  26. type
  27. IFMXApplicationDelegate = interface(NSApplicationDelegate)
  28. ['{A54E08CA-77CC-4F22-B6D9-833DD6AB696D}']
  29. procedure onMenuClicked(sender: NSMenuItem); cdecl;
  30. end;
  31. NSApplicationDelegate2 = interface(IFMXApplicationDelegate)
  32. ['{BE9AEDB7-80AC-49B1-8921-F226CC9310F4}']
  33. procedure applicationWillFinishLaunching(Notification: NSNotification); cdecl;
  34. function application(theApplication: Pointer; openFile: CFStringRef): Boolean; cdecl;
  35. end;
  36. IURLEventHandler = interface(IObjectiveC)
  37. ['{29B79998-AB68-4694-93D7-AA09A1BF08F7}']
  38. procedure Handle(Event, ReplyEvent: NSAppleEventDescriptor); cdecl;
  39. end;
  40. TURLEventHandler = class(TOCLocal, IURLEventHandler)
  41. FOpenURLEvent: TOpenURLEvent;
  42. public
  43. constructor Create(AOpenURLEvent: TOpenURLEvent);
  44. procedure Handle(Event, ReplyEvent: NSAppleEventDescriptor); cdecl;
  45. end;
  46. TNSApplicationDelegate2 = class(TOCLocal, NSApplicationDelegate2)
  47. private
  48. FOnOpenFile: TOpenFileEvent;
  49. FURLEventHandler: TURLEventHandler;
  50. public
  51. constructor Create(AOnOpenFile: TOpenFileEvent; AOpenURLEvent: TOpenURLEvent = nil);
  52. destructor Destroy; override;
  53. procedure applicationDidFinishLaunching(Notification: NSNotification); cdecl;
  54. procedure applicationWillTerminate(Notification: NSNotification); cdecl;
  55. function applicationShouldTerminate(Notification: NSNotification): NSInteger; cdecl;
  56. function applicationDockMenu(sender: NSApplication): NSMenu; cdecl;
  57. procedure onMenuClicked(sender: NSMenuItem); cdecl;
  58. procedure applicationWillFinishLaunching(Notification: NSNotification); cdecl;
  59. procedure applicationDidHide(Notification: NSNotification); cdecl;
  60. procedure applicationDidUnhide(Notification: NSNotification); cdecl;
  61. function application(theApplication: Pointer; openFile: CFStringRef): Boolean; cdecl;
  62. end;
  63. var
  64. Delegate: NSApplicationDelegate2;
  65. procedure InstallApplicationOpenFileDelegate(AOnOpenFile: TOpenFileEvent;
  66. AOpenURLEvent: TOpenURLEvent = nil);
  67. var
  68. NSApp: NSApplication;
  69. AutoReleasePool: NSAutoreleasePool;
  70. begin
  71. AutoReleasePool := TNSAutoreleasePool.Alloc;
  72. try
  73. AutoReleasePool.init;
  74. NSApp := TNSApplication.Wrap(TNSApplication.OCClass.sharedApplication);
  75. Delegate := TNSApplicationDelegate2.Create(AOnOpenFile, AOpenURLEvent);
  76. NSApp.setDelegate(NSApplicationDelegate2(Delegate));
  77. finally
  78. AutoReleasePool.release;
  79. end;
  80. end;
  81. //Функция скопирована из FMX.Platform.Mac
  82. function SendOSXMessage(const sender: TObject; const OSXMessageClass: TOSXMessageClass;
  83. const NSSender: NSObject): NSObject;
  84. var
  85. MessageObject: TOSXMessageObject;
  86. begin
  87. if OSXMessageClass = nil then
  88. raise EArgumentNilException.Create(SArgumentNil);
  89. MessageObject := TOSXMessageObject.Create(NSSender);
  90. try
  91. TMessageManager.DefaultManager.SendMessage(sender, OSXMessageClass.Create(MessageObject, False), True);
  92. Result := MessageObject.ReturnValue;
  93. finally
  94. MessageObject.Free;
  95. end;
  96. end;
  97. //Проверка, занилена ли глобальная переменная PlatformCocoa из FMX.Platform.Mac
  98. function PlatformCocoaIsNil: Boolean;
  99. begin
  100. //но так как оно нам недоступно, то проверим по последнему элементу созданному
  101. //в конструкторе
  102. Result := (@System.Classes.WakeMainThread = nil);
  103. end;
  104. function PlatformCocoaTerminating: Boolean;
  105. begin
  106. Result := IFMXApplicationService(TPlatformServices.Current.GetPlatformService(IFMXApplicationService)).Terminating;
  107. end;
  108. //Эмуляция PlatformCocoa.DefaultAction('Q', [ssCommand])
  109. function PlatformCocoa_DefaultAction_Commad_Q: Boolean;
  110. begin
  111. //Копия ветки кода из TPlatformCocoa.DefaultAction выполняющаяся при входящих параметрах
  112. //PlatformCocoa.DefaultAction('Q', [ssCommand])
  113. Result := False;
  114. try
  115. if FMX.Forms.application.MainForm <> nil then
  116. begin
  117. FMX.Forms.application.MainForm.Close;
  118. if not PlatformCocoaTerminating then
  119. Exit;
  120. end
  121. else
  122. begin
  123. if Screen <> nil then
  124. Screen.ActiveForm := nil;
  125. application.Terminate;
  126. end;
  127. except
  128. HandleException(application);
  129. end;
  130. Result := True;
  131. end;
  132. { TNSApplicationDelegate2 }
  133. constructor TNSApplicationDelegate2.Create(AOnOpenFile: TOpenFileEvent;
  134. AOpenURLEvent: TOpenURLEvent = nil);
  135. begin
  136. inherited Create;
  137. FOnOpenFile := AOnOpenFile;
  138. if Assigned(AOpenURLEvent) then
  139. FURLEventHandler := TURLEventHandler.Create(AOpenURLEvent);
  140. end;
  141. destructor TNSApplicationDelegate2.Destroy;
  142. begin
  143. FreeAndNil(FURLEventHandler);
  144. inherited;
  145. end;
  146. procedure TNSApplicationDelegate2.onMenuClicked(sender: NSMenuItem);
  147. begin
  148. SendOSXMessage(Self, TApplicationMenuClickedMessage, sender);
  149. end;
  150. procedure TNSApplicationDelegate2.applicationDidFinishLaunching(
  151. Notification: NSNotification);
  152. begin
  153. SendOSXMessage(Self, TApplicationDidFinishLaunchingMessage, Notification);
  154. end;
  155. function TNSApplicationDelegate2.applicationShouldTerminate(
  156. Notification: NSNotification): NSInteger;
  157. begin
  158. if (FMX.Forms.application = nil) or PlatformCocoaIsNil or PlatformCocoaTerminating
  159. or PlatformCocoa_DefaultAction_Commad_Q then
  160. Result := NSTerminateNow
  161. else
  162. Result := NSTerminateCancel;
  163. end;
  164. //Перед загрузкой приложения попадаем сюда
  165. procedure TNSApplicationDelegate2.applicationWillFinishLaunching(
  166. Notification: NSNotification);
  167. const
  168. kAEGetURL: Integer = Ord('G') shl 24 + Ord('U') shl 16 + Ord('R') shl 8 + Ord('L'); //'GURL'
  169. kInternetEventClass: Integer = Ord('G') shl 24 + Ord('U') shl 16 + Ord('R') shl 8 + Ord('L'); //'GURL'
  170. var
  171. selector: SEL;
  172. sharedAppleEventManager: NSAppleEventManager;
  173. begin
  174. if Assigned(FURLEventHandler) then
  175. begin
  176. sharedAppleEventManager := TNSAppleEventManager.Wrap(
  177. TNSAppleEventManager.OCClass.sharedAppleEventManager);
  178. selector := sel_registerName(PAnsiChar('Handle:ReplyEvent:'));
  179. sharedAppleEventManager.setEventHandler(FURLEventHandler.GetObjectID, selector,
  180. kInternetEventClass, kAEGetURL);
  181. end;
  182. end;
  183. // 应用程序取消隐藏
  184. procedure TNSApplicationDelegate2.applicationDidHide(Notification: NSNotification);
  185. begin
  186. //ShowMessage('applicationDidHide');
  187. end;
  188. procedure TNSApplicationDelegate2.applicationDidUnhide(Notification: NSNotification);
  189. begin
  190. //ShowMessage('applicationDidUnhide');
  191. end;
  192. procedure TNSApplicationDelegate2.applicationWillTerminate(
  193. Notification: NSNotification);
  194. begin
  195. SendOSXMessage(Self, TApplicationWillTerminateMessage, Notification);
  196. Halt;
  197. end;
  198. function TNSApplicationDelegate2.applicationDockMenu(
  199. sender: NSApplication): NSMenu;
  200. var
  201. ReturnValue: NSObject;
  202. begin
  203. ReturnValue := SendOSXMessage(Self, TApplicationDockMenuMessage, sender);
  204. if ReturnValue <> nil then
  205. Result := ReturnValue as NSMenu
  206. else
  207. Result := nil;
  208. end;
  209. function TNSApplicationDelegate2.application(theApplication: Pointer;
  210. openFile: CFStringRef): Boolean;
  211. var
  212. Range: CFRange;
  213. S: string;
  214. begin
  215. Result := Assigned(FOnOpenFile);
  216. if not Result then
  217. Exit;
  218. Range.location := 0;
  219. Range.length := CFStringGetLength(openFile);
  220. SetLength(S, Range.length);
  221. CFStringGetCharacters(openFile, Range, PChar(S));
  222. try
  223. FOnOpenFile(S);
  224. except
  225. FMX.Forms.application.HandleException(ExceptObject);
  226. Result := False;
  227. end;
  228. end;
  229. { TURLEventHandler }
  230. constructor TURLEventHandler.Create(AOpenURLEvent: TOpenURLEvent);
  231. begin
  232. inherited Create;
  233. FOpenURLEvent := AOpenURLEvent;
  234. end;
  235. procedure TURLEventHandler.Handle(Event, ReplyEvent: NSAppleEventDescriptor);
  236. const
  237. keyDirectObject: Integer = Ord('-') shl 24 + Ord('-') shl 16 + Ord('-') shl 8 + Ord('-'); //'----'
  238. begin
  239. //FOpenURLEvent(NSToString(Event.paramDescriptorForKeyword(keyDirectObject).stringValue));
  240. FOpenURLEvent(string(Event.paramDescriptorForKeyword(keyDirectObject).stringValue));
  241. end;
  242. end.