avoid.el 15.5 KB
Newer Older
Richard M. Stallman's avatar
Richard M. Stallman committed
1
;;; avoid.el --- make mouse pointer stay out of the way of editing
Richard M. Stallman's avatar
Richard M. Stallman committed
2

Richard M. Stallman's avatar
Richard M. Stallman committed
3
;;; Copyright (C) 1993, 1994 Free Software Foundation, Inc.
Richard M. Stallman's avatar
Richard M. Stallman committed
4

Karl Heuer's avatar
Karl Heuer committed
5
;; Author: Boris Goldowsky <boris@gnu.org>
Richard M. Stallman's avatar
Richard M. Stallman committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
;; Keywords: mouse

;; This file is part of GNU Emacs.

;; GNU Emacs 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.

;; GNU Emacs 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
Erik Naggum's avatar
Erik Naggum committed
21 22 23
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
Richard M. Stallman's avatar
Richard M. Stallman committed
24 25 26

;;; Commentary:

Erik Naggum's avatar
Erik Naggum committed
27 28 29 30 31 32 33 34
;; For those who are annoyed by the mouse pointer obscuring text,
;; this mode moves the mouse pointer - either just a little out of
;; the way, or all the way to the corner of the frame. 
;; To use, load or evaluate this file and type M-x mouse-avoidance-mode .
;; To set up permanently, put the following in your .emacs: 
;;
;; (if window-system (mouse-avoidance-mode 'animate))
;;
35 36 37
;; Other legitimate alternatives include
;; `banish', `exile', `jump', `cat-and-mouse', and `proteus'.
;; They do somewhat different things.
Erik Naggum's avatar
Erik Naggum committed
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
;; See the documentation for function `mouse-avoidance-mode' for
;; details of the different modes.
;;
;; For added silliness, make the animatee animate...
;; put something similar to the following into your .emacs:
;;
;; (if window-system
;;     (mouse-avoidance-set-pointer-shape
;;	     (eval (nth (random 4)
;;			'(x-pointer-man x-pointer-spider
;;			  x-pointer-gobbler x-pointer-gumby)))))
;;
;; For completely random pointer shape, replace the setq above with:
;; (setq x-pointer-shape (mouse-avoidance-random-shape))
;; 
;; Bugs / Warnings / To-Do:
;;
;; - Using this code does slow emacs down.  "banish" mode shouldn't
;;   be too bad, and on my workstation even "animate" is reasonable.
;;
;; - It ought to find out where any overlapping frames are and avoid them,
;;   rather than always raising the frame.

;; Credits:
;; This code was helped by all those who contributed suggestions, 
;;   fixes, and additions
;; Joe Harrington (and his advisor), for the original inspiration.
;; Ken Manheimer, for dreaming up the Protean mode.
;; Richard Stallman, for the awful cat-and-mouse pun, among other things.
;; Mike Williams, Denis Howe, Bill Benedetto, Chris Moore, Don Morris,
;; Simon Marshall, and M.S. Ashton, for their feedback.

Richard M. Stallman's avatar
Richard M. Stallman committed
70 71 72 73
;;; Code:

(provide 'avoid)

74 75 76 77 78 79
(defgroup avoid nil
  "Make mouse pointer stay out of the way of editing."
  :prefix "mouse-avoidance-"
  :group 'mouse)


80 81 82
(defcustom mouse-avoidance-mode nil
  "Activate mouse avoidance mode.  
See function `mouse-avoidance-mode' for possible values.
Dave Love's avatar
Dave Love committed
83 84
Setting this variable directly does not take effect;
use either \\[customize] or the function `mouse-avoidance-mode'."
85 86 87 88 89 90 91 92
  :set (lambda (symbol value)
	 ;; 'none below prevents toggling when value is nil.
	 (mouse-avoidance-mode (or value 'none))) 
  :initialize 'custom-initialize-default
  :type '(choice (const :tag "none" nil) (const banish) (const jump) 
		 (const animate) (const exile) (const proteus)
		 )
  :group 'avoid
Dan Nicolaescu's avatar
Dan Nicolaescu committed
93 94
  :require 'avoid
  :version "20.3")
95

Richard M. Stallman's avatar
Richard M. Stallman committed
96

97
(defcustom mouse-avoidance-nudge-dist 15
Richard M. Stallman's avatar
Richard M. Stallman committed
98
  "*Average distance that mouse will be moved when approached by cursor.
99
Only applies in mouse-avoidance-mode `jump' and its derivatives.
100 101 102
For best results make this larger than `mouse-avoidance-threshold'."
  :type 'integer
  :group 'avoid)
Richard M. Stallman's avatar
Richard M. Stallman committed
103

104 105 106 107
(defcustom mouse-avoidance-nudge-var 10
  "*Variability of `mouse-avoidance-nudge-dist' (which see)."
  :type 'integer
  :group 'avoid)
Richard M. Stallman's avatar
Richard M. Stallman committed
108

109 110 111 112
(defcustom mouse-avoidance-animation-delay .01
  "Delay between animation steps, in seconds."
  :type 'number
  :group 'avoid)
Richard M. Stallman's avatar
Richard M. Stallman committed
113

114
(defcustom mouse-avoidance-threshold 5
Richard M. Stallman's avatar
Richard M. Stallman committed
115 116
  "*Mouse-pointer's flight distance.
If the cursor gets closer than this, the mouse pointer will move away.
117 118 119
Only applies in mouse-avoidance-modes `animate' and `jump'."
  :type 'integer
  :group 'avoid)
Richard M. Stallman's avatar
Richard M. Stallman committed
120

Karl Heuer's avatar
Karl Heuer committed
121 122
;; Internal variables
(defvar mouse-avoidance-state nil)
Richard M. Stallman's avatar
Richard M. Stallman committed
123 124
(defvar mouse-avoidance-pointer-shapes nil)
(defvar mouse-avoidance-n-pointer-shapes 0)
125
(defvar mouse-avoidance-old-pointer-shape nil)
Richard M. Stallman's avatar
Richard M. Stallman committed
126

127 128 129
;; This timer is used to run something when Emacs is idle.
(defvar mouse-avoidance-timer nil)

Richard M. Stallman's avatar
Richard M. Stallman committed
130 131
;;; Functions:

132 133 134 135 136
(defsubst mouse-avoidance-set-pointer-shape (shape)
  "Set the shape of the mouse pointer to SHAPE."
  (setq x-pointer-shape shape)
  (set-mouse-color nil))

Karl Heuer's avatar
Karl Heuer committed
137
(defun mouse-avoidance-point-position ()
Richard M. Stallman's avatar
Richard M. Stallman committed
138
  "Return the position of point as (FRAME X . Y).
Karl Heuer's avatar
Karl Heuer committed
139 140 141 142
Analogous to mouse-position."
  (let* ((w (selected-window))
	 (edges (window-edges w))
	 (list 
143 144 145 146
	  (compute-motion (max (window-start w) (point-min))   ; start pos
			  ;; window-start can be < point-min if the
			  ;; latter has changed since the last redisplay 
			  '(0 . 0)	                       ; start XY
Karl Heuer's avatar
Karl Heuer committed
147
			  (point)	                       ; stop pos
148
			  (cons (window-width) (window-height)); stop XY: none
Karl Heuer's avatar
Karl Heuer committed
149
			  (1- (window-width))                  ; width
150
			  (cons (window-hscroll w) 0)          ; 0 may not be right?
Karl Heuer's avatar
Karl Heuer committed
151 152 153
			  (selected-window))))
    ;; compute-motion returns (pos HPOS VPOS prevhpos contin)
    ;; we want:               (frame hpos . vpos)
154 155 156 157 158 159 160 161 162
    (cons (selected-frame)
	  (cons (+ (car edges)       (car (cdr list)))
		(+ (car (cdr edges)) (car (cdr (cdr list))))))))

;(defun mouse-avoidance-point-position-test ()
;  (interactive)
;  (message (format "point=%s mouse=%s" 
;		   (cdr (mouse-avoidance-point-position))
;		   (cdr (mouse-position)))))
Karl Heuer's avatar
Karl Heuer committed
163 164 165 166 167 168 169 170 171 172 173 174 175

(defun mouse-avoidance-set-mouse-position (pos)
  ;; Carefully set mouse position to given position (X . Y)
  ;; Ideally, should check if X,Y is in the current frame, and if not,
  ;; leave the mouse where it was.  However, this is currently
  ;; difficult to do, so we just raise the frame to avoid frame switches.
  ;; Returns t if it moved the mouse.
  (let ((f (selected-frame)))
    (raise-frame f)
    (set-mouse-position f (car pos) (cdr pos))
    t))
      
(defun mouse-avoidance-too-close-p (mouse)
Richard M. Stallman's avatar
Richard M. Stallman committed
176
  ;;  Return t if mouse pointer and point cursor are too close.
177
  ;; Acceptable distance is defined by mouse-avoidance-threshold.
Karl Heuer's avatar
Karl Heuer committed
178 179 180 181
  (let ((point (mouse-avoidance-point-position)))
    (and (eq (car mouse) (car point))
	 (car (cdr mouse))
	 (< (abs (- (car (cdr mouse)) (car (cdr point))))
182
	    mouse-avoidance-threshold)
Karl Heuer's avatar
Karl Heuer committed
183
	 (< (abs (- (cdr (cdr mouse)) (cdr (cdr point))))
184
	    mouse-avoidance-threshold))))
Richard M. Stallman's avatar
Richard M. Stallman committed
185

Karl Heuer's avatar
Karl Heuer committed
186
(defun mouse-avoidance-banish-destination ()
Richard M. Stallman's avatar
Richard M. Stallman committed
187
  "The position to which mouse-avoidance-mode `banish' moves the mouse.
Karl Heuer's avatar
Karl Heuer committed
188 189 190 191
You can redefine this if you want the mouse banished to a different corner."
 (cons (1- (frame-width))
       0))

Richard M. Stallman's avatar
Richard M. Stallman committed
192 193
(defun mouse-avoidance-banish-mouse ()
  ;; Put the mouse pointer in the upper-right corner of the current frame.
Karl Heuer's avatar
Karl Heuer committed
194 195 196 197 198 199 200
  (mouse-avoidance-set-mouse-position (mouse-avoidance-banish-destination)))

(defsubst mouse-avoidance-delta (cur delta dist var min max)
  ;; Decide how far to move in either dimension.
  ;; Args are the CURRENT location, the desired DELTA for
  ;; warp-conservation, the DISTANCE we like to move, the VARIABILITY
  ;; in distance allowed, and the MIN and MAX possible window positions.
Karl Heuer's avatar
Karl Heuer committed
201
  ;; Returns something as close to DELTA as possible within the constraints.
Karl Heuer's avatar
Karl Heuer committed
202 203 204 205 206 207 208 209 210 211 212 213 214 215
  (let ((L1 (max (- min cur) (+ (- dist) (- var))))
	(R1                  (+ (- dist)    var ))
	(L2                  (+    dist  (- var)))
	(R2 (min (- max cur) (+    dist     var))))
    (if (< R1 (- min cur)) (setq L1 nil R1 nil))
    (if (> L2 (- max cur)) (setq L2 nil R2 nil))
    (cond ((and L1 (< delta L1)) L1)
	  ((and R1 (< delta R1)) delta)
	  ((and R1 (< delta 0)) R1)
	  ((and L2 (< delta L2)) L2)
	  ((and R2 (< delta R2)) delta)
	  (R2)
	  ((or R1 L2))
	  (t 0))))
Richard M. Stallman's avatar
Richard M. Stallman committed
216 217 218

(defun mouse-avoidance-nudge-mouse () 
  ;; Push the mouse a little way away, possibly animating the move
Karl Heuer's avatar
Karl Heuer committed
219 220
  ;; For these modes, state keeps track of the total offset that we've
  ;; accumulated, and tries to keep it close to zero.
Richard M. Stallman's avatar
Richard M. Stallman committed
221
  (let* ((cur (mouse-position))
Karl Heuer's avatar
Karl Heuer committed
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
	 (cur-frame (car cur))
	 (cur-pos (cdr cur))
	 (deltax (mouse-avoidance-delta 
		  (car cur-pos) (- (random mouse-avoidance-nudge-var)
				   (car mouse-avoidance-state))
		  mouse-avoidance-nudge-dist mouse-avoidance-nudge-var
		  0 (frame-width)))
	 (deltay (mouse-avoidance-delta 
		  (cdr cur-pos) (- (random mouse-avoidance-nudge-var)
				   (cdr mouse-avoidance-state))
		  mouse-avoidance-nudge-dist mouse-avoidance-nudge-var
		  0 (frame-height))))
    (setq mouse-avoidance-state
	  (cons (+ (car mouse-avoidance-state) deltax)
		(+ (cdr mouse-avoidance-state) deltay)))
Richard M. Stallman's avatar
Richard M. Stallman committed
237 238
    (if (or (eq mouse-avoidance-mode 'animate) 
	    (eq mouse-avoidance-mode 'proteus))
239
	(let ((i 0.0))
Richard M. Stallman's avatar
Richard M. Stallman committed
240
	  (while (<= i 1)
Karl Heuer's avatar
Karl Heuer committed
241 242 243 244
	    (mouse-avoidance-set-mouse-position 
	     (cons (+ (car cur-pos) (round (* i deltax)))
		   (+ (cdr cur-pos) (round (* i deltay)))))
    	    (setq i (+ i (max .1 (/ 1.0 mouse-avoidance-nudge-dist))))
Richard M. Stallman's avatar
Richard M. Stallman committed
245
	    (if (eq mouse-avoidance-mode 'proteus)
246 247
		(mouse-avoidance-set-pointer-shape 
		 (mouse-avoidance-random-shape)))
Richard M. Stallman's avatar
Richard M. Stallman committed
248
	    (sit-for mouse-avoidance-animation-delay)))
Richard M. Stallman's avatar
Richard M. Stallman committed
249 250
      (mouse-avoidance-set-mouse-position (cons (+ (car (cdr cur)) deltax)
						(+ (cdr (cdr cur)) deltay))))))
Richard M. Stallman's avatar
Richard M. Stallman committed
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269

(defun mouse-avoidance-random-shape ()
  "Return a random cursor shape.
This assumes that any variable whose name begins with x-pointer- and
has an integer value is a valid cursor shape.  You might want to
redefine this function to suit your own tastes."
  (if (null mouse-avoidance-pointer-shapes)
      (progn
	(setq mouse-avoidance-pointer-shapes
	      (mapcar '(lambda (x) (symbol-value (intern x)))
		      (all-completions "x-pointer-" obarray
				       '(lambda (x) 
					  (and (boundp x)
					       (integerp (symbol-value x)))))))
	(setq mouse-avoidance-n-pointer-shapes 
	      (length mouse-avoidance-pointer-shapes))))
  (nth (random mouse-avoidance-n-pointer-shapes)
       mouse-avoidance-pointer-shapes))

Karl Heuer's avatar
Karl Heuer committed
270 271
(defun mouse-avoidance-banish-hook ()
  (if (and (not executing-kbd-macro)	; don't check inside macro
272
	   (cadr (mouse-position))      ; don't move unless in an Emacs frame
273
	   ;; Don't do anything if last event was a mouse event.
274 275
	   (not (and (consp last-input-event)
		     (symbolp (car last-input-event))
276 277 278 279 280 281
		     (let ((modifiers (event-modifiers (car last-input-event))))
		       (or (memq (car last-input-event)
				 '(mouse-movement scroll-bar-movement))
			   (memq 'click modifiers)
			   (memq 'drag modifiers)
			   (memq 'down modifiers))))))
Karl Heuer's avatar
Karl Heuer committed
282 283 284 285 286 287
      (mouse-avoidance-banish-mouse)))

(defun mouse-avoidance-exile-hook ()
  ;; For exile mode, the state is nil when the mouse is in its normal
  ;; position, and set to the old mouse-position when the mouse is in exile.
  (if (and (not executing-kbd-macro)
288
	   ;; Don't do anything if last event was a mouse event.
289 290
	   (not (and (consp last-input-event)
		     (symbolp (car last-input-event))
291 292 293 294 295 296
		     (let ((modifiers (event-modifiers (car last-input-event))))
		       (or (memq (car last-input-event)
				 '(mouse-movement scroll-bar-movement))
			   (memq 'click modifiers)
			   (memq 'drag modifiers)
			   (memq 'down modifiers))))))
Karl Heuer's avatar
Karl Heuer committed
297 298 299 300 301 302 303 304 305 306 307 308 309 310
      (let ((mp (mouse-position)))
	(cond ((and (not mouse-avoidance-state)
		    (mouse-avoidance-too-close-p mp))
	       (setq mouse-avoidance-state mp)
	       (mouse-avoidance-banish-mouse))
	      ((and mouse-avoidance-state
		    (not (mouse-avoidance-too-close-p mouse-avoidance-state)))
	       (if (and (eq (car mp) (selected-frame))
			(equal (cdr mp) (mouse-avoidance-banish-destination)))
		   (mouse-avoidance-set-mouse-position
		    ;; move back only if user has not moved mouse
		    (cdr mouse-avoidance-state)))
	       ;; but clear state anyway, to be ready for another move
	       (setq mouse-avoidance-state nil))))))
Richard M. Stallman's avatar
Richard M. Stallman committed
311 312

(defun mouse-avoidance-fancy-hook ()
Karl Heuer's avatar
Karl Heuer committed
313 314
  ;; Used for the "fancy" modes, ie jump et al.
  (if (and (not executing-kbd-macro)	; don't check inside macro
315
	   ;; Don't do anything if last event was a mouse event.
316 317
	   (not (and (consp last-input-event)
		     (symbolp (car last-input-event))
318 319 320 321 322 323
		     (let ((modifiers (event-modifiers (car last-input-event))))
		       (or (memq (car last-input-event)
				 '(mouse-movement scroll-bar-movement))
			   (memq 'click modifiers)
			   (memq 'drag modifiers)
			   (memq 'down modifiers)))))
Karl Heuer's avatar
Karl Heuer committed
324
	   (mouse-avoidance-too-close-p (mouse-position)))
Richard M. Stallman's avatar
Richard M. Stallman committed
325 326
      (let ((old-pos (mouse-position)))
	(mouse-avoidance-nudge-mouse)
327 328 329
	(if (not (eq (selected-frame) (car old-pos)))
	    ;; This should never happen.
	    (apply 'set-mouse-position old-pos)))))
Richard M. Stallman's avatar
Richard M. Stallman committed
330

331
;;;###autoload
Richard M. Stallman's avatar
Richard M. Stallman committed
332 333
(defun mouse-avoidance-mode (&optional mode)
  "Set cursor avoidance mode to MODE.
Karl Heuer's avatar
Karl Heuer committed
334
MODE should be one of the symbols `banish', `exile', `jump', `animate',
Richard M. Stallman's avatar
Richard M. Stallman committed
335 336
`cat-and-mouse', `proteus', or `none'.

Karl Heuer's avatar
Karl Heuer committed
337
If MODE is nil, toggle mouse avoidance between `none` and `banish'
Richard M. Stallman's avatar
Richard M. Stallman committed
338 339 340 341
modes.  Positive numbers and symbols other than the above are treated
as equivalent to `banish'; negative numbers and `-' are equivalent to `none'.

Effects of the different modes: 
Richard M. Stallman's avatar
Richard M. Stallman committed
342 343
 * banish: Move the mouse to the upper-right corner on any keypress.
 * exile: Move the mouse to the corner only if the cursor gets too close,
Karl Heuer's avatar
Karl Heuer committed
344
     and allow it to return once the cursor is out of the way.
Richard M. Stallman's avatar
Richard M. Stallman committed
345
 * jump: If the cursor gets too close to the mouse, displace the mouse
Karl Heuer's avatar
Karl Heuer committed
346
     a random distance & direction.
Richard M. Stallman's avatar
Richard M. Stallman committed
347 348 349
 * animate: As `jump', but shows steps along the way for illusion of motion.
 * cat-and-mouse: Same as `animate'.
 * proteus: As `animate', but changes the shape of the mouse pointer too.
Richard M. Stallman's avatar
Richard M. Stallman committed
350

Karl Heuer's avatar
Karl Heuer committed
351 352
Whenever the mouse is moved, the frame is also raised.

353
\(see `mouse-avoidance-threshold' for definition of \"too close\",
Richard M. Stallman's avatar
Richard M. Stallman committed
354 355
and `mouse-avoidance-nudge-dist' and `mouse-avoidance-nudge-var' for
definition of \"random distance\".)"
Richard M. Stallman's avatar
Richard M. Stallman committed
356 357 358
  (interactive
   (list (intern (completing-read
		  "Select cursor avoidance technique (SPACE for list): "
Karl Heuer's avatar
Karl Heuer committed
359 360
		  '(("banish") ("exile") ("jump") ("animate")
		    ("cat-and-mouse") ("proteus") ("none"))
Richard M. Stallman's avatar
Richard M. Stallman committed
361 362 363
		  nil t))))
  (if (eq mode 'cat-and-mouse)
      (setq mode 'animate))
364 365 366
  (if mouse-avoidance-timer
      (cancel-timer mouse-avoidance-timer))
  (setq mouse-avoidance-timer nil)
367 368 369 370 371 372

  ;; Restore pointer shape if necessary
  (if (eq mouse-avoidance-mode 'proteus)
      (mouse-avoidance-set-pointer-shape mouse-avoidance-old-pointer-shape))

  ;; Do additional setup depending on version of mode requested
Richard M. Stallman's avatar
Richard M. Stallman committed
373 374 375 376 377
  (cond	((eq mode 'none)
	 (setq mouse-avoidance-mode nil))
	((or (eq mode 'jump)
	     (eq mode 'animate)
	     (eq mode 'proteus))
378 379
	 (setq mouse-avoidance-timer
	       (run-with-idle-timer 0.1 t 'mouse-avoidance-fancy-hook))
Karl Heuer's avatar
Karl Heuer committed
380
	 (setq mouse-avoidance-mode mode
381 382
	       mouse-avoidance-state (cons 0 0)
	       mouse-avoidance-old-pointer-shape x-pointer-shape))
Karl Heuer's avatar
Karl Heuer committed
383
	((eq mode 'exile)
384 385
	 (setq mouse-avoidance-timer
	       (run-with-idle-timer 0.1 t 'mouse-avoidance-exile-hook))
Karl Heuer's avatar
Karl Heuer committed
386 387
	 (setq mouse-avoidance-mode mode
	       mouse-avoidance-state nil))
Richard M. Stallman's avatar
Richard M. Stallman committed
388 389 390 391
	((or (eq mode 'banish) 
	     (eq mode t)
	     (and (null mode) (null mouse-avoidance-mode))
	     (and mode (> (prefix-numeric-value mode) 0)))
392 393
	 (setq mouse-avoidance-timer
	       (run-with-idle-timer 0.1 t 'mouse-avoidance-banish-hook))
Richard M. Stallman's avatar
Richard M. Stallman committed
394
	 (setq mouse-avoidance-mode 'banish))
Karl Heuer's avatar
Karl Heuer committed
395 396
	(t (setq mouse-avoidance-mode nil)))
  (force-mode-line-update))
Richard M. Stallman's avatar
Richard M. Stallman committed
397

398 399 400 401 402
;; Most people who use avoid mode leave it on all the time, so it's not
;; very informative to announce it in the mode line.
;;(or (assq 'mouse-avoidance-mode minor-mode-alist)
;;    (setq minor-mode-alist (cons '(mouse-avoidance-mode " Avoid")
;;				 minor-mode-alist)))
Richard M. Stallman's avatar
Richard M. Stallman committed
403

404 405 406 407 408
;; Needed for custom.
(if mouse-avoidance-mode 
    (mouse-avoidance-mode mouse-avoidance-mode))

;;; avoid.el ends here