540 lines
18 KiB
Scheme
540 lines
18 KiB
Scheme
(define (mdisplay . args)
|
|
(for-each display args)
|
|
(force-output (current-output-port)))
|
|
|
|
(define (:optional args default)
|
|
(if (null? args)
|
|
default
|
|
(car args)))
|
|
|
|
(define (assq/false key alist)
|
|
(let ((p (assq key alist)))
|
|
(and p (cdr p))))
|
|
|
|
(define (flatten lists)
|
|
(fold-right append
|
|
'()
|
|
lists))
|
|
|
|
(define (list-diff list1 list2)
|
|
;; returns every member in list2 that is not in list1
|
|
(filter (lambda (e)
|
|
(not (member e list1)))
|
|
list2))
|
|
|
|
;; *** cml utilities *************************************************
|
|
|
|
(define (select* . list) (select list))
|
|
|
|
(define (make-sync-point)
|
|
(make-placeholder))
|
|
|
|
(define (sync-point-release sp)
|
|
(placeholder-set! sp #t))
|
|
|
|
(define (sync-point-wait sp)
|
|
(placeholder-value sp))
|
|
|
|
(define (spawn* id . fun)
|
|
(let ((id (if (null? fun) "unnamed" id))
|
|
(fun (if (null? fun) id (car fun))))
|
|
(let ((sp (make-sync-point)))
|
|
(spawn (lambda ()
|
|
(with-handler
|
|
(lambda (condition punt)
|
|
(mdisplay "condition in " id ":\n " condition "\n")
|
|
(punt))
|
|
(lambda ()
|
|
(let ((res (fun (lambda () (sync-point-release sp)))))
|
|
(mdisplay "thread " id " returned: " res "\n"))
|
|
))))
|
|
(sync-point-wait sp))))
|
|
|
|
(define (with-lock lock thunk)
|
|
(obtain-lock lock)
|
|
(let ((r (thunk)))
|
|
(release-lock lock)
|
|
r))
|
|
|
|
;; *** option utilities **********************************************
|
|
|
|
(define-record-type options :options
|
|
(make-options dpy colormap option-alist value-alist type-alist default-alist)
|
|
options?
|
|
(dpy options:dpy)
|
|
(colormap options:colormap)
|
|
(option-alist options:option-alist set-options:option-alist!)
|
|
(value-alist options:value-alist set-options:value-alist!)
|
|
(type-alist options:type-alist set-options:type-alist!)
|
|
(default-alist options:default-alist))
|
|
|
|
(define (create-options dpy colormap spec options)
|
|
(let ((option-alist (map (lambda (s)
|
|
(let* ((n (first s))
|
|
(op (assq n options)))
|
|
(cons n (if op (cdr op) (third s)))))
|
|
spec))
|
|
(value-alist '())
|
|
(type-alist (map (lambda (s)
|
|
(cons (first s) (second s)))
|
|
spec))
|
|
(default-alist (map (lambda (s)
|
|
(cons (first s) (third s)))
|
|
spec)))
|
|
(for-each (lambda (name.option name.type)
|
|
(allocate-option dpy colormap (car name.option)
|
|
(cdr name.type) (cdr name.option)))
|
|
option-alist type-alist)
|
|
(make-options dpy colormap option-alist value-alist type-alist
|
|
default-alist)))
|
|
|
|
;; if the colormap gets freed, then the colors don't have to
|
|
(define (free-options options free-colors?)
|
|
(for-each (lambda (n.v)
|
|
(let* ((name (car n.v))
|
|
(value (cdr n.v))
|
|
(type (assq/false name (options:type-alist options))))
|
|
(cond
|
|
((eq? (option-type font) type)
|
|
(free-font (options:dpy options) value))
|
|
((eq? (option-type color) type)
|
|
(free-colors (options:dpy options) (options:colormap options)
|
|
(list value) 0))
|
|
((eq? (option-type colors) type)
|
|
(free-colors (options:dpy options) (options:colormap options)
|
|
value 0)))))
|
|
(options:value-alist options)))
|
|
|
|
(define (get-option-value options name)
|
|
(let ((p (assq name (options:value-alist options))))
|
|
(if p
|
|
(cdr p)
|
|
(let ((op (assq name (options:option-alist options)))
|
|
(tp (assq name (options:type-alist options))))
|
|
(if (or (not op) (not tp))
|
|
(error "option not defined" name)
|
|
(let ((v (allocate-option (options:dpy options)
|
|
(options:colormap options)
|
|
name (cdr tp) (cdr op))))
|
|
(set-options:value-alist! options
|
|
(cons (cons name v)
|
|
(options:value-alist options)))
|
|
v))))))
|
|
|
|
(define (get-option options name)
|
|
(let ((vp (assq name (options:option-alist options))))
|
|
(if (not vp)
|
|
(error "option not defined" name)
|
|
(cdr vp))))
|
|
|
|
(define (set-option! options name def)
|
|
(set-options:option-alist! options
|
|
(cons (cons name def)
|
|
(filter (lambda (n.o)
|
|
(not (eq? (car n.o) name)))
|
|
(options:option-alist options))))
|
|
(set-options:value-alist! options
|
|
(filter (lambda (n.v)
|
|
(not (eq? (car n.v) name)))
|
|
(options:value-alist options))))
|
|
|
|
(define (get-options options)
|
|
(options:option-alist options))
|
|
|
|
(define (get-options-diff options)
|
|
(list-diff (options:default-alist options)
|
|
(options:option-alist options)))
|
|
|
|
(define-enumerated-type option-type :option-type
|
|
option-type? option-types option-type-name option-type-index
|
|
(int number inexact exact string symbol font color colors
|
|
boolean symbol-list keys keys-list sexp))
|
|
|
|
(define-syntax define-options-spec
|
|
(syntax-rules
|
|
()
|
|
((define-options-spec id (name type default) ...)
|
|
(define id (list (list (quote name) (option-type type) default)
|
|
...)))))
|
|
|
|
(define (options-spec-union spec1 spec2)
|
|
(append spec1 spec2))
|
|
|
|
(define (allocate-option dpy colormap name type def)
|
|
(let ((check (lambda (value pred)
|
|
(if (not (pred value))
|
|
(error "wrong type argument" value) ;; TODO: other error??
|
|
value))))
|
|
(cond
|
|
((eq? type (option-type int)) (check def integer?))
|
|
((eq? type (option-type number)) (check def number?))
|
|
((eq? type (option-type inexact)) (check def inexact?))
|
|
((eq? type (option-type exact)) (check def inexact?))
|
|
((eq? type (option-type string)) (check def string?))
|
|
((eq? type (option-type symbol)) (check def symbol?))
|
|
((eq? type (option-type font))
|
|
(check (load-query-font dpy def) (lambda (v) v)))
|
|
((eq? type (option-type color))
|
|
(let ((c (alloc-named-color dpy colormap def)))
|
|
(check (and c (color:pixel c)) (lambda (v) v))))
|
|
((eq? type (option-type colors))
|
|
(and (check def list?)
|
|
(map (lambda (c) (allocate-option dpy colormap name
|
|
(option-type color) c))
|
|
def)))
|
|
((eq? type (option-type boolean))
|
|
(check def boolean?)) ;; maybe allow 'yes 'no ??
|
|
((eq? type (option-type symbol-list))
|
|
(and (check def list?)
|
|
(map (lambda (s) (allocate-option dpy colormap name
|
|
(option-type symbol) s))
|
|
def)))
|
|
((eq? type (option-type keys))
|
|
(let ((keys (string->keys dpy def)))
|
|
(check keys (lambda (x) x))))
|
|
((eq? type (option-type keys-list))
|
|
(and (check def list?)
|
|
(map (lambda (s) (allocate-option dpy colormap name
|
|
(option-type keys) s))
|
|
def)))
|
|
((eq? type (option-type sexp))
|
|
def)
|
|
(else (error "option type not implemented" name type)))))
|
|
|
|
;; *** keys utilities ************************************************
|
|
|
|
(define-record-type key :key
|
|
(make-key modifiers keycode)
|
|
key?
|
|
(modifiers key:modifiers)
|
|
(keycode key:keycode))
|
|
|
|
(define (string->keys dpy s)
|
|
(let* ((keys-s (split-space s))
|
|
(keys (map (lambda (s) (string->key dpy s)) keys-s)))
|
|
(and (not (memq #f keys)) keys)))
|
|
|
|
(define (is-uppercase? keysym)
|
|
(let ((low.up (convert-case keysym)))
|
|
(not (equal? (car low.up) keysym))))
|
|
|
|
(define (string->key dpy s)
|
|
(let* ((l (reverse (split-minus s)))
|
|
(mod-strings (reverse (cdr l)))
|
|
(key-string (car l))
|
|
(keysym (string->keysym key-string))
|
|
(keycode (keysym->keycode dpy keysym))
|
|
(modifiers (enum-set-union (if (is-uppercase? keysym)
|
|
(state-set shift)
|
|
(state-set))
|
|
(strings->modifiers mod-strings))))
|
|
(and modifiers keycode
|
|
(make-key modifiers keycode))))
|
|
|
|
(define split-minus (infix-splitter (rx "-")))
|
|
(define split-space (infix-splitter (rx " ")))
|
|
|
|
(define (strings->modifiers str-list)
|
|
(let ((l (map string->modifiers str-list)))
|
|
(and (not (memq #f l))
|
|
(fold enum-set-union
|
|
(state-set)
|
|
l))))
|
|
|
|
(define (string->modifiers s)
|
|
(cond
|
|
((equal? s "C") (state-set control))
|
|
((equal? s "M") (state-set mod1))
|
|
((equal? s "M1") (state-set mod1))
|
|
((equal? s "M2") (state-set mod2))
|
|
((equal? s "M3") (state-set mod3))
|
|
((equal? s "M4") (state-set mod4))
|
|
((equal? s "M5") (state-set mod5))
|
|
((equal? s "S") (state-set shift)) ;; needed?
|
|
(else #f)))
|
|
|
|
;; *** xlib utilities ************************************************
|
|
|
|
(define (reparent-to-root dpy window)
|
|
(let ((r (root-rectangle dpy window)))
|
|
(reparent-window dpy window (window-root dpy window)
|
|
(rectangle:x r) (rectangle:y r))))
|
|
|
|
(define (window-path dpy window)
|
|
(cons window
|
|
(let ((p (window-parent dpy window)))
|
|
(if (zero? p)
|
|
'()
|
|
(window-path dpy p)))))
|
|
|
|
(define (window-viewable? dpy window)
|
|
(eq? (window-attribute:map-state (get-window-attributes dpy window))
|
|
(map-state is-viewable)))
|
|
|
|
(define (window-focused? dpy window)
|
|
(eq? (get-input-focus-window dpy) window))
|
|
|
|
(define (window-contains-focus? dpy window)
|
|
(or (window-focused? dpy window)
|
|
(any (lambda (child)
|
|
(window-contains-focus? dpy child))
|
|
(window-children dpy window))))
|
|
|
|
(define (take-focus dpy window time)
|
|
;; implements the TAKE_FOCUS protocol
|
|
(let* ((protocols (get-wm-protocols dpy window))
|
|
(wm-take-focus (intern-atom dpy "WM_TAKE_FOCUS" #f))
|
|
(wm-hints (get-wm-hints dpy window))
|
|
(t (and wm-hints (assq (wm-hint input?) wm-hints)))
|
|
(input? (if t (cdr t) #t)))
|
|
(let ((type (if (not (and protocols wm-take-focus
|
|
(memq wm-take-focus protocols)))
|
|
(if input?
|
|
'passive
|
|
'no-input)
|
|
(if input?
|
|
'locally-active
|
|
'globally-active))))
|
|
;; we use passive as the default (with no hints at all)
|
|
(case type
|
|
((passive)
|
|
(set-input-focus dpy window (revert-to parent) time)) ;; ??
|
|
((globally-active) #t)
|
|
((locally-active)
|
|
(send-protocol-message dpy window wm-take-focus time))
|
|
((no-focus) #f)))))
|
|
|
|
(define (send-protocol-message dpy window atom time)
|
|
(send-event dpy window #f (event-mask)
|
|
(create-client-message-event
|
|
(event-type client-message) 0 #t dpy window
|
|
(make-property (intern-atom dpy "WM_PROTOCOLS" #f)
|
|
(property-format long)
|
|
(list atom time)))))
|
|
|
|
(define (delete-window dpy window time)
|
|
(let* ((protocols (get-wm-protocols dpy window))
|
|
(wm-delete-window (intern-atom dpy "WM_DELETE_WINDOW" #f)))
|
|
(if (member wm-delete-window protocols)
|
|
(send-protocol-message dpy window wm-delete-window time)
|
|
(destroy-window dpy window))))
|
|
|
|
(define (move-resize-window* dpy window rect)
|
|
(move-resize-window dpy window
|
|
(rectangle:x rect) (rectangle:y rect)
|
|
(rectangle:width rect) (rectangle:height rect)))
|
|
|
|
(define (root-rectangle dpy win)
|
|
(let ((r (translate-coordinates dpy win (default-root-window dpy)
|
|
0 0)))
|
|
(make-rectangle (if r (car r) 0) (if r (cadr r) 0)
|
|
(window-width dpy win) (window-height dpy win))))
|
|
|
|
(define (window-rectangle dpy win)
|
|
(let ((wa (get-window-attributes dpy win)))
|
|
(make-rectangle (window-attribute:x wa) (window-attribute:y wa)
|
|
(window-attribute:width wa) (window-attribute:height wa))))
|
|
|
|
(define (clip-rectangle dpy win)
|
|
(make-rectangle 0 0 (window-width dpy win) (window-height dpy win)))
|
|
|
|
(define (draw-shadow-rectangle dpy win gc r lc dc)
|
|
(let* ((x1 (rectangle:x r))
|
|
(y1 (rectangle:y r))
|
|
(x2 (- (+ x1 (rectangle:width r)) 1))
|
|
(y2 (- (+ y1 (rectangle:height r)) 1)))
|
|
(set-gc-foreground! dpy gc lc)
|
|
(draw-lines dpy win gc (list (cons x1 y2) (cons x1 y1) (cons x2 y1))
|
|
(coord-mode origin))
|
|
(set-gc-foreground! dpy gc dc)
|
|
(draw-lines dpy win gc (list (cons x2 y1) (cons x2 y2) (cons x1 y2))
|
|
(coord-mode origin))))
|
|
|
|
(define (fill-rectangle* dpy win gc rect)
|
|
(fill-rectangle dpy win gc (rectangle:x rect) (rectangle:y rect)
|
|
(rectangle:width rect) (rectangle:height rect)))
|
|
|
|
(define (invalidate-window dpy win)
|
|
(let ((wa (get-window-attributes dpy win)))
|
|
(clear-area dpy win 0 0 (window-attribute:width wa)
|
|
(window-attribute:height wa) #t)))
|
|
|
|
(define (text-center-pos rect font-struct str)
|
|
(let* ((cs (text-extents font-struct str))
|
|
(tw (char-struct:width cs)))
|
|
(cons (floor (/ (- (rectangle:width rect) tw) 2))
|
|
(+ (floor (/ (rectangle:height rect) 2))
|
|
(font-struct:descent font-struct)))))
|
|
|
|
;; maximize-window moves and resizes the window fill as much space of
|
|
;; it's parent (or the window specified with the optional
|
|
;; argument). If there is a WM_NORMAL hint present for the window, and
|
|
;; the window has to be smaller than the parent, it is centered.
|
|
|
|
(define (maximize-window dpy window . maybe-parent)
|
|
(let ((r (apply maximal-rect/hints dpy window maybe-parent)))
|
|
(move-resize-window dpy window (rectangle:x r) (rectangle:y r)
|
|
(rectangle:width r) (rectangle:height r))))
|
|
|
|
(define (maximal-rect/hints dpy window . maybe-parent)
|
|
(let ((parent (:optional maybe-parent (window-parent dpy window))))
|
|
(let ((max-width (window-width dpy parent))
|
|
(max-height (window-height dpy parent)))
|
|
(let ((w.h (maximal-size/hints dpy window max-width max-height)))
|
|
(let ((width (car w.h))
|
|
(height (cdr w.h)))
|
|
(let ((x (quotient (- max-width width) 2))
|
|
(y (quotient (- max-height height) 2)))
|
|
(make-rectangle x y width height)))))))
|
|
|
|
(define (maximal-size/hints dpy window max-width max-height)
|
|
(let ((hints (get-wm-normal-hints dpy window))) ;; or group-leader?
|
|
(if hints
|
|
(adjust-size/hints hints max-width max-height)
|
|
(cons max-width max-height))))
|
|
|
|
(define (size-window dpy window default-size)
|
|
(let ((size (desired-size/hints dpy window default-size)))
|
|
(configure-window dpy window
|
|
(make-window-change-alist
|
|
(width (car size))
|
|
(height (cdr size))))))
|
|
|
|
(define (desired-size/hints dpy window default-size)
|
|
(let ((hints (get-wm-normal-hints dpy window))) ;; or group-leader?
|
|
(if hints
|
|
(let ((size (or (assq/false (size-hint us-size) hints)
|
|
(assq/false (size-hint size) hints)
|
|
default-size)))
|
|
(adjust-size/hints hints (car size) (cdr size)))
|
|
default-size)))
|
|
|
|
(define (desired-position/hints dpy window default-position)
|
|
(let ((hints (get-wm-normal-hints dpy window))) ;; or group-leader?
|
|
(if hints
|
|
(or (assq/false (size-hint us-position) hints)
|
|
(assq/false (size-hint position) hints)
|
|
default-position)
|
|
default-position)))
|
|
|
|
;; returns width/height pair that conform to the defined
|
|
;; aspects/resize-inc etc. in hints, and are as close as possible to
|
|
;; width/height (but never bigger).
|
|
|
|
(define (adjust-size/hints size-hints width height)
|
|
(let ((min-size-hint (assq (size-hint min-size) size-hints))
|
|
(max-size-hint (assq (size-hint max-size) size-hints))
|
|
(resize-inc-hint (assq (size-hint resize-inc) size-hints))
|
|
(aspect-hint (assq (size-hint aspect) size-hints))
|
|
(base-size-hint (assq (size-hint base-size) size-hints)))
|
|
|
|
;; respect the desired maximal size ******************************
|
|
(if max-size-hint
|
|
(let ((max-width (car (cdr max-size-hint)))
|
|
(max-height (cdr (cdr max-size-hint))))
|
|
(if (> width max-width) (set! width max-width))
|
|
(if (> height max-height) (set! height max-height))))
|
|
|
|
;; ignore the minimal size, but give a warning *******************
|
|
; (let ((hint (or min-size-hint base-size-hint))) ;; according to ICCCM
|
|
; (if hint
|
|
; (let* ((min-size (cdr hint))
|
|
; (min-width (car min-size))
|
|
; (min-height (cdr min-size)))
|
|
; (if (or (< width min-width)
|
|
; (< height min-height))
|
|
; (debug-message 1 "% has to be smaller, than the desired minimal size %."
|
|
; window hint)))))
|
|
|
|
;; respect aspect ratios *****************************************
|
|
(if aspect-hint
|
|
(let* ((base-width (if base-size-hint
|
|
(car (cdr base-size-hint))
|
|
0))
|
|
(base-height (if base-size-hint
|
|
(cdr (cdr base-size-hint))
|
|
0))
|
|
(width* (- width base-width))
|
|
(height* (- height base-height))
|
|
|
|
(ratio (/ width* height*))
|
|
(min-ratio (/ (car (car (cdr aspect-hint)))
|
|
(cdr (car (cdr aspect-hint)))))
|
|
(max-ratio (/ (car (cdr (cdr aspect-hint)))
|
|
(cdr (cdr (cdr aspect-hint)))))
|
|
(new-ratio ratio))
|
|
(if (> ratio max-ratio)
|
|
(set! new-ratio max-ratio)
|
|
(if (< ratio min-ratio)
|
|
(set! new-ratio min-ratio)))
|
|
(if (< new-ratio ratio)
|
|
(set! width* (* height* new-ratio))
|
|
(if (> new-ratio ratio)
|
|
(set! height* (/ width* new-ratio))))
|
|
(set! width (+ width* base-width))
|
|
(set! height (+ height* base-height))))
|
|
|
|
;; respect resize-incs *******************************************
|
|
(if resize-inc-hint
|
|
(let* ((width-inc (car (cdr resize-inc-hint)))
|
|
(height-inc (cdr (cdr resize-inc-hint)))
|
|
(base-size-hint (or base-size-hint min-size-hint))
|
|
(base-width (if base-size-hint
|
|
(car (cdr base-size-hint))
|
|
0))
|
|
(base-height (if base-size-hint
|
|
(cdr (cdr base-size-hint))
|
|
0)))
|
|
(set! width
|
|
(+ base-width (* width-inc (quotient (- width base-width)
|
|
width-inc))))
|
|
(set! height
|
|
(+ base-height (* height-inc (quotient (- height base-height)
|
|
height-inc))))))
|
|
|
|
;; result ********************************************************
|
|
(cons width height)))
|
|
|
|
(define (point-in-rectangle? r x y)
|
|
(and (>= x (rectangle:x r))
|
|
(>= y (rectangle:y r))
|
|
(< x (+ (rectangle:x r) (rectangle:width r)))
|
|
(< y (+ (rectangle:y r) (rectangle:height r)))))
|
|
|
|
(define (window-level dpy win)
|
|
(length (window-path dpy win)))
|
|
|
|
(define (with-prevent-events dpy window event-mask thunk)
|
|
(let* ((before (window-attribute:your-event-mask
|
|
(get-window-attributes dpy window)))
|
|
(new (enum-set-intersection before
|
|
(enum-set-negation event-mask))))
|
|
(dynamic-wind
|
|
(lambda ()
|
|
(display-select-input dpy window new))
|
|
thunk
|
|
(lambda ()
|
|
(display-select-input dpy window before)))))
|
|
|
|
(define (all-window-colormaps dpy window)
|
|
(let ((wins (cons window
|
|
(let* ((a (intern-atom dpy "WM_COLORMAP_WINDOWS" #f))
|
|
(t (intern-atom dpy "WINDOW" #f))
|
|
(p (get-full-window-property dpy window a #f t)))
|
|
(if (and p (property:data p))
|
|
(property:data p)
|
|
'())))))
|
|
(map (lambda (win)
|
|
(window-attribute:colormap (get-window-attributes dpy win)))
|
|
wins)))
|
|
|
|
(define (install-colormaps dpy window)
|
|
(for-each (lambda (c)
|
|
(install-colormap dpy c))
|
|
(all-window-colormaps dpy window)))
|
|
|
|
(define (uninstall-colormaps dpy window)
|
|
(for-each (lambda (c)
|
|
(uninstall-colormap dpy c))
|
|
(all-window-colormaps dpy window)))
|