2 ;; .sawfish/rc -- Sawfish window manager configuration file
3 ;; Copyright 2008-2015 by Michal Nazarewicz
5 ;; This program is free software: you can redistribute it and/or
6 ;; modify it under the terms of the GNU General Public License as
7 ;; published by the Free Software Foundation, either version 3 of the
8 ;; License, or (at your option) any later version.
10 ;; This program is distributed in the hope that it will be useful, but
11 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 ;; General Public License for more details.
15 ;; You should have received a copy of the GNU General Public License
16 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
19 (define sawfish-quit-cmd-file (getenv "SAWFISH_QUIT_CMD_FILE"))
20 (if sawfish-quit-cmd-file (unsetenv "SAWFISH_QUIT_CMD_FILE"))
22 ;;{{{ What machine are we running
25 "Screen resolution formatted as WIDTHxHEIGHT."
26 (concat (number->string (screen-width)) "x" (number->string (screen-height))))
31 (require 'sawfish.wm.util.window-order)
33 (define (mn-run command)
34 "Runs command asynchronously with a double fork to separate running
35 program from Sawfish process.
37 COMMAND is a string representing command to run. It will be executed
38 via `system' function with additional ampersand at the end so that the
39 double fork followed by the middle process exiting happens."
40 (format standard-error ">> %s\n" command)
41 (system (concat command " & type disown >/dev/null 2>&1 && disown -a")))
43 (define (mn-run-unless regexp command #!optional current-vp)
44 "Searches for window which WM_CLASS (formatted as %s/%s) matches
45 REGEXP. If found returns this window otherwise runs COMMAND (in
46 background) and returns nil.
48 COMMAND is executed using `mn-run' function.
50 If current-vp is not-nil, will look only through windows in current
53 Windows are matched in most recently focused first order."
57 (let ((cls (get-x-text-property w 'WM_CLASS)))
61 (and (> (length cls) 1) (aref cls 1))
64 (window-order current-workspace t (not current-vp)))
71 ;; Show errors on screen and standard-error
72 (require 'sawfish.wm.ext.error-handler)
73 (setq error-destination 'both)
75 (write standard-error "Sawfish starting...\n")
78 (custom-set-typed-variable
79 (quote wm-modifier-value) (quote (super)) (quote modifier-list))
81 ;; Focus and Auto Raise
82 (require 'sawfish.wm.ext.auto-raise)
83 (setq focus-mode 'enter-only
85 raise-windows-on-focus nil
86 raise-window-timeout 200)
88 ;; Moving and resizing
89 (require 'sawfish.wm.commands.move-resize)
90 (setq move-show-position t
91 resize-show-dimensions t
92 move-outline-mode 'box
93 resize-outline-mode 'box
95 move-snap-mode 'magnetism)
97 (bind-keys window-keymap
99 (lambda () (raise-lower-window-and-transients (input-focus)))
101 (lambda () (move-window-interactively (input-focus)))
103 (lambda () (resize-window-interactively(input-focus))))
106 (set-number-of-viewports 10 1)
108 ; W-F<num> switches to viewport <num>-1
109 ; W-<num> moves current window to viewport <num>-1
110 ; expect <num> on dvorak are not numbers.
111 (do ((i 0 (1+ i)) (keys "&[{}(=*)+]"))
112 ((= i (length keys)))
113 (bind-keys global-keymap
114 (concat "W-F" (number->string (1+ i)))
115 (lambda () (set-screen-viewport i 0)))
116 (bind-keys window-keymap
117 (concat "W-" (elt keys i))
118 (lambda () (set-window-viewport (input-focus) i 0))))
119 (bind-keys global-keymap
122 (set-screen-viewport (% (1+ (car (screen-viewport))) 10) 0)))
123 (bind-keys global-keymap
126 (set-screen-viewport (% (+ 9 (car (screen-viewport))) 10) 0)))
131 (define default-window-alpha (cons 0.9 0.7))
133 (defconst window-alpha-max #xffffffff)
134 (defconst window-alpha-min #x1fffffff)
136 ;; If alpha is a list then:
137 ;; * if blur is nil takes alpha's car,
138 ;; * if blur is not nil takes alpha's cdr.
139 ;; If now alpha is not a number returns `window-alpha-max'.
140 ;; If alpha is within [0, 1] mueltiplies it by `window-alpha-max'
141 ;; Returns (max `window-alpha-min' (min `window-alpha-max' alpha))
142 (defun window-alpha-value (alpha blur)
143 (let ((a (if (listp alpha) (if blur (cdr alpha) (car alpha)) alpha)))
145 (clamp (or (and (>= a 0) (<= a 1) (* a window-alpha-max)) a)
146 window-alpha-min window-alpha-max)
149 ;; Return's window's alpha property or `default-window-alpha'
150 (defun window-alpha-get (w)
151 (or (window-get w 'alpha) default-window-alpha))
153 (defun window-alpha-put (w #!optional alpha)
154 (when (if (not alpha)
155 (window-remprop w 'alpha)
156 (window-put w 'alpha alpha)
158 (set-window-alpha w (or alpha default-window-alpha)
159 (not (eq w (input-focus))))))
161 ;; Sets window opacity.
162 ;; If alpha is nil uses `window-alpha-get'.
163 ;; Uses `window-alpha-value' to get alpha value from alpha and blur arguments.
164 (defun set-window-alpha (wnd alpha #!optional blur)
165 (let ((a (window-alpha-value (or alpha (window-alpha-get wnd)) blur))
166 (wid (window-frame-id wnd)))
167 (if (< a window-alpha-max)
168 (set-x-property wid '_NET_WM_WINDOW_OPACITY
169 (make-vector 1 a) 'CARDINAL 32)
170 (delete-x-property wid '_NET_WM_WINDOW_OPACITY)))
173 (add-hook 'focus-in-hook (lambda (w fmode) (set-window-alpha w nil nil)))
174 (add-hook 'focus-out-hook (lambda (w fmode) (set-window-alpha w nil t )))
176 ; If there are windows present when sawfish starts handle them properly,
177 ; that's what after-add-window-hook is for, but after sawfish is
178 ; started there is no longer need to keep this hook, so
179 ; after-initialisation-hook removes the other hook.
180 (let ((f (lambda (w) (set-window-alpha w nil (not (eq (input-focus) w))))))
181 (add-hook 'after-add-window-hook f)
182 (add-hook 'after-initialization-hook
183 (lambda () (remove-hook 'after-add-window-hook f))))
186 ;;{{{ Applications, menus & bindings
188 (require 'sawfish.wm.menus)
189 (require 'sawfish.wm.customize)
191 (setq xterm-program "term -N Term")
192 (define mn-mpdshow-command "term -N MPD -mesg -g 80x1 -e mpd-show")
193 (define mn-irssi-command "term -N IRC -mesg -g 80x10 -e irssi")
196 (define (mn-quit command)
197 (if sawfish-quit-cmd-file
198 (let ((fd (open-file sawfish-quit-cmd-file 'write)))
199 (write fd (concat command "\n"))
205 '(("_Customize" customize)
207 ("_Quit" ("Yes" quit))
208 ("Reboo_t" ("Yes" (mn-quit "reboot"))))
209 (and (file-exists-p "/usr/local/sbin/reboot-w")
210 '(("_Windows" ("Yes" (mn-quit "reboot-w")))))
211 '(("_Power Off" ("Yes" (mn-quit "poweroff"))))))
213 (define (mn-window-opaque-p wnd)
214 (>= (window-alpha-value (window-alpha-get wnd) nil) window-alpha-max))
216 (define (mn-toggle-opaque)
217 (let ((wnd (input-focus)))
218 (window-alpha-put wnd (if (mn-window-opaque-p wnd) nil (cons 1 0.7)))))
220 (add-window-menu-toggle "Opa_que" mn-toggle-opaque mn-window-opaque-p)
223 (bind-keys root-window-keymap
224 "Button3-Click" popup-root-menu)
226 (bind-keys window-keymap
227 "W-Space" (lambda () (popup-window-menu (input-focus))))
235 (let ((spec (cdr spec)))
238 (display-window (apply mn-run-unless spec)))
242 (let* ((home (getenv "HOME"))
243 (libexec (concat home "/.local/libexec/"))
244 (sh-maybe (lambda (dir fname #!optional args)
245 (let ((fname (concat dir fname)))
246 (if (file-exists-p fname)
247 (concat "/bin/sh " fname (or args "")))))))
248 `(("W-;" "^Emacs/emacs$" "e")
249 ("W-." "/IRC$" ,mn-irssi-command)
250 ("W-a" "GIMP" "gimp")
251 ("W-o" "Geeqie" "geeqie")
252 ("Pause" . ,(sh-maybe libexec "xlocker"))
253 ("Scroll_Lock" . ,(sh-maybe libexec "xlocker"))
254 ("Print" . ,(sh-maybe libexec "xlocker"))
255 ("W-\"" . "m rewind")
259 ("XF86Display" . ,(sh-maybe home "/.xscreen-config"))
260 ("XF86MonBrightnessUp" . "xbacklight + 25")
261 ("XF86MonBrightnessDown" . "xbacklight - 25"))))
263 (define (mn-get-browser-priority wnd)
264 (let ((cls (get-x-text-property wnd 'WM_CLASS)))
265 (when (> (length cls) 1)
268 (and (string= (car spec) (aref cls 0))
269 (or (not (cadr spec))
270 (string= (cadr spec) (aref cls 1)))
271 (throw 'return (cddr spec))))
272 '(("Navigator" "Firefox-esr" . 0)
273 ("Navigator" "Firefox" . 0)
276 ("Google-chrome" () . 3)
277 ("Navigator" () . 4)))))))
279 ;;; Looks for a web browser to select. Switches to a window which currently
280 ;;; doesn’t hold input focus prioritising windows as follows:
282 ;;; * non-iconified windows are preferred over iconified windows,
283 ;;; * windows visible on current view-port are preferred over other windows and
284 ;;; * Firefox > Opera > Chromium > Chrome.
286 ;;; If no browser window is found starts Firefox.
287 (define (mn-get-browser)
288 (let ((focused (input-focus))
289 (best-pri 1000) best found-firefox)
290 ;; If focused window is outside of current viewport pretend it’s not
291 ;; focused. This is needed if we switch to far away viewport while
292 ;; retaining focus in a browser window.
293 (when (window-outside-viewport-p focused)
297 (let ((pri (mn-get-browser-priority wnd)))
299 (setq found-firefox t))
302 (memq focused (transient-group wnd)))))
303 (when (window-outside-viewport-p wnd) (setq pri (+ pri 10 )))
304 (when (window-iconified-p wnd) (setq pri (+ pri 100)))
305 (when (< pri best-pri)
306 (setq best-pri pri best wnd))))
309 (display-window (or (last (transient-children best t)) best))
310 (or found-firefox (mn-run "firefox")))))
312 (bind-keys global-keymap "W-," mn-get-browser)
314 ;;; Returns t if given window is a terminal window. More specifically, whether
315 ;;; window’s WM_CLASS is Term.
316 (define (mpn-terminal-window-p wnd)
317 (let ((cls (get-x-text-property wnd 'WM_CLASS)))
318 (and cls (string-equal "Term" (aref cls 0)))))
320 ;;; Selects terminal window in current view-port or starts a terminal.
322 ;;; Also warps mouse to the centre of the window if not already above it."
323 (define (mn-get-term)
324 (let ((terms (delete-if-not mpn-terminal-window-p
325 (window-order current-workspace t))))
327 ;; If there are at least two terminal windows and the first one has
328 ;; input focus, display the second one. Otherwise, display the first
331 (or (and (eq (car terms) (input-focus)) (cadr terms)) (car terms)))
332 ;; No terminal windows, start a new one.
333 (mn-run xterm-program))))
335 (setq warp-to-window-enabled t
336 warp-to-window-offset (cons 50 50))
338 (bind-keys global-keymap "W-$" mn-get-term)
341 (require 'sawfish.wm.ext.run-application)
342 (setq run-application:y-position 'top)
343 (bind-keys global-keymap "W-r" 'run-application)
345 (let ((map (make-keymap))
346 (make-cmd (lambda (cmd)
349 (setq cmd (format nil "Master %d%%" cmd)))
350 (mn-run (concat "amixer set " cmd))))))
351 (let ((chars ";,.aoe'qjk") (i 0) ch)
352 (while (< i (length chars))
353 (setq ch (aref chars i) i (1+ i))
355 (concat "" ch) (make-cmd (- (* i 10) 5))
356 (concat "W-" ch) (make-cmd (* i 10)))))
358 "W-p" (make-cmd "Master toggle")
359 "u" (make-cmd "Master 70%")
360 "W-u" (make-cmd "Master 50%"))
361 (bind-keys global-keymap
363 "XF86AudioLowerVolume" (make-cmd "Master 10%-")
364 "XF86AudioRaiseVolume" (make-cmd "Master 10%+")
365 "XF86AudioMute" (make-cmd "Master toggle")
366 "XF86AudioMicMute" (make-cmd "Capture toggle")))
368 ; Having Super-Tab bound seems to overwrite existing W-Tab binding.
369 ; cabinet-switch is nice and all, but having it under W-Tab is not what I want.
370 ; Unbind Super-Tab to get cycle-windows back.
371 (unbind-keys global-keymap "Super-Tab")
372 ; Also unbind C-Tab since this is used for switching tabs in a browser.
373 (unbind-keys global-keymap "C-Tab")
375 ;; Window manipulations
376 (bind-keys window-keymap
378 "W-c" 'pack-window-up
379 "W-t" 'pack-window-down
380 "W-h" 'pack-window-left
381 "W-n" 'pack-window-right
383 "W-C" 'grow-window-up
384 "W-T" 'grow-window-down
385 "W-H" 'grow-window-left
386 "W-N" 'grow-window-right
387 ;; Shrink till it overlaps with one less window
388 "C-W-c" 'shrink-window-up
389 "C-W-t" 'shrink-window-down
390 "C-W-h" 'shrink-window-left
391 "C-W-n" 'shrink-window-right
392 ;; Move till it overlaps with one less window
393 "C-W-C" 'yank-window-up
394 "C-W-T" 'yank-window-down
395 "C-W-H" 'yank-window-left
396 "C-W-N" 'yank-window-right)
399 ;; Run some applications
400 (add-hook 'after-initialization-hook
402 (let ((sn (system-name)))
405 (when (string-looking-at (car spec) sn)
406 (apply mn-run-unless (cdr spec))))
407 `(("" "^Emacs/emacs$" "e")
408 ("erwin|pikus" "/IRC$" ,mn-irssi-command)
409 ("erwin" "/MPD$" ,mn-mpdshow-command)))
410 (set-screen-viewport 0 0))))
414 ;; Desktop background
416 (let ((set-bg-script (concat (getenv "HOME") "/.local/libexec/set-bg")))
417 (when (file-exists-p set-bg-script)
418 (require 'rep.io.timers)
420 (define set-bg--timer (make-timer (lambda (_) (set-bg))))
422 (define (set-bg--state-func proc)
423 (set-timer set-bg--timer (if (= (process-exit-value proc) 0) 900 10)))
426 (delete-timer set-bg--timer)
427 (start-process (let ((proc (make-process)))
428 (set-process-output-stream proc standard-output)
429 (set-process-error-stream proc standard-error)
430 (set-process-function proc set-bg--state-func)
431 (set-process-dir proc "/")
432 (set-process-prog proc set-bg-script)
437 (set-hook 'randr-change-notify-hook set-bg)
438 (bind-keys root-window-keymap "Button1-Click2" set-bg)))
442 (define-frame-type-mapper
444 (if (window-transient-p wnd)
448 (require 'sawfish.wm.ext.match-window)
450 (define-match-window-setter 'location
451 (lambda (wnd prop value)
452 (declare (unused prop))
453 (let ((x (nth 0 value)) (y (nth 1 value))
454 (w (nth 2 value)) (h (nth 3 value))
458 (when (< x 0) (setq x (+ (screen-width ) x)))
459 (when (< y 0) (setq y (+ (screen-height) y)))
460 (window-put wnd 'ignore-program-position #t)
461 (move-window-to wnd x y))
464 (resize-window-with-hints wnd w h))
467 (set-screen-viewport (1- v) 0)
468 (set-window-viewport wnd (1- v) 0))
470 (when (and x w (= x 0) (= w (screen-width)))
471 (window-put wnd 'queued-horizontal-maximize #t))
472 (when (and y h (= y 0) (= h (screen-height)))
473 (window-put wnd 'queued-horizontal-maximize #t)))))
475 ;; (define-match-window-matcher 'screen
476 ;; (lambda (w _prop value)
477 ;; (and (let ((w) (car value)) (or (not w) (= (screen-width ) w)))
478 ;; (let ((h) (car value)) (or (not h) (= (screen-height) h))))))
481 (let ((match-screen (lambda (_wnd width height)
482 (and (or (eq t width)
483 (= width (screen-width)))
485 (= height (screen-height))))))
486 (first-term-p (lambda (wnd)
487 (and (mpn-terminal-window-p wnd)
488 (do ((wnds (managed-windows) (cdr wnds)))
490 (and (not (eq (car wnds) wnd))
491 (mpn-terminal-window-p (car wnds))))
493 (setq match-window-profile
496 (lambda (profile-set)
497 (when (stringp (caar profile-set))
498 (setcar (car profile-set)
499 (cons 'WM_CLASS (caar profile-set))))
500 (mapcar (lambda (profile)
501 (let ((matches (car profile-set)))
502 (when (or (numberp (car profile))
503 (eq t (car profile)))
504 (setq matches (cons (list match-screen
508 profile (cddr profile)))
509 (cons matches profile)))
512 (4160 2560 (location 136 0 1464 2560 1)))
515 (4160 2560 (location 1600 0 906 1440)))
517 (("^Firefox(-esr)?/Navigator$")
518 (4160 2560 (location 2506 0 1654 1440 1)))
520 (("^MPlayer/|^mpv/|^openttd/|^Inkscape/|^[Vv]irt-manager/|^gzdoom/")
522 (("^Gimp/|^Inkscape/")
526 (("^Gimp/gimp$" (WM_WINDOW_ROLE . "^gimp-[^s]"))
527 ( (viewport 2 . 1) (group . Gimp)))
529 (("^Gimp/gimp$" (WM_WINDOW_ROLE . "^gimp-startup$"))
530 ( (place-mode . centered) (depth . 16)
531 (never-focus . #t) (focus-when-mapped . #f)
532 (sticky-and-skip . #t)))
535 (((WM_ICON_NAME . "Geeqie$"))
536 ( (viewport 3 . 1) (alpha 1 . 1))))))))