diff options
author | Chris Feng <chris.w.feng@gmail.com> | 2016-02-06T04·59+0800 |
---|---|---|
committer | Chris Feng <chris.w.feng@gmail.com> | 2016-02-06T04·59+0800 |
commit | 0e4055d3392537cd4138181b9c615fa03053add8 (patch) | |
tree | cf9642d140b82a8c8d401dd01e26145fd7f060ea /exwm-workspace.el | |
parent | 2d42fee327f92b01444718cfc83ce5f00716fa33 (diff) |
Add auto-hiding minibuffer support
* exwm-floating.el (exwm-floating--set-floating): Take auto-hiding minibuffer into account when calculating available height. (exwm-floating--unset-floating): Restack the container to avoid further restacking. * exwm-input.el (exwm-input--update-focus): Use more accurate restacking. (exwm-input--on-minibuffer-setup): Replaced by `exwm-layout--on-minibuffer-setup' and `exwm-workspace--on-minibuffer-setup'. (exwm-input-command-whitelist, exwm-input--during-command) (exwm-input--on-KeyPress-line-mode): The functionality of `exwm-input-command-whitelist' is replaced by `exwm-input--during-command', which can automatically tell whether functions like `read-event' are being called. (exwm-input--init): Add/remove corresponding hooks. * exwm-layout.el (exwm-layout--on-minibuffer-setup): Also set input focus. (exwm-layout--on-echo-area-change): New function for refreshing layout when the size of echo area changes. (exwm-layout--init): Track size changes for fixed minibuffer and echo area. * exwm-manage.el (exwm-manage--on-ConfigureRequest): Never grant restacking requests initiated by other clients. * exwm-workspace.el (exwm-workspace--minibuffer): New variable for the auto-hiding minibuffer. (exwm-workspace-minibuffer-position): For setting the position of the auto-hiding minibuffer. (exwm-workspace-display-echo-area-timeout): Seconds before echo area auto-hides. (exwm-workspace--display-echo-area-timer): The corresponding timer. (exwm-workspace-switch): Configure the auto-hiding minibuffer when switching workspace. (exwm-workspace--update-minibuffer): New function for adjusting the height of the auto-hiding minibuffer. (exwm-workspace--on-ConfigureNotify): New function for configuring the container of the auto-hiding minibuffer. (exwm-workspace--display-buffer): New function for forcing `minibuffer-completion-help' to use the workspace frame. (exwm-workspace--show-minibuffer, exwm-workspace--hide-minibuffer): New functions for showing/hiding the auto-hiding minibuffer (container). (exwm-workspace--on-minibuffer-setup, exwm-workspace--on-minibuffer-exit): New functions called when the auto-hiding minibuffer entered/exists. (exwm-workspace--on-echo-area-dirty, exwm-workspace--on-echo-area-clear): New functions when the auto-hiding echo area is ready to show/hide. (exwm-workspace--init): Set up the auto-hiding minibuffer and workspace frames. Track sizes changes for auto-hiding minibuffer and echo area. No need to set OverrideRedirect on workspace frames. * exwm.el (exwm--init-icccm-ewmh): Correct the value of _NET_WORKAREA.
Diffstat (limited to 'exwm-workspace.el')
-rw-r--r-- | exwm-workspace.el | 268 |
1 files changed, 248 insertions, 20 deletions
diff --git a/exwm-workspace.el b/exwm-workspace.el index 8bdcc6345029..9669428c85f9 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -24,7 +24,6 @@ ;; This module adds workspace support for EXWM. ;; Todo: -;; + Auto hide minibuffer, or allow users to place it elsewhere. ;; + Add system tray support. ;;; Code: @@ -100,6 +99,17 @@ (defvar exwm-workspace-current-index 0 "Index of current active workspace.") (defvar exwm-workspace-show-all-buffers nil "Non-nil to show buffers on other workspaces.") +(defvar exwm-workspace--minibuffer nil + "The minibuffer frame shared among all frames.") +(defvar exwm-workspace-minibuffer-position nil + "Position of the minibuffer frame. + +Value nil means to use the default position which is fixed at bottom, while +'top and 'bottom mean to use an auto-hiding minibuffer.") +(defvar exwm-workspace-display-echo-area-timeout 1 + "Timeout for displaying echo area.") +(defvar exwm-workspace--display-echo-area-timer nil + "Timer for auto-hiding echo area.") ;;;###autoload (defun exwm-workspace-switch (index &optional force) @@ -135,7 +145,30 @@ The optional FORCE option is for internal use only." ;; Close the (possible) active minibuffer (when (active-minibuffer-window) (run-with-idle-timer 0 nil (lambda () (abort-recursive-edit)))) - (setq default-minibuffer-frame frame) + (if (not (memq exwm-workspace-minibuffer-position '(top bottom))) + (setq default-minibuffer-frame frame) + ;; Resize/reposition the minibuffer frame + (let ((x 0) + (y (if (eq exwm-workspace-minibuffer-position 'top) + 0 + (- (frame-pixel-height frame) + (frame-pixel-height exwm-workspace--minibuffer)))) + (width (x-display-pixel-width)) + (container (frame-parameter exwm-workspace--minibuffer + 'exwm-container))) + (xcb:+request exwm--connection + (make-instance 'xcb:ReparentWindow + :window container + :parent (frame-parameter frame 'exwm-workspace) + :x x :y y)) + (xcb:+request exwm--connection + (make-instance 'xcb:ConfigureWindow + :window container + :value-mask (logior xcb:ConfigWindow:Width + xcb:ConfigWindow:StackMode) + :width width + :stack-mode xcb:StackMode:Above)) + (set-frame-width exwm-workspace--minibuffer width nil t))) ;; Hide windows in other workspaces by preprending a space (unless exwm-workspace-show-all-buffers (dolist (i exwm--id-buffer-alist) @@ -266,6 +299,146 @@ The optional FORCE option is for internal use only." (xcb:flush exwm--connection) frame)) +(defun exwm-workspace--update-minibuffer (&optional echo-area) + "Update the minibuffer frame." + (let ((height + (with-current-buffer + (window-buffer (minibuffer-window exwm-workspace--minibuffer)) + (max 1 + (if echo-area + (let ((width (frame-width exwm-workspace--minibuffer)) + (result 0)) + (mapc (lambda (i) + (setq result + (+ result + (ceiling (1+ (length i)) width)))) + (split-string (current-message) "\n")) + result) + (count-screen-lines)))))) + (when (and (integerp max-mini-window-height) + (> height max-mini-window-height)) + (setq height max-mini-window-height)) + (set-frame-height exwm-workspace--minibuffer height))) + +(defun exwm-workspace--on-ConfigureNotify (data _synthetic) + "Adjust the container to fit the minibuffer frame." + (let ((obj (make-instance 'xcb:ConfigureNotify)) + value-mask y) + (xcb:unmarshal obj data) + (with-slots (window height) obj + (when (eq (frame-parameter exwm-workspace--minibuffer 'exwm-outer-id) + window) + (when (and (floatp max-mini-window-height) + (> height (* max-mini-window-height + (frame-pixel-height exwm-workspace--current)))) + (setq height (floor + (* max-mini-window-height + (frame-pixel-height exwm-workspace--current)))) + (xcb:+request exwm--connection + (make-instance 'xcb:ConfigureWindow + :window window + :value-mask xcb:ConfigWindow:Height + :height height))) + (if (eq exwm-workspace-minibuffer-position 'top) + (setq value-mask xcb:ConfigWindow:Height + y 0) + (setq value-mask (logior xcb:ConfigWindow:Y xcb:ConfigWindow:Height) + y (- (frame-pixel-height exwm-workspace--current) height))) + (xcb:+request exwm--connection + (make-instance 'xcb:ConfigureWindow + :window (frame-parameter exwm-workspace--minibuffer + 'exwm-container) + :value-mask value-mask + :y y + :height height)) + (xcb:flush exwm--connection))))) + +(defun exwm-workspace--display-buffer (buffer alist) + "Display buffer in the current workspace frame. + +This functions is modified from `display-buffer-reuse-window' and +`display-buffer-pop-up-window'." + (when (eq (selected-frame) exwm-workspace--minibuffer) + (setq buffer (get-buffer buffer)) ;convert string to buffer. + (let (window) + (if (setq window (get-buffer-window buffer exwm-workspace--current)) + (window--display-buffer buffer window 'reuse alist) + (when (setq window (or (window--try-to-split-window + (get-largest-window exwm-workspace--current t) + alist) + (window--try-to-split-window + (get-lru-window exwm-workspace--current t) + alist))) + (window--display-buffer + buffer window 'window alist display-buffer-mark-dedicated)))))) + +(defun exwm-workspace--show-minibuffer () + "Show the minibuffer frame." + ;; Cancel pending timer. + (when exwm-workspace--display-echo-area-timer + (cancel-timer exwm-workspace--display-echo-area-timer) + (setq exwm-workspace--display-echo-area-timer nil)) + ;; Show the minibuffer frame. + (xcb:+request exwm--connection + (make-instance 'xcb:MapWindow + :window (frame-parameter exwm-workspace--minibuffer + 'exwm-container))) + (xcb:flush exwm--connection) + ;; Unfortunately we need the following lines to workaround a cursor + ;; flickering issue for line-mode floating X windows. They just make the + ;; minibuffer appear to be focused. + (with-current-buffer (window-buffer (minibuffer-window + exwm-workspace--minibuffer)) + (setq cursor-in-non-selected-windows + (frame-parameter exwm-workspace--minibuffer 'cursor-type)))) + +(defun exwm-workspace--hide-minibuffer () + "Hide the minibuffer frame." + ;; Hide the minibuffer frame. + (xcb:+request exwm--connection + (make-instance 'xcb:UnmapWindow + :window (frame-parameter exwm-workspace--minibuffer + 'exwm-container))) + (xcb:flush exwm--connection)) + +(defun exwm-workspace--on-minibuffer-setup () + "Run in minibuffer-setup-hook to show the minibuffer and its container." + (unless (> -1 (minibuffer-depth)) + (add-hook 'post-command-hook #'exwm-workspace--update-minibuffer) + (exwm-workspace--show-minibuffer) + ;; Set input focus on the Emacs frame + (x-focus-frame (window-frame (minibuffer-selected-window))))) + +(defun exwm-workspace--on-minibuffer-exit () + "Run in minibuffer-exit-hook to hide the minibuffer container." + (unless (> -1 (minibuffer-depth)) + (remove-hook 'post-command-hook #'exwm-workspace--update-minibuffer) + (exwm-workspace--hide-minibuffer))) + +(defvar exwm-input--during-command) + +(defun exwm-workspace--on-echo-area-dirty () + "Run when new message arrives to show the echo area and its container." + (when (and (not (active-minibuffer-window)) + (or (current-message) + cursor-in-echo-area)) + (exwm-workspace--update-minibuffer t) + (exwm-workspace--show-minibuffer) + (unless (or (not exwm-workspace-display-echo-area-timeout) + exwm-input--during-command ;e.g. read-event + input-method-use-echo-area) + (setq exwm-workspace--display-echo-area-timer + (run-with-timer exwm-workspace-display-echo-area-timeout nil + #'exwm-workspace--on-echo-area-clear))))) + +(defun exwm-workspace--on-echo-area-clear () + "Run in echo-area-clear-hook to hide echo area container." + (unless (active-minibuffer-window) + (exwm-workspace--hide-minibuffer)) + (when exwm-workspace--display-echo-area-timer + (cancel-timer exwm-workspace--display-echo-area-timer) + (setq exwm-workspace--display-echo-area-timer nil))) + (defun exwm-workspace--init () "Initialize workspace module." (cl-assert (and (< 0 exwm-workspace-number) (>= 10 exwm-workspace-number))) @@ -276,19 +449,79 @@ The optional FORCE option is for internal use only." (0 (y-or-n-p prompt)) (x (yes-or-no-p (format "[EXWM] %d window%s currently alive. %s" x (if (= x 1) "" "s") prompt)))))) - ;; Initialize workspaces - (setq exwm-workspace--list (frame-list)) - (when (< 1 (length exwm-workspace--list)) - ;; Emacs client creates an extra (but unusable) frame - (dolist (i exwm-workspace--list) - (unless (frame-parameter i 'window-id) - (setq exwm-workspace--list (delq i exwm-workspace--list)))) - (cl-assert (= 1 (length exwm-workspace--list))) - ;; Prevent user from deleting this frame by accident - (set-frame-parameter (car exwm-workspace--list) 'client nil)) - ;; Create remaining frames - (dotimes (_ (1- exwm-workspace-number)) - (nconc exwm-workspace--list (list (make-frame '((window-system . x)))))) + (if (not (memq exwm-workspace-minibuffer-position '(top bottom))) + ;; Initialize workspaces with minibuffers. + (progn + (setq exwm-workspace--list (frame-list)) + (when (< 1 (length exwm-workspace--list)) + ;; Emacs client creates an extra (but unusable) frame. + (dolist (i exwm-workspace--list) + (unless (frame-parameter i 'window-id) + (setq exwm-workspace--list (delq i exwm-workspace--list)))) + (cl-assert (= 1 (length exwm-workspace--list))) + ;; Prevent user from deleting this frame by accident. + (set-frame-parameter (car exwm-workspace--list) 'client nil)) + ;; Create remaining frames. + (dotimes (_ (1- exwm-workspace-number)) + (nconc exwm-workspace--list + (list (make-frame '((window-system . x))))))) + ;; Initialize workspaces without minibuffers. + (let ((old-frames (frame-list))) + (setq exwm-workspace--minibuffer + (make-frame '((window-system . x) (minibuffer . only) + (left . 10000) (right . 10000) + (width . 0) (height . 0))) + default-minibuffer-frame exwm-workspace--minibuffer) + ;; Remove/hide existing frames. + (dolist (f old-frames) + (if (frame-parameter f 'client) + (make-frame-invisible f) + (delete-frame f)))) + (let ((outer-id (string-to-number + (frame-parameter exwm-workspace--minibuffer + 'outer-window-id))) + (container (xcb:generate-id exwm--connection))) + (set-frame-parameter exwm-workspace--minibuffer 'exwm-outer-id outer-id) + (set-frame-parameter exwm-workspace--minibuffer 'exwm-container + container) + (xcb:+request exwm--connection + (make-instance 'xcb:CreateWindow + :depth 0 :wid container :parent exwm--root + :x -1 :y -1 :width 1 :height 1 + :border-width 0 :class xcb:WindowClass:CopyFromParent + :visual 0 ;CopyFromParent + :value-mask xcb:CW:OverrideRedirect + :override-redirect 1)) + (exwm--debug + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_WM_NAME + :window container + :data "Minibuffer container"))) + (xcb:+request exwm--connection + (make-instance 'xcb:ReparentWindow + :window outer-id :parent container :x 0 :y 0)) + ;; Attach event listener for monitoring the frame + (xcb:+request exwm--connection + (make-instance 'xcb:ChangeWindowAttributes + :window outer-id + :value-mask xcb:CW:EventMask + :event-mask xcb:EventMask:StructureNotify)) + (xcb:+event exwm--connection 'xcb:ConfigureNotify + #'exwm-workspace--on-ConfigureNotify)) + ;; Show/hide minibuffer / echo area when they're active/inactive. + (add-hook 'minibuffer-setup-hook #'exwm-workspace--on-minibuffer-setup) + (add-hook 'minibuffer-exit-hook #'exwm-workspace--on-minibuffer-exit) + (run-with-idle-timer 0 t #'exwm-workspace--on-echo-area-dirty) + (add-hook 'echo-area-clear-hook #'exwm-workspace--on-echo-area-clear) + ;; Create workspace frames. + (dotimes (_ exwm-workspace-number) + (push (make-frame `((window-system . x) + (minibuffer . ,(minibuffer-window + exwm-workspace--minibuffer)))) + exwm-workspace--list)) + ;; The default behavior of `display-buffer' (indirectly called by + ;; `minibuffer-completion-help') is not correct here. + (cl-pushnew '(exwm-workspace--display-buffer) display-buffer-alist)) ;; Configure workspaces (dolist (i exwm-workspace--list) (let ((outer-id (string-to-number (frame-parameter i 'outer-window-id))) @@ -296,11 +529,6 @@ The optional FORCE option is for internal use only." ;; Save window IDs (set-frame-parameter i 'exwm-outer-id outer-id) (set-frame-parameter i 'exwm-workspace workspace) - ;; Set OverrideRedirect on all frames - (xcb:+request exwm--connection - (make-instance 'xcb:ChangeWindowAttributes - :window outer-id :value-mask xcb:CW:OverrideRedirect - :override-redirect 1)) (xcb:+request exwm--connection (make-instance 'xcb:CreateWindow :depth 0 :wid workspace :parent exwm--root |