ch11ng / exwm

Emacs X Window Manager
2.85k stars 134 forks source link

Can `ivy-posframe` work with exwm? #550

Open WJCFerguson opened 5 years ago

WJCFerguson commented 5 years ago

ivy-posframe modifies ivy so that completions can be presented in a floating frame, rather than the minibuffer.

This would be great with exwm, as it would allow completions that don't resize windows. However, on exwm-mode windows the frame appears under the X window, i.e. invisible.

I assume this is something about Z positioning, but I'm afraid I just don't know how to fix it(?). We have access to the frame parameters the ivy-posframe frame is created with (ivy-posframe-parameters). I tried setting z-group to above, but either I did it wrong, or it needs something else.

Here's a config I tried:

(use-package ivy-posframe
  :config
  (setq ivy-posframe-parameters '((left-fringe . 10)
                                  (right-fringe . 10)
                                  (z-group . above))
        ivy-display-function #'ivy-posframe-display-at-frame-bottom-left)
  (ivy-posframe-enable))

Any ideas? Thanks.

Edit: Warning: you can't undo ivy-posframe, so unfortunately it means restarting your exwm session to get rid of it. - not any more, just call (ivy-posframe-mode nil).

ch11ng commented 5 years ago

The answer is probably no as it uses 'child frames'. From the Elisp reference:

Raising, lowering and restacking child frames (see Raising and Lowering) or changing the ‘z-group’ (see Position Parameters) of a child frame changes only the stacking order of child frames with the same parent.

QiangF commented 5 years ago

