racket/collects/mred/private/wx/common/queue.rkt
Matthew Flatt 9d563c786a racket/gui, win32: fix problem with modal dialogs
Although most events in other frames were blocked, it was
possible to bring other frames to the front and to select
menu items in other frames.
2012-04-05 06:58:53 -06:00

661 lines
26 KiB
Racket

#lang racket/base
(require ffi/unsafe
racket/draw/private/utils
ffi/unsafe/atomic
racket/class
racket/port
"rbtree.rkt"
"../../lock.rkt"
"handlers.rkt"
"once.rkt")
(provide
(protect-out queue-evt
set-check-queue!
set-queue-wakeup!
add-event-boundary-callback!
add-event-boundary-sometimes-callback!
remove-event-boundary-callback!
pre-event-sync
boundary-tasks-ready-evt
sometimes-delay-msec
set-platform-queue-sync!
eventspace?
current-eventspace
queue-event
queue-refresh-event
yield
yield/no-sync
yield-refresh
eventspace-event-evt
(rename-out [make-new-eventspace make-eventspace])
event-dispatch-handler
eventspace-shutdown?
main-eventspace?
eventspace-handler-thread
eventspace-event-evt
eventspace-wait-cursor-count
eventspace-extra-table
eventspace-adjust-external-modal!
queue-callback
middle-queue-key
make-timer-callback
add-timer-callback
remove-timer-callback
register-frame-shown
get-top-level-windows
other-modal?
queue-quit-event
queue-prefs-event
queue-about-event
queue-file-event
queue-start-empty-event
begin-busy-cursor
end-busy-cursor
is-busy?)
scheme_register_process_global)
;; ------------------------------------------------------------
;; Create a Scheme evt that is ready when a queue is nonempty
(define _Scheme_Type _short)
(define-mz scheme_make_type (_fun _string -> _Scheme_Type))
(define event-queue-type (scheme_make_type "event-queue"))
(define-mz scheme_add_evt (_fun _Scheme_Type
(_fun #:atomic? #t _scheme -> _int)
(_fun #:atomic? #t _scheme _gcpointer -> _void)
_pointer
_int
-> _void))
(define (do-check-queue) #f)
(define (do-queue-wakeup fds) #f)
(define (check-queue o)
(if (do-check-queue) 1 0))
(define (queue-wakeup o fds)
(do-queue-wakeup fds))
(scheme_add_evt event-queue-type check-queue queue-wakeup #f 0)
(define queue-evt (let ([p (malloc 16)]
[p2 (malloc 'nonatomic _pointer)])
(memset p 0 16)
(ptr-set! p _Scheme_Type event-queue-type)
(ptr-set! p2 _pointer p)
(ptr-ref p2 _scheme)))
(define (set-check-queue! check)
(set! do-check-queue check))
(define (set-queue-wakeup! wake)
(set! do-queue-wakeup wake))
;; ------------------------------------------------------------
;; Pre-event sync
(define boundary-ht (make-hasheq))
(define sometimes-boundary-ht (make-hasheq))
(define tasks-ready? #f)
(define task-ready-sema (make-semaphore))
(define boundary-tasks-ready-evt (semaphore-peek-evt task-ready-sema))
(define (alert-tasks-ready)
(let ([ready? (or (positive? (hash-count boundary-ht))
(positive? (hash-count sometimes-boundary-ht)))])
(unless (eq? ready? tasks-ready?)
(set! tasks-ready? ready?)
(if ready?
(semaphore-post task-ready-sema)
(semaphore-wait task-ready-sema)))))
(define (add-event-boundary-callback! v proc)
(atomically
(hash-set! boundary-ht v proc)
(alert-tasks-ready)))
(define (add-event-boundary-sometimes-callback! v proc)
(atomically
(when (zero? (hash-count sometimes-boundary-ht))
(set! last-time (current-inexact-milliseconds)))
(hash-set! sometimes-boundary-ht v proc)
(alert-tasks-ready)))
(define (remove-event-boundary-callback! v)
(atomically
(hash-remove! boundary-ht v)
(hash-remove! sometimes-boundary-ht v)
(alert-tasks-ready)))
(define last-time -inf.0)
(define sometimes-delay-msec 100)
;; Call this function only in atomic mode:
(define (pre-event-sync force?)
(let ([now (current-inexact-milliseconds)])
(when (or (now . > . (+ last-time sometimes-delay-msec))
force?)
(set! last-time now)
(hash-for-each sometimes-boundary-ht
(lambda (v p) (hash-remove! sometimes-boundary-ht v) (p v)))))
(hash-for-each boundary-ht (lambda (v p) (hash-remove! boundary-ht v) (p v)))
(alert-tasks-ready))
;; ------------------------------------------------------------
;; Eventspaces
(define-struct eventspace (handler-thread
queue-proc
frames-hash
done-evt
[shutdown? #:mutable]
done-sema
[wait-cursor-count #:mutable]
extra-table
[external-modal #:mutable])
#:property prop:evt (lambda (v)
(wrap-evt (eventspace-done-evt v)
(lambda (_) v))))
(define-struct timed (alarm-evt msecs val [id #:mutable]))
(define (make-timer-callback msecs thunk)
(make-timed (alarm-evt msecs)
msecs
thunk
0))
(define (timed-compare a b)
(if (eq? a b)
0
(let ([am (timed-msecs a)]
[bm (timed-msecs b)])
(cond
[(= am bm) (if ((timed-id a) . < . (timed-id b))
-1
1)]
[(< am bm) -1]
[else 1]))))
;; This table refers to handle threads of eventspaces
;; that have an open window, etc., so that the eventspace
;; isn't GCed
(define active-eventspaces (make-hasheq))
(define current-cb-box (make-parameter #f))
(define-mz scheme_add_managed (_fun _racket ; custodian
_racket ; object
(_fun #:atomic? #t #:keep (lambda (v) (set-box! (current-cb-box) v))
_racket _racket -> _void)
_racket ; data
_int ; strong?
-> _gcpointer))
(define (shutdown-eventspace! e ignored)
;; atomic mode
(unless (eventspace-shutdown? e)
(set-eventspace-shutdown?! e #t)
(semaphore-post (eventspace-done-sema e))
(for ([f (in-list (get-top-level-windows e))])
(send f destroy))
(hash-remove! active-eventspaces (eventspace-handler-thread e))))
(define platform-queue-sync void)
(define (set-platform-queue-sync! proc)
(set! platform-queue-sync proc))
(define (make-eventspace* th)
(let ([done-sema (make-semaphore 1)]
[done-set? #t]
[frames (make-hasheq)])
(let ([e
(make-eventspace th
(let ([count 0])
(let ([lo (mcons #f #f)]
[refresh (mcons #f #f)]
[med (mcons #f #f)]
[hi (mcons #f #f)]
[timer (box '())]
[timer-counter 0]
[newly-posted-sema (make-semaphore)])
(let* ([check-done
(lambda ()
(if (or (positive? count)
(positive? (hash-count frames))
(not (null? (unbox timer))))
(when done-set?
(hash-set! active-eventspaces th #t)
(set! done-set? #f)
(semaphore-try-wait? done-sema))
(unless done-set?
(hash-remove! active-eventspaces th)
(set! done-set? #t)
(semaphore-post done-sema))))]
[enqueue (lambda (v q)
(set! count (add1 count))
(check-done)
(let ([p (mcons v #f)])
(if (mcdr q)
(set-mcdr! (mcdr q) p)
(set-mcar! q p))
(set-mcdr! q p)))]
[first (lambda (q peek?)
(and (mcar q)
(if peek?
always-evt
(wrap-evt
always-evt
(lambda (_)
(start-atomic)
(set! count (sub1 count))
(check-done)
(let ([result (mcar (mcar q))])
(set-mcar! q (mcdr (mcar q)))
(unless (mcar q)
(set-mcdr! q #f))
(end-atomic)
result))))))]
[remove-timer
(lambda (v timer)
(set-box! timer (rbtree-remove
timed-compare
v
(unbox timer)))
(check-done))]
[timer-first-ready
(lambda (timer peek?)
(let ([rb (unbox timer)])
(and (not (null? rb))
(let* ([v (rbtree-min (unbox timer))]
[evt (timed-alarm-evt v)])
(and (sync/timeout 0 evt)
;; It's ready
(if peek?
always-evt
(wrap-evt
always-evt
(lambda (_)
(start-atomic)
(remove-timer v timer)
(end-atomic)
(timed-val v)))))))))]
[timer-first-wait
(lambda (timer peek?)
(let ([rb (unbox timer)])
(and (not (null? rb))
(wrap-evt
(timed-alarm-evt (rbtree-min (unbox timer)))
(lambda (_) #f)))))]
[make-event-choice
(lambda (peek? sync?)
(choice-evt
(wrap-evt (semaphore-peek-evt newly-posted-sema)
(lambda (_) #f))
(or (first hi peek?)
(timer-first-ready timer peek?)
(first refresh peek?)
(first med peek?)
(and (not peek?)
sync?
;; before going with low-priority events,
;; make sure we're sync'ed up with the
;; GUI platform's event queue:
(platform-queue-sync)
(first med peek?))
(first lo peek?)
(timer-first-wait timer peek?)
;; nothing else ready...
never-evt)))])
(case-lambda
[(v)
;; Enqueue
(start-atomic)
(let ([val (cdr v)])
(case (car v)
[(lo) (enqueue val lo)]
[(refresh) (enqueue val refresh)]
[(med) (enqueue val med)]
[(hi) (enqueue val hi)]
[(timer-add)
(set! timer-counter (add1 timer-counter))
(set-timed-id! val timer-counter)
(set-box! timer
(rbtree-insert
timed-compare
val
(unbox timer)))
(check-done)]
[(timer-remove) (remove-timer val timer)]
[(frame-add) (hash-set! frames val #t) (check-done)]
[(frame-remove) (hash-remove! frames val) (check-done)]))
(semaphore-post newly-posted-sema)
(set! newly-posted-sema (make-semaphore))
(check-done)
(end-atomic)]
[()
;; Dequeue as evt
(start-atomic)
(begin0
(make-event-choice #f #t)
(end-atomic))]
[(only-refresh? peek? sync?)
(start-atomic)
(begin0
(cond
[only-refresh?
;; Dequeue only refresh event
(or (first refresh peek?) never-evt)]
[else
(make-event-choice peek? sync?)])
(end-atomic))]))))
frames
(semaphore-peek-evt done-sema)
#f
done-sema
0
(make-hash)
0)]
[cb-box (box #f)])
(parameterize ([current-cb-box cb-box])
(scheme_add_managed (current-custodian)
e
shutdown-eventspace!
cb-box ; retain callback until it's called
0))
e)))
(define main-eventspace (make-eventspace* (current-thread)))
(define current-eventspace (make-parameter main-eventspace))
;; So we can get from a thread to the eventspace that
;; it handles (independent of the `current-eventspace'
;; parameter):
(define handler-thread-of (make-thread-cell #f))
(thread-cell-set! handler-thread-of main-eventspace)
(define make-new-eventspace
(let ([make-eventspace
(lambda ()
(define pause (make-semaphore))
(define break-paramz (current-break-parameterization))
(define es
(make-eventspace*
(parameterize-break
#f ; disable breaks until we're in the yield loop
(thread
(lambda ()
(sync pause) ; wait until `es' has a value
(thread-cell-set! handler-thread-of es)
(current-eventspace es)
(let loop ()
(call-with-continuation-prompt
(lambda ()
;; re-enable breaks (if they are supposed to be enabled):
(call-with-break-parameterization
break-paramz
(lambda ()
;; yield; any abort (including a break exception)
;; will get caught and the loop will yield again
(yield (make-semaphore))))))
(loop)))))))
(semaphore-post pause) ; `es' has a value
es)])
make-eventspace))
(define (queue-event eventspace thunk [level 'med])
((eventspace-queue-proc eventspace) (cons level thunk)))
(define (queue-refresh-event eventspace thunk)
((eventspace-queue-proc eventspace) (cons 'refresh thunk)))
(define dispatch-event-prompt (make-continuation-prompt-tag))
(define dispatch-event-key (gensym))
(define (really-dispatch-event e)
(let ([b (continuation-mark-set-first
#f
dispatch-event-key
#f
dispatch-event-prompt)])
(unless b
(error 'default-event-dispatch-handler
"not in an event-dispatch context"))
(let ([thunk (atomically
(begin0
(unbox b)
(set-box! b #f)))])
(unless thunk
(error 'default-event-dispatch-handler
"event in current context was already dispatched"))
(thunk))))
(define event-dispatch-handler (make-parameter really-dispatch-event))
(define (handle-event thunk e)
(call-with-continuation-prompt ; to delimit continuations
(lambda ()
(call-with-continuation-prompt ; to delimit search for dispatch-event-key
(lambda ()
;; communicate the thunk to `really-dispatch-event':
(let ([b (box thunk)])
;; use the event-dispatch handler:
(with-continuation-mark dispatch-event-key b
((event-dispatch-handler) e))
;; if the event-dispatch handler doesn't chain
;; to the original one, then do so now:
(when (unbox b)
(set-box! b #f)
(thunk))))
dispatch-event-prompt))))
(define yield
(case-lambda
[()
(let ([e (current-eventspace)])
(if (eq? (current-thread) (eventspace-handler-thread e))
(let ([v (sync/timeout 0 ((eventspace-queue-proc e)))])
(if v
(begin (handle-event v e) #t)
#f))
#f))]
[(evt)
(unless (or (evt? evt)
(eq? evt 'wait))
(raise-type-error 'yield "evt or 'wait" evt))
(let* ([e (current-eventspace)]
[handler? (eq? (current-thread) (eventspace-handler-thread e))])
(cond
[(and (eq? evt 'wait)
(not handler?))
#t]
[else
(define (wait-now)
(if handler?
(sync (if (eq? evt 'wait)
(wrap-evt e (lambda (_) #t))
evt)
(handle-evt ((eventspace-queue-proc e))
(lambda (v)
(when v (handle-event v e))
(yield evt))))
(sync evt)))
(if (evt? evt)
;; `yield' is supposed to return immediately if the
;; event is already ready:
(sync/timeout wait-now evt)
(wait-now))]))]))
(define (yield/no-sync)
(let ([e (current-eventspace)])
(when (eq? (current-thread) (eventspace-handler-thread e))
(let ([v (sync/timeout 0 ((eventspace-queue-proc e) #f #f #f))])
(if v
(begin (handle-event v e) #t)
#f)))))
(define yield-refresh
(lambda ()
(let ([e (current-eventspace)])
(and (eq? (current-thread) (eventspace-handler-thread e))
(let loop ([result #f])
(let ([v (sync/timeout 0 ((eventspace-queue-proc e) #t #f #t))])
(if v
(begin
(handle-event v e)
(loop #t))
result)))))))
(define (eventspace-event-evt [e (current-eventspace)])
(unless (eventspace? e)
(raise-type-error 'eventspace-event-evt "eventspace" e))
(wrap-evt ((eventspace-queue-proc e) #f #t #t)
(lambda (_) e)))
(define (main-eventspace? e)
(eq? e main-eventspace))
(define (queue-callback thunk [high? #t])
(let ([es (current-eventspace)])
(when (eventspace-shutdown? es)
(error 'queue-callback "eventspace is shutdown: ~e" es))
(queue-event es thunk (cond
[(not high?) 'lo]
[(eq? high? middle-queue-key) 'med]
[else 'hi]))))
(define middle-queue-key (gensym 'middle))
(define (add-timer-callback cb es)
;; in atomic mode
(queue-event es cb 'timer-add))
(define (remove-timer-callback cb es)
;; in atomic mode
(unless (eventspace-shutdown? es)
(queue-event es cb 'timer-remove)))
(define (register-frame-shown f on?)
(queue-event (send f get-eventspace) f (if on?
'frame-add
'frame-remove)))
(define (get-top-level-windows [e (current-eventspace)])
;; called in event-pump thread
(hash-map (eventspace-frames-hash e)
(lambda (k v) k)))
(define (other-modal? win [e #f] [ignore-win #f])
;; called in atomic mode in eventspace's thread
(and
;; deliver mouse-motion events even if a modal window
;; is open
(or (not e)
(not (or (send e leaving?)
(send e entering?)
(send e moving?))))
;; for any other kind of mouse or key event, deliver only
;; if no model dialog is open
(let ([es (send win get-eventspace)])
(or (positive? (eventspace-external-modal es))
(let loop ([frames (get-top-level-windows es)])
(and (pair? frames)
(let ([status (if (eq? ignore-win (car frames))
#f
(send (car frames) frame-relative-dialog-status win))])
(case status
[(#f) (loop (cdr frames))]
[(same) (loop (cdr frames))]
[(other) #t]))))))))
(define (eventspace-adjust-external-modal! es amt)
(atomically
(set-eventspace-external-modal!
es
(+ (eventspace-external-modal es) amt))))
(define (queue-quit-event)
;; called in event-pump thread
(queue-event main-eventspace (application-quit-handler) 'med))
(define (queue-prefs-event)
;; called in event-pump thread
(queue-event main-eventspace (application-pref-handler) 'med))
(define (queue-about-event)
;; called in event-pump thread
(queue-event main-eventspace (application-about-handler) 'med))
(define (queue-file-event file)
;; called in event-pump thread
(queue-event main-eventspace (lambda ()
((application-file-handler) file))
'med))
(define (queue-start-empty-event)
;; called in event-pump thread
(queue-event main-eventspace (application-start-empty-handler)
'med))
(define (begin-busy-cursor)
(let ([e (current-eventspace)])
(atomically
(set-eventspace-wait-cursor-count!
e
(add1 (eventspace-wait-cursor-count e)))
(when (= (eventspace-wait-cursor-count e) 1)
(for ([e (in-list (get-top-level-windows))])
(send e set-wait-cursor-mode #t))))))
(define (end-busy-cursor)
(let ([e (current-eventspace)])
(atomically
(set-eventspace-wait-cursor-count!
e
(sub1 (eventspace-wait-cursor-count e)))
(when (zero? (eventspace-wait-cursor-count e))
(for ([e (in-list (get-top-level-windows))])
(send e set-wait-cursor-mode #f))))))
(define (is-busy?) (positive? (eventspace-wait-cursor-count (current-eventspace))))
;; ----------------------------------------
;; Before exiting, wait until frames are closed, etc.:
(executable-yield-handler
(let ([old-eyh (executable-yield-handler)])
(lambda (v)
(yield main-eventspace)
(old-eyh v))))
;; When using a REPL in a thread that has an eventspace,
;; yield to events when the port would block.
(current-get-interaction-input-port
(let ([orig (current-get-interaction-input-port)])
(lambda ()
(let ([e (thread-cell-ref handler-thread-of)])
(if e
(let ([filter (lambda (v)
(cond
[(eq? v 0) (yield) 0]
[(evt? v)
(parameterize ([current-eventspace e])
(yield))
(choice-evt v
(wrap-evt (eventspace-event-evt e)
(lambda (_) 0)))]
[else v]))])
(filter-read-input-port
(orig)
(lambda (str v)
(filter v))
(lambda (s skip evt v)
(filter v))))
(orig))))))