Sawfish

This place is for you, to place your personal code snippets and let the community be able to use them, if desired.

Take it easy. Don't force yourself to write in wiki style. If it doesn't appear as you expected, leave it as-is. Someone will fix.

Volume control[]

Author: Jeremy Hankins

One of the useful features of a desktop environment is that it usually gives you a way to control the volume without opening up a shell and running a mixer program. I don't use a desktop environment, so I use this to control the sound volume on my system. It uses rexima, which is a mixer program with an easily scriptable interface.

(require 'rep.io.timers)

(defun rexima-get ()
  (let* ((stream (make-string-output-stream))
         (process (make-process stream))
         (retval (call-process process nil
                               "/usr/bin/rexima" "-v")))
    (when (and (eql retval 0)
               (string-match "^vol\\s+(\\d+)"
                             (get-output-stream-string stream)))
      (string->number (expand-last-match "\\1")))))

(define volume-display-timer
  (make-timer (lambda ()
                (display-message (format nil "Volume: %d" (rexima-get)))
                (make-timer (lambda () (display-message nil)) 1))))

(defun display-volume ()
  (set-timer volume-display-timer 0 500))

(defun rexima-inc (increment)
  (let ((val (format nil "%+d" increment))
        (process (make-process standard-output)))
    (start-process process "/usr/bin/rexima" "vol" val)))

(define-command 'audio-up
  (lambda ()
    (rexima-inc 5)
    (display-volume)))

(define-command 'audio-down
  (lambda ()
    (rexima-inc -5)
    (display-volume)))

(bind-keys global-keymap "Super-'" 'audio-up
                         "Super-a" 'audio-down)

Unless you use a dvorak layout you'll probably want to change the keybinding; using "q" for audio-up and "a" for audio-down would give you the same physical keys that I use.

Tip: How to save a script easily[]

This applies at least to firefox.

Select a small portion of the script you want to save, by a mouse drag. Then right-click, and choose "View selection source". Then a window opens. Save it.

When the code is big, this is much easier then to select the exactly entire code manually.

Tablet support[]

Author: D M German

Version: 14 Sep 2009

Using a tablet with sawfish poses some challenges. For starters, it is difficult to issue button 2 commands, and I have to flip the pen to do button 3. Also, once the screen is rotated (from pc to tablet mode) windows are outside the screen, partially, or completely, and frequently icons on the right of the window bar are outside the screen.

For some time I have wanted to look at the way themes work. My goal was to hack a theme for a tablet, which has very different requirements, ie:

  • Title bar buttons should be to the left
  • Avoid the use of second and third buttons

I spent some time yesterday and discovered the mx-flat theme. It is very configurable. With a little bit of work I got what I needed.

Following is the customization code (put it in your lisp/rc).

;; set the mxflat for the tablet use

(setq mxflat:customize-buttons t)
(setq mxflat:iconify-button-position -9)
(setq mxflat:maximize-button-position -8)
(setq mxflat:close-button-position -4)
(setq mxflat:border-width-focused 3)

(setq configure-button-keymap
       (bind-keys (make-keymap)
               "Button1-Off" 'dmg-move-current-window-inside
               "Button3-Off" '(call-command
                       (lambda ()
                               (if mxflat:custom-title
                                       (setq mxflat:custom-title nil)
                                       (setq mxflat:custom-title t)
                               )
                               ;(reframe-windows-with-style 'mxflat)
                               (map-windows refresh-window)
                       ))
       )
)

Hard screen edge for dragging window[]

Author: Derek Upham

Version: 17 Feb 2009

This script makes the screen edge "hard" when you drag a window; no matter how far the mouse pointer goes, the entire window stays in the screen.

Download it from here. Read also this comment.

Screenlets Helper[]

Author: Christopher Roy Bratusek (thanks to Jeremy and Nathan)

Version: 0.1

This is a wrapper around add-window-matcher to simplify matching all screenlets in the same manner, without letting your SawfishRC blow up by lots of duplicated code.

;;; Helper for Screenlets
;;; Adjust to your needs

( define ( add-screenlets-matcher wm-name pos )
  ( add-window-matcher 'WM_NAME wm-name
       `( position . ,pos )
       '( fixed-position . t )
       '( sticky . t )
       '( sticky-viewport . t )
       '( cycle-skip . t )
       '( depth . -2 )
       '( never-maximize . t )
       '( never-iconify . t ) ) )

Usage: (FolderView is taken for this example)

( add-screenlets-matcher "FolderViewScreenlet.py" 'south-west )

Note: named positions (north, south-west, center and such are only available in Sawfish 1.6.0 and newer


Conky toggle[]

Author: Jeremy Hankins

I use conky to display system stats, but I usually don't need to see it. So I have a toggle key for it. This requires sawfish 1.6.0 for the position matcher and the app.jl script for filter-byclass -- though it could probably be easily modified to work without it.

(add-window-matcher 'WM_CLASS "^Conky/Conky$"
                    '(depth . 1)
                    '(focus-mode . click)
                    '(frame-type . none)
                    '(sticky . #t)
                    '(sticky-viewport . #t)
                    '(position . north-east)
                    '(iconified . t)
                    '(ignore-stacking-requests . #t))

;; Toggle whether the conky window is iconified.
(define-command
  'conky
  (lambda ()
    (let ((conky (car (filter-windows (filter-byclass "Conky")))))
      (when conky
        (toggle-window-iconified conky)))))

(add-hook 'after-initialization-hook
          (lambda ()
            (unless (filter-windows (filter-byclass "Conky"))
              (system "conky &"))))

Then bind the conky command to a key.

Variation on xterm[]

Author: Jeremy Hankins

This is fairly minor variation on the code to run an xterm that's in sawfish.wm.commands.user, but I find it a bit more flexible and convenient. The app-selector, filter-byclass and filter-byname functions are available in app.jl. Beware of the after-initialization-hook bit if you have your shell set up to rewrite the xterm title. If you want to use it make sure that your xterm title always starts with "shell@host".

(define-cycle-command-pair
  'cycle-shell 'cycle-shell-backwards
  (app-selector (filter-byclass "XTerm")))

(setq xterm-program "/usr/bin/xterm"
      xterm-args '("-geometry 80x40"
                   "-u8"
                   "-j"))

(defun terminal (#!key command title #!rest args)
  "Run an xterm."
  (system (concat xterm-program
                  " " (mapconcat identity (append xterm-args args) " ")
                  (and title (format nil " -title '%s'" title))
                  (and command (format nil " -e %s" command))
                  " </dev/null &")))

;; The machine name, without the domain.
(setq host (let ((name (system-name)))
             (string-match "\\." name)
             (substring name 0 (match-start))))

(defun shell ()
  "Start a login shell."
  (terminal #:title (concat "shell@" host) "-ls"))

(define-command 'shell shell)

;; Start a shell after initialization, but only if there isn't already
;; a local shell running.
(add-hook 'after-initialization-hook
          (lambda ()
            (unless (filter-windows (filter-byname
                                     (concat "^shell@" host)))
              (shell))))

Bind the commands cycle-shell and shell to keys, and you're set.

ssh[]

Author: Jeremy Hankins

I use screen extensively, and running screen sessions inside of screen sessions doesn't makes sense. So rather than ssh via the command line (i.e., from within screen) I start a new xterm for my ssh connections. This uses the terminal function (see above) to run an xterm. It also uses prext.jl to access the menu of ssh hosts, but you could easily enough access it via a popup menu.

(defun ssh (host #!optional user title)
  "Run ssh in a terminal and connect to the specified host."
  (unless title
    (string-match "\\." host)
    (setq title (concat "shell@" (substring host 0 (match-start)))))
  (terminal #:title title
            #:command (concat "ssh "
                              (if user
                                  (concat user "@" host)
                                host))))

;; The .hosts file contains a list of hostnames, one per line.
(defun ssh-menu ()
  "Generate a menu of hosts to ssh to."
  (let ((f (open-file "~/.hosts" 'read))
        line hosts)
    (while (setq line (read-line f))
      (when (string-match "^\\s*([a-zA-Z\\.]+)\\s*(#.*)?$" line)
        (setq hosts (cons (substring line (match-start 1)
                                     (match-end 1))
                          hosts))))
    (close-file f)
    (mapcar (lambda (h)
              `(,h (ssh ,h)))
            hosts)))

