#!/bin/bash
DIR="${HOME}/.ratpoison"
pname="$(basename "$0")"


LIBRARY='

(defun contents-from-stream (stream &key length (min-size 256) max-extend)
  "
STREAM:     May be a binary or character, file or non-file stream.
LENGTH:     NIL, or the number of stream elements to read.
MIN-SIZE:   Minimum pre-allocated buffer size. If LENGTH is given, or STREAM
            has a FILE-LENGTH, then the MIN-SIZE is ignored.
MAX-EXTEND: NIL ==> double the buffer size, or double the buffer size until
            it is greater than MAX-EXTEND, and then increment by MAX-EXTEND.
RETURN:     A vector containing the elements read from the STREAM.
"
  (let* ((busize (or length (ignore-errors (file-length stream)) min-size))
         (eltype (stream-ELEMENT-TYPE stream))
         (initel (if (subtypep eltype (quote integer)) 0 #\Space))
         (buffer (make-ARRAY busize
                             :ELEMENT-TYPE eltype
                             :INITIAL-ELEMENT initel
                             :adjustable t :fill-pointer t))
         (start 0))
    (loop
       (let ((end (read-sequence buffer stream :start start)))
         (when (or (< end busize) (and length (= length end)))
           ;; we got eof, or have read enough
           (setf (fill-pointer buffer) end)
           (return-from contents-from-stream buffer))
         ;; no eof; extend the buffer
         (setf busize
               (if (or (null max-extend) (<= (* 2 busize) max-extend))
                   (* 2 busize)
                   (+ busize max-extend))
               start end))
       (adjust-array buffer busize :initial-element initel :fill-pointer t))))


(defun text-file-contents (path &key (if-does-not-exist :error)
                           (external-format :default))
  "
RETURN: The contents of the file at PATH as a LIST of STRING lines.
        or what is specified by IF-DOES-NOT-EXIST if it does not exist.
"
  (with-open-file (in path :direction :input
                      :if-does-not-exist if-does-not-exist
                      :external-format external-format)
    (if (streamp in)
        (contents-from-stream in :min-size 16384)
        in)))


(defun (setf text-file-contents) (new-contents path
                                  &key (if-does-not-exist :create)
                                  (if-exists :supersede)
                                  (external-format :default))
  "
RETURN: The NEW-CONTENTS, or if-exists or if-does-not-exist in case of error.
DO:     Store the NEW-CONTENTS into the file at PATH.  By default,
        that file is created or superseded; this can be changed with
        the keyword IF-DOES-NOT-EXIST or IF-EXISTS.
"
  (with-open-file (out path :direction :output
                       :if-does-not-exist if-does-not-exist
                       :if-exists if-exists
                       :external-format external-format)
    (if (streamp out)
        (write-sequence new-contents out)
        out)))


(defvar *absent* (cons (quote *absent*) nil))
(defmacro define-plist-field (name field &optional (offset (quote identity)))
 "
OFFSET: must be a symbol naming an accessor to find the plist in the record.
"
  `(progn
     (defun ,name (record)
       (let* ((value (getf (,offset record) (quote ,field) *absent*)))
         (if (eq value *absent*)
             (values nil nil)
             (values value t))))
     (defun (setf ,name) (new-value record)
       (setf (getf (,offset record) (quote ,field)) new-value))))

(define-plist-field frame-number        :number  cdr)
(define-plist-field frame-x             :x       cdr)
(define-plist-field frame-y             :y       cdr)
(define-plist-field frame-width         :width   cdr)
(define-plist-field frame-height        :height  cdr)
(define-plist-field frame-screen-width  :screenw cdr)
(define-plist-field frame-screen-height :screenh cdr)
'

BALANCE="$LIBRARY"'

(defun order-by-id (f) (frame-number f))
(defun order-by-x  (f) (frame-x      f))

(defun balance-frames (frames &optional (order-function (function order-by-x)))
  (let* ((frames  (sort frames
                        (function <)
                        :key order-function))
         (screen-width  (frame-screen-width  (first frames)))
         (screen-height (frame-screen-height (first frames)))
         (numframes     (length frames)))
    (loop
      :with width = (truncate screen-width numframes)
      :for x :from 0 :by width
      :for frame :in frames
      :do (setf (frame-x frame) x
                (frame-y frame) 0
                (frame-width frame) width
                (frame-height frame) screen-height))
    frames))



(defun margin-frames (frames right bottom)
  "
First frame should be sized with right and bottom margins.
"
  (flet ((main   (frames) (first  frames))
         (bottom (frames) (second frames))
         (right  (frames) (third  frames)))
    (let ((screen-width  (frame-screen-width  (main frames)))
          (screen-height (frame-screen-height (main frames))))
      (setf (frame-x      (main frames)) 0
            (frame-y      (main frames)) 0
            (frame-width  (main frames)) (- screen-width right)
            (frame-height (main frames)) (- screen-height bottom)
            (frame-x      (bottom frames)) 0
            (frame-y      (bottom frames)) (- screen-height bottom)
            (frame-width  (bottom frames)) (- screen-width right)
            (frame-height (bottom frames)) screen-height
            (frame-x      (right frames)) (- screen-width right)
            (frame-y      (right frames)) 0
            (frame-width  (right frames)) screen-width
            (frame-height (right frames)) screen-height)
      frames)))


(defvar *path* "~/.ratpoison/last-balanced")
(let* ((*print-pretty* nil)
       (input  (read-from-string
                (format nil "(~A)"
                        (substitute #\space #\,
                                    (text-file-contents
                                     *path*))))))
  (cond
    ((eq :order (caar input))
     (let ((order (let ((order (second (pop input))))
                    (if (member order (quote (order-by-x order-by-id)))
                        order
                        (quote order-by-x)))))
       (setf (text-file-contents *path*)
             (format nil "~(~{~S~^,~}~)"
                     (balance-frames input order)))))
    ((eq :margin (caar input))
     (pop input)
     (if (= 3 (length input))
         (setf (text-file-contents *path*)
               (format nil "~(~{~S~^,~}~)"
                       (margin-frames input 128 128)))
         (format *error-output* "Unexpected 4 frames for a margin operation in command file ~A" *path*)))
    (t
     (format *error-output* "Unexpected command file ~A" *path*))))
'

SETSIZE="$LIBRARY"'


(defvar *path* "~/.ratpoison/last-balanced")
(let* ((*print-pretty* nil)
       (input  (read-from-string
                (format nil "(~A)"
                        (substitute #\space #\,
                                    (text-file-contents
                                     *path*))))))
  (cond
    ((eq :setsize (caar input))
     (destructuring-bind (setsize width height) (first input)
       (let ((curframe (second input)))
          (setf (text-file-contents *path*)
                (format nil "~(~{~S~^,~}~)"
                        (set-size (third input) width height curframe))))))
    (t
     (format *error-output* "Unexpected command file ~A" *path*))))

'

RENUMBER='
(defun renumber (old new &rest winums)
  (handler-case
      (let* ((old     (parse-integer old))
             (new     (parse-integer new))
             (winums  (mapcar (function parse-integer) winums))
             (focused (first winums)))
        (assert (member old winums) () "No window number ~A" old)
        (ext:shell (format nil "ratpoison -c \"select ~A\" -c \"number ~A\" -c \"select ~A\""
                           old new (if (= old focused) new focused))))
    (error (err) (format *error-output* "~A" err))))
'

function renumber(){
    local old="$1"
    local new="$2"
    # ${winums[0]} is the focused window,
    # ${winums[1]} is the other window.
    clisp  -ansi -q -norc -E ISO-8859-1 \
        -x "$RENUMBER" \
        -x '(renumber "'"$1"'" "'"$2"'" '"$(ratpoison -c windows|sed -e 's/^\([0-9]\+\)\([-+*]\)\(.*\)/\2 \1/'|LC_ALL=C sort|sed -e 's/^..\(.*\)/\"\1\"/'|tr '\012' ' ')"')' \
        >/dev/null
}

function setsize(){
    # set the size of the current window.
    local width="$1"
    local heigth="$2"
    local gravity="$3"
    if [ -z "$gravity" ] ; then
        gravity=nw
    fi
    ( echo '(:setsize '"$gravity"')' ; ratpoison -c 'curframe' -c 'fdump' ) > "$DIR/last-balanced"
    clisp  -ansi -q -norc -E ISO-8859-1 -x "$BALANCE"
    exec ratpoison -c "frestore $(cat "$DIR/last-balanced")"
}

cmd="$1";shift
case "$cmd" in
save)
    name="$1"
    if [ -z "$name" ] ; then
        name=current
    fi
    mkdir "${DIR}" 2>/dev/null
    exec ratpoison -c 'fdump' > "${DIR}/${name}"
    ;;
load)
    name="$1"
    if [ -z "$name" ] ; then
        name=current
    fi
    exec ratpoison -c "frestore $(cat  "${DIR}/${name}")"
    ;;
list)
    cd ${DIR}
    rm -rf *~
    exec /bin/ls -m
    ;;
rm)
    name="$1"
    if [ -z "$name" ] ; then
        name=current
    fi
    exec rm  "${DIR}/${name}"
    ;;
showfocus)
    cmds=(
        "set border 6"
        "set fwcolor red"     # border color focused window.
        "set bwcolor yellow"  # border color unfocused windows.
        "set fgcolor black"   # foreground ratpoison windows (menu).
        "set bgcolor yellow"  # background ratpoison windows (menu).
        )
    for cmd in "${cmds[@]}" ; do
        ratpoison -c "$cmd"
    done
    echo "Warning: some windows don't take back colors (eg. emacs)."
    exit 0
    ;;
thin)
    cmds=(
        "set border 0"
        )
    for cmd in "${cmds[@]}" ; do
        ratpoison -c "$cmd"
    done
    exit 0
    ;;
renumber)
    renumber "$1" "$2"
    exit 0
    ;;
setsize)
    setsize "$1" "$2"
    exit 0
    ;;
margin)
    ratpoison -c only -c hsplit -c vsplit  -c focus -c 'select -'  -c focus -c 'select -' -c focus
    ( echo '(:margin 128 128)' ; ratpoison -c 'fdump' ) > "$DIR/last-balanced"
    clisp  -ansi -q -norc -E ISO-8859-1 -x "$BALANCE"
    exec ratpoison -c "frestore $(cat "$DIR/last-balanced")"  $(ratpoison -c windows|while read line ; do printf ' -c next' ; done)
    ;;
balance)
    order=order-by-x
    for arg ; do
        case "$arg" in
        -i) order=order-by-id ;;
        -x) order=order-by-x ;;
        *)  echo "Usage:"
            echo "$(basename $0) balance [-i|-x]"
            echo "  -i   order the windows from left to right by ID"
            echo "  -x   order the windows from left to right by original X coordinate"
            if [ $arg = -h -o $arg = --help ] ; then
                exit 0
            else
                exit 1
            fi
            ;;
        esac
    done
    ( echo '(:order '"$order"')' ; ratpoison -c 'fdump' ) > "$DIR/last-balanced"
    clisp  -ansi -q -norc -E ISO-8859-1 -x "$BALANCE"
    exec ratpoison -c "frestore $(cat "$DIR/last-balanced")"
    ;;
help|-h|--help)
    echo "${pname} usage:"
    echo "    ${pname} load \$NAME            # load a named configuration"
    echo "    ${pname} save \$NAME            # save a configuration under name"
    echo "    ${pname} balance [-i|-x]"
    echo "               -i   order the windows from left to right by ID"
    echo "               -x   order the windows from left to right by original X coordinate"
    echo "    ${pname} list                   # list configurations"
    echo "    ${pname} rm \$NAME              # delete named configuration"
    echo "    ${pname} showfocus              # color borders for focus and other"
    echo "    ${pname} thin                   # reverts showfocus"
    echo "    ${pname} margin                 # resize the windows for tmpwm"
    echo "    ${pname} renumber \$old \$new   # renumber the window"
    echo "    ${pname} help                   # display this help"
    echo "    ${pname} \$RATPOISON_COMMAND    # forwards to ratpoison -c \$RATPOISON_COMMAND"
    exit 0
    ;;
*)
    if ratpoison -c help|sed -e 's/[^ ]\+ \+\([^ ]\+\).*/\1/'|sort -u|grep -q -s "^${cmd}\$" ; then
        # if [ "$cmd" = "help" ] ; then
        #     "$0" --help
        # fi
        exec ratpoison -c "${cmd} $*"
    else
        printf "${pname} error: Unknown command %s\n" "$cmd"
        "$0" help
        exit 1
    fi
esac
echo 'How did I come here?'
exit 2
ViewGit