orion-wm/src/manager.scm

359 lines
11 KiB
Scheme

(define-record-type wm :wm
(make-wm type in-channel out-channel internal-out-channel
dpy window colormap options
clients current-client)
wm?
(type wm:type)
(in-channel wm:in-channel)
(out-channel wm:out-channel)
(internal-out-channel wm:internal-out-channel)
(dpy wm:dpy)
(window wm:window)
(colormap wm:colormap)
(options wm:options)
(clients wm:clients set-wm:clients!)
(current-client wm:current-client set-wm:current-client!))
(define wm-clients wm:clients)
(define wm-current-client wm:current-client)
(define-enumerated-type manager-type :manager-type
manager-type? manager-types manager-type-name manager-type-index
(split switch move))
(define (manager-name type)
(cond
((eq? type (manager-type split)) "split-wm")
((eq? type (manager-type switch)) "switch-wm")
((eq? type (manager-type move)) "move-wm")))
(define focus-policy '(enter click)) ;; TODO: -> options
(define (create-wm dpy parent options children
type options-spec out-channel fun)
(let* ((wa (get-window-attributes dpy parent))
(main-window
(create-simple-window dpy parent 0 0 (window-attribute:width wa)
(window-attribute:height wa)
0 (white-pixel dpy) (black-pixel dpy)))
(colormap (create-colormap dpy main-window
(window-attribute:visual wa)
(colormap-alloc none)))
(in-channel (make-channel))
(internal-out-channel (make-channel))
(wm (make-wm type in-channel out-channel internal-out-channel
dpy main-window colormap
(create-options dpy colormap options-spec options)
'() #f)))
;; set properties ************************************************
(set-wm-name! dpy main-window
(string-list->property (list (manager-name type))))
;; icon ??
;; size-hints ??
(set-wm-hints! dpy main-window
(make-wm-hint-alist (input? #t)))
;; class-hint ??
(set-wm-protocols! dpy main-window
(list (intern-atom dpy "WM_TAKE_FOCUS" #t)))
;; TODO: Colormaps
;; spawn handlers ************************************************
(spawn* (lambda (release)
(call-with-event-channel
(wm:dpy wm) (wm:window wm)
(event-mask structure-notify
enter-window
focus-change
exposure)
(lambda (event-channel)
(call-with-current-continuation
(lambda (exit)
(release)
(send internal-out-channel '(fit-windows)) ;; ??
(let loop ()
(select*
(wrap (receive-rv event-channel)
(lambda (xevent)
(handle-xevent wm exit xevent)))
(wrap (receive-rv (wm:in-channel wm))
(lambda (msg)
(handle-external-message wm exit msg))))
(loop))))))
(free-colormap dpy colormap)))
(for-each (lambda (window)
(wm-manage-window wm window))
children)
(fun wm internal-out-channel)))
(define (handle-xevent wm exit xevent)
(let ((main-window (wm:window wm))
(dpy (wm:dpy wm))
(internal-out-channel (wm:internal-out-channel wm))
(type (any-event-type xevent)))
(cond
((expose-event? xevent)
(send internal-out-channel '(draw-main-window)))
((configure-event? xevent)
(send internal-out-channel '(fit-windows)))
;; the manager got the focus (as a client)
((client-message-event? xevent)
(let* ((p (client-message-event-property xevent))
(type (property:type p)))
(if (equal? type (intern-atom dpy "WM_PROTOCOLS" #t))
(let ((name (car (property:data p)))
(time (cadr (property:data p)))
(client (wm:current-client wm)))
(if (and client
(equal? name (intern-atom dpy "WM_TAKE_FOCUS" #t)))
(handle-external-message wm exit
(list 'select-client client time)))
))))
)))
(define (handle-external-message wm exit msg)
(let ((internal-out-channel (wm:internal-out-channel wm))
(dpy (wm:dpy wm)))
(case (car msg)
((manage-window)
(let* ((window (second msg))
(client (create-client wm window)))
(set-wm:clients! wm (cons client (wm:clients wm)))
(send internal-out-channel
(list 'init-client client (third msg)))
(send internal-out-channel (list 'fit-client client))
;; sync ??
(map-window dpy window)
(send internal-out-channel (list 'update-client-state client))))
((unmanage-window)
(let* ((window (second msg))
(client (find (lambda (c)
(eq? window (client:window c)))
(wm:clients wm))))
(if client
(begin
(reparent-to-root dpy window)
(handle-external-message wm exit
(list 'deinit-client client))))))
((destroy-manager)
;; (send internal-out-channel '(deinit-manager))
;; sync ??
(destroy-window dpy (wm:window wm)))
((deinit-client)
(let ((client (second msg)))
(set-wm:clients! wm (filter (lambda (c) (not (eq? c client)))
(wm:clients wm)))
(if (eq? (wm:current-client wm) client)
(set-wm:current-client! wm #f)) ;; select another ??
(send (wm:internal-out-channel wm) (list 'deinit-client client))
;; sync ??
(destroy-window dpy (client:client-window client))))
((select-client)
(let ((client (second msg))
(time (third msg)))
(set-wm:current-client! wm client)
(raise-window dpy (client:client-window client))
(take-focus dpy (client:window client) time)
; (for-each (lambda (c)
; (if (not (eq? c client))
; (grab-button dpy
; (button button1) (state-set)
; (client:client-window c) #f
; (event-mask button-press)
; (grab-mode async) (grab-mode async)
; none none)))
; (wm:clients wm))
))
)))
(define (wm-deinit-client wm client)
(mdisplay "manager deinit-client\n")
(send (wm:in-channel wm) (list 'deinit-client client)))
;; *** external messages *********************************************
(define (wm-manage-window wm window . rect)
(send (wm:in-channel wm)
(list 'manage-window window
(if (null? rect)
#f
(car rect))))
;; sync ??
)
(define (wm-unmanage-window wm window)
(send (wm:in-channel wm) (list 'unmanage-window window)))
(define (wm-select-client wm client time)
(send (wm:in-channel wm) (list 'select-client client time)))
(define (destroy-wm wm)
(send (wm:in-channel wm) '(destroy-manager)))
(define (send-root-drop wm window x y)
(send (wm:out-channel wm) (list 'root-drop window x y)))
;; *** client ********************************************************
(define-record-type client :client
(make-client window client-window data)
client?
(window client:window)
(client-window client:client-window)
(data client:data set-client:data!))
(define (create-client wm window)
(mdisplay "creating client for " window "\n")
(let* ((dpy (wm:dpy wm))
(client-window (create-simple-window dpy (wm:window wm)
0 0
(window-width dpy window)
(window-height dpy window)
0
(white-pixel dpy)
(black-pixel dpy)))
(client (make-client window client-window #f)))
(reparent-window dpy window client-window 0 0)
(create-client-handler wm client)
client))
(define (create-client-handler wm client)
(spawn*
(lambda (release)
(call-with-event-channel
(wm:dpy wm) (client:client-window client)
(event-mask exposure
enter-window
button-press
structure-notify
focus-change)
(lambda (client-window-channel)
(call-with-event-channel
(wm:dpy wm) (client:window client)
(event-mask property-change
structure-notify)
(lambda (client-channel)
(call-with-current-continuation
(lambda (exit)
(release)
(let loop ()
(select*
(wrap (receive-rv client-window-channel)
(lambda (xevent)
(handle-client-window-xevent wm exit client xevent)))
(wrap (receive-rv client-channel)
(lambda (xevent)
(handle-client-xevent wm exit client xevent))))
(loop)))))))))))
(define (handle-client-window-xevent wm exit client xevent)
(let ((type (any-event-type xevent))
(internal-out-channel (wm:internal-out-channel wm))
(dpy (wm:dpy wm)))
(cond
((expose-event? xevent)
(send internal-out-channel
(list 'draw-client-window client)))
((configure-event? xevent)
(send internal-out-channel
(list 'fit-client client)))
((or (focus-change-event? xevent) (circulate-event? xevent))
;; TODO: look at mode? or maybe only look at focus-in of the
;; client, because the client-window never gets the focus
;; anyway.
(if (window-exists? dpy (client:window client)) ;; TODO: not perfect
(send internal-out-channel
(list 'update-client-state client))))
((eq? (event-type enter-notify) type)
(if (memq 'enter focus-policy)
(wm-select-client wm client (crossing-event-time xevent))))
((eq? (event-type button-press) type)
(if (memq 'click focus-policy)
(wm-select-client wm client (button-event-time xevent))))
((destroy-window-event? xevent)
(mdisplay "client-window destroyed\n")
(exit)))))
(define (handle-client-xevent wm exit client xevent)
(let ((type (any-event-type xevent))
(internal-out-channel (wm:internal-out-channel wm))
(dpy (wm:dpy wm)))
(cond
((property-event? xevent)
(let ((name (get-atom-name (property-event-display xevent)
(property-event-atom xevent))))
(cond
((equal? "WM_NAME" name)
(send internal-out-channel
(list 'update-client-state client)))
;; TODO: respect NORMAL_HINTS change
)))
((configure-event? xevent)
;; TODO: we have to prevent this event if changed the size on our own.
;; --> XReconfigureWMWindow ??
;;(send internal-out-channel (list 'fit-client-window client))
#t)
((reparent-event? xevent) #t)
; (if (or (not (window-exists? dpy (client:window client)))
; (not (eq? (client:client-window client)
; (window-parent dpy (client:window client)))))
; (begin
; (mdisplay "manager " (wm:type wm) " reparented client\n")
; (wm-deinit-client wm client)
; (exit)))
;; TODO: withdrawn-state etc.
((destroy-window-event? xevent)
(mdisplay "destroy-window client\n")
(wm-deinit-client wm client)
(exit))
)))
;; *** client names **************************************************
(define client-name
(let ((names '()) ;; (window oname name)
(lock (make-lock)))
(lambda (dpy client)
(let* ((w (client:window client))
(cname (let* ((p (get-wm-name dpy w))
(l (if p (property->string-list p) '())))
(if (null? l)
"<untitled>"
(car l)))))
(with-lock lock
(lambda ()
(let ((name? (let ((p (assq w names)))
(and p (equal? (cadr p) cname)
(caddr p)))))
(set! names
(filter (lambda (e)
(and (not (eq? (car e) w))
(window-exists? dpy (car e))))
names))
(let ((name (if name? name?
(unique-name cname
(map caddr names)))))
(set! names (cons (list w cname name) names))
name))))))))
(define (unique-name name names)
(if (not (member name names))
name
(let loop ((i 1))
(let ((n (string-append name "<" (number->string i) ">")))
(if (member n names)
(loop (+ i 1))
n)))))