avoid.el 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. ;;; avoid.el --- make mouse pointer stay out of the way of editing
  2. ;; Copyright (C) 1993-1994, 2000-2017 Free Software Foundation, Inc.
  3. ;; Author: Boris Goldowsky <boris@gnu.org>
  4. ;; Keywords: mouse
  5. ;; This file is part of GNU Emacs.
  6. ;; GNU Emacs is free software: you can redistribute it and/or modify
  7. ;; it under the terms of the GNU General Public License as published by
  8. ;; the Free Software Foundation, either version 3 of the License, or
  9. ;; (at your option) any later version.
  10. ;; GNU Emacs is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ;; GNU General Public License for more details.
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;; For those who are annoyed by the mouse pointer obscuring text,
  18. ;; this mode moves the mouse pointer - either just a little out of
  19. ;; the way, or all the way to the corner of the frame.
  20. ;; To use, load or evaluate this file and type M-x mouse-avoidance-mode .
  21. ;; To set up permanently, put the following in your .emacs:
  22. ;;
  23. ;; (if (display-mouse-p) (mouse-avoidance-mode 'animate))
  24. ;;
  25. ;; Other legitimate alternatives include
  26. ;; `banish', `exile', `jump', `cat-and-mouse', and `proteus'.
  27. ;; They do somewhat different things.
  28. ;; See the documentation for function `mouse-avoidance-mode' for
  29. ;; details of the different modes.
  30. ;;
  31. ;; For added silliness, make the animatee animate...
  32. ;; put something similar to the following into your .emacs:
  33. ;;
  34. ;; (if (eq window-system 'x)
  35. ;; (mouse-avoidance-set-pointer-shape
  36. ;; (nth (random 4)
  37. ;; (list x-pointer-man x-pointer-spider
  38. ;; x-pointer-gobbler x-pointer-gumby))))
  39. ;;
  40. ;; For completely random pointer shape, replace the setq above with:
  41. ;; (setq x-pointer-shape (mouse-avoidance-random-shape))
  42. ;;
  43. ;; Bugs / Warnings / To-Do:
  44. ;;
  45. ;; - Using this code does slow Emacs down. "banish" mode shouldn't
  46. ;; be too bad, and on my workstation even "animate" is reasonable.
  47. ;; Credits:
  48. ;; This code was helped by all those who contributed suggestions,
  49. ;; fixes, and additions
  50. ;; Joe Harrington (and his advisor), for the original inspiration.
  51. ;; Ken Manheimer, for dreaming up the Protean mode.
  52. ;; Richard Stallman, for the awful cat-and-mouse pun, among other things.
  53. ;; Mike Williams, Denis Howe, Bill Benedetto, Chris Moore, Don Morris,
  54. ;; Simon Marshall, and M.S. Ashton, for their feedback.
  55. ;;; Code:
  56. (eval-when-compile (require 'cl-lib))
  57. (defgroup avoid nil
  58. "Make mouse pointer stay out of the way of editing."
  59. :prefix "mouse-avoidance-"
  60. :group 'mouse)
  61. ;;;###autoload
  62. (defcustom mouse-avoidance-mode nil
  63. "Activate Mouse Avoidance mode.
  64. See function `mouse-avoidance-mode' for possible values.
  65. Setting this variable directly does not take effect;
  66. use either \\[customize] or the function `mouse-avoidance-mode'."
  67. :set (lambda (_symbol value)
  68. ;; 'none below prevents toggling when value is nil.
  69. (mouse-avoidance-mode (or value 'none)))
  70. :initialize 'custom-initialize-default
  71. :type '(choice (const :tag "none" nil) (const banish) (const jump)
  72. (const animate) (const exile) (const proteus))
  73. :group 'avoid
  74. :require 'avoid
  75. :version "20.3")
  76. (defcustom mouse-avoidance-nudge-dist 15
  77. "Average distance that mouse will be moved when approached by cursor.
  78. Only applies in Mouse Avoidance mode `jump' and its derivatives.
  79. For best results make this larger than `mouse-avoidance-threshold'."
  80. :type 'integer
  81. :group 'avoid)
  82. (defcustom mouse-avoidance-nudge-var 10
  83. "Variability of `mouse-avoidance-nudge-dist' (which see)."
  84. :type 'integer
  85. :group 'avoid)
  86. (defcustom mouse-avoidance-animation-delay .01
  87. "Delay between animation steps, in seconds."
  88. :type 'number
  89. :group 'avoid)
  90. (defcustom mouse-avoidance-threshold 5
  91. "Mouse-pointer's flight distance.
  92. If the cursor gets closer than this, the mouse pointer will move away.
  93. Only applies in Mouse Avoidance modes `animate' and `jump'."
  94. :type 'integer
  95. :group 'avoid)
  96. (defcustom mouse-avoidance-banish-position '((frame-or-window . frame)
  97. (side . right)
  98. (side-pos . 3)
  99. (top-or-bottom . top)
  100. (top-or-bottom-pos . 0))
  101. "Position to which Mouse Avoidance mode `banish' moves the mouse.
  102. An alist where keywords mean:
  103. FRAME-OR-WINDOW: banish the mouse to corner of frame or window.
  104. SIDE: banish the mouse on right or left corner of frame or window.
  105. SIDE-POS: Distance from right or left edge of frame or window.
  106. TOP-OR-BOTTOM: banish the mouse to top or bottom of frame or window.
  107. TOP-OR-BOTTOM-POS: Distance from top or bottom edge of frame or window."
  108. :group 'avoid
  109. :version "24.3"
  110. :type '(alist :key-type symbol :value-type (choice symbol integer))
  111. :options '((frame-or-window symbol) (side symbol) (side-pos integer)
  112. (top-or-bottom symbol) (top-or-bottom-pos integer)))
  113. ;; Internal variables
  114. (defvar mouse-avoidance-state nil)
  115. (defvar mouse-avoidance-pointer-shapes nil)
  116. (defvar mouse-avoidance-n-pointer-shapes 0)
  117. (defvar mouse-avoidance-old-pointer-shape nil)
  118. (defvar mouse-avoidance-animating-pointer nil)
  119. ;; This timer is used to run something when Emacs is idle.
  120. (defvar mouse-avoidance-timer nil)
  121. ;;; Functions:
  122. (defsubst mouse-avoidance-set-pointer-shape (shape)
  123. "Set the shape of the mouse pointer to SHAPE."
  124. (when (boundp 'x-pointer-shape)
  125. (setq x-pointer-shape shape)
  126. (set-mouse-color nil)))
  127. (defun mouse-avoidance-point-position ()
  128. "Return the position of point as (FRAME X . Y).
  129. Analogous to `mouse-position'."
  130. (let* ((edges (window-inside-edges))
  131. (posn-at-point (posn-at-point))
  132. (x-y (and posn-at-point (posn-x-y posn-at-point))))
  133. (when x-y
  134. (cons (selected-frame)
  135. (cons (+ (car edges)
  136. (/ (car x-y) (frame-char-width)))
  137. (+ (car (cdr edges))
  138. (/ (cdr x-y) (frame-char-height))))))))
  139. ;(defun mouse-avoidance-point-position-test ()
  140. ; (interactive)
  141. ; (message "point=%s mouse=%s"
  142. ; (cdr (mouse-avoidance-point-position))
  143. ; (cdr (mouse-position))))
  144. (defun mouse-avoidance-set-mouse-position (pos)
  145. ;; Carefully set mouse position to given position (X . Y)
  146. ;; Returns t if it moved the mouse.
  147. (let ((f (selected-frame)))
  148. (set-mouse-position f (car pos) (cdr pos))
  149. t))
  150. (defun mouse-avoidance-too-close-p (mouse)
  151. "Return t if mouse pointer and point cursor are too close.
  152. MOUSE is the current mouse position as returned by `mouse-position'.
  153. Acceptable distance is defined by `mouse-avoidance-threshold'."
  154. (let* ((frame (car mouse))
  155. (mouse-y (cdr (cdr mouse)))
  156. (tool-bar-lines (frame-parameter nil 'tool-bar-lines))
  157. point)
  158. (or tool-bar-lines
  159. (setq tool-bar-lines 0))
  160. (cond
  161. ((and mouse-y (< mouse-y tool-bar-lines))
  162. nil)
  163. ((setq point (mouse-avoidance-point-position))
  164. (let ((mouse-x (car (cdr mouse))))
  165. (and (eq frame (car point))
  166. (not (null mouse-x))
  167. (< (abs (- mouse-x (car (cdr point))))
  168. mouse-avoidance-threshold)
  169. (< (abs (- mouse-y (cdr (cdr point))))
  170. mouse-avoidance-threshold)))))))
  171. (defun mouse-avoidance-banish-destination ()
  172. "The position to which Mouse Avoidance mode `banish' moves the mouse.
  173. If you want the mouse banished to a different corner set
  174. `mouse-avoidance-banish-position' as you need."
  175. (let* ((fra-or-win (assoc-default
  176. 'frame-or-window
  177. mouse-avoidance-banish-position 'eq))
  178. (list-values (pcase fra-or-win
  179. (`frame (list 0 0 (frame-width) (frame-height)))
  180. (`window (window-edges))))
  181. (alist (cl-loop for v in list-values
  182. for k in '(left top right bottom)
  183. collect (cons k v)))
  184. (side (assoc-default
  185. 'side
  186. mouse-avoidance-banish-position #'eq))
  187. (side-dist (assoc-default
  188. 'side-pos
  189. mouse-avoidance-banish-position #'eq))
  190. (top-or-bottom (assoc-default
  191. 'top-or-bottom
  192. mouse-avoidance-banish-position #'eq))
  193. (top-or-bottom-dist (assoc-default
  194. 'top-or-bottom-pos
  195. mouse-avoidance-banish-position #'eq))
  196. (side-fn (pcase side
  197. (`left '+)
  198. (`right '-)))
  199. (top-or-bottom-fn (pcase top-or-bottom
  200. (`top '+)
  201. (`bottom '-))))
  202. (cons (funcall side-fn ; -/+
  203. (assoc-default side alist 'eq) ; right or left
  204. side-dist) ; distance from side
  205. (funcall top-or-bottom-fn ; -/+
  206. (assoc-default top-or-bottom alist 'eq) ; top/bottom
  207. top-or-bottom-dist)))) ; distance from top/bottom
  208. (defun mouse-avoidance-banish-mouse ()
  209. "Put the mouse pointer to `mouse-avoidance-banish-position'."
  210. (mouse-avoidance-set-mouse-position (mouse-avoidance-banish-destination)))
  211. (defsubst mouse-avoidance-delta (cur delta dist var min max)
  212. ;; Decide how far to move in either dimension.
  213. ;; Args are the CURRENT location, the desired DELTA for
  214. ;; warp-conservation, the DISTANCE we like to move, the VARIABILITY
  215. ;; in distance allowed, and the MIN and MAX possible window positions.
  216. ;; Returns something as close to DELTA as possible within the constraints.
  217. (let ((L1 (max (- min cur) (+ (- dist) (- var))))
  218. (R1 (+ (- dist) var ))
  219. (L2 (+ dist (- var)))
  220. (R2 (min (- max cur) (+ dist var))))
  221. (if (< R1 (- min cur)) (setq L1 nil R1 nil))
  222. (if (> L2 (- max cur)) (setq L2 nil R2 nil))
  223. (cond ((and L1 (< delta L1)) L1)
  224. ((and R1 (< delta R1)) delta)
  225. ((and R1 (< delta 0)) R1)
  226. ((and L2 (< delta L2)) L2)
  227. ((and R2 (< delta R2)) delta)
  228. (R2)
  229. ((or R1 L2))
  230. (t 0))))
  231. (defun mouse-avoidance-nudge-mouse ()
  232. ;; Push the mouse a little way away, possibly animating the move.
  233. ;; For these modes, state keeps track of the total offset that we've
  234. ;; accumulated, and tries to keep it close to zero.
  235. (let* ((cur (mouse-position))
  236. (cur-pos (cdr cur))
  237. (pos (window-edges))
  238. (wleft (pop pos))
  239. (wtop (pop pos))
  240. (wright (pop pos))
  241. (wbot (pop pos))
  242. (deltax (mouse-avoidance-delta
  243. (car cur-pos) (- (random mouse-avoidance-nudge-var)
  244. (car mouse-avoidance-state))
  245. mouse-avoidance-nudge-dist mouse-avoidance-nudge-var
  246. wleft (1- wright)))
  247. (deltay (mouse-avoidance-delta
  248. (cdr cur-pos) (- (random mouse-avoidance-nudge-var)
  249. (cdr mouse-avoidance-state))
  250. mouse-avoidance-nudge-dist mouse-avoidance-nudge-var
  251. wtop (1- wbot))))
  252. (setq mouse-avoidance-state
  253. (cons (+ (car mouse-avoidance-state) deltax)
  254. (+ (cdr mouse-avoidance-state) deltay)))
  255. (if (or (eq mouse-avoidance-mode 'animate)
  256. (eq mouse-avoidance-mode 'proteus))
  257. (let ((i 0.0)
  258. (incr (max .1 (/ 1.0 mouse-avoidance-nudge-dist))))
  259. (setq mouse-avoidance-animating-pointer t)
  260. (while (<= i 1)
  261. (mouse-avoidance-set-mouse-position
  262. (cons (+ (car cur-pos) (round (* i deltax)))
  263. (+ (cdr cur-pos) (round (* i deltay)))))
  264. (setq i (+ i incr))
  265. (if (eq mouse-avoidance-mode 'proteus)
  266. (mouse-avoidance-set-pointer-shape
  267. (mouse-avoidance-random-shape)))
  268. (sit-for mouse-avoidance-animation-delay))
  269. (setq mouse-avoidance-animating-pointer nil))
  270. (mouse-avoidance-set-mouse-position (cons (+ (car (cdr cur)) deltax)
  271. (+ (cdr (cdr cur)) deltay))))))
  272. (defun mouse-avoidance-random-shape ()
  273. "Return a random cursor shape.
  274. This assumes that any variable whose name begins with x-pointer- and
  275. has an integer value is a valid cursor shape. You might want to
  276. redefine this function to suit your own tastes."
  277. (if (null mouse-avoidance-pointer-shapes)
  278. (progn
  279. (setq mouse-avoidance-pointer-shapes
  280. (mapcar (lambda (x) (symbol-value (intern x)))
  281. (all-completions "x-pointer-" obarray
  282. (lambda (x)
  283. (and (boundp x)
  284. (integerp (symbol-value x)))))))
  285. (setq mouse-avoidance-n-pointer-shapes
  286. (length mouse-avoidance-pointer-shapes))))
  287. (nth (random mouse-avoidance-n-pointer-shapes)
  288. mouse-avoidance-pointer-shapes))
  289. (defun mouse-avoidance-ignore-p ()
  290. (let ((mp (mouse-position)))
  291. (or (not (frame-pointer-visible-p)) ; The pointer is hidden
  292. (not cursor-type) ; There's no cursor
  293. executing-kbd-macro ; don't check inside macro
  294. (null (cadr mp)) ; don't move unless in an Emacs frame
  295. (not (eq (car mp) (selected-frame)))
  296. ;; Don't do anything if last event was a mouse event.
  297. ;; FIXME: this code fails in the case where the mouse was moved
  298. ;; since the last key-press but without generating any event.
  299. (and (consp last-input-event)
  300. (symbolp (car last-input-event))
  301. (let ((modifiers (event-modifiers (car last-input-event))))
  302. (or (memq (car last-input-event)
  303. '(mouse-movement scroll-bar-movement
  304. select-window focus-out))
  305. (memq 'click modifiers)
  306. (memq 'double modifiers)
  307. (memq 'triple modifiers)
  308. (memq 'drag modifiers)
  309. (memq 'down modifiers)
  310. (memq 'meta modifiers)
  311. (memq 'control modifiers)
  312. (memq 'shift modifiers)
  313. (memq 'hyper modifiers)
  314. (memq 'super modifiers)
  315. (memq 'alt modifiers)))))))
  316. (defun mouse-avoidance-banish ()
  317. (if (not (mouse-avoidance-ignore-p))
  318. (mouse-avoidance-banish-mouse)))
  319. (defun mouse-avoidance-exile ()
  320. ;; For exile mode, the state is nil when the mouse is in its normal
  321. ;; position, and set to the old mouse-position when the mouse is in exile.
  322. (if (not (mouse-avoidance-ignore-p))
  323. (let ((mp (mouse-position)))
  324. (cond ((and (not mouse-avoidance-state)
  325. (mouse-avoidance-too-close-p mp))
  326. (setq mouse-avoidance-state mp)
  327. (mouse-avoidance-banish-mouse))
  328. ((and mouse-avoidance-state
  329. (not (mouse-avoidance-too-close-p mouse-avoidance-state)))
  330. (if (and (eq (car mp) (selected-frame))
  331. (equal (cdr mp) (mouse-avoidance-banish-destination)))
  332. (mouse-avoidance-set-mouse-position
  333. ;; move back only if user has not moved mouse
  334. (cdr mouse-avoidance-state)))
  335. ;; but clear state anyway, to be ready for another move
  336. (setq mouse-avoidance-state nil))))))
  337. (defun mouse-avoidance-fancy ()
  338. ;; Used for the "fancy" modes, ie jump et al.
  339. (if (and (not mouse-avoidance-animating-pointer)
  340. (not (mouse-avoidance-ignore-p))
  341. (mouse-avoidance-too-close-p (mouse-position)))
  342. (let ((old-pos (mouse-position)))
  343. (mouse-avoidance-nudge-mouse)
  344. (if (not (eq (selected-frame) (car old-pos)))
  345. ;; This should never happen.
  346. (apply 'set-mouse-position old-pos)))))
  347. ;;;###autoload
  348. (defun mouse-avoidance-mode (&optional mode)
  349. "Set Mouse Avoidance mode to MODE.
  350. MODE should be one of the symbols `banish', `exile', `jump', `animate',
  351. `cat-and-mouse', `proteus', or `none'.
  352. If MODE is nil, toggle mouse avoidance between `none' and `banish'
  353. modes. Positive numbers and symbols other than the above are treated
  354. as equivalent to `banish'; negative numbers and `-' are equivalent to `none'.
  355. Effects of the different modes:
  356. * banish: Move the mouse to the upper-right corner on any keypress.
  357. * exile: Move the mouse to the corner only if the cursor gets too close,
  358. and allow it to return once the cursor is out of the way.
  359. * jump: If the cursor gets too close to the mouse, displace the mouse
  360. a random distance & direction.
  361. * animate: As `jump', but shows steps along the way for illusion of motion.
  362. * cat-and-mouse: Same as `animate'.
  363. * proteus: As `animate', but changes the shape of the mouse pointer too.
  364. \(See `mouse-avoidance-threshold' for definition of \"too close\",
  365. and `mouse-avoidance-nudge-dist' and `mouse-avoidance-nudge-var' for
  366. definition of \"random distance\".)"
  367. (interactive
  368. (list (intern (completing-read
  369. "Select cursor avoidance technique (SPACE for list): "
  370. '(("banish") ("exile") ("jump") ("animate")
  371. ("cat-and-mouse") ("proteus") ("none"))
  372. nil t))))
  373. (if (eq mode 'cat-and-mouse)
  374. (setq mode 'animate))
  375. (if mouse-avoidance-timer
  376. (cancel-timer mouse-avoidance-timer))
  377. (setq mouse-avoidance-timer nil)
  378. ;; Restore pointer shape if necessary
  379. (if (eq mouse-avoidance-mode 'proteus)
  380. (mouse-avoidance-set-pointer-shape mouse-avoidance-old-pointer-shape))
  381. ;; Do additional setup depending on version of mode requested
  382. (cond ((eq mode 'none)
  383. (setq mouse-avoidance-mode nil))
  384. ((or (eq mode 'jump)
  385. (eq mode 'animate)
  386. (eq mode 'proteus))
  387. (setq mouse-avoidance-timer
  388. (run-with-idle-timer 0.1 t 'mouse-avoidance-fancy))
  389. (setq mouse-avoidance-mode mode
  390. mouse-avoidance-state (cons 0 0)
  391. mouse-avoidance-old-pointer-shape
  392. (and (boundp 'x-pointer-shape) x-pointer-shape)))
  393. ((eq mode 'exile)
  394. (setq mouse-avoidance-timer
  395. (run-with-idle-timer 0.1 t 'mouse-avoidance-exile))
  396. (setq mouse-avoidance-mode mode
  397. mouse-avoidance-state nil))
  398. ((or (eq mode 'banish)
  399. (eq mode t)
  400. (and (null mode) (null mouse-avoidance-mode))
  401. (and mode (> (prefix-numeric-value mode) 0)))
  402. (setq mouse-avoidance-timer
  403. (run-with-idle-timer 0.1 t 'mouse-avoidance-banish))
  404. (setq mouse-avoidance-mode 'banish))
  405. (t (setq mouse-avoidance-mode nil)))
  406. (force-mode-line-update))
  407. ;; Most people who use avoid mode leave it on all the time, so it's not
  408. ;; very informative to announce it in the mode line.
  409. ;;(or (assq 'mouse-avoidance-mode minor-mode-alist)
  410. ;; (setq minor-mode-alist (cons '(mouse-avoidance-mode " Avoid")
  411. ;; minor-mode-alist)))
  412. ;; Needed for custom.
  413. (if mouse-avoidance-mode
  414. (mouse-avoidance-mode mouse-avoidance-mode))
  415. (provide 'avoid)
  416. ;;; avoid.el ends here