1000 lines
30 KiB
Racket
1000 lines
30 KiB
Racket
#lang racket/base
|
|
|
|
(require data/queue
|
|
json
|
|
racket/async-channel
|
|
racket/file
|
|
racket/list
|
|
racket/path
|
|
racket/port
|
|
racket/string
|
|
racket/system
|
|
"private/utils.rkt"
|
|
"racket-webview-downloader.rkt"
|
|
"menu.rkt")
|
|
|
|
(provide rkt-wv
|
|
rkt-wv-win
|
|
|
|
rkt-webview-new-context
|
|
rkt-webview-create
|
|
rkt-webview-close
|
|
rkt-webview-set-ou-token
|
|
rkt-webview-set-url!
|
|
rkt-webview-set-html!
|
|
rkt-webview-run-js
|
|
rkt-webview-call-js
|
|
rkt-webview-move
|
|
rkt-webview-resize
|
|
rkt-webview-show
|
|
rkt-webview-hide
|
|
rkt-webview-show-normal
|
|
rkt-webview-maximize
|
|
rkt-webview-minimize
|
|
rkt-webview-window-state
|
|
rkt-webview-set-title!
|
|
rkt-webview-set-icon!
|
|
rkt-webview-present
|
|
rkt-webview-exit
|
|
rkt-webview-valid?
|
|
rkt-webview-open-devtools
|
|
rkt-webview-choose-dir
|
|
rkt-webview-file-open
|
|
rkt-webview-file-save
|
|
rkt-webview-messagebox
|
|
rkt-webview-version
|
|
rkt-webview-set-loglevel
|
|
rkt-webview-info
|
|
|
|
rkt-webview-tray-create
|
|
rkt-webview-tray-set-icon!
|
|
rkt-webview-tray-set-tooltip!
|
|
rkt-webview-tray-show-message
|
|
rkt-webview-tray-set-menu!)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Protocol constants
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define cmd-handle-is-valid 1)
|
|
(define cmd-quit 2)
|
|
(define cmd-context-new 3)
|
|
(define cmd-create-wv 4)
|
|
(define cmd-close-wv 5)
|
|
(define cmd-set-url 6)
|
|
(define cmd-set-html 7)
|
|
(define cmd-run-js 8)
|
|
(define cmd-call-js 9)
|
|
(define cmd-open-devtools 10)
|
|
(define cmd-move 11)
|
|
(define cmd-resize 12)
|
|
(define cmd-hide 13)
|
|
(define cmd-show 14)
|
|
(define cmd-present 15)
|
|
(define cmd-maximize 16)
|
|
(define cmd-minimize 17)
|
|
(define cmd-show-normal 18)
|
|
(define cmd-window-state 19)
|
|
(define cmd-set-title 20)
|
|
(define cmd-choose-dir 21)
|
|
(define cmd-file-open 22)
|
|
(define cmd-file-save 23)
|
|
(define cmd-set-ou-token 24)
|
|
(define cmd-msg-box 25)
|
|
(define cmd-set-loglevel 26)
|
|
(define cmd-info 27)
|
|
(define cmd-set-icon 28)
|
|
(define cmd-create-tray 29)
|
|
(define cmd-tray-set-icon 30)
|
|
(define cmd-tray-set-tooltip 31)
|
|
(define cmd-tray-show-message 32)
|
|
(define cmd-tray-set-menu 33)
|
|
(define cmd-tray-clear-menu 34)
|
|
|
|
(define stdio-protocol-version 1)
|
|
|
|
(define (result-code->symbol code)
|
|
(case code
|
|
((-1) 'no_result_yet)
|
|
((0) 'oke)
|
|
((1) 'set_html_failed)
|
|
((2) 'set_navigate_failed)
|
|
((3) 'eval_js_failed)
|
|
((4) 'no_devtools_on_platform)
|
|
((5) 'no_delegate_for_context)
|
|
((6) 'webview_missing_dependency)
|
|
((7) 'webview_canceled)
|
|
((8) 'webview_invalid_state)
|
|
((9) 'webview_invalid_argument)
|
|
((10) 'webview_unspecified)
|
|
((11) 'webview_dispatch_failed)
|
|
((12) 'move_failed)
|
|
((13) 'resize_failed)
|
|
((14) 'choose_dir_failed)
|
|
((15) 'open_file_failed)
|
|
((16) 'save_file_failed)
|
|
((17) 'failed)
|
|
((18) 'invalid_handle)
|
|
(else 'failed)))
|
|
|
|
(define (window-state-code->symbol code)
|
|
(case code
|
|
((-1) 'invalid)
|
|
((0) 'normal)
|
|
((1) 'minimized)
|
|
((2) 'maximized)
|
|
((3) 'hidden)
|
|
((16) 'normal_active)
|
|
((18) 'maximized_active)
|
|
(else 'invalid)))
|
|
|
|
(define (loglevel->integer level)
|
|
(case level
|
|
((error) 1)
|
|
((warning) 2)
|
|
((info) 3)
|
|
((debug) 4)
|
|
(else (error 'rkt-webview-set-loglevel
|
|
"expected 'error, 'warning, 'info or 'debug; got ~a"
|
|
level))))
|
|
|
|
(define (messagetype->integer type)
|
|
(case type
|
|
((info) 1)
|
|
((error) 2)
|
|
((warning) 3)
|
|
((yes-no) 4)
|
|
((oke-cancel) 5)
|
|
(else (error 'rkt-webview-messagebox
|
|
"unknown message type: ~a"
|
|
type))))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Backend location and child environment
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define os (system-type 'os*))
|
|
|
|
(define rktwebview-prg
|
|
(if (eq? os 'windows)
|
|
"rktwebview_prg.exe"
|
|
"rktwebview_prg"))
|
|
|
|
(define webengine-process
|
|
(if (eq? os 'windows)
|
|
"QtWebEngineProcess.exe"
|
|
"QtWebEngineProcess"))
|
|
|
|
(define backend-program #f)
|
|
(define backend-directory #f)
|
|
|
|
(define (executable-file? path)
|
|
;; Let subprocess report a platform-specific execute error. Checking only
|
|
;; existence also works for Windows and for unpacked Unix release archives.
|
|
(and path (file-exists? path)))
|
|
|
|
(define (ensure-backend-program!)
|
|
(unless backend-program
|
|
(let ((override (getenv "RKT_WEBVIEW_PRG")))
|
|
(cond
|
|
((and override (executable-file? override))
|
|
(set! backend-program (path->complete-path override))
|
|
(set! backend-directory (path-only backend-program)))
|
|
|
|
(override
|
|
(error 'racket-webview
|
|
"RKT_WEBVIEW_PRG does not point to an executable file: ~a"
|
|
override))
|
|
|
|
(else
|
|
(unless (racket-webview-qt-is-available?)
|
|
(cond
|
|
((not (racket-webview-qt-resolves?))
|
|
(error 'racket-webview
|
|
"Cannot resolve the racket-webview-qt download site"))
|
|
((not (racket-webview-qt-is-downloadable?))
|
|
(error 'racket-webview
|
|
(string-append
|
|
"No downloadable racket-webview-qt backend is available "
|
|
"for OS ~a and architecture ~a")
|
|
os
|
|
(system-type 'arch)))
|
|
((not (download-racket-webview-qt))
|
|
(error 'racket-webview
|
|
"The racket-webview-qt backend could not be downloaded"))))
|
|
|
|
(set! backend-directory (racket-webview-qt-directory))
|
|
(set! backend-program (build-path backend-directory rktwebview-prg))
|
|
(unless (executable-file? backend-program)
|
|
(error 'racket-webview
|
|
"The racket-webview-qt executable is missing: ~a"
|
|
backend-program)))))))
|
|
|
|
(define (environment-set-string! environment name value)
|
|
(environment-variables-set! environment
|
|
(string->bytes/utf-8 name)
|
|
(string->bytes/utf-8 value)))
|
|
|
|
(define (make-backend-environment)
|
|
(define environment
|
|
(environment-variables-copy (current-environment-variables)))
|
|
(define directory-string (path->string backend-directory))
|
|
|
|
(environment-set-string! environment "QT_PLUGIN_PATH" directory-string)
|
|
(environment-set-string! environment
|
|
"QTWEBENGINEPROCESS_PATH"
|
|
(path->string
|
|
(build-path backend-directory webengine-process)))
|
|
(environment-set-string! environment
|
|
"QTWEBENGINE_RESOURCES_PATH"
|
|
(path->string
|
|
(build-path backend-directory "resources")))
|
|
(environment-set-string! environment
|
|
"QTWEBENGINE_LOCALES_PATH"
|
|
(path->string
|
|
(build-path backend-directory
|
|
"translations"
|
|
"qtwebengine_locales")))
|
|
|
|
(when (eq? os 'linux)
|
|
(let* ((old-library-path
|
|
(environment-variables-ref environment #"LD_LIBRARY_PATH"))
|
|
(added-library-path
|
|
(string-append
|
|
directory-string
|
|
":"
|
|
(path->string (build-path backend-directory "platforms")))))
|
|
(environment-set-string! environment "QT_QPA_PLATFORM" "xcb")
|
|
(environment-set-string!
|
|
environment
|
|
"LD_LIBRARY_PATH"
|
|
(if old-library-path
|
|
(string-append added-library-path
|
|
":"
|
|
(bytes->string/utf-8 old-library-path))
|
|
added-library-path))))
|
|
|
|
environment)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Handles and event dispatch
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define-struct rkt-wv
|
|
(win evt-queue callback [valid #:mutable] [close-callback #:mutable])
|
|
#:transparent)
|
|
|
|
(define rkt-wv-store (make-hash))
|
|
(define evt-cb-hash (make-hash))
|
|
|
|
(define state-lock (make-semaphore 1))
|
|
|
|
(define (with-state-lock thunk)
|
|
(call-with-semaphore state-lock thunk))
|
|
|
|
(define event-channel (make-async-channel))
|
|
|
|
(define event-thread
|
|
(thread
|
|
(lambda ()
|
|
(let loop ()
|
|
;; Capture the callback while reading stdout. In particular, a
|
|
;; "closed" event is written before the corresponding close result;
|
|
;; the handle may already have been removed by the time this thread runs.
|
|
(define event (async-channel-get event-channel))
|
|
(define callback (vector-ref event 0))
|
|
(define data (vector-ref event 1))
|
|
(when callback
|
|
(with-handlers ((exn:fail?
|
|
(lambda (exception)
|
|
(err-webview
|
|
"Exception in webview event callback: ~a"
|
|
(exn-message exception)))))
|
|
(callback data)))
|
|
(loop)))))
|
|
|
|
(define (invalidate-all-handles!)
|
|
(with-state-lock
|
|
(lambda ()
|
|
(for ([(wv handle) (in-hash rkt-wv-store)])
|
|
(set-rkt-wv-valid! handle #f))
|
|
(hash-clear! evt-cb-hash)
|
|
(hash-clear! rkt-wv-store))))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Stdio transport
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define backend-process #f)
|
|
(define backend-in #f)
|
|
(define backend-out #f)
|
|
(define backend-error-in #f)
|
|
(define backend-reader-thread #f)
|
|
(define backend-error-thread #f)
|
|
(define backend-watcher-thread #f)
|
|
|
|
(define backend-failure #f)
|
|
(define backend-closing? #f)
|
|
(define backend-ready? #f)
|
|
(define backend-ready-status #f)
|
|
(define backend-ready-semaphore (make-semaphore 0))
|
|
|
|
(define startup-lock (make-semaphore 1))
|
|
(define write-lock (make-semaphore 1))
|
|
(define pending-lock (make-semaphore 1))
|
|
(define pending-responses (make-hash))
|
|
(define request-id 0)
|
|
(define function-calls 0)
|
|
(define event-count 0)
|
|
|
|
(define (make-backend-exception message)
|
|
(exn:fail message (current-continuation-marks)))
|
|
|
|
(define (with-pending-lock thunk)
|
|
(call-with-semaphore pending-lock thunk))
|
|
|
|
(define (fail-pending-requests! exception)
|
|
(define channels
|
|
(with-pending-lock
|
|
(lambda ()
|
|
(define result
|
|
(for/list ([(id channel) (in-hash pending-responses)])
|
|
channel))
|
|
(hash-clear! pending-responses)
|
|
result)))
|
|
(for-each (lambda (channel)
|
|
(async-channel-put channel exception))
|
|
channels))
|
|
|
|
(define (backend-active?)
|
|
(and backend-process
|
|
backend-in
|
|
backend-out
|
|
(not backend-failure)))
|
|
|
|
(define (backend-ended! message)
|
|
;; Preserve the first failure. Every pending and future command must see
|
|
;; the same terminal backend exception.
|
|
(define exception
|
|
(or backend-failure
|
|
(make-backend-exception message)))
|
|
|
|
(unless backend-ready?
|
|
(set! backend-ready-status exception)
|
|
(semaphore-post backend-ready-semaphore))
|
|
|
|
(unless backend-closing?
|
|
(unless backend-failure
|
|
(set! backend-failure exception)
|
|
(err-webview "racket-webview-qt backend stopped: ~a" message)
|
|
(invalidate-all-handles!)))
|
|
|
|
(fail-pending-requests! exception)
|
|
exception)
|
|
|
|
(define (dispatch-protocol-message message)
|
|
(unless (hash? message)
|
|
(error 'racket-webview "Backend emitted a non-object JSON value"))
|
|
|
|
(let ((type (hash-ref message 'type #f)))
|
|
(cond
|
|
((equal? type "ready")
|
|
(set! backend-ready-status (hash-ref message 'protocol #f))
|
|
(semaphore-post backend-ready-semaphore))
|
|
|
|
((equal? type "result")
|
|
(let* ((id (hash-ref message 'id #f))
|
|
(channel
|
|
(and id
|
|
(with-pending-lock
|
|
(lambda ()
|
|
(define result (hash-ref pending-responses id #f))
|
|
(when result
|
|
(hash-remove! pending-responses id))
|
|
result)))))
|
|
(if channel
|
|
(async-channel-put channel message)
|
|
(warn-webview "Result for unknown request id ~a" id))))
|
|
|
|
((equal? type "event")
|
|
(let* ((wv (hash-ref message 'wv -1))
|
|
(callback
|
|
(with-state-lock
|
|
(lambda ()
|
|
(hash-ref evt-cb-hash wv #f)))))
|
|
(set! event-count (add1 event-count))
|
|
(when callback
|
|
(async-channel-put event-channel
|
|
(vector callback
|
|
(hash-ref message 'data ""))))))
|
|
|
|
((equal? type "protocol-error")
|
|
(let* ((id (hash-ref message 'id #f))
|
|
(text (hash-ref message 'message "unknown error"))
|
|
(exception
|
|
(make-backend-exception
|
|
(format "Backend protocol error: ~a" text)))
|
|
(channel
|
|
(and id
|
|
(with-pending-lock
|
|
(lambda ()
|
|
(define result (hash-ref pending-responses id #f))
|
|
(when result
|
|
(hash-remove! pending-responses id))
|
|
result)))))
|
|
(if channel
|
|
(async-channel-put channel exception)
|
|
(err-webview "Backend protocol error: ~a" text))))
|
|
|
|
(else
|
|
(warn-webview "Unknown backend message type: ~a" type)))))
|
|
|
|
(define (start-protocol-reader!)
|
|
(set!
|
|
backend-reader-thread
|
|
(thread
|
|
(lambda ()
|
|
(with-handlers
|
|
((exn:fail?
|
|
(lambda (exception)
|
|
(backend-ended!
|
|
(format "cannot read backend stdout: ~a"
|
|
(exn-message exception))))))
|
|
(let loop ()
|
|
(define message (read-json backend-in))
|
|
(if (eof-object? message)
|
|
(backend-ended! "stdout was closed")
|
|
(begin
|
|
(dispatch-protocol-message message)
|
|
(loop)))))))))
|
|
|
|
(define (log-backend-message level message [elapsed #f])
|
|
(define formatted
|
|
(if (number? elapsed)
|
|
(format "~a: ~a" elapsed message)
|
|
message))
|
|
(case level
|
|
((error err)
|
|
(err-webview-backend "~a" formatted))
|
|
((warning warn)
|
|
(warn-webview-backend "~a" formatted))
|
|
((info)
|
|
(info-webview-backend "~a" formatted))
|
|
(else
|
|
(dbg-webview-backend "~a" formatted))))
|
|
|
|
(define (log-structured-backend-line line)
|
|
(with-handlers
|
|
((exn:fail?
|
|
(lambda (exception)
|
|
(warn-webview-backend
|
|
"Invalid structured backend log: ~a; line: ~a"
|
|
(exn-message exception)
|
|
line))))
|
|
(define entry (string->jsexpr (substring line 5)))
|
|
(unless (hash? entry)
|
|
(error 'log-structured-backend-line "JSON object expected"))
|
|
(define level-value (hash-ref entry 'level "debug"))
|
|
(define level
|
|
(cond
|
|
((symbol? level-value) level-value)
|
|
((string? level-value) (string->symbol (string-downcase level-value)))
|
|
(else 'debug)))
|
|
(define message (hash-ref entry 'message ""))
|
|
(define elapsed (hash-ref entry 'elapsed #f))
|
|
(log-backend-message level (format "~a" message) elapsed)))
|
|
|
|
(define (log-backend-line line)
|
|
(cond
|
|
((string-prefix? line "json:")
|
|
(log-structured-backend-line line))
|
|
((regexp-match? #rx"^ERROR[ ]*:" line)
|
|
(err-webview-backend "~a" line))
|
|
((regexp-match? #rx"^WARNING[ ]*:" line)
|
|
(warn-webview-backend "~a" line))
|
|
((regexp-match? #rx"^INFO[ ]*:" line)
|
|
(info-webview-backend "~a" line))
|
|
((regexp-match? #rx"^DEBUG[ ]*:" line)
|
|
(dbg-webview-backend "~a" line))
|
|
(else
|
|
;; Libraries used by Qt may occasionally write unstructured text to
|
|
;; stderr. Keep it visible without confusing it with protocol output.
|
|
(dbg-webview-backend "~a" line))))
|
|
|
|
(define (start-error-forwarder!)
|
|
(set!
|
|
backend-error-thread
|
|
(thread
|
|
(lambda ()
|
|
(with-handlers
|
|
((exn:fail?
|
|
(lambda (exception)
|
|
(warn-webview "Cannot read backend stderr: ~a"
|
|
(exn-message exception)))))
|
|
(let loop ()
|
|
(define line (read-line backend-error-in 'any))
|
|
(unless (eof-object? line)
|
|
(log-backend-line line)
|
|
(loop))))))))
|
|
|
|
(define (start-process-watcher!)
|
|
(set!
|
|
backend-watcher-thread
|
|
(thread
|
|
(lambda ()
|
|
(with-handlers
|
|
((exn:fail?
|
|
(lambda (exception)
|
|
(backend-ended!
|
|
(format "cannot wait for backend process: ~a"
|
|
(exn-message exception))))))
|
|
(subprocess-wait backend-process)
|
|
(define status (subprocess-status backend-process))
|
|
(unless backend-closing?
|
|
(backend-ended!
|
|
(format "process exited with status ~a" status))))))))
|
|
|
|
(define (reset-backend-state!)
|
|
(set! backend-process #f)
|
|
(set! backend-in #f)
|
|
(set! backend-out #f)
|
|
(set! backend-error-in #f)
|
|
(set! backend-reader-thread #f)
|
|
(set! backend-error-thread #f)
|
|
(set! backend-watcher-thread #f)
|
|
(set! backend-failure #f)
|
|
(set! backend-closing? #f)
|
|
(set! backend-ready? #f)
|
|
(set! backend-ready-status #f))
|
|
|
|
(define (stop-failed-backend-start!)
|
|
(when backend-out
|
|
(with-handlers ((exn:fail? void))
|
|
(close-output-port backend-out)))
|
|
(when backend-process
|
|
(with-handlers ((exn:fail? void))
|
|
(subprocess-kill backend-process #t))
|
|
(with-handlers ((exn:fail? void))
|
|
(subprocess-wait backend-process)))
|
|
(when backend-reader-thread
|
|
(with-handlers ((exn:fail? void))
|
|
(thread-wait backend-reader-thread)))
|
|
(when backend-error-thread
|
|
(with-handlers ((exn:fail? void))
|
|
(thread-wait backend-error-thread)))
|
|
(when backend-watcher-thread
|
|
(with-handlers ((exn:fail? void))
|
|
(thread-wait backend-watcher-thread)))
|
|
(reset-backend-state!))
|
|
|
|
(define (start-backend!)
|
|
(ensure-backend-program!)
|
|
|
|
(set! backend-failure #f)
|
|
(set! backend-closing? #f)
|
|
(set! backend-ready? #f)
|
|
(set! backend-ready-status #f)
|
|
(set! backend-ready-semaphore (make-semaphore 0))
|
|
|
|
(let-values (((process stdout stdin stderr)
|
|
(parameterize ((current-environment-variables
|
|
(make-backend-environment))
|
|
(current-directory backend-directory))
|
|
(subprocess #f #f #f backend-program))))
|
|
(set! backend-process process)
|
|
(set! backend-in stdout)
|
|
(set! backend-out stdin)
|
|
(set! backend-error-in stderr)
|
|
|
|
(start-error-forwarder!)
|
|
(start-protocol-reader!)
|
|
(start-process-watcher!)
|
|
|
|
(unless (sync/timeout 15 backend-ready-semaphore)
|
|
(stop-failed-backend-start!)
|
|
(error 'racket-webview
|
|
"The racket-webview-qt backend did not complete its stdio handshake"))
|
|
|
|
(when (exn:fail? backend-ready-status)
|
|
(let ((exception backend-ready-status))
|
|
(stop-failed-backend-start!)
|
|
(raise exception)))
|
|
|
|
(unless (equal? backend-ready-status stdio-protocol-version)
|
|
(let ((actual backend-ready-status))
|
|
(stop-failed-backend-start!)
|
|
(error 'racket-webview
|
|
"Unsupported backend stdio protocol ~a; expected ~a"
|
|
actual
|
|
stdio-protocol-version)))
|
|
|
|
(set! backend-ready? #t)))
|
|
|
|
(define (ensure-backend!)
|
|
(call-with-semaphore
|
|
startup-lock
|
|
(lambda ()
|
|
;; A backend failure is terminal for this transport instance. Do not
|
|
;; silently start a fresh process: callers with stale webview handles
|
|
;; must receive the original exception instead.
|
|
(when backend-failure
|
|
(raise backend-failure))
|
|
(unless (backend-active?)
|
|
(start-backend!)))))
|
|
|
|
(define (send-command command data)
|
|
(ensure-backend!)
|
|
|
|
(let ((response-channel (make-async-channel))
|
|
(id #f))
|
|
(call-with-semaphore
|
|
write-lock
|
|
(lambda ()
|
|
(set! request-id (add1 request-id))
|
|
(set! id request-id)
|
|
(set! function-calls (add1 function-calls))
|
|
|
|
(with-pending-lock
|
|
(lambda ()
|
|
(hash-set! pending-responses id response-channel)))
|
|
|
|
(with-handlers
|
|
((exn:fail?
|
|
(lambda (exception)
|
|
(define failure
|
|
(backend-ended!
|
|
(format "cannot write backend stdin: ~a"
|
|
(exn-message exception))))
|
|
(raise failure))))
|
|
(write-json (hasheq 'type "command"
|
|
'id id
|
|
'command command
|
|
'data data)
|
|
backend-out)
|
|
(newline backend-out)
|
|
(flush-output backend-out))))
|
|
|
|
(let ((response (async-channel-get response-channel)))
|
|
(when (exn:fail? response)
|
|
(raise response))
|
|
response)))
|
|
|
|
(define (command-integer-result command data)
|
|
(hash-ref (send-command command data) 'result 17))
|
|
|
|
(define (command-symbol-result command data)
|
|
(result-code->symbol (command-integer-result command data)))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Public low-level API
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define (rkt-webview-new-context boilerplate-js server-cert)
|
|
(command-integer-result
|
|
cmd-context-new
|
|
(hasheq 'boilerplate_js boilerplate-js
|
|
'has_pem_cert (and server-cert #t)
|
|
'pem_cert (or server-cert ""))))
|
|
|
|
(define (rkt-webview-create context parent evt-callback close-callback)
|
|
(define parent-win
|
|
(if parent
|
|
(rkt-wv-win parent)
|
|
0))
|
|
(define wv
|
|
(command-integer-result
|
|
cmd-create-wv
|
|
(hasheq 'context context
|
|
'parent parent-win)))
|
|
(define handle
|
|
(make-rkt-wv wv
|
|
(make-queue)
|
|
evt-callback
|
|
#t
|
|
close-callback))
|
|
(with-state-lock
|
|
(lambda ()
|
|
(hash-set! evt-cb-hash
|
|
wv
|
|
(lambda (event)
|
|
(evt-callback handle event)))
|
|
(hash-set! rkt-wv-store wv handle)))
|
|
handle)
|
|
|
|
(define (rkt-webview-close handle)
|
|
(when (rkt-wv-valid handle)
|
|
(with-handlers ((exn:fail?
|
|
(lambda (exception)
|
|
(warn-webview "Could not close backend handle ~a: ~a"
|
|
(rkt-wv-win handle)
|
|
(exn-message exception)))))
|
|
(command-integer-result
|
|
cmd-close-wv
|
|
(hasheq 'wv (rkt-wv-win handle))))
|
|
|
|
(set-rkt-wv-valid! handle #f)
|
|
(with-state-lock
|
|
(lambda ()
|
|
(hash-remove! evt-cb-hash (rkt-wv-win handle))
|
|
(hash-remove! rkt-wv-store (rkt-wv-win handle))))
|
|
((rkt-wv-close-callback handle)))
|
|
#t)
|
|
|
|
(define (rkt-webview-set-loglevel level)
|
|
(command-integer-result
|
|
cmd-set-loglevel
|
|
(hasheq 'wv (loglevel->integer level)))
|
|
(void))
|
|
|
|
(define (rkt-webview-info)
|
|
(define open-windows
|
|
(command-integer-result cmd-info (hasheq)))
|
|
(list (list 'transport 'stdio)
|
|
(list 'shm-usage 0)
|
|
(list 'shm-freelist 0 0)
|
|
(list 'shm-alloc 0 0 0.0)
|
|
(list 'open-windows open-windows)
|
|
(list 'calls function-calls)
|
|
(list 'events event-count)
|
|
(list 'log-file "stderr")))
|
|
|
|
(define (rkt-webview-set-ou-token handle token)
|
|
(command-integer-result
|
|
cmd-set-ou-token
|
|
(hasheq 'wv (rkt-wv-win handle)
|
|
'token token))
|
|
#t)
|
|
|
|
(define (rkt-webview-set-url! handle url)
|
|
(command-symbol-result
|
|
cmd-set-url
|
|
(hasheq 'wv (rkt-wv-win handle)
|
|
'url url)))
|
|
|
|
(define (rkt-webview-set-html! handle html)
|
|
(command-symbol-result
|
|
cmd-set-html
|
|
(hasheq 'wv (rkt-wv-win handle)
|
|
'html html)))
|
|
|
|
(define (rkt-webview-set-title! handle title)
|
|
(command-symbol-result
|
|
cmd-set-title
|
|
(hasheq 'wv (rkt-wv-win handle)
|
|
'title title)))
|
|
|
|
(define (rkt-webview-set-icon! handle icon-file)
|
|
(command-symbol-result
|
|
cmd-set-icon
|
|
(hasheq 'wv (rkt-wv-win handle)
|
|
'icon icon-file)))
|
|
|
|
(define (rkt-webview-run-js handle js)
|
|
(command-symbol-result
|
|
cmd-run-js
|
|
(hasheq 'wv (rkt-wv-win handle)
|
|
'js js)))
|
|
|
|
(define (rkt-webview-call-js handle js)
|
|
(define response
|
|
(send-command
|
|
cmd-call-js
|
|
(hasheq 'wv (rkt-wv-win handle)
|
|
'js js)))
|
|
(list (result-code->symbol (hash-ref response 'result 17))
|
|
(hash-ref response 'data "")))
|
|
|
|
(define (rkt-webview-resize handle width height)
|
|
(command-symbol-result
|
|
cmd-resize
|
|
(hasheq 'wv (rkt-wv-win handle)
|
|
'w width
|
|
'h height)))
|
|
|
|
(define (rkt-webview-move handle x y)
|
|
(command-symbol-result
|
|
cmd-move
|
|
(hasheq 'wv (rkt-wv-win handle)
|
|
'x x
|
|
'y y)))
|
|
|
|
(define (handle-command-symbol-result command handle)
|
|
(command-symbol-result command (hasheq 'wv (rkt-wv-win handle))))
|
|
|
|
(define (rkt-webview-show handle)
|
|
(handle-command-symbol-result cmd-show handle))
|
|
|
|
(define (rkt-webview-hide handle)
|
|
(handle-command-symbol-result cmd-hide handle))
|
|
|
|
(define (rkt-webview-show-normal handle)
|
|
(handle-command-symbol-result cmd-show-normal handle))
|
|
|
|
(define (rkt-webview-minimize handle)
|
|
(handle-command-symbol-result cmd-minimize handle))
|
|
|
|
(define (rkt-webview-maximize handle)
|
|
(handle-command-symbol-result cmd-maximize handle))
|
|
|
|
(define (rkt-webview-present handle)
|
|
(handle-command-symbol-result cmd-present handle))
|
|
|
|
(define (rkt-webview-window-state handle)
|
|
(window-state-code->symbol
|
|
(command-integer-result
|
|
cmd-window-state
|
|
(hasheq 'wv (rkt-wv-win handle)))))
|
|
|
|
(define (rkt-webview-open-devtools handle)
|
|
(handle-command-symbol-result cmd-open-devtools handle))
|
|
|
|
(define (rkt-webview-choose-dir handle title base-dir)
|
|
(command-symbol-result
|
|
cmd-choose-dir
|
|
(hasheq 'wv (rkt-wv-win handle)
|
|
'title title
|
|
'base_dir base-dir)))
|
|
|
|
(define (rkt-webview-file-open handle title base-dir permitted-exts)
|
|
(command-symbol-result
|
|
cmd-file-open
|
|
(hasheq 'wv (rkt-wv-win handle)
|
|
'title title
|
|
'base_dir base-dir
|
|
'permitted_exts permitted-exts)))
|
|
|
|
(define (rkt-webview-file-save handle title base-dir permitted-exts)
|
|
(command-symbol-result
|
|
cmd-file-save
|
|
(hasheq 'wv (rkt-wv-win handle)
|
|
'title title
|
|
'base_dir base-dir
|
|
'permitted_exts permitted-exts)))
|
|
|
|
(define (rkt-webview-messagebox handle title message submessage type)
|
|
(command-symbol-result
|
|
cmd-msg-box
|
|
(hasheq 'wv (rkt-wv-win handle)
|
|
'title title
|
|
'message message
|
|
'submessage submessage
|
|
'type (messagetype->integer type))))
|
|
|
|
(define (rkt-webview-version)
|
|
(list (list 'webview-stdio-api 1 0 0)
|
|
(list 'racket-webview-qt 0 2 3)))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Tray API
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define (rkt-webview-tray-create icon-file tooltip evt-callback)
|
|
(define tray
|
|
(command-integer-result
|
|
cmd-create-tray
|
|
(hasheq 'icon icon-file
|
|
'tooltip tooltip)))
|
|
(define handle
|
|
(make-rkt-wv tray
|
|
(make-queue)
|
|
evt-callback
|
|
#t
|
|
(lambda () #t)))
|
|
(with-state-lock
|
|
(lambda ()
|
|
(hash-set! evt-cb-hash
|
|
tray
|
|
(lambda (event)
|
|
(evt-callback handle event)))
|
|
(hash-set! rkt-wv-store tray handle)))
|
|
handle)
|
|
|
|
(define (rkt-webview-tray-set-icon! tray icon-file)
|
|
(command-symbol-result
|
|
cmd-tray-set-icon
|
|
(hasheq 'wv (rkt-wv-win tray)
|
|
'icon icon-file)))
|
|
|
|
(define (rkt-webview-tray-set-tooltip! tray tooltip)
|
|
(command-symbol-result
|
|
cmd-tray-set-tooltip
|
|
(hasheq 'wv (rkt-wv-win tray)
|
|
'tooltip tooltip)))
|
|
|
|
(define (rkt-webview-tray-show-message tray title message)
|
|
(command-symbol-result
|
|
cmd-tray-show-message
|
|
(hasheq 'wv (rkt-wv-win tray)
|
|
'title title
|
|
'message message)))
|
|
|
|
(define (rkt-webview-tray-set-menu! tray menu)
|
|
(define menu-json
|
|
(cond
|
|
((wv-menu? menu)
|
|
(and (not (wv-menu-empty? menu))
|
|
(wv-menu->json menu)))
|
|
((not menu) #f)
|
|
(else
|
|
(error 'rkt-webview-tray-set-menu!
|
|
"expected a wv-menu or #f; got ~a"
|
|
menu))))
|
|
|
|
(if menu-json
|
|
(command-symbol-result
|
|
cmd-tray-set-menu
|
|
(hasheq 'wv (rkt-wv-win tray)
|
|
'menu_json menu-json))
|
|
(command-symbol-result
|
|
cmd-tray-clear-menu
|
|
(hasheq 'wv (rkt-wv-win tray)))))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Administration and cleanup
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define (rkt-webview-valid? handle)
|
|
(and (rkt-wv-valid handle)
|
|
(not (= 0
|
|
(command-integer-result
|
|
cmd-handle-is-valid
|
|
(hasheq 'wv (rkt-wv-win handle)))))))
|
|
|
|
(define webview-exit-done #f)
|
|
|
|
(define (wait-for-backend-and-reset!)
|
|
(when backend-out
|
|
(with-handlers ((exn:fail? void))
|
|
(close-output-port backend-out)))
|
|
(when backend-process
|
|
(with-handlers ((exn:fail? void))
|
|
(subprocess-wait backend-process)))
|
|
(when backend-reader-thread
|
|
(with-handlers ((exn:fail? void))
|
|
(thread-wait backend-reader-thread)))
|
|
(when backend-error-thread
|
|
(with-handlers ((exn:fail? void))
|
|
(thread-wait backend-error-thread)))
|
|
(when backend-watcher-thread
|
|
(with-handlers ((exn:fail? void))
|
|
(thread-wait backend-watcher-thread)))
|
|
(reset-backend-state!))
|
|
|
|
(define (rkt-webview-exit . arguments)
|
|
(define close-windows
|
|
(if (null? arguments)
|
|
#t
|
|
(car arguments)))
|
|
(define message
|
|
(if (or (null? arguments)
|
|
(null? (cdr arguments)))
|
|
#f
|
|
(cadr arguments)))
|
|
|
|
(set! backend-closing? #t)
|
|
|
|
(when close-windows
|
|
(let ((handles
|
|
(with-state-lock
|
|
(lambda ()
|
|
(for/list ([(wv handle) (in-hash rkt-wv-store)])
|
|
handle)))))
|
|
(for-each rkt-webview-close handles)))
|
|
|
|
(when (backend-active?)
|
|
(with-handlers ((exn:fail?
|
|
(lambda (exception)
|
|
(warn-webview "Could not stop backend cleanly: ~a"
|
|
(exn-message exception)))))
|
|
(send-command cmd-quit (hasheq))))
|
|
|
|
(when backend-process
|
|
(wait-for-backend-and-reset!))
|
|
|
|
(invalidate-all-handles!)
|
|
(set! webview-exit-done #t)
|
|
(info-webview "webview-exit done")
|
|
|
|
(when message
|
|
(error message)))
|