/mio/AppDelegate.m
Objective C | 1185 lines | 616 code | 274 blank | 295 comment | 160 complexity | 8dc42c9f3c55a370ec6aa702ded609d1 MD5 | raw file
- //
- // AppDelegate.m
- // tsx
- //
- // Created by Jørgen Skogmo on 16-11-2012.
- // Copyright (c) 2012 Jørgen Skogmo. All rights reserved.
- //
- #import "AppDelegate.h"
- #import "NSDataAdditions.h"
- /// Check this one: https://github.com/PabloGS/UIWebView-Debug/blob/master/WebView%2BDebug.m
- /// #import <objc/runtime.h>
- static const int ddLogLevel = LOG_LEVEL_VERBOSE;
- @implementation AppDelegate
- // ---------------------------------------------------------------------------------------------------------------------------
- #pragma mark - IB-actions and Keyboard Shortcuts
- - (void) OnKeyDown:(NSEvent *)evt {
- /// Forwarded from BorderlessWindow.keyDown
-
- unichar keyCode = [[evt characters] characterAtIndex: 0];
-
- //NSLog(@"AppDel.OnKeyDown:%u, %@", keyCode, evt);
-
- /// Handle command+key
- if ([evt modifierFlags] & NSCommandKeyMask) {
-
- if( keyCode == 114 ){ // r -> Reload
- NSLog(@"Shortcut: Reload WebView");
- //[(AppDelegate *)[[NSApplication sharedApplication] delegate] ReloadWebView];
- [self ReloadWebView];
- }
-
- if( keyCode == 112 ){ // p -> Toggle PerformanceUI
- NSLog(@"Shortcut: Show/Hide PerformanceUI");
- //[(AppDelegate *)[[NSApplication sharedApplication] delegate] TogglePerformaceUI];
- [self TogglePerformaceUI];
- }
-
- }
- }
- - (void) ReloadWebView {
- [webView reloadFromOrigin:nil];
- [performanceUI resetJSCounters];
- }
- - (void) TogglePerformaceUI {
- [performanceUI setHidden:![performanceUI isHidden]];
- }
- - (IBAction)openDocumentation:(id)sender {
- [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://github.com/baseio/mio"]];
- }
- // ---------------------------------------------------------------------------------------------------------------------------
- #pragma mark - Application Launch
- NSTimer *waitTimer;
- - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
-
-
- environment = [[Environment alloc] init];
- netconf = [environment getNetConf];
-
- OnTCPSocketConnected_defined = NO;
- OnTCPSocketMessage_defined = NO;
- OnOSCMessage_defined = NO;
-
- STATISTICS_FILE = @"/Applications/Mobiglobe/statistics.txt";
- ERROR_EXCEPTIONS = 0;
- MEMORY_USE = @"-";
-
- /// Order Performance-view in front of webview:
- [webView setWantsLayer:YES];
- [performanceUI setWantsLayer:YES];
-
- // Setup Logger for Console.app
- [DDLog addLogger:[DDASLLogger sharedInstance]];
-
- // Setup Logger for Xcode.app
- #ifdef DEBUG
- [DDLog addLogger:[DDTTYLogger sharedInstance]];
- #endif
-
- // Setup Logger for Archive
- // # Note: DDFileLogger.generateShortUUID() has been modified to create logfiles with sweeter filenames
- fileLogger = [[DDFileLogger alloc] init];
-
- fileLogger.maximumFileSize = 0; // Disable rolling due to filesize
- fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling
- fileLogger.logFileManager.maximumNumberOfLogFiles = 30; // Keep a month's worth of log files on the system
-
- [DDLog addLogger:fileLogger];
-
-
-
- // Read main info.plist
- NSDictionary *plist = [[NSBundle mainBundle] infoDictionary];
- NSString *shellVersion = [plist valueForKey:@"CFBundleShortVersionString"];
- NSString *shellBuildCode = [plist valueForKey:@"CFBundleVersion"];
- MioVersion = [NSString stringWithFormat:@"Mio v.%@ (%@)", shellVersion, shellBuildCode];
-
- DDLogInfo(@"Mio version '%@@%@' Starting", shellBuildCode, shellVersion );
-
- #pragma mark # Aboutbox
-
- [about_version setStringValue: MioVersion];
- [about_icon setImage:
- [[NSImage alloc] initWithContentsOfFile:
- [[NSBundle mainBundle] pathForResource:@"aboutbox" ofType:@"png"]]];
-
- /*
- NSMutableAttributedString* string = [[NSMutableAttributedString alloc] init];
-
- [string appendAttributedString: [NSAttributedString stringWithColor:@"Documentation: " color:@"222222"]];
- [string appendAttributedString: [NSAttributedString hyperlink:@"github.com/baseio/mio" href:@"http://github.com/baseio/mio" color:@"6685ed" underline:NO]];
-
- NSMutableParagraphStyle *style=[[NSMutableParagraphStyle alloc] init];
- [style setAlignment:NSCenterTextAlignment];
- [string addAttributes:[NSDictionary dictionaryWithObject:style forKey:NSParagraphStyleAttributeName] range:NSMakeRange(0,[string length])];
-
- //[about_homepage setAllowsEditingTextAttributes: YES];
- [about_homepage setSelectable: YES];
- [about_homepage setAttributedStringValue: string];
-
- 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>";
-
- NSAttributedString* string = [[NSAttributedString alloc] initWithHTML:[text dataUsingEncoding:NSUTF8StringEncoding] documentAttributes:nil];
- [about_homepage setAttributedStringValue: string];
- */
- [[aboutbox standardWindowButton:NSWindowZoomButton] setHidden:YES];
- [[aboutbox standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
-
- // CONFIG
- #pragma mark # Parse app.json config
-
- /// First, read in app.json from the bundle. On a clean build with no env vars this will be the default app
- NSError *err = nil;
- NSString *app_config = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/app.json"];
- NSString *appjson_content = [[NSString alloc] initWithContentsOfFile:app_config encoding:NSUTF8StringEncoding error:&err];
- if( err != nil ) DDLogError(@"Count not read app.json:%@", err);
- /// Second, see if the environment has a MIOAPP var, and attempt to load it
- //NSLog(@"ENV %@", [[NSProcessInfo processInfo] environment] );
- NSString *env_app_config = [[[NSProcessInfo processInfo] environment] objectForKey:@"MIOAPP"];
- if( env_app_config ) {
- // Obey ENV MIOAPP if present
- env_app_config = [env_app_config stringByAppendingString:@"/app.json"];
- NSString *env_appjson_content = [[NSString alloc] initWithContentsOfFile:env_app_config encoding:NSUTF8StringEncoding error:&err];
- if( err != nil ){
- DDLogError(@"Count not read %@/app.json:%@", env_app_config, err);
- }else{
- /// Load succeeded, use it
- appjson_content = env_appjson_content;
- }
- }
-
- //DDLogInfo(@"appjson_content: %@", appjson_content );
- // Parse app.json into Dictionary
-
- NSData *json = [appjson_content dataUsingEncoding:NSUTF8StringEncoding];
- config = [NSJSONSerialization JSONObjectWithData:json options:NSJSONReadingAllowFragments error:&err];
- if( err != nil ) DDLogError(@"app.json Error:%@", err);
-
-
- //DDLogInfo(@"App Config:%@", config);
- // Setup shell title
- NSString *title = @"io";
- if( [config valueForKey:@"name"] != nil ){
- title = [config valueForKey:@"name"];
- }
- // Setup shell Window properties
- NSDictionary *windowSettings = [config valueForKey:@"window"];
-
- // Hide mouse? (Only applicable in Fullscreen mode)
- BOOL hide_mouse = NO;
- if( [windowSettings valueForKey:@"hidemouse"] != nil ){
- if( ((int)[[windowSettings valueForKey:@"hidemouse"] integerValue]) == 1 ){
- hide_mouse = YES;
- }
- }
-
- // Hide menu?
- BOOL hide_menu = NO;
- if( [windowSettings valueForKey:@"menu"] != nil ){
- if( ((int)[[windowSettings valueForKey:@"menu"] integerValue]) == 0 ){
- hide_menu = YES;
- }
- }
-
- // Hide window frame?
- BOOL hide_frame = NO;
- if( [windowSettings valueForKey:@"frame"] != nil ){
- if( ((int)[[windowSettings valueForKey:@"frame"] integerValue]) == 0 ){
- hide_frame = YES;
- }
- }
-
- // Fullscreen? If so, also hide frame and menu
- BOOL use_fullscreen = NO;
- if( [windowSettings valueForKey:@"fullscreen"] != nil ){
- if( ((int)[[windowSettings valueForKey:@"fullscreen"] integerValue]) == 1 ){
- DDLogInfo(@"Fullscreen Requested");
- hide_menu = YES;
- hide_frame = YES;
- use_fullscreen = YES;
- }
- }
-
-
- // Apply
- #pragma mark # Apply window config
-
- DDLogInfo(@"# Window");
-
- //[self.window setAlphaValue:0.0]; // Hide at start, call io.Ready() from JS to show (fade-in) the window.
-
-
- // Default to mainScreen
- NSRect screenRect = [[NSScreen mainScreen] frame];
-
- NSPoint position = {.x=0, .y=0};
- BOOL useRequestedXY = false;
- if( [windowSettings valueForKey:@"x"] != nil ){
- position.x = ((int)[[windowSettings valueForKey:@"x"] integerValue]);
- useRequestedXY = true;
- }
- /// Note: y-position is currently not implemented
- if( [windowSettings valueForKey:@"y"] != nil ){
- position.y = ((int)[[windowSettings valueForKey:@"y"] integerValue]);
- useRequestedXY = true;
- }
-
- if(useRequestedXY){
- // Determine wich [NSScreen screens] index the x and y is IN
- uint _index = 0;
- uint sindex = 0;
- for (NSScreen* screen in [NSScreen screens]) {
- DDLogInfo(@"* Screen %d width:%d origin.x:%d", _index, (int)screen.frame.size.width, (int)screen.frame.origin.x );
- if( position.x > (int)screen.frame.origin.x && position.x < (int)(screen.frame.origin.x+screen.frame.size.width) ){
- sindex = _index;
- DDLogInfo(@"* Requested (window.x: %d) is on screen-index %d", (int)position.x, sindex);
- }
- _index ++;
- }
-
- screenRect = [[[NSScreen screens] objectAtIndex:sindex] frame];
- 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);
- }
-
-
-
-
- if( use_fullscreen ){
- DDLogInfo(@"+ Creating fullscreen window");
-
- /*
- // Kiosk mode
- NSApplicationPresentationOptions options =
- NSApplicationPresentationHideMenuBar
- + NSApplicationPresentationHideDock
- + NSApplicationPresentationDisableHideApplication
- + NSApplicationPresentationDisableProcessSwitching
- + NSApplicationPresentationDisableAppleMenu
- // + NSApplicationPresentationDisableForceQuit;
- ;
- [NSApp setPresentationOptions:options];
- */
- [NSMenu setMenuBarVisible:NO];
-
- [self.window setStyleMask:NSBorderlessWindowMask];
- [self.window setFrame:screenRect display:YES];
-
- }else{
-
- // Setup shell size
- int w = 600;
- int h = 300;
-
- int cw = (int)[[windowSettings valueForKey:@"width"] integerValue];
- int ch = (int)[[windowSettings valueForKey:@"height"] integerValue];
- if( cw != 0 ) w = cw;
- if( ch != 0 ) h = ch;
-
- if( !hide_frame){
- // add 22px to the height
- h += 22;
- }
-
- // Default position: window centered on mainScreeen
- int x = (screenRect.size.width - w) / 2;
- int y = (screenRect.size.height - h) / 2;
-
- if(useRequestedXY){
- x = position.x;
-
- // Keep Vertical center (for now) assuming screen@1 is talled than mainScreen
- y += screenRect.origin.y;
- position.y = y;
- }
- // ... also if the window is larger than the screen
- if( cw > screenRect.size.width ) x = - ((cw - screenRect.size.width) / 2);
-
- if( hide_frame ){
- [self.window setStyleMask:NSBorderlessWindowMask];
- }else{
- [self.window setTitle:[config valueForKey:@"name"]];
- }
-
- if( hide_menu ){
- [NSMenu setMenuBarVisible:NO];
- }
-
- DDLogInfo(@"+ Creating window x:%i, y:%i, w:%u, h:%u", x,y,w,h );
- [self.window setFrame:NSMakeRect(x,y,w,h) display:YES];
-
-
- /// Resizeable?
- if( [windowSettings valueForKey:@"resize"] != nil ){
- if( ((int)[[windowSettings valueForKey:@"resize"] integerValue]) == 0 ){
- [[self.window standardWindowButton:NSWindowZoomButton] setEnabled:NO];
- [[self.window standardWindowButton:NSWindowMiniaturizeButton] setEnabled:NO]; // or: setHidden:YES
- [self.window setStyleMask:[self.window styleMask] & ~NSResizableWindowMask];
- }
- }
-
- if(useRequestedXY){
- //DDLogInfo(@"> Moving win to x:%d, y:%d", x, y);
- //[self.window setFrameOrigin:position];
- }
- }
-
- [self.window makeKeyAndOrderFront:self];
- [self.window makeFirstResponder:webView];
-
-
- // If launched from CLI we need to force ourself to foreground:
- NSRunningApplication* app = [NSRunningApplication currentApplication];
- [app activateWithOptions: NSApplicationActivateIgnoringOtherApps];
-
- if( hide_mouse ){
- [NSCursor hide];
- }
-
-
- // Setup Socket(s)
- #pragma mark # Setup Socket(s)
-
- DDLogInfo(@"# Network");
- //netconf = [environment getNetConf];
- //netconf = [self GetNetConf];
- // -> ObjectsAndKeys:ifn, @"interface", addr, @"primaryIP", plussOne, @"primaryIP_Next", wifiAddress, @"secondaryIP", nil];
-
- //DDLogInfo(@"netconf: %@", netconf );
-
- DDLogInfo(@"* Primary IP: %@", [netconf objectForKey:@"primaryIP"] );
- if( [netconf objectForKey:@"secondaryIP"] ){
- DDLogInfo(@"* Secondary IP: %@", [netconf objectForKey:@"secondaryIP"] );
- }
-
-
- // Use pre-defined IP-endPoint ?
- // @010: Renamed 'auto' to 'next', Renamed 'hostname' to 'ipconf'
- NSString *host = @"next";
- if( [config valueForKey:@"ipconf"] != nil ){
- host = [config valueForKey:@"ipconf"];
- DDLogInfo(@"* Config.ipconf = %@", host);
- UDP_HOST = host;
- TCP_HOST = host;
- }
- if( [host isEqualToString:@"next"] ){
- UDP_HOST = [netconf objectForKey:@"primaryIP_Next"];
- TCP_HOST = [netconf objectForKey:@"primaryIP_Next"];
- }
-
- if( [host isEqualToString:@"local"] ){
- UDP_HOST = @"127.0.0.1";
- TCP_HOST = @"127.0.0.1";
- }
-
- // Setup UDP
- NSDictionary *udpSocketSettings = [config valueForKey:@"udpsocket"];
-
- if( [udpSocketSettings valueForKey:@"enabled"] != nil ){
- if( ((int)[[udpSocketSettings valueForKey:@"enabled"] integerValue]) == 1 ){
-
- if( [udpSocketSettings valueForKey:@"host"] != nil ){
- NSString *tmpUdpHost = [udpSocketSettings valueForKey:@"host"];
- if( [tmpUdpHost isNotEqualTo:@"auto"] ){
- UDP_HOST = tmpUdpHost;
- }
- }
-
- UDP_PORT = 3334;
- if( [udpSocketSettings valueForKey:@"port"] != nil ){
- UDP_PORT = ((uint)[[udpSocketSettings valueForKey:@"port"] integerValue]);
- }
-
- udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
-
- DDLogInfo(@"+ Creating UDP socket-link between: %@ and %@ on Port %u over \"%@\"", [netconf objectForKey:@"primaryIP"], UDP_HOST, UDP_PORT, [netconf objectForKey:@"interface"]);
-
- ///
- NSError *error = nil;
- if (![udpSocket bindToPort:UDP_PORT error:&error]){
- DDLogError(@"UDP Error: Can not bind to %u Error: %@", UDP_PORT, error);
- }
- if (![udpSocket beginReceiving:&error]){
- DDLogError(@"UDP Error: Can not receive on %u Error: %@", UDP_PORT, error);
- }
- }
- }
-
- // Setup TCP
- NSDictionary *tcpSocketSettings = [config valueForKey:@"tcpsocket"];
-
- if( [tcpSocketSettings valueForKey:@"enabled"] != nil ){
- if( ((int)[[tcpSocketSettings valueForKey:@"enabled"] integerValue]) == 1 ){
-
- if( [tcpSocketSettings valueForKey:@"host"] != nil ){
- NSString *tmpTcpHost = [tcpSocketSettings valueForKey:@"host"];
- if( [tmpTcpHost isNotEqualTo:@"auto"] ){
- TCP_HOST = tmpTcpHost;
- }
- }
-
- TCP_PORT = 3335; // Default
- if( [tcpSocketSettings valueForKey:@"port"] != nil ){
- TCP_PORT = ((uint)[[tcpSocketSettings valueForKey:@"port"] integerValue]);
- }
-
- tcpSocketQueue = dispatch_queue_create("tcpSocketQueue", NULL);
- tcpSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:tcpSocketQueue];
- ///tcpSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
-
- DDLogInfo(@"+ Creating TCP socket-link between: %@ and %@ on Port %u over \"%@\"", [netconf objectForKey:@"primaryIP"], TCP_HOST, TCP_PORT, [netconf objectForKey:@"interface"]);
-
- // instead of connecting directly, we run a repeating timer that will attempt to reconnect to the host if the connection drops:
- reconnectTcpTimer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(tcpReConnect:) userInfo:nil repeats:YES];
- DDLogInfo(@"+ Creating TCP ConnectionWatcher");
-
- }
- }
-
- // Use Monitoring ?
- #pragma mark # Setup Monitoring
-
- NSDictionary *monitoringSettings = [config valueForKey:@"monitoring"];
-
- if( [monitoringSettings valueForKey:@"enabled"] != nil ){
- if( ((int)[[monitoringSettings valueForKey:@"enabled"] integerValue]) == 1 ){
-
- ACMonitor = [[ACMonitoring alloc] initWithConfig: monitoringSettings];
- /*
-
- MON_IPS = [monitoringSettings valueForKey:@"ip"];
- MON_PORT = (uint)[[monitoringSettings valueForKey:@"port"] integerValue];
- monitoringSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
- BOOL monSockOK = YES;
- NSError *error = nil;
- if (![monitoringSocket bindToPort:MON_PORT error:&error]){
- DDLogError(@"Network Error: Can not bind to %u Error: %@", MON_PORT, error);
- monSockOK = NO;
- }
- if (![monitoringSocket beginReceiving:&error]){
- DDLogError(@"Network Error: Can not receive on %u Error: %@", MON_PORT, error);
- [monitoringSocket close];
- monSockOK = NO;
- }
-
- if( monSockOK == YES ){
- ACMONITORING_ACTIVE = YES;
- DDLogInfo(@"Monitoring enabled on port %u, accepting commands from %@", MON_PORT, [MON_IPS componentsJoinedByString:@", "]);
- }else{
- DDLogInfo(@"Monitoring failed to bind to port %u", MON_PORT);
- }
- */
- }
- }else{
- DDLogInfo(@"Monitoring disbled");
- }
-
-
- // Use Auto-heal ?
- AUTOHEAL = NO;
- if( [config valueForKey:@"autoheal"] != nil ){
- if( ((int)[[config valueForKey:@"autoheal"] integerValue]) == 1 ){
- AUTOHEAL = YES;
- }
- }
-
- CRASH_ON_JAVASCRIPT_ERRORS = YES;
- if( [config valueForKey:@"crash_on_error"] != nil ){
- if( ((int)[[config valueForKey:@"crash_on_error"] integerValue]) == 0 ){
- CRASH_ON_JAVASCRIPT_ERRORS = NO;
- }
- }
-
-
- #pragma mark # Init WebApp
-
- DDLogInfo(@"# Webview");
- // Prepare webkit
- [self initJavaScriptBridge];
-
- // Enable WebGL
- DDLogInfo(@"+ Enabling WebGL");
- [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"WebKitWebGLEnabled"];
- [[NSUserDefaults standardUserDefaults] synchronize];
-
-
- NSString *INDEXFILE = @"index.html";
- if( [config valueForKey:@"index"] != nil ){
- INDEXFILE = [config valueForKey:@"index"];
- }
-
-
- NSDictionary *httpSettings = [config valueForKey:@"httpserver"];
- HTTPSERVER = false;
- if( [httpSettings valueForKey:@"enabled"] != nil ){
- if( ((int)[[httpSettings valueForKey:@"enabled"] integerValue]) == 1 ){
- // Start HTTP Server. Note: I have modified HTTPConnection.m with improved MIME-types
- httpServer = [[HTTPServer alloc] init];
- HTTPSERVER = YES;
-
- NSString *httpRoot = [config valueForKey:@"app"];
- // If the $app does *not* start with a "/", rewrite to mainbundle:
- if( [[httpRoot substringToIndex:1] isNotEqualTo:@"/"] ){
- httpRoot = [NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] resourcePath], httpRoot];
- }
-
- HTTP_PORT = 8080;
- if( [httpSettings valueForKey:@"port"] != nil ){
- HTTP_PORT = ((uint)[[httpSettings valueForKey:@"port"] integerValue]);
- }
- [httpServer setPort:HTTP_PORT];
- [httpServer setDocumentRoot:httpRoot];
- NSError *error = nil;
- if([httpServer start:&error]){
- DDLogInfo(@"HTTPServer: Document root: %@", httpRoot );
- }else{
- DDLogError(@"HTTPServer: Error: %@", error);
- }
- NSString *url = [NSString stringWithFormat:@"http://127.0.0.1:%i%@", HTTP_PORT, INDEXFILE];
- DDLogInfo(@"+ Loading app '%@' from URL %@", title, url);
-
- [webView setMainFrameURL:url];
-
- }
- }
-
- NSDictionary *oscSettings = [config valueForKey:@"oscserver"];
- if( [oscSettings valueForKey:@"enabled"] != nil ){
- if( ((int)[[oscSettings valueForKey:@"enabled"] integerValue]) == 1 ){
-
- uint oscPort = 2555;
- if( [oscSettings valueForKey:@"port"] != nil ){
- oscPort = ((uint)[[oscSettings valueForKey:@"port"] integerValue]);
- }else{
- DDLogInfo(@"+ OSC Server using DEFAULT port %u", oscPort);
- }
- [self startOSCServerOnPort:oscPort];
- }
- }
-
-
- DDLogInfo(@"# Memory consumption: %@", [environment getMemoryReport] );
-
- if( !HTTPSERVER ){
- // Load application from file
- NSString *appdir = [config valueForKey:@"app"];
- if( [[appdir substringToIndex:1] isEqualToString:@"/"] ){
- appdir = [NSString stringWithFormat:@"%@/%@", appdir, INDEXFILE];
- }else{
- appdir = [NSString stringWithFormat:@"%@/%@/%@", [[NSBundle mainBundle] resourcePath], appdir, INDEXFILE];
- }
-
- DDLogInfo(@"+ Loading app '%@' from FILE %@", title, appdir);
- [webView setMainFrameURL:appdir];
- }
-
-
- }
- // ---------------------------------------------------------------------------------------------------------------------------
- #pragma mark - OSC Server
- - (void)startOSCServerOnPort:(uint)port {
- oscServer = [[F53OSCServer alloc] init];
- [oscServer setPort:port];
- [oscServer setDelegate:self];
- [oscServer startListening];
- DDLogInfo(@"+ OSC Server listening on port %u", port);
- }
- - (void)takeMessage:(F53OSCMessage *)message {
- NSString *addressPattern = message.addressPattern;
- NSArray *arguments = message.arguments;
-
- NSString *argsString = [arguments componentsJoinedByString:@","];
- if( OnOSCMessage_defined ){
- NSString *js = [NSString stringWithFormat:@"OnOSCMessage('%@','%@');", addressPattern, argsString ];
- NSLog(@"OSC Objc->JS: %@", js);
- [webView stringByEvaluatingJavaScriptFromString:js];
- }
- }
- // ---------------------------------------------------------------------------------------------------------------------------
- #pragma mark - UDP Socket-Socket Delegates
- - (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext {
-
- /// print message data
-
- /*
-
-
-
- NSString *senderIP = [GCDAsyncUdpSocket hostFromAddress:address];
- uint16_t senderPORT = [GCDAsyncUdpSocket portFromAddress:address];
-
-
- NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
- if (msg){
- DDLogInfo(@"UDPMessage from %@:%u : %@", senderIP, senderPORT, msg);
- }
- */
- }
- // ---------------------------------------------------------------------------------------------------------------------------
- #pragma mark - TCP-Socket Delegates
- - (void)tcpReConnect:(NSTimer *)timer {
- // Called continously by $reconnectTcpTimer
- // Note: The $timeout below _needs_ to be shorter than the $repeatInterval of the $reconnectTcpTimer
-
- if( ![tcpSocket isConnected] ){
- DDLogError(@" TCP ConnectionWatcher : Socket Disconnected: Attempting (re)connection");
- //[self tcpConnect];
- NSError *tcperr = nil;
- [tcpSocket connectToHost:TCP_HOST onPort:TCP_PORT viaInterface:[netconf objectForKey:@"interface"] withTimeout:2 error:&tcperr];
- if( tcperr != nil ){
- DDLogError(@"ERROR in tcpConnect() : TCP Connect error: %@", tcperr );
- }
- }
- }
- - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
- //DDLogError(@"TCP Disconnect: %@", [err localizedDescription] );
- }
- - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
- DDLogInfo(@"TCP Connected to %@:%u", host, port);
- //[tcpSocket readDataToData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
- [tcpSocket readDataToData:[GCDAsyncSocket LFData] withTimeout:-1 tag:1];
-
- if( OnTCPSocketConnected_defined ){
- dispatch_async(dispatch_get_main_queue(), ^{
- NSString *js = [NSString stringWithFormat:@"OnTCPSocketConnected('%@','%hu');", host, port ];
- NSLog(@"TCP Objc->JS: %@", js);
- [webView stringByEvaluatingJavaScriptFromString:js];
- });
- }
- /// Let app know that we connected
- /*
- dispatch_async(dispatch_get_main_queue(), ^{
- if( [[webView stringByEvaluatingJavaScriptFromString:@"typeof OnTCPSocketConnected"] isEqualToString:@"function"] ){
- NSString *js = [NSString stringWithFormat:@"OnTCPSocketConnected('%@','%hu');", host, port ];
- NSLog(@"Objc->JS: %@", js);
- [webView stringByEvaluatingJavaScriptFromString:js];
- }
- });
- */
- }
- - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
- // The "didWrite" is actually not confirmed at all - called even if we're disconnected :-|
- DDLogInfo(@"TCP Write: %@", [sock userData] );
- }
- - (void)OnTCPSocketMessage:(NSString*)message {
-
- if( OnTCPSocketMessage_defined ){
- dispatch_async(dispatch_get_main_queue(), ^{
- NSString *js = [NSString stringWithFormat:@"OnTCPSocketMessage('%@');", message ];
- NSLog(@"Objc->JS: %@", js);
- [webView stringByEvaluatingJavaScriptFromString:js];
- });
- }
-
- /*
- /// Relay to app
- dispatch_async(dispatch_get_main_queue(), ^{
- if( [[webView stringByEvaluatingJavaScriptFromString:@"typeof OnTCPSocketMessage"] isEqualToString:@"function"] ){
- NSString *js = [NSString stringWithFormat:@"OnTCPSocketMessage('%@');", message ];
- NSLog(@"Objc->JS: %@", js);
- [webView stringByEvaluatingJavaScriptFromString:js];
- }
- });
- */
- }
- - (void)socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag {
- NSString* string = [NSString stringWithUTF8String:[data bytes]];
- //DDLogInfo(@"TCP Read: RAW '%@'", string );
- //DDLogInfo(@"TCP Read: CLEANED '%@'", [self trim:string] );
- //DDLogInfo(@"TCP Read: TEST '%@'", [self trim:@" a b c "] );
-
- NSString* message = [NSString stringWithFormat:@"'%@'", [self trim:string]];
- DDLogInfo(@"TCP Read: %@", message );
- if( [message isEqualToString:@""] ){
- DDLogError(@"TCP Read EMPTY-MESSAGE");
- }else{
- /// use tag:0 for messaging (and any other tag for sys)
- if( tag == 0 ){
-
- if( OnTCPSocketMessage_defined ){
- /// select main thread
- dispatch_async(dispatch_get_main_queue(), ^{
- /// let app known about the message
- [[webView windowScriptObject] callWebScriptMethod:@"OnTCPSocketMessage" withArguments:[NSArray arrayWithObject:message]];
- });
- }
- /*
- // select main thread.
- dispatch_async(dispatch_get_main_queue(), ^{
- if( [[webView stringByEvaluatingJavaScriptFromString:@"typeof OnTCPSocketMessage"] isEqualToString:@"function"] ){
- NSLog(@"OnTCPSocketMessage is defined");
- [[webView windowScriptObject] callWebScriptMethod:@"OnTCPSocketMessage" withArguments:[NSArray arrayWithObject:message]];
- }
- });
- */
- }
- }
- [tcpSocket readDataToData:[GCDAsyncSocket LFData] withTimeout:-1 tag:0];
- }
- /*
- TODO: Expand "method-exists-verification" in sock.didReadData with support for calling Someobject.OnTCPSocketMessage()
-
- JavaScript:
- App.OnTCPMessage = function(msg){ console.log("App.OnTCPMessage: ", msg); };
-
- Objc:
- WebScriptObject* Obj = [win evaluateWebScript:@"App"];
- NSNumber* result = [Obj callWebScriptMethod:@"OnTCPMessage" withArguments:[NSArray arrayWithObject:@"a message"]];
- /// http://stackoverflow.com/questions/2333791/cocoa-webview-cannot-call-specific-javascript-method-using-callwebscriptmethod
-
- */
- /// [self trim:string]
- -(NSString*) trim:(NSString*)string {
- return [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet] ];
- }
- // ---------------------------------------------------------------------------------------------------------------------------
- #pragma mark - JavaSript Bridge
- // Allows JavaScript to call all our methods
- - (void)initJavaScriptBridge {
-
- // Initialize webInspector
- DDLogInfo(@"+ Initialized webInspector");
- [[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:@"WebKitDeveloperExtras"];
- [[NSUserDefaults standardUserDefaults] synchronize];
-
- //webInspector = [[WebInspector alloc] initWithWebView:webView];
- //[webInspector showConsole:webView];
-
- [webView setFrameLoadDelegate:self];
- [webView setResourceLoadDelegate: self];
- [webView setEditingDelegate:self];
- [webView setUIDelegate:self];
-
- //[NSClassFromString(@"WebView") performSelector:@selector(_enableRemoteInspector)];
- }
- - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
- /*
- NSLog(@"didFinishLoadForFrame");
- NSLog(@"Eval-test-1: %@", [webView stringByEvaluatingJavaScriptFromString:@"typeof OnTCPSocketMessage"] );
- NSLog(@"Eval-test-2: %hhd", [[webView stringByEvaluatingJavaScriptFromString:@"typeof OnTCPSocketMessage"] isEqualToString:@"function"] );
- */
-
- if( [[webView stringByEvaluatingJavaScriptFromString:@"typeof OnTCPSocketConnected"] isEqualToString:@"function"] ){
- DDLogInfo(@"OnTCPSocketConnected defined");
- OnTCPSocketConnected_defined = YES;
- }
-
- if( [[webView stringByEvaluatingJavaScriptFromString:@"typeof OnTCPSocketMessage"] isEqualToString:@"function"] ){
- DDLogInfo(@"OnTCPSocketMessage defined");
- OnTCPSocketMessage_defined = YES;
- }
-
- if( [[webView stringByEvaluatingJavaScriptFromString:@"typeof OnOSCMessage"] isEqualToString:@"function"] ){
- DDLogInfo(@"OnOSCMessage_defined defined");
- OnOSCMessage_defined = YES;
- }
-
- //if (![[webView stringByEvaluatingJavaScriptFromString:@"typeof WebViewJavascriptBridge == 'object'"] isEqualToString:@"true"]) {
-
- }
- // Map javascript object "Mio" to self
- - (void)webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)windowScriptObject forFrame:(WebFrame *)frame {
- DDLogInfo(@"+ Creating JavaScript Bridge for Object 'Mio'");
- [windowScriptObject setValue:self forKey:@"Mio"];
- [windowScriptObject setValue:MioVersion forKey:@"mioversion"];
-
- DDLogInfo(@"+ Creating FPS Monitor");
- /// Inject WebView FPS Meter script
- NSString * js =
- @"(function() {"
- @" var prt=Date.now();"
- @" var fps=0, min=Infinity, max=0, fms=0;"
- @" function update() {"
- @" var tim = Date.now();"
- @" fms++;"
- @" if( tim > prt + 1000 ) {"
- @" fps = Math.round( (fms*1000) / (tim-prt) );"
- @" min = Math.min( min, fps );"
- @" max = Math.max( max, fps );"
- @" prt = tim;"
- @" fms = 0;"
- @" var str = fps + ' FPS (' + min + '-' + max + ')';"
- @" Mio.ReportFPS( fps, str );"
- // @" console.log( str );"
- @" }"
- @" requestAnimationFrame( update );"
- @" }"
- @" update();"
- @"})();";
- [webView stringByEvaluatingJavaScriptFromString:js];
-
- DDLogInfo(@"# App");
- DDLogInfo(@"+ Delegating control to javascript app");
-
- /*
- //[NSCursor hide]; // test
-
- NSLog(@"!!! Fronting");
-
- uint PID = [[NSProcessInfo processInfo] processIdentifier];
- NSLog(@"PID: %u", PID);
-
- NSRunningApplication* app = [NSRunningApplication runningApplicationWithProcessIdentifier: PID];
- [app activateWithOptions: NSApplicationActivateAllWindows];
- */
- }
- // Map io methods to Obj-C implementations
- + (BOOL)isSelectorExcludedFromWebScript:(SEL)sel { return NO; }
- + (BOOL)isKeyExcludedFromWebScript:(const char *)name{ return NO; }
- // This logs all errors from Javascript. Needs the '[webView setUIDelegate:self]'
- - (void) webView:(WebView*)webView addMessageToConsole:(NSDictionary*)message {
- NSLog(@"CONSOLE: %@", message);
-
- if (![message isKindOfClass:[NSDictionary class]]) return;
-
- DDLogInfo(@"JavaScript Error: %@ in %@:%@",
- [message objectForKey:@"message"],
- [[message objectForKey:@"sourceURL"] lastPathComponent], // could be nil
- [message objectForKey:@"lineNumber"]);
-
- // Critical ?
-
- BOOL critical = NO;
-
- if([[message objectForKey:@"MessageLevel"] isEqualToString:@"ErrorMessageLevel"] ){
- critical = YES;
- [performanceUI addJSError];
- }
-
- NSString *msg = [message objectForKey:@"message"];
-
- if([msg rangeOfString:@"ReferenceError"].location != NSNotFound ) critical = YES;
-
- if( critical ){
- DDLogError(@"Critical JavaScript Error: %@ in %@:%@",
- [message objectForKey:@"message"],
- [[message objectForKey:@"sourceURL"] lastPathComponent], // could be nil
- [message objectForKey:@"lineNumber"]);
-
-
- ERROR_EXCEPTIONS++;
-
- // Actions
-
- if( AUTOHEAL ){
- // Check time since last heal
-
- // Last
- NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"heal" ofType:@"plist"];
- NSDictionary *plistDict = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
- NSInteger last = [[plistDict valueForKey:@"last"] integerValue];
-
- // Now
- NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
- [fmt setDateFormat:@"yyyyMMddHHmmss"]; // 201212131920
- NSInteger now = [[fmt stringFromDate:[NSDate date]] integerValue];
- // Update plist
- [plistDict setValue:[[NSNumber alloc] initWithInteger:now] forKey:@"last"];
- [plistDict writeToFile:plistPath atomically: YES];
-
- //NSLog(@"now:%lu, last:%lu, diff:%lu", now, last, (now-last) );
- if( (now-last) < 60 ){
- // too recent. Crash hard to prevent loops
- DDLogError(@"## ErrorAction: Autoheal=true, ACMonitoring=NOT_ASSERTED, Last error too recent to attempt heal.");
- DDLogError(@"## APPLICATION WILL CRASH");
- abort();
- //[[NSApplication sharedApplication] terminate:nil];
- }else{
- // heal
- DDLogError(@"## ErrorAction: Autoheal=true, ACMonitoring=NOT_ASSERTED");
- DDLogError(@"## APPLICATION WILL HEAL");
- [NSApp relaunch:nil];
- }
- }
-
- if( !AUTOHEAL ){
- if( ACMONITORING_ACTIVE ){
-
- if( CRASH_ON_JAVASCRIPT_ERRORS ){
- // Die, and let the watchdog restart us
- DDLogError(@"## ErrorAction: Autoheal=false, ACMonitoring=true");
- DDLogError(@"## APPLICATION WILL CRASH");
-
- // EXIT
- // a) trigger the crash-reporter (good for Uthelm);
- //abort();
- // or b) just close (good for A+C Monitoring)
- [[NSApplication sharedApplication] terminate:nil];
- }
- }
- }
-
-
- }
- }
- // ---------------------------------------------------------------------------------------------------------------------------
- #pragma mark - JavaScript API
- // Public JavaScript API
- /*
- The exposed functions will be accessible from JavaScript in the "io namespace", e.g.:
- <script>
- Mio.Log("Navigator: "+ Navigator);
- Mio.SendUDP("x_0.234");
- </script>
- */
- +(NSString*)webScriptNameForSelector:(SEL)sel {
- if(sel == @selector(JS_Log:)) return @"Log";
- if(sel == @selector(JS_AppReady:)) return @"Ready";
- if(sel == @selector(JS_SendUDP:)) return @"SendUDP";
- if(sel == @selector(JS_SendTCP:)) return @"SendTCP";
- if(sel == @selector(JS_LogStats:)) return @"LogStats";
- if(sel == @selector(JS_ReadFile:)) return @"ReadFile";
-
- if(sel == @selector(JS_ReportFPS:andSummary:)) return @"ReportFPS";
-
- if(sel == @selector(JS_WriteFile:withData:withType:)) return @"WriteFile";
- if(sel == @selector(JS_WriteFileDialog:withData:withType:)) return @"WriteFileDialog";
-
- return nil;
- }
- - (void)JS_ReportFPS:(NSString*)fps andSummary:(NSString*)displayString {
- //DDLogInfo(@"FPS-Report: %@, %@", [NSNumber numberWithLong:[fps integerValue]], displayString);
-
- [performanceUI setFPS:[NSNumber numberWithLong:[fps integerValue]] and:displayString];
- }
-
- - (void)JS_Log:(NSString*) logText {
- DDLogInfo(@"JS: %@", logText);
- //NSLog(@"io.Log: %@", logText);
- }
- - (void)JS_SendUDP:(NSString*) msg {
- //DDLogInfo(@"io.SendUDP: %@", msg);
- NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
- [udpSocket sendData:data toHost:UDP_HOST port:UDP_PORT withTimeout:-1 tag:0];
-
-
- /// Verbose Logging
- /*
- // for local debug / socket monitoring
- [udpSocket2 sendData:data toHost:@"127.0.0.1" port:4444 withTimeout:-1 tag:0];
- // log message to file
- NSString *UDP_LOG_FILE = @"/Applications/Mobiglobe/udp_internal_send_log.txt";
- NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
- [fmt setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss"];
- NSString *dateString = [fmt stringFromDate:[NSDate date]];
-
- NSFileHandle *output = [NSFileHandle fileHandleForWritingAtPath:UDP_LOG_FILE];
- if(output == nil) {
- //NSLog(@"Creating stats file");
- [[NSFileManager defaultManager] createFileAtPath:UDP_LOG_FILE contents:nil attributes:nil];
- output = [NSFileHandle fileHandleForWritingAtPath:UDP_LOG_FILE];
- } else {
- [output seekToEndOfFile];
- }
- [output writeData:[[NSString stringWithFormat:@"%@\t%@\n", dateString, msg] dataUsingEncoding:NSUTF8StringEncoding]];
- */
- ///
-
- }
- - (void)JS_SendTCP:(NSString *)msg {
- // TCP Sockets think they are connected until a write fails
- NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
- [tcpSocket setUserData:msg];
- [tcpSocket writeData:data withTimeout:-1 tag:0];
- }
- - (void)JS_AppReady:(NSString*) arg {
- DDLogInfo(@"io.AppReady");
- if( [self.window alphaValue] < 1.0f ){
- // Fade window in
- // https://gist.github.com/1397050
- [self.window setAlphaValue:0.f];
- //[self.window makeKeyAndOrderFront:nil];
- [self.window makeKeyAndOrderFront:self];
- [NSAnimationContext beginGrouping];
- [[NSAnimationContext currentContext] setDuration:1.0f];
- [[self.window animator] setAlphaValue:1.f];
- [NSAnimationContext endGrouping];
- }
- }
- - (void)JS_LogStats:(NSString *) msg {
- // Write $msg to the $STATISTICS_FILE logfile (/Applications/Mobiglobe/statistics.txt)
- NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
- [fmt setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss"];
- NSString *dateString = [fmt stringFromDate:[NSDate date]];
-
- NSFileHandle *output = [NSFileHandle fileHandleForWritingAtPath:STATISTICS_FILE];
- if(output == nil) {
- //NSLog(@"Creating stats file");
- [[NSFileManager defaultManager] createFileAtPath:STATISTICS_FILE contents:nil attributes:nil];
- output = [NSFileHandle fileHandleForWritingAtPath:STATISTICS_FILE];
- } else {
- // append
- [output seekToEndOfFile];
- }
- [output writeData:[[NSString stringWithFormat:@"%@\t%@\n", dateString, msg] dataUsingEncoding:NSUTF8StringEncoding]];
- }
- - (NSString *)JS_ReadFile:(NSString *)filename {
-
- // Prefix with CWD unless $filename starts with "/"
- if( ! [[filename substringToIndex:1] isEqualToString:@"/"] ){
- filename = [NSString stringWithFormat:@"%@/%@", [config valueForKey:@"app"], filename];
- NSLog(@"Using RELATIVE path from app.json > appDir: %@, Final:%@", [config valueForKey:@"app"], filename );
- }
-
- NSString *res = [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:nil];
- NSLog(@"ReadFile result: %@", res);
- return res;
- }
- - (void)JS_WriteFile:(NSString *)filename withData:(NSString *)strData withType:(NSString *)type {
- //DDLogInfo(@"JS_WriteFile filename:%@,\n type:%@,\n strData:%@", filename, type, strData);
- DDLogInfo(@"JS_WriteFile filename:%@, type:%@, data.length:%lu", filename, type, (unsigned long)[strData length]);
- // Resolve to HOMEDIR if $filename starts with "~"
- if( [[filename substringToIndex:1] isEqualToString:@"~"] ){
- filename = [NSString stringWithFormat:@"%@%@", NSHomeDirectory(), [filename substringFromIndex:1]];
- //NSLog(@"Using HOMEDIR. Final:%@", filename );
- }
- // Prefix with CWD unless $filename starts with "/"
- if( ! [[filename substringToIndex:1] isEqualToString:@"/"] ){
- filename = [NSString stringWithFormat:@"%@/%@", [config valueForKey:@"app"], filename];
- //NSLog(@"Using RELATIVE path from app.json > appDir: %@, Final:%@", [config valueForKey:@"app"], filename );
- }
-
- if( [type isEqualToString:@"BASE64"] ){
- // http://stackoverflow.com/questions/8341458/base64-decoding-issue-with-image-data-iphone
- // http://stackoverflow.com/questions/9452637/nsimage-from-nspasteboard-to-base64-for-http-post-not-working-as-expected
- NSString *b64str = [strData substringFromIndex:22]; // discard "data:image/png;base64," part
- NSBitmapImageRep *img = [NSBitmapImageRep imageRepWithData:[NSData dataWithBase64EncodedString:b64str]];
- NSData *png = [img representationUsingType:NSPNGFileType properties:nil];
- [png writeToFile:filename atomically:YES];
-
- }else{
- [strData writeToFile:filename atomically:YES encoding:NSUTF8StringEncoding error:nil];
- }
-
- }
- - (void)JS_WriteFileDialog:(NSString *)filename withData:(NSString *)strData withType:(NSString *)type {
- //DDLogInfo(@"JS_WriteFile filename:%@,\n type:%@,\n strData:%@", filename, type, strData);
-
- NSSavePanel *panel = [NSSavePanel savePanel];
- [panel setNameFieldStringValue:filename];
- [panel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) {
- if (result == NSFileHandlingPanelOKButton) {
- NSURL *selectedFileUrl = [panel URL];
- NSString *selectedFile = [[selectedFileUrl absoluteString] substringFromIndex:7]; // discard "file://"
- //NSLog(@"SaveAs selectedFileUrl:%@, selectedFile:%@", selectedFileUrl, selectedFile);
- [self JS_WriteFile:selectedFile withData:strData withType:type];
- }
- }];
- }
- // ---------------------------------------------------------------------------------------------------------------------------
- #pragma mark - View Cycle
- - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
- return YES;
- }
- - (void)applicationWillTerminate:(NSNotification *)notification{
- DDLogInfo(@"# QUIT");
-
- [NSCursor unhide];
- }
- @end