485 lines
15 KiB
Scheme
485 lines
15 KiB
Scheme
(define-options-spec move-wm-options-spec
|
|
(titlebar-colors colors '("#aaaaaa" "#eeeeee" "#777777" "black"))
|
|
(titlebar-colors-focused colors '("#666699" "#aaaacc" "#333366" "#eeeeee"))
|
|
(titlebar-height int 18)
|
|
(titlebar-style symbol 'flat)
|
|
(font font "-*-helvetica-medium-r-normal-*-12-*-*-*-*-*-*-*")
|
|
(border-width int 3)
|
|
(corner-width int 10)
|
|
(border-style symbol 'raised) ;; raised | sunken | flat
|
|
(border-colors colors '("#333333" "#dddddd"))
|
|
)
|
|
|
|
(define (create-move-wm out-channel dpy parent options default-options
|
|
. children)
|
|
(create-wm dpy parent options (append default-options
|
|
'((focus-policy . (click))))
|
|
children
|
|
(manager-type move) move-wm-options-spec
|
|
out-channel
|
|
(lambda (wm in-channel)
|
|
(init-move-wm wm in-channel)
|
|
wm)))
|
|
|
|
(define (init-move-wm wm channel)
|
|
(let* ((dpy (wm:dpy wm))
|
|
(window (wm:window wm))
|
|
(gc (create-gc dpy window '())))
|
|
(spawn* (list 'move-wm wm)
|
|
(lambda (release)
|
|
(release)
|
|
(call-with-current-continuation
|
|
(lambda (exit)
|
|
(let loop ()
|
|
(let ((msg (receive channel)))
|
|
(handle-message wm gc exit msg)
|
|
(loop)))))
|
|
(free-gc dpy gc)))))
|
|
|
|
(define (handle-message wm gc exit msg)
|
|
(let ((dpy (wm:dpy wm))
|
|
(window (wm:window wm)))
|
|
(case (car msg)
|
|
((wait)
|
|
(let ((sp (second msg))
|
|
(message (third msg)))
|
|
(handle-message wm gc
|
|
(lambda args
|
|
(sync-point-release sp)
|
|
(apply exit args))
|
|
message)
|
|
(sync-point-release sp)))
|
|
|
|
((deinit-manager)
|
|
(exit 'deinit-manager))
|
|
|
|
((draw-main-window) #t)
|
|
|
|
((update-manager-state) #t)
|
|
|
|
((fit-windows)
|
|
(map (lambda (client)
|
|
(assert-client-visible wm client))
|
|
(wm-clients wm)))
|
|
|
|
((init-client)
|
|
(init-client wm (second msg) (third msg)))
|
|
|
|
((deinit-client)
|
|
(deinit-client wm (second msg)))
|
|
|
|
((configure-window)
|
|
(let ((window (second msg))
|
|
(changes (third msg))
|
|
(client? (client-of-window wm window)))
|
|
(if (window-exists? dpy window) (begin
|
|
(let* ((r (window-rectangle dpy window))
|
|
(dx (cond
|
|
((assq (window-change x) changes) =>
|
|
(lambda (c.x) (- (cdr c.x) (rectangle:x r))))
|
|
(else 0)))
|
|
(dy (cond
|
|
((assq (window-change y) changes) =>
|
|
(lambda (c.y) (- (cdr c.y) (rectangle:y r))))
|
|
(else 0)))
|
|
(dw (cond
|
|
((assq (window-change width) changes) =>
|
|
(lambda (c.w) (- (cdr c.w) (rectangle:width r))))
|
|
(else 0)))
|
|
(dh (cond
|
|
((assq (window-change height) changes) =>
|
|
(lambda (c.h) (- (cdr c.h) (rectangle:height r))))
|
|
(else 0)))
|
|
(send-synthetic-event
|
|
(lambda ()
|
|
(let ((r (root-rectangle dpy window)))
|
|
(send-event dpy window #f (event-mask structure-notify)
|
|
(create-configure-event
|
|
(event-type configure-notify) 0 #t dpy
|
|
window window
|
|
;; TODO: border-width/gravity ?
|
|
(rectangle:x r) (rectangle:y r)
|
|
(rectangle:width r) (rectangle:height r)
|
|
0 none #f))))))
|
|
(cond
|
|
((or (assq (window-change width) changes)
|
|
(assq (window-change height) changes)
|
|
(assq (window-change border-width) changes))
|
|
(if client?
|
|
(let* ((cw (client:client-window client?))
|
|
(cr (window-rectangle dpy cw)))
|
|
(configure-window
|
|
dpy cw
|
|
(append (make-window-change-alist
|
|
(border-width 0)
|
|
(x (+ (rectangle:x cr) dx))
|
|
(y (+ (rectangle:y cr) dy))
|
|
(width (+ (rectangle:width cr) dw))
|
|
(height (+ (rectangle:height cr) dh)))
|
|
changes)))
|
|
(configure-window dpy window
|
|
(append (make-window-change-alist
|
|
(border-width 0))
|
|
changes))))
|
|
((or (assq (window-change x) changes)
|
|
(assq (window-change y) changes)
|
|
(assq (window-change stack-mode) changes))
|
|
(if client?
|
|
(let* ((cw (client:client-window client?))
|
|
(cr (window-rectangle dpy cw)))
|
|
(configure-window
|
|
dpy cw
|
|
(append (make-window-change-alist
|
|
(border-width 0)
|
|
(x (+ (rectangle:x cr) dx))
|
|
(y (+ (rectangle:y cr) dy)))
|
|
changes))))
|
|
(send-synthetic-event))
|
|
(else (send-synthetic-event))))))))
|
|
|
|
((iconify-client)
|
|
(let ((client (second msg)))
|
|
(if (not (client-data:icon client))
|
|
(begin
|
|
(unmap-window dpy (client:client-window client))
|
|
(unmap-window dpy (client:window client))
|
|
(set-wm-state! dpy (client:window client) (wm-state iconic)
|
|
none)
|
|
(let ((icon (create-client-icon wm client)))
|
|
(set-client-data:icon! client icon)
|
|
(map-icon icon))))))
|
|
|
|
((maximize-client)
|
|
(let ((client (second msg)))
|
|
(maximize-window dpy (client:client-window client))))
|
|
|
|
((normalize-client)
|
|
(let ((client (second msg)))
|
|
(if (client-data:icon client)
|
|
(begin
|
|
(destroy-icon (client-data:icon client))
|
|
(map-window dpy (client:window client))
|
|
(map-window dpy (client:client-window client))
|
|
(set-wm-state! dpy (client:window client) (wm-state normal)
|
|
none)
|
|
(set-client-data:icon! client #f)))))
|
|
|
|
((draw-client-window)
|
|
(draw-client-window wm (second msg) gc))
|
|
|
|
((fit-client)
|
|
;; client-window changed it's size
|
|
(fit-client-windows wm (second msg)))
|
|
|
|
((fit-client-window)
|
|
;; client changed it's size ??
|
|
(fit-client-window wm (second msg)))
|
|
|
|
((manager-focused) #t)
|
|
|
|
((update-client-state)
|
|
(let* ((client (second msg))
|
|
(focused? (third msg))
|
|
(state (if focused?
|
|
'focused
|
|
'normal))
|
|
(titlebar (client-data:titlebar client)))
|
|
(set-titlebar-state! titlebar state)))
|
|
|
|
((update-client-name)
|
|
(let ((client (second msg))
|
|
(name (third msg)))
|
|
(let ((titlebar (client-data:titlebar client)))
|
|
(set-titlebar-title! titlebar name))))
|
|
|
|
((show-clients)
|
|
(let ((clients (second msg)))
|
|
(for-each (lambda (c)
|
|
(if (client-data:icon c)
|
|
(handle-message wm gc exit
|
|
(list 'normalize-client c)))
|
|
(raise-window dpy (client:client-window c)))
|
|
clients)))
|
|
|
|
(else (warn "unhandled move-wm message" wm msg)))))
|
|
|
|
(define (make-client-data titlebar resizer icon)
|
|
(list titlebar resizer icon))
|
|
|
|
(define (client-data:titlebar client)
|
|
(first (client:data client)))
|
|
|
|
(define (client-data:resizer client)
|
|
(second (client:data client)))
|
|
|
|
(define (client-data:icon client)
|
|
(third (client:data client)))
|
|
|
|
(define (set-client-data:icon! client icon)
|
|
(set-car! (cddr (client:data client)) icon))
|
|
|
|
(define (window-wants-decoration? dpy window)
|
|
(cond
|
|
((get-motif-wm-hints dpy window) =>
|
|
(lambda (hints)
|
|
(mdisplay (motif-wm-hints:decorations hints) "\n")
|
|
(or (not (motif-wm-hints:decorations hints))
|
|
(not (null? (enum-set->list (motif-wm-hints:decorations hints)))))))
|
|
(else #t)))
|
|
|
|
(define (init-client wm client maybe-rect)
|
|
(let ((dpy (wm:dpy wm)))
|
|
(let* ((r (initial-client-rect wm client maybe-rect))
|
|
(channel (make-channel))
|
|
(titlebar (create-client-titlebar channel wm client))
|
|
(resizer (create-resizer wm client))
|
|
(options (wm:options wm)))
|
|
(set-client:data! client (make-client-data titlebar resizer #f))
|
|
(set-titlebar-title! titlebar (client-name dpy client))
|
|
(let* ((bw (get-option-value options 'border-width))
|
|
(th (get-option-value options 'titlebar-height))
|
|
(x.y (if (window-wants-decoration? dpy (client:window client))
|
|
(cons (rectangle:x r) (rectangle:y r))
|
|
(cons (- (rectangle:x r) bw)
|
|
(- (rectangle:y r) (+ bw th))))))
|
|
(move-resize-window dpy (client:client-window client)
|
|
(car x.y) (cdr x.y)
|
|
(+ (rectangle:width r) (* 2 bw))
|
|
(+ (rectangle:height r) (* 2 bw) th)))
|
|
(fit-client-windows wm client)
|
|
|
|
(install-dragging-control channel dpy
|
|
(titlebar:window titlebar)
|
|
(client:client-window client))
|
|
(spawn*
|
|
(list 'move-wm-client-handler wm client)
|
|
(lambda (release)
|
|
(release)
|
|
(let loop ()
|
|
(select*
|
|
(wrap (receive-rv channel)
|
|
(lambda (msg)
|
|
(case (car msg)
|
|
((drop)
|
|
;; check if outside...
|
|
(let ((window-x (second msg))
|
|
(window-y (third msg))
|
|
(root-x (fourth msg))
|
|
(root-y (fifth msg)))
|
|
(let ((r (root-rectangle dpy (wm:window wm))))
|
|
(if (point-in-rectangle? r root-x root-y)
|
|
(move-window dpy (client:client-window client)
|
|
window-x window-y)
|
|
(send (wm:out-channel wm)
|
|
(list 'root-drop (client:window client)
|
|
root-x root-y))))))
|
|
((click)
|
|
(wm-select-client wm client (fourth msg)))
|
|
;; from titlebar-buttons
|
|
((kill)
|
|
(delete-window dpy (client:window client) (second msg)))
|
|
((iconify)
|
|
(wm-iconify-window wm (client:window client)))
|
|
((maximize)
|
|
(wm-maximize-window wm (client:window client)))
|
|
))))
|
|
;; TODO: internal channel
|
|
(loop))
|
|
(destroy-resizer dpy resizer)))
|
|
|
|
(map-titlebar titlebar)
|
|
(if (window-exists? dpy (client:window client))
|
|
(map-window dpy (client:window client)))
|
|
(map-window dpy (client:client-window client)))))
|
|
|
|
(define (create-client-titlebar channel wm client)
|
|
(let ((options (wm:options wm)))
|
|
(create-titlebar channel (wm:dpy wm) (client:client-window client)
|
|
(wm:colormap wm)
|
|
(list (cons 'buttons '(kill maximize iconify))
|
|
(cons 'normal-colors
|
|
(get-option options 'titlebar-colors))
|
|
(cons 'active-colors
|
|
(get-option options'titlebar-colors-focused))
|
|
(cons 'focused-colors
|
|
(get-option options 'titlebar-colors-focused))
|
|
(cons 'border-style
|
|
(get-option options 'titlebar-style))))))
|
|
|
|
(define (deinit-client wm client)
|
|
(let ((dpy (wm:dpy wm)))
|
|
(set-input-focus dpy (wm:window wm) (revert-to parent) current-time)))
|
|
|
|
(define (create-client-icon wm client)
|
|
(let* ((other-icons (filter (lambda (x) x)
|
|
(map client-data:icon (wm-clients wm))))
|
|
(r (find-icon-rect (clip-rectangle (wm:dpy wm) (wm:window wm))
|
|
other-icons)))
|
|
(create-icon wm client r)))
|
|
|
|
;; ***
|
|
|
|
(define (fit-client-windows wm client)
|
|
(let* ((dpy (wm:dpy wm))
|
|
(options (wm:options wm))
|
|
(border-width (get-option-value options 'border-width))
|
|
(titlebar-height (get-option-value options 'titlebar-height))
|
|
(wa (get-window-attributes dpy (client:client-window client))))
|
|
;; TODO: is called much too often
|
|
(if (window-exists? dpy (client:window client))
|
|
(move-resize-window dpy (client:window client)
|
|
border-width
|
|
(+ border-width titlebar-height)
|
|
(- (window-attribute:width wa)
|
|
(* 2 border-width))
|
|
(- (window-attribute:height wa)
|
|
(+ (* 2 border-width) titlebar-height))))
|
|
(move-resize-titlebar
|
|
(client-data:titlebar client)
|
|
(make-rectangle border-width border-width
|
|
(- (window-attribute:width wa) (* 2 border-width))
|
|
titlebar-height))))
|
|
|
|
(define (fit-client-window wm client)
|
|
(let* ((dpy (wm:dpy wm))
|
|
(options (wm:options wm))
|
|
(border-width (get-option-value options 'border-width))
|
|
(titlebar-height (get-option-value options 'titlebar-height)))
|
|
(if (window-exists? dpy (client:window client))
|
|
(let ((wa (get-window-attributes dpy (client:window client))))
|
|
(resize-window dpy (client:client-window client)
|
|
(+ (window-attribute:width wa) (* 2 border-width))
|
|
(+ (window-attribute:height wa)
|
|
(* 2 border-width)
|
|
titlebar-height))))))
|
|
|
|
(define (assert-client-visible wm client)
|
|
(let* ((dpy (wm:dpy wm))
|
|
(win (client:client-window client))
|
|
(x (window-x dpy win))
|
|
(y (window-y dpy win))
|
|
(w (window-width dpy (wm:window wm)))
|
|
(h (window-height dpy (wm:window wm))))
|
|
;; TODO: assert-icon-visible ...
|
|
(if (>= x w)
|
|
(set-window-x! dpy win (- w 10)))
|
|
(if (>= y h)
|
|
(set-window-y! dpy win (- h 10)))))
|
|
|
|
(define (draw-client-window wm client gc)
|
|
(let* ((options (wm:options wm))
|
|
(colors (get-option-value options 'border-colors))
|
|
(window (client:client-window client))
|
|
(dpy (wm:dpy wm))
|
|
(border-style (get-option-value options 'border-style))
|
|
(border-width (get-option-value options 'border-width))
|
|
(clip-rect (clip-rectangle dpy window)))
|
|
(if (not (eq? border-style 'flat))
|
|
(let ((light (if (eq? border-style 'sunken)
|
|
(car colors) (cadr colors)))
|
|
(dark (if (eq? border-style 'sunken)
|
|
(cadr colors) (car colors))))
|
|
(for-each (lambda (i)
|
|
(let ((r (make-rectangle
|
|
(+ i (rectangle:x clip-rect))
|
|
(+ i (rectangle:y clip-rect))
|
|
(- (rectangle:width clip-rect) (* i 2))
|
|
(- (rectangle:height clip-rect) (* i 2)))))
|
|
(draw-shadow-rectangle dpy window gc
|
|
r light dark)))
|
|
(iota border-width))))))
|
|
|
|
(define (initial-client-rect wm client maybe-rect)
|
|
(let* ((dpy (wm:dpy wm))
|
|
(win (client:window client))
|
|
(default-width (if maybe-rect (rectangle:width maybe-rect) 400))
|
|
(default-height (if maybe-rect (rectangle:height maybe-rect) 200))
|
|
(w.h (initial-client-size wm client default-width default-height))
|
|
;; TODO: Transients centered?
|
|
(options (wm:options wm))
|
|
(maybe-x.y
|
|
(let* ((bw (get-option-value options 'border-width))
|
|
(th (get-option-value options 'titlebar-height))
|
|
(x.y (find-free-position wm client w.h '(0 . 0))))
|
|
x.y))
|
|
(x.y (desired-position/hints dpy win maybe-x.y)))
|
|
(make-rectangle (car x.y) (cdr x.y)
|
|
(car w.h) (cdr w.h))))
|
|
|
|
(define (initial-client-size wm client default-width default-height)
|
|
(let* ((dpy (wm:dpy wm))
|
|
(win (client:window client))
|
|
(w.h-1
|
|
(let ((w.h (minimal-size/hints dpy win default-width
|
|
default-height)))
|
|
(cons (if (< default-width (car w.h))
|
|
(car w.h)
|
|
default-width)
|
|
(if (< default-height (cdr w.h))
|
|
(cdr w.h)
|
|
default-height))))
|
|
(w.h (maximal-size/hints dpy win (car w.h-1) (cdr w.h-1))))
|
|
;;(w.h (desired-size/hints dpy win w.h-2)))
|
|
w.h))
|
|
|
|
(define (find-free-position wm client size default-pos)
|
|
(let* ((dpy (wm:dpy wm))
|
|
(max-w (window-width dpy (wm:window wm)))
|
|
(max-h (window-height dpy (wm:window wm)))
|
|
(w (car size))
|
|
(h (cdr size))
|
|
(rects (map (lambda (client)
|
|
(window-rectangle dpy (client:client-window client)))
|
|
(filter (lambda (c)
|
|
(and (not (eq? c client))
|
|
(not (client-data:icon c))))
|
|
(wm-clients wm))))
|
|
(list1 (map (lambda (x.y)
|
|
(make-rectangle (car x.y) (cdr x.y) w h))
|
|
(possible-positions rects)))
|
|
(list2 (filter (lambda (r) (rect-ok? r rects))
|
|
list1))
|
|
;; list2 may contain rects that are outside the wm
|
|
(list3 (filter (lambda (r)
|
|
(not (or (> (+ (rectangle:x r) (rectangle:width r))
|
|
max-w)
|
|
(> (+ (rectangle:y r) (rectangle:height r))
|
|
max-h))))
|
|
list2)))
|
|
(if (null? list3)
|
|
default-pos
|
|
(let ((r (car list3)))
|
|
(cons (rectangle:x r) (rectangle:y r))))))
|
|
|
|
;; possible positions are all rect-corners except the upper left, and
|
|
;; all intersection points of all bottom and right sides of the rects.
|
|
(define (possible-positions rects)
|
|
(let ((corners (flatten
|
|
(map (lambda (r)
|
|
(let* ((x1 (rectangle:x r))
|
|
(y1 (rectangle:y r))
|
|
(x2 (+ x1 (rectangle:width r)))
|
|
(y2 (+ y1 (rectangle:height r))))
|
|
(list (cons x1 y2)
|
|
;; (cons x2 y2) also included below
|
|
(cons x2 y1))))
|
|
rects)))
|
|
(xs (map (lambda (r)
|
|
(+ (rectangle:x r) (rectangle:width r)))
|
|
rects))
|
|
(ys (map (lambda (r)
|
|
(+ (rectangle:y r) (rectangle:height r)))
|
|
rects)))
|
|
(append (list (cons 0 0))
|
|
corners
|
|
(flatten (map (lambda (x)
|
|
(map (lambda (y) (cons x y))
|
|
ys))
|
|
xs)))))
|
|
|
|
;; it's ok if it does not overlap with any rect
|
|
(define (rect-ok? rect rects)
|
|
(not (any (lambda (r)
|
|
(rectangles-overlap? rect r))
|
|
rects)))
|