(define-command 'ssh (lambda () (prext-menu (ssh-menu) "ssh:")))

To use it create a file .hosts in your home directory that lists the hosts that you commonly connect to. Then bind the ssh command to a key.

Popup menu to focus, un-iconify, for emacs / xterm ...[]

Author: Pettar Gustad

Verison: 2009 02 20

It popups menu to focus, un-iconify, etc, only for emacs / xterm ...

Download it from here.

Window Cycling in workspace / viewport[]

Commands to cycle windows in the same workspace / viewport are found

Debug helping codes[]

Basic Window Snooper[]

See also similar code by Daniel Pfeiffer.

Author: Christopher Roy Bratusek

Version: 0.90.1

This command displays several infos about the currently focused window

At this point I would like to say, that there's a way to let display-message-with-timeout *not* timeout the message: passing a negative timeout (it then behaves just like display-message).

;;; Window Snooping

;; doesn't work without this:
(require 'rep.io.timers)
;; and you forgotten this:
(define (display-message-with-timeout message timeout)
  (display-message message)
  (make-timer (lambda () (display-message nil)) timeout))

(defvar window-snooper-timeout 15)

(define (get-wm-name)
  (setq net-wm-name (get-x-text-property (input-focus) '_NET_WM_NAME)))

(define (get-wm-class)
  (setq wm-class (get-x-text-property (input-focus) 'WM_CLASS)))

(define (get-wm-icon-name)
  (setq net-wm-icon-name (get-x-text-property (input-focus) '_NET_WM_ICON_NAME)))

(define (get-wm-role)
  (setq wm-window-role (get-x-text-property (input-focus) 'WM_WINDOW_ROLE)))

(define (get-wm-locale-name)
  (setq wm-locale-name (get-x-text-property (input-focus) 'WM_LOCALE_NAME)))

(define (get-window-pos)
  (setq window-x (car (window-position (input-focus))))
  (setq window-y (cdr (window-position (input-focus)))))

(define (get-window-dims)
  (setq window-width (car (window-dimensions (input-focus))))
  (setq window-height (cdr (window-dimensions (input-focus)))))

(define (get-frame-dims)
  (setq frame-width (- (car (window-frame-dimensions (input-focus))) (car (window-dimensions (input-focus)))))
  (setq frame-height (- (cdr (window-frame-dimensions (input-focus))) (cdr (window-dimensions (input-focus))))))

(define (get-window-infos)
  (get-wm-name)
  (get-wm-class)
  (get-wm-icon-name)
  (get-wm-role)
  (get-wm-locale-name)
  (get-window-pos)
  (get-window-dims)
  (get-frame-dims))

(define (window-snooper)
  (get-window-infos)
  (display-message-with-timeout
      (format nil "About the currently focused window:\
        \n\n===================================\
	\n\n _NET_WM_NAME:\t\t %s\
	\nWM_CLASS:\t\t\t %s\
	\n_NET_WM_ICON_NAME:\t %s\
	\nWM_WINDOW_ROLE:\t %s\
	\nWM_LOCALE_NAME:\t %s\
        \nWindow X:\t\t %s pixels\
        \nWindow Y:\t\t %s pixels\
	\nWindow Width:\t\t\t %s pixels\
	\nWindow Height:\t\t %s pixels\
	\nFrame Width:\t\t\t %s pixels\
	\nFrame Height:\t\t\t %s pixels\
	\n\n==================================="
	net-wm-name
	wm-class
	net-wm-icon-name
	wm-window-role
	wm-locale-name
        window-x
        window-y
	window-width
	window-height
	frame-width
	frame-height)
	  window-snooper-timeout))

( define-command 'window-snooper window-snooper)

( bind-keys global-keymap
  "C-S-s" 'window-snooper )

TODO
- truncate [" and "] stuff
- get state infos (and display it)
- get image (and draw it)
- get keymap for this window (and display it)
- get window-matchers for this window (and display it)
- display WM_CLASS like UIs window-matcher-grabber does
- more?

NOTE: I've included a version of this in Debug-utils. - Jeremy Hankins)

Hook Snooper[]

hook snooper by Daniel Pfeiffer

It tells which hook is called.

Reporting[]

Author: Jeremy Hankins

I use this to report data about variables and such in sawfish code that I'm debugging. There are probably better or easier ways to do this; I'm interested in suggestions.

(defun warn (#!rest args)
  "Give a warning message."
  (error-handler-function
   'Warning
   (list 
    (concat "\t"
            (mapconcat (lambda (obj)
                         (if (stringp obj)
                             obj
                           (prin1-to-string obj)))
                       args "\n\t\t")))))

You can call it with strings (e.g., "Here I am!" to show that a section of code is running), or symbols, or both. Where the messages go depends on the value of error-destination.

NOTE: I've incorporated a better version of this into Debug-utils. It's formatted better, and you can choose between displaying to the screen or to standard-error.

sawfishrc[]

Author: Teika kazura

Version: 2009 10 24

Puts this at the bottom of your ~/.sawfishrc

(format standard-error "%s: init done\n" (current-time-string))

It helps when you change ~/.sawfishrc.

Automaticaly add new window to tabgroup[]

Author: Sergey Kozhemyakin

Verison: 2009 12 14

Description: It add newly opened window to tabgroups.

Assign all windows, that should be in same tabgroup, to same sawfish group. Specify in config groups that should be tabbed.

(setq tabbed-groups '(Chats Terminals))

(define (maybe-add-window-to-tabgroup w)
  (let ((grp (window-actual-group-id w)))
    (when (memq grp tabbed-groups)
      (let ((fw (car (windows-by-group grp t))))
        (unless (eq fw w)
          (tab-group-window w fw))))))

(add-hook 'after-add-window-hook maybe-add-window-to-tabgroup)

Ediff auto raise[]

Author: Teika kazura

Version: 2010-12-21

Auto raise ediff windows. More precisely, when you start ediff, all emacs windows are raised, with the ediff controller small window on top.

(add-hook 'map-notify-hook
	  (lambda (w)
	    (require 'sawfish.wm.stacking)
	    (when (equal (window-class w 'configurator)
			 "Emacs/Ediff")
	      (mapc raise-window
		    (filter-windows
		     (lambda (tmp)
		       (equal (window-class tmp) "Emacs"))))
	      (raise-window w))))

Example format for contributors[]

Author: Foo Dev

Version: 20090909

Description: Does Bar when Foo is FooBar

  (if (eq foo 'foobar)
    (bar))

Nice script, I'm using it [Foo User]

... I don't know what this might be good for ... [DAU]

See also[]