;;; xwem-keymacro.el --- Recording/playing keyboard macros. ;; Copyright (C) 2003-2005 by XWEM Org. ;; Author: Zajcev Evgeny ;; Steve Youngs ;; Created: Fri Dec 12 17:18:00 MSK 2003 ;; Keywords: xwem, xlib ;; X-CVS: $Id: xwem-keymacro.el,v 1.11 2005-04-04 19:54:13 lg Exp $ ;; This file is part of XWEM. ;; XWEM 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 2, or (at your option) ;; any later version. ;; XWEM 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 should have received a copy of the GNU General Public License ;; along with XEmacs; see the file COPYING. If not, write to the Free ;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA ;; 02111-1307, USA. ;;; Synched up with: Not in FSF ;;; Commentary: ;; XWEM uses Record X extension to collect keypresses when saving ;; keyboard macros, this method is tranparent to user and allow ;; catching events without need of keyboard grabbing. ;; XWEM keyboard macro is the same as Emacs keyboard macro. It is a ;; vector of Emacs events. XWEM uses special keymap - ;; `xwem-user-macros-map' to hold user defined macros. This keymap ;; used to save/restore keyboard macros across XWEM sessions. ;; XWEM's support for keyboard macros is full-featured. It includes ;; H-x q command to start recursive edition, command which allow to ;; change keyboard macro execution on fly. It has workaround Emacs ;; blocking, when Emacs enters minibuffer and block waiting for user ;; input, keyboard macro execution stops (deadlock! Emacs can't send ;; further input, because it waiting for this input), but when Emacs ;; is about to block, i.e. enter `read-from-minibuffer' XWEM installs ;; special itimer which track Emacs state and send next event if need. ;; This feature allow you to create keyboard macros which requires ;; interaction with user, for example 'H-x r xterm RET H-u H-z H-x b ;; emac RET'. When defining keyboard macro remember that XWEM can't ;; track window operations such as map, configure, destroy and others, ;; so please use `xwem-misc-pause' command(default binded to H-z to ;; sleep for a while). For example you need to define macro - 1) ;; start new xterm 2) in newly managed xterm enter "test it" text. It ;; will be something like: H-x ( H-a x H-u H-z test it H-x ), because ;; when you start new xterm application it does not appear on screen ;; immediately, but when executing keyboard macro all keystrokes are ;; sent to X server as fast as possible. ;; To save keyboard macros on on exit add something like: ;; ;; (add-hook 'xwem-exit-hook 'xwem-keymacro-save-macros) ;; ;; And to restore saved keyboard macros on start do next: ;; ;; (add-hook 'xwem-after-init-hook 'xwem-keymacro-load-macros) ;; ;;; Code: (require 'xlib-xtest) (require 'xlib-xrecord) (require 'xwem-load) (require 'xwem-misc) ;; Macros customization (defcustom xwem-keymacro-minib-bg "gray60" "*Background color for xwem's minibuffer while recording KBD macro. If nil - background will not change." :type 'color :group 'xwem-keyboard) (defcustom xwem-keymacro-debug nil "*Non-nil mean run keyboard macrosing stuff in debug mode." :type 'boolean :group 'xwem-keyboard) (defcustom xwem-keymacro-show-macro nil "*Non-nil mean show keyboard macro in minibuffer, while executing." :type 'boolean :group 'xwem-keyboard) (defcustom xwem-keymacro-macrofile "xwem-macros.el" "*Default filename where keyboard macros stores." :type 'file :group 'xwem-keyboard) ;;; Internal variables ;;; Macros recording/playing internal variables ;;;###xwem-autoload (defvar xwem-keymacro-macros-stack nil "List of defined keyboard macroses.") (defvar xwem-keymacro-macros-depth nil "Current depth of keyboard macro execution. INTERNAL VARIABLE.") (defvar xwem-keymacro-default-command 'xwem-keymacro-default "Default command while recording/playing macro. NOTE: may be changed to play macro.") (defvar xwem-keymacro-prefix-arg nil "Value of `xwem-prefix-arg' when entering macro recording. Internal variable, do not modify directly.") (defvar xwem-keymacro-minib-old-bg nil "Variable used to hold old backgroup of xwem's minibuffer. Internal variable, do not modify directly.") (defvar xwem-keymacro-saving nil "Non-nil mean that we saving macro now. Internal variable, do not modify it directly.") (defvar xwem-keymacro-dpy nil "Display used as data connection. Internal variable, do not modify it directly.") (defvar xwem-keymacro-rcontext nil "Record context used when keymacrosing. Internal variable, do not modify it directly.") (defvar xwem-keymacro-rclient nil "Client used when keymacrosing. Internal variable, do not modify it directly.") (defvar xwem-keymacro-rranges nil "Record ranges used when keymacrosing. Internal variable, do not modify it directly.") (defvar xwem-keymacro-initialized nil "Non-nil when keyboard macrosing initialized. Internal variable, do not modify it directly.") (defun xwem-keymacro-init () "Initialize keyboard macrosing stuff." (pushnew '(macro "Macro") xwem-messages-label-prefixes) ;; Use xlib-xrecord extension to intercept KeyPress/KeyRelease ;; events. (let ((xrec-ext (X-XRecordQueryVersion (xwem-dpy))) (xtest-ext (XQueryExtension (xwem-dpy) "XTEST"))) (when (or (null (car xrec-ext)) (null (car xtest-ext))) ;; No XRECORD or XTEST extension support (error 'xwem-error "RECORD or XTEST extension missing")) ;; (xwem-dpy) supports XRECORD extension (setq xwem-keymacro-rcontext (make-X-RecordContext :dpy (xwem-dpy) :id (X-Dpy-get-id (xwem-dpy)))) (setq xwem-keymacro-rranges ;; We are only interested in KeyPrees/KeyRelease events (list (make-X-RecordRange :device-events (cons X-KeyPress X-KeyRelease)))) (setq xwem-keymacro-rclient (float X-XRecordAllClients)) (setq xwem-keymacro-rcontext (X-XRecordCreateContext (xwem-dpy) xwem-keymacro-rcontext 0 (list xwem-keymacro-rclient) xwem-keymacro-rranges)) (X-XRecordRegisterClients (xwem-dpy) xwem-keymacro-rcontext 0 (list xwem-keymacro-rclient) xwem-keymacro-rranges) (setq xwem-keymacro-dpy (XOpenDisplay (format "%s:%d" (X-Dpy-name (xwem-dpy)) (X-Dpy-default-screen (xwem-dpy))))) (when xwem-keymacro-debug (setf (X-Dpy-log-buffer xwem-keymacro-dpy) "XREC.log")) (setq xwem-keymacro-initialized t))) (defun xwem-keymacro-extract (xevs &optional cutlen) "Extract keyboard macro from X-Events list XEVS. Return list of Emacs events. CUTLEN is how many events cut from the end (default is 1)." (let ((evs (butlast (xwem-xevents->emacs-events xevs nil) (or cutlen 1)))) (key-sequence-list-description (vconcat evs)))) ;;;###xwem-autoload (defun xwem-keymacro-executing-p () "Return non-nil if executing keyboard macro." (and (boundp 'xwem-keymacro-keys) (boundp 'xwem-keymacro-keys-index))) ;;;###xwem-autoload (defun xwem-keymacro-execute-keys (keys) "Execute keyboard macro KEYS." ;; XXX Adjust KEYS in case KEYS is 'self-insert (when (and (> (length keys) 1) (eq (aref keys 0) 'self-insert)) (setq keys (vector keys))) (let ((xwem-keymacro-keys keys) (xwem-keymacro-keys-index 0)) (while (< xwem-keymacro-keys-index (length xwem-keymacro-keys)) (xwem-dispatch-command-event (aref xwem-keymacro-keys xwem-keymacro-keys-index)) (incf xwem-keymacro-keys-index)))) (defun xwem-keymacro-start-recording () "Start recording Keys." ;; Clear events queue and enable context (setf (X-Dpy-evq xwem-keymacro-dpy) nil) (X-XRecordEnableContext xwem-keymacro-dpy xwem-keymacro-rcontext) (setq xwem-keymacro-saving t) ;; Change xwem's minibuffer background (when xwem-keymacro-minib-bg (setq xwem-keymacro-minib-old-bg (face-background-name 'default (xwem-minib-frame xwem-minibuffer))) (set-face-property 'default 'background xwem-keymacro-minib-bg (xwem-minib-frame xwem-minibuffer)))) (defun xwem-keymacro-stop-recording () "Stop recording Keys." (X-XRecordDisableContext (xwem-dpy) xwem-keymacro-rcontext) (XFlush (xwem-dpy)) (setq xwem-keymacro-saving nil) ;; Change xwem's minibuffer background back (when xwem-keymacro-minib-old-bg (set-face-property 'default 'background xwem-keymacro-minib-old-bg (xwem-minib-frame xwem-minibuffer)))) ;;;###autoload(autoload 'xwem-keymacro-begin "xwem-keymacro" nil t) (define-xwem-command xwem-keymacro-begin (arg) "Start to record keyboard macro. If used with prefix ARG, then query for bind after macro define." (xwem-interactive "P") (when (not xwem-keymacro-initialized) (error 'xwem-error "Keyboard macros not initialized, use `xwem-keymacro-init'")) (if xwem-keymacro-saving (xwem-message 'warning "Already defining macro...") (xwem-message 'macro "Defining KBD macro ...") (setq xwem-keymacro-prefix-arg arg) (xwem-keymacro-start-recording))) ;;;###autoload(autoload 'xwem-keymacro-end "xwem-keymacro" nil t) (define-xwem-command xwem-keymacro-end (arg) "Stop recording keyboard macro. If recording done with prefix argument, then query for key to bind." (xwem-interactive (list xwem-keymacro-prefix-arg)) (if (not xwem-keymacro-saving) (xwem-message 'warning "Not recording KBD macro.") ;; Wait last keyrelease, so `xwem-keymacro-extract' will cut keys ;; properly. (when (= (X-Event-type xwem-last-xevent) X-KeyPress) (xwem-kbd-wait-key-release (X-Event-xkey-keycode xwem-last-xevent))) (xwem-keymacro-stop-recording) (let ((kmacro (xwem-keymacro-extract (X-Dpy-evq xwem-keymacro-dpy) (length xwem-this-command-keys)))) (if (not xwem-keymacro-prefix-arg) ;; Save last keyboard macro (push kmacro xwem-keymacro-macros-stack) (let ((key (xwem-read-key "Enter character to bind: "))) (define-key 'xwem-user-macros-prefix (events-to-keys (vector key)) kmacro)))) (xwem-message 'macro "KBD macro defined."))) ;;;###xwem-autoload (defun xwem-keymacro-internal-play (keys &optional times) "Play Emacs KEYS TIMES times." (unless times (setq times 1)) ;; Unset some variables (setq xwem-this-command-keys []) (setq xwem-kbd-now-grabbing nil) (setq xwem-prefix-arg nil) (when xwem-keymacro-show-macro (xwem-message 'macro "Executing macro: '%s'%s" (key-description keys) (if (> times 1) (format " %d times" times) ""))) ;; Force release of modifiers (xwem-kbd-force-mods-release) ;; Execute KEYS TIMES times (dotimes (i times) (xwem-keymacro-execute-keys keys))) ;; Commands to be used in `xwem-keymacro-user-macros' ;;;###autoload(autoload 'xwem-keymacro-undefined "xwem-keymacro" nil t) (define-xwem-command xwem-keymacro-undefined () "Undefined macro command." (xwem-interactive) (xwem-message 'warning "Macro key `%s' is not defined" (key-description xwem-this-command-keys))) ;;;###autoload(autoload 'xwem-keymacro-play-last "xwem-keymacro") (define-xwem-command xwem-keymacro-play-last (arg) "Play last KBD macro ARG times." (xwem-interactive "*_p") (when (null xwem-keymacro-macros-stack) (error 'xwem-error "No KBD macros defined")) (if (null xwem-keymacro-macros-depth) (setq xwem-keymacro-macros-depth 0) (when (>= xwem-keymacro-macros-depth (1- (length xwem-keymacro-macros-stack))) (error 'xwem-error (format "Can't play macro of %d depth" (1+ xwem-keymacro-macros-depth)))) (incf xwem-keymacro-macros-depth)) (xwem-unwind-protect (xwem-keymacro-internal-play (nth xwem-keymacro-macros-depth xwem-keymacro-macros-stack) arg) (if (zerop xwem-keymacro-macros-depth) (setq xwem-keymacro-macros-depth nil) (decf xwem-keymacro-macros-depth)))) ;;;###autoload(autoload 'xwem-keymacro-recursive-edit "xwem-keymacro" nil t) (define-xwem-command xwem-keymacro-recursive-edit (arg) "Enter recursive edit. Using \\\\[xwem-keymacro-recursive-edit] you can change keyboard macro execution in different way, i.e. you can temporary suspend macro execution. Use \\\\[xwem-exit-recursive-edit] to exit recursive edit." (xwem-interactive "_P") (or (xwem-keymacro-executing-p) xwem-keymacro-saving (error 'xwem-error "Not defining or executing keyboard macro")) (let ((xwem-this-command-keys []) (xwem-prefix-arg nil)) (unless (xwem-keymacro-executing-p) (X-XRecordDisableContext (xwem-dpy) xwem-keymacro-rcontext)) (xwem-recursive-edit) (unless (xwem-keymacro-executing-p) (X-XRecordEnableContext xwem-keymacro-dpy xwem-keymacro-rcontext)) )) ;;;###autoload(autoload 'xwem-keymacro-exit-recursive-edit "xwem-keymacro" nil t) (define-xwem-command xwem-keymacro-exit-recursive-edit () "Exit recursive edition." (xwem-interactive "*") ;; XXX (when (xwem-keymacro-executing-p) (xwem-kbd-force-mods-release)) (xwem-exit-recursive-edit)) ;;;###autoload (defun xwem-keymacro-save-macros (&optional file) "Save all defined macros to FILE. Default value for FILE is ~/.xwem/xwem-macros.el." (let ((buf (get-buffer-create " * temp keymacro buffer *"))) (with-current-buffer buf (erase-buffer) (map-keymap #'(lambda (kseq fbind) (when (vectorp fbind) (insert (format "(define-key 'xwem-user-macros-prefix '%S %S)\n" kseq fbind)))) (xwem-kbd-fixup-keymap 'xwem-user-macros-prefix)) (write-file (or file (expand-file-name xwem-keymacro-macrofile xwem-dir))) (kill-buffer buf)))) ;;;###autoload (defun xwem-keymacro-load-macros (&optional file) "Load macros saved with `xwem-key-save-macros' from FILE. Default value for FILE is ~/.xwem/xwem-macros.el" (load (or file (expand-file-name xwem-keymacro-macrofile xwem-dir)))) (provide 'xwem-keymacro) ;;; On-load actions (if xwem-started (xwem-keymacro-init) (add-hook 'xwem-keyboard-init-hook 'xwem-keymacro-init)) ;;; xwem-keymacro.el ends here