git.fiddlerwoaroof.com
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
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) ()
... ...
@@ -9,7 +9,8 @@
9 9
    #:define-extractor
10 10
    #:clear-extractors
11 11
    #:add-extractor
12
-   #:get-plist))
12
+   #:get-plist
13
+   #:objc-typecase))
13 14
 
14 15
 (in-package :objc-runtime.data-extractors)
15 16
 (named-readtables:in-readtable :objc-readtable)
... ...
@@ -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
+