[Top][All Lists]

[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 
      (define-key map (vector k) #'ignore))
  "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* (...
          (let ((case-fold-search t))
             ;; 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
             ;; * 
             (string-match-p "gtk3" system-configuration-features)
             (string-match-p "gnome\\|cinnamon" (or (getenv 
                                                    (getenv "DESKTOP_SESSION") 
--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 
    ;; 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 
    ;; otherwise the border is not updated (BUG!).
    (let* ((face (if (facep 'child-frame-border) 'child-frame-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

--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)
              (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.


reply via email to

[Prev in Thread] Current Thread [Next in Thread]