|
|
- ;;; helm-sys.el --- System related functions for helm. -*- lexical-binding: t -*-
-
- ;; Copyright (C) 2012 ~ 2019 Thierry Volpiatto <thierry.volpiatto@gmail.com>
-
- ;; This program is free software; you can redistribute it and/or modify
- ;; it under the terms of the GNU General Public License as published by
- ;; the Free Software Foundation, either version 3 of the License, or
- ;; (at your option) any later version.
-
- ;; This program is distributed in the hope that it will be useful,
- ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ;; GNU General Public License for more details.
-
- ;; You should have received a copy of the GNU General Public License
- ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
-
- ;;; Code:
-
- (require 'cl-lib)
- (require 'helm)
- (require 'helm-help)
- (require 'helm-utils)
-
- (defgroup helm-sys nil
- "System related helm library."
- :group 'helm)
-
- (defface helm-top-columns
- '((t :inherit helm-header))
- "Face for helm help string in minibuffer."
- :group 'helm-sys)
-
- (defcustom helm-top-command
- (cl-case system-type
- (darwin "env COLUMNS=%s ps -axo pid,user,pri,nice,ucomm,tty,start_time,vsz,%%cpu,%%mem,etime,command")
- (t "env COLUMNS=%s top -b -n 1"))
- "Top command used to display output of top.
- A format string where %s will be replaced with `frame-width'.
-
- To use 'top' command, a version supporting batch mode (-b option) is needed.
- On Mac OSX 'top' command doesn't support this, so ps command
- is used instead by default.
- Normally 'top' command output have 12 columns, but in some versions you may
- have less than this, so you can either customize top to use 12 columns with the
- interactives 'f' and 'W' commands of top, or modify
- `helm-top-sort-columns-alist' to fit with the number of columns
- your 'top' command is using.
-
- If you modify 'ps' command be sure that 'pid' comes in first
- and \"env COLUMNS=%s\" is specified at beginning of command.
- Ensure also that no elements contain spaces (e.g use start_time and not start).
- Same as for 'top' you can customize `helm-top-sort-columns-alist' to make sort commands
- working properly according to your settings."
- :group 'helm-sys
- :type 'string)
-
- (defcustom helm-top-sort-columns-alist '((com . 11)
- (mem . 9)
- (cpu . 8)
- (user . 1))
- "Allow defining which column to use when sorting output of top/ps command.
- Only com, mem, cpu and user are sorted, so no need to put something else there,
- it will have no effect.
- Note that column numbers are counted from zero, i.e column 1 is the nth 0 column."
- :group 'helm-sys
- :type '(alist :key-type symbol :value-type (integer :tag "Column number")))
-
- (defcustom helm-top-poll-delay 1.5
- "Helm top poll after this delay when `helm-top-poll-mode' is enabled.
- The minimal delay allowed is 1.5, if less than this helm-top will use 1.5."
- :group 'helm-sys
- :type 'float)
-
- (defcustom helm-top-poll-delay-post-command 1.0
- "Helm top stop polling during this delay.
- This delay is additioned to `helm-top-poll-delay' after emacs stop
- being idle."
- :group 'helm-sys
- :type 'float)
-
- (defcustom helm-top-poll-preselection 'linum
- "Stay on same line or follow candidate when `helm-top-poll' update display.
- Possible values are 'candidate or 'linum.
- This affect also sorting functions in the same way."
- :group'helm-sys
- :type '(radio :tag "Preferred preselection action for helm-top"
- (const :tag "Follow candidate" candidate)
- (const :tag "Stay on same line" linum)))
- ;;; Top (process)
- ;;
- ;;
- (defvar helm-top-sort-fn nil)
- (defvar helm-top-map
- (let ((map (make-sparse-keymap)))
- (set-keymap-parent map helm-map)
- (define-key map (kbd "M-P") 'helm-top-run-sort-by-cpu)
- (define-key map (kbd "M-C") 'helm-top-run-sort-by-com)
- (define-key map (kbd "M-M") 'helm-top-run-sort-by-mem)
- (define-key map (kbd "M-U") 'helm-top-run-sort-by-user)
- map))
-
- (defvar helm-top-after-init-hook nil
- "Local hook for helm-top.")
-
- (defvar helm-top--poll-timer nil)
-
- (defun helm-top-poll (&optional no-update delay)
- (when helm-top--poll-timer
- (cancel-timer helm-top--poll-timer))
- (condition-case nil
- (progn
- (when (and (helm--alive-p) (null no-update))
- ;; Fix quitting while process is running
- ;; by binding `with-local-quit' in init function
- ;; Issue #1521.
- (helm-force-update
- (cl-ecase helm-top-poll-preselection
- (candidate (replace-regexp-in-string
- "[0-9]+" "[0-9]+"
- (regexp-quote (helm-get-selection nil t))))
- (linum `(lambda ()
- (goto-char (point-min))
- (forward-line ,(helm-candidate-number-at-point)))))))
- (setq helm-top--poll-timer
- (run-with-idle-timer
- (helm-aif (current-idle-time)
- (time-add it (seconds-to-time
- (or delay (helm-top--poll-delay))))
- (or delay (helm-top--poll-delay)))
- nil
- 'helm-top-poll)))
- (quit (cancel-timer helm-top--poll-timer))))
-
- (defun helm-top--poll-delay ()
- (max 1.5 helm-top-poll-delay))
-
- (defun helm-top-poll-no-update ()
- (helm-top-poll t (+ (helm-top--poll-delay)
- helm-top-poll-delay-post-command)))
-
- (defun helm-top-initialize-poll-hooks ()
- ;; When emacs is idle during say 20s
- ;; the idle timer will run in 20+1.5 s.
- ;; This is fine when emacs stays idle, because the next timer
- ;; will run at 21.5+1.5 etc... so the display will be updated
- ;; at every 1.5 seconds.
- ;; But as soon as emacs looses its idleness, the next update
- ;; will occur at say 21+1.5 s, so we have to reinitialize
- ;; the timer at 0+1.5.
- (add-hook 'post-command-hook 'helm-top-poll-no-update)
- (add-hook 'focus-in-hook 'helm-top-poll-no-update))
-
- ;;;###autoload
- (define-minor-mode helm-top-poll-mode
- "Refresh automatically helm top buffer once enabled."
- :group 'helm-top
- :global t
- (if helm-top-poll-mode
- (progn
- (add-hook 'helm-top-after-init-hook 'helm-top-poll-no-update)
- (add-hook 'helm-top-after-init-hook 'helm-top-initialize-poll-hooks))
- (remove-hook 'helm-top-after-init-hook 'helm-top-poll-no-update)
- (remove-hook 'helm-top-after-init-hook 'helm-top-initialize-poll-hooks)))
-
- (defvar helm-source-top
- (helm-build-in-buffer-source "Top"
- :header-name (lambda (name)
- (concat name (if helm-top-poll-mode
- " (auto updating)"
- " (Press C-c C-u to refresh)")))
- :init #'helm-top-init
- :after-init-hook 'helm-top-after-init-hook
- :cleanup (lambda ()
- (when helm-top--poll-timer
- (cancel-timer helm-top--poll-timer))
- (remove-hook 'post-command-hook 'helm-top-poll-no-update)
- (remove-hook 'focus-in-hook 'helm-top-poll-no-update))
- :display-to-real #'helm-top-display-to-real
- :persistent-action '(helm-top-sh-persistent-action . never-split)
- :persistent-help "SIGTERM"
- :help-message 'helm-top-help-message
- :mode-line 'helm-top-mode-line
- :follow 'never
- :keymap helm-top-map
- :filtered-candidate-transformer #'helm-top-sort-transformer
- :action-transformer #'helm-top-action-transformer
- :group 'helm-sys))
-
- (defvar helm-top--line nil)
- (defun helm-top-transformer (candidates _source)
- "Transformer for `helm-top'.
- Return empty string for non--valid candidates."
- (cl-loop for disp in candidates collect
- (cond ((string-match "^ *[0-9]+" disp) disp)
- ((string-match "^ *PID" disp)
- (setq helm-top--line (cons (propertize disp 'face 'helm-top-columns) "")))
- (t (cons disp "")))
- into lst
- finally return (or (member helm-top--line lst)
- (cons helm-top--line lst))))
-
- (defun helm-top--skip-top-line ()
- (let* ((src (helm-get-current-source))
- (src-name (assoc-default 'name src)))
- (helm-aif (and (stringp src-name)
- (string= src-name "Top")
- (helm-get-selection nil t src))
- (when (string-match-p "^ *PID" it)
- (helm-next-line)))))
-
- (defun helm-top-action-transformer (actions _candidate)
- "Action transformer for `top'.
- Show actions only on line starting by a PID."
- (let ((disp (helm-get-selection nil t)))
- (cond ((string-match "\\` *[0-9]+" disp)
- (list '("kill (SIGTERM)" . (lambda (_pid)
- (helm-top-sh "TERM" (helm-top--marked-pids))))
- '("kill (SIGKILL)" . (lambda (_pid)
- (helm-top-sh "KILL" (helm-top--marked-pids))))
- '("kill (SIGINT)" . (lambda (_pid)
- (helm-top-sh "INT" (helm-top--marked-pids))))
- '("kill (Choose signal)"
- . (lambda (_pid)
- (let ((pids (helm-top--marked-pids)))
- (helm-top-sh
- (helm-comp-read (format "Kill %d pids with signal: "
- (length pids))
- '("ALRM" "HUP" "INT" "KILL" "PIPE" "POLL"
- "PROF" "TERM" "USR1" "USR2" "VTALRM"
- "STKFLT" "PWR" "WINCH" "CHLD" "URG"
- "TSTP" "TTIN" "TTOU" "STOP" "CONT"
- "ABRT" "FPE" "ILL" "QUIT" "SEGV"
- "TRAP" "SYS" "EMT" "BUS" "XCPU" "XFSZ")
- :must-match t)
- pids))))))
- (t actions))))
-
- (defun helm-top--marked-pids ()
- (helm-remove-if-not-match "\\`[0-9]+\\'" (helm-marked-candidates)))
-
- (defun helm-top-sh (sig pids)
- "Run kill shell command with signal SIG on PIDS for `helm-top'."
- (message "kill -%s %s exited with status %s"
- sig (mapconcat 'identity pids " ")
- (apply #'call-process
- "kill" nil nil nil (format "-%s" sig) pids)))
-
- (defun helm-top-sh-persistent-action (pid)
- (helm-top-sh "TERM" (list pid))
- (helm-delete-current-selection))
-
- (defun helm-top-init ()
- "Insert output of top command in candidate buffer."
- (with-local-quit
- (unless helm-top-sort-fn (helm-top-set-mode-line "CPU"))
- (with-current-buffer (helm-candidate-buffer 'global)
- (call-process-shell-command
- (format helm-top-command (frame-width))
- nil (current-buffer)))))
-
- (defun helm-top-display-to-real (line)
- "Return pid only from LINE."
- (car (split-string line)))
-
- ;; Sort top command
-
- (defun helm-top-set-mode-line (str)
- (if (string-match "Sort:\\[\\(.*\\)\\] " helm-top-mode-line)
- (setq helm-top-mode-line (replace-match str nil nil helm-top-mode-line 1))
- (setq helm-top-mode-line (concat (format "Sort:[%s] " str) helm-top-mode-line))))
-
- (defun helm-top-sort-transformer (candidates source)
- (helm-top-transformer
- (if helm-top-sort-fn
- (cl-loop for c in candidates
- if (string-match "^ *[0-9]+" c)
- collect c into pid-cands
- else collect c into header-cands
- finally return (append
- header-cands
- (sort pid-cands helm-top-sort-fn)))
- candidates)
- source))
-
- (defun helm-top-sort-by-com (s1 s2)
- (let* ((split-1 (split-string s1))
- (split-2 (split-string s2))
- (col (cdr (assq 'com helm-top-sort-columns-alist)))
- (com-1 (nth col split-1))
- (com-2 (nth col split-2)))
- (string< com-1 com-2)))
-
- (defun helm-top-sort-by-mem (s1 s2)
- (let* ((split-1 (split-string s1))
- (split-2 (split-string s2))
- (col (cdr (assq 'mem helm-top-sort-columns-alist)))
- (mem-1 (string-to-number (nth col split-1)))
- (mem-2 (string-to-number (nth col split-2))))
- (> mem-1 mem-2)))
-
- (defun helm-top-sort-by-cpu (s1 s2)
- (let* ((split-1 (split-string s1))
- (split-2 (split-string s2))
- (col (cdr (assq 'cpu helm-top-sort-columns-alist)))
- (cpu-1 (string-to-number (nth col split-1)))
- (cpu-2 (string-to-number (nth col split-2))))
- (> cpu-1 cpu-2)))
-
- (defun helm-top-sort-by-user (s1 s2)
- (let* ((split-1 (split-string s1))
- (split-2 (split-string s2))
- (col (cdr (assq 'user helm-top-sort-columns-alist)))
- (user-1 (nth col split-1))
- (user-2 (nth col split-2)))
- (string< user-1 user-2)))
-
- (defun helm-top--preselect-fn ()
- (if (eq helm-top-poll-preselection 'linum)
- `(lambda ()
- (goto-char (point-min))
- (forward-line ,(helm-candidate-number-at-point)))
- (replace-regexp-in-string
- "[0-9]+" "[0-9]+"
- (regexp-quote (helm-get-selection nil t)))))
-
- (defun helm-top-run-sort-by-com ()
- (interactive)
- (helm-top-set-mode-line "COM")
- (setq helm-top-sort-fn 'helm-top-sort-by-com)
- (helm-update (helm-top--preselect-fn)))
-
- (defun helm-top-run-sort-by-cpu ()
- (interactive)
- (helm-top-set-mode-line "CPU")
- ;; Force sorting by CPU even if some versions of top are using by
- ;; default CPU sorting (Issue #1908).
- (setq helm-top-sort-fn 'helm-top-sort-by-cpu)
- (helm-update (helm-top--preselect-fn)))
-
- (defun helm-top-run-sort-by-mem ()
- (interactive)
- (helm-top-set-mode-line "MEM")
- (setq helm-top-sort-fn 'helm-top-sort-by-mem)
- (helm-update (helm-top--preselect-fn)))
-
- (defun helm-top-run-sort-by-user ()
- (interactive)
- (helm-top-set-mode-line "USER")
- (setq helm-top-sort-fn 'helm-top-sort-by-user)
- (helm-update (helm-top--preselect-fn)))
-
- ;;; X RandR resolution change
- ;;
- ;;
- ;;; FIXME I do not care multi-display.
-
- (defun helm-xrandr-info ()
- "Return a pair with current X screen number and current X display name."
- (with-temp-buffer
- (call-process "xrandr" nil (current-buffer) nil
- "--current")
- (let (screen output)
- (goto-char (point-min))
- (save-excursion
- (when (re-search-forward "\\(^Screen \\)\\([0-9]\\):" nil t)
- (setq screen (match-string 2))))
- (when (re-search-forward "^\\(.*\\) connected" nil t)
- (setq output (match-string 1)))
- (list screen output))))
-
- (defun helm-xrandr-screen ()
- "Return current X screen number."
- (car (helm-xrandr-info)))
-
- (defun helm-xrandr-output ()
- "Return current X display name."
- (cadr (helm-xrandr-info)))
-
- (defvar helm-source-xrandr-change-resolution
- (helm-build-sync-source "Change Resolution"
- :candidates
- (lambda ()
- (with-temp-buffer
- (call-process "xrandr" nil (current-buffer) nil
- "--screen" (helm-xrandr-screen) "-q")
- (goto-char 1)
- (cl-loop while (re-search-forward " \\([0-9]+x[0-9]+\\)" nil t)
- for mode = (match-string 1)
- unless (member mode modes)
- collect mode into modes
- finally return modes)))
- :action
- (helm-make-actions "Change Resolution"
- (lambda (mode)
- (call-process "xrandr" nil nil nil
- "--screen" (helm-xrandr-screen)
- "--output" (helm-xrandr-output)
- "--mode" mode)))))
-
- ;;; Emacs process
- ;;
- ;;
- (defvar helm-source-emacs-process
- (helm-build-sync-source "Emacs Process"
- :init (lambda ()
- (let (tabulated-list-use-header-line)
- (list-processes--refresh)))
- :candidates (lambda () (mapcar #'process-name (process-list)))
- :persistent-action (lambda (elm)
- (delete-process (get-process elm))
- (helm-delete-current-selection))
- :persistent-help "Kill Process"
- :action (helm-make-actions "Kill Process"
- (lambda (_elm)
- (cl-loop for p in (helm-marked-candidates)
- do (delete-process (get-process p)))))))
-
- ;;;###autoload
- (defun helm-top ()
- "Preconfigured `helm' for top command."
- (interactive)
- (add-hook 'helm-after-update-hook 'helm-top--skip-top-line)
- (unwind-protect
- (helm :sources 'helm-source-top
- :buffer "*helm top*" :full-frame t
- :candidate-number-limit 9999
- :preselect "^\\s-*[0-9]+"
- :truncate-lines helm-show-action-window-other-window)
- (remove-hook 'helm-after-update-hook 'helm-top--skip-top-line)))
-
- ;;;###autoload
- (defun helm-list-emacs-process ()
- "Preconfigured `helm' for emacs process."
- (interactive)
- (helm-other-buffer 'helm-source-emacs-process "*helm process*"))
-
- ;;;###autoload
- (defun helm-xrandr-set ()
- "Preconfigured helm for xrandr."
- (interactive)
- (helm :sources 'helm-source-xrandr-change-resolution
- :buffer "*helm xrandr*"))
-
- (provide 'helm-sys)
-
- ;; Local Variables:
- ;; byte-compile-warnings: (not obsolete)
- ;; coding: utf-8
- ;; indent-tabs-mode: nil
- ;; End:
-
- ;;; helm-sys.el ends here
|