'read-passwd' can toggle the visibility of passwords

* doc/lispref/minibuf.texi (Reading a Password):
* etc/NEWS: 'read-passwd' can toggle the visibility of passwords.

* etc/images/README: Mention the new images below.
* etc/images/conceal.pbm:
* etc/images/conceal.svg:
* etc/images/reveal.pbm:
* etc/images/reveal.svg: New images.

* lisp/simple.el (read-passwd--mode-line-buffer)
(read-passwd--mode-line-icon): New defvars.
(read-passwd--toggle-visibility, read-passwd-mode): New defuns.

* lisp/subr.el (read-passwd-map): Add 'TAB' binding.
(read-passwd--hide-password): New defvar.
(read-passwd--hide-password): Rename function from
`read-password--hide-password'.  Adapt callees.  Implement both
hiding and showing the password.  (Bug#69237)
(read-passwd): Call `read-passwd-mode'.
This commit is contained in:
Michael Albinus 2024-02-25 10:06:09 +01:00
parent 67ba629a91
commit 39e3fce0d5
9 changed files with 128 additions and 8 deletions

View file

@ -2562,6 +2562,14 @@ times match.
The optional argument @var{default} specifies the default password to
return if the user enters empty input. If @var{default} is @code{nil},
then @code{read-passwd} returns the null string in that case.
This function uses @code{read-passwd-mode}, a minor mode. It binds two
keys in the minbuffer: @kbd{C-u} (@code{delete-minibuffer-contents})
deletes the password, and @kbd{TAB}
(@code{read-passwd--toggle-visibility}) toggles the visibility of the
password. There is also an additional icon in the mode-line. Clicking
on this icon with @key{mouse-1} toggles the visibility of the password
as well.
@end defun
@node Minibuffer Commands

View file

@ -322,6 +322,12 @@ Previously, it was set to t but this broke remote file name detection.
** Multi-character key echo now ends with a suggestion to use Help.
Customize 'echo-keystrokes-help' to nil to prevent that.
+++
** 'read-passwd' can toggle the visibility of passwords.
Use 'TAB' in the minibuffer to show or hide the password. Likewise,
there is an icon on the mode-line, which toggles the visibility of the
password when clicking with 'mouse-1'.
* Editing Changes in Emacs 30.1
@ -1939,7 +1945,8 @@ Example:
"Uses c:\remote\dir\files and the key \C-x."
...)
where the doc string contains four control characters CR, DEL, FF and ^X.
where the docstring contains four control characters 'CR', 'DEL', 'FF'
and 'C-x'.
The warning name is 'docstrings-control-chars'.
@ -2025,7 +2032,7 @@ automatically, which means that the size parameter to 'obarray-make' can
safely be omitted. That is, they do not become slower as they fill up.
The old vector representation is still accepted by functions operating
on obarrays, but 'obarrayp' only returns 't' for obarray objects.
on obarrays, but 'obarrayp' only returns t for obarray objects.
'type-of' now returns 'obarray' for obarray objects.
Old code which (incorrectly) created "obarrays" as Lisp vectors filled

View file

@ -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,8 @@ 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
conceal.pbm and reveal.pbm are generated from the respective *.svg
files, using the ImageMagick converter tool.

BIN
etc/images/conceal.pbm Normal file

Binary file not shown.

4
etc/images/conceal.svg Normal file
View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
<path d="m 1.53125 0.46875 l -1.0625 1.0625 l 14 14 l 1.0625 -1.0625 l -2.382812 -2.382812 c 1.265624 -1.0625 2.171874 -2.496094 2.589843 -4.097657 c -0.914062 -3.523437 -4.097656 -5.984375 -7.738281 -5.988281 c -1.367188 0.011719 -2.707031 0.371094 -3.894531 1.042969 z m 6.46875 3.53125 c 2.210938 0 4 1.789062 4 4 c -0.003906 0.800781 -0.246094 1.578125 -0.699219 2.238281 l -1.46875 -1.46875 c 0.105469 -0.242187 0.164063 -0.503906 0.167969 -0.769531 c 0 -1.105469 -0.894531 -2 -2 -2 c -0.265625 0.003906 -0.527344 0.0625 -0.769531 0.167969 l -1.46875 -1.46875 c 0.660156 -0.453125 1.4375 -0.695313 2.238281 -0.699219 z m -6.144531 0.917969 c -0.753907 0.898437 -1.296875 1.957031 -1.59375 3.09375 c 0.914062 3.523437 4.097656 5.984375 7.738281 5.988281 c 0.855469 -0.007812 1.703125 -0.152344 2.511719 -0.425781 l -1.667969 -1.667969 c -0.277344 0.058594 -0.5625 0.089844 -0.84375 0.09375 c -2.210938 0 -4 -1.789062 -4 -4 c 0.003906 -0.28125 0.035156 -0.566406 0.09375 -0.84375 z m 0 0" fill="#2e3436"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
etc/images/reveal.pbm Normal file

