PageRenderTime 25ms CodeModel.GetById 9ms RepoModel.GetById 1ms app.codeStats 0ms

/mio/AppDelegate.m

https://gitlab.com/base.io/mio
Objective C | 1185 lines | 616 code | 274 blank | 295 comment | 160 complexity | 8dc42c9f3c55a370ec6aa702ded609d1 MD5 | raw file
  1. //
  2. // AppDelegate.m
  3. // tsx
  4. //
  5. // Created by Jørgen Skogmo on 16-11-2012.
  6. // Copyright (c) 2012 Jørgen Skogmo. All rights reserved.
  7. //
  8. #import "AppDelegate.h"
  9. #import "NSDataAdditions.h"
  10. /// Check this one: https://github.com/PabloGS/UIWebView-Debug/blob/master/WebView%2BDebug.m
  11. /// #import <objc/runtime.h>
  12. static const int ddLogLevel = LOG_LEVEL_VERBOSE;
  13. @implementation AppDelegate
  14. // ---------------------------------------------------------------------------------------------------------------------------
  15. #pragma mark - IB-actions and Keyboard Shortcuts
  16. - (void) OnKeyDown:(NSEvent *)evt {
  17. /// Forwarded from BorderlessWindow.keyDown
  18. unichar keyCode = [[evt characters] characterAtIndex: 0];
  19. //NSLog(@"AppDel.OnKeyDown:%u, %@", keyCode, evt);
  20. /// Handle command+key
  21. if ([evt modifierFlags] & NSCommandKeyMask) {
  22. if( keyCode == 114 ){ // r -> Reload
  23. NSLog(@"Shortcut: Reload WebView");
  24. //[(AppDelegate *)[[NSApplication sharedApplication] delegate] ReloadWebView];
  25. [self ReloadWebView];
  26. }
  27. if( keyCode == 112 ){ // p -> Toggle PerformanceUI
  28. NSLog(@"Shortcut: Show/Hide PerformanceUI");
  29. //[(AppDelegate *)[[NSApplication sharedApplication] delegate] TogglePerformaceUI];
  30. [self TogglePerformaceUI];
  31. }
  32. }
  33. }
  34. - (void) ReloadWebView {
  35. [webView reloadFromOrigin:nil];
  36. [performanceUI resetJSCounters];
  37. }
  38. - (void) TogglePerformaceUI {
  39. [performanceUI setHidden:![performanceUI isHidden]];
  40. }
  41. - (IBAction)openDocumentation:(id)sender {
  42. [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://github.com/baseio/mio"]];
  43. }
  44. // ---------------------------------------------------------------------------------------------------------------------------
  45. #pragma mark - Application Launch
  46. NSTimer *waitTimer;
  47. - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
  48. environment = [[Environment alloc] init];
  49. netconf = [environment getNetConf];
  50. OnTCPSocketConnected_defined = NO;
  51. OnTCPSocketMessage_defined = NO;
  52. OnOSCMessage_defined = NO;
  53. STATISTICS_FILE = @"/Applications/Mobiglobe/statistics.txt";
  54. ERROR_EXCEPTIONS = 0;
  55. MEMORY_USE = @"-";
  56. /// Order Performance-view in front of webview:
  57. [webView setWantsLayer:YES];
  58. [performanceUI setWantsLayer:YES];
  59. // Setup Logger for Console.app
  60. [DDLog addLogger:[DDASLLogger sharedInstance]];
  61. // Setup Logger for Xcode.app
  62. #ifdef DEBUG
  63. [DDLog addLogger:[DDTTYLogger sharedInstance]];
  64. #endif
  65. // Setup Logger for Archive
  66. // # Note: DDFileLogger.generateShortUUID() has been modified to create logfiles with sweeter filenames
  67. fileLogger = [[DDFileLogger alloc] init];
  68. fileLogger.maximumFileSize = 0; // Disable rolling due to filesize
  69. fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling
  70. fileLogger.logFileManager.maximumNumberOfLogFiles = 30; // Keep a month's worth of log files on the system
  71. [DDLog addLogger:fileLogger];
  72. // Read main info.plist
  73. NSDictionary *plist = [[NSBundle mainBundle] infoDictionary];
  74. NSString *shellVersion = [plist valueForKey:@"CFBundleShortVersionString"];
  75. NSString *shellBuildCode = [plist valueForKey:@"CFBundleVersion"];
  76. MioVersion = [NSString stringWithFormat:@"Mio v.%@ (%@)", shellVersion, shellBuildCode];
  77. DDLogInfo(@"Mio version '%@@%@' Starting", shellBuildCode, shellVersion );
  78. #pragma mark # Aboutbox
  79. [about_version setStringValue: MioVersion];
  80. [about_icon setImage:
  81. [[NSImage alloc] initWithContentsOfFile:
  82. [[NSBundle mainBundle] pathForResource:@"aboutbox" ofType:@"png"]]];
  83. /*
  84. NSMutableAttributedString* string = [[NSMutableAttributedString alloc] init];
  85. [string appendAttributedString: [NSAttributedString stringWithColor:@"Documentation: " color:@"222222"]];
  86. [string appendAttributedString: [NSAttributedString hyperlink:@"github.com/baseio/mio" href:@"http://github.com/baseio/mio" color:@"6685ed" underline:NO]];
  87. NSMutableParagraphStyle *style=[[NSMutableParagraphStyle alloc] init];
  88. [style setAlignment:NSCenterTextAlignment];
  89. [string addAttributes:[NSDictionary dictionaryWithObject:style forKey:NSParagraphStyleAttributeName] range:NSMakeRange(0,[string length])];
  90. //[about_homepage setAllowsEditingTextAttributes: YES];
  91. [about_homepage setSelectable: YES];
  92. [about_homepage setAttributedStringValue: string];
  93. NSString *text = @"<html><span style=\"color:#222;\">Documentation </span><a style=\"color:#6685ed;\" href=\"http://github.com/baseio/mio\">github.com/baseio/mio</a></html>";
  94. NSAttributedString* string = [[NSAttributedString alloc] initWithHTML:[text dataUsingEncoding:NSUTF8StringEncoding] documentAttributes:nil];
  95. [about_homepage setAttributedStringValue: string];
  96. */
  97. [[aboutbox standardWindowButton:NSWindowZoomButton] setHidden:YES];
  98. [[aboutbox standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
  99. // CONFIG
  100. #pragma mark # Parse app.json config
  101. /// First, read in app.json from the bundle. On a clean build with no env vars this will be the default app
  102. NSError *err = nil;
  103. NSString *app_config = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/app.json"];
  104. NSString *appjson_content = [[NSString alloc] initWithContentsOfFile:app_config encoding:NSUTF8StringEncoding error:&err];
  105. if( err != nil ) DDLogError(@"Count not read app.json:%@", err);
  106. /// Second, see if the environment has a MIOAPP var, and attempt to load it
  107. //NSLog(@"ENV %@", [[NSProcessInfo processInfo] environment] );
  108. NSString *env_app_config = [[[NSProcessInfo processInfo] environment] objectForKey:@"MIOAPP"];
  109. if( env_app_config ) {
  110. // Obey ENV MIOAPP if present
  111. env_app_config = [env_app_config stringByAppendingString:@"/app.json"];
  112. NSString *env_appjson_content = [[NSString alloc] initWithContentsOfFile:env_app_config encoding:NSUTF8StringEncoding error:&err];
  113. if( err != nil ){
  114. DDLogError(@"Count not read %@/app.json:%@", env_app_config, err);
  115. }else{
  116. /// Load succeeded, use it
  117. appjson_content = env_appjson_content;
  118. }
  119. }
  120. //DDLogInfo(@"appjson_content: %@", appjson_content );
  121. // Parse app.json into Dictionary
  122. NSData *json = [appjson_content dataUsingEncoding:NSUTF8StringEncoding];
  123. config = [NSJSONSerialization JSONObjectWithData:json options:NSJSONReadingAllowFragments error:&err];
  124. if( err != nil ) DDLogError(@"app.json Error:%@", err);
  125. //DDLogInfo(@"App Config:%@", config);
  126. // Setup shell title
  127. NSString *title = @"io";
  128. if( [config valueForKey:@"name"] != nil ){
  129. title = [config valueForKey:@"name"];
  130. }
  131. // Setup shell Window properties
  132. NSDictionary *windowSettings = [config valueForKey:@"window"];
  133. // Hide mouse? (Only applicable in Fullscreen mode)
  134. BOOL hide_mouse = NO;
  135. if( [windowSettings valueForKey:@"hidemouse"] != nil ){
  136. if( ((int)[[windowSettings valueForKey:@"hidemouse"] integerValue]) == 1 ){
  137. hide_mouse = YES;
  138. }
  139. }
  140. // Hide menu?
  141. BOOL hide_menu = NO;
  142. if( [windowSettings valueForKey:@"menu"] != nil ){
  143. if( ((int)[[windowSettings valueForKey:@"menu"] integerValue]) == 0 ){
  144. hide_menu = YES;
  145. }
  146. }
  147. // Hide window frame?
  148. BOOL hide_frame = NO;
  149. if( [windowSettings valueForKey:@"frame"] != nil ){
  150. if( ((int)[[windowSettings valueForKey:@"frame"] integerValue]) == 0 ){
  151. hide_frame = YES;
  152. }
  153. }
  154. // Fullscreen? If so, also hide frame and menu
  155. BOOL use_fullscreen = NO;
  156. if( [windowSettings valueForKey:@"fullscreen"] != nil ){
  157. if( ((int)[[windowSettings valueForKey:@"fullscreen"] integerValue]) == 1 ){
  158. DDLogInfo(@"Fullscreen Requested");
  159. hide_menu = YES;
  160. hide_frame = YES;
  161. use_fullscreen = YES;
  162. }
  163. }
  164. // Apply
  165. #pragma mark # Apply window config
  166. DDLogInfo(@"# Window");
  167. //[self.window setAlphaValue:0.0]; // Hide at start, call io.Ready() from JS to show (fade-in) the window.
  168. // Default to mainScreen
  169. NSRect screenRect = [[NSScreen mainScreen] frame];
  170. NSPoint position = {.x=0, .y=0};
  171. BOOL useRequestedXY = false;
  172. if( [windowSettings valueForKey:@"x"] != nil ){
  173. position.x = ((int)[[windowSettings valueForKey:@"x"] integerValue]);
  174. useRequestedXY = true;
  175. }
  176. /// Note: y-position is currently not implemented
  177. if( [windowSettings valueForKey:@"y"] != nil ){
  178. position.y = ((int)[[windowSettings valueForKey:@"y"] integerValue]);
  179. useRequestedXY = true;
  180. }
  181. if(useRequestedXY){
  182. // Determine wich [NSScreen screens] index the x and y is IN
  183. uint _index = 0;
  184. uint sindex = 0;
  185. for (NSScreen* screen in [NSScreen screens]) {
  186. DDLogInfo(@"* Screen %d width:%d origin.x:%d", _index, (int)screen.frame.size.width, (int)screen.frame.origin.x );
  187. if( position.x > (int)screen.frame.origin.x && position.x < (int)(screen.frame.origin.x+screen.frame.size.width) ){
  188. sindex = _index;
  189. DDLogInfo(@"* Requested (window.x: %d) is on screen-index %d", (int)position.x, sindex);
  190. }
  191. _index ++;
  192. }
  193. screenRect = [[[NSScreen screens] objectAtIndex:sindex] frame];
  194. DDLogInfo(@"* Using screen %d (left:%d, bottom:%d, width:%d, height:%d)", sindex, (int)screenRect.origin.x, (int)screenRect.origin.y, (int)screenRect.size.width, (int)screenRect.size.height);
  195. }
  196. if( use_fullscreen ){
  197. DDLogInfo(@"+ Creating fullscreen window");
  198. /*
  199. // Kiosk mode
  200. NSApplicationPresentationOptions options =
  201. NSApplicationPresentationHideMenuBar
  202. + NSApplicationPresentationHideDock
  203. + NSApplicationPresentationDisableHideApplication
  204. + NSApplicationPresentationDisableProcessSwitching
  205. + NSApplicationPresentationDisableAppleMenu
  206. // + NSApplicationPresentationDisableForceQuit;
  207. ;
  208. [NSApp setPresentationOptions:options];
  209. */
  210. [NSMenu setMenuBarVisible:NO];
  211. [self.window setStyleMask:NSBorderlessWindowMask];
  212. [self.window setFrame:screenRect display:YES];
  213. }else{
  214. // Setup shell size
  215. int w = 600;
  216. int h = 300;
  217. int cw = (int)[[windowSettings valueForKey:@"width"] integerValue];
  218. int ch = (int)[[windowSettings valueForKey:@"height"] integerValue];
  219. if( cw != 0 ) w = cw;
  220. if( ch != 0 ) h = ch;
  221. if( !hide_frame){
  222. // add 22px to the height
  223. h += 22;
  224. }
  225. // Default position: window centered on mainScreeen
  226. int x = (screenRect.size.width - w) / 2;
  227. int y = (screenRect.size.height - h) / 2;
  228. if(useRequestedXY){
  229. x = position.x;
  230. // Keep Vertical center (for now) assuming screen@1 is talled than mainScreen
  231. y += screenRect.origin.y;
  232. position.y = y;
  233. }
  234. // ... also if the window is larger than the screen
  235. if( cw > screenRect.size.width ) x = - ((cw - screenRect.size.width) / 2);
  236. if( hide_frame ){
  237. [self.window setStyleMask:NSBorderlessWindowMask];
  238. }else{
  239. [self.window setTitle:[config valueForKey:@"name"]];
  240. }
  241. if( hide_menu ){
  242. [NSMenu setMenuBarVisible:NO];
  243. }
  244. DDLogInfo(@"+ Creating window x:%i, y:%i, w:%u, h:%u", x,y,w,h );
  245. [self.window setFrame:NSMakeRect(x,y,w,h) display:YES];
  246. /// Resizeable?
  247. if( [windowSettings valueForKey:@"resize"] != nil ){
  248. if( ((int)[[windowSettings valueForKey:@"resize"] integerValue]) == 0 ){
  249. [[self.window standardWindowButton:NSWindowZoomButton] setEnabled:NO];
  250. [[self.window standardWindowButton:NSWindowMiniaturizeButton] setEnabled:NO]; // or: setHidden:YES
  251. [self.window setStyleMask:[self.window styleMask] & ~NSResizableWindowMask];
  252. }
  253. }
  254. if(useRequestedXY){
  255. //DDLogInfo(@"> Moving win to x:%d, y:%d", x, y);
  256. //[self.window setFrameOrigin:position];
  257. }
  258. }
  259. [self.window makeKeyAndOrderFront:self];
  260. [self.window makeFirstResponder:webView];
  261. // If launched from CLI we need to force ourself to foreground:
  262. NSRunningApplication* app = [NSRunningApplication currentApplication];
  263. [app activateWithOptions: NSApplicationActivateIgnoringOtherApps];
  264. if( hide_mouse ){
  265. [NSCursor hide];
  266. }
  267. // Setup Socket(s)
  268. #pragma mark # Setup Socket(s)
  269. DDLogInfo(@"# Network");
  270. //netconf = [environment getNetConf];
  271. //netconf = [self GetNetConf];
  272. // -> ObjectsAndKeys:ifn, @"interface", addr, @"primaryIP", plussOne, @"primaryIP_Next", wifiAddress, @"secondaryIP", nil];
  273. //DDLogInfo(@"netconf: %@", netconf );
  274. DDLogInfo(@"* Primary IP: %@", [netconf objectForKey:@"primaryIP"] );
  275. if( [netconf objectForKey:@"secondaryIP"] ){
  276. DDLogInfo(@"* Secondary IP: %@", [netconf objectForKey:@"secondaryIP"] );
  277. }
  278. // Use pre-defined IP-endPoint ?
  279. // @010: Renamed 'auto' to 'next', Renamed 'hostname' to 'ipconf'
  280. NSString *host = @"next";
  281. if( [config valueForKey:@"ipconf"] != nil ){
  282. host = [config valueForKey:@"ipconf"];
  283. DDLogInfo(@"* Config.ipconf = %@", host);
  284. UDP_HOST = host;
  285. TCP_HOST = host;
  286. }
  287. if( [host isEqualToString:@"next"] ){
  288. UDP_HOST = [netconf objectForKey:@"primaryIP_Next"];
  289. TCP_HOST = [netconf objectForKey:@"primaryIP_Next"];
  290. }
  291. if( [host isEqualToString:@"local"] ){
  292. UDP_HOST = @"127.0.0.1";
  293. TCP_HOST = @"127.0.0.1";
  294. }
  295. // Setup UDP
  296. NSDictionary *udpSocketSettings = [config valueForKey:@"udpsocket"];
  297. if( [udpSocketSettings valueForKey:@"enabled"] != nil ){
  298. if( ((int)[[udpSocketSettings valueForKey:@"enabled"] integerValue]) == 1 ){
  299. if( [udpSocketSettings valueForKey:@"host"] != nil ){
  300. NSString *tmpUdpHost = [udpSocketSettings valueForKey:@"host"];
  301. if( [tmpUdpHost isNotEqualTo:@"auto"] ){
  302. UDP_HOST = tmpUdpHost;
  303. }
  304. }
  305. UDP_PORT = 3334;
  306. if( [udpSocketSettings valueForKey:@"port"] != nil ){
  307. UDP_PORT = ((uint)[[udpSocketSettings valueForKey:@"port"] integerValue]);
  308. }
  309. udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
  310. DDLogInfo(@"+ Creating UDP socket-link between: %@ and %@ on Port %u over \"%@\"", [netconf objectForKey:@"primaryIP"], UDP_HOST, UDP_PORT, [netconf objectForKey:@"interface"]);
  311. ///
  312. NSError *error = nil;
  313. if (![udpSocket bindToPort:UDP_PORT error:&error]){
  314. DDLogError(@"UDP Error: Can not bind to %u Error: %@", UDP_PORT, error);
  315. }
  316. if (![udpSocket beginReceiving:&error]){
  317. DDLogError(@"UDP Error: Can not receive on %u Error: %@", UDP_PORT, error);
  318. }
  319. }
  320. }
  321. // Setup TCP
  322. NSDictionary *tcpSocketSettings = [config valueForKey:@"tcpsocket"];
  323. if( [tcpSocketSettings valueForKey:@"enabled"] != nil ){
  324. if( ((int)[[tcpSocketSettings valueForKey:@"enabled"] integerValue]) == 1 ){
  325. if( [tcpSocketSettings valueForKey:@"host"] != nil ){
  326. NSString *tmpTcpHost = [tcpSocketSettings valueForKey:@"host"];
  327. if( [tmpTcpHost isNotEqualTo:@"auto"] ){
  328. TCP_HOST = tmpTcpHost;
  329. }
  330. }
  331. TCP_PORT = 3335; // Default
  332. if( [tcpSocketSettings valueForKey:@"port"] != nil ){
  333. TCP_PORT = ((uint)[[tcpSocketSettings valueForKey:@"port"] integerValue]);
  334. }
  335. tcpSocketQueue = dispatch_queue_create("tcpSocketQueue", NULL);
  336. tcpSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:tcpSocketQueue];
  337. ///tcpSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
  338. DDLogInfo(@"+ Creating TCP socket-link between: %@ and %@ on Port %u over \"%@\"", [netconf objectForKey:@"primaryIP"], TCP_HOST, TCP_PORT, [netconf objectForKey:@"interface"]);
  339. // instead of connecting directly, we run a repeating timer that will attempt to reconnect to the host if the connection drops:
  340. reconnectTcpTimer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(tcpReConnect:) userInfo:nil repeats:YES];
  341. DDLogInfo(@"+ Creating TCP ConnectionWatcher");
  342. }
  343. }
  344. // Use Monitoring ?
  345. #pragma mark # Setup Monitoring
  346. NSDictionary *monitoringSettings = [config valueForKey:@"monitoring"];
  347. if( [monitoringSettings valueForKey:@"enabled"] != nil ){
  348. if( ((int)[[monitoringSettings valueForKey:@"enabled"] integerValue]) == 1 ){
  349. ACMonitor = [[ACMonitoring alloc] initWithConfig: monitoringSettings];
  350. /*
  351. MON_IPS = [monitoringSettings valueForKey:@"ip"];
  352. MON_PORT = (uint)[[monitoringSettings valueForKey:@"port"] integerValue];
  353. monitoringSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
  354. BOOL monSockOK = YES;
  355. NSError *error = nil;
  356. if (![monitoringSocket bindToPort:MON_PORT error:&error]){
  357. DDLogError(@"Network Error: Can not bind to %u Error: %@", MON_PORT, error);
  358. monSockOK = NO;
  359. }
  360. if (![monitoringSocket beginReceiving:&error]){
  361. DDLogError(@"Network Error: Can not receive on %u Error: %@", MON_PORT, error);
  362. [monitoringSocket close];
  363. monSockOK = NO;
  364. }
  365. if( monSockOK == YES ){
  366. ACMONITORING_ACTIVE = YES;
  367. DDLogInfo(@"Monitoring enabled on port %u, accepting commands from %@", MON_PORT, [MON_IPS componentsJoinedByString:@", "]);
  368. }else{
  369. DDLogInfo(@"Monitoring failed to bind to port %u", MON_PORT);
  370. }
  371. */
  372. }
  373. }else{
  374. DDLogInfo(@"Monitoring disbled");
  375. }
  376. // Use Auto-heal ?
  377. AUTOHEAL = NO;
  378. if( [config valueForKey:@"autoheal"] != nil ){
  379. if( ((int)[[config valueForKey:@"autoheal"] integerValue]) == 1 ){
  380. AUTOHEAL = YES;
  381. }
  382. }
  383. CRASH_ON_JAVASCRIPT_ERRORS = YES;
  384. if( [config valueForKey:@"crash_on_error"] != nil ){
  385. if( ((int)[[config valueForKey:@"crash_on_error"] integerValue]) == 0 ){
  386. CRASH_ON_JAVASCRIPT_ERRORS = NO;
  387. }
  388. }
  389. #pragma mark # Init WebApp
  390. DDLogInfo(@"# Webview");
  391. // Prepare webkit
  392. [self initJavaScriptBridge];
  393. // Enable WebGL
  394. DDLogInfo(@"+ Enabling WebGL");
  395. [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"WebKitWebGLEnabled"];
  396. [[NSUserDefaults standardUserDefaults] synchronize];
  397. NSString *INDEXFILE = @"index.html";
  398. if( [config valueForKey:@"index"] != nil ){
  399. INDEXFILE = [config valueForKey:@"index"];
  400. }
  401. NSDictionary *httpSettings = [config valueForKey:@"httpserver"];
  402. HTTPSERVER = false;
  403. if( [httpSettings valueForKey:@"enabled"] != nil ){
  404. if( ((int)[[httpSettings valueForKey:@"enabled"] integerValue]) == 1 ){
  405. // Start HTTP Server. Note: I have modified HTTPConnection.m with improved MIME-types
  406. httpServer = [[HTTPServer alloc] init];
  407. HTTPSERVER = YES;
  408. NSString *httpRoot = [config valueForKey:@"app"];
  409. // If the $app does *not* start with a "/", rewrite to mainbundle:
  410. if( [[httpRoot substringToIndex:1] isNotEqualTo:@"/"] ){
  411. httpRoot = [NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] resourcePath], httpRoot];
  412. }
  413. HTTP_PORT = 8080;
  414. if( [httpSettings valueForKey:@"port"] != nil ){
  415. HTTP_PORT = ((uint)[[httpSettings valueForKey:@"port"] integerValue]);
  416. }
  417. [httpServer setPort:HTTP_PORT];
  418. [httpServer setDocumentRoot:httpRoot];
  419. NSError *error = nil;
  420. if([httpServer start:&error]){
  421. DDLogInfo(@"HTTPServer: Document root: %@", httpRoot );
  422. }else{
  423. DDLogError(@"HTTPServer: Error: %@", error);
  424. }
  425. NSString *url = [NSString stringWithFormat:@"http://127.0.0.1:%i%@", HTTP_PORT, INDEXFILE];
  426. DDLogInfo(@"+ Loading app '%@' from URL %@", title, url);
  427. [webView setMainFrameURL:url];
  428. }
  429. }
  430. NSDictionary *oscSettings = [config valueForKey:@"oscserver"];
  431. if( [oscSettings valueForKey:@"enabled"] != nil ){
  432. if( ((int)[[oscSettings valueForKey:@"enabled"] integerValue]) == 1 ){
  433. uint oscPort = 2555;
  434. if( [oscSettings valueForKey:@"port"] != nil ){
  435. oscPort = ((uint)[[oscSettings valueForKey:@"port"] integerValue]);
  436. }else{
  437. DDLogInfo(@"+ OSC Server using DEFAULT port %u", oscPort);
  438. }
  439. [self startOSCServerOnPort:oscPort];
  440. }
  441. }
  442. DDLogInfo(@"# Memory consumption: %@", [environment getMemoryReport] );
  443. if( !HTTPSERVER ){
  444. // Load application from file
  445. NSString *appdir = [config valueForKey:@"app"];
  446. if( [[appdir substringToIndex:1] isEqualToString:@"/"] ){
  447. appdir = [NSString stringWithFormat:@"%@/%@", appdir, INDEXFILE];
  448. }else{
  449. appdir = [NSString stringWithFormat:@"%@/%@/%@", [[NSBundle mainBundle] resourcePath], appdir, INDEXFILE];
  450. }
  451. DDLogInfo(@"+ Loading app '%@' from FILE %@", title, appdir);
  452. [webView setMainFrameURL:appdir];
  453. }
  454. }
  455. // ---------------------------------------------------------------------------------------------------------------------------
  456. #pragma mark - OSC Server
  457. - (void)startOSCServerOnPort:(uint)port {
  458. oscServer = [[F53OSCServer alloc] init];
  459. [oscServer setPort:port];
  460. [oscServer setDelegate:self];
  461. [oscServer startListening];
  462. DDLogInfo(@"+ OSC Server listening on port %u", port);
  463. }
  464. - (void)takeMessage:(F53OSCMessage *)message {
  465. NSString *addressPattern = message.addressPattern;
  466. NSArray *arguments = message.arguments;
  467. NSString *argsString = [arguments componentsJoinedByString:@","];
  468. if( OnOSCMessage_defined ){
  469. NSString *js = [NSString stringWithFormat:@"OnOSCMessage('%@','%@');", addressPattern, argsString ];
  470. NSLog(@"OSC Objc->JS: %@", js);
  471. [webView stringByEvaluatingJavaScriptFromString:js];
  472. }
  473. }
  474. // ---------------------------------------------------------------------------------------------------------------------------
  475. #pragma mark - UDP Socket-Socket Delegates
  476. - (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext {
  477. /// print message data
  478. /*
  479. NSString *senderIP = [GCDAsyncUdpSocket hostFromAddress:address];
  480. uint16_t senderPORT = [GCDAsyncUdpSocket portFromAddress:address];
  481. NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  482. if (msg){
  483. DDLogInfo(@"UDPMessage from %@:%u : %@", senderIP, senderPORT, msg);
  484. }
  485. */
  486. }
  487. // ---------------------------------------------------------------------------------------------------------------------------
  488. #pragma mark - TCP-Socket Delegates
  489. - (void)tcpReConnect:(NSTimer *)timer {
  490. // Called continously by $reconnectTcpTimer
  491. // Note: The $timeout below _needs_ to be shorter than the $repeatInterval of the $reconnectTcpTimer
  492. if( ![tcpSocket isConnected] ){
  493. DDLogError(@" TCP ConnectionWatcher : Socket Disconnected: Attempting (re)connection");
  494. //[self tcpConnect];
  495. NSError *tcperr = nil;
  496. [tcpSocket connectToHost:TCP_HOST onPort:TCP_PORT viaInterface:[netconf objectForKey:@"interface"] withTimeout:2 error:&tcperr];
  497. if( tcperr != nil ){
  498. DDLogError(@"ERROR in tcpConnect() : TCP Connect error: %@", tcperr );
  499. }
  500. }
  501. }
  502. - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
  503. //DDLogError(@"TCP Disconnect: %@", [err localizedDescription] );
  504. }
  505. - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
  506. DDLogInfo(@"TCP Connected to %@:%u", host, port);
  507. //[tcpSocket readDataToData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
  508. [tcpSocket readDataToData:[GCDAsyncSocket LFData] withTimeout:-1 tag:1];
  509. if( OnTCPSocketConnected_defined ){
  510. dispatch_async(dispatch_get_main_queue(), ^{
  511. NSString *js = [NSString stringWithFormat:@"OnTCPSocketConnected('%@','%hu');", host, port ];
  512. NSLog(@"TCP Objc->JS: %@", js);
  513. [webView stringByEvaluatingJavaScriptFromString:js];
  514. });
  515. }
  516. /// Let app know that we connected
  517. /*
  518. dispatch_async(dispatch_get_main_queue(), ^{
  519. if( [[webView stringByEvaluatingJavaScriptFromString:@"typeof OnTCPSocketConnected"] isEqualToString:@"function"] ){
  520. NSString *js = [NSString stringWithFormat:@"OnTCPSocketConnected('%@','%hu');", host, port ];
  521. NSLog(@"Objc->JS: %@", js);
  522. [webView stringByEvaluatingJavaScriptFromString:js];
  523. }
  524. });
  525. */
  526. }
  527. - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
  528. // The "didWrite" is actually not confirmed at all - called even if we're disconnected :-|
  529. DDLogInfo(@"TCP Write: %@", [sock userData] );
  530. }
  531. - (void)OnTCPSocketMessage:(NSString*)message {
  532. if( OnTCPSocketMessage_defined ){
  533. dispatch_async(dispatch_get_main_queue(), ^{
  534. NSString *js = [NSString stringWithFormat:@"OnTCPSocketMessage('%@');", message ];
  535. NSLog(@"Objc->JS: %@", js);
  536. [webView stringByEvaluatingJavaScriptFromString:js];
  537. });
  538. }
  539. /*
  540. /// Relay to app
  541. dispatch_async(dispatch_get_main_queue(), ^{
  542. if( [[webView stringByEvaluatingJavaScriptFromString:@"typeof OnTCPSocketMessage"] isEqualToString:@"function"] ){
  543. NSString *js = [NSString stringWithFormat:@"OnTCPSocketMessage('%@');", message ];
  544. NSLog(@"Objc->JS: %@", js);
  545. [webView stringByEvaluatingJavaScriptFromString:js];
  546. }
  547. });
  548. */
  549. }
  550. - (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag {
  551. NSString* string = [NSString stringWithUTF8String:[data bytes]];
  552. //DDLogInfo(@"TCP Read: RAW '%@'", string );
  553. //DDLogInfo(@"TCP Read: CLEANED '%@'", [self trim:string] );
  554. //DDLogInfo(@"TCP Read: TEST '%@'", [self trim:@" a b c "] );
  555. NSString* message = [NSString stringWithFormat:@"'%@'", [self trim:string]];
  556. DDLogInfo(@"TCP Read: %@", message );
  557. if( [message isEqualToString:@""] ){
  558. DDLogError(@"TCP Read EMPTY-MESSAGE");
  559. }else{
  560. /// use tag:0 for messaging (and any other tag for sys)
  561. if( tag == 0 ){
  562. if( OnTCPSocketMessage_defined ){
  563. /// select main thread
  564. dispatch_async(dispatch_get_main_queue(), ^{
  565. /// let app known about the message
  566. [[webView windowScriptObject] callWebScriptMethod:@"OnTCPSocketMessage" withArguments:[NSArray arrayWithObject:message]];
  567. });
  568. }
  569. /*
  570. // select main thread.
  571. dispatch_async(dispatch_get_main_queue(), ^{
  572. if( [[webView stringByEvaluatingJavaScriptFromString:@"typeof OnTCPSocketMessage"] isEqualToString:@"function"] ){
  573. NSLog(@"OnTCPSocketMessage is defined");
  574. [[webView windowScriptObject] callWebScriptMethod:@"OnTCPSocketMessage" withArguments:[NSArray arrayWithObject:message]];
  575. }
  576. });
  577. */
  578. }
  579. }
  580. [tcpSocket readDataToData:[GCDAsyncSocket LFData] withTimeout:-1 tag:0];
  581. }
  582. /*
  583. TODO: Expand "method-exists-verification" in sock.didReadData with support for calling Someobject.OnTCPSocketMessage()
  584. JavaScript:
  585. App.OnTCPMessage = function(msg){ console.log("App.OnTCPMessage: ", msg); };
  586. Objc:
  587. WebScriptObject* Obj = [win evaluateWebScript:@"App"];
  588. NSNumber* result = [Obj callWebScriptMethod:@"OnTCPMessage" withArguments:[NSArray arrayWithObject:@"a message"]];
  589. /// http://stackoverflow.com/questions/2333791/cocoa-webview-cannot-call-specific-javascript-method-using-callwebscriptmethod
  590. */
  591. /// [self trim:string]
  592. -(NSString*) trim:(NSString*)string {
  593. return [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet] ];
  594. }
  595. // ---------------------------------------------------------------------------------------------------------------------------
  596. #pragma mark - JavaSript Bridge
  597. // Allows JavaScript to call all our methods
  598. - (void)initJavaScriptBridge {
  599. // Initialize webInspector
  600. DDLogInfo(@"+ Initialized webInspector");
  601. [[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:@"WebKitDeveloperExtras"];
  602. [[NSUserDefaults standardUserDefaults] synchronize];
  603. //webInspector = [[WebInspector alloc] initWithWebView:webView];
  604. //[webInspector showConsole:webView];
  605. [webView setFrameLoadDelegate:self];
  606. [webView setResourceLoadDelegate: self];
  607. [webView setEditingDelegate:self];
  608. [webView setUIDelegate:self];
  609. //[NSClassFromString(@"WebView") performSelector:@selector(_enableRemoteInspector)];
  610. }
  611. - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
  612. /*
  613. NSLog(@"didFinishLoadForFrame");
  614. NSLog(@"Eval-test-1: %@", [webView stringByEvaluatingJavaScriptFromString:@"typeof OnTCPSocketMessage"] );
  615. NSLog(@"Eval-test-2: %hhd", [[webView stringByEvaluatingJavaScriptFromString:@"typeof OnTCPSocketMessage"] isEqualToString:@"function"] );
  616. */
  617. if( [[webView stringByEvaluatingJavaScriptFromString:@"typeof OnTCPSocketConnected"] isEqualToString:@"function"] ){
  618. DDLogInfo(@"OnTCPSocketConnected defined");
  619. OnTCPSocketConnected_defined = YES;
  620. }
  621. if( [[webView stringByEvaluatingJavaScriptFromString:@"typeof OnTCPSocketMessage"] isEqualToString:@"function"] ){
  622. DDLogInfo(@"OnTCPSocketMessage defined");
  623. OnTCPSocketMessage_defined = YES;
  624. }
  625. if( [[webView stringByEvaluatingJavaScriptFromString:@"typeof OnOSCMessage"] isEqualToString:@"function"] ){
  626. DDLogInfo(@"OnOSCMessage_defined defined");
  627. OnOSCMessage_defined = YES;
  628. }
  629. //if (![[webView stringByEvaluatingJavaScriptFromString:@"typeof WebViewJavascriptBridge == 'object'"] isEqualToString:@"true"]) {
  630. }
  631. // Map javascript object "Mio" to self
  632. - (void)webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)windowScriptObject forFrame:(WebFrame *)frame {
  633. DDLogInfo(@"+ Creating JavaScript Bridge for Object 'Mio'");
  634. [windowScriptObject setValue:self forKey:@"Mio"];
  635. [windowScriptObject setValue:MioVersion forKey:@"mioversion"];
  636. DDLogInfo(@"+ Creating FPS Monitor");
  637. /// Inject WebView FPS Meter script
  638. NSString * js =
  639. @"(function() {"
  640. @" var prt=Date.now();"
  641. @" var fps=0, min=Infinity, max=0, fms=0;"
  642. @" function update() {"
  643. @" var tim = Date.now();"
  644. @" fms++;"
  645. @" if( tim > prt + 1000 ) {"
  646. @" fps = Math.round( (fms*1000) / (tim-prt) );"
  647. @" min = Math.min( min, fps );"
  648. @" max = Math.max( max, fps );"
  649. @" prt = tim;"
  650. @" fms = 0;"
  651. @" var str = fps + ' FPS (' + min + '-' + max + ')';"
  652. @" Mio.ReportFPS( fps, str );"
  653. // @" console.log( str );"
  654. @" }"
  655. @" requestAnimationFrame( update );"
  656. @" }"
  657. @" update();"
  658. @"})();";
  659. [webView stringByEvaluatingJavaScriptFromString:js];
  660. DDLogInfo(@"# App");
  661. DDLogInfo(@"+ Delegating control to javascript app");
  662. /*
  663. //[NSCursor hide]; // test
  664. NSLog(@"!!! Fronting");
  665. uint PID = [[NSProcessInfo processInfo] processIdentifier];
  666. NSLog(@"PID: %u", PID);
  667. NSRunningApplication* app = [NSRunningApplication runningApplicationWithProcessIdentifier: PID];
  668. [app activateWithOptions: NSApplicationActivateAllWindows];
  669. */
  670. }
  671. // Map io methods to Obj-C implementations
  672. + (BOOL)isSelectorExcludedFromWebScript:(SEL)sel { return NO; }
  673. + (BOOL)isKeyExcludedFromWebScript:(const char *)name{ return NO; }
  674. // This logs all errors from Javascript. Needs the '[webView setUIDelegate:self]'
  675. - (void) webView:(WebView*)webView addMessageToConsole:(NSDictionary*)message {
  676. NSLog(@"CONSOLE: %@", message);
  677. if (![message isKindOfClass:[NSDictionary class]]) return;
  678. DDLogInfo(@"JavaScript Error: %@ in %@:%@",
  679. [message objectForKey:@"message"],
  680. [[message objectForKey:@"sourceURL"] lastPathComponent], // could be nil
  681. [message objectForKey:@"lineNumber"]);
  682. // Critical ?
  683. BOOL critical = NO;
  684. if([[message objectForKey:@"MessageLevel"] isEqualToString:@"ErrorMessageLevel"] ){
  685. critical = YES;
  686. [performanceUI addJSError];
  687. }
  688. NSString *msg = [message objectForKey:@"message"];
  689. if([msg rangeOfString:@"ReferenceError"].location != NSNotFound ) critical = YES;
  690. if( critical ){
  691. DDLogError(@"Critical JavaScript Error: %@ in %@:%@",
  692. [message objectForKey:@"message"],
  693. [[message objectForKey:@"sourceURL"] lastPathComponent], // could be nil
  694. [message objectForKey:@"lineNumber"]);
  695. ERROR_EXCEPTIONS++;
  696. // Actions
  697. if( AUTOHEAL ){
  698. // Check time since last heal
  699. // Last
  700. NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"heal" ofType:@"plist"];
  701. NSDictionary *plistDict = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
  702. NSInteger last = [[plistDict valueForKey:@"last"] integerValue];
  703. // Now
  704. NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
  705. [fmt setDateFormat:@"yyyyMMddHHmmss"]; // 201212131920
  706. NSInteger now = [[fmt stringFromDate:[NSDate date]] integerValue];
  707. // Update plist
  708. [plistDict setValue:[[NSNumber alloc] initWithInteger:now] forKey:@"last"];
  709. [plistDict writeToFile:plistPath atomically: YES];
  710. //NSLog(@"now:%lu, last:%lu, diff:%lu", now, last, (now-last) );
  711. if( (now-last) < 60 ){
  712. // too recent. Crash hard to prevent loops
  713. DDLogError(@"## ErrorAction: Autoheal=true, ACMonitoring=NOT_ASSERTED, Last error too recent to attempt heal.");
  714. DDLogError(@"## APPLICATION WILL CRASH");
  715. abort();
  716. //[[NSApplication sharedApplication] terminate:nil];
  717. }else{
  718. // heal
  719. DDLogError(@"## ErrorAction: Autoheal=true, ACMonitoring=NOT_ASSERTED");
  720. DDLogError(@"## APPLICATION WILL HEAL");
  721. [NSApp relaunch:nil];
  722. }
  723. }
  724. if( !AUTOHEAL ){
  725. if( ACMONITORING_ACTIVE ){
  726. if( CRASH_ON_JAVASCRIPT_ERRORS ){
  727. // Die, and let the watchdog restart us
  728. DDLogError(@"## ErrorAction: Autoheal=false, ACMonitoring=true");
  729. DDLogError(@"## APPLICATION WILL CRASH");
  730. // EXIT
  731. // a) trigger the crash-reporter (good for Uthelm);
  732. //abort();
  733. // or b) just close (good for A+C Monitoring)
  734. [[NSApplication sharedApplication] terminate:nil];
  735. }
  736. }
  737. }
  738. }
  739. }
  740. // ---------------------------------------------------------------------------------------------------------------------------
  741. #pragma mark - JavaScript API
  742. // Public JavaScript API
  743. /*
  744. The exposed functions will be accessible from JavaScript in the "io namespace", e.g.:
  745. <script>
  746. Mio.Log("Navigator: "+ Navigator);
  747. Mio.SendUDP("x_0.234");
  748. </script>
  749. */
  750. +(NSString*)webScriptNameForSelector:(SEL)sel {
  751. if(sel == @selector(JS_Log:)) return @"Log";
  752. if(sel == @selector(JS_AppReady:)) return @"Ready";
  753. if(sel == @selector(JS_SendUDP:)) return @"SendUDP";
  754. if(sel == @selector(JS_SendTCP:)) return @"SendTCP";
  755. if(sel == @selector(JS_LogStats:)) return @"LogStats";
  756. if(sel == @selector(JS_ReadFile:)) return @"ReadFile";
  757. if(sel == @selector(JS_ReportFPS:andSummary:)) return @"ReportFPS";
  758. if(sel == @selector(JS_WriteFile:withData:withType:)) return @"WriteFile";
  759. if(sel == @selector(JS_WriteFileDialog:withData:withType:)) return @"WriteFileDialog";
  760. return nil;
  761. }
  762. - (void)JS_ReportFPS:(NSString*)fps andSummary:(NSString*)displayString {
  763. //DDLogInfo(@"FPS-Report: %@, %@", [NSNumber numberWithLong:[fps integerValue]], displayString);
  764. [performanceUI setFPS:[NSNumber numberWithLong:[fps integerValue]] and:displayString];
  765. }
  766. - (void)JS_Log:(NSString*) logText {
  767. DDLogInfo(@"JS: %@", logText);
  768. //NSLog(@"io.Log: %@", logText);
  769. }
  770. - (void)JS_SendUDP:(NSString*) msg {
  771. //DDLogInfo(@"io.SendUDP: %@", msg);
  772. NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
  773. [udpSocket sendData:data toHost:UDP_HOST port:UDP_PORT withTimeout:-1 tag:0];
  774. /// Verbose Logging
  775. /*
  776. // for local debug / socket monitoring
  777. [udpSocket2 sendData:data toHost:@"127.0.0.1" port:4444 withTimeout:-1 tag:0];
  778. // log message to file
  779. NSString *UDP_LOG_FILE = @"/Applications/Mobiglobe/udp_internal_send_log.txt";
  780. NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
  781. [fmt setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss"];
  782. NSString *dateString = [fmt stringFromDate:[NSDate date]];
  783. NSFileHandle *output = [NSFileHandle fileHandleForWritingAtPath:UDP_LOG_FILE];
  784. if(output == nil) {
  785. //NSLog(@"Creating stats file");
  786. [[NSFileManager defaultManager] createFileAtPath:UDP_LOG_FILE contents:nil attributes:nil];
  787. output = [NSFileHandle fileHandleForWritingAtPath:UDP_LOG_FILE];
  788. } else {
  789. [output seekToEndOfFile];
  790. }
  791. [output writeData:[[NSString stringWithFormat:@"%@\t%@\n", dateString, msg] dataUsingEncoding:NSUTF8StringEncoding]];
  792. */
  793. ///
  794. }
  795. - (void)JS_SendTCP:(NSString *)msg {
  796. // TCP Sockets think they are connected until a write fails
  797. NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
  798. [tcpSocket setUserData:msg];
  799. [tcpSocket writeData:data withTimeout:-1 tag:0];
  800. }
  801. - (void)JS_AppReady:(NSString*) arg {
  802. DDLogInfo(@"io.AppReady");
  803. if( [self.window alphaValue] < 1.0f ){
  804. // Fade window in
  805. // https://gist.github.com/1397050
  806. [self.window setAlphaValue:0.f];
  807. //[self.window makeKeyAndOrderFront:nil];
  808. [self.window makeKeyAndOrderFront:self];
  809. [NSAnimationContext beginGrouping];
  810. [[NSAnimationContext currentContext] setDuration:1.0f];
  811. [[self.window animator] setAlphaValue:1.f];
  812. [NSAnimationContext endGrouping];
  813. }
  814. }
  815. - (void)JS_LogStats:(NSString *) msg {
  816. // Write $msg to the $STATISTICS_FILE logfile (/Applications/Mobiglobe/statistics.txt)
  817. NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
  818. [fmt setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss"];
  819. NSString *dateString = [fmt stringFromDate:[NSDate date]];
  820. NSFileHandle *output = [NSFileHandle fileHandleForWritingAtPath:STATISTICS_FILE];
  821. if(output == nil) {
  822. //NSLog(@"Creating stats file");
  823. [[NSFileManager defaultManager] createFileAtPath:STATISTICS_FILE contents:nil attributes:nil];
  824. output = [NSFileHandle fileHandleForWritingAtPath:STATISTICS_FILE];
  825. } else {
  826. // append
  827. [output seekToEndOfFile];
  828. }
  829. [output writeData:[[NSString stringWithFormat:@"%@\t%@\n", dateString, msg] dataUsingEncoding:NSUTF8StringEncoding]];
  830. }
  831. - (NSString *)JS_ReadFile:(NSString *)filename {
  832. // Prefix with CWD unless $filename starts with "/"
  833. if( ! [[filename substringToIndex:1] isEqualToString:@"/"] ){
  834. filename = [NSString stringWithFormat:@"%@/%@", [config valueForKey:@"app"], filename];
  835. NSLog(@"Using RELATIVE path from app.json > appDir: %@, Final:%@", [config valueForKey:@"app"], filename );
  836. }
  837. NSString *res = [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:nil];
  838. NSLog(@"ReadFile result: %@", res);
  839. return res;
  840. }
  841. - (void)JS_WriteFile:(NSString *)filename withData:(NSString *)strData withType:(NSString *)type {
  842. //DDLogInfo(@"JS_WriteFile filename:%@,\n type:%@,\n strData:%@", filename, type, strData);
  843. DDLogInfo(@"JS_WriteFile filename:%@, type:%@, data.length:%lu", filename, type, (unsigned long)[strData length]);
  844. // Resolve to HOMEDIR if $filename starts with "~"
  845. if( [[filename substringToIndex:1] isEqualToString:@"~"] ){
  846. filename = [NSString stringWithFormat:@"%@%@", NSHomeDirectory(), [filename substringFromIndex:1]];
  847. //NSLog(@"Using HOMEDIR. Final:%@", filename );
  848. }
  849. // Prefix with CWD unless $filename starts with "/"
  850. if( ! [[filename substringToIndex:1] isEqualToString:@"/"] ){
  851. filename = [NSString stringWithFormat:@"%@/%@", [config valueForKey:@"app"], filename];
  852. //NSLog(@"Using RELATIVE path from app.json > appDir: %@, Final:%@", [config valueForKey:@"app"], filename );
  853. }
  854. if( [type isEqualToString:@"BASE64"] ){
  855. // http://stackoverflow.com/questions/8341458/base64-decoding-issue-with-image-data-iphone
  856. // http://stackoverflow.com/questions/9452637/nsimage-from-nspasteboard-to-base64-for-http-post-not-working-as-expected
  857. NSString *b64str = [strData substringFromIndex:22]; // discard "data:image/png;base64," part
  858. NSBitmapImageRep *img = [NSBitmapImageRep imageRepWithData:[NSData dataWithBase64EncodedString:b64str]];
  859. NSData *png = [img representationUsingType:NSPNGFileType properties:nil];
  860. [png writeToFile:filename atomically:YES];
  861. }else{
  862. [strData writeToFile:filename atomically:YES encoding:NSUTF8StringEncoding error:nil];
  863. }
  864. }
  865. - (void)JS_WriteFileDialog:(NSString *)filename withData:(NSString *)strData withType:(NSString *)type {
  866. //DDLogInfo(@"JS_WriteFile filename:%@,\n type:%@,\n strData:%@", filename, type, strData);
  867. NSSavePanel *panel = [NSSavePanel savePanel];
  868. [panel setNameFieldStringValue:filename];
  869. [panel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) {
  870. if (result == NSFileHandlingPanelOKButton) {
  871. NSURL *selectedFileUrl = [panel URL];
  872. NSString *selectedFile = [[selectedFileUrl absoluteString] substringFromIndex:7]; // discard "file://"
  873. //NSLog(@"SaveAs selectedFileUrl:%@, selectedFile:%@", selectedFileUrl, selectedFile);
  874. [self JS_WriteFile:selectedFile withData:strData withType:type];
  875. }
  876. }];
  877. }
  878. // ---------------------------------------------------------------------------------------------------------------------------
  879. #pragma mark - View Cycle
  880. - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
  881. return YES;
  882. }
  883. - (void)applicationWillTerminate:(NSNotification *)notification{
  884. DDLogInfo(@"# QUIT");
  885. [NSCursor unhide];
  886. }
  887. @end