diff --git a/etc/images/README b/etc/images/README index a778d9ce6c3..77377d36b5a 100644 --- a/etc/images/README +++ b/etc/images/README @@ -125,7 +125,7 @@ For more information see the adwaita-icon-theme repository at: https://gitlab.gnome.org/GNOME/adwaita-icon-theme -Emacs images and their source in the Adwaita/scalable directory: +Emacs images and their source in the Adwaita/symbolic directory: checked.svg ui/checkbox-checked-symbolic.svg unchecked.svg ui/checkbox-symbolic.svg @@ -137,3 +137,5 @@ Emacs images and their source in the Adwaita/scalable directory: left.svg ui/pan-start-symbolic.svg right.svg ui/pan-end-symbolic.svg up.svg ui/pan-up-symbolic.svg + conceal.svg actions/view-conceal-symbolic.svg + reveal.svg actions/view-reveal-symbolic.svg diff --git a/etc/images/conceal.svg b/etc/images/conceal.svg new file mode 100644 index 00000000000..172b73ed3d3 --- /dev/null +++ b/etc/images/conceal.svg @@ -0,0 +1,4 @@ + + + + diff --git a/etc/images/reveal.svg b/etc/images/reveal.svg new file mode 100644 index 00000000000..41ae3733a53 --- /dev/null +++ b/etc/images/reveal.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/simple.el b/lisp/simple.el index 9a33049f4ca..667b6c84a66 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -10858,6 +10858,80 @@ visible-mode (setq-local vis-mode-saved-buffer-invisibility-spec buffer-invisibility-spec) (setq buffer-invisibility-spec nil))) + + +;; It would be preferable to use "👁" ("\N{EYE}"). However, there is +;; no corresponding Unicode char with a slash. Therefore, we use images. +(defvar read-passwd-show-password-image "reveal.svg" + "Mode-line image to show a hidden password") + +(defvar read-passwd-hide-password-image "conceal.svg" + "Mode-line image to hide a visible password") + +(defvar read-passwd-mode-line-buffer nil + "Buffer to modify `mode-line-format' for showing/hiding passwords.") + +(defvar read-passwd-mode-line-string nil + "Propertized mode line indicator for showing/hiding passwords.") + +(defvar read-passwd-mode-line-display nil + "Display properties for `read-passwd-mode-line-string'.") + +(defun read-passwd--toggle-visibility () + "Toggle minibuffer contents visibility. +Adapt also mode line." + (interactive) + (with-current-buffer read-passwd-mode-line-buffer + (setq read-passwd--hide-password (not read-passwd--hide-password)) + (when (display-graphic-p) + (setq read-passwd-mode-line-display + (find-image + `((:type svg + :file ,(if read-passwd--hide-password + read-passwd-hide-password-image + read-passwd-show-password-image)) + :ascent center)) + read-passwd-mode-line-string + `(:propertize " " + display ,read-passwd-mode-line-display + help-echo "mouse-1: Toggle password visibility" + mouse-face mode-line-highlight + local-map + (keymap + (mode-line keymap (mouse-1 . read-passwd--toggle-visibility))))) + (force-mode-line-update))) + (read-passwd--hide-password)) + +(define-minor-mode read-passwd-mode + "Toggle visibility of password in minibuffer." + :group 'mode-line + :group 'minibuffer + :keymap read-passwd-map + :version "30.1" + (setq read-passwd--hide-password nil + ;; Stolen from `eldoc-minibuffer-message'. + read-passwd-mode-line-buffer + (window-buffer + (or (window-in-direction 'above (minibuffer-window)) + (minibuffer-selected-window) + (get-largest-window)))) + + (when (display-graphic-p) + (if read-passwd-mode + (with-current-buffer read-passwd-mode-line-buffer + ;; Add `read-passwd-mode-line-string'. + (when (listp mode-line-format) + (setq mode-line-format + (cons '(:eval read-passwd-mode-line-string) + mode-line-format)))) + (with-current-buffer read-passwd-mode-line-buffer + ;; Remove `read-passwd-mode-line-string'. + (when (listp mode-line-format) + (setq mode-line-format (cdr mode-line-format)))))) + + (when read-passwd-mode + (read-passwd--toggle-visibility))) + (defvar messages-buffer-mode-map (let ((map (make-sparse-keymap))) diff --git a/lisp/subr.el b/lisp/subr.el index c317d558e24..2f475891df9 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -3375,14 +3375,23 @@ read-passwd-map (let ((map (make-sparse-keymap))) (set-keymap-parent map minibuffer-local-map) (define-key map "\C-u" #'delete-minibuffer-contents) ;bug#12570 + (define-key map "\t" #'read-passwd--toggle-visibility) map) "Keymap used while reading passwords.") -(defun read-password--hide-password () +(defvar read-passwd--hide-password t) + +(defun read-passwd--hide-password () + "Make minibuffer contents hidden or visible." (let ((beg (minibuffer-prompt-end))) (dotimes (i (1+ (- (buffer-size) beg))) - (put-text-property (+ i beg) (+ 1 i beg) - 'display (string (or read-hide-char ?*)))))) + (if read-passwd--hide-password + (put-text-property + (+ i beg) (+ 1 i beg) 'display (string (or read-hide-char ?*))) + (remove-list-of-text-properties (+ i beg) (+ 1 i beg) '(display))) + (put-text-property + (+ i beg) (+ 1 i beg) + 'help-echo "C-u: Clear password\nTAB: Toggle password visibility")))) (defun read-passwd (prompt &optional confirm default) "Read a password, prompting with PROMPT, and return it. @@ -3420,18 +3429,20 @@ read-passwd (setq-local inhibit-modification-hooks nil) ;bug#15501. (setq-local show-paren-mode nil) ;bug#16091. (setq-local inhibit--record-char t) - (add-hook 'post-command-hook #'read-password--hide-password nil t)) + (read-passwd-mode 1) + (add-hook 'post-command-hook #'read-passwd--hide-password nil t)) (unwind-protect (let ((enable-recursive-minibuffers t) (read-hide-char (or read-hide-char ?*))) (read-string prompt nil t default)) ; t = "no history" + (read-passwd-mode -1) (when (buffer-live-p minibuf) (with-current-buffer minibuf ;; Not sure why but it seems that there might be cases where the ;; minibuffer is not always properly reset later on, so undo ;; whatever we've done here (bug#11392). (remove-hook 'after-change-functions - #'read-password--hide-password 'local) + #'read-passwd--hide-password 'local) (kill-local-variable 'post-self-insert-hook) ;; And of course, don't keep the sensitive data around. (erase-buffer))))))))