Binary file not shown.

4
etc/images/reveal.svg Normal file
View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
<path d="m 8 2 c -3.648438 0.003906 -6.832031 2.476562 -7.738281 6.007812 c 0.914062 3.527344 4.097656 5.988282 7.738281 5.992188 c 3.648438 -0.003906 6.832031 -2.476562 7.738281 -6.011719 c -0.914062 -3.523437 -4.097656 -5.984375 -7.738281 -5.988281 z m 0 2 c 2.210938 0 4 1.789062 4 4 s -1.789062 4 -4 4 s -4 -1.789062 -4 -4 s 1.789062 -4 4 -4 z m 0 2 c -1.105469 0 -2 0.894531 -2 2 s 0.894531 2 2 2 s 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 0" fill="#2e3436"/>
</svg>

After

Width:  |  Height:  |  Size: 606 B

View file

@ -10858,6 +10858,87 @@ and setting it to nil."
(setq-local vis-mode-saved-buffer-invisibility-spec
buffer-invisibility-spec)
(setq buffer-invisibility-spec nil)))
(defvar read-passwd--mode-line-buffer nil
"Buffer to modify `mode-line-format' for showing/hiding passwords.")
(defvar read-passwd--mode-line-icon nil
"Propertized mode line icon for showing/hiding passwords.")
(defun read-passwd--toggle-visibility ()
"Toggle minibuffer contents visibility.
Adapt also mode line."
(interactive)
(setq read-passwd--hide-password (not read-passwd--hide-password))
(with-current-buffer read-passwd--mode-line-buffer
(setq read-passwd--mode-line-icon
`(:propertize
,(if icon-preference
(icon-string
(if read-passwd--hide-password
'read-passwd--show-password-icon
'read-passwd--hide-password-icon))
"")
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"
(require 'icons)
;; It would be preferable to use "👁" ("\N{EYE}"). However, there is
;; no corresponding Unicode char with a slash. So we use symbols as
;; fallback only, with "⦵" ("\N{CIRCLE WITH HORIZONTAL BAR}") for
;; hiding the password.
(define-icon read-passwd--show-password-icon nil
'((image "reveal.svg" "reveal.pbm" :height (0.8 . em))
(symbol "👁")
(text "o"))
"Mode line icon to show a hidden password."
:group mode-line-faces
:version "30.1"
:help-echo "mouse-1: Toggle password visibility")
(define-icon read-passwd--hide-password-icon nil
'((image "conceal.svg" "conceal.pbm" :height (0.8 . em))
(symbol "")
(text "x"))
"Mode line icon to hide a visible password."
:group mode-line-faces
:version "30.1"
:help-echo "mouse-1: Toggle password visibility")
(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))))
(if read-passwd-mode
(with-current-buffer read-passwd--mode-line-buffer
;; Add `read-passwd--mode-line-icon'.
(when (listp mode-line-format)
(setq mode-line-format
(cons '(:eval read-passwd--mode-line-icon)
mode-line-format))))
(with-current-buffer read-passwd--mode-line-buffer
;; Remove `read-passwd--mode-line-icon'.
(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)))

View file

@ -3378,14 +3378,23 @@ with Emacs. Do not call it directly in your own packages."
(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 password in minibuffer 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.
@ -3423,18 +3432,20 @@ by doing (clear-string STRING)."
(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"
(when (buffer-live-p minibuf)
(with-current-buffer minibuf
(read-passwd-mode -1)
;; 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))))))))