1 ;;; proced.el --- operate on system processes like dired
3 ;; Copyright (C) 2008 Free Software Foundation, Inc.
5 ;; Author: Roland Winkler <Roland.Winkler@physik.uni-erlangen.de>
6 ;; Keywords: Processes, Unix
8 ;; This file is part of GNU Emacs.
10 ;; GNU Emacs is free software: you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation, either version 3 of the License, or
13 ;; (at your option) any later version.
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
25 ;; Proced makes an Emacs buffer containing a listing of the current
26 ;; system processes. You can use the normal Emacs commands to move around
27 ;; in this buffer, and special Proced commands to operate on the processes
31 ;; - use defcustom where appropriate
32 ;; - interactive temporary customizability of `proced-grammar-alist'
33 ;; - allow "sudo kill PID", "renice PID"
37 (require 'time-date
) ; for `with-decoded-time-value'
45 (defcustom proced-signal-function
'signal-process
46 "Name of signal function.
47 It can be an elisp function (usually `signal-process') or a string specifying
48 the external command (usually \"kill\")."
50 :type
'(choice (function :tag
"function")
51 (string :tag
"command")))
53 (defcustom proced-signal-list
54 '( ;; signals supported on all POSIX compliant systems
56 ("INT (2. Terminal interrupt)")
57 ("QUIT (3. Terminal quit)")
58 ("ABRT (6. Process abort)")
59 ("KILL (9. Kill - cannot be caught or ignored)")
60 ("ALRM (14. Alarm Clock)")
61 ("TERM (15. Termination)")
63 ;; Which systems do not support these signals so that we can
64 ;; exclude them from `proced-signal-list'?
65 ("CONT (Continue executing)")
66 ("STOP (Stop executing / pause - cannot be caught or ignored)")
67 ("TSTP (Terminal stop / pause)"))
68 "List of signals, used for minibuffer completion."
70 :type
'(repeat (string :tag
"signal")))
72 ;; For which attributes can we use a fixed width of the output field?
73 ;; A fixed width speeds up formatting, yet it can make
74 ;; `proced-grammar-alist' system-dependent.
75 ;; (If proced runs like top(1) we want it to be fast.)
77 ;; If it is impossible / unlikely that an attribute has the same value
78 ;; for two processes, then sorting can be based on one ordinary (fast)
79 ;; predicate like `<'. Otherwise, a list of proced predicates can be used
80 ;; to refine the sort.
82 ;; It would be neat if one could temporarily override the following
84 (defvar proced-grammar-alist
85 '( ;; attributes defined in `system-process-attributes'
86 (euid "EUID" "%d" right proced-
< nil
(euid pid
) (nil t nil
))
87 (user "USER" "%s" left proced-string-lessp nil
(user pid
) (nil t nil
))
88 (egid "EGID" "%d" right proced-
< nil
(egid euid pid
) (nil t nil
))
89 (group "GROUP" "%s" left proced-string-lessp nil
(group user pid
) (nil t nil
))
90 (comm "COMMAND" "%s" left proced-string-lessp nil
(comm pid
) (nil t nil
))
91 (state "STAT" "%s" left proced-string-lessp nil
(state pid
) (nil t nil
))
92 (ppid "PPID" "%d" right proced-
< nil
(ppid pid
) (nil t nil
))
93 (pgrp "PGRP" "%d" right proced-
< nil
(pgrp euid pid
) (nil t nil
))
94 (sess "SESS" "%d" right proced-
< nil
(sess pid
) (nil t nil
))
95 (ttname "TTY" proced-format-ttname left proced-string-lessp nil
(ttname pid
) (nil t nil
))
96 (tpgid "TPGID" "%d" right proced-
< nil
(tpgid pid
) (nil t nil
))
97 (minflt "MINFLT" "%d" right proced-
< nil
(minflt pid
) (nil t t
))
98 (majflt "MAJFLT" "%d" right proced-
< nil
(majflt pid
) (nil t t
))
99 (cminflt "CMINFLT" "%d" right proced-
< nil
(cminflt pid
) (nil t t
))
100 (cmajflt "CMAJFLT" "%d" right proced-
< nil
(cmajflt pid
) (nil t t
))
101 (utime "UTIME" proced-format-time right proced-time-lessp t
(utime pid
) (nil t t
))
102 (stime "STIME" proced-format-time right proced-time-lessp t
(stime pid
) (nil t t
))
103 (cutime "CUTIME" proced-format-time right proced-time-lessp t
(cutime pid
) (nil t t
))
104 (cstime "CSTIME" proced-format-time right proced-time-lessp t
(cstime pid
) (nil t t
))
105 (pri "PR" "%d" right proced-
< t
(pri pid
) (nil t t
))
106 (nice "NI" "%3d" 3 proced-
< t
(nice pid
) (t t nil
))
107 (thcount "THCOUNT" "%d" right proced-
< t
(thcount pid
) (nil t t
))
108 (start "START" proced-format-start
6 proced-time-lessp nil
(start pid
) (t t nil
))
109 (vsize "VSIZE" "%d" right proced-
< t
(vsize pid
) (nil t t
))
110 (rss "RSS" "%d" right proced-
< t
(rss pid
) (nil t t
))
111 (etime "ETIME" proced-format-time right proced-time-lessp t
(etime pid
) (nil t t
))
112 (pcpu "%CPU" "%.1f" right proced-
< t
(pcpu pid
) (nil t t
))
113 (pmem "%MEM" "%.1f" right proced-
< t
(pmem pid
) (nil t t
))
114 (args "ARGS" "%s" left proced-string-lessp nil
(args pid
) (nil t nil
))
116 ;; attributes defined by proced (see `proced-process-attributes')
117 (pid "PID" "%d" right proced-
< nil
(pid) (t t nil
))
118 ;; time: sum of utime and stime
119 (time "TIME" proced-format-time right proced-time-lessp t
(time pid
) (nil t t
))
120 ;; ctime: sum of cutime and cstime
121 (ctime "CTIME" proced-format-time right proced-time-lessp t
(ctime pid
) (nil t t
)))
122 "Alist of rules for handling Proced attributes.
124 Each element has the form
126 (KEY NAME FORMAT JUSTIFY PREDICATE REVERSE SORT-SCHEME FILTER-SCHEME).
128 KEY is the car of a process attribute.
130 NAME appears in the header line.
132 FORMAT specifies the format for displaying the attribute values.
133 It is either a string passed to `format' or a function called with one
134 argument, the value of the attribute.
136 If JUSTIFY is an integer, its modulus gives the width of the attribute
137 vales formatted with FORMAT. If JUSTIFY is positive, NAME appears
138 right-justified, otherwise it appears left-justified. If JUSTIFY is 'left
139 or 'right, the field width is calculated from all field values in the listing.
140 If JUSTIFY is 'left, the field values are formatted left-justified and
141 right-justified otherwise.
143 PREDICATE is the predicate for sorting and filtering the process listing
144 based on attribute KEY. PREDICATE takes two arguments P1 and P2,
145 the corresponding attribute values of two processes. PREDICATE should
146 return 'equal if P1 has same rank like P2. Any other non-nil value says
147 that P1 is \"less than\" P2, or nil if not.
149 REVERSE is non-nil if the sort order is opposite to the order defined
152 SORT-SCHEME is a list (KEY1 KEY2 ...) defing a hierarchy of rules
153 for sorting the process listing. KEY1, KEY2, ... are KEYs appearing as cars
154 of `proced-grammar-alist'. First the PREDICATE of KEY1 is evaluated.
155 If it yields non-equal, it defines the sorting order for the corresponding
156 processes. If it evaluates to 'equal the PREDICATE of KEY2 is evaluated, etc.
158 FILTER-SCHEME is a list (LESS-B EQUAL-B LARGER-B) used by the command
159 `proced-filter-attribute' for filtering KEY (see there). This command
160 compares the value of attribute KEY of every process with the value
161 of attribute KEY of the process at the position of point using PREDICATE.
162 If PREDICATE yields non-nil, the process is accepted if LESS-B is non-nil.
163 If PREDICATE yields 'equal, the process is accepted if EQUAL-B is non-nil.
164 If PREDICATE yields nil, the process is accepted if LARGER-B is non-nil.")
166 (defvar proced-custom-attributes nil
167 "List of functions defining custom attributes.
168 This variable extends the functionality of `proced-process-attributes'.
169 Each function is called with one argument, the list of attributes
170 of a system process. It returns a cons cell of the form (KEY . VALUE)
171 like `system-process-attributes'.")
173 ;; Formatting and sorting rules are defined "per attribute". If formatting
174 ;; and / or sorting should use more than one attribute, it appears more
175 ;; transparent to define a new derived attribute, so that formatting and
176 ;; sorting can use them consistently. (Are there exceptions to this rule?
177 ;; Would it be advantageous to have yet more general methods available?)
178 ;; Sorting can also be based on attributes that are invisible in the listing.
180 (defvar proced-format-alist
181 '((short user pid pcpu pmem start time args
)
182 (medium user pid pcpu pmem vsize rss ttname state start time args
)
183 (long user euid group pid pri nice pcpu pmem vsize rss ttname state
185 (verbose user euid group egid pid ppid pgrp sess comm pri nice pcpu pmem
186 state thcount vsize rss ttname tpgid minflt majflt cminflt cmajflt
187 start time utime stime ctime cutime cstime etime args
))
188 "Alist of formats of listing.
189 The car of each element is a symbol, the name of the format.
190 The cdr is a list of keys appearing in `proced-grammar-alist'.")
192 (defvar proced-format
'short
193 "Current format of Proced listing.
194 It can be the car of an element of `proced-format-alist'.
195 It can also be a list of keys appearing in `proced-grammar-alist'.")
196 (make-variable-buffer-local 'proced-format
)
198 ;; FIXME: is there a better name for filter `user' that does not coincide
199 ;; with an attribute key?
200 (defvar proced-filter-alist
201 `((user (user .
,(concat "\\`" (user-real-login-name) "\\'")))
202 (user-running (user .
,(concat "\\`" (user-real-login-name) "\\'"))
203 (state .
"\\`[Rr]\\'"))
205 (all-running (state .
"\\`[Rr]\\'"))
206 (emacs (fun-all .
(lambda (list)
207 (proced-filter-children list
,(emacs-pid))))))
208 "Alist of process filters.
209 The car of each element is a symbol, the name of the filter.
210 The cdr is a list of elementary filters that are applied to every process.
211 A process is displayed if it passes all elementary filters of a selected
214 An elementary filter can be one of the following:
215 \(KEY . REGEXP) If value of attribute KEY matches REGEXP,
217 \(KEY . FUN) Apply function FUN to attribute KEY. Accept this process,
218 if FUN returns non-nil.
219 \(function . FUN) For each process, apply function FUN to list of attributes
220 of each. Accept the process if FUN returns non-nil.
221 \(fun-all . FUN) Apply function FUN to entire process list.
222 FUN must return the filtered list.")
224 (defvar proced-filter
'user
225 "Current filter of proced listing.
226 It can be the car of an element of `proced-filter-alist'.
227 It can also be a list of elementary filters as in the cdrs of the elements
228 of `proced-filter-alist'.")
229 (make-variable-buffer-local 'proced-filter
)
231 (defvar proced-sort
'pcpu
232 "Current sorting scheme for proced listing.
233 It must be the KEY of an element of `proced-grammar-alist'.
234 It can also be a list of KEYs as in the SORT-SCHEMEs of the elements
235 of `proced-grammar-alist'.")
236 (make-variable-buffer-local 'proced-format
)
238 (defcustom proced-goal-attribute
'args
239 "If non-nil, key of the attribute that defines the `goal-column'."
241 :type
'(choice (const :tag
"none" nil
)
242 (symbol :tag
"key")))
244 (defcustom proced-timer-interval
5
245 "Time interval in seconds for updating Proced buffers."
249 (defcustom proced-timer-flag nil
250 "Non-nil for regular update of a Proced buffer.
251 Can be changed interactively via `proced-toggle-timer-flag'."
254 (make-variable-buffer-local 'proced-timer-flag
)
256 ;; Internal variables
258 (defvar proced-process-alist nil
259 "Alist of PIDs displayed by Proced.")
260 (make-variable-buffer-local 'proced-process-alist
)
262 (defvar proced-sort-internal nil
263 "Sorting scheme for listing (internal format).")
265 (defvar proced-marker-char ?
* ; the answer is 42
266 "In proced, the current mark character.")
268 ;; face and font-lock code taken from dired
269 (defgroup proced-faces nil
270 "Faces used by Proced."
275 '((t (:inherit font-lock-constant-face
)))
276 "Face used for proced marks."
277 :group
'proced-faces
)
278 (defvar proced-mark-face
'proced-mark
279 "Face name used for proced marks.")
281 (defface proced-marked
282 '((t (:inherit font-lock-warning-face
)))
283 "Face used for marked processes."
284 :group
'proced-faces
)
285 (defvar proced-marked-face
'proced-marked
286 "Face name used for marked processes.")
288 (defvar proced-re-mark
"^[^ \n]"
289 "Regexp matching a marked line.
290 Important: the match ends just after the marker.")
292 (defvar proced-header-line nil
293 "Headers in Proced buffer as a string.")
294 (make-variable-buffer-local 'proced-header-line
)
296 (defvar proced-log-buffer
"*Proced log*"
297 "Name of Proced Log buffer.")
299 (defvar proced-process-tree nil
300 "Process tree of listing (internal variable).")
302 (defvar proced-timer nil
303 "Stores if Proced timer is already installed.")
305 (defconst proced-help-string
306 "(n)ext, (p)revious, (m)ark, (u)nmark, (k)ill, (q)uit (type ? for more help)"
307 "Help string for proced.")
309 (defconst proced-header-help-echo
310 "mouse-2: sort by attribute %s%s"
311 "Help string shown when mouse is over a sortable header.")
313 (defconst proced-field-help-echo
314 "mouse-2, RET: filter by attribute %s %s"
315 "Help string shown when mouse is over a filterable field.")
317 (defvar proced-font-lock-keywords
321 (list proced-re-mark
'(0 proced-mark-face
))
324 (list (concat "^[" (char-to-string proced-marker-char
) "]")
325 '(".+" (proced-move-to-goal-column) nil
(0 proced-marked-face
)))))
327 (defvar proced-mode-map
328 (let ((km (make-sparse-keymap)))
330 (define-key km
" " 'proced-next-line
)
331 (define-key km
"n" 'next-line
)
332 (define-key km
"p" 'previous-line
)
333 (define-key km
"\C-n" 'next-line
)
334 (define-key km
"\C-p" 'previous-line
)
335 (define-key km
"\C-?" 'previous-line
)
336 (define-key km
[down] 'next-line)
337 (define-key km [up] 'previous-line)
339 (define-key km "d" 'proced-mark) ; Dired compatibility ("delete")
340 (define-key km "m" 'proced-mark)
341 (define-key km "u" 'proced-unmark)
342 (define-key km "\177" 'proced-unmark-backward)
343 (define-key km "M" 'proced-mark-all)
344 (define-key km "U" 'proced-unmark-all)
345 (define-key km "t" 'proced-toggle-marks)
346 (define-key km "C" 'proced-mark-children)
347 (define-key km "P" 'proced-mark-parents)
349 (define-key km "f" 'proced-filter-interactive)
350 (define-key km [mouse-2] 'proced-filter-attribute)
351 (define-key km "\C-m" 'proced-filter-attribute)
353 (define-key km "sc" 'proced-sort-pcpu)
354 (define-key km "sm" 'proced-sort-pmem)
355 (define-key km "sp" 'proced-sort-pid)
356 (define-key km "ss" 'proced-sort-start)
357 (define-key km "sS" 'proced-sort-interactive)
358 (define-key km "st" 'proced-sort-time)
359 (define-key km "su" 'proced-sort-user)
360 (define-key km [header-line mouse-2] 'proced-sort-header)
362 (define-key km "F" 'proced-format-interactive)
364 (define-key km "o" 'proced-omit-processes)
365 (define-key km "x" 'proced-send-signal) ; Dired compatibility
366 (define-key km "k" 'proced-send-signal) ; kill processes
368 (define-key km "g" 'revert-buffer) ; Dired compatibility
369 (define-key km "h" 'describe-mode)
370 (define-key km "?" 'proced-help)
371 (define-key km "q" 'quit-window)
372 (define-key km [remap undo] 'proced-undo)
373 (define-key km [remap advertised-undo] 'proced-undo)
375 "Keymap for proced commands.")
378 proced-menu proced-mode-map "Proced Menu"
381 :help "Mark Current Process"]
382 ["Unmark" proced-unmark
383 :help "Unmark Current Process"]
384 ["Mark All" proced-mark-all
385 :help "Mark All Processes"]
386 ["Unmark All" proced-unmark-all
387 :help "Unmark All Process"]
388 ["Toggle Marks" proced-toggle-marks
389 :help "Marked Processes Become Unmarked, and Vice Versa"]
390 ["Mark Children" proced-mark-children
391 :help "Mark Current Process and its Children"]
392 ["Mark Parents" proced-mark-parents
393 :help "Mark Current Process and its Parents"]
396 :help "Select Filter for Process Listing"
397 ,@(mapcar (lambda (el)
398 (let ((filter (car el)))
399 `[,(symbol-name filter)
400 (proced-filter-interactive ',filter)
402 :selected (eq proced-filter ',filter)]))
403 proced-filter-alist))
405 :help "Select Sorting Scheme"
406 ["Sort..." proced-sort-interactive
407 :help "Sort Process List"]
409 ["Sort by %CPU" proced-sort-pcpu]
410 ["Sort by %MEM" proced-sort-pmem]
411 ["Sort by PID" proced-sort-pid]
412 ["Sort by START" proced-sort-start]
413 ["Sort by TIME" proced-sort-time]
414 ["Sort by USER" proced-sort-user])
416 :help "Select Format for Process Listing"
417 ,@(mapcar (lambda (el)
418 (let ((format (car el)))
419 `[,(symbol-name format)
420 (proced-format-interactive ',format)
422 :selected (eq proced-format ',format)]))
423 proced-format-alist))
425 ["Omit Marked Processes" proced-omit-processes
426 :help "Omit Marked Processes in Process Listing."]
428 ["Revert" revert-buffer
429 :help "Revert Process Listing"]
430 ["Regular Update" proced-toggle-timer-flag
432 :selected (eval proced-timer-flag)
433 :help "Regular Update of Proced buffer"]
434 ["Send signal" proced-send-signal
435 :help "Send Signal to Marked Processes"]))
438 (defun proced-marker-regexp ()
439 "Return regexp matching `proced-marker-char'."
440 ;; `proced-marker-char' must appear in column zero
441 (concat "^" (regexp-quote (char-to-string proced-marker-char))))
443 (defun proced-success-message (action count)
444 "Display success message for ACTION performed for COUNT processes."
445 (message "%s %s process%s" action count (if (= 1 count) "" "es")))
447 ;; Unlike dired, we do not define our own commands for vertical motion.
448 ;; If `goal-column' is set, `next-line' and `previous-line' are fancy
449 ;; commands to satisfy our modest needs. If `proced-goal-attribute'
450 ;; and/or `goal-column' are not set, `next-line' and `previous-line'
451 ;; are really what we need to preserve the column of point.
452 ;; We use `proced-move-to-goal-column' for "non-interactive" cases only
453 ;; to get a well-defined position of point.
455 (defun proced-move-to-goal-column ()
456 "Move to `goal-column' if non-nil."
460 (forward-char goal-column)
463 (defun proced-header-line ()
464 "Return header line for Proced buffer."
465 (list (propertize " " 'display '(space :align-to 0))
466 (replace-regexp-in-string ;; preserve text properties
467 "\\(%\\)" "\\1\\1" (substring proced-header-line (window-hscroll)))))
469 (defun proced-pid-at-point ()
470 "Return pid of system process at point.
471 Return nil if point is not on a process line."
474 (if (looking-at "^. .")
475 (get-text-property (match-end 0) 'proced-pid))))
479 (define-derived-mode proced-mode nil "Proced"
480 "Mode for displaying UNIX system processes and sending signals to them.
481 Type \\<proced-mode-map>\\[proced-mark] to mark a process for later commands.
482 Type \\[proced-send-signal] to send signals to marked processes.
487 (setq buffer-read-only t
489 header-line-format '(:eval (proced-header-line)))
490 (add-hook 'post-command-hook 'force-mode-line-update nil t)
491 (set (make-local-variable 'revert-buffer-function) 'proced-revert)
492 (set (make-local-variable 'font-lock-defaults)
493 '(proced-font-lock-keywords t nil nil beginning-of-line))
494 (if (and (not proced-timer) proced-timer-interval)
496 (run-at-time t proced-timer-interval 'proced-timer))))
498 ;; Proced mode is suitable only for specially formatted data.
499 (put 'proced-mode 'mode-class 'special)
502 (defun proced (&optional arg)
503 "Mode for displaying UNIX system processes and sending signals to them.
504 Type \\<proced-mode-map>\\[proced-mark] to mark a process for later commands.
505 Type \\[proced-send-signal] to send signals to marked processes.
507 If invoked with optional ARG the window displaying the process
508 information will be displayed but not selected.
512 (let ((buffer (get-buffer-create "*Proced*")) new)
514 (setq new (zerop (buffer-size)))
515 (if new (proced-mode))
519 (display-buffer buffer)
520 (pop-to-buffer buffer)
522 (substitute-command-keys
523 "Type \\<proced-mode-map>\\[quit-window] to quit, \\[proced-help] for help")))))
525 (defun proced-timer ()
526 "Update Proced buffers regularly using `run-at-time'."
527 (dolist (buf (buffer-list))
528 (with-current-buffer buf
529 (if (and (eq major-mode 'proced-mode)
531 (proced-update t t)))))
533 (defun proced-toggle-timer-flag (arg)
534 "Change whether this Proced buffer is updated regularly.
535 With prefix ARG, update this buffer regularly if ARG is positive,
536 otherwise do not update. Sets the variable `proced-timer-flag'.
537 The time interval for updates is specified via `proced-timer-interval'."
538 (interactive (list (or current-prefix-arg 'toggle)))
539 (setq proced-timer-flag
540 (cond ((eq arg 'toggle) (not proced-timer-flag))
541 (arg (> (prefix-numeric-value arg) 0))
542 (t (not proced-timer-flag))))
543 (message "`proced-timer-flag' set to %s" proced-timer-flag))
545 (defun proced-mark (&optional count)
546 "Mark the current (or next COUNT) processes."
548 (proced-do-mark t count))
550 (defun proced-unmark (&optional count)
551 "Unmark the current (or next COUNT) processes."
553 (proced-do-mark nil count))
555 (defun proced-unmark-backward (&optional count)
556 "Unmark the previous (or COUNT previous) processes."
557 ;; Analogous to `dired-unmark-backward',
558 ;; but `ibuffer-unmark-backward' behaves different.
560 (proced-do-mark nil (- (or count 1))))
562 (defun proced-do-mark (mark &optional count)
563 "Mark the current (or next COUNT) processes using MARK."
564 (or count (setq count 1))
565 (let ((backward (< count 0))
567 (setq count (1+ (if (<= 0 count) count
568 (min (1- (line-number-at-pos)) (abs count)))))
570 (while (not (or (zerop (setq count (1- count))) (eobp)))
571 (proced-insert-mark mark backward))
572 (proced-move-to-goal-column)))
574 (defun proced-mark-all ()
576 If `transient-mark-mode' is turned on and the region is active,
579 (proced-do-mark-all t))
581 (defun proced-unmark-all ()
582 "Unmark all processes.
583 If `transient-mark-mode' is turned on and the region is active,
586 (proced-do-mark-all nil))
588 (defun proced-do-mark-all (mark)
589 "Mark all processes using MARK.
590 If `transient-mark-mode' is turned on and the region is active,
592 (let ((count 0) end buffer-read-only)
595 ;; Operate even on those lines that are only partially a part
596 ;; of region. This appears most consistent with
597 ;; `proced-move-to-goal-column'.
598 (progn (setq end (save-excursion
599 (goto-char (region-end))
600 (unless (looking-at "^") (forward-line))
602 (goto-char (region-beginning))
603 (unless (looking-at "^") (beginning-of-line)))
604 (goto-char (point-min))
605 (setq end (point-max)))
606 (while (< (point) end)
607 (setq count (1+ count))
608 (proced-insert-mark mark))
609 (proced-success-message "Marked" count))))
611 (defun proced-toggle-marks ()
612 "Toggle marks: marked processes become unmarked, and vice versa."
614 (let ((mark-re (proced-marker-regexp))
617 (goto-char (point-min))
619 (cond ((looking-at mark-re)
620 (proced-insert-mark nil))
622 (proced-insert-mark t))
624 (forward-line 1)))))))
626 (defun proced-insert-mark (mark &optional backward)
627 "If MARK is non-nil, insert `proced-marker-char'.
628 If BACKWARD is non-nil, move one line backwards before inserting the mark.
629 Otherwise move one line forward after inserting the mark."
630 (if backward (forward-line -1))
631 (insert (if mark proced-marker-char ?\s))
633 (unless backward (forward-line)))
635 (defun proced-mark-children (ppid &optional omit-ppid)
636 "Mark child processes of process PPID.
637 Also mark process PPID unless prefix OMIT-PPID is non-nil."
638 (interactive (list (proced-pid-at-point) current-prefix-arg))
639 (proced-mark-process-alist
640 (proced-filter-children proced-process-alist ppid omit-ppid)))
642 (defun proced-mark-parents (cpid &optional omit-cpid)
643 "Mark parent processes of process CPID.
644 Also mark CPID unless prefix OMIT-CPID is non-nil."
645 (interactive (list (proced-pid-at-point) current-prefix-arg))
646 (proced-mark-process-alist
647 (proced-filter-parents proced-process-alist cpid omit-cpid)))
649 (defun proced-mark-process-alist (process-alist &optional quiet)
652 (let (buffer-read-only)
654 (goto-char (point-min))
656 (when (assq (proced-pid-at-point) process-alist)
657 (insert proced-marker-char)
659 (setq count (1+ count)))
662 (proced-success-message "Marked" count))))
664 ;; Mostly analog of `dired-do-kill-lines'.
665 ;; However, for negative args the target lines of `dired-do-kill-lines'
666 ;; include the current line, whereas `dired-mark' for negative args operates
667 ;; on the preceding lines. Here we are consistent with `dired-mark'.
668 (defun proced-omit-processes (&optional arg quiet)
669 "Omit marked processes.
670 With prefix ARG, omit that many lines starting with the current line.
671 \(A negative argument omits backward.)
672 If `transient-mark-mode' is turned on and the region is active,
673 omit the processes in region.
674 If QUIET is non-nil suppress status message.
675 Returns count of omitted lines."
677 (let ((mark-re (proced-marker-regexp))
680 (cond ((use-region-p) ;; Omit active region
681 (let ((lines (count-lines (region-beginning) (region-end))))
683 (goto-char (region-beginning))
684 (while (< count lines)
685 (proced-omit-process)
686 (setq count (1+ count))))))
687 ((not arg) ;; Omit marked lines
689 (goto-char (point-min))
690 (while (and (not (eobp))
691 (re-search-forward mark-re nil t))
692 (proced-omit-process)
693 (setq count (1+ count)))))
694 ((< 0 arg) ;; Omit forward
695 (while (and (not (eobp)) (< count arg))
696 (proced-omit-process)
697 (setq count (1+ count))))
698 ((< arg 0) ;; Omit backward
699 (while (and (not (bobp)) (< count (- arg)))
701 (proced-omit-process)
702 (setq count (1+ count)))))
703 (unless (zerop count) (proced-move-to-goal-column))
704 (unless quiet (proced-success-message "Omitted" count))
707 (defun proced-omit-process ()
708 "Omit process from listing point is on.
709 Update `proced-process-alist' accordingly."
710 (setq proced-process-alist
711 (assq-delete-all (proced-pid-at-point) proced-process-alist))
712 (delete-region (line-beginning-position)
713 (save-excursion (forward-line) (point))))
717 (defun proced-filter (process-alist filter-list)
718 "Apply FILTER-LIST to PROCESS-ALIST."
719 (if (symbolp filter-list)
720 (setq filter-list (cdr (assq filter-list proced-filter-alist))))
721 (dolist (filter filter-list)
723 (cond ( ;; apply function to entire process list
724 (eq (car filter) 'fun-all)
725 (setq new-alist (funcall (cdr filter) process-alist)))
726 ( ;; apply predicate to each list of attributes
727 (eq (car filter) 'function)
728 (dolist (process process-alist)
729 (if (funcall (car filter) (cdr process))
730 (push process new-alist))))
731 (t ;; apply predicate to specified attribute
732 (let ((fun (if (stringp (cdr filter))
734 (string-match ,(cdr filter) val))
737 (dolist (process process-alist)
738 (setq value (cdr (assq (car filter) (cdr process))))
739 (if (and value (funcall fun value))
740 (push process new-alist))))))
741 (setq process-alist new-alist)))
744 (defun proced-filter-interactive (scheme &optional revert)
745 "Filter Proced buffer using SCHEME.
746 When called interactively, an empty string means nil, i.e., no filtering.
747 With prefix REVERT non-nil revert listing."
749 (let ((scheme (completing-read "Filter: "
750 proced-filter-alist nil t)))
751 (list (if (string= "" scheme) nil (intern scheme))
752 current-prefix-arg)))
753 (setq proced-filter scheme)
754 (proced-update revert))
756 (defun proced-process-tree (process-alist)
757 "Return process tree for PROCESS-ALIST.
758 The process tree is an alist with elements (PPID PID1 PID2 ...).
759 PPID is a parent PID. PID1, PID2, ... are the child processes of PPID.
760 The list of children does not include grandchildren."
761 (let (children-list ppid cpids)
762 (dolist (process process-alist children-list)
763 (setq ppid (cdr (assq 'ppid (cdr process))))
766 (if (setq cpids (assq ppid children-list))
767 (cons (cons ppid (cons (car process) (cdr cpids)))
768 (assq-delete-all ppid children-list))
769 (cons (list ppid (car process))
772 (defun proced-filter-children (process-alist ppid &optional omit-ppid)
773 "For PROCESS-ALIST return list of child processes of PPID.
774 This list includes PPID unless OMIT-PPID is non-nil."
775 (let ((proced-process-tree (proced-process-tree process-alist))
777 (dolist (pid (proced-children-pids ppid))
778 (push (assq pid process-alist) new-alist))
780 (assq-delete-all ppid new-alist)
784 (defun proced-children-pids (ppid)
785 "Return list of children PIDs of PPID (including PPID)."
786 (let ((cpids (cdr (assq ppid proced-process-tree))))
788 (cons ppid (apply 'append (mapcar 'proced-children-pids cpids)))
791 (defun proced-filter-parents (process-alist pid &optional omit-pid)
792 "For PROCESS-ALIST return list of parent processes of PID.
793 This list includes CPID unless OMIT-CPID is non-nil."
794 (let ((parent-list (unless omit-pid (list (assq pid process-alist)))))
795 (while (setq pid (cdr (assq 'ppid (cdr (assq pid process-alist)))))
796 (push (assq pid process-alist) parent-list))
799 (defun proced-filter-attribute (&optional event)
800 "Filter Proced listing based on the attribute at point.
801 Optional EVENT is the location of the Proced field."
802 (interactive (list last-input-event))
803 (if event (posn-set-point (event-end event)))
804 (let ((key (get-text-property (point) 'proced-key))
805 (pid (get-text-property (point) 'proced-pid)))
807 (let* ((grammar (assq key proced-grammar-alist))
808 (predicate (nth 4 grammar))
809 (filter (nth 7 grammar))
810 (ref (cdr (assq key (cdr (assq pid proced-process-alist)))))
813 (dolist (process proced-process-alist)
814 (setq val (funcall predicate (cdr (assq key (cdr process))) ref))
815 (if (cond ((not val) (nth 2 filter))
816 ((eq val 'equal) (nth 1 filter))
818 (push process new-alist)))
819 (setq proced-process-alist new-alist)
821 (message "No filter defined here."))))
823 ;; Proced predicates for sorting and filtering are based on a three-valued
825 ;; Predicates takes two arguments P1 and P2, the corresponding attribute
826 ;; values of two processes. Predicate should return 'equal if P1 has
827 ;; same rank like P2. Any other non-nil value says that P1 is "less than" P2,
830 (defun proced-< (num1 num2)
831 "Return t if NUM1 less than NUM2.
832 Return `equal' if NUM1 equals NUM2. Return nil if NUM1 greater than NUM2."
837 (defun proced-string-lessp (s1 s2)
838 "Return t if string S1 is less than S2 in lexicographic order.
839 Return `equal' if S1 and S2 have identical contents.
840 Return nil otherwise."
843 (string-lessp s1 s2)))
845 (defun proced-time-lessp (t1 t2)
846 "Return t if time value T1 is less than time value T2.
847 Return `equal' if T1 equals T2. Return nil otherwise."
848 (with-decoded-time-value ((high1 low1 micro1 t1)
849 (high2 low2 micro2 t2))
850 (cond ((< high1 high2))
851 ((< high2 high1) nil)
855 ((< micro2 micro1) nil)
860 (defsubst proced-xor (b1 b2)
861 "Return the logical exclusive or of args B1 and B2."
865 (defun proced-sort-p (p1 p2)
866 "Predicate for sorting processes P1 and P2."
867 (if (not (cdr proced-sort-internal))
868 ;; only one predicate: fast scheme
869 (let* ((sorter (car proced-sort-internal))
870 (k1 (cdr (assq (car sorter) (cdr p1))))
871 (k2 (cdr (assq (car sorter) (cdr p2)))))
872 ;; if the attributes are undefined, we should really abort sorting
874 (proced-xor (funcall (nth 1 sorter) k1 k2)
876 (let ((sort-list proced-sort-internal) sorter predicate k1 k2)
878 (while (setq sorter (pop sort-list))
879 (setq k1 (cdr (assq (car sorter) (cdr p1)))
880 k2 (cdr (assq (car sorter) (cdr p2)))
883 (funcall (nth 1 sorter) k1 k2)))
884 (if (not (eq predicate 'equal))
885 (throw 'done (proced-xor predicate (nth 2 sorter)))))
888 (defun proced-sort (process-alist sorter)
889 "Sort PROCESS-ALIST using scheme SORTER.
890 Return sorted process list."
891 ;; translate SORTER into a list of lists (KEY PREDICATE REVERSE)
892 (setq proced-sort-internal
893 (mapcar (lambda (arg)
894 (let ((grammar (assq arg proced-grammar-alist)))
895 (list arg (nth 4 grammar) (nth 5 grammar))))
896 (cond ((listp sorter) sorter)
897 ((and (symbolp sorter)
898 (nth 6 (assq sorter proced-grammar-alist))))
899 ((symbolp sorter) (list sorter))
900 (t (error "Sorter undefined %s" sorter)))))
901 (if proced-sort-internal
902 (sort process-alist 'proced-sort-p)
905 (defun proced-sort-interactive (scheme &optional revert)
906 "Sort Proced buffer using SCHEME.
907 When called interactively, an empty string means nil, i.e., no sorting.
908 With prefix REVERT non-nil revert listing."
910 (let ((scheme (completing-read "Sorting type: "
911 proced-grammar-alist nil t)))
912 (list (if (string= "" scheme) nil (intern scheme))
913 current-prefix-arg)))
914 (setq proced-sort scheme)
915 (proced-update revert))
917 (defun proced-sort-pcpu (&optional revert)
918 "Sort Proced buffer by percentage CPU time (%CPU)."
920 (proced-sort-interactive 'pcpu revert))
922 (defun proced-sort-pmem (&optional revert)
923 "Sort Proced buffer by percentage memory usage (%MEM)."
925 (proced-sort-interactive 'pmem))
927 (defun proced-sort-pid (&optional revert)
928 "Sort Proced buffer by PID."
930 (proced-sort-interactive 'pid revert))
932 (defun proced-sort-start (&optional revert)
933 "Sort Proced buffer by time the command started (START)."
935 (proced-sort-interactive 'start revert))
937 (defun proced-sort-time (&optional revert)
938 "Sort Proced buffer by CPU time (TIME)."
940 (proced-sort-interactive 'time revert))
942 (defun proced-sort-user (&optional revert)
943 "Sort Proced buffer by USER."
945 (proced-sort-interactive 'user revert))
947 (defun proced-sort-header (event &optional revert)
948 "Sort Proced listing based on an attribute.
949 EVENT is a mouse event with starting position in the header line.
950 It is converted in the corresponding attribute key."
952 (let ((start (event-start event))
954 (save-selected-window
955 (select-window (posn-window start))
956 (setq col (+ (1- (car (posn-col-row start)))
958 (when (and (<= 0 col) (< col (length proced-header-line)))
959 (setq key (get-text-property col 'proced-key proced-header-line))
961 (proced-sort-interactive key revert)
962 (message "No sorter defined here."))))))
966 (defun proced-format-time (time)
967 "Format time intervall TIME."
968 (let* ((ftime (float-time time))
969 (days (truncate ftime 86400))
970 (ftime (mod ftime 86400))
971 (hours (truncate ftime 3600))
972 (ftime (mod ftime 3600))
973 (minutes (truncate ftime 60))
974 (seconds (mod ftime 60)))
976 (format "%d-%02d:%02d:%02d" days hours minutes seconds))
978 (format "%02d:%02d:%02d" hours minutes seconds))
980 (format "%02d:%02d" minutes seconds)))))
982 (defun proced-format-start (start)
984 The return string is always 6 characters wide."
985 (let ((d-start (decode-time start))
986 (d-current (decode-time)))
987 (cond ( ;; process started in previous years
988 (< (nth 5 d-start) (nth 5 d-current))
989 (format-time-string " %Y" start))
990 ;; process started today
991 ((and (= (nth 3 d-start) (nth 3 d-current))
992 (= (nth 4 d-start) (nth 4 d-current)))
993 (format-time-string " %H:%M" start))
994 (t ;; process started this year
995 (format-time-string "%b %e" start)))))
997 (defun proced-format-ttname (ttname)
998 "Format attribute TTNAME, omitting prefix \"/dev/\"."
999 ;; Does this work for all systems?
1000 (format "%s" (substring ttname
1001 (if (string-match "\\`/dev/" ttname)
1004 (defun proced-format (process-alist format)
1005 "Display PROCESS-ALIST using FORMAT."
1006 (if (symbolp format)
1007 (setq format (cdr (assq format proced-format-alist))))
1008 (insert (make-string (length process-alist) ?\n))
1009 (let ((whitespace " ") header-list grammar)
1010 ;; Loop over all attributes
1011 (while (setq grammar (pop format))
1012 (if (symbolp grammar)
1013 (setq grammar (assq grammar proced-grammar-alist)))
1014 (let* ((key (car grammar))
1015 (fun (if (stringp (nth 2 grammar))
1016 `(lambda (arg) (format ,(nth 2 grammar) arg))
1018 (whitespace (if format whitespace ""))
1020 ;; We use the text property `proced-key' to store in each
1021 ;; field the corresponding key.
1022 ;; Of course, the sort predicate appearing in help-echo
1023 ;; is only part of the story. But it gives the main idea.
1024 (hprops `(proced-key ,key mouse-face highlight
1025 help-echo ,(format proced-header-help-echo
1026 (if (nth 5 grammar) "-" "+")
1028 (fprops `(proced-key ,key mouse-face highlight
1029 help-echo ,(format proced-field-help-echo
1031 (mapconcat (lambda (s)
1033 (nth 7 grammar) ""))))
1036 (goto-char (point-min))
1037 (cond ( ;; fixed width of output field
1038 (numberp (nth 3 grammar))
1039 (dolist (process process-alist)
1041 (setq value (cdr (assq key (cdr process))))
1043 (apply 'propertize (funcall fun value) fprops)
1044 (make-string (abs (nth 3 grammar)) ?\s))
1047 (push (format (concat "%" (number-to-string (nth 3 grammar)) "s")
1048 (apply 'propertize (nth 1 grammar) hprops))
1051 ( ;; last field left-justified
1052 (and (not format) (eq 'left (nth 3 grammar)))
1053 (dolist (process process-alist)
1055 (setq value (cdr (assq key (cdr process))))
1056 (if value (insert (apply 'propertize (funcall fun value) fprops)))
1058 (push (apply 'propertize (nth 1 grammar) hprops) header-list))
1060 (t ;; calculated field width
1061 (let ((width (length (nth 1 grammar)))
1063 (dolist (process process-alist)
1064 (setq value (cdr (assq key (cdr process))))
1066 (setq value (apply 'propertize (funcall fun value) fprops)
1067 width (max width (length value))
1068 field-list (cons value field-list))
1069 (push "" field-list)))
1070 (let ((afmt (concat "%" (if (eq 'left (nth 3 grammar)) "-" "")
1071 (number-to-string width) "s")))
1072 (push (format afmt (apply 'propertize (nth 1 grammar) hprops))
1074 (dolist (value (nreverse field-list))
1076 (insert (format afmt value) whitespace)
1077 (forward-line))))))))
1080 (goto-char (point-min))
1081 (dolist (process process-alist)
1082 ;; We use the text property `proced-pid' to store in each line
1083 ;; the corresponding pid
1084 (put-text-property (point) (line-end-position) 'proced-pid (car process))
1087 (setq proced-header-line
1088 (mapconcat 'identity (nreverse header-list) whitespace))
1089 (if (string-match "[ \t]+$" proced-header-line)
1090 (setq proced-header-line (substring proced-header-line 0
1091 (match-beginning 0))))
1092 ;; (delete-trailing-whitespace)
1093 (goto-char (point-min))
1094 (while (re-search-forward "[ \t\r]+$" nil t)
1095 (delete-region (match-beginning 0) (match-end 0)))))
1097 (defun proced-format-interactive (scheme &optional revert)
1098 "Format Proced buffer using SCHEME.
1099 When called interactively, an empty string means nil, i.e., no formatting.
1100 With prefix REVERT non-nil revert listing."
1102 (let ((scheme (completing-read "Format: "
1103 proced-format-alist nil t)))
1104 (list (if (string= "" scheme) nil (intern scheme))
1105 current-prefix-arg)))
1106 (setq proced-format scheme)
1107 (proced-update revert))
1111 (defun proced-process-attributes ()
1112 "Return alist of attributes for each system process.
1113 This alist can be customized via `proced-custom-attributes'."
1114 (mapcar (lambda (pid)
1115 (let* ((attributes (system-process-attributes pid))
1116 (utime (cdr (assq 'utime attributes)))
1117 (stime (cdr (assq 'stime attributes)))
1118 (cutime (cdr (assq 'cutime attributes)))
1119 (cstime (cdr (assq 'cstime attributes))))
1121 (append (list (cons 'pid pid))
1122 (if (and utime stime)
1123 (list (cons 'time (time-add utime stime))))
1124 (if (and cutime cstime)
1125 (list (cons 'ctime (time-add cutime cstime))))
1127 (dolist (fun proced-custom-attributes)
1128 (push (funcall fun attributes) attributes))
1129 (cons pid attributes)))
1130 (list-system-processes)))
1132 (defun proced-update (&optional revert quiet)
1133 "Update the `proced' process information. Preserves point and marks.
1134 With prefix REVERT non-nil, revert listing.
1135 Suppress status information if QUIET is nil."
1136 ;; This is the main function that generates and updates the process listing.
1138 (setq revert (or revert (not proced-process-alist)))
1139 (or quiet (message (if revert "Updating process information..."
1140 "Updating process display...")))
1141 ;; If point is on a field, we try to return point to that field.
1142 ;; Otherwise we try to return to the same column
1143 (let ((old-pos (let ((key (get-text-property (point) 'proced-key)))
1144 (list (proced-pid-at-point) key
1146 (if (get-text-property (1- (point)) 'proced-key)
1147 (- (point) (previous-single-property-change
1148 (point) 'proced-key))
1150 (current-column)))))
1151 buffer-read-only mp-list)
1152 ;; remember marked processes (whatever the mark was)
1153 (goto-char (point-min))
1154 (while (re-search-forward "^\\(\\S-\\)" nil t)
1155 (push (cons (save-match-data (proced-pid-at-point))
1156 (match-string-no-properties 1)) mp-list))
1158 ;; all attributes of all processes
1159 (setq proced-process-alist (proced-process-attributes))
1160 ;; do not keep undo information
1161 (if (consp buffer-undo-list)
1162 (setq buffer-undo-list nil)))
1163 ;; filtering and sorting
1164 (setq proced-process-alist
1165 (proced-sort (proced-filter proced-process-alist
1166 proced-filter) proced-sort))
1169 (proced-format proced-process-alist proced-format)
1170 (goto-char (point-min))
1174 (setq proced-header-line (concat " " proced-header-line))
1175 (if revert (set-buffer-modified-p nil))
1176 ;; set `goal-column'
1177 (let ((grammar (assq proced-goal-attribute proced-grammar-alist)))
1178 (setq goal-column ;; set to nil if no match
1180 (not (zerop (buffer-size)))
1181 (string-match (regexp-quote (nth 1 grammar))
1182 proced-header-line))
1186 ;; restore process marks and buffer position (if possible)
1187 (goto-char (point-min))
1188 (if (or mp-list old-pos)
1189 (let (pid mark new-pos)
1191 (setq pid (proced-pid-at-point))
1192 (when (setq mark (assq pid mp-list))
1195 (beginning-of-line))
1196 (when (eq (car old-pos) pid)
1198 (let ((limit (line-end-position)) pos)
1199 (while (and (not new-pos)
1200 (setq pos (next-property-change (point) nil limit)))
1202 (when (eq (nth 1 old-pos)
1203 (get-text-property (point) 'proced-key))
1204 (forward-char (min (nth 2 old-pos)
1205 (- (next-property-change (point))
1207 (setq new-pos (point))))
1209 (setq new-pos (if goal-column
1210 (+ (line-beginning-position) goal-column)
1211 (line-beginning-position)))))
1212 (setq new-pos (min (+ (line-beginning-position) (nth 2 old-pos))
1213 (line-end-position)))))
1217 (proced-move-to-goal-column)))
1218 (proced-move-to-goal-column))
1220 ;; Does the long mode-name clutter the modeline?
1224 (concat ": " (symbol-name proced-filter))
1227 (let* ((key (if (listp proced-sort) (car proced-sort)
1229 (grammar (assq key proced-grammar-alist)))
1230 (concat " by " (if (nth 5 grammar) "-" "+")
1233 (force-mode-line-update)
1235 (or quiet (input-pending-p)
1236 (message (if revert "Updating process information...done."
1237 "Updating process display...done.")))))
1239 (defun proced-revert (&rest args)
1240 "Analog of `revert-buffer'."
1243 ;; I do not want to reinvent the wheel. Should we rename `dired-pop-to-buffer'
1244 ;; and move it to window.el so that proced and ibuffer can easily use it, too?
1245 ;; What about functions like `appt-disp-window' that use
1246 ;; `shrink-window-if-larger-than-buffer'?
1247 (autoload 'dired-pop-to-buffer "dired")
1249 (defun proced-send-signal (&optional signal)
1250 "Send a SIGNAL to the marked processes.
1251 If no process is marked, operate on current process.
1252 SIGNAL may be a string (HUP, INT, TERM, etc.) or a number.
1253 If SIGNAL is nil display marked processes and query interactively for SIGNAL."
1255 (let ((regexp (proced-marker-regexp))
1257 ;; collect marked processes
1259 (goto-char (point-min))
1260 (while (re-search-forward regexp nil t)
1261 (push (cons (proced-pid-at-point)
1262 ;; How much info should we collect here?
1263 (substring (match-string-no-properties 0) 2))
1267 (nreverse process-alist)
1268 ;; take current process
1269 (list (cons (proced-pid-at-point)
1270 (buffer-substring-no-properties
1271 (+ 2 (line-beginning-position))
1272 (line-end-position))))))
1274 ;; Display marked processes (code taken from `dired-mark-pop-up').
1275 (let ((bufname " *Marked Processes*")
1276 (header-line (substring-no-properties proced-header-line)))
1277 (with-current-buffer (get-buffer-create bufname)
1278 (setq truncate-lines t
1279 proced-header-line header-line ; inherit header line
1280 header-line-format '(:eval (proced-header-line)))
1281 (add-hook 'post-command-hook 'force-mode-line-update nil t)
1283 (dolist (process process-alist)
1284 (insert " " (cdr process) "\n"))
1285 (save-window-excursion
1286 (dired-pop-to-buffer bufname) ; all we need
1287 (let* ((completion-ignore-case t)
1288 (pnum (if (= 1 (length process-alist))
1290 (format "%d processes" (length process-alist))))
1291 ;; The following is an ugly hack. Is there a better way
1292 ;; to help people like me to remember the signals and
1294 (tmp (completing-read (concat "Send signal [" pnum
1295 "] (default TERM): ")
1297 nil nil nil nil "TERM")))
1298 (setq signal (if (string-match "^\\(\\S-+\\)\\s-" tmp)
1299 (match-string 1 tmp) tmp))))))
1303 ;; Why not always use `signal-process'? See
1304 ;; http://lists.gnu.org/archive/html/emacs-devel/2008-03/msg02955.html
1305 (if (functionp proced-signal-function)
1306 ;; use built-in `signal-process'
1307 (let ((signal (if (stringp signal)
1308 (if (string-match "\\`[0-9]+\\'" signal)
1309 (string-to-number signal)
1310 (make-symbol signal))
1312 (dolist (process process-alist)
1315 proced-signal-function (car process) signal))
1316 (setq count (1+ count))
1317 (proced-log "%s\n" (cdr process))
1318 (push (cdr process) failures))
1319 (error ;; catch errors from failed signals
1320 (proced-log "%s\n" err)
1321 (proced-log "%s\n" (cdr process))
1322 (push (cdr process) failures)))))
1323 ;; use external system call
1324 (let ((signal (concat "-" (if (numberp signal)
1325 (number-to-string signal) signal))))
1326 (dolist (process process-alist)
1329 (if (zerop (call-process
1330 proced-signal-function nil t nil
1331 signal (number-to-string (car process))))
1332 (setq count (1+ count))
1333 (proced-log (current-buffer))
1334 (proced-log "%s\n" (cdr process))
1335 (push (cdr process) failures))
1336 (error ;; catch errors from failed signals
1337 (proced-log (current-buffer))
1338 (proced-log "%s\n" (cdr process))
1339 (push (cdr process) failures)))))))
1341 ;; Proced error message are not always very precise.
1342 ;; Can we issue a useful one-line summary in the
1343 ;; message area (using FAILURES) if only one signal failed?
1346 (format "%d of %d signal%s failed"
1347 (length failures) (length process-alist)
1348 (if (= 1 (length process-alist)) "" "s")))
1349 (proced-success-message "Sent signal to" count)))
1351 (run-hooks 'proced-after-send-signal-hook))))
1353 ;; similar to `dired-why'
1354 (defun proced-why ()
1355 "Pop up a buffer with error log output from Proced.
1356 A group of errors from a single command ends with a formfeed.
1357 Thus, use \\[backward-page] to find the beginning of a group of errors."
1359 (if (get-buffer proced-log-buffer)
1360 (save-selected-window
1361 ;; move `proced-log-buffer' to the front of the buffer list
1362 (select-window (display-buffer (get-buffer proced-log-buffer)))
1363 (setq truncate-lines t)
1364 (set-buffer-modified-p nil)
1365 (setq buffer-read-only t)
1366 (goto-char (point-max))
1371 ;; similar to `dired-log'
1372 (defun proced-log (log &rest args)
1373 "Log a message or the contents of a buffer.
1374 If LOG is a string and there are more args, it is formatted with
1375 those ARGS. Usually the LOG string ends with a \\n.
1376 End each bunch of errors with (proced-log t signal):
1377 this inserts the current time, buffer and signal at the start of the page,
1378 and \f (formfeed) at the end."
1379 (let ((obuf (current-buffer)))
1380 (with-current-buffer (get-buffer-create proced-log-buffer)
1381 (goto-char (point-max))
1382 (let (buffer-read-only)
1383 (cond ((stringp log)
1385 (apply 'format log args)
1388 (insert-buffer-substring log))
1393 (insert (current-time-string)
1394 "\tBuffer `" (buffer-name obuf) "', "
1395 (format "signal `%s'\n" (car args)))
1396 (goto-char (point-max))
1397 (insert "\f\n")))))))
1399 ;; similar to `dired-log-summary'
1400 (defun proced-log-summary (signal string)
1401 "State a summary of SIGNAL's failures, in echo area and log buffer.
1402 STRING is an overall summary of the failures."
1403 (message "Signal %s: %s--type ? for details" signal string)
1404 ;; Log a summary describing a bunch of errors.
1405 (proced-log (concat "\n" string "\n"))
1406 (proced-log t signal))
1408 (defun proced-help ()
1409 "Provide help for the `proced' user."
1412 (if (eq last-command 'proced-help)
1414 (message proced-help-string)))
1416 (defun proced-undo ()
1417 "Undo in a proced buffer.
1418 This doesn't recover killed processes, it just undoes changes in the proced
1419 buffer. You can use it to recover marks."
1421 (let (buffer-read-only)
1423 (message "Change in Proced buffer undone.
1424 Killed processes cannot be recovered by Emacs."))
1428 ;; arch-tag: a6e312ad-9032-45aa-972d-31a8cfc545af
1429 ;;; proced.el ends here