1
;; merlin/pile.jl -- a bad pile
5
;; Copyright (C) 2002 merlin <merlin@merlin.org>
7
;; http://merlin.org/sawfish/
9
;; This is free software; you can redistribute it and/or modify it
10
;; under the terms of the GNU General Public License as published by
11
;; the Free Software Foundation; either version 2, or (at your option)
14
;; This is distributed in the hope that it will be useful, but
15
;; WITHOUT ANY WARRANTY; without even the implied warranty of
16
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
;; GNU General Public License for more details.
19
;; You should have received a copy of the GNU General Public License
20
;; along with sawfish; see the file COPYING. If not, write to
21
;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
27
;; This software requires a patch to be applied to the Sawfish source to
28
;; add some additional XLib bindings.
30
;; Please see x.c.patch.
36
;; Create a directory ~/.sawfish/lisp/merlin and then put this file there:
37
;; mkdir -p ~/.sawfish/lisp/merlin
38
;; mv pile.jl ~/.sawfish/lisp/merlin
40
;; You also need merlin/sawlet.jl, merlin/util.jl and merlin/x-util.jl.
42
;; You're probably best off unpacking the entire merlin.tgz archive.
44
;; Then add to your .sawfishrc:
45
;; (require 'merlin.pile)
47
;; ; `pile' is the name of the pile; you can choose any name you
48
;; ; want, and have multiple piles.
50
;; Then restart sawfish. A pile should appear.
52
;; Go to Customize->Sawlets->Pile
53
;; - Here you can customize the behaviour of the pile.
55
;; Next, go to Customize->Matched Windows
56
;; - Here you must add a matched window setting for any fish that you
57
;; want captured to have Place mode pile.
59
;; Now, restart your apps. Hopefully they'll be in the pile.
61
;; You can create multiple piles and can configure them programatically
62
;; at creation if you want..
68
;; This is PRE-ALPHA INCOMPLETE SOFTWARE!
70
;; this is a bit hacky!
72
;; todo: should I tell windows they've moved??
76
;; beos-window-menu is hardwired in, which may not be cool
80
(define-structure merlin.pile
102
sawfish.wm.ext.beos-window-menu
103
sawfish.wm.util.display-window
111
(define (pile-p sawlet)
114
(define (dimensions pile)
115
(if (sawlet-active pile)
116
(window-dimensions (sawlet-frame pile))
122
(setq piles (nconc piles (list pile)))
125
(when (eq pile (window-get window 'place-mode))
126
(after-add-window-eye window)))
129
(define (mapchattelry thunk pile)
131
((chattelry (sawlet-get pile 'merlin.pile:chattelry)))
139
((base (window-position (sawlet-frame pile))))
140
(setq piles (delq pile piles))
143
;; (x-reparent-window (car chattel) nil base) -- doesn't work anymore
144
(x-map-request (car chattel)))
146
(sawlet-put pile 'merlin.pile:chattelry nil)))
148
(define (capture pile)
150
((window (select-window)))
151
(when (and window (not (eq window (sawlet-frame pile))))
152
(window-put window 'place-mode pile)
153
(after-add-window-eye window))))
155
(define (eject pile id)
157
((base (window-position (sawlet-frame pile))))
160
(when (eq id (car chattel))
161
(sawlet-put pile 'suspend t)
162
;; (x-reparent-window id nil base) -- doesn't work anymore?
164
(sawlet-put pile 'suspend nil)))
166
(sawlet-put pile 'merlin.pile:chattelry
167
(delete-if (lambda (chattel) (eq id (car chattel))) (sawlet-get pile 'merlin.pile:chattelry)))
168
(sawlet-reconfigure pile)))
170
(define (raise pile id)
171
(let (match) ; this is awful; move to front of list
174
(when (eq id (car chattel)) (setq match chattel)))
176
(sawlet-put pile 'merlin.pile:chattelry
177
(cons match (delete-if (lambda (chattel) (eq id (car chattel))) (sawlet-get pile 'merlin.pile:chattelry)))))
178
;; raising is not necessary if I move the others off..
179
;; (x-configure-window id `((stack-mode . top-if)))
180
(replace pile)) ;; awful
182
(define (constrain value hints axis) ;; TODO: min-aspect / max-aspect
184
((minn (or (cdr (assq (intern (format nil "min-%s" axis)) hints)) 1))
185
(maxx (or (cdr (assq (intern (format nil "max-%s" axis)) hints)) 10000))
186
(base (or (cdr (assq (intern (format nil "base-%s" axis)) hints)) 0))
187
(inc (or (cdr (assq (intern (format nil "%s-inc" axis)) hints)) 1)))
188
(max minn (min maxx (+ base (* inc (quotient (- value base) inc)))))))
190
(define (replace pile)
192
((root (sawlet-get pile 'root))
193
(chattel (car (sawlet-get pile 'merlin.pile:chattelry)))
194
(dim (cons- (dimensions pile) 10)) ; for demo purposes
198
(format nil "%s - %s" pile
199
(aref (x-get-text-property (car chattel) 'WM_NAME) 0))
204
((width (constrain (car dim) (nth 2 chattel) 'width))
205
(height (constrain (cdr dim) (nth 2 chattel) 'height)))
207
(car chattel) `((x . ,x) (y . ,y) (width . ,width) (height . ,height))))
208
(setq x (car dim) y (cdr dim)))
211
; TODO: now that I have x-get-window-properties I could query the size hints
212
; during replace, rather than storing them here..
213
; TODO: would it be better to do this in add-window-hook? Wouldn't get framed
214
; before it is deframed...
215
(define (after-add-window-eye window)
217
((pile (window-get window 'place-mode)))
218
(when (and (memq pile piles) (not (sawlet-get pile 'suspend)))
220
((id (window-id window))
221
(dim (window-dimensions window))
222
(chattelry (sawlet-get pile 'merlin.pile:chattelry))
223
(hints (window-size-hints window)))
224
(x-change-window-attributes id `((override-redirect . ,t)))
225
(x-map-notify id) ; this removes it from window-manager
226
(x-change-window-attributes id `((override-redirect . ,nil)))
227
(x-configure-window id `((border-width . 0)))
228
(x-reparent-window id (sawlet-get pile 'window) (cons 0 0))
229
(sawlet-put pile 'merlin.pile:chattelry (cons (list id dim hints) chattelry))
230
(sawlet-reconfigure pile)
231
(x-x-map-window id)))))
233
(add-hook 'after-add-window-hook after-add-window-eye)
237
(define (abbreviate name #!optional len)
238
(unless len (setq len 20))
239
(if (> (length name) len)
240
(concat (substring name 0 len) "...")
243
(define (make-pile-menu pile thunk)
244
(let ((chattelry (sawlet-get pile 'merlin.pile:chattelry)))
247
(list (abbreviate (aref (x-get-text-property (car chattel) 'WM_NAME) 0))
248
(lambda () (thunk chattel))
249
(cons 'check (and (eq chattel (car chattelry))))
250
(cons 'group (sawlet-symbol pile 'window-menu))))
253
(define (popup-pile-menu window)
255
((pile (sawlet-from-frame window)))
256
(when (memq pile piles)
258
`((,(_ "_Capture") ,(lambda () (capture pile)))
260
,(make-pile-menu pile (lambda (chattel) (raise pile (car chattel)))))
262
,(make-pile-menu pile (lambda (chattel) (eject pile (car chattel))))))))))
264
(define-command 'popup-pile-menu popup-pile-menu #:spec "%W")
268
;; ignore attempts by piled windows to move/resize themselves
269
(define (configure-request-handler pile event)
271
; ((id (cdr (assq 'window event)))
272
; (width (cdr (assq 'width event)))
273
; (height (cdr (assq 'height event)))
274
; (chattelry (sawlet-get pile 'merlin.pile:chattelry)))
277
; (when (and (equal id (car chattel)))
278
; (rplaca (cdr chattel) (cons width height))
279
; (sawlet-reconfigure pile))) chattelry))
282
(define (destroy-notify-handler pile event)
284
((id (cdr (assq 'window event)))
285
(chattelry (sawlet-get pile 'merlin.pile:chattelry)))
286
(sawlet-put pile 'merlin.pile:chattelry
287
(delete-if (lambda (chattel) (eq id (car chattel))) chattelry))
288
(sawlet-reconfigure pile))
291
(define (expose-handler pile event)
292
(x-clear-window (sawlet-get pile 'window))
295
(define (button-press-handler pile event)
296
(popup-pile-menu (sawlet-frame pile))
300
(define-placement-mode pile (lambda (window))))
302
(define (pile-window-menu pile)
303
(or (make-pile-menu pile (lambda (chattel)
304
(raise pile (car chattel))
305
(display-window (sawlet-frame pile))))
306
(list (list "<empty>" (lambda () (display-window (sawlet-frame pile)))))))
308
(eval-in ; make the window-menu display pile contents
310
(require 'merlin.pile)
311
(require 'merlin.sawlet)
312
(define (make-item w)
313
(fluid-set windows-left (delq w (fluid windows-left)))
314
(if (pile-p (sawlet-from-frame w))
315
(cons (make-label w) (lambda () (pile-window-menu (sawlet-from-frame w))))
320
(cons 'check (and (eq (input-focus) w)))
321
'(group . beos-window-menu)))))
322
'sawfish.wm.ext.beos-window-menu)
324
(defmacro defpile (pile . keys)
326
(require 'merlin.sawlet)
330
keys ; allow override
333
:post-configure ,replace
334
:wm-size-hints ,(lambda () (cons nil nil))
335
:dimensions ,dimensions
336
:expose-handler ,expose-handler
337
:button-press-handler ,button-press-handler
338
:destroy-notify-handler ,destroy-notify-handler
339
:configure-request-handler ,configure-request-handler
342
:background ,(get-color-rgb 0 0 0)
343
:matcher-actions '((place-mode . ,place-window-mode) (frame-type . normal)
344
(never-focus . #f) (sticky . #f) (sticky-viewport . #f) (window-list-skip . #f) (skip-tasklist . #f))