;;; symon.el --- tiny graphical system monitor ;; Copyright (C) 2015 zk_phi ;; 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 2 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, write to the Free Software ;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ;; Author: zk_phi ;; URL: http://hins11.yu-yake.com/ ;; Package-Version: 20160630 ;; Package-Commit: 7beeedd70dc37f5904c781fb697c8df056196ee9 ;; Version: 1.2.0 ;;; Commentary: ;; Load this script ;; ;; (require 'symon) ;; ;; and turn on `symon-mode'. ;; ;; (symon-mode) ;; ;; then a tiny system monitor is displayed in minibuffer, during idle. ;;; Change Log: ;; 1.0.0 first release ;; 1.1.0 add option symon-sparkline-thickness ;; 1.1.1 add symon-windows-page-file-monitor ;; 1.1.2 add darwin support (mac os x) ;; 1.2.0 add paging feature ;;; Code: (require 'battery) (require 'ring) (defconst symon-version "1.2.0") (defgroup symon nil "tiny graphical system monitor" :group 'emacs) ;; + customs ;; core (defcustom symon-refresh-rate 4 "refresh rate of symon display. *set this option BEFORE enabling `symon-mode'.*" :group 'symon) (defcustom symon-delay 2 "delay in seconds until symon is displayed. *set this option BEFORE enabling `symon-mode'.*" :group 'symon) (defcustom symon-history-size 50 "number of old values to keep. sparklines grow faster when set smaller. *set this option BEFORE enabling `symon-mode'.*" :group 'symon) (defcustom symon-monitors (cond ((memq system-type '(gnu/linux cygwin)) '(symon-linux-memory-monitor symon-linux-cpu-monitor symon-linux-network-rx-monitor symon-linux-network-tx-monitor)) ((memq system-type '(darwin)) '(symon-darwin-memory-monitor symon-darwin-cpu-monitor symon-darwin-network-rx-monitor symon-darwin-network-tx-monitor)) ((memq system-type '(windows-nt)) '(symon-windows-memory-monitor symon-windows-cpu-monitor symon-windows-network-rx-monitor symon-windows-network-tx-monitor))) "List of monitors used to read system statuses. This variable also can be a list of lists from version 1.2, that case monitors are displayed in multiple pages. *set this option BEFORE enabling `symon-mode'.*") ;; sparkline (defcustom symon-sparkline-height 11 "height of sparklines." :group 'symon) (defcustom symon-sparkline-width 80 "width of sparklines." :group 'symon) (defcustom symon-sparkline-ascent 100 "`:ascent' property for sparklines." :group 'symon) (defcustom symon-sparkline-thickness 2 "line width of sparklines." :group 'symon) (defcustom symon-sparkline-type 'gridded "type of sparklines." :group 'symon) ;; some darwin builds cannot render xbm images (foreground color is ;; always black), so convert to xpm before rendering. (defcustom symon-sparkline-use-xpm (eq system-type 'darwin) "when non-nil, convert sparklines to xpm from xbm before rendering." :group 'symon) ;; network monitor (defcustom symon-network-rx-upper-bound 300 "upper-bound of sparkline for network RX status." :group 'symon) (defcustom symon-network-tx-upper-bound 100 "upper-bound of sparkline for network TX status." :group 'symon) (defcustom symon-network-rx-lower-bound 0 "lower-bound of sparkline for network RX status." :group 'symon) (defcustom symon-network-tx-lower-bound 0 "lower-bound of sparkline for network TX status." :group 'symon) ;; page-file monitor (defcustom symon-windows-page-file-upper-bound 2000 "upper-bound of sparkline for page file usage." :group 'symon) ;; + utilities ;; + general (defun symon--flatten (lst) "flatten LST" (if (consp lst) (apply 'nconc (mapcar 'symon--flatten lst)) (list lst))) ;; + sparkline generator ;; sparkline-types are internally a symbol with property ;; 'symon-sparkline-type associated to a function that generates a ;; 2d-bool-vector. (defvar symon--sparkline-base-cache [nil symon-sparkline-width symon-sparkline-height nil]) (defun symon--get-sparkline-base () (unless (and (eq (aref symon--sparkline-base-cache 0) symon-sparkline-type) (= (aref symon--sparkline-base-cache 1) symon-sparkline-width) (= (aref symon--sparkline-base-cache 2) symon-sparkline-height)) (aset symon--sparkline-base-cache 0 symon-sparkline-type) (aset symon--sparkline-base-cache 1 symon-sparkline-width) (aset symon--sparkline-base-cache 2 symon-sparkline-height) (aset symon--sparkline-base-cache 3 (funcall (get symon-sparkline-type 'symon-sparkline-type)))) (copy-sequence (aref symon--sparkline-base-cache 3))) (defun symon--make-sparkline (list &optional minimum maximum) "make sparkline image from LIST." (let ((num-samples (length list))) (unless (zerop num-samples) (let* ((image-data (symon--get-sparkline-base)) (maximum (if maximum (float maximum) 100.0)) (minimum (if minimum (float minimum) 0.0)) (topmargin (1- symon-sparkline-thickness)) (height (- symon-sparkline-height topmargin)) (height-per-point (/ height (1+ (- maximum minimum)))) (width-per-sample (/ symon-sparkline-width (float num-samples))) (samples (apply 'vector list)) sample y ix) (dotimes (x symon-sparkline-width) (setq sample (aref samples (floor (/ x width-per-sample)))) (when (numberp sample) (setq y (floor (* (- sample minimum) height-per-point))) (when (and (<= 0 y) (< y height)) (dotimes (dy symon-sparkline-thickness) (aset image-data (+ (* (- symon-sparkline-height (+ y dy) 1) symon-sparkline-width) x) t))))) `(image :type xbm :data ,image-data :ascent ,symon-sparkline-ascent :height ,symon-sparkline-height :width ,symon-sparkline-width))))) (defun symon--convert-sparkline-to-xpm (sparkline) "convert sparkline to an xpm image." (let ((data (plist-get (cdr sparkline) :data))) (with-temp-buffer (insert (format "/* XPM */ static char * sparkline_xpm[] = { \"%d %d 2 1\", \"@ c %s\", \". c none\"" symon-sparkline-width symon-sparkline-height (face-foreground 'default))) (let ((ix 0)) (dotimes (x symon-sparkline-height) (insert ",\n\"") (dotimes (y symon-sparkline-width) (insert (if (aref data ix) ?@ ?.)) (setq ix (1+ ix))) (insert "\""))) (insert "};") `(image :type xpm :data ,(buffer-string) :ascent ,symon-sparkline-ascent :height ,symon-sparkline-height :width ,symon-sparkline-width)))) ;; + symon monitor generator ;; a symon monitor is internally a symbol with property 'symon-monitor ;; associated to a vector of 3 functions: [SETUP-FN CLEANUP-FN ;; DISPLAY-FN]. SETUP-FN is called on activation of `symon-mode', and ;; expected to setup Emacs to fetch status values in a specific ;; interval. CLEANUP-FN is called on deactivation and expected to tell ;; Emacs to stop fetching. DISPLAY-FN is called just before displaying ;; monitor, and must return display string for the monitor. (defun symon--make-history-ring () "like `(make-ring symon-history-size)' but filled with `nil'." (cons 0 (cons symon-history-size (make-vector symon-history-size nil)))) (defmacro define-symon-monitor (name &rest plist) "define a new symon monitor NAME. following keywords are supoprted in PLIST: :setup (default: nil) an expression evaluated when activating symon-mode, and expected to do some preparation. :cleanup (default: nil) an expression evaluated when deactivating symon-mode, and expected to do some cleanup. :fetch (default: nil) an expression that evaluates to the latest status value. the value must be a number (otherwise `N/A' is displayed as the value). :interval (default: symon-refresh-rate) fetch interval in seconds. :index (default: \"\") string prepended to the status value (\"MEM:\" for memory monitor, for example). :unit (default: \"\") string appended to the status value (\"%\" for memory monitor, for example). :annotation (default: nil) an expression that evaluates to the annotation string for the metrics (\"xxxKB Swapped\" for memory monitor, for example). if this expression returns a non-nil value, it is surrounded with parentheses and appended to the status value. :display (default: nil) an expression evaluated before updating symon display. when this expression evaluates to a non-nil value, it will be displayed instead of standard symon display format. :sparkline (default: nil) when non-nil, sparklines are rendered. :lower-bound (default: 100.0) upper bound of sparkline. :upper-bound (default: 0.0) lower bound of sparkline." (let* ((cell (make-vector 2 nil)) (sparkline (plist-get plist :sparkline)) (interval (or (plist-get plist :interval) 'symon-refresh-rate)) (display (plist-get plist :display)) (update-fn `(lambda () (ring-insert (aref ,cell 0) ,(plist-get plist :fetch)))) (setup-fn `(lambda () (aset ,cell 0 (symon--make-history-ring)) (aset ,cell 1 (run-with-timer 0 ,interval ,update-fn)) ,(plist-get plist :setup) (funcall ,update-fn))) (cleanup-fn `(lambda () (cancel-timer (aref ,cell 1)) ,(plist-get plist :cleanup))) (display-fn (if display `(lambda () (concat ,display " ")) `(lambda () (let* ((lst (ring-elements (aref ,cell 0))) (val (car lst))) (concat ,(plist-get plist :index) (if (not (numberp val)) "N/A " (concat (format "%d%s " val ,(or (plist-get plist :unit) "")) (let ((annot ,(plist-get plist :annotation))) (when annot (concat "(" annot ") "))))) ,(when sparkline `(when (window-system) (let ((sparkline (symon--make-sparkline lst ,(plist-get plist :lower-bound) ,(plist-get plist :upper-bound)))) (when symon-sparkline-use-xpm (setq sparkline (symon--convert-sparkline-to-xpm sparkline))) (concat (propertize " " 'display sparkline) " ")))))))))) `(put ',name 'symon-monitor (vector ,setup-fn ,cleanup-fn ,display-fn)))) ;; + process management (defvar symon--process-buffer-name " *symon-process*") (defvar symon--process-reference-count 0) (defun symon--read-value-from-process-buffer (index) "Read a value from a specific buffer" (when (get-buffer symon--process-buffer-name) (with-current-buffer symon--process-buffer-name (when (save-excursion (search-backward-regexp (concat index ":\\([0-9]+\\)\\>") nil t)) (read (match-string 1)))))) (defun symon--maybe-start-process (cmd) (setq symon--process-reference-count (1+ symon--process-reference-count)) (unless (get-buffer symon--process-buffer-name) (let ((proc (start-process-shell-command "symon-process" symon--process-buffer-name cmd)) (filter (lambda (proc str) (when (get-buffer symon--process-buffer-name) (with-current-buffer symon--process-buffer-name (when (and (string-match "-" str) (search-backward "----" nil t)) (delete-region 1 (point))) (goto-char (1+ (buffer-size))) (insert str)))))) (set-process-query-on-exit-flag proc nil) (set-process-filter proc filter)))) (defun symon--maybe-kill-process () (setq symon--process-reference-count (1- symon--process-reference-count)) (when (and (zerop symon--process-reference-count) (get-buffer symon--process-buffer-name)) (kill-buffer symon--process-buffer-name))) ;; + predefined monitors ;; + linux monitors (defun symon-linux--read-lines (file reader indices) (with-temp-buffer (insert-file-contents file) (goto-char 1) (mapcar (lambda (index) (save-excursion (when (search-forward-regexp (concat "^" index "\\(.*\\)$") nil t) (if reader (funcall reader (match-string 1)) (match-string 1))))) indices))) (defvar symon-linux--last-cpu-ticks nil) (define-symon-monitor symon-linux-cpu-monitor :index "CPU:" :unit "%" :sparkline t :setup (setq symon-linux--last-cpu-ticks nil) :fetch (cl-destructuring-bind (cpu) (symon-linux--read-lines "/proc/stat" (lambda (str) (mapcar 'read (split-string str nil t))) '("cpu")) (let ((total (apply '+ cpu)) (idle (nth 3 cpu))) (prog1 (when symon-linux--last-cpu-ticks (let ((total-diff (- total (car symon-linux--last-cpu-ticks))) (idle-diff (- idle (cdr symon-linux--last-cpu-ticks)))) (/ (* (- total-diff idle-diff) 100) total-diff))) (setq symon-linux--last-cpu-ticks (cons total idle)))))) (define-symon-monitor symon-linux-memory-monitor :index "MEM:" :unit "%" :sparkline t :fetch (cl-destructuring-bind (memtotal memavailable memfree buffers cached) (symon-linux--read-lines "/proc/meminfo" (lambda (str) (and str (read str))) '("MemTotal:" "MemAvailable:" "MemFree:" "Buffers:" "Cached:")) (if memavailable (/ (* (- memtotal memavailable) 100) memtotal) (/ (* (- memtotal (+ memfree buffers cached)) 100) memtotal))) :annotation (cl-destructuring-bind (swaptotal swapfree) (symon-linux--read-lines "/proc/meminfo" 'read '("SwapTotal:" "SwapFree:")) (let ((swapped (/ (- swaptotal swapfree) 1000))) (unless (zerop swapped) (format "%dMB Swapped" swapped))))) (define-symon-monitor symon-linux-battery-monitor :index "BAT:" :unit "%" :sparkline t :fetch (when battery-status-function (read (cdr (assoc ?p (funcall battery-status-function)))))) (defvar symon-linux--last-network-rx nil) (define-symon-monitor symon-linux-network-rx-monitor :index "RX:" :unit "KB/s" :sparkline t :upper-bound symon-network-rx-upper-bound :lower-bound symon-network-rx-lower-bound :setup (setq symon-linux--last-network-rx nil) :fetch (with-temp-buffer (insert-file-contents "/proc/net/dev") (goto-char 1) (let ((rx 0)) (while (search-forward-regexp "^[\s\t]*\\(.*\\):" nil t) (unless (string= (match-string 1) "lo") (setq rx (+ rx (read (current-buffer)))))) (prog1 (when symon-linux--last-network-rx (/ (- rx symon-linux--last-network-rx) symon-refresh-rate 1000)) (setq symon-linux--last-network-rx rx))))) (defvar symon-linux--last-network-tx nil) (define-symon-monitor symon-linux-network-tx-monitor :index "TX:" :unit "KB/s" :sparkline t :upper-bound symon-network-tx-upper-bound :lower-bound symon-network-tx-lower-bound :setup (setq symon-linux--last-network-tx nil) :fetch (with-temp-buffer (insert-file-contents "/proc/net/dev") (goto-char 1) (let ((tx 0)) (while (search-forward-regexp "^[\s\t]*\\(.*\\):" nil t) (unless (string= (match-string 1) "lo") (forward-word 8) (setq tx (+ tx (read (current-buffer)))))) (prog1 (when symon-linux--last-network-tx (/ (- tx symon-linux--last-network-tx) symon-refresh-rate 1000)) (setq symon-linux--last-network-tx tx))))) ;; + darwin monitors (defun symon-darwin--maybe-start-process () (symon--maybe-start-process (format " while true; do echo \"----\" interface=`route get 0.0.0.0 | grep interface | awk '{print $2}'` s=`netstat -bi -I $interface | tail -1`; echo $s | awk '{print \"rx:\"$7}' echo $s | awk '{print \"tx:\"$8}' s=`hostinfo | grep 'Load average' | awk '{print \"cpu:\"$3}' | sed 's/,//'` echo $s m1=`sysctl hw.memsize | sed 's/.*:\s*//'` m_active=`vm_stat | grep 'Pages active' | sed 's/.*: *//'` m_wired=`vm_stat | grep 'Pages wired' | sed 's/.*: *//'` s=`echo \"scale=2; (($m_active+$m_wired)*4096*100 / $m1)\"| bc -l` echo \"mem:$s\" sleep %d done" symon-refresh-rate))) (define-symon-monitor symon-darwin-cpu-monitor :index "CPU:" :unit "%" :sparkline t :setup (symon-darwin--maybe-start-process) :cleanup (symon--maybe-kill-process) :fetch (symon--read-value-from-process-buffer "cpu")) (define-symon-monitor symon-darwin-memory-monitor :index "MEM:" :unit "%" :sparkline t :setup (symon-darwin--maybe-start-process) :cleanup (symon--maybe-kill-process) :fetch (symon--read-value-from-process-buffer "mem")) (defvar symon-darwin--last-network-rx nil) (define-symon-monitor symon-darwin-network-rx-monitor :index "RX:" :unit "KB/s" :sparkline t :upper-bound symon-network-rx-upper-bound :lower-bound symon-network-rx-lower-bound :setup (progn (symon-darwin--maybe-start-process) (setq symon-darwin--last-network-rx nil)) :cleanup (symon--maybe-kill-process) :fetch (let ((rx (symon--read-value-from-process-buffer "rx"))) (prog1 (when symon-darwin--last-network-rx (/ (- rx symon-darwin--last-network-rx) symon-refresh-rate 1000)) (setq symon-darwin--last-network-rx rx)))) (defvar symon-darwin--last-network-tx nil) (define-symon-monitor symon-darwin-network-tx-monitor :index "TX:" :unit "KB/s" :sparkline t :upper-bound symon-network-tx-upper-bound :lower-bound symon-network-tx-lower-bound :setup (progn (symon-darwin--maybe-start-process) (setq symon-darwin--last-network-tx nil)) :cleanup (symon--maybe-kill-process) :fetch (let ((tx (symon--read-value-from-process-buffer "tx"))) (prog1 (when symon-darwin--last-network-tx (/ (- tx symon-darwin--last-network-tx) symon-refresh-rate 1000)) (setq symon-darwin--last-network-tx tx)))) (define-symon-monitor symon-darwin-battery-monitor :index "BAT:" :unit "%" :sparkline t :fetch (when battery-status-function (read (cdr (assoc ?p (funcall battery-status-function)))))) ;; + windows monitors (defun symon-windows--maybe-start-wmi-process () (symon--maybe-start-process (format "powershell -command \ $last = 0; \ while(1) \ { \ echo ----; \ \ $t = (gwmi Win32_ComputerSystem).TotalPhysicalMemory / 1000; \ $f = (gwmi Win32_OperatingSystem).FreePhysicalMemory; \ echo mem:$(($t - $f) * 100 / $t); \ \ echo swap:$((gwmi Win32_PageFileUsage).CurrentUsage); \ \ echo bat:$((gwmi Win32_Battery).EstimatedChargeRemaining); \ \ $r = 0; \ $t = 0; \ $w = gwmi Win32_PerfRawData_Tcpip_NetworkInterface; \ foreach($x in $w){ \ $r = $r + $x.BytesReceivedPersec; \ $t = $t + $x.BytesSentPersec \ } \ echo rx:$($r / 1000); \ echo tx:$($t / 1000); \ \ $p = (gwmi Win32_PerfRawData_Counters_ProcessorInformation)[0]; \ if($last) \ { \ $dt = $p.Timestamp_Sys100NS - $last.Timestamp_Sys100NS; \ $dp = $p.PercentProcessorTime - $last.PercentProcessorTime; \ echo cpu:$((1 - ($dp / $dt)) * 100); \ } \ $last = $p; \ \ sleep %d \ }" symon-refresh-rate))) (define-symon-monitor symon-windows-cpu-monitor :index "CPU:" :unit "%" :sparkline t :setup (symon-windows--maybe-start-wmi-process) :cleanup (symon--maybe-kill-process) :fetch (symon--read-value-from-process-buffer "cpu")) (define-symon-monitor symon-windows-memory-monitor :index "MEM:" :unit "%" :sparkline t :setup (symon-windows--maybe-start-wmi-process) :cleanup (symon--maybe-kill-process) :fetch (symon--read-value-from-process-buffer "mem")) (define-symon-monitor symon-windows-page-file-monitor :index "PF:" :unit "MB" :sparkline t :upper-bound symon-windows-page-file-upper-bound :setup (symon-windows--maybe-start-wmi-process) :cleanup (symon--maybe-kill-process) :fetch (symon--read-value-from-process-buffer "swap")) (define-symon-monitor symon-windows-battery-monitor :index "BAT:" :unit "%" :sparkline t :setup (symon-windows--maybe-start-wmi-process) :cleanup (symon--maybe-kill-process) :fetch (symon--read-value-from-process-buffer "bat")) (defvar symon-windows--last-network-rx nil) (define-symon-monitor symon-windows-network-rx-monitor :index "RX:" :unit "KB/s" :sparkline t :upper-bound symon-network-rx-upper-bound :lower-bound symon-network-rx-lower-bound :setup (progn (symon-windows--maybe-start-wmi-process) (setq symon-windows--last-network-rx nil)) :cleanup (symon--maybe-kill-process) :fetch (let ((rx (symon--read-value-from-process-buffer "rx"))) (prog1 (when symon-windows--last-network-rx (/ (- rx symon-windows--last-network-rx) symon-refresh-rate)) (setq symon-windows--last-network-rx rx)))) (defvar symon-windows--last-network-tx nil) (define-symon-monitor symon-windows-network-tx-monitor :index "TX:" :unit "KB/s" :sparkline t :upper-bound symon-network-tx-upper-bound :lower-bound symon-network-tx-lower-bound :setup (progn (symon-windows--maybe-start-wmi-process) (setq symon-windows--last-network-tx nil)) :cleanup (symon--maybe-kill-process) :fetch (let ((tx (symon--read-value-from-process-buffer "tx"))) (prog1 (when symon-windows--last-network-tx (/ (- tx symon-windows--last-network-tx) symon-refresh-rate)) (setq symon-windows--last-network-tx tx)))) ;; + misc monitors (define-symon-monitor symon-current-time-monitor :display (format-time-string "%H:%M")) ;; + predefined sparkline types (defun symon--sparkline-draw-horizontal-grid (vec y) (dotimes (x/2 (/ symon-sparkline-width 2)) (aset vec (+ (* y symon-sparkline-width) (* x/2 2)) t))) (defun symon--sparkline-draw-vertical-grid (vec x) (dotimes (y/2 (/ symon-sparkline-height 2)) (aset vec (+ (* (* y/2 2) symon-sparkline-width) x) t))) (defun symon--make-plain-sparkline () (make-bool-vector (* symon-sparkline-height symon-sparkline-width) nil)) (defun symon--make-bounded-sparkline () (let ((vec (symon--make-plain-sparkline))) (symon--sparkline-draw-horizontal-grid vec 0) (symon--sparkline-draw-horizontal-grid vec (1- symon-sparkline-height)) vec)) (defun symon--make-boxed-sparkline () (let ((vec (symon--make-bounded-sparkline))) (symon--sparkline-draw-vertical-grid vec 0) (symon--sparkline-draw-vertical-grid vec (1- symon-sparkline-width)) vec)) (defun symon--make-gridded-sparkline () (let ((vec (symon--make-boxed-sparkline))) (symon--sparkline-draw-horizontal-grid vec (/ symon-sparkline-height 2)) (symon--sparkline-draw-vertical-grid vec (/ symon-sparkline-width 4)) (symon--sparkline-draw-vertical-grid vec (/ symon-sparkline-width 2)) (symon--sparkline-draw-vertical-grid vec (/ (* symon-sparkline-width 3) 4)) vec)) (put 'plain 'symon-sparkline-type 'symon--make-plain-sparkline) (put 'bounded 'symon-sparkline-type 'symon--make-bounded-sparkline) (put 'boxed 'symon-sparkline-type 'symon--make-boxed-sparkline) (put 'gridded 'symon-sparkline-type 'symon--make-gridded-sparkline) ;; + symon core (defvar symon--cleanup-fns nil) ; List[Fn] (defvar symon--display-fns nil) ; List[List[Fn]] (defvar symon--display-active nil) (defvar symon--active-page nil) (defvar symon--total-page-num nil) (defvar symon--timer-objects nil) (defun symon--initialize () (unless symon-monitors (message "Warning: `symon-monitors' is empty.")) (let* ((symon-monitors ; for backward-compatibility (if (symbolp (car symon-monitors)) (list symon-monitors) symon-monitors)) (monitors (mapcar (lambda (lst) (mapcar (lambda (s) (get s 'symon-monitor)) lst)) symon-monitors)) (monitors-flattened (symon--flatten monitors))) (mapc (lambda (m) (funcall (aref m 0))) monitors-flattened) ; setup-fns (setq symon--cleanup-fns (mapcar (lambda (m) (aref m 1)) monitors-flattened) symon--display-fns (mapcar (lambda (l) (mapcar (lambda (m) (aref m 2)) l)) monitors) symon--display-active nil symon--total-page-num (length symon-monitors) symon--timer-objects (list (run-with-timer 0 symon-refresh-rate 'symon--redisplay) (run-with-idle-timer symon-delay t 'symon-display))) (add-hook 'pre-command-hook 'symon--display-end) (add-hook 'kill-emacs-hook 'symon--cleanup))) (defun symon--cleanup () (remove-hook 'kill-emacs-hook 'symon--cleanup) (remove-hook 'pre-command-hook 'symon--display-end) (mapc 'cancel-timer symon--timer-objects) (mapc 'funcall symon--cleanup-fns)) (defun symon--display-update () "update symon display" (unless (or cursor-in-echo-area (active-minibuffer-window)) (let ((message-log-max nil) ; do not insert to *Messages* buffer (display-string nil) (page 0)) (dolist (lst symon--display-fns) (if (= page symon--active-page) (message "%s" (apply 'concat (mapcar 'funcall lst))) (mapc 'funcall lst)) (setq page (1+ page)))) (setq symon--display-active t))) (defun symon-display () "activate symon display." (interactive) (setq symon--active-page 0) (symon--display-update)) (defun symon--redisplay () "update symon display." (when symon--display-active (setq symon--active-page (% (1+ symon--active-page) symon--total-page-num)) (symon--display-update))) (defun symon--display-end () "deactivate symon display." (setq symon--display-active nil)) ;;;###autoload (define-minor-mode symon-mode "tiny graphical system monitor" :init-value nil :global t (if symon-mode (symon--initialize) (symon--cleanup))) ;; + provide (provide 'symon) ;;; symon.el ends here