[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
On the pains of using child-frames / posframes
From: |
Tassilo Horn |
Subject: |
On the pains of using child-frames / posframes |
Date: |
Sat, 17 Jul 2021 22:18:51 +0200 |
User-agent: |
mu4e 1.5.13; emacs 28.0.50 |
Daniel Mendler <mail@daniel-mendler.de> writes:
> Not yet, for now I am only collecting and marking hacks and
> workarounds in the code. There are already many workarounds for bugs
> in child frames. The same applies to the Posframe package.
Indeed, posframe and corfu share a quite a bunch of workarounds for
making child-frames working as intended. In corfu.el those workarounds
are nicely annotated, so here is a summary:
The `no-accept-focus' frame parameter doesn't work reliably, e.g., it
doesn't work for me in Sway (a wayland window manager). I've reported
that as a sway bug, and a sway dev told me that this is probably a bug
in their Xwayland implementation. But he also said that the GTK
function gtk_window_set_accept_focus used by emacs to indicate that a
frame shouldn't receive focus in gtk builds is a no-op for wayland, and
that wayland doesn't really have a concept of non-focusable windows. So
no-accept-focus will never work when emacs is built as a native wayland
application, i.e., with the pgtk branch.
Corfu works around that by redirecting focus back to the parent frame
(posframe does the same), and ignoring mouse-clicks in the buffer shown
by the child-frame.
--8<---------------cut here---------------start------------->8---
(defun corfu--popup-redirect-focus ()
"Redirect focus from popup."
(redirect-frame-focus corfu--frame (frame-parent corfu--frame)))
;;; XXX HACK install redirect focus hook
(add-hook 'pre-command-hook #'corfu--popup-redirect-focus nil 'local)
(defvar corfu--mouse-ignore-map
(let ((map (make-sparse-keymap)))
(dolist (k '(mouse-1 down-mouse-1 drag-mouse-1 double-mouse-1 triple-mouse-1
mouse-2 down-mouse-2 drag-mouse-2 double-mouse-2 triple-mouse-2
mouse-3 down-mouse-3 drag-mouse-3 double-mouse-3 triple-mouse-3
mouse-4 down-mouse-4 drag-mouse-4 double-mouse-4 triple-mouse-4
mouse-5 down-mouse-5 drag-mouse-5 double-mouse-5 triple-mouse-5
mouse-6 down-mouse-6 drag-mouse-6 double-mouse-6 triple-mouse-6
mouse-7 down-mouse-7 drag-mouse-7 double-mouse-7
triple-mouse-7))
(define-key map (vector k) #'ignore))
map)
"Ignore all mouse clicks.")
;;; XXX HACK install mouse ignore map
(use-local-map corfu--mouse-ignore-map)
--8<---------------cut here---------------end--------------->8---
With GTK builds and Gnome / Cinnamon, one needs the following hack to
make the child-frame resize properly:
--8<---------------cut here---------------start------------->8---
(let* (...
(x-gtk-resize-child-frames
(let ((case-fold-search t))
(and
;; XXX HACK to fix resizing on gtk3/gnome taken from posframe.el
;; More information:
;; * https://github.com/minad/corfu/issues/17
;; * https://gitlab.gnome.org/GNOME/mutter/-/issues/840
;; *
https://lists.gnu.org/archive/html/emacs-devel/2020-02/msg00001.html
(string-match-p "gtk3" system-configuration-features)
(string-match-p "gnome\\|cinnamon" (or (getenv
"XDG_CURRENT_DESKTOP")
(getenv "DESKTOP_SESSION")
""))
'resize-mode)))
--8<---------------cut here---------------end--------------->8---
Some workarounds against flicker, and a case in which the order of
setting a face attribute and setting the frame parameters makes a
difference for no obvious reason.
--8<---------------cut here---------------start------------->8---
;; XXX HACK Setting the same frame-parameter/face-background is not a nop
(BUG!).
;; Check explicitly before applying the setting.
;; Without the check, the frame flickers on Mac.
;; XXX HACK We have to apply the face background before adjusting the frame
parameter,
;; otherwise the border is not updated (BUG!).
(let* ((face (if (facep 'child-frame-border) 'child-frame-border
'internal-border))
(new (face-attribute 'corfu-border :background)))
(unless (equal (face-attribute face :background corfu--frame) new)
(set-face-background face new corfu--frame)))
(let ((new (face-attribute 'corfu-background :background)))
(unless (equal (frame-parameter corfu--frame 'background-color) new)
(set-frame-parameter corfu--frame 'background-color new)))
--8<---------------cut here---------------end--------------->8---
More flicker avoidance.
--8<---------------cut here---------------start------------->8---
;; XXX HACK Make the frame invisible before moving the popup from above to
below the line in
;; order to avoid flicker.
(unless (eq (< (cdr (frame-position corfu--frame)) yb) (< y yb))
(make-frame-invisible corfu--frame))
(set-frame-size corfu--frame width height t)
(set-frame-position corfu--frame x y)
(make-frame-visible corfu--frame)))
--8<---------------cut here---------------end--------------->8---
It seems like some overlays can make `posn-at-point' return wrong
y-values.
--8<---------------cut here---------------start------------->8---
;;; XXX HACK On Emacs 28 y-coordinate position computation is wrong if
;;; there exists a flymake underline overlay at that point. Therefore
;;; compute the y-coordinate at the line beginning.
(x (or (car (posn-x-y (posn-at-point pos))) 0))
(y (save-excursion
(goto-char pos)
(beginning-of-line)
(or (cdr (posn-x-y (posn-at-point))) 0))))
--8<---------------cut here---------------end--------------->8---
Also, the number of frame parameters one needs to apply for a typical
child-frame whose intent is to display completions, tooltips, or inline
help is quite large and nothing which one can write from the top of
one's head.
--8<---------------cut here---------------start------------->8---
(defvar corfu--frame-parameters
'((no-accept-focus . t)
(no-focus-on-map . t)
(min-width . t)
(min-height . t)
(width . 0)
(height . 0)
(border-width . 0)
(child-frame-border-width . 1)
(left-fringe . 0)
(right-fringe . 0)
(vertical-scroll-bars . nil)
(horizontal-scroll-bars . nil)
(menu-bar-lines . 0)
(tool-bar-lines . 0)
(tab-bar-lines . 0)
(no-other-frame . t)
(unsplittable . t)
(undecorated . t)
(cursor-type . nil)
(minibuffer . nil)
(visibility . nil)
(no-special-glyphs . t)
(desktop-dont-save . t))
"Default child frame parameters.")
--8<---------------cut here---------------end--------------->8---
Ditto for the buffer one's going to display in the child-frame.
--8<---------------cut here---------------start------------->8---
(defvar corfu--buffer-parameters
'((mode-line-format . nil)
(header-line-format . nil)
(tab-line-format . nil)
(frame-title-format . "")
(truncate-lines . t)
(cursor-in-non-selected-windows . nil)
(cursor-type . nil)
(show-trailing-whitespace . nil)
(display-line-numbers . nil)
(left-fringe-width . nil)
(right-fringe-width . nil)
(left-margin-width . 0)
(right-margin-width . 0)
(fringes-outside-margins . 0)
(buffer-read-only . t))
"Default child frame buffer parameters.")
--8<---------------cut here---------------end--------------->8---
This all looks to me as if child-frames should probably have some
dedicated API in vanilla emacs so that using them becomes a bit easier
and less verbose. Also, that packages like corfu and posframe have to
duplicate each other's workarounds for several issues like the
unreliability of `no-accept-focus' doesn't seem right. If there are
issues on certain supported platforms, those are better addressed in
emacs itself.
Bye,
Tassilo