There is a workaround mentioned on reddit:

 (setq ivy-posframe-parameters '((parent-frame nil)))
ch11ng commented 5 years ago

Thanks @QiangF . I tried the solution and it seems working. @WJCFerguson could you verify that?

BTW, apart from this package you can already avoid resizing X windows on completion with (setq exwm-workspace-minibuffer-position 'bottom). But that makes a lot of other changes.

WJCFerguson commented 5 years ago

I can confirm that the parent-frame nil fix does bring the posframe to above X windows, but for me with two monitors it results in the posframe being in badly positioned.

It appears that the frame is sent to an absolute coordinate using the desired x,y relative to the frame, so mine always ends up somewhere on my left monitor.

I assumed I might want to add the frame position to the posframe position, so I tried writing an advising function adding (frame-position (selected-frame)) to the result, but it turns out that call doesn't give me the frame position - perhaps that's an exwm bug?. Right now I'm getting (0 . 0) for all frames, but in the past I've also had them give non-zero but incorrect positions (e.g. my right monitor frame thinks it's on the left and vice-versa). This may actually be the original cause of the posframe mis-positioning, or related to it.

(On hotplug exwm-randr-screen-change-hook calls my function that sets my monitor layout with xrandr using absolute positioning, and then sets exwm-randr-workspace-output-plist to distribute frames across the monitors)

ch11ng commented 4 years ago

frame-position returns the internal record of what Emacs think where it is. It may have something to do with EXWM as Emacs frames are not not managed here. Perhaps you can simply use (elt exwm-workspace--workareas exwm-workspace-current-index) which returns [X Y WITH HEIGHT]; all workspace frames are placed according to this after all.

sarg commented 4 years ago

@WJCFerguson , have you found a solution for multi-monitor setup? Seems that this snippet works fine for me:

(defun +ivy-posframe-display-exwm (str)
  (ivy-posframe--display str
   (lambda (info)
     (let* ((workarea (elt exwm-workspace--workareas exwm-workspace-current-index))
            (x (aref workarea 0))
            (y (aref workarea 1))

            (fw (aref workarea 2))
            (fh (aref workarea 3))

            (pw (plist-get info :posframe-width))
            (ph (plist-get info :posframe-height)))

       (cons (+ x (/ (- fw pw) 2)) (+ y (/ (- fh ph) 2)))))))

(setq ivy-posframe-display-functions-alist
      '((t . +ivy-posframe-display-exwm))

      ivy-posframe-parameters '((parent-frame nil)
                                (z-group . above)))

;; force set frame-position on every posframe display
(advice-add 'posframe--set-frame-position :before
            (lambda (&rest args)
              (setq-local posframe--last-posframe-pixel-position nil)))
WJCFerguson commented 4 years ago

Thank you @sarg, that does work. I didn't find time to get to a solution, so I'm grateful to get one from you.

WJCFerguson commented 4 years ago

I kind of wanted different positioning, so instead I did this to shift the standard function like so:

  (defun jf/ivy-posframe-shift-display (return-value)
    "Shift the RETURN-VALUE to account for the work area's position."
    (let ((workarea (elt exwm-workspace--workareas exwm-workspace-current-index)))
      (cons (+ (aref workarea 0) (car return-value))
            (+ (aref workarea 1) (cdr return-value)))))
  (advice-add 'posframe-poshandler-window-bottom-left-corner
              :filter-return 'jf/ivy-posframe-shift-display)

It does require checking the right ivy-posframe-display-at-* function in ivy-posframe to find what posframe-poshandler-window-* function to advise, based on what value you have for ivy-posframe-style.

sarg commented 4 years ago

I've noticed minor bug: when I open posframe on secondary display then on every typed letter it'll jump for a moment to a primary display.

It happens because posframe-show first does posframe--set-frame-size and then posframe--set-position (https://github.com/tumashu/posframe/blob/master/posframe.el#L533). The first is using fit-frame-to-buffer which in fact can reposition the frame.

I'll try to pinpoint the reason why fit-frame-to-buffer doesn't respect original frame position for multi-monitor setup.

WJCFerguson commented 4 years ago

I see that too.

I'm curious what makes posframe positioning work badly for exwm in the first place. Surely posframe works fine for normal use of emacs in multiple windows (say, one instance with frames on two monitors)? So is there something about exwm that means the way posframe normally does it fail - e.g. frame parameters being incorrect or something. I have to do paid-for work right now, but perhaps I can work this out at the weekend.

sarg commented 4 years ago

I've spent some time after paid-for work and seems that the bug lies in lisp/window.el. Please see this patch: 0001-Fix-fit-frame-to-buffer-for-multi-monitor-setup.txt

With patched fit-frame-to-buffer advice for posframe--set-frame-position is not needed anymore.

sarg commented 4 years ago

The aforementioned patch has been merged into emacs-27: http://git.savannah.gnu.org/cgit/emacs.git/commit/?h=emacs-27&id=b42b894d1def7180ab715615116fe6af65b76bd8

medranocalvo commented 4 years ago

@sarg, that's outstanding, thank you very much.

ch11ng commented 4 years ago

@sarg Thank you!

markgdawson commented 3 years ago

I see issues with ivy-posframe when I set focus to follow the mouse:

  (setq mouse-autoselect-window t
        focus-follows-mouse 'autoraise)

It seems that #'show-posframe always moves the mouse to the top left of the screen. This makes the childframe unresponsive if mouse-autoselect-window is set and moving the mouse happens to activate another buffer.

I can somewhat mitigate this behaviour by modifying the call to posframe-show in ivy-posframe--display to add an :accept-focus t argument. But as mentioned in the posframe-show function docs, this gives some odd behaviour, notably the childframe becomes editable.

My current workaround is the following code which disables the mouse movement during the call to ivy-read and restores the mouse position afterwards (as a bonus). This seems to resolve the issues.

  (defun advise-fn-suspend-follow-mouse (fn &rest args)
    (let ((focus-follows-mouse nil)
          (mouse-autoselect-window nil)
          (pos (x-mouse-absolute-pixel-position)))
      (unwind-protect
          (apply fn args)
        (x-set-mouse-absolute-pixel-position (car pos)
                                             (cdr pos)))))

  (advice-add #'ivy-posframe--read :around #'advise-fn-suspend-follow-mouse)
thomasheartman commented 3 years ago

@sarg Thanks for the snippet you posted above; it works great! However, there is one issue:

When I trigger ivy-posframe from one of my secondary monitors after setting the frame font size below a certain threshold, the posframe appears on a different monitor (and off-center at that). It seems as if the center of the frame can't be calculated properly.

More details: I have a secondary monitor in portrait mode with a lower resolution than my main monitor. As such, I tend to set the frame font size on this frame to about half of what I use for my main monitor. The posframe is positioned correctly until I get below 147, at which point it suddenly appears on a different monitor. Whether the posframe is completely within the bounds of this other frame or not seems to depend on the font size I use. The posframe font size is always the same and is the default font size.

Do you have any idea what might be happening and how I could fix this? Thanks for any input you may have!

Stebalien commented 3 years ago

For anyone reading this thread, I've found that setting the parent frame to nil after creating it (through advice) works better than setting it in the initial frame parameters:

(with-eval-after-load 'posframe
    (define-advice posframe-show (:filter-return (frame) exwm-deparent)
      (set-frame-parameter frame 'parent-frame nil)
      frame))

It this is likely because posframe uses the parent frame when determining size/position.

tumashu commented 3 years ago

I try to fix ivy-posframe in exwm problem, maybe useful:

  1. https://github.com/tumashu/posframe/commit/9a6bb60c55cea7627764f42fe3c296dea6abec8c
  2. https://github.com/tumashu/ivy-posframe/commit/ada1dbab9bb203207586e2f6d359b4949602e50f
tumashu commented 3 years ago

I have update ivy-posframe, it should work well with exwm, at the moment, I use the below refposhandler,
it can improve future I think, PR is welcome :-)

(defun ivy-posframe-refposhandler-default (&optional frame)
  "The default posframe refposhandler used by ivy-posframe."
  (cond
   ;; EXWM environment
   (exwm--connection
    (or (ignore-errors
          ;; Need user install xwininfo.
          (posframe-refposhandler-xwininfo frame))
        ;; FIXME: maybe exwm provide some function,
        ;; Which can get top-left of emacs.
        (cons 0 0)))
   (t nil)))
tumashu commented 3 years ago

posframe need update too