Created
November 9, 2019 12:28
-
-
Save mflatt/1c9c367b4fe5bdb86e99aa69b9f55890 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#lang racket/base | |
(require ffi/unsafe | |
racket/class | |
ffi/unsafe/alloc | |
ffi/unsafe/try-atomic | |
ffi/unsafe/schedule | |
"utils.rkt" | |
"types.rkt" | |
"const.rkt" | |
"key.rkt" | |
"wndclass.rkt" | |
"../../lock.rkt" | |
"../common/queue.rkt") | |
(provide (protect-out win32-start-event-pump) | |
;; from common/queue: | |
current-eventspace | |
queue-event | |
queue-refresh-event | |
yield) | |
;; ------------------------------------------------------------ | |
;; Win32 event pump | |
(define _LPMSG _pointer) | |
(define-user32 GetQueueStatus (_wfun _UINT -> _DWORD)) | |
(define-user32 GetMessageW (_wfun _LPMSG _HWND _UINT _UINT -> _BOOL)) | |
(define-user32 PeekMessageW (_wfun _LPMSG _HWND _UINT _UINT _UINT -> _BOOL)) | |
(define-user32 TranslateMessage (_wfun _LPMSG -> _BOOL)) | |
(define-user32 DispatchMessageW (_wfun _LPMSG -> _LRESULT)) | |
(define-user32 PostQuitMessage (_wfun _int -> _void)) | |
(define-user32 EnumThreadWindows (_wfun _DWORD _fpointer _LPARAM -> _BOOL)) | |
(define-user32 GetWindow (_wfun _HWND _UINT -> _HWND)) | |
(define-kernel32 GetCurrentThreadId (_wfun -> _DWORD)) | |
(define-user32 GetAncestor (_wfun _HWND _UINT -> _HWND)) | |
(define-user32 IsChild (_wfun _HWND _HWND -> _BOOL)) | |
(define _enum_proc (_wfun #:atomic? #t _HWND _LPARAM -> _BOOL)) | |
(define free-msg | |
((deallocator) | |
(lambda (msg) | |
(free msg)))) | |
(define malloc-msg | |
((allocator free-msg) | |
(lambda () | |
(define p (malloc _MSG 'raw)) | |
(cpointer-push-tag! p 'MSG) | |
p))) | |
(define (events-ready?) | |
;; Check for events only since the last PeekMessage: | |
(not (zero? (LOWORD (GetQueueStatus QS_ALLINPUT))))) | |
(define (install-wakeup fds) | |
(pre-event-sync #t) | |
(unsafe-poll-ctx-eventmask-wakeup fds QS_ALLINPUT)) | |
(set-check-queue! events-ready?) | |
(set-queue-wakeup! install-wakeup) | |
(define other-peek-evt (make-semaphore)) | |
(define peek-other-peek-evt (semaphore-peek-evt other-peek-evt)) | |
(define (message-dequeue es hwnd) | |
;; Called in the eventspace for hwnd: | |
(let ([t (eventspace-extra-table es)] | |
[id (cast hwnd _HWND _intptr)]) | |
(atomically (hash-remove! t id)) | |
(let ([msg (malloc-msg)]) | |
(let loop () | |
(let ([v (PeekMessageW msg hwnd 0 0 PM_REMOVE)]) | |
;; Since we called PeekMeessage in a thread other than the | |
;; event-pump thread, set `other-peek-evt' so the pump | |
;; knows to check again. | |
(unless (sync/timeout 0 peek-other-peek-evt) | |
(semaphore-post other-peek-evt)) | |
;; Now handle the event: | |
(when v | |
(unless (generates-key-event? (cast msg _pointer _MSG-pointer)) | |
(TranslateMessage msg)) | |
(call-as-nonatomic-retry-point | |
(lambda () | |
;; in atomic mode: | |
(DispatchMessageW msg))) | |
;; Maybe there's another event for this window: | |
(loop)))) | |
(free-msg msg)))) | |
(define (queue-message-dequeue es hwnd) | |
;; in atomic mode | |
(let ([t (eventspace-extra-table es)] | |
[id (cast hwnd _HWND _intptr)]) | |
(unless (hash-ref t id #f) | |
(hash-set! t id #t) | |
(queue-event es (lambda () (message-dequeue es hwnd)))))) | |
;; For use only in the event-pump thread: | |
(define msg (malloc-msg)) | |
(define known-hwnds (make-hash)) | |
(define (check-window-event hwnd data) | |
(hash-set! known-hwnds hwnd #t) | |
;; in atomic mode | |
(let* ([root (let loop ([hwnd hwnd]) | |
(let ([p (GetWindow hwnd GW_OWNER)]) | |
(if p | |
(loop p) | |
hwnd)))] | |
[wx (any-hwnd->wx root)]) | |
(if wx | |
;; One of our windows, so make sure its eventspace | |
;; asks for the message: | |
(let ([v (PeekMessageW msg hwnd 0 0 PM_NOREMOVE)]) | |
(when v | |
(queue-message-dequeue (send wx get-eventspace) | |
hwnd))) | |
;; Not our window, so dispatch any available events | |
(let loop () | |
(let ([v (PeekMessageW msg hwnd 0 0 PM_REMOVE)]) | |
(when v | |
(TranslateMessage msg) | |
(DispatchMessageW msg) | |
(loop))))) | |
#t)) | |
(define check_window_event (function-ptr check-window-event _enum_proc)) | |
(define minus-one (cast -1 _intptr _HWND)) | |
(define (dispatch-all-ready) | |
;; in atomic mode | |
(pre-event-sync #f) | |
(clean-up-destroyed) | |
;; Windows uses messages above #x0400 to hilite items in the task | |
;; bar, implement input methods (such as pinyin input), etc. In any | |
;; case, these messages won't be handled by us, so they can't | |
;; trigger callbacks. Also, handle messages without a window. | |
(let loop () | |
(let ([v (or (PeekMessageW msg #f WM_USER #xFFFF PM_REMOVE) | |
(PeekMessageW msg minus-one 0 0 PM_REMOVE))]) | |
(when v | |
(TranslateMessage msg) | |
(DispatchMessageW msg) | |
(loop)))) | |
;; Per-window checking lets us put an event in the right | |
;; eventspace: | |
(hash-clear! known-hwnds) | |
(EnumThreadWindows (GetCurrentThreadId) check_window_event 0) | |
;; Just in case, double-check for messages to windows that we | |
;; don't recognize from enumeration: | |
(let loop () | |
(let ([v (PeekMessageW msg #f 0 0 PM_NOREMOVE)]) | |
(when v | |
(let ([hwnd (MSG-hwnd msg)]) | |
(unless (for/or ([k (in-hash-keys known-hwnds)]) | |
(IsChild k hwnd)) | |
(when (PeekMessageW msg hwnd 0 0 PM_REMOVE) | |
(TranslateMessage msg) | |
(DispatchMessageW msg) | |
(loop)))))))) | |
(define (win32-start-event-pump) | |
(thread (lambda () | |
(let loop () | |
(unless (let ([any-tasks? (sync/timeout 0 boundary-tasks-ready-evt)]) | |
(sync/timeout (and any-tasks? (* sometimes-delay-msec 0.001)) | |
queue-evt | |
other-peek-evt | |
(if any-tasks? | |
(wrap-evt (system-idle-evt) | |
(lambda (v) #f)) | |
boundary-tasks-ready-evt))) | |
(atomically (pre-event-sync #t))) | |
(as-entry dispatch-all-ready) | |
(loop))))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment