1 ;;;============================================================================
5 ;;; Copyright (c) 2011-2012 by Marc Feeley, All Rights Reserved.
7 ;;;============================================================================
9 (##namespace ("intf#"))
11 (##include "~~lib/gambit#.scm")
12 (##include "~~lib/_gambit#.scm")
14 (##include "intf#.scm")
15 (##include "url#.scm")
26 ;;;============================================================================
28 ;; Interface with Objective-C.
30 (c-declare #<<c-declare-end
32 #include <objc/objc.h>
34 const char *class_getName(Class cls);
35 id objc_getClass(const char *name);
36 id objc_msgSend(id self, SEL op, ...);
45 ___SCMOBJ release_id(void *ptr)
47 id x = ___CAST(id,ptr);
50 return ___FIX(___NO_ERR);
53 Class retain_Class(Class x)
60 ___SCMOBJ release_Class(void *ptr)
62 Class x = ___CAST(Class,ptr);
65 return ___FIX(___NO_ERR);
71 (c-define-type id (pointer (struct "objc_object") (id Class) "release_id"))
72 (c-define-type Class (pointer (struct "objc_class") (Class id) "release_Class"))
73 (c-define-type SEL (pointer (struct "objc_selector") (SEL)))
76 (c-lambda (nonnull-char-string) Class
77 "___result = retain_Class(objc_getClass(___arg1));"))
80 (c-lambda (Class) nonnull-char-string
81 "___result = ___CAST(char*,class_getName(___arg1));")) ;;;TODO: remove cast
84 (c-lambda (nonnull-UTF-8-string) SEL
85 "___result = sel_registerName(___arg1);"))
88 (c-lambda (SEL) nonnull-UTF-8-string
89 "___result = ___CAST(char*,sel_getName(___arg1));")) ;;;TODO: remove cast
91 ;; Message sending (with 0, 1 and 2 parameters).
95 "___result = retain_id(___CAST(id (*)(id, SEL),objc_msgSend)(___arg1, ___arg2));"))
98 (c-lambda (id SEL id) id
99 "___result = retain_id(___CAST(id (*)(id, SEL, id),objc_msgSend)(___arg1, ___arg2, ___arg3));"))
102 (c-lambda (id SEL id id) id
103 "___result = retain_id(___CAST(id (*)(id, SEL, id, id),objc_msgSend)(___arg1, ___arg2, ___arg3, ___arg4));"))
108 (c-lambda (id) nonnull-UTF-8-string
109 "___result = ___CAST(char*,[___CAST(NSString*,___arg1) UTF8String]);")) ;;;TODO: remove cast
112 (c-lambda (nonnull-UTF-8-string) id
113 "___result = retain_id([NSString stringWithUTF8String: ___arg1]);"))
117 "___result = [___CAST(NSNumber*,___arg1) boolValue];"))
121 "___result = retain_id([NSNumber numberWithBool:___arg1]);"))
125 "___result = [___CAST(NSNumber*,___arg1) intValue];"))
129 "___result = retain_id([NSNumber numberWithInt:___arg1]);"))
133 "___result = [___CAST(NSNumber*,___arg1) floatValue];"))
137 "___result = retain_id([NSNumber numberWithFloat:___arg1]);"))
140 (c-lambda (id) double
141 "___result = [___CAST(NSNumber*,___arg1) doubleValue];"))
144 (c-lambda (double) id
145 "___result = retain_id([NSNumber numberWithDouble:___arg1]);"))
147 ;;;----------------------------------------------------------------------------
149 ;; Implement conversions between NSString* and Scheme strings.
151 (c-declare #<<c-declare-end
153 #include <Foundation/NSString.h>
155 ___SCMOBJ SCMOBJ_to_NSStringSTAR(___SCMOBJ src, NSString **dst, int arg_num)
158 * Convert a Scheme string to NSString* .
166 else if (!___STRINGP(src))
167 return ___FIX(___STOC_WCHARSTRING_ERR+arg_num);
171 int len = ___INT(___STRINGLENGTH(src));
172 unichar *buf = ___alloc_mem(sizeof(unichar)*len);
175 return ___FIX(___STOC_HEAP_OVERFLOW_ERR+arg_num);
177 for (i=0; i<len; i++)
179 ___UCS_4 c = ___INT(___STRINGREF(src,___FIX(i)));
183 result = retain_id([NSString stringWithCharacters:buf length:len]);
190 return ___FIX(___NO_ERR);
193 ___SCMOBJ NSStringSTAR_to_SCMOBJ(NSString *src, ___SCMOBJ *dst, int arg_num)
202 int len = [src length];
204 result = ___alloc_scmobj(___sSTRING, len<<___LCS, ___STILL);
206 if (___FIXNUMP(result))
207 return ___FIX(___CTOS_HEAP_OVERFLOW_ERR+arg_num);
209 for (i=0; i<len; i++)
211 ___UCS_4 c = [src characterAtIndex:i];
212 ___STRINGSET(result,___FIX(i),___CHR(c))
218 return ___FIX(___NO_ERR);
221 #define ___BEGIN_CFUN_SCMOBJ_to_NSStringSTAR(src,dst,i) \
222 if ((___err = SCMOBJ_to_NSStringSTAR(src, &dst, i)) == ___FIX(___NO_ERR)) {
223 #define ___END_CFUN_SCMOBJ_to_NSStringSTAR(src,dst,i) }
225 #define ___BEGIN_CFUN_NSStringSTAR_to_SCMOBJ(src,dst) \
226 if ((___err = NSStringSTAR_to_SCMOBJ(src, &dst, ___RETURN_POS)) == ___FIX(___NO_ERR)) {
227 #define ___END_CFUN_NSStringSTAR_to_SCMOBJ(src,dst) \
228 ___EXT(___release_scmobj)(dst); }
230 #define ___BEGIN_SFUN_NSStringSTAR_to_SCMOBJ(src,dst,i) \
231 if ((___err = NSStringSTAR_to_SCMOBJ(src, &dst, i)) == ___FIX(___NO_ERR)) {
232 #define ___END_SFUN_NSStringSTAR_to_SCMOBJ(src,dst,i) \
233 ___EXT(___release_scmobj)(dst); }
235 #define ___BEGIN_SFUN_SCMOBJ_to_NSStringSTAR(src,dst) \
236 { ___err = SCMOBJ_to_NSStringSTAR(src, &dst, ___RETURN_POS);
237 #define ___END_SFUN_SCMOBJ_to_NSStringSTAR(src,dst) }
242 (c-define-type NSString* "NSString*"
243 "NSStringSTAR_to_SCMOBJ"
244 "SCMOBJ_to_NSStringSTAR"
247 ;;;----------------------------------------------------------------------------
249 ;; Interface with NSDate Class.
251 (define NSDate (string->Class "NSDate"))
252 (define alloc (string->SEL "alloc"))
253 (define init (string->SEL "init"))
254 (define description (string->SEL "description"))
258 (send0 (send0 (send0 NSDate alloc) init) description)))
260 ;;;----------------------------------------------------------------------------
262 ;; Interface with NSBundle Class.
264 (define NSBundle (string->Class "NSBundle"))
265 (define mainBundle (string->SEL "mainBundle"))
266 (define objectForInfoDictionaryKey (string->SEL "objectForInfoDictionaryKey:"))
268 (define (mainBundle-info key)
270 (send1 (send0 NSBundle mainBundle)
271 objectForInfoDictionaryKey (string->id key))))
275 (define CFBundleName (mainBundle-info "CFBundleName"))
276 (define CFBundleDisplayName (mainBundle-info "CFBundleDisplayName"))
278 ;;;----------------------------------------------------------------------------
280 ;; Interface with UIDevice Class.
282 (define currentDevice-batteryLevel
284 "___result = [[UIDevice currentDevice] batteryLevel];"))
286 (define currentDevice-batteryMonitoringEnabled
288 "___result = [UIDevice currentDevice].batteryMonitoringEnabled;"))
290 (define currentDevice-batteryMonitoringEnabled-set!
291 (c-lambda (bool) void
292 "[UIDevice currentDevice].batteryMonitoringEnabled = ___arg1;"))
294 (define currentDevice-multitaskingSupported
296 "___result = [UIDevice currentDevice].multitaskingSupported;"))
298 (define currentDevice-model
299 (c-lambda () NSString*
300 "___result = [[UIDevice currentDevice] model];"))
302 (define currentDevice-name
303 (c-lambda () NSString*
304 "___result = [[UIDevice currentDevice] name];"))
306 (define currentDevice-systemName
307 (c-lambda () NSString*
308 "___result = [[UIDevice currentDevice] systemName];"))
310 (define currentDevice-systemVersion
311 (c-lambda () NSString*
312 "___result = [[UIDevice currentDevice] systemVersion];"))
314 (define currentDevice-uniqueIdentifier
315 (c-lambda () NSString*
316 "___result = [[UIDevice currentDevice] uniqueIdentifier];"))
318 (define (device-status)
319 (currentDevice-batteryMonitoringEnabled-set! #t)
320 (list (currentDevice-batteryLevel)
321 (currentDevice-batteryMonitoringEnabled)
322 (currentDevice-multitaskingSupported)
323 (currentDevice-model)
325 (currentDevice-systemName)
326 (currentDevice-systemVersion)
327 (currentDevice-uniqueIdentifier)))
329 (define (device-model)
330 (let ((m (currentDevice-model)))
331 (cond ((has-prefix? m "iPhone")
333 ((has-prefix? m "iPod touch")
335 ((has-prefix? m "iPad")
341 (currentDevice-uniqueIdentifier))
344 ;;;----------------------------------------------------------------------------
346 ;; Interface with AudioToolbox.
348 (c-declare #<<c-declare-end
350 #import <AudioToolbox/AudioToolbox.h>
355 (c-define-type SystemSoundID unsigned-int32)
357 (define AudioServicesPlayAlertSound
358 (c-lambda (SystemSoundID) void "AudioServicesPlayAlertSound"))
360 (define AudioServicesPlaySystemSound
361 (c-lambda (SystemSoundID) void "AudioServicesPlaySystemSound"))
363 (define kSystemSoundID_FlashScreen #x00000FFE)
364 (define kSystemSoundID_Vibrate #x00000FFF)
365 (define kSystemSoundID_UserPreferredAlert #x00001000)
367 ;;;----------------------------------------------------------------------------
369 ;; Interface with ViewController.
371 (c-declare #<<c-declare-end
373 #include "ViewController.h"
378 ;; C functions callable from Scheme.
380 (define set-navigation
381 (c-lambda (int) void "set_navigation"))
383 (define show-cancelButton
384 (c-lambda () void "show_cancelButton"))
386 (define hide-cancelButton
387 (c-lambda () void "hide_cancelButton"))
390 (c-lambda (int) void "show_webView"))
392 (define show-textView
393 (c-lambda (int) void "show_textView"))
395 (define show-imageView
396 (c-lambda (int) void "show_imageView"))
398 (define set-textView-font
399 (c-lambda (int NSString* int) void "set_textView_font"))
401 (define set-textView-content
402 (c-lambda (int NSString*) void "set_textView_content"))
404 (define get-textView-content
405 (c-lambda (int) NSString* "get_textView_content"))
407 (define add-output-to-textView
408 (c-lambda (int NSString*) void "add_output_to_textView"))
410 (define add-input-to-textView
411 (c-lambda (int NSString*) void "add_input_to_textView"))
413 (define (set-webView-content view str #!optional (base-url-path #f) (enable-scaling #f) (mime-type "text/html"))
414 ((c-lambda (int NSString* NSString* bool NSString*) void "set_webView_content") view str base-url-path enable-scaling mime-type))
416 (define (set-webView-content-from-file view path #!optional (base-url-path (path-directory path)) (enable-scaling #f) (mime-type "text/html"))
417 ((c-lambda (int NSString* NSString* bool NSString*) void "set_webView_content_from_file") view path base-url-path enable-scaling mime-type))
419 (define eval-js-in-webView
420 (c-lambda (int NSString*) NSString* "eval_js_in_webView"))
423 (c-lambda (NSString*) void "open_URL"))
425 (define set-idle-timer
426 (c-lambda (bool) void "set_idle_timer"))
428 (define set-toolbar-alpha
429 (c-lambda (double) void "set_toolbar_alpha"))
431 (define segm-ctrl-set-title
432 (c-lambda (int NSString*) void "segm_ctrl_set_title"))
434 (define segm-ctrl-insert
435 (c-lambda (int NSString*) void "segm_ctrl_insert"))
437 (define segm-ctrl-remove
438 (c-lambda (int) void "segm_ctrl_remove"))
440 (define segm-ctrl-remove-all
441 (c-lambda () void "segm_ctrl_remove_all"))
444 (c-lambda (NSString* NSString*) void "set_pref"))
447 (c-lambda (NSString*) NSString* "get_pref"))
449 (define set-pasteboard
450 (c-lambda (NSString*) void "set_pasteboard"))
452 (define get-pasteboard
453 (c-lambda () NSString* "get_pasteboard"))
456 (c-lambda (NSString* NSString* NSString* NSString*) void "popup_alert"))
458 (define (setup-location-updates desired-accuracy #!optional (distance-filter 0.0))
459 ((c-lambda (double double) void "setup_location_updates") desired-accuracy distance-filter))
461 (define (set-navigation-bar titles)
462 (segm-ctrl-remove-all)
463 (let loop ((i 0) (lst titles))
466 (segm-ctrl-insert i (car lst))
467 (loop (+ i 1) (cdr lst))))))
469 ;; Scheme functions callable from C.
471 (c-define (send-input str) (NSString*) void "send_input" "extern"
473 (let ((rp repl-port))
477 (force-output rp)))))
479 (c-define (send-event str) (NSString*) void "send_event" "extern"
481 (let ((ep event-port))
485 (force-output ep)))))
487 (c-define (send-key str) (NSString*) void "send_key" "extern"
489 (let ((hk handle-key))
493 (define handle-key #f)
497 (add-input-to-textView 0 str)))
499 (c-define (heartbeat) () double "heartbeat" "extern"
501 ;; make sure other threads get to run
502 (##thread-heartbeat!)
504 ;; check if there has been any REPL output
505 (let ((rp repl-port))
507 (let ((output (read-line rp #f)))
509 (add-output-to-textView 0 output)))))
511 ;; return interval until next heartbeat
512 (next-heartbeat-interval))
514 (define (next-heartbeat-interval)
516 (##declare (not interrupts-enabled))
522 (let ((root (macro-btq-left run-queue)))
523 (and (##not (##eq? root run-queue))
524 (##eq? (macro-btq-left root) run-queue)
525 (##eq? (macro-btq-right root) run-queue))))))
526 (if runnable-threads?
529 ;; There are other threads that can run, so request
530 ;; to call "heartbeat" real soon to run those threads.
534 (macro-toq-leftmost run-queue))
536 (if (##eq? next-sleeper run-queue)
539 ;; There is a sleeping thread, so figure out in
540 ;; how much time it needs to wake up.
542 (##flonum.- (macro-thread-timeout next-sleeper)
543 (##current-time-point))
544 interval-min-wait))))
546 (macro-btq-deq-next run-queue))
548 (if (##eq? next-condvar run-queue)
549 interval-no-io-pending ;; I/O is not pending, just relax
550 interval-io-pending))) ;; I/O is pending, so come back soon
551 (##flonum.min sleep-interval io-interval)))))
553 (define interval-runnable 0.0)
554 (set! interval-runnable 0.0)
556 (define interval-io-pending 0.0)
557 (set! interval-io-pending 0.02)
559 (define interval-no-io-pending 0.0)
560 (set! interval-no-io-pending 1.0)
562 (define interval-min-wait 0.0)
563 (set! interval-min-wait 0.0001)
565 (c-define (eval-string str) (NSString*) NSString* "eval_string" "extern"
568 (define (catch-all-errors thunk)
569 (with-exception-catcher
571 (write-to-string exc))
574 (define (write-to-string obj)
575 (with-output-to-string
577 (lambda () (write obj))))
579 (define (read-from-string str)
580 (with-input-from-string str read))
583 (lambda () (write-to-string (eval (read-from-string str)))))))
585 ;;;----------------------------------------------------------------------------
587 ;; Setup pipe to do I/O on the REPL being run by the primordial thread.
589 (define repl-port #f)
591 (receive (i o) (open-string-pipe)
593 ;; Hack... set the names of the port.
594 (##vector-set! i 4 (lambda (port) '(console)))
596 (set! ##stdio/console-repl-channel (##make-repl-channel-ports i i))
600 (input-port-timeout-set! o -inf.0))
602 ;;;----------------------------------------------------------------------------
604 ;; Handling of events from the webView.
606 (define event-port #f)
608 (define event-handler
613 (define location-update-event-handler
618 (receive (i o) (open-vector-pipe '(direction: input))
626 (let ((event (read i)))
627 (if (not (eof-object? event))
628 (let ((x (has-prefix? event "location-update:")))
631 (with-exception-catcher
635 (list->vector (with-input-from-string x read-all))))))
636 (location-update-event-handler location))
637 (event-handler event))
640 (define (set-event-handler proc)
641 (set! event-handler (proc event-handler)))
643 (define (set-location-update-event-handler proc)
644 (set! location-update-event-handler proc))
646 (define (show-view view)
649 (define (set-view-content view content #!optional (base-url-path #f) (enable-scaling #f) (mime-type "text/html"))
652 (with-output-to-string "" (lambda () (print content)))
657 (define (has-prefix? str prefix)
660 (let ((len-str (string-length str))
661 (len-prefix (string-length prefix)))
662 (and (>= len-str len-prefix)
663 (string=? (substring str 0 len-prefix) prefix)
664 (substring str len-prefix len-str)))))
666 (define (get-event-parameters rest)
667 (call-with-input-string
671 (read-all port (lambda (p) (read-line p #\:)))))))
674 ;;;----------------------------------------------------------------------------
676 ;; Make it impossible to quit the application with a call to "exit" or
677 ;; with a ",q" from the REPL. This is needed to conform to the iOS
678 ;; Developer Program License Agreement (I don't know which section
679 ;; but I remember it had to do with the iOS human interface design).
682 (lambda (#!optional (status 0))
683 (error "To exit, press the sleep button for 5 seconds then the home button for 10 seconds")))
686 ;;;----------------------------------------------------------------------------
688 ;; Make it impossible to access files outside of Gambit REPL. This is
689 ;; needed to conform to the iOS Developer Program License Agreement:
691 ;; 3.3.4 An Application may only read data from or write data to an
692 ;; Application's designated container area on the device, except as
693 ;; otherwise specified by Apple.
695 ;; The "~~" path will be equal to the app's bundle directory. The app's
696 ;; home directory is the directory containing the app's bundle directory.
699 (##path-normalize "~~/.."))
701 (define (contained-path-resolve path)
703 (let ((str (##path-expand path)))
704 (if (has-prefix? (##path-normalize str) app-home-dir)
705 str ;; only allow files in app directory
707 (error "App container violation")
710 (set! ##path-resolve-hook contained-path-resolve)
712 ;; Make the current-directory and the "~" path equal to the app's
713 ;; Documents directory. This directory is backed-up by iTunes.
715 (set! ##os-path-homedir
716 (c-lambda () NSString* "get_documents_dir"))
718 (current-directory "~")
721 ;;;============================================================================