git.fiddlerwoaroof.com
scripting-bridge.lisp
702ad690
 (defpackage :objc.scripting-bridge
f436a52b
   (:import-from :objc.manipulators :defun-ct :shortcut :<> :ext :sel)
702ad690
   (:use :cl :cffi)
f436a52b
   (:export
    #:app))
702ad690
 (in-package :objc.scripting-bridge)
 (named-readtables:in-readtable :objc-readtable)
 
 (serapeum:eval-always
   (define-foreign-library scripting-bridge
     (:darwin (:framework "ScriptingBridge"))))
 
 (use-foreign-library scripting-bridge)
 
382ab041
 (defun app (bundle-id)
   [#@SBApplication @(applicationWithBundleIdentifier:)
                    :pointer (objc-runtime:make-nsstring bundle-id)])
702ad690
 
382ab041
 (defun itunes-app ()
6bcb7ec9
   (app "com.apple.Music"))
382ab041
 
 (defun safari-app ()
   (app "com.apple.Safari"))
 
51cdb675
 (defclass sbsafari ()
   ((%app-ref :reader app-ref :initform (safari-app))))
 
 (defgeneric windows (object)
   (:method ((object sbsafari))
     (mapcar 'safari-window
             (objc-runtime.data-extractors:extract-from-objc
              [(app-ref object) @(windows)]?))))
 
 (fw.lu:defclass+ safari-window ()
   ((%window-ref :reader window-ref :initarg :window-ref)))
 
 (defgeneric name (thing)
   (:method ((thing safari-window))
     [(window-ref thing) @(name)]@))
 
 (defgeneric tabs (object)
   (:method ((object safari-window))
     (mapcar 'safari-tab
             (objc-runtime.data-extractors:extract-from-objc
              [(window-ref object) @(tabs)]))))
 
 (fw.lu:defclass+ safari-tab ()
   ((%tab-ref :reader tab-ref :initarg :tab-ref)))
 
 (defgeneric source (tab)
   (:method ((tab safari-tab))
     [(tab-ref tab) @(source)]@))
 (defgeneric text (tab)
   (:method ((tab safari-tab))
     [(tab-ref tab) @(text)]@))
 (defgeneric url (tab)
   (:method ((tab safari-tab))
     [(tab-ref tab) @(URL)]@))
 
382ab041
 (defun current-track-info (itunes)
702ad690
   (let* ((current-track [itunes @(currentTrack)]))
     (format t "~&Track: ~A (~v,1,0,'⋆<~>)~%Album: ~a (~v,1,0,'*<~>)~%Artist: ~a~%"
             [current-track @(name)]@
             (/ [current-track @(rating)]# 20)
             [current-track @(album)]@
             (/ [current-track @(albumRating)]# 10)
             [current-track @(artist)]@)))
382ab041
 
 (defun-ct tab-info ()
   (data-lens:juxt
    (<> (ext) (sel t @(name)))
    (<> (ext) (sel t @(URL)))))
 
 (data-lens:shortcut window-info data-lens:juxt
   (sel :int @(id))
   #'identity
   (sel :nsstring @(name)))
 
 (defun safari-tab-info (safari)
   (funcall (data-lens:over (tab-info))
            (mapcan (<> (ext) (sel t @(tabs)))
                    (objc-runtime.data-extractors:extract-from-objc
                     [safari @(windows)]))))
 
 (defun format-tab-info (info)
   (format t "~{~:@{** ~a~% ~a~%~}~2%~}" info))
 
 (defun safari-main ()
   (format-tab-info
    (safari-tab-info
     (safari-app))))
 
 (defun count-invocations (hof)
   (lambda (f &rest hof-args)
     (let ((count 0))
       (declare (dynamic-extent count))
       (flet ((nested-lambda (&rest args)
                (prog1 (apply f count args)
                  (incf count))))
         (apply hof #'nested-lambda hof-args)))))
 
f436a52b
 (defmacro comment (&body b)
   b ())
a70ac4c5
 
382ab041
 #+nil
f436a52b
 (comment
   (defun kebab-case (s)
     (loop
        for start = 0 then end
        for end = (position-if 'upper-case-p s) then (when start (position-if 'upper-case-p s :start (1+ end)))
        while start
        collect (string-downcase (subseq s start end)) into parts
        finally (return (serapeum:string-join parts #\-))))
 
   (defun get-method-symbol (selector-name package)
     (funcall (alexandria:compose (lambda (x) (intern x package))
                                  #'string-upcase
6bcb7ec9
                                  (lambda (x) (substitute #\- #\:
f436a52b
                                                          (string-trim ":-" x)))
                                  'kebab-case)
              selector-name))
 
   (defun intern-method (selector-name package)
     (let ((symbol (get-method-symbol selector-name package)))
       (format t "~&~s ~s~%" symbol selector-name)
       (if (alexandria:starts-with-subseq "set" selector-name)
           (setf (fdefinition `(setf ,symbol))
                 (lambda (new-val receiver &rest r)
                   (declare (ignore r))
                   (objc-runtime:objc-msg-send receiver (objc-runtime::ensure-selector selector-name) :pointer new-val)))
           (setf (fdefinition symbol)
                 (sel (objc-runtime::ensure-selector selector-name))))))
 
   (defun populate-package (objc-class package)
     (mapc (lambda (method-name)
             (intern-method method-name package))
           (objc-runtime:get-method-names objc-class)))
 
   (defmacro define-objc-call (selector (&rest argument-specs) result-type &optional extractor)
     (declare (ignorable extractor))
     `(defun ,(get-method-symbol (cadr selector) *package*) (receiver ,@(mapcar #'car argument-specs))
        ,(case result-type
           (:string `(objc-runtime:objc-msg-send-string receiver ,selector ,@(mapcan #'reverse argument-specs)))
           ((:long :int) `(objc-runtime:objc-msg-send-int receiver ,selector ,@(mapcan #'reverse argument-specs)))
           (t `(objc-runtime:objc-msg-send receiver ,selector ,@(mapcan #'reverse argument-specs))))))
 
   (defmacro define-objc (() &body calls)
     `(progn ,@(loop for call in calls
                  collect `(define-objc-call ,@call))))
 
   (define-objc-call @(init) () :pointer)
   (define-objc-call @(sharedApplication) () :pointer)
   )