;;; aumix-mode.el --- run the aumix program in a buffer  -*- lexical-binding: t; -*-

;; Copyright (C) 2006-2021  Free Software Foundation, Inc.

;; Author: Kevin Ryde <user42_kevin@yahoo.com.au>
;; Version: 7
;; Keywords: multimedia, mixer, aumix
;; URL: http://user42.tuxfamily.org/aumix-mode/index.html
;; EmacsWiki: Aumix
;;
;; aumix-mode.el is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by the
;; Free Software Foundation; either version 3, or (at your option) any later
;; version.
;;
;; aumix-mode.el is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
;; Public License for more details.
;;
;; You can get a copy of the GNU General Public License online at
;; <http://www.gnu.org/licenses/>.


;;; Commentary:

;; "M-x aumix" runs the aumix sound volume adjuster program in a buffer.
;; See the docstrings of `aumix' and `aumix-mode' below.

;;; Emacsen:

;; Designed for Emacs 20 up.

;;; Install:

;; To make M-x aumix available put aumix-mode.el in one of your `load-path'
;; directories and the following in your .emacs
;;
;;     (autoload 'aumix "aumix-mode" nil t)
;;
;; If you use desktop.el then make an autoload for the aumix-mode function
;; too so `desktop-read' can load it when restoring an aumix-mode buffer.
;;
;;     (autoload 'aumix-mode "aumix-mode" nil t)
;;
;; There's autoload cookies for these functions below if you install via
;; `M-x package-install' or know how to use `update-file-autoloads'.

;;; History:

;; Version 1 - the first version
;; Version 2 - new program and switches variables
;;           - correction to `provide'
;; Version 3 - flag aumix-mode-switches as risky-local-variable
;; Version 4 - correction to `process-kill-without-query' for emacs21
;;           - cl for `dotimes' in emacs20
;; Version 5 - ensure curses interface if aumix built with gtk interface
;;           - add desktop.el save/restore
;; Version 6 - autoload aumix-mode for desktop.el
;; Version 7 - don't autoload the variables

;;; Code:

(require 'term)

;;;###autoload
(defgroup aumix-mode nil "Aumix Mode"
 :prefix "aumix-mode-"
 :group  'multimedia
 :link   '(url-link :tag "aumix-mode.el home page"
                    "http://user42.tuxfamily.org/aumix-mode/index.html")
 :link   '(url-link :tag "Aumix home page"
                    "http://jpj.net/~trevor/aumix.html"))

(defcustom aumix-mode-program "aumix"
  "Executable program name for aumix."
  :type  'string)
;; automatically `risky-local-variable-p' due to name `-program'

(defcustom aumix-mode-switches nil
  "List of command line switches to pass to aumix.
Only a few aumix options do anything for interactive use.
\"-d\" can set a mixer device file (default /dev/mixer), for
example

    (setq aumix-mode-switches \\='(\"-d\" \"/dev/mixer2\"))"

  :type  '(repeat string))
;;
;; Not sure if switches can do very much damage.  Make risky just in case
;; "-d filename" causes ioctls to some device other than a mixer.
;;;###autoload
(put 'aumix-mode-switches 'risky-local-variable t)

(defcustom aumix-mode-hook nil
  "Hook called by `aumix-mode'.
This hook, and the parent `term-mode-hook', are run when the
buffer setups are made but before the \"aumix\" program is
started.  This is convenient for applying `font-lock-mode' or
similar before the program output starts.

See also `term-exec-hook' which runs after \"aumix\" is started."

  :type    'hook
  :options '(turn-on-filladapt-mode))

;;-----------------------------------------------------------------------------

(defvar aumix-mode-map
  (let ((map (make-sparse-keymap)))
    ;;
    (dotimes (i 31)
      (let ((key (1+ i))) ;; 1 to 31
        (unless (member key '(?\C-c ?\C-l ?\t))
          (define-key map (vector key) nil))))
    (define-key map "Q"    #'aumix-mode-quit)
    (define-key map "q"    #'aumix-mode-quit)
    (define-key map "\C-l" #'term-send-raw)
    map)
  "Keymap for `aumix-mode'.
This inherits from parent keymap `term-raw-map'.  That parent
keymap is only set when `aumix-mode' is first called, since in
Emacs 21 `term-raw-map' is only created then.

Explicit nil settings in `aumix-mode-map' let control characters
have their usual `global-map' meanings instead of `term-send-raw'
in `term-raw-map'.  This is good for keys aumix doesn't use, in
particular C-x and C-h have their usual prefix meanings.

C-c is left for the various prefixed bindings of `term-raw-map'.
C-l and Tab are `term-send-raw' since they're used by the aumix
program.")

;;-----------------------------------------------------------------------------

(defun aumix-mode-quit ()
  "Quit from aumix, and kill the buffer.
This is a direct kill of the subprocess (rather than sending
\"q\" to aumix and waiting for it to exit itself).

For reference, if you use $LANG or $LANGUAGE set to something
other than English then the quit key in aumix might be
translated.  For example \"b\" (Beenden) in German.  In that case
the aumix program exits but the buffer remains.  The buffer is
left on aumix exit so that if it dies then an error message is
seen.  Re-bind the keys in `aumix-mode-map' if something other
than \"q\" for quit is desired."

  (interactive)
  (kill-buffer nil))

(defun aumix-mode-kill-process ()
  "An internal part of aumix-mode.el.
This function is designed to be called from `kill-buffer-hook'.
Kill the aumix sub-process when killing the buffer."
  (if (get-buffer-process (current-buffer))
      (delete-process nil)))

;; this is autoloaded for the benefit of desktop.el
;;;###autoload
(defun aumix-mode ()
  "Major mode for running the aumix program.
Key bindings are per the aumix program.  Press `k' for a summary,
or see KEYS in \"man aumix\".  `\\[aumix-mode-quit]' quits and kills the buffer.

The various terminal setups of `term-mode' are unchanged.  Note
in Emacs 24 the ANSI colours use `font-lock-mode' so be sure
that's turned on (which it is by default).  In Emacs 21 the
opposite was true; if you turn on font lock then you lose the
colours.

`clone-buffer' doesn't work on an aumix-mode buffer, as of Emacs
24.3.  Two or more aumix programs can run simultaneously (their
displays update with the underlying mixer settings), so perhaps
`clone-buffer' could be made to work.  A `rename-buffer' and
second `M-x aumix' works.  But normally only a single aumix-mode
buffer is desired.

----
The aumix home page is
  URL `http://jpj.net/~trevor/aumix.html'
The aumix-mode.el home page is
  URL `http://user42.tuxfamily.org/aumix-mode/index.html'

----
\\{aumix-mode-map}
"

  (term-mode)

  ;; kill sub-process when killing buffer
  (add-hook 'kill-buffer-hook #'aumix-mode-kill-process
            nil ;; not append
            t)  ;; buffer-local

  (setq major-mode 'aumix-mode
        mode-name  "Aumix")

  ;; desktop.el no misc-data needed
  (set (make-local-variable 'desktop-save-buffer) #'ignore)

  (run-mode-hooks 'aumix-mode-hook)

  ;; Unset $DISPLAY to ensure aumix uses the curses interface, not the gtk
  ;; interface if it was built with gtk interface.  The "-C" command line
  ;; option can do that too, but as of aumix 2.9.1 in the curses build -C
  ;; demands a colour file whereas prefer to leave that as the default
  ;; colours or let the user put a "-C" colours in aumix-mode-switches.
  (let ((process-environment (copy-sequence process-environment)))
    (setenv "DISPLAY" nil)

    (term-exec (current-buffer)
               "aumix" ;; process name
               aumix-mode-program nil aumix-mode-switches))

  (term-char-mode)

  (use-local-map aumix-mode-map)

  ;; In term.el of emacs21 `term-raw-map' is only created
  ;; when `term-char-mode' is called, so must wait until this point to set
  ;; it as the parent keymap.
  (set-keymap-parent aumix-mode-map term-raw-map)

  ;; `term-in-char-mode' looks for (current-local-map) equal to
  ;; `term-raw-map' to identify char mode, so buffer-local setting of
  ;; `term-raw-map' to be `aumix-mode-map' instead.
  (set (make-local-variable 'term-raw-map) aumix-mode-map)

  (let ((proc (get-buffer-process (current-buffer))))
    ;; adapt to emacs22 pointless incompatible rename of
    ;; `process-kill-without-query'
    (if (eval-when-compile (fboundp 'set-process-query-on-exit-flag))
        (set-process-query-on-exit-flag proc nil) ;; emacs22
      (process-kill-without-query proc))))        ;; emacs21

(put 'aumix-mode 'derived-mode-parent 'term-mode)

;;;###autoload
(defun aumix ()
  "Run the aumix program in a buffer.
An *aumix* buffer is created if it doesn't already exist.
See `aumix-mode' for details of operating the mode."

  (interactive)
  (switch-to-buffer "*aumix*")
  (unless (eq major-mode 'aumix-mode)
    (aumix-mode)))

;;-----------------------------------------------------------------------------
;; desktop.el save/restore (for emacs22 up)
;;
;; `desktop-create-buffer' loads the .el of an autoload mode func such as
;; `aumix-mode' when it appears in a saved desktop.  The alternative would
;; be to autoload `aumix-mode-desktop-restore' and the `eval-after-load',
;; but that seems like more.

(defun aumix-mode-desktop-restore (_file-name buffer-name _misc-data)
  "Restore `aumix-mode' buffer for desktop.el.
This is designed for use from `desktop-buffer-mode-handlers'."
  ;; checkdoc-params: (file-name buffer-name misc-data)

  (set-buffer (get-buffer-create buffer-name))
  (aumix-mode)
  (current-buffer)) ;; success, return buffer

(eval-after-load "desktop"
  '(when (boundp 'desktop-buffer-mode-handlers) ;; new in emacs22
     (add-to-list 'desktop-buffer-mode-handlers
                  '(aumix-mode . aumix-mode-desktop-restore))))

;;-----------------------------------------------------------------------------

;; LocalWords: docstrings dev subprocess el ncurses terminfo eterm usr

(provide 'aumix-mode)

;;; aumix-mode.el ends here
