974 lines
33 KiB
Racket
974 lines
33 KiB
Racket
#lang racket/base
|
|
|
|
(require "racket-webview-qt.rkt"
|
|
"racket-webview-version.rkt"
|
|
"utils.rkt"
|
|
"mimetypes.rkt"
|
|
"rgba.rkt"
|
|
finalizer
|
|
racket/async-channel
|
|
web-server/http
|
|
web-server/servlet-dispatch
|
|
web-server/web-server
|
|
web-server/servlet-env
|
|
(prefix-in c: net/cookies)
|
|
net/url
|
|
racket/runtime-path
|
|
racket/file
|
|
racket/string
|
|
racket/path
|
|
racket/port
|
|
racket/contract
|
|
xml
|
|
xml/xexpr
|
|
json
|
|
(prefix-in g: gregor)
|
|
(prefix-in g: gregor/time)
|
|
gregor-utils
|
|
lru-cache
|
|
racket-self-signed-cert
|
|
)
|
|
|
|
(provide webview-new-context
|
|
webview-create
|
|
webview-devtools
|
|
webview-close
|
|
|
|
webview-run-js
|
|
webview-call-js
|
|
webview-call-js-result?
|
|
|
|
webview-move
|
|
webview-resize
|
|
webview-show
|
|
webview-hide
|
|
webview-show-normal
|
|
webview-maximize
|
|
webview-minimize
|
|
webview-window-state
|
|
webview-set-title!
|
|
|
|
webview-messagebox
|
|
|
|
webview-choose-dir
|
|
webview-file-open
|
|
webview-file-save
|
|
wv-permitted-exts
|
|
|
|
make-wv-permitted-exts
|
|
wv-permitted-exts-name
|
|
wv-permitted-exts-exts
|
|
set-wv-permitted-exts-exts!
|
|
set-wv-permitted-exts-name!
|
|
wv-permitted-exts?
|
|
wv-permitted-exts-exts?
|
|
wv-list-of-permitted-exts?
|
|
webview-filter->exts
|
|
|
|
webview-bind!
|
|
webview-unbind!
|
|
|
|
webview-set-url!
|
|
webview-navigate!
|
|
webview-set-html!
|
|
webview-base-url
|
|
|
|
webview-set-innerHTML!
|
|
|
|
webview-set-value!
|
|
webview-value
|
|
webview-value/boolean
|
|
webview-value/symbol
|
|
webview-value/number
|
|
webview-value/date
|
|
webview-value/time
|
|
webview-value/datetime
|
|
webview-value/color
|
|
|
|
webview-add-class!
|
|
webview-remove-class!
|
|
webview-set-style!
|
|
webview-unset-style!
|
|
webview-get-style
|
|
|
|
webview-set-attr!
|
|
webview-attr
|
|
webview-attr/boolean
|
|
webview-attr/symbol
|
|
webview-attr/number
|
|
webview-attr/date
|
|
webview-attr/time
|
|
webview-attr/datetime
|
|
webview-attr/color
|
|
|
|
;webview-del-attr!
|
|
|
|
webview-standard-file-getter
|
|
webview-default-boilerplate-js
|
|
|
|
webview-version
|
|
|
|
wv-context-base-url
|
|
wv-win-window-nr
|
|
|
|
test
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Web server
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define-runtime-path js-path "../js")
|
|
|
|
(define (webview-default-boilerplate-js . custom-js)
|
|
(let ((file (build-path js-path "boilerplate.js")))
|
|
(let ((bjs (file->string file)))
|
|
(let ((js (string-append bjs
|
|
(if (null? custom-js)
|
|
""
|
|
((car custom-js))))))
|
|
js))))
|
|
|
|
(define-struct wv-context
|
|
([context #:mutable]
|
|
[port #:mutable]
|
|
[file-getter #:mutable]
|
|
[webserver-thread #:mutable]
|
|
[base-url #:mutable]
|
|
[request-count #:mutable]
|
|
[sec-token-cache #:mutable]
|
|
[cert-ou-token #:mutable]
|
|
)
|
|
#:transparent
|
|
)
|
|
|
|
(define-struct wv-win
|
|
([handle #:mutable]
|
|
[context #:mutable]
|
|
[window-nr #:mutable]
|
|
)
|
|
#:transparent)
|
|
|
|
(define (process-html context path out)
|
|
(let ((html (file->string path)))
|
|
(display html out)))
|
|
; (boilerplate-js ((wv-context-boilerplate-js wv-win-handle))))
|
|
; (set! html (string-replace html "<head>"
|
|
; (string-append "<head>" "\n"
|
|
; "<script>" "\n"
|
|
; boilerplate-js "\n"
|
|
; "</script>" "\n")))
|
|
; (display html out)))
|
|
|
|
(define (process-file context ext path out)
|
|
(let ((content (file->bytes path)))
|
|
(display content out)))
|
|
|
|
(define (make-security-token)
|
|
(letrec ((f (λ (n)
|
|
(if (= 0 n)
|
|
""
|
|
(string-append
|
|
(string (integer->char (+ 97 (random 26))))
|
|
(f (- n 1)))))))
|
|
(string->symbol (f 20))))
|
|
|
|
(define (get-security-token req)
|
|
(let* ((headers (request-headers/raw req))
|
|
(cookie-header (findf (λ (h)
|
|
(eq? (string->symbol
|
|
(format "~a" (header-field h)))
|
|
'Cookie))
|
|
headers)))
|
|
(if (eq? cookie-header #f)
|
|
#f
|
|
(let ((cookies (c:cookie-header->alist (header-value cookie-header))))
|
|
;(displayln (format "Cookies: ~a" cookies))
|
|
(let ((sec-token (findf (λ (c)
|
|
(eq? (string->symbol
|
|
(format "~a" (car c)))
|
|
'rkt-webview-token))
|
|
cookies)))
|
|
(if (eq? sec-token #f)
|
|
#f
|
|
(string->symbol (format "~a" (cdr sec-token)))))))))
|
|
|
|
(define (make-sec-header sec-cache)
|
|
(let ((tok (make-security-token)))
|
|
(lru-add! sec-cache tok)
|
|
(make-header #"Set-Cookie"
|
|
(string->bytes/utf-8
|
|
(format "rkt-webview-token=~a" tok)))
|
|
)
|
|
)
|
|
|
|
(define (web-serve context req)
|
|
(let* ((path (url->string (request-uri req)))
|
|
(file-getter (wv-context-file-getter context))
|
|
(token (get-security-token req))
|
|
(sec-cache (wv-context-sec-token-cache context))
|
|
(cache-empty? (lru-empty? sec-cache))
|
|
(token-in-cache? (not (or (eq? token #f)
|
|
(eq? (lru-has? sec-cache token) #f))))
|
|
)
|
|
(if (and (eq? token-in-cache? #f) (not cache-empty?))
|
|
(response/output
|
|
#:code 401
|
|
(λ (out)
|
|
#t))
|
|
(let* ((file-to-serve (build-path (file-getter path)))
|
|
(ext-bytes (path-get-extension file-to-serve))
|
|
(ext (if (eq? ext-bytes #f) #f
|
|
(string->symbol
|
|
(string-downcase
|
|
(substring (bytes->string/utf-8 ext-bytes) 1)))))
|
|
)
|
|
(if (file-exists? file-to-serve)
|
|
(response/output
|
|
#:mime-type (string->bytes/utf-8 (mimetype-for-ext ext))
|
|
#:headers (list (make-sec-header sec-cache))
|
|
(λ (out)
|
|
(if (or (eq? ext 'html) (eq? ext 'htm))
|
|
(process-html context file-to-serve out)
|
|
(process-file context ext file-to-serve out))
|
|
))
|
|
(response/output
|
|
#:code 404
|
|
#:headers (list (make-sec-header sec-cache))
|
|
(λ (out)
|
|
(displayln (format "~a not found" path) out)))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
(define (start-web-server h channel cert)
|
|
(if (eq? cert #f)
|
|
(thread (λ ()
|
|
(serve
|
|
#:dispatch (dispatch/servlet
|
|
(λ (req) (web-serve h req)))
|
|
#:listen-ip "127.0.0.1"
|
|
#:port 0
|
|
#:confirmation-channel channel
|
|
)
|
|
)
|
|
)
|
|
(let* ((cert-path (let* ((context-nr (wv-context-context h))
|
|
(p (build-path (find-system-path 'pref-dir)
|
|
"racket-webview"
|
|
(format "context-~a" context-nr)))
|
|
)
|
|
(make-directory* p)
|
|
p))
|
|
(context-nr (wv-context-context h))
|
|
(f1 (build-path cert-path (format "self-signed-~a.cert" context-nr)))
|
|
(f2 (build-path cert-path (format "self-signed-~a.key" context-nr)))
|
|
(fh1 (open-output-file f1 #:exists 'replace #:permissions #o600))
|
|
(fh2 (open-output-file f2 #:exists 'replace #:permissions #o600)))
|
|
(display (certificate cert) fh1)
|
|
(display (private-key cert) fh2)
|
|
(close-output-port fh1)
|
|
(close-output-port fh2)
|
|
(thread (λ ()
|
|
(serve
|
|
#:dispatch (dispatch/servlet
|
|
(λ (req) (web-serve h req)))
|
|
#:dispatch-server-connect@ (make-ssl-connect@ f1 f2)
|
|
#:listen-ip "127.0.0.1"
|
|
#:port 0
|
|
#:confirmation-channel channel
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
(define (remove-cert-files h)
|
|
(let* ((context-nr (wv-context-context h))
|
|
(cert-path (build-path (find-system-path 'pref-dir)
|
|
"racket-webview"
|
|
(format "context-~a" context-nr)))
|
|
(f1 (build-path cert-path (format "self-signed-~a.cert" context-nr)))
|
|
(f2 (build-path cert-path (format "self-signed-~a.key" context-nr)))
|
|
)
|
|
(when (file-exists? f1)
|
|
(delete-file f1))
|
|
(when (file-exists? f2)
|
|
(delete-file f2))
|
|
)
|
|
)
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Utilities
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define (util-parse-event evt)
|
|
(let ((wv-d0 (hash-copy (with-input-from-string evt read-json))))
|
|
(hash-set! wv-d0 'event
|
|
(string->symbol
|
|
(format "~a" (hash-ref wv-d0 'event 'nil))))
|
|
wv-d0))
|
|
|
|
(define (webview-call-js-result? x)
|
|
(if (and (list? x) (= (length x) 2))
|
|
(and (symbol? (car x))
|
|
(string? (cadr x)))
|
|
#f))
|
|
|
|
|
|
(define-syntax with-id
|
|
(syntax-rules ()
|
|
((_ id el
|
|
(code ...))
|
|
(string-append (format "{ let ~a = document.getElementById('~a');\n" 'el id)
|
|
(format code ...)
|
|
"\n}")
|
|
)
|
|
((_ id el
|
|
(code ...) -> retval)
|
|
(string-append (format "{ let ~a = document.getElementById('~a');\n" 'el id)
|
|
(format code ...)
|
|
(format "return ~a;\n" retval)
|
|
"}"))
|
|
((_ id el -> retval)
|
|
(string-append (format "{ let ~a = document.getElementById('~a');\n" 'el id)
|
|
(format "return ~a;\n" retval)
|
|
"}"))
|
|
)
|
|
)
|
|
|
|
(define-syntax with-selector
|
|
(syntax-rules ()
|
|
((_ selector func)
|
|
(format "return window.rkt_with_selector('~a', ~a)"
|
|
(esc-quote selector)
|
|
func))))
|
|
|
|
(define (make-exts-filter list-of-pe)
|
|
(define (mef pe)
|
|
(format "~a ~a"
|
|
(wv-permitted-exts-name pe)
|
|
(map (λ (e) (format "*.~a" e)) (wv-permitted-exts-exts pe))))
|
|
(string-join (map mef (if (list? list-of-pe) list-of-pe (list list-of-pe))) ";;"))
|
|
|
|
(define (webview-filter->exts str)
|
|
(let ((re #px"^([^ ]+)\\s+[(]([^)]+)[)]"))
|
|
(let ((m (regexp-match re str)))
|
|
(cond ((eq? m #f)
|
|
#f)
|
|
(else (let* ((re1 #px"\\s+")
|
|
(re2 #px"[*][.](.*)")
|
|
(exts (regexp-split re1 (caddr m))))
|
|
(make-wv-permitted-exts (cadr m)
|
|
(map (λ (e)
|
|
(let ((m (regexp-match re2 e)))
|
|
(string->symbol (cadr m))))
|
|
exts))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Data structures
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define-struct wv-permitted-exts
|
|
((name #:mutable) (exts #:mutable))
|
|
#:transparent
|
|
)
|
|
|
|
(define (wv-permitted-exts-exts? e)
|
|
(list-of? symbol? e))
|
|
|
|
(define (wv-permitted-exts-name? e)
|
|
(string? e))
|
|
|
|
(define wv-pm-exts? wv-permitted-exts?)
|
|
|
|
(set! wv-permitted-exts? (λ (e)
|
|
(and (wv-pm-exts? e)
|
|
(wv-permitted-exts-exts? (wv-permitted-exts-exts e))
|
|
(wv-permitted-exts-name? (wv-permitted-exts-name e))
|
|
)))
|
|
|
|
(define (wv-list-of-permitted-exts? l)
|
|
(list-of? (λ (e) (and
|
|
(wv-permitted-exts? e)
|
|
(wv-permitted-exts-exts? (wv-permitted-exts-exts e))))
|
|
l))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Webview functions
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define/contract (webview-new-context file-getter
|
|
#:boilerplate-js [bj (webview-default-boilerplate-js)])
|
|
(->* (procedure?) (#:boilerplate-js string?) wv-context?)
|
|
(let* ((h (make-wv-context 0 0 file-getter #f #f 0
|
|
(make-lru 250 #:cmp eq?)
|
|
(symbol->string (make-security-token))
|
|
))
|
|
(cert (generate-self-signed-cert 2048 365 '("127.0.0.1" "localhost")
|
|
"NL" "Dijkema"
|
|
#:ou (wv-context-cert-ou-token h)))
|
|
(server-cert (certificate cert))
|
|
(channel (make-async-channel))
|
|
(server (let ((s (start-web-server h channel cert)))
|
|
(sleep 1) ;;; ToDO Check if server is up
|
|
s))
|
|
(port-nr (let ((pn (async-channel-get channel)))
|
|
(set-wv-context-port! h pn)
|
|
pn))
|
|
(base-req (format "https://127.0.0.1:~a" (wv-context-port h)))
|
|
)
|
|
(set-wv-context-context! h (rkt-webview-new-context bj server-cert))
|
|
(set-wv-context-webserver-thread! h server)
|
|
(set-wv-context-base-url! h base-req)
|
|
(register-finalizer h (λ (h) (remove-cert-files h)))
|
|
h))
|
|
|
|
;([context #:mutable]
|
|
; [port #:mutable]
|
|
; [file-getter #:mutable]
|
|
; [webserver-thread #:mutable]
|
|
; [base-url #:mutable]
|
|
; [request-count #:mutable]
|
|
; [sec-token-cache #:mutable]
|
|
; [cert-ou-token #:mutable]
|
|
|
|
|
|
;(define-struct wv
|
|
; ([handle #:mutable]
|
|
; [context #:mutable]
|
|
; [window-nr #:mutable]
|
|
; )
|
|
; #:transparent)
|
|
|
|
(define/contract (webview-create context url-path event-callback
|
|
#:parent [p #f])
|
|
(->* (wv-context? string? procedure?)
|
|
(#:parent (or/c wv-win? #f))
|
|
wv-win?)
|
|
(let* ((h (make-wv-win #f context -1))
|
|
(event-processor (λ (wv evt)
|
|
(event-callback h (util-parse-event evt))))
|
|
(close-callback (λ () #t))
|
|
(ph (if (wv-win? p) (wv-win-handle p) #f))
|
|
(context-nr (wv-context-context context))
|
|
(wv (let ((internal-handle (rkt-webview-create context-nr ph event-processor close-callback)))
|
|
(set-wv-win-handle! h internal-handle)
|
|
(set-wv-win-window-nr! h (rkt-wv-win internal-handle))
|
|
internal-handle))
|
|
)
|
|
(rkt-webview-set-ou-token (wv-win-handle h) (wv-context-cert-ou-token context))
|
|
(webview-set-url! h url-path)
|
|
h)
|
|
)
|
|
|
|
(define/contract (webview-devtools wv)
|
|
(-> wv-win? symbol?)
|
|
(rkt-webview-open-devtools (wv-win-handle wv)))
|
|
|
|
(define/contract (webview-move wv x y)
|
|
(-> wv-win? number? number? symbol?)
|
|
(rkt-webview-move (wv-win-handle wv) x y))
|
|
|
|
(define/contract (webview-resize wv w h)
|
|
(-> wv-win? number? number? symbol?)
|
|
(rkt-webview-resize (wv-win-handle wv) w h))
|
|
|
|
(define/contract (webview-set-html! wv html)
|
|
(-> wv-win? (or/c string? xexpr?) symbol?)
|
|
(if (string? html)
|
|
(rkt-webview-set-html! (wv-win-handle wv) html)
|
|
(rkt-webview-set-html! (wv-win-handle wv) (xexpr->string html))
|
|
)
|
|
)
|
|
|
|
(define/contract (webview-set-url! wv url)
|
|
(-> wv-win? (or/c string? url?) symbol?)
|
|
(let* ((u (if (string? url) (string->url url) url))
|
|
(c (wv-win-context wv))
|
|
(bu (string->url (wv-context-base-url c))))
|
|
(when (or (eq? (url-scheme u) #f) (eq? (url-host u) #f))
|
|
(set-url-host! u (url-host bu))
|
|
(set-url-port! u (url-port bu))
|
|
(set-url-scheme! u (url-scheme bu))
|
|
(set-url-path-absolute?! u #t)
|
|
)
|
|
(when (eq? (url-scheme u) #f)
|
|
(set-url-scheme! (url-scheme bu)))
|
|
(rkt-webview-set-url! (wv-win-handle wv) (url->string u))
|
|
)
|
|
)
|
|
|
|
(define/contract (webview-navigate! wv place)
|
|
(-> wv-win? string? symbol?)
|
|
(webview-run-js wv
|
|
(format "window.location = '~a';"
|
|
(esc-quote place))))
|
|
|
|
(define/contract (webview-base-url wv)
|
|
(-> wv-win? url?)
|
|
(string->url (wv-context-base-url (wv-win-context wv))))
|
|
|
|
|
|
(define-syntax def-win-func
|
|
(syntax-rules ()
|
|
((_ name name-to-wrap)
|
|
(define/contract (name wv)
|
|
(-> wv-win? symbol?)
|
|
(name-to-wrap (wv-win-handle wv))))))
|
|
|
|
(def-win-func webview-show rkt-webview-show)
|
|
(def-win-func webview-hide rkt-webview-hide)
|
|
(def-win-func webview-maximize rkt-webview-maximize)
|
|
(def-win-func webview-minimize rkt-webview-minimize)
|
|
(def-win-func webview-show-normal rkt-webview-show-normal)
|
|
(def-win-func webview-present rkt-webview-present)
|
|
(def-win-func webview-window-state rkt-webview-window-state)
|
|
|
|
(define/contract (webview-messagebox wv type title message #:sub [submessage ""])
|
|
(->* (wv-win? symbol? string? string?) (#:sub string?) symbol?)
|
|
(rkt-webview-messagebox (wv-win-handle wv) title message submessage type))
|
|
|
|
(define/contract (webview-choose-dir wv title base-dir)
|
|
(-> wv-win? string? (or/c path? string?) symbol?)
|
|
(let ((bd (if (path? base-dir) (path->string base-dir) base-dir)))
|
|
(let ((res (rkt-webview-choose-dir (wv-win-handle wv) title bd)))
|
|
res)
|
|
)
|
|
)
|
|
|
|
;(if (eq? res #f)
|
|
; #f
|
|
; (cond ((eq? (car res) 'oke)
|
|
; (let ((r (make-hash (hash->list (fromJson (cadr res))))))
|
|
; (hash-set! r 'state (string->symbol (hash-ref r 'state)))
|
|
; r))
|
|
; (else #f))
|
|
; )
|
|
; )
|
|
; )
|
|
; )
|
|
|
|
(define (file-open-save wv title base-dir permitted-exts open-save-f)
|
|
(let* ((bd (if (path? base-dir) (path->string base-dir) base-dir))
|
|
(ext-filter (make-exts-filter permitted-exts)))
|
|
(displayln ext-filter);
|
|
(let ((res (open-save-f (wv-win-handle wv) title bd ext-filter)))
|
|
res)))
|
|
; (if (eq? res #f)
|
|
; #f
|
|
; (cond ((eq? (car res) 'oke)
|
|
; (let* ((h (make-hash (hash->list (fromJson (cadr res))))))
|
|
; (hash-set! h 'state (string->symbol (hash-ref h 'state)))
|
|
; (hash-set! h 'used-filter (filter->exts (hash-ref h 'used-filter)))
|
|
; h))
|
|
; (else #f))
|
|
; )
|
|
; )
|
|
; )
|
|
; )
|
|
|
|
|
|
(define/contract (webview-file-open wv title base-dir permitted-exts)
|
|
(-> wv-win? string? (or/c path? string?) (or/c wv-permitted-exts? wv-list-of-permitted-exts?)
|
|
symbol?)
|
|
(file-open-save wv title base-dir permitted-exts rkt-webview-file-open))
|
|
|
|
(define/contract (webview-file-save wv title base-dir permitted-exts)
|
|
(-> wv-win? string? (or/c path? string?) (or/c wv-permitted-exts? wv-list-of-permitted-exts?)
|
|
symbol?)
|
|
(file-open-save wv title base-dir permitted-exts rkt-webview-file-save))
|
|
|
|
|
|
|
|
(define/contract (webview-set-title! wv title)
|
|
(-> wv-win? string? symbol?)
|
|
(rkt-webview-set-title! (wv-win-handle wv) title))
|
|
|
|
(define/contract (webview-close wv)
|
|
(-> wv-win? symbol?)
|
|
(begin
|
|
(rkt-webview-close (wv-win-handle wv))
|
|
'oke))
|
|
|
|
(define/contract (webview-bind! wv selector event)
|
|
(-> wv-win? (or/c symbol? string?) (or/c symbol? list-of-symbol?) list?)
|
|
(let* ((sel (if (symbol? selector)
|
|
(format "#~a" selector)
|
|
selector))
|
|
(event* (if (symbol? event) (list event) event))
|
|
(evt (format "[ ~a ]" (string-join
|
|
(map (λ (e) (format "'~a'" e)) event*) ", ")))
|
|
)
|
|
(let ((r (webview-call-js wv
|
|
(format "return window.rkt_bind_evt_ids(~a, '~a', ~a)"
|
|
(wv-win-window-nr wv) sel evt))))
|
|
(map (λ (el)
|
|
(list (string->symbol (car el)) (cadr el) (caddr el)))
|
|
r))))
|
|
|
|
(define/contract (webview-unbind! wv selector event)
|
|
(-> wv-win? (or/c symbol? string?) (or/c symbol? list-of-symbol?) list?)
|
|
(let* ((sel (if (symbol? selector)
|
|
(format "#~a" selector)
|
|
selector))
|
|
(event* (if (symbol? event) (list event) event))
|
|
(evt (format "[ ~a ]" (string-join
|
|
(map (λ (e) (format "'~a'" e)) event*) ", ")))
|
|
)
|
|
(let ((r (webview-call-js wv
|
|
(format "return window.rkt_unbind_evt_ids(~a, '~a', ~a)"
|
|
(wv-win-window-nr wv) sel evt))))
|
|
(map (λ (el)
|
|
(list (string->symbol (car el)) (cadr el) (caddr el)))
|
|
r))))
|
|
|
|
(define/contract (webview-run-js wv js)
|
|
(-> wv-win? string? symbol?)
|
|
(rkt-webview-run-js (wv-win-handle wv) js))
|
|
|
|
(define (webview-standard-file-getter base-path
|
|
#:not-exist [on-not-exist (λ (file base-path path) path)]
|
|
)
|
|
(λ (file)
|
|
(let ((f (if (string=? file "/") "index.html" file)))
|
|
(when (string-prefix? f "/")
|
|
(set! f (substring f 1)))
|
|
(let ((p (build-path base-path f)))
|
|
(if (not (file-exists? p))
|
|
(on-not-exist file base-path p)
|
|
p)))))
|
|
|
|
|
|
(define (webview-call-js wv js)
|
|
(-> wv-win? string? (or/c string? list? boolean? hash?))
|
|
(let ((result (rkt-webview-call-js (wv-win-handle wv) js)))
|
|
(if (webview-call-js-result? result)
|
|
(if (eq? (car result) 'oke)
|
|
(hash-ref (fromJson (cadr result)) 'result #f)
|
|
(error
|
|
(format "Error calling javascript. Message: ~a"
|
|
(hash-ref (fromJson (cadr result)) 'exn result)))
|
|
)
|
|
(error
|
|
(format "Wrong result from webview-call-js: ~a" result))
|
|
)
|
|
)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Functions on top of the basics
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define/contract (webview-set-innerHTML! wv id html)
|
|
(-> wv-win? symbol? (or/c string? xexpr?) symbol?)
|
|
(if (string? html)
|
|
(let ((r (webview-call-js wv
|
|
(with-id id el
|
|
("el.innerHTML = '~a'; return true;" (esc-quote html))))))
|
|
(if r 'oke 'failed))
|
|
(webview-set-innerHTML! wv id (xexpr->string html))
|
|
)
|
|
)
|
|
|
|
(define/contract (webview-set-value! wv id val)
|
|
(-> wv-win? symbol? (or/c symbol? string? number? boolean? g:date? g:time? g:datetime? rgba?) symbol?)
|
|
(webview-run-js wv
|
|
(with-id id el
|
|
((string-append
|
|
"if (el.type == 'checkbox' || el.type == 'radio') {\n"
|
|
" el.checked = ~a;\n"
|
|
"} else {\n"
|
|
" el.value = '~a';\n"
|
|
"}")
|
|
(if (eq? val #f) "false" "true")
|
|
(cond
|
|
([g:date? val]
|
|
(date->string val))
|
|
([g:time? val]
|
|
(time->string val))
|
|
([g:datetime? val]
|
|
(datetime->string val))
|
|
([rgba? val]
|
|
(rgba->hex val))
|
|
(else
|
|
(esc-quote (format "~a" val))))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
(define/contract (webview-value wv id)
|
|
(-> wv-win? symbol? (or/c string? boolean?))
|
|
(let ((v (webview-call-js wv
|
|
(with-id id el
|
|
((string-append "let f = function() {\n"
|
|
" if (el.type == 'checkbox' || el.type == 'radio') {\n"
|
|
" return '' + el.checked;\n"
|
|
" } else {\n"
|
|
" return el.value;\n"
|
|
" }\n"
|
|
"};\n"))
|
|
-> "f()"))))
|
|
(if (eq? v #f)
|
|
#f
|
|
v)))
|
|
|
|
(define-syntax wvv
|
|
(syntax-rules ()
|
|
((_ name type-pred? convert)
|
|
(define/contract (name wv id)
|
|
(-> wv-win? symbol? (or/c type-pred? boolean?))
|
|
(let ((v (webview-value wv id)))
|
|
(if (eq? v #f)
|
|
#f
|
|
(convert v))))
|
|
)
|
|
)
|
|
)
|
|
|
|
(wvv webview-value/time g:time? string->time)
|
|
(wvv webview-value/date g:date? string->date)
|
|
(wvv webview-value/datetime g:datetime? string->datetime)
|
|
(wvv webview-value/number number? string->number)
|
|
(wvv webview-value/symbol symbol? string->symbol)
|
|
(wvv webview-value/color rgba? hex->rgba)
|
|
(wvv webview-value/boolean boolean? (λ (e) (if (string=? e "true") #t #f)))
|
|
|
|
|
|
(define/contract (webview-add-class! wv id-or-selector class)
|
|
(-> wv-win? (or/c symbol? string?) (or/c symbol? string? list?) hash?)
|
|
(let ((sel (if (symbol? id-or-selector)
|
|
(format "#~a" id-or-selector)
|
|
id-or-selector))
|
|
(cl (mk-js-array class))
|
|
)
|
|
(webview-call-js wv (with-selector sel
|
|
(format
|
|
(js-code
|
|
"function(id, el) {"
|
|
" let cl = ~a;"
|
|
" cl.forEach(function(c) {"
|
|
" el.classList.add(c);"
|
|
" });"
|
|
" return id;"
|
|
"}") cl)))
|
|
)
|
|
)
|
|
|
|
(define/contract (webview-remove-class! wv id-or-selector class)
|
|
(-> wv-win? (or/c symbol? string?) (or/c symbol? string? list?) hash?)
|
|
(let ((sel (if (symbol? id-or-selector)
|
|
(format "#~a" id-or-selector)
|
|
id-or-selector))
|
|
(cl (mk-js-array class))
|
|
)
|
|
(webview-call-js wv
|
|
(with-selector sel
|
|
(format
|
|
(js-code
|
|
"function(id, el) {"
|
|
" let cl = ~a;"
|
|
" cl.forEach(function(c) {"
|
|
" el.classList.remove(c);"
|
|
" });"
|
|
" return id;"
|
|
"}") cl))
|
|
)
|
|
)
|
|
)
|
|
|
|
(define/contract (webview-set-style! wv selector style-entries)
|
|
(-> wv-win? (or/c symbol? string?) (or/c kv? list-of-kv?) hash?)
|
|
(let ((sel (if (symbol? selector)
|
|
(format "#~a" selector)
|
|
selector))
|
|
(cl (mk-js-array (if (kv? style-entries)
|
|
(list style-entries)
|
|
style-entries)))
|
|
)
|
|
(webview-call-js wv
|
|
(with-selector sel
|
|
(format
|
|
(js-code
|
|
"function(id, el) {"
|
|
" let cl = ~a;"
|
|
" cl.forEach(function(st) {"
|
|
" el.style[st[0]] = st[1];"
|
|
" });"
|
|
" return id;"
|
|
"}") cl))
|
|
)
|
|
)
|
|
)
|
|
|
|
(define/contract (webview-get-style wv selector styles)
|
|
(-> wv-win? (or/c symbol? string?) (or/c symbol? list-of-symbol?) (or/c list? hash?))
|
|
(let ((sel (if (symbol? selector)
|
|
(format "#~a" selector)
|
|
selector))
|
|
(cl (mk-js-array (if (symbol? styles)
|
|
(list styles)
|
|
styles)))
|
|
)
|
|
(let ((r (webview-call-js wv
|
|
(with-selector sel
|
|
(format
|
|
(js-code
|
|
"function(id, el) {"
|
|
" let cl = ~a;"
|
|
" let r = {};"
|
|
" cl.forEach(function(st) {"
|
|
" let stl = window.getComputedStyle(el);"
|
|
" r[st] = stl[st];"
|
|
" });"
|
|
" return { id: id, style: r };"
|
|
"}") cl))
|
|
)))
|
|
(let ((h (hash-ref r 'with-ids)))
|
|
(let ((l (map (λ (e) (cons (string->symbol (hash-ref e 'id)) (hash-ref e 'style))) h)))
|
|
(if (symbol? selector)
|
|
(cdr (car l))
|
|
l)))
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
(define/contract (webview-unset-style! wv selector style-entries)
|
|
(-> wv-win? (or/c symbol? string?) (or/c symbol? list-of-symbol?) hash?)
|
|
(let ((sel (if (symbol? selector)
|
|
(format "#~a" selector)
|
|
selector))
|
|
(cl (mk-js-array (if (symbol? style-entries)
|
|
(list style-entries)
|
|
style-entries)))
|
|
)
|
|
(webview-call-js wv
|
|
(with-selector sel
|
|
(format
|
|
(js-code
|
|
"function(id, el) {"
|
|
" let cl = ~a;"
|
|
" cl.forEach(function(st) {"
|
|
" el.style[st] = '';"
|
|
" });"
|
|
" return id;"
|
|
"}") cl)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
(define/contract (webview-set-attr! wv selector attr-entries)
|
|
(-> wv-win? (or/c symbol? string?)
|
|
(or/c kv? list-of-kv?) hash?)
|
|
(let* ((sel (if (symbol? selector)
|
|
(format "#~a" selector)
|
|
selector))
|
|
(ae* (if (kv? attr-entries) (list attr-entries) attr-entries))
|
|
(ae** (map (λ (kv)
|
|
(let ((v (kv-2 kv)))
|
|
(make-kv (kv-1 kv)
|
|
(cond
|
|
([g:date? v] (date->string v))
|
|
([g:time? v] (time->string v))
|
|
([g:datetime? v] (datetime->string v))
|
|
([rgba? v] (rgba->hex v))
|
|
(else (format "~a" v))))
|
|
)) ae*))
|
|
(cl (mk-js-array ae**))
|
|
)
|
|
(webview-call-js wv
|
|
(with-selector sel
|
|
(format
|
|
(js-code
|
|
"function(id, el) {"
|
|
" let cl = ~a;"
|
|
" cl.forEach(function(av) {"
|
|
" el.setAttribute(av[0], av[1]);"
|
|
" });"
|
|
" return id;"
|
|
"}") cl))
|
|
)
|
|
)
|
|
)
|
|
|
|
(define/contract (webview-attr wv id attr)
|
|
(-> wv-win? symbol? (or/c symbol? string?) (or/c string? boolean?))
|
|
(let ((v (webview-call-js wv
|
|
(with-id id el
|
|
-> (format "el.getAttribute('~a');" attr))
|
|
)))
|
|
(if (eq? v 'null)
|
|
#f
|
|
v)
|
|
)
|
|
)
|
|
|
|
(define-syntax wva
|
|
(syntax-rules ()
|
|
((_ name type-pred? convert)
|
|
(define/contract (name wv id attr)
|
|
(-> wv-win? symbol? (or/c symbol? string?) (or/c type-pred? boolean?))
|
|
(let ((v (webview-attr wv id attr)))
|
|
(if (eq? v #f)
|
|
#f
|
|
(convert v))))
|
|
)
|
|
)
|
|
)
|
|
|
|
(wva webview-attr/number number? string->number)
|
|
(wva webview-attr/symbol symbol? string->symbol)
|
|
(wva webview-attr/color rgba? hex->rgba)
|
|
(wva webview-attr/date g:date? string->date)
|
|
(wva webview-attr/time g:time? string->time)
|
|
(wva webview-attr/datetime g:datetime string->datetime)
|
|
(wva webview-attr/boolean boolean? (λ (e) (if (string=? e "true") #t #f)))
|
|
|
|
(define (webview-version)
|
|
(cons (list 'webview webview-major webview-minor webview-patch)
|
|
(rkt-webview-version)))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; testing
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define-runtime-path example-path "../example")
|
|
|
|
(define file-getter (webview-standard-file-getter example-path))
|
|
|
|
(define test-context (webview-new-context file-getter))
|
|
|
|
(define (test)
|
|
(let* ((cb (λ (handle evt)
|
|
(displayln evt)))
|
|
(h (webview-create test-context "index.html" cb))
|
|
)
|
|
(displayln h)
|
|
(webview-set-title! h "This is a test window")
|
|
(webview-resize h 800 600)
|
|
(webview-move h 350 220)
|
|
;(webview-present h)
|
|
h))
|
|
|
|
|
|
; (while (not (webview-has-events? h))
|
|
; (displayln "Waiting...")
|
|
; (sleep 1))
|
|
; (let ((evt (webview-get-event h)))
|
|
; (when (string=? (hash-ref evt 'evt) "html-loaded")
|
|
; (webview-bind h "button" "click")))
|
|
; h)) |