1 ;;; xwem-keymacro.el --- Recording/playing keyboard macros.
3 ;; Copyright (C) 2003-2005 by XWEM Org.
5 ;; Author: Zajcev Evgeny <zevlg@yandex.ru>
6 ;; Steve Youngs <steve@youngs.au.com>
7 ;; Created: Fri Dec 12 17:18:00 MSK 2003
8 ;; Keywords: xwem, xlib
9 ;; X-CVS: $Id: xwem-keymacro.el,v 1.11 2005-04-04 19:54:13 lg Exp $
11 ;; This file is part of XWEM.
13 ;; XWEM is free software; you can redistribute it and/or modify it
14 ;; under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation; either version 2, or (at your option)
18 ;; XWEM is distributed in the hope that it will be useful, but WITHOUT
19 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
20 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
21 ;; License for more details.
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with XEmacs; see the file COPYING. If not, write to the Free
25 ;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
28 ;;; Synched up with: Not in FSF
32 ;; XWEM uses Record X extension to collect keypresses when saving
33 ;; keyboard macros, this method is tranparent to user and allow
34 ;; catching events without need of keyboard grabbing.
36 ;; XWEM keyboard macro is the same as Emacs keyboard macro. It is a
37 ;; vector of Emacs events. XWEM uses special keymap -
38 ;; `xwem-user-macros-map' to hold user defined macros. This keymap
39 ;; used to save/restore keyboard macros across XWEM sessions.
41 ;; XWEM's support for keyboard macros is full-featured. It includes
42 ;; H-x q command to start recursive edition, command which allow to
43 ;; change keyboard macro execution on fly. It has workaround Emacs
44 ;; blocking, when Emacs enters minibuffer and block waiting for user
45 ;; input, keyboard macro execution stops (deadlock! Emacs can't send
46 ;; further input, because it waiting for this input), but when Emacs
47 ;; is about to block, i.e. enter `read-from-minibuffer' XWEM installs
48 ;; special itimer which track Emacs state and send next event if need.
49 ;; This feature allow you to create keyboard macros which requires
50 ;; interaction with user, for example 'H-x r xterm RET H-u H-z H-x b
51 ;; emac RET'. When defining keyboard macro remember that XWEM can't
52 ;; track window operations such as map, configure, destroy and others,
53 ;; so please use `xwem-misc-pause' command(default binded to H-z to
54 ;; sleep for a while). For example you need to define macro - 1)
55 ;; start new xterm 2) in newly managed xterm enter "test it" text. It
56 ;; will be something like: H-x ( H-a x H-u H-z test it H-x ), because
57 ;; when you start new xterm application it does not appear on screen
58 ;; immediately, but when executing keyboard macro all keystrokes are
59 ;; sent to X server as fast as possible.
61 ;; To save keyboard macros on on exit add something like:
63 ;; (add-hook 'xwem-exit-hook 'xwem-keymacro-save-macros)
65 ;; And to restore saved keyboard macros on start do next:
67 ;; (add-hook 'xwem-after-init-hook 'xwem-keymacro-load-macros)
74 (require 'xlib-xrecord)
80 ;; Macros customization
81 (defcustom xwem-keymacro-minib-bg "gray60"
82 "*Background color for xwem's minibuffer while recording KBD macro.
83 If nil - background will not change."
85 :group 'xwem-keyboard)
87 (defcustom xwem-keymacro-debug nil
88 "*Non-nil mean run keyboard macrosing stuff in debug mode."
90 :group 'xwem-keyboard)
92 (defcustom xwem-keymacro-show-macro nil
93 "*Non-nil mean show keyboard macro in minibuffer, while executing."
95 :group 'xwem-keyboard)
97 (defcustom xwem-keymacro-macrofile "xwem-macros.el"
98 "*Default filename where keyboard macros stores."
100 :group 'xwem-keyboard)
102 ;;; Internal variables
105 ;;; Macros recording/playing internal variables
107 (defvar xwem-keymacro-macros-stack nil
108 "List of defined keyboard macroses.")
110 (defvar xwem-keymacro-macros-depth nil
111 "Current depth of keyboard macro execution.
114 (defvar xwem-keymacro-default-command 'xwem-keymacro-default
115 "Default command while recording/playing macro.
116 NOTE: may be changed to play macro.")
118 (defvar xwem-keymacro-prefix-arg nil
119 "Value of `xwem-prefix-arg' when entering macro recording.
120 Internal variable, do not modify directly.")
122 (defvar xwem-keymacro-minib-old-bg nil
123 "Variable used to hold old backgroup of xwem's minibuffer.
124 Internal variable, do not modify directly.")
126 (defvar xwem-keymacro-saving nil
127 "Non-nil mean that we saving macro now.
128 Internal variable, do not modify it directly.")
129 (defvar xwem-keymacro-dpy nil
130 "Display used as data connection.
131 Internal variable, do not modify it directly.")
132 (defvar xwem-keymacro-rcontext nil
133 "Record context used when keymacrosing.
134 Internal variable, do not modify it directly.")
135 (defvar xwem-keymacro-rclient nil
136 "Client used when keymacrosing.
137 Internal variable, do not modify it directly.")
138 (defvar xwem-keymacro-rranges nil
139 "Record ranges used when keymacrosing.
140 Internal variable, do not modify it directly.")
142 (defvar xwem-keymacro-initialized nil
143 "Non-nil when keyboard macrosing initialized.
144 Internal variable, do not modify it directly.")
146 (defun xwem-keymacro-init ()
147 "Initialize keyboard macrosing stuff."
148 (pushnew '(macro "Macro") xwem-messages-label-prefixes)
150 ;; Use xlib-xrecord extension to intercept KeyPress/KeyRelease
152 (let ((xrec-ext (X-XRecordQueryVersion (xwem-dpy)))
153 (xtest-ext (XQueryExtension (xwem-dpy) "XTEST")))
154 (when (or (null (car xrec-ext))
155 (null (car xtest-ext)))
156 ;; No XRECORD or XTEST extension support
157 (error 'xwem-error "RECORD or XTEST extension missing"))
159 ;; (xwem-dpy) supports XRECORD extension
160 (setq xwem-keymacro-rcontext (make-X-RecordContext
162 :id (X-Dpy-get-id (xwem-dpy))))
163 (setq xwem-keymacro-rranges
164 ;; We are only interested in KeyPrees/KeyRelease events
165 (list (make-X-RecordRange
166 :device-events (cons X-KeyPress X-KeyRelease))))
168 (setq xwem-keymacro-rclient (float X-XRecordAllClients))
169 (setq xwem-keymacro-rcontext (X-XRecordCreateContext
170 (xwem-dpy) xwem-keymacro-rcontext 0
171 (list xwem-keymacro-rclient)
172 xwem-keymacro-rranges))
173 (X-XRecordRegisterClients (xwem-dpy) xwem-keymacro-rcontext 0
174 (list xwem-keymacro-rclient)
175 xwem-keymacro-rranges)
177 (setq xwem-keymacro-dpy (XOpenDisplay
178 (format "%s:%d" (X-Dpy-name (xwem-dpy))
179 (X-Dpy-default-screen (xwem-dpy)))))
180 (when xwem-keymacro-debug
181 (setf (X-Dpy-log-buffer xwem-keymacro-dpy) "XREC.log"))
183 (setq xwem-keymacro-initialized t)))
185 (defun xwem-keymacro-extract (xevs &optional cutlen)
186 "Extract keyboard macro from X-Events list XEVS.
187 Return list of Emacs events.
188 CUTLEN is how many events cut from the end (default is 1)."
189 (let ((evs (butlast (xwem-xevents->emacs-events xevs nil)
191 (key-sequence-list-description (vconcat evs))))
194 (defun xwem-keymacro-executing-p ()
195 "Return non-nil if executing keyboard macro."
196 (and (boundp 'xwem-keymacro-keys)
197 (boundp 'xwem-keymacro-keys-index)))
200 (defun xwem-keymacro-execute-keys (keys)
201 "Execute keyboard macro KEYS."
202 ;; XXX Adjust KEYS in case KEYS is 'self-insert
203 (when (and (> (length keys) 1)
204 (eq (aref keys 0) 'self-insert))
205 (setq keys (vector keys)))
207 (let ((xwem-keymacro-keys keys)
208 (xwem-keymacro-keys-index 0))
209 (while (< xwem-keymacro-keys-index (length xwem-keymacro-keys))
210 (xwem-dispatch-command-event
211 (aref xwem-keymacro-keys xwem-keymacro-keys-index))
212 (incf xwem-keymacro-keys-index))))
214 (defun xwem-keymacro-start-recording ()
215 "Start recording Keys."
216 ;; Clear events queue and enable context
217 (setf (X-Dpy-evq xwem-keymacro-dpy) nil)
218 (X-XRecordEnableContext xwem-keymacro-dpy xwem-keymacro-rcontext)
219 (setq xwem-keymacro-saving t)
221 ;; Change xwem's minibuffer background
222 (when xwem-keymacro-minib-bg
223 (setq xwem-keymacro-minib-old-bg
224 (face-background-name 'default (xwem-minib-frame xwem-minibuffer)))
225 (set-face-property 'default 'background xwem-keymacro-minib-bg
226 (xwem-minib-frame xwem-minibuffer))))
228 (defun xwem-keymacro-stop-recording ()
229 "Stop recording Keys."
230 (X-XRecordDisableContext (xwem-dpy) xwem-keymacro-rcontext)
232 (setq xwem-keymacro-saving nil)
234 ;; Change xwem's minibuffer background back
235 (when xwem-keymacro-minib-old-bg
236 (set-face-property 'default 'background xwem-keymacro-minib-old-bg
237 (xwem-minib-frame xwem-minibuffer))))
239 ;;;###autoload(autoload 'xwem-keymacro-begin "xwem-keymacro" nil t)
240 (define-xwem-command xwem-keymacro-begin (arg)
241 "Start to record keyboard macro.
242 If used with prefix ARG, then query for bind after macro define."
243 (xwem-interactive "P")
245 (when (not xwem-keymacro-initialized)
246 (error 'xwem-error "Keyboard macros not initialized, use `xwem-keymacro-init'"))
248 (if xwem-keymacro-saving
249 (xwem-message 'warning "Already defining macro...")
251 (xwem-message 'macro "Defining KBD macro ...")
252 (setq xwem-keymacro-prefix-arg arg)
253 (xwem-keymacro-start-recording)))
255 ;;;###autoload(autoload 'xwem-keymacro-end "xwem-keymacro" nil t)
256 (define-xwem-command xwem-keymacro-end (arg)
257 "Stop recording keyboard macro.
258 If recording done with prefix argument, then query for key to bind."
259 (xwem-interactive (list xwem-keymacro-prefix-arg))
261 (if (not xwem-keymacro-saving)
262 (xwem-message 'warning "Not recording KBD macro.")
264 ;; Wait last keyrelease, so `xwem-keymacro-extract' will cut keys
266 (when (= (X-Event-type xwem-last-xevent) X-KeyPress)
267 (xwem-kbd-wait-key-release (X-Event-xkey-keycode xwem-last-xevent)))
269 (xwem-keymacro-stop-recording)
270 (let ((kmacro (xwem-keymacro-extract (X-Dpy-evq xwem-keymacro-dpy)
271 (length xwem-this-command-keys))))
272 (if (not xwem-keymacro-prefix-arg)
273 ;; Save last keyboard macro
274 (push kmacro xwem-keymacro-macros-stack)
276 (let ((key (xwem-read-key "Enter character to bind: ")))
277 (define-key 'xwem-user-macros-prefix (events-to-keys (vector key)) kmacro))))
279 (xwem-message 'macro "KBD macro defined.")))
282 (defun xwem-keymacro-internal-play (keys &optional times)
283 "Play Emacs KEYS TIMES times."
284 (unless times (setq times 1))
286 ;; Unset some variables
287 (setq xwem-this-command-keys [])
288 (setq xwem-kbd-now-grabbing nil)
289 (setq xwem-prefix-arg nil)
291 (when xwem-keymacro-show-macro
292 (xwem-message 'macro "Executing macro: '%s'%s"
293 (key-description keys)
295 (format " %d times" times)
298 ;; Force release of modifiers
299 (xwem-kbd-force-mods-release)
301 ;; Execute KEYS TIMES times
303 (xwem-keymacro-execute-keys keys)))
305 ;; Commands to be used in `xwem-keymacro-user-macros'
306 ;;;###autoload(autoload 'xwem-keymacro-undefined "xwem-keymacro" nil t)
307 (define-xwem-command xwem-keymacro-undefined ()
308 "Undefined macro command."
311 (xwem-message 'warning "Macro key `%s' is not defined"
312 (key-description xwem-this-command-keys)))
314 ;;;###autoload(autoload 'xwem-keymacro-play-last "xwem-keymacro")
315 (define-xwem-command xwem-keymacro-play-last (arg)
316 "Play last KBD macro ARG times."
317 (xwem-interactive "*_p")
319 (when (null xwem-keymacro-macros-stack)
320 (error 'xwem-error "No KBD macros defined"))
322 (if (null xwem-keymacro-macros-depth)
323 (setq xwem-keymacro-macros-depth 0)
324 (when (>= xwem-keymacro-macros-depth
325 (1- (length xwem-keymacro-macros-stack)))
326 (error 'xwem-error (format "Can't play macro of %d depth"
327 (1+ xwem-keymacro-macros-depth))))
328 (incf xwem-keymacro-macros-depth))
331 (xwem-keymacro-internal-play
332 (nth xwem-keymacro-macros-depth xwem-keymacro-macros-stack) arg)
334 (if (zerop xwem-keymacro-macros-depth)
335 (setq xwem-keymacro-macros-depth nil)
336 (decf xwem-keymacro-macros-depth))))
338 ;;;###autoload(autoload 'xwem-keymacro-recursive-edit "xwem-keymacro" nil t)
339 (define-xwem-command xwem-keymacro-recursive-edit (arg)
340 "Enter recursive edit.
341 Using \\<xwem-global-map>\\[xwem-keymacro-recursive-edit] you can
342 change keyboard macro execution in different way, i.e. you can
343 temporary suspend macro execution. Use
344 \\<xwem-global-map>\\[xwem-exit-recursive-edit] to exit recursive
346 (xwem-interactive "_P")
348 (or (xwem-keymacro-executing-p)
350 (error 'xwem-error "Not defining or executing keyboard macro"))
352 (let ((xwem-this-command-keys [])
353 (xwem-prefix-arg nil))
355 (unless (xwem-keymacro-executing-p)
356 (X-XRecordDisableContext (xwem-dpy) xwem-keymacro-rcontext))
358 (xwem-recursive-edit)
360 (unless (xwem-keymacro-executing-p)
361 (X-XRecordEnableContext xwem-keymacro-dpy xwem-keymacro-rcontext))
364 ;;;###autoload(autoload 'xwem-keymacro-exit-recursive-edit "xwem-keymacro" nil t)
365 (define-xwem-command xwem-keymacro-exit-recursive-edit ()
366 "Exit recursive edition."
367 (xwem-interactive "*")
370 (when (xwem-keymacro-executing-p)
371 (xwem-kbd-force-mods-release))
373 (xwem-exit-recursive-edit))
376 (defun xwem-keymacro-save-macros (&optional file)
377 "Save all defined macros to FILE.
378 Default value for FILE is ~/.xwem/xwem-macros.el."
379 (let ((buf (get-buffer-create " * temp keymacro buffer *")))
380 (with-current-buffer buf
383 (map-keymap #'(lambda (kseq fbind)
384 (when (vectorp fbind)
385 (insert (format "(define-key 'xwem-user-macros-prefix '%S %S)\n" kseq fbind))))
386 (xwem-kbd-fixup-keymap 'xwem-user-macros-prefix))
388 (write-file (or file (expand-file-name xwem-keymacro-macrofile xwem-dir)))
392 (defun xwem-keymacro-load-macros (&optional file)
393 "Load macros saved with `xwem-key-save-macros' from FILE.
394 Default value for FILE is ~/.xwem/xwem-macros.el"
395 (load (or file (expand-file-name xwem-keymacro-macrofile xwem-dir))))
398 (provide 'xwem-keymacro)
403 (add-hook 'xwem-keyboard-init-hook 'xwem-keymacro-init))
405 ;;; xwem-keymacro.el ends here