]> git.donarmstrong.com Git - lib.git/blob - emacs_el/tiny-tools/tiny/tinylock.el
add tiny-tools
[lib.git] / emacs_el / tiny-tools / tiny / tinylock.el
1 ;;; tinylock.el --- Simple emacs locking utility
2
3 ;; This file is not part of Emacs
4
5 ;;{{{ Id
6
7 ;; Copyright (C)    1995-2007 Jari Aalto
8 ;; Keywords:        extensions
9 ;; Author:          Jari Aalto
10 ;; Maintainer:      Jari Aalto
11 ;;
12 ;; To get information on this program, call -x tinylock-version.
13 ;; Look at the code with folding.el
14
15 ;; COPYRIGHT NOTICE
16 ;;
17 ;; This program is free software; you can redistribute it and/or modify it
18 ;; under the terms of the GNU General Public License as published by the Free
19 ;; Software Foundation; either version 2 of the License, or (at your option)
20 ;; any later version.
21 ;;
22 ;; This program is distributed in the hope that it will be useful, but
23 ;; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
24 ;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
25 ;; for more details.
26 ;;
27 ;; You should have received a copy of the GNU General Public License
28 ;; along with program; see the file COPYING. If not, write to the
29 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
30 ;; Boston, MA 02110-1301, USA.
31 ;;
32 ;; Visit <http://www.gnu.org/copyleft/gpl.html> for more information
33
34 ;;}}}
35 ;;{{{ Install
36
37 ;; ....................................................... &t-install ...
38 ;; Put this file on your Emacs-Lisp load path, add one of these into your
39 ;; ~/.emacs startup file
40 ;;
41 ;;   Normal load
42 ;;
43 ;;      (require 'tinylock)
44 ;;
45 ;;   Autoload, your emacs starts up faster, prefered, but doesn't
46 ;;   activate the auto locking feature.
47 ;;
48 ;;      (autoload 'tinylock-lock "tinylock" "Lock emacs" t)
49 ;;
50 ;;   ESC-l, suggested keybinding, replaces downcase-word binding
51 ;;   because you can accomplish the same with C-x C-l,
52 ;;   downcase-region.
53 ;;
54 ;;      (global-set-key "\M-l" 'tinylock-lock)     ;; Suggested keybinding.
55 ;;
56 ;;
57 ;;   If you have any questions, use this function
58 ;;
59 ;;      M-x tinylock-submit-feedback
60 ;;
61 ;;   See also Example section at the end of file.
62
63 ;;}}}
64 ;;{{{ Documentation
65
66 ;; ..................................................... &t-commentary ...
67
68 ;;; Commentary:
69
70 ;; Overview of features
71 ;;
72 ;;      o   Locks emacs completely until right key is entered.
73 ;;      o   Auto-locks emacs after NN minutes idle time
74 ;;      o   False login attemps are stored in history log.
75 ;;      o   Blanks display or displays message buffer when locked.
76 ;;      o   Hooks: before and after lock is activated and removed
77 ;;
78 ;;  About locking procedure
79 ;;
80 ;;      Don't get shocked now... When the lock gets in effect there must be
81 ;;      no running processes inside emacs that would generate error and
82 ;;      make emacs vulnerable to break in.  That's why all the running
83 ;;      processes are killed before the lock takes in effect. If you have
84 ;;      some valuable processes that are constantly running, you must make
85 ;;      a separate "process control" function that would restart any such
86 ;;      processes. Use the appropriate hook to activate those processes
87 ;;      again after the emacs is unlocked. Use hooks
88 ;;
89 ;;          tinylock-:before-lock-hook       ;; Save processes here
90 ;;          tinylock-:after-lock-hook        ;; restore processes here
91 ;;
92 ;;      and following function which tells you what processes are running.
93 ;;
94 ;;          M-x list-processes
95 ;;
96 ;;      All extra frames are also deleted. At least for now, because I
97 ;;      don't know a reasonable way to save the frame configurations
98 ;;      right now. Please send me piece of code or pointer to package
99 ;;      that can save and restore frames and the windows back to previous
100 ;;      state if you know good solution.
101 ;;
102 ;;  About auto locking feature, Emacs prior 19.34
103 ;;
104 ;;      When you load this package the `tinylock-:load-hook' runs
105 ;;      `tinylock-install-lock-timer' command that setup up a timer process that
106 ;;      wakes up periodically. If the emacs has not changed compared to
107 ;;      last saved emacs state, then the auto locking takes in effect
108 ;;      immediately.
109 ;;
110 ;;      In old Emacs the activity is determined in simple way
111 ;;
112 ;;      o   if buffer list order has changed user is doing something.
113 ;;      o   if `switch-buffer' was used, user is doing something
114 ;;      o   if any buffer's size has changed, user is doing something.
115 ;;
116 ;;      This checking may not be enough: if user just scroll some
117 ;;      text in buffer for NN minutes, then from `tinylock-process' 's point of
118 ;;      view there has not been any activity and the user may suddenly
119 ;;      notice that emacs locks up. Doing nothing but viewing one buffer
120 ;;      all the time is fortunately rare.
121 ;;
122 ;;  About auto locking feature in new Emacs
123 ;;
124 ;;      New Emacs releases have command `run-with-idle-timer' which we use
125 ;;      if it is available. When there has been no activity for NN minutes,
126 ;;      your Emacs locks up.
127 ;;
128 ;;      The advice code and the other tricks we needed to detect idle
129 ;;      activity in lower emacs versions aren't installed in these Emacs
130 ;;      versions, so you don't have to worry about sudden lock.
131 ;;
132 ;;  Auto lock password
133 ;;
134 ;;      Do not put password in your ~/.emacs, but answer to the question
135 ;;      which is asked when this file is loaded. If you want to change it
136 ;;      during your emacs session, call function
137 ;;
138 ;;          M-x tinylock-auto-lock-set-password
139 ;;
140 ;;  Changing the auto lock interval
141 ;;
142 ;;      The auto lock interval depends on the wake up time of timer
143 ;;      process. The default time is 20 minutes when you load this
144 ;;      file. You can change the time by calling
145 ;;
146 ;;          M-x tinylock-auto-lock-set-interval
147 ;;
148 ;;      Or by putting this code in your ~/.emacs
149 ;;
150 ;;          ;; First define the hook, so that we can append to it
151 ;;          (setq tinylock-:load-hook
152 ;;             '(tinylock-timer-control tinylock-auto-lock-set-password)
153 ;;
154 ;;          ;; add function to the end
155 ;;          (add-hook 'tinylock-:load-hook 'my-tinylock-auto-lock-set-interval 'append)
156 ;;
157 ;;
158 ;;          (defun my-tinylock-auto-lock-set-interval ()
159 ;;            "Change interval to 10 minutes."
160 ;;            (tinylock-auto-lock-set-interval 10))
161 ;;          ;; end of example
162
163 ;;}}}
164
165 ;;; Change Log:
166
167 ;;; Code:
168
169 ;;{{{ setup: require
170
171 (require 'tinylibm)
172
173 (eval-and-compile
174   ;; If this is not 19.34+, then we need advice code, otherwise it is
175   ;; skipped.
176   (unless (fboundp 'run-with-idle-timer)
177     (require 'advice))
178   (ti::package-package-require-timer))
179
180 (eval-when-compile (ti::package-use-dynamic-compilation))
181
182 (ti::package-defgroup-tiny TinyLock tinylock-: extensions
183   "Simple emacs locking utility.
184  Overview of features
185
186         o   Locks emacs completely until right key is entered.
187         o   Auto-locks emacs after XXX idle time
188         o   False attemps are stored in history log.
189         o   Blanks or displays buffer message when locked.
190         o   Hooks: before and after lock is entered/removed")
191
192 ;;}}}
193 ;;{{{ setup: variables
194
195 (defcustom tinylock-:load-hook nil
196   "*Hook run after file is loaded."
197   :type  'hook
198   :group 'TinyLock)
199
200 (defcustom tinylock-:before-lock-hook  nil
201   "*Hook that is run when the locking process initiates.
202 This is your chance to save frame configurations or processes before
203 they are killed."
204   :type  'hook
205   :group 'TinyLock)
206
207 (defcustom tinylock-:after-lock-hook nil
208   "*Hook that is run after lock is removed."
209   :type  'hook
210   :group 'TinyLock)
211
212 ;;; ....................................................... &v-private ...
213
214 (defconst tinylock-:history nil
215   "\(DATE PASSWD\) A storage where attempts of entering locked Emacs is put.
216 Cleared every time lock takes effect.")
217
218 (defvar tinylock-:auto-lock-data  nil
219   "Data to tell about the idle state, updated by timer process.
220 Contains:
221 '(current-time          ;; time stamp of user activity
222   (BUFFER-LIST)
223   (SIZE SIZE SIZE ..))   ;; every buffers size.")
224
225 (defvar tinylock-:auto-lock-password  nil
226   "Password in auto lock situation.
227 Password is asked when you load this file. You shouldn't define
228 this in you ~/.emacs")
229
230 (defvar tinylock-:auto-lock-interval  nil
231   "The timer interval in minutes. Use \\[tinylock-auto-lock-set-interval].")
232
233 (defvar tinylock-:idle-timer-process  nil
234   "19.34+ timer process.")
235
236 ;;; ........................................................ &v-config ...
237
238 (defcustom tinylock-:login-error-sleep 2
239   "*Time in seconds that is waited until new login to is possible."
240   :type '(integer :tag "Seconds")
241   :group 'TinyLock)
242
243 (defcustom tinylock-:buffer-login-history "*tinylock-hist*"
244   "*Buffer to output the history data."
245   :type 'string
246   :group 'TinyLock)
247
248 (defcustom tinylock-:buffer-blank "*blank*"
249   "*Buffer name used when screen is blanked."
250   :type 'string
251   :group 'TinyLock)
252
253 (defcustom tinylock-:blank-when-locked-flag t
254   "*Non-nil means show `tinylock-:buffer-blank' buffer."
255   :type 'string
256   :group 'TinyLock)
257
258 ;;}}}
259 ;;{{{ setup: version
260
261 ;;; ....................................................... &v-version ...
262
263 ;;;###autoload (autoload 'tinylock-version "tinylock" "Display commentary." t)
264 (eval-and-compile
265   (ti::macrof-version-bug-report
266    "tinylock.el"
267    "tinylock"
268    tinylock-:version-id
269    "$Id: tinylock.el,v 2.42 2007/05/06 23:15:20 jaalto Exp $"
270    '(tinylock-:version-id
271      tinylock-:before-lock-hook
272      tinylock-:after-lock-hook
273      tinylock-:load-hook
274      tinylock-:auto-lock-data
275      tinylock-:auto-lock-password
276      tinylock-:auto-lock-interval
277      tinylock-:idle-timer-process
278      tinylock-:login-error-sleep
279      tinylock-:buffer-login-history
280      tinylock-:buffer-blank
281      tinylock-:blank-when-locked-flag)))
282
283 ;;}}}
284 ;;{{{ code: macros, advices
285
286 (defmacro tinylock-time-dd (time)
287   "Return Day from TIME."
288   (list 'string-to-int (list 'substring time 8 10)))
289
290 (defmacro tinylock-time-hh (time)
291   "Return hour from TIME."
292   (list  'string-to-int (list 'substring time -13 -11)))
293
294 (eval-and-compile
295   (unless (fboundp 'run-with-idle-timer) ;we need this if not 19.34+
296     (require 'advice)
297     ;;   What else easy means we have to tell that user is working with
298     ;;   the emacs ?
299     ;;
300     ;;   The advice shouldn't disturb normal emacs behavior and the functions
301     ;;   calls are _inlined_, ie. function is expanded to point
302     ;;   when byte compiled, so that the advice works as fast as possible
303     ;;   and doesn't take time from the original function.
304     ;;
305     ;; (ti::advice-control '(switch-to-buffer other-window)  "^til$" 'dis)
306     ;;
307     (defadvice switch-to-buffer  (before til act) ;C-x C-b
308       "Tell to Emacs auto lock that there is user activity."
309       (if (interactive-p)
310           (inline (tinylock-user-activity))))
311
312     (defadvice execute-extended-command  (before til act) ;; M-x called
313       "Tell to Emacs auto lock that there is user activity."
314       (if (interactive-p) (inline (tinylock-user-activity))))
315
316     (defadvice other-window  (before til act) ;C-x o
317       "Tell to Emacs auto lock that there is user activity."
318       (if (interactive-p)
319           (inline (tinylock-user-activity))))))
320
321 ;;}}}
322 ;;{{{ code: misc funcs
323
324 ;;; ----------------------------------------------------------------------
325 ;;;
326 (defun tinylock-auto-lock-set-interval (minutes)
327   "Set new MINUTES interval by stopping and restarting timer process."
328   (interactive "Nminutes: ")
329   (tinylock-install-lock-timer nil minutes)
330   nil)
331
332 ;;; ----------------------------------------------------------------------
333 ;;;
334 (defun tinylock-auto-lock-set-password ()
335   "Set auto lock password."
336   (interactive)
337   (let* (pass)
338     (if (ti::nil-p (setq pass (ti::query-read-input-as-password
339                                "TinyLock, give autolock password: ")))
340         (error "Password is empty.")
341       (setq tinylock-:auto-lock-password pass))
342     nil))
343
344 ;;; ----------------------------------------------------------------------
345 ;;;
346 (defun tinylock-process-on ()
347   "Start auto lock process."
348   (tinylock-install-lock-timer nil tinylock-:auto-lock-interval))
349
350 ;;; ----------------------------------------------------------------------
351 ;;;
352 (defun tinylock-install-lock-timer (&optional uninstall interval)
353   "Install process that locks Emacs when there is no activity.
354
355 Input:
356
357   UNINSTALL   `tinylock-process'
358   INTERVAL    in minutes, by default 20."
359   (interactive "P")
360   ;; .......................................................... kill ...
361   (ti::compat-timer-cancel-function 'tinylock-process)
362   (setq tinylock-:idle-timer-process nil)
363   ;; .................................................... set values ...
364   (setq tinylock-:auto-lock-interval
365         (or interval
366             tinylock-:auto-lock-interval
367             20))                        ;Default 20 minutes
368   ;; ................................................... maybe start ...
369   (unless uninstall
370     (cond
371      ((fboundp 'run-with-idle-timer)    ;19.34+
372       (setq
373        tinylock-:idle-timer-process
374        (ti::funcall
375         'run-with-idle-timer
376         (* tinylock-:auto-lock-interval 60)
377         'repeat
378         'tinylock-lock-now)))
379      (t
380       (tinylock-process-data-set)
381       (run-at-time
382        "1 sec"
383        (* tinylock-:auto-lock-interval 60)
384        'tinylock-process))))
385   (if (interactive-p)
386       (message "Autolock process %s"
387                (if uninstall
388                    "deleted"
389                  "started"))))
390
391 ;;; ----------------------------------------------------------------------
392 ;;;
393 (defun tinylock-user-activity ()
394   "Tell to timer process that the has bee user activity."
395   (or
396    (ignore-errors (setcar tinylock-:auto-lock-data (current-time)))
397    ;;  Hmm, data is corrupted... reset it.
398    (tinylock-process-data-set)))
399
400 ;;; ----------------------------------------------------------------------
401 ;;;
402 (defun tinylock-process-data-set ()
403   "Update timer process data."
404   (setq tinylock-:auto-lock-data
405         (list
406          (current-time)
407          (buffer-list)
408          (mapcar
409           (function
410            (lambda (x)
411              (with-current-buffer x
412                (buffer-size))))
413           (buffer-list))))
414   nil)
415
416 ;;; ----------------------------------------------------------------------
417 ;;; Just testing... (tinylock-process-data-set) (tinylock-process-data-unchanged-p)
418 ;;;
419 (defun tinylock-process-data-unchanged-p ()
420   "Return t if timer data has not changed = No activity in."
421   (let* ((data          tinylock-:auto-lock-data)
422          (time          (nth 0 data))
423          (buffer-list   (nth 1 data))
424          (size-list     (nth 2 data))
425          (list          (buffer-list))
426          (i             0)
427          unchanged)
428     (if (null tinylock-:auto-lock-data)
429         (tinylock-process-data-set)
430       ;; o if buffer list order is the same, the user may not have
431       ;;   done any new work...
432       ;; o Next we check if buffer sizes have changed, if not, then
433       ;;   user hasn't done any work in emacs.
434       (condition-case nil
435           (and (> (ti::date-time-difference (current-time) time)
436                   (- (* tinylock-:auto-lock-interval 60) 5)) ;5 sec timeframe
437                (equal list buffer-list)
438                (progn
439                  (setq unchanged t)
440                  (dolist (elt list)
441                    (with-current-buffer elt
442                      (if (not (eq (buffer-size)
443                                   (nth i size-list)))
444                          ;; Found changed buffer, stop there and
445                          ;; reset lock status, and quit the loop by
446                          ;; killing the list
447                          ;;
448                          (setq list nil  unchanged nil))
449                      (incf  i)))))
450         ;;  Data is corrupted somehow, fix it.
451         (error
452          (tinylock-process-data-set)))
453       unchanged)))
454
455 ;;; ----------------------------------------------------------------------
456 ;;;
457 (defun tinylock-process ()
458   "Lock up Emacs if it there has not been any user activity."
459   (when (tinylock-process-data-unchanged-p)
460     ;;  When Emacs locks up, this function process will die too.
461     (tinylock-lock-now))
462   (tinylock-process-data-set))
463
464 ;;; ----------------------------------------------------------------------
465 ;;;
466 (defun tinylock-add-history (passwd)
467   "Add login attempt to `tinylock-:history'.PASSWD is the attempted login password."
468   (let* ((d (current-time-string)))
469     (setq tinylock-:history
470           (append  tinylock-:history
471                    (list (list d passwd))))))
472
473 ;;; ----------------------------------------------------------------------
474 ;;;
475 (defun tinylock-kill-process-control (&optional kill)
476   "Return all processes in string format, or KILL all processes (not timer)."
477   (let* ((list  (process-list))
478          ret)
479     (if list
480         (mapcar
481          (function
482           (lambda (x)
483             (cond
484              ((null kill)
485               (setq ret (concat (or ret "") (prin1-to-string x))))
486              (t
487               ;;  let's not kill the timer
488               (if (not (string-match "display-time\\|timer"
489                                      (prin1-to-string x)))
490                   (delete-process x))))))
491          list))
492     ret))
493
494 ;;; ----------------------------------------------------------------------
495 ;;;
496 ;;;###autoload
497 (defun tinylock-history ()
498   "Displays login history. Optionally to given buffer BUFFER."
499   (interactive)
500   (let* ((i 0))
501     (switch-to-buffer-other-window
502      (get-buffer-create tinylock-:buffer-login-history))
503     (erase-buffer)
504     (dolist (elt tinylock-:history)
505       (insert (format "%2d: %-27s %s\n" i (nth 0 elt) (or (nth 1 elt) "<nil>") ))
506       (setq i (1+ i)))))
507
508 ;;; ----------------------------------------------------------------------
509 ;;;
510 (defun tinylock-blank-control (&optional unblank)
511   "Blank display or UNBLANK."
512   (let* ((blank (get-buffer-create tinylock-:buffer-blank)))
513     (cond
514      (unblank
515       (ti::kill-buffer-safe blank))
516      (t
517       (ti::select-frame-non-dedicated)
518       (dolist (frame (delq (selected-frame) (frame-list)))
519         (delete-frame frame))
520       (switch-to-buffer blank t)
521       (delete-other-windows)            ;delete all other windows
522       ;;   This is necessary in 19.28 for some unknown reason
523       ;;   otw, the sreen is not shown
524       (sit-for 1)))))
525
526 ;;}}}
527 ;;{{{ code: main
528
529 ;;; ----------------------------------------------------------------------
530 ;;;
531 (defun tinylock-lock-now ()
532   "Lock up Emac."
533   (tinylock-lock tinylock-:auto-lock-password "Autolocking.. emacs " 'doit ))
534
535 ;;; ------------------------------------------------------------ &main ---
536 ;;;
537 ;;;###autoload
538 (defun tinylock-lock (psw &optional msg lock-now)
539   "Lock Emacs with PSW password and MSG.
540 Message is displayed if LOCK-NOW is nil.
541 If LOCK-NOW is non-nil emacs is immediately locked with PSW."
542   (interactive
543    (list
544     (progn
545       (message "Now enter lock string...") (sit-for 1)
546       (ti::query-read-input-invisible))))
547   (let* ((cursor-in-echo-area nil)
548          ;;  It's good programming style NOT to use globals directly
549          ;;  inside code This way maintainer sees at glance what it uses.
550          (key-msg        "This emacs is locked, enter key:")
551          (entry-err      "Unauthorized access.")
552          (wait           tinylock-:login-error-sleep)
553          (loop           t)
554          (msg            (or msg "Lock Emacs ? "))
555          ans)
556     (catch 'done
557       (if (ti::nil-p psw)
558           (error "Password is empty."))
559       (if (and (null lock-now)
560                (null (y-or-n-p msg)))
561           (throw 'done t))
562       (save-window-excursion
563         (run-hooks 'tinylock-:before-lock-hook))
564       ;;  It's better to save work, you may forgot the password :-/
565       (save-some-buffers 'noAsk)
566       (ti::compat-timer-list-control     'save)
567       (tinylock-install-lock-timer   'kill)     ;our process
568       (tinylock-kill-process-control 'kill)     ;get rid of them
569       (ti::compat-timer-list-control     'kill) ;Stop all timers
570       (tinylock-blank-control)
571       ;;   we need to restore windows config when we return
572       (save-window-excursion
573         (save-excursion
574           ;; Now we make interrupting impossible, C-g won't work now on...
575           (setq inhibit-quit t)
576           (setq tinylock-:history nil)  ;clear the log buffer
577           (message "TinyLock: Emacs LOCKED %s " (ti::date-standard-date))
578           (sleep-for 1)
579           (message "")
580           (while loop
581             (when (input-pending-p)     ;wait for kbd event
582               (discard-input)
583               (message key-msg)
584               (sleep-for 1)
585               (message "")
586               (discard-input)
587               (setq ans (ti::query-read-input-invisible))
588               (cond
589                ((string-equal ans psw)
590                 (setq loop  nil))       ; right password, let user in
591                (t
592                 (tinylock-add-history ans) ; record to log
593                 (message entry-err)
594                 (sit-for wait)))))))
595       (tinylock-blank-control 'unblank)
596       (message "TinyLock: Emacs unlocked %s" (ti::date-standard-date))
597       (setq quit-flag nil inhibit-quit nil) ; restore flags
598       (ti::compat-timer-list-control 'restore)
599       (tinylock-process-on)
600       (run-hooks 'tinylock-:after-lock-hook)
601       nil)))
602
603 ;;}}}
604 ;;{{{ Default: hook functions.
605
606 ;;; ----------------------------------------------------------------------
607 ;;;
608 (defun tinylock-before-lock-function ()
609   "Saves emacs state, so that you can recover from accidental crash."
610   (when (fboundp 'tid-save-state)
611     (message "TinyLock: wait, using TinyDesk to save emacs state...")
612     (ti::funcall 'tid-save-state "~/emacs.lock-state.saved")
613     (message "TinyLock: wait, using TinyDesk to save emacs state...done.")))
614
615 ;;; ----------------------------------------------------------------------
616 ;;;
617 (defun tinylock-after-lock-function ()
618   "Restores Emacs state after lock"
619   (display-time)                        ;re-enable process
620   (when (fboundp 'timi-report-install-maybe)
621     (ti::funcall 'timi-report-install-maybe)))
622
623 (add-hook 'tinylock-:load-hook 'tinylock-process-on)
624
625 ;; Ask lock password at startup
626
627 (if tinylock-:auto-lock-password
628     (remove-hook 'tinylock-:load-hook 'tinylock-auto-lock-set-password)
629   (add-hook 'tinylock-:load-hook 'tinylock-auto-lock-set-password))
630
631 (add-hook 'tinylock-:before-lock-hook 'tinylock-before-lock-function)
632 (add-hook 'tinylock-:after-lock-hook  'tinylock-after-lock-function)
633
634 ;;}}}
635
636 (provide   'tinylock)
637 (run-hooks 'tinylock-:load-hook)
638
639 ;;; tinylock.el ends here