48df527e |
(in-package :objc-runtime)
|
da75eb42 |
(serapeum:eval-always
(named-readtables:in-readtable :objc-readtable))
|
48df527e |
(serapeum:eval-always
(cffi:define-foreign-library cocoa
(:darwin (:framework "Cocoa")))
|
ae1d3bf8 |
(cffi:define-foreign-library foundation
|
48df527e |
(:darwin (:framework "Foundation")))
|
ae1d3bf8 |
(cffi:define-foreign-library appkit
(:darwin (:framework "AppKit"))))
|
48df527e |
(use-foreign-library foundation)
(use-foreign-library cocoa)
(use-foreign-library appkit)
(defctype o-class :pointer)
(defctype o-selector :pointer)
(defcfun (objc-look-up-class "objc_lookUpClass" :library foundation)
o-class
(name :string))
(defcfun (objc-allocate-class-pair "objc_allocateClassPair" :library foundation)
:pointer
(superclass :pointer)
(name :string)
(extra-bytes :int))
|
5e082198 |
(defcfun (objc-register-class-pair "objc_registerClassPair" :library foundation)
:void
(superclass :pointer))
|
48df527e |
(defcfun (objc-get-protocol "objc_getProtocol" :library foundation)
:pointer
(name :string))
(defcfun (class-add-protocol "class_addProtocol" :library foundation)
:boolean
(class :pointer)
(protocol :pointer))
|
5e082198 |
(serapeum:eval-always
(defctype sizet
:ulong
|
382ab041 |
#+32-bit-target :uint))
|
5e082198 |
(defcfun (class-add-ivar "class_addIvar" :library foundation)
:boolean
(class :pointer)
(name :string)
(size :ulong)
(alignment :uint8)
(types :string))
(defun add-pointer-ivar (class name)
(class-add-ivar class name
(foreign-type-size :pointer)
(floor (log (foreign-type-size :pointer)
2))
"@"))
|
382ab041 |
#+nil
(defun make-app-delegate-class (outlets)
(let ((app-delegate-class (objc-runtime::objc-allocate-class-pair
#@NSObject "AppDelegate" 0)))
(objc-runtime:add-pointer-ivar app-delegate-class "window")
(objc-runtime:add-pointer-ivar app-delegate-class "delegate")
(loop for outlet in outlets do
|
074f64b0 |
(objc-runtime:add-pointer-ivar app-delegate-class outlet))
|
382ab041 |
app-delegate-class))
|
3d652c2e |
#+(or)
|
382ab041 |
(defun %setup-objc-class (name base ivars)
(let ((class-pair (objc-allocate-class-pair base name 0)))
(loop for ivar in ivars
|
074f64b0 |
)))
|
382ab041 |
|
48df527e |
(defcfun (objc-class-get-name "class_getName" :library foundation)
:string
(cls o-class))
(defcfun (objc-class-get-superclass "class_getSuperclass" :library foundation)
:pointer
(cls o-class))
(defcfun (objc-get-class-list "objc_getClassList" :library foundation)
:int
(cls-buffer o-class)
(buffer-count :int))
(defcfun (sel-register-name "sel_registerName" :library foundation)
o-selector
(name :string))
|
9a92915d |
(defmacro safe-objc-msg-send (result-type thing selector &rest args)
(alexandria:once-only (thing selector)
`(if [,thing @(respondsToSelector:) :pointer ,selector]b
,(ccase result-type
|
60bee14d |
(:string `[,thing ,selector ,@args]s)
(:nsstring `[,thing ,selector ,@args]@)
(:pointer `[,thing ,selector ,@args])
(:int `[,thing ,selector ,@args]#)
(:bool `[,thing ,selector ,@args]b))
|
9a92915d |
(error "invalid selector"))))
|
d5a8a26b |
;;; This is a macro, because objc-msg-send is a macro.... which makes "apply" impossible
;;; \o/
(defmacro objc-msg-send-nsstring (thing selector &rest args)
`[[,thing ,selector ,@args] @(UTF8String)]s)
|
279ed7b0 |
|
9a92915d |
(defmacro objc-msg-send-bool (thing selector &rest args)
`(= 1 [,thing ,selector ,@args]#))
|
48df527e |
(defcfun (class-copy-method-list "class_copyMethodList" :library foundation)
:pointer
(cls o-class)
(numMethods (:pointer :int)))
(defcfun (method-get-name "method_getName")
|
a17f757e |
:string
|
48df527e |
(method :pointer))
|
10e90099 |
(defcfun (method-get-type-encoding "method_getTypeEncoding")
:string
(method :pointer))
|
48df527e |
(defcfun (sel-get-name "sel_getName")
:string
(sel o-selector))
|
4abbc169 |
(defcfun (class-get-instance-variable "class_getInstanceVariable" :library foundation)
:pointer
(cls o-class)
(name :string))
|
6b08248f |
(defcfun (class-add-method "class_addMethod" :library foundation)
:boolean
(class :pointer)
(selector :pointer)
(cb :pointer)
|
4abbc169 |
(type :string))
|
6b08248f |
|
4abbc169 |
(defcfun (object-get-class "object_getClass" :library foundation)
:pointer
(object :pointer))
(defcfun (object-get-ivar "object_getIvar" :library foundation)
:pointer
(object :pointer)
(ivar :pointer))
|
5e082198 |
(defcfun (object-get-instance-variable "object_getInstanceVariable" :library foundation)
:pointer
(object :pointer)
(name :string)
(out :pointer))
|
4abbc169 |
(defcfun (class-get-property "class_getProperty" :library foundation)
:pointer
(cls o-class)
(name :string))
|
eaa1c811 |
(defcstruct objc-property-attribute-t
(name :string)
(value :string))
(defcfun (class-add-property "class_addProperty" :library foundation)
:pointer
(cls o-class)
(name :string)
(attributes (:pointer (:struct objc-property-attribute-t)))
(attribute-count :unsigned-int))
|
4abbc169 |
(defcfun (property-copy-attribute-value "property_copyAttributeValue" :library foundation)
:string
(prop :pointer)
(name :string))
(defcfun (property-get-attributes "property_getAttributes" :library foundation)
:string
(prop :pointer))
|
279ed7b0 |
(defun get-classes ()
|
d5a8a26b |
(let ((num-classes (objc-get-class-list (null-pointer) 0))
(result (list)))
|
279ed7b0 |
(with-foreign-object (classes :pointer num-classes)
|
d5a8a26b |
(dotimes (n (objc-get-class-list classes num-classes) (nreverse result))
|
85382f54 |
(push (mem-aref classes :pointer n)
|
d5a8a26b |
result)))))
|
279ed7b0 |
|
48df527e |
(defgeneric get-methods (class)
(:method ((class string))
(get-methods (objc-look-up-class class)))
|
49baa990 |
#+ccl
(:method ((class ccl:macptr))
(with-foreign-object (num-methods :int)
(let ((methods (class-copy-method-list class num-methods)))
(let ((result (list)))
(dotimes (n (mem-aref num-methods :int) (nreverse result))
(push (mem-aref methods :pointer n)
result))))))
|
48df527e |
#+sbcl
(:method ((class sb-sys:system-area-pointer))
(with-foreign-object (num-methods :int)
(let ((methods (class-copy-method-list class num-methods)))
(let ((result (list)))
(dotimes (n (mem-aref num-methods :int) (nreverse result))
(push (mem-aref methods :pointer n)
result)))))))
|
382ab041 |
(defmethod get-methods (f)
(list))
|
116081f3 |
|
6b08248f |
(defun make-nsstring (str)
|
5fbe87c1 |
[[#@NSString @(alloc)] @(initWithCString:encoding:) :string str :uint 4])
|
6b08248f |
|
6f42890d |
(defun extract-nsstring (ns-str)
[ns-str @(UTF8String)]s)
|
6b08248f |
|
d5a8a26b |
(defun get-method-name (method)
(sel-get-name (method-get-name method)))
|
48df527e |
(defun get-method-names (thing)
(mapcar (alexandria:compose #'sel-get-name
#'method-get-name)
(get-methods thing)))
(defgeneric graph->dot (graph stream)
(:method :around (graph stream)
|
6b08248f |
(declare (ignore graph))
|
48df527e |
(format stream "~&digraph {~%~4trankdir=LR;~%")
(call-next-method)
(format stream "~&}"))
(:method ((graph hash-table) stream)
(loop for class being the hash-keys of graph using (hash-value superclass)
do (format stream "~&~4t\"~a\" -> \"~a\"~%" class superclass))))
(defparameter *selector-cache* (make-hash-table :test 'equal))
|
382ab041 |
(defparameter *class-cache* (make-hash-table :test 'equal))
|
48df527e |
|
49baa990 |
(serapeum:eval-always
(defun normalize-selector-name (sel-name)
(substitute #\: #\? sel-name)))
|
382ab041 |
(defun ensure-class (name)
(let ((objc-class (objc-look-up-class name)))
(when (and objc-class (not (null-pointer-p objc-class)))
|
eaa1c811 |
(alexandria:ensure-gethash name *class-cache* objc-class))))
|
382ab041 |
|
48df527e |
(defun ensure-selector (name)
|
49baa990 |
(alexandria:ensure-gethash name
*selector-cache*
|
48df527e |
(sel-register-name name)))
(defmacro with-objc-classes ((&rest class-defs) &body body)
`(let (,@(mapcar (fw.lu:destructuring-lambda ((lisp-name foreign-name))
`(,lisp-name (objc-look-up-class ,foreign-name)))
class-defs))
,@body))
|
116081f3 |
(cffi:defcvar (ns-app "NSApp" :library appkit) :pointer)
(defclass objc-class ()
((%objc-class-name :initarg :name :reader name)
(%class-pointer :initarg :pointer :reader class-pointer)
(%cache :initform (make-hash-table :test 'equal) :allocation :class :reader objc-class-cache)))
(defclass objc-selector ()
((%objc-selector-name :initarg :name :reader name)
(%selector-pointer :initarg :pointer :reader selector-pointer)
(%args :initarg :args :reader args)
(%result-type :initarg :result-type :reader result-type)
(%cache :initform (make-hash-table :test 'equal) :allocation :class :reader objc-selector-cache))
(:metaclass closer-mop:funcallable-standard-class))
(defun make-message-lambda-form (args rettype)
(alexandria:with-gensyms ((target :target))
(fw.lu:with (arg-syms (mapcar (serapeum:op _ (gensym "arg")) args))
`(lambda (selector)
(lambda (,target ,@arg-syms)
(cffi:foreign-funcall
"objc_msgSend"
:pointer ,target
:pointer selector
,@(mapcan #'list args arg-syms)
,rettype))))))
(defmethod initialize-instance :after ((sel objc-selector) &key &allow-other-keys)
(with-accessors ((pointer selector-pointer)
(args args)
(rettype result-type))
sel
(closer-mop:set-funcallable-instance-function
sel
(funcall (compile nil (make-message-lambda-form args rettype))
pointer))))
(defgeneric reset-class-cache (class)
(:method ((class symbol))
(reset-class-cache (find-class class)))
(:method ((class class))
(setf (slot-value (closer-mop:class-prototype class) '%cache)
(make-hash-table :test 'equal))))
(define-condition no-such-objc-class (serious-condition)
((%wanted-name :initarg :wanted-name :reader wanted-name))
(:report (lambda (c s)
(format s "No such Objective-C class: ~a" (wanted-name c)))))
(defun %ensure-wrapped-objc-class (name)
(let* ((class-cache (objc-class-cache (closer-mop:class-prototype (find-class 'objc-class))))
(cached (gethash name class-cache)))
(if cached
|
ae1d3bf8 |
cached
|
116081f3 |
(let ((objc-class (objc-look-up-class name)))
(if (null-pointer-p objc-class)
(error 'no-such-objc-class :wanted-name name)
(setf (gethash name class-cache)
(make-instance 'objc-class
:name name
:pointer objc-class)))))))
|
382ab041 |
;; TODO: should this error if there is no corresponding selector?
;; Or should we let that fall through to message sending?
|
116081f3 |
(defun %ensure-wrapped-objc-selector (name target-class result-type args)
(assert (= (count #\: name)
|
ae1d3bf8 |
(length args))
|
116081f3 |
(name args)
"Invalid number of arg types for selector ~s" name)
(let* ((class-cache (objc-selector-cache (closer-mop:class-prototype (find-class 'objc-selector))))
(cached (gethash (list name target-class)
class-cache)))
(if cached
|
ae1d3bf8 |
cached
|
116081f3 |
(let ((objc-selector (ensure-selector name)))
(setf (gethash (list name target-class) class-cache)
(make-instance 'objc-selector
:name name
:pointer objc-selector
:result-type result-type
:args args))))))
|
48df527e |
(defgeneric make-objc-instance (class &rest args)
(:method ((class string) &rest args)
(apply #'make-objc-instance (objc-look-up-class class) args))
#+ccl
(:method ((class ccl:macptr) &rest args)
(declare (ignore args))
(with-selectors (alloc init)
[[class alloc] init]))
#+sbcl
(:method ((class sb-sys:system-area-pointer) &rest args)
(declare (ignore args))
(with-selectors (alloc init)
[[class alloc] init])))
|
116081f3 |
(defun ensure-wrapped-objc-class (name)
(tagbody
retry (restart-case (return-from ensure-wrapped-objc-class
(%ensure-wrapped-objc-class name))
(use-value (new)
:interactive (lambda ()
(format t "New Objective-C class name: ")
(multiple-value-list (read)))
:report "Retry with new class name"
(setf name new)
(go retry)))))
|
48df527e |
|
116081f3 |
(defmacro with-selectors ((&rest selector-specs) &body body)
`(let (,@(mapcar (fw.lu:destructuring-lambda ((sym foreign-selector))
`(,sym (ensure-selector ,foreign-selector)))
|
122bb166 |
(mapcar (fwoar.anonymous-gf:glambda (spec)
|
116081f3 |
(:method ((spec symbol))
(list spec (normalize-selector-name
(string-downcase spec))))
(:method ((spec cons))
(list (car spec) (cadr spec))))
selector-specs)))
,@body))
|
48df527e |
|
116081f3 |
(defmacro with-typed-selectors ((&rest defs) &body body)
(let ((expanded-defs (loop for ((name objc-name) args ret-type) in defs
|
ae1d3bf8 |
collect
`((,name (&rest r) (apply ,name r))
(,name (%ensure-wrapped-objc-selector ,objc-name ',ret-type ',args))))))
|
116081f3 |
`(let (,@(mapcar #'second expanded-defs))
(flet (,@(mapcar #'first expanded-defs))
,@body))))
|
10e90099 |
(defun description (nsobject)
[nsobject @(description)]@)
(defun future-ns-date (seconds)
[[#@NSDate @(alloc)]
@(initWithTimeIntervalSinceNow:)
:double (coerce seconds 'double-float)])
(defun tick-ns-runloop (run-loop &optional (time 0.5d0))
(let ((date (future-ns-date time)))
(unwind-protect [run-loop @(runUntilDate:)
:pointer date]
[date @(release)])))
|