Browse code
Add systems for the scripting-bridge and the mcclim browser
Ed Langley authored on 31/12/2018 21:42:09
Showing 9 changed files
Showing 9 changed files
- build-reading-list-reader.sh
- build.lisp
- clim-objc-browser.lisp
- objc-data-extractors.lisp
- objc-runtime.asd
- objc-runtime.lisp
- package.lisp
- readtable.lisp
- scripting-bridge.lisp
1 | 1 |
old mode 100644 |
2 | 2 |
new mode 100755 |
... | ... |
@@ -7,9 +7,13 @@ mkdir -p dist |
7 | 7 |
pushd dist |
8 | 8 |
rm -rf fwoar.lisputils |
9 | 9 |
git clone https://github.com/fiddlerwoaroof/fwoar.lisputils.git |
10 |
+rm -rf data-lens |
|
11 |
+git clone https://github.com/fiddlerwoaroof/data-lens.git |
|
10 | 12 |
popd |
11 | 13 |
|
12 | 14 |
export CL_SOURCE_REGISTRY="$PWD/dist//" |
13 | 15 |
sbcl --no-userinit \ |
16 |
+ --disable-debugger \ |
|
14 | 17 |
--load ~/quicklisp/setup.lisp \ |
15 |
- --load build.lisp |
|
18 |
+ --eval "(progn (pushnew :$1 *features*) |
|
19 |
+ (load \"build.lisp\"))" |
... | ... |
@@ -3,12 +3,31 @@ |
3 | 3 |
(load (compile-file "objc-runtime.asd"))) |
4 | 4 |
|
5 | 5 |
(eval-when (:compile-toplevel :load-toplevel :execute) |
6 |
- (ql:quickload '(:objc-runtime :yason :plump :cl-ppcre))) |
|
6 |
+ (ql:quickload '(:objc-runtime :yason :plump :cl-ppcre :data-lens))) |
|
7 | 7 |
|
8 |
-(load "reading-list-reader.lisp") |
|
8 |
+#+reading-list |
|
9 |
+(progn |
|
10 |
+ (load "reading-list-reader.lisp") |
|
9 | 11 |
|
10 |
-(eval-when (:compile-toplevel :load-toplevel :execute) |
|
11 |
- (sb-ext:save-lisp-and-die "reading-list2org" |
|
12 |
- :toplevel (intern "MAIN" |
|
13 |
- "READING-LIST-READER") |
|
14 |
- :executable t)) |
|
12 |
+ (eval-when (:compile-toplevel :load-toplevel :execute) |
|
13 |
+ (sb-ext:save-lisp-and-die "reading-list2org" |
|
14 |
+ :toplevel (intern "MAIN" |
|
15 |
+ "READING-LIST-READER") |
|
16 |
+ :executable t))) |
|
17 |
+ |
|
18 |
+#+safari2org |
|
19 |
+(progn |
|
20 |
+ (load "scripting-bridge.lisp") |
|
21 |
+ (eval-when (:compile-toplevel :load-toplevel :execute) |
|
22 |
+ (sb-ext:save-lisp-and-die "safari2org" |
|
23 |
+ :toplevel (intern "SAFARI-MAIN" |
|
24 |
+ "OBJC.SCRIPTING-BRIDGE") |
|
25 |
+ :executable t))) |
|
26 |
+#+safari2org-beta |
|
27 |
+(progn |
|
28 |
+ (load "scripting-bridge.lisp") |
|
29 |
+ (eval-when (:compile-toplevel :load-toplevel :execute) |
|
30 |
+ (sb-ext:save-lisp-and-die "safari2org-beta" |
|
31 |
+ :toplevel (intern "SAFARI-2-MAIN" |
|
32 |
+ "OBJC.SCRIPTING-BRIDGE") |
|
33 |
+ :executable t))) |
... | ... |
@@ -49,6 +49,13 @@ |
49 | 49 |
(format stream "@(~a)" |
50 | 50 |
(objc-runtime::get-method-name object))) |
51 | 51 |
|
52 |
+(define-presentation-translator string-to-objc-class (string objc-class class-browser |
|
53 |
+ :tester ((inp) (objc-runtime:ensure-class inp)) |
|
54 |
+ :tester-definitive t) |
|
55 |
+ (inp) |
|
56 |
+ (format *terminal-io* "~&translating ~s to an objc-class" inp) |
|
57 |
+ (objc-runtime:ensure-class inp)) |
|
58 |
+ |
|
52 | 59 |
(defun display-classes (frame pane) |
53 | 60 |
(updating-output (pane :unique-id (or (visible-classes frame) |
54 | 61 |
(classes frame)) |
... | ... |
@@ -75,7 +82,9 @@ |
75 | 82 |
(format pane " Method: ~a~%" (objc-runtime::get-method-name method))))))) |
76 | 83 |
|
77 | 84 |
(define-class-browser-command (com-get-methods :name t :menu t) ((the-class objc-class :gesture :select)) |
78 |
- (setf (current-class *application-frame*) the-class)) |
|
85 |
+ (if (cffi:pointerp the-class) |
|
86 |
+ (setf (current-class *application-frame*) the-class) |
|
87 |
+ (format *terminal-io* "~&The value ~s is not a pointer to a class, but a ~s" the-class (type-of the-class)))) |
|
79 | 88 |
|
80 | 89 |
|
81 | 90 |
(define-class-browser-command (com-refresh-classes :name t :menu t) () |
... | ... |
@@ -22,3 +22,22 @@ |
22 | 22 |
(:file "gcd" :depends-on ("objc-runtime")) |
23 | 23 |
(:file "objc-runtime" :depends-on ("package" "readtable" "objc-runtime-types")) |
24 | 24 |
(:file "objc-data-extractors" :depends-on ("objc-runtime" "readtable")))) |
25 |
+ |
|
26 |
+(defsystem :objc-runtime/scripting-bridge |
|
27 |
+ :description "" |
|
28 |
+ :author "Ed L <edward@elangley.org>" |
|
29 |
+ :license "MIT" |
|
30 |
+ :depends-on (:objc-runtime |
|
31 |
+ :data-lens) |
|
32 |
+ :components ((:file "scripting-bridge"))) |
|
33 |
+ |
|
34 |
+(defsystem :objc-runtime/clim-objc-browser |
|
35 |
+ :description "" |
|
36 |
+ :author "Ed L <edward@elangley.org>" |
|
37 |
+ :license "MIT" |
|
38 |
+ :pathname #+lp-systems #p"projects:objc-lisp-bridge;" #-lp-systems nil |
|
39 |
+ :depends-on (:objc-runtime |
|
40 |
+ :serapeum |
|
41 |
+ :alexandria |
|
42 |
+ :mcclim) |
|
43 |
+ :components ((:file "clim-objc-browser"))) |
... | ... |
@@ -47,7 +47,7 @@ |
47 | 47 |
(serapeum:eval-always |
48 | 48 |
(defctype sizet |
49 | 49 |
:ulong |
50 |
- #+32-bit-target :uint)) |
|
50 |
+ #+32-bit-target :uint)) |
|
51 | 51 |
|
52 | 52 |
(defcfun (class-add-ivar "class_addIvar" :library foundation) |
53 | 53 |
:boolean |
... | ... |
@@ -64,6 +64,24 @@ |
64 | 64 |
2)) |
65 | 65 |
"@")) |
66 | 66 |
|
67 |
+ |
|
68 |
+#+nil |
|
69 |
+(defun make-app-delegate-class (outlets) |
|
70 |
+ (let ((app-delegate-class (objc-runtime::objc-allocate-class-pair |
|
71 |
+ #@NSObject "AppDelegate" 0))) |
|
72 |
+ (objc-runtime:add-pointer-ivar app-delegate-class "window") |
|
73 |
+ (objc-runtime:add-pointer-ivar app-delegate-class "delegate") |
|
74 |
+ |
|
75 |
+ (loop for outlet in outlets do |
|
76 |
+ (objc-runtime:add-pointer-ivar app-delegate-class outlet)) |
|
77 |
+ |
|
78 |
+ app-delegate-class)) |
|
79 |
+ |
|
80 |
+(defun %setup-objc-class (name base ivars) |
|
81 |
+ (let ((class-pair (objc-allocate-class-pair base name 0))) |
|
82 |
+ (loop for ivar in ivars |
|
83 |
+ ))) |
|
84 |
+ |
|
67 | 85 |
(defcfun (objc-class-get-name "class_getName" :library foundation) |
68 | 86 |
:string |
69 | 87 |
(cls o-class)) |
... | ... |
@@ -190,6 +208,9 @@ |
190 | 208 |
(push (mem-aref methods :pointer n) |
191 | 209 |
result))))))) |
192 | 210 |
|
211 |
+(defmethod get-methods (f) |
|
212 |
+ (list)) |
|
213 |
+ |
|
193 | 214 |
|
194 | 215 |
(defun make-nsstring (str) |
195 | 216 |
[[#@NSString @(alloc)] @(initWithCString:encoding:) :string str :uint 1]) |
... | ... |
@@ -216,11 +237,17 @@ |
216 | 237 |
do (format stream "~&~4t\"~a\" -> \"~a\"~%" class superclass)))) |
217 | 238 |
|
218 | 239 |
(defparameter *selector-cache* (make-hash-table :test 'equal)) |
240 |
+(defparameter *class-cache* (make-hash-table :test 'equal)) |
|
219 | 241 |
|
220 | 242 |
(serapeum:eval-always |
221 | 243 |
(defun normalize-selector-name (sel-name) |
222 | 244 |
(substitute #\: #\? sel-name))) |
223 | 245 |
|
246 |
+(defun ensure-class (name) |
|
247 |
+ (let ((objc-class (objc-look-up-class name))) |
|
248 |
+ (when (and objc-class (not (null-pointer-p objc-class))) |
|
249 |
+ (alexandria.0.dev:ensure-gethash name *class-cache* objc-class)))) |
|
250 |
+ |
|
224 | 251 |
(defun ensure-selector (name) |
225 | 252 |
(alexandria:ensure-gethash name |
226 | 253 |
*selector-cache* |
... | ... |
@@ -297,7 +324,8 @@ |
297 | 324 |
:name name |
298 | 325 |
:pointer objc-class))))))) |
299 | 326 |
|
300 |
-;; TODO: should this error if there is no corresponding selector? Or should we let that fall through to message sending? |
|
327 |
+;; TODO: should this error if there is no corresponding selector? |
|
328 |
+;; Or should we let that fall through to message sending? |
|
301 | 329 |
(defun %ensure-wrapped-objc-selector (name target-class result-type args) |
302 | 330 |
(assert (= (count #\: name) |
303 | 331 |
(length args)) |
... | ... |
@@ -26,7 +26,10 @@ |
26 | 26 |
#:ensure-wrapped-objc-class |
27 | 27 |
#:add-pointer-ivar |
28 | 28 |
#:objc-msg-send-int |
29 |
- #:objc-msg-send-string)) |
|
29 |
+ #:objc-msg-send-string |
|
30 |
+ #:make-nsstring |
|
31 |
+ #:ensure-class |
|
32 |
+ #:ensure-selector)) |
|
30 | 33 |
|
31 | 34 |
(defpackage :objc-runtime.data-extractors |
32 | 35 |
(:use :cl ) |
... | ... |
@@ -47,7 +47,7 @@ |
47 | 47 |
'(#\) #\( #\[ #\]))) |
48 | 48 |
collect (read-char s t nil t)) |
49 | 49 |
'string))) |
50 |
- `(objc-look-up-class ,class-name)))) |
|
50 |
+ `(ensure-class ,class-name)))) |
|
51 | 51 |
(:macro-char #\@ :dispatch t) |
52 | 52 |
(:dispatch-macro-char #\@ #\( (read-until (serapeum:op (char= _ #\))) |
53 | 53 |
'ensure-selector)) |
... | ... |
@@ -1,4 +1,5 @@ |
1 | 1 |
(defpackage :objc.scripting-bridge |
2 |
+ (:import-from :data-lens :defun-ct :shortcut) |
|
2 | 3 |
(:use :cl :cffi) |
3 | 4 |
(:export )) |
4 | 5 |
(in-package :objc.scripting-bridge) |
... | ... |
@@ -10,10 +11,17 @@ |
10 | 11 |
|
11 | 12 |
(use-foreign-library scripting-bridge) |
12 | 13 |
|
13 |
-(defun get-itunes-app () |
|
14 |
- [#@SBApplication @(applicationWithBundleIdentifier:) :pointer @"com.apple.iTunes"]) |
|
14 |
+(defun app (bundle-id) |
|
15 |
+ [#@SBApplication @(applicationWithBundleIdentifier:) |
|
16 |
+ :pointer (objc-runtime:make-nsstring bundle-id)]) |
|
15 | 17 |
|
16 |
-(defun get-current-track-info (itunes) |
|
18 |
+(defun itunes-app () |
|
19 |
+ (app "com.apple.iTunes")) |
|
20 |
+ |
|
21 |
+(defun safari-app () |
|
22 |
+ (app "com.apple.Safari")) |
|
23 |
+ |
|
24 |
+(defun current-track-info (itunes) |
|
17 | 25 |
(let* ((current-track [itunes @(currentTrack)])) |
18 | 26 |
(format t "~&Track: ~A (~v,1,0,'⋆<~>)~%Album: ~a (~v,1,0,'*<~>)~%Artist: ~a~%" |
19 | 27 |
[current-track @(name)]@ |
... | ... |
@@ -21,3 +29,168 @@ |
21 | 29 |
[current-track @(album)]@ |
22 | 30 |
(/ [current-track @(albumRating)]# 10) |
23 | 31 |
[current-track @(artist)]@))) |
32 |
+ |
|
33 |
+(defvar *it*) |
|
34 |
+(serapeum:eval-always |
|
35 |
+ (defgeneric sel (type sel) |
|
36 |
+ (:method :around (type sel) |
|
37 |
+ (lambda (*it*) |
|
38 |
+ (call-next-method))) |
|
39 |
+ |
|
40 |
+ (:method (type sel) |
|
41 |
+ [*it* sel]) |
|
42 |
+ |
|
43 |
+ (:method ((type (eql :int)) sel) |
|
44 |
+ [*it* sel]#) |
|
45 |
+ |
|
46 |
+ (:method ((type (eql :string)) sel) |
|
47 |
+ [*it* sel]s) |
|
48 |
+ |
|
49 |
+ (:method ((type (eql :nsstring)) sel) |
|
50 |
+ [*it* sel]@))) |
|
51 |
+ |
|
52 |
+(defun-ct ext () |
|
53 |
+ (lambda (it) |
|
54 |
+ (objc-runtime.data-extractors:extract-from-objc it))) |
|
55 |
+ |
|
56 |
+(defun-ct <> (&rest funs) |
|
57 |
+ (apply #'alexandria:compose funs)) |
|
58 |
+ |
|
59 |
+(defun-ct tab-info () |
|
60 |
+ (data-lens:juxt |
|
61 |
+ (<> (ext) (sel t @(name))) |
|
62 |
+ (<> (ext) (sel t @(URL))))) |
|
63 |
+ |
|
64 |
+(data-lens:shortcut window-info data-lens:juxt |
|
65 |
+ (sel :int @(id)) |
|
66 |
+ #'identity |
|
67 |
+ (sel :nsstring @(name))) |
|
68 |
+ |
|
69 |
+(defun safari-tab-info (safari) |
|
70 |
+ (funcall (data-lens:over (tab-info)) |
|
71 |
+ (mapcan (<> (ext) (sel t @(tabs))) |
|
72 |
+ (objc-runtime.data-extractors:extract-from-objc |
|
73 |
+ [safari @(windows)])))) |
|
74 |
+ |
|
75 |
+(defun format-tab-info (info) |
|
76 |
+ (format t "~{~:@{** ~a~% ~a~%~}~2%~}" info)) |
|
77 |
+ |
|
78 |
+ |
|
79 |
+(defun safari-main () |
|
80 |
+ (format-tab-info |
|
81 |
+ (safari-tab-info |
|
82 |
+ (safari-app)))) |
|
83 |
+ |
|
84 |
+ |
|
85 |
+(defun count-invocations (hof) |
|
86 |
+ (lambda (f &rest hof-args) |
|
87 |
+ (let ((count 0)) |
|
88 |
+ (declare (dynamic-extent count)) |
|
89 |
+ (flet ((nested-lambda (&rest args) |
|
90 |
+ (prog1 (apply f count args) |
|
91 |
+ (incf count)))) |
|
92 |
+ (apply hof #'nested-lambda hof-args))))) |
|
93 |
+ |
|
94 |
+(defun-ct <count (f) |
|
95 |
+ (lambda (c &rest v) |
|
96 |
+ (list* c (apply f v)))) |
|
97 |
+ |
|
98 |
+(defun-ct add-index (hof f) |
|
99 |
+ (lambda (&rest hof-args) |
|
100 |
+ (let ((count 0)) |
|
101 |
+ (declare (dynamic-extent count)) |
|
102 |
+ (flet ((nested-lambda (&rest args) |
|
103 |
+ (prog1 (apply f count args) |
|
104 |
+ (incf count)))) |
|
105 |
+ (apply hof #'nested-lambda hof-args))))) |
|
106 |
+ |
|
107 |
+(defun get-window-tabs (window) |
|
108 |
+ (funcall (<> 'objc-runtime.data-extractors:extract-from-objc |
|
109 |
+ (sel t @(tabs))) |
|
110 |
+ window)) |
|
111 |
+ |
|
112 |
+(data-lens:shortcut get-safari-info <> |
|
113 |
+ (add-index 'mapcar |
|
114 |
+ (<> (fw.lu:destructuring-lambda ((c win-id win-name . tabs)) |
|
115 |
+ (format t "~&Window: ~d ~d ~a~%~{~a~%~}~%" win-id c win-name tabs)) |
|
116 |
+ (<count |
|
117 |
+ (<> (data-lens:transform-elt 1 (<> (add-index 'mapcar |
|
118 |
+ (<> (fw.lu:destructuring-lambda ((c ti u)) |
|
119 |
+ (format nil "~a ~a~%~4t~a" c ti u)) |
|
120 |
+ (<count (tab-info)))) |
|
121 |
+ 'get-window-tabs)) |
|
122 |
+ 'window-info)))) |
|
123 |
+ (ext) |
|
124 |
+ (sel t @(windows))) |
|
125 |
+ |
|
126 |
+(defun safari-2-main () |
|
127 |
+ (get-safari-info (safari-app))) |
|
128 |
+ |
|
129 |
+ |
|
130 |
+ |
|
131 |
+(defun find-tab (name windows) |
|
132 |
+ (remove-if-not (serapeum:op (serapeum:string-contains-p name _)) |
|
133 |
+ windows |
|
134 |
+ :key (<> 'string-downcase 'car))) |
|
135 |
+ |
|
136 |
+(defun current-tab (window) |
|
137 |
+ [window @(currentTab)]) |
|
138 |
+(defun (setf current-tab) (new-value window) |
|
139 |
+ [window @(setCurrentTab:) :pointer new-value] |
|
140 |
+ new-value) |
|
141 |
+ |
|
142 |
+#+nil |
|
143 |
+(defun kebab-case (s) |
|
144 |
+ (loop |
|
145 |
+ for start = 0 then end |
|
146 |
+ for end = (position-if 'upper-case-p s) then (when start (position-if 'upper-case-p s :start (1+ end))) |
|
147 |
+ while start |
|
148 |
+ collect (string-downcase (subseq s start end)) into parts |
|
149 |
+ finally (return (serapeum:string-join parts #\-)))) |
|
150 |
+ |
|
151 |
+#+nil |
|
152 |
+(defun get-method-symbol (selector-name package) |
|
153 |
+ (funcall (alexandria:compose (lambda (x) (intern x package)) |
|
154 |
+ #'string-upcase |
|
155 |
+ (lambda (x) (substitute #\- #\: |
|
156 |
+ (string-trim ":-" x))) |
|
157 |
+ 'kebab-case) |
|
158 |
+ selector-name)) |
|
159 |
+ |
|
160 |
+#+nil |
|
161 |
+(defun intern-method (selector-name package) |
|
162 |
+ (let ((symbol (get-method-symbol selector-name package))) |
|
163 |
+ (format t "~&~s ~s~%" symbol selector-name) |
|
164 |
+ (if (alexandria:starts-with-subseq "set" selector-name) |
|
165 |
+ (setf (fdefinition `(setf ,symbol)) |
|
166 |
+ (lambda (new-val receiver &rest r) |
|
167 |
+ (declare (ignore r)) |
|
168 |
+ (objc-runtime:objc-msg-send receiver (objc-runtime::ensure-selector selector-name) :pointer new-val))) |
|
169 |
+ (setf (fdefinition symbol) |
|
170 |
+ (sel (objc-runtime::ensure-selector selector-name)))))) |
|
171 |
+ |
|
172 |
+#+nil |
|
173 |
+(defun populate-package (objc-class package) |
|
174 |
+ (mapc (lambda (method-name) |
|
175 |
+ (intern-method method-name package)) |
|
176 |
+ (objc-runtime:get-method-names objc-class))) |
|
177 |
+ |
|
178 |
+#+nil |
|
179 |
+(defmacro define-objc-call (selector (&rest argument-specs) result-type &optional extractor) |
|
180 |
+ (declare (ignorable extractor)) |
|
181 |
+ `(defun ,(get-method-symbol (cadr selector) *package*) (receiver ,@(mapcar #'car argument-specs)) |
|
182 |
+ ,(case result-type |
|
183 |
+ (:string `(objc-runtime:objc-msg-send-string receiver ,selector ,@(mapcan #'reverse argument-specs))) |
|
184 |
+ ((:long :int) `(objc-runtime:objc-msg-send-int receiver ,selector ,@(mapcan #'reverse argument-specs))) |
|
185 |
+ (t `(objc-runtime:objc-msg-send receiver ,selector ,@(mapcan #'reverse argument-specs)))))) |
|
186 |
+ |
|
187 |
+#+nil |
|
188 |
+(defmacro define-objc (() &body calls) |
|
189 |
+ `(progn ,@(loop for call in calls |
|
190 |
+ collect `(define-objc-call ,@call)))) |
|
191 |
+ |
|
192 |
+#+nil |
|
193 |
+(define-objc-call @(init) () :pointer) |
|
194 |
+#+nil |
|
195 |
+(define-objc-call @(sharedApplication) () :pointer) |
|
196 |
+ |