From 3f7722079cebd0d998239ce40457899135250a15 Mon Sep 17 00:00:00 2001 From: Chris Feng Date: Thu, 18 Feb 2016 19:56:01 +0800 Subject: [PATCH] Fix floating X window bugs introduced by 9c95c03e * exwm-floating.el (exwm-floating--set-floating): Make floating frames invisible before resizing them. (exwm-floating--fit-frame-to-window): Removed since unused. * exwm-layout.el (exwm-layout-hide-mode-line, exwm-layout-show-mode-line): Use set frame height instead of exwm-floating--fit-frame-to-window. * exwm-core.el (exwm-mode): Replace `exwm-manage--close-window' with `exwm-manage--kill-buffer-query-function'. * exwm-floating.el (exwm-floating--unset-floating): Reparent out floating frames. * exwm-manage.el (exwm-manage--unmanage-window): Reparent out floating frames. Hide floating frames. (exwm-manage--close-window, exwm-manage--kill-buffer-query-function): Rename `exwm-manage--close-window' since it's only used in `kill-buffer-query-functions'. Reparent out floating frames. --- exwm-core.el | 4 +- exwm-floating.el | 60 +++++++++--------- exwm-layout.el | 29 +++++---- exwm-manage.el | 158 ++++++++++++++++++++++++++--------------------- 4 files changed, 137 insertions(+), 114 deletions(-) diff --git a/exwm-core.el b/exwm-core.el index 9430b43..b09ca52 100644 --- a/exwm-core.el +++ b/exwm-core.el @@ -142,9 +142,7 @@ (add-hook 'change-major-mode-hook #'kill-buffer nil t) ;; Kill buffer -> close window (add-hook 'kill-buffer-query-functions - (lambda () - (exwm-manage--close-window exwm--id (current-buffer))) - nil t) + #'exwm-manage--kill-buffer-query-function nil t) (setq buffer-read-only t left-margin-width nil right-margin-width nil diff --git a/exwm-floating.el b/exwm-floating.el index b5ab8a8..82b4487 100644 --- a/exwm-floating.el +++ b/exwm-floating.el @@ -50,6 +50,8 @@ (defvar exwm-floating--cursor-bottom-left nil) (defvar exwm-floating--cursor-left nil) +(declare-function exwm-layout--refresh "exwm-layout.el") + ;;;###autoload (defun exwm-floating--set-floating (id) "Make window ID floating." @@ -78,6 +80,8 @@ (internal-border-width . ,exwm-floating-border-width) (left . 10000) (top . 10000) + (width . ,window-min-width) + (height . ,window-min-height) (unsplittable . t))))) ;and fix the size later (outer-id (string-to-number (frame-parameter frame 'outer-window-id))) (container (with-current-buffer (exwm--id->buffer id) @@ -151,7 +155,17 @@ y (/ (- display-height height) 2)))))) (exwm--log "Floating geometry (corrected): %dx%d%+d%+d" width height x y) ;; Fit frame to client - (exwm-floating--fit-frame-to-window outer-id width height) + ;; It seems we have to make the frame invisible in order to resize it + ;; timely. + ;; The frame will be made visible by `select-frame-set-input-focus'. + (make-frame-invisible frame) + (let ((edges (window-inside-pixel-edges window))) + (set-frame-size frame + (+ width (- (frame-pixel-width frame) + (- (elt edges 2) (elt edges 0)))) + (+ height (- (frame-pixel-height frame) + (- (elt edges 3) (elt edges 1)))) + t)) ;; Reparent this frame to the container (xcb:+request exwm--connection (make-instance 'xcb:ReparentWindow @@ -171,8 +185,12 @@ (setq window-size-fixed exwm--fixed-size exwm--frame original-frame exwm--floating-frame frame) + ;; Do the refresh manually. + (remove-hook 'window-configuration-change-hook #'exwm-layout--refresh) (set-window-buffer window (current-buffer)) ;this changes current buffer - (set-window-dedicated-p window t)) + (add-hook 'window-configuration-change-hook #'exwm-layout--refresh) + (set-window-dedicated-p window t) + (exwm-layout--show id window)) (select-frame-set-input-focus frame)) (run-hooks 'exwm-floating-setup-hook)) @@ -182,6 +200,16 @@ (interactive) (let ((buffer (exwm--id->buffer id))) (with-current-buffer buffer + ;; Reparent the frame back to the root window. + (when exwm--floating-frame + (let ((frame-id (frame-parameter exwm--floating-frame 'exwm-outer-id))) + (xcb:+request exwm--connection + (make-instance 'xcb:UnmapWindow :window frame-id)) + (xcb:+request exwm--connection + (make-instance 'xcb:ReparentWindow + :window frame-id + :parent exwm--root + :x 0 :y 0)))) ;; Reparent the container to the workspace (xcb:+request exwm--connection (make-instance 'xcb:ReparentWindow @@ -221,34 +249,6 @@ (exwm-floating--unset-floating exwm--id) (exwm-floating--set-floating exwm--id)))) -;;;###autoload -(defun exwm-floating--fit-frame-to-window (&optional frame-outer-id - width height) - "Resize a floating frame to make it fit the size of the window. - -Default to resize `exwm--floating-frame' unless FRAME-OUTER-ID is non-nil. -This function will issue an `xcb:GetGeometry' request unless WIDTH and HEIGHT -are provided. You should call `xcb:flush' and restore the value of -`window-size-fixed' afterwards." - (setq window-size-fixed nil) - (unless (and width height) - (let ((geometry (xcb:+request-unchecked+reply exwm--connection - (make-instance 'xcb:GetGeometry :drawable exwm--id)))) - (setq width (slot-value geometry 'width) - height (slot-value geometry 'height)))) - (xcb:+request exwm--connection - (make-instance 'xcb:ConfigureWindow - :window (or frame-outer-id - (frame-parameter exwm--floating-frame - 'exwm-outer-id)) - :value-mask (eval-when-compile - (logior xcb:ConfigWindow:Width - xcb:ConfigWindow:Height)) - :width (+ width (* 2 exwm-floating-border-width)) - :height (+ height (* 2 exwm-floating-border-width) - (window-mode-line-height) - (window-header-line-height))))) - (define-obsolete-function-alias 'exwm-floating-hide-mode-line 'exwm-layout-hide-mode-line "25.1" "Hide mode-line of a floating frame.") (define-obsolete-function-alias 'exwm-floating-show-mode-line diff --git a/exwm-layout.el b/exwm-layout.el index a70cf1b..52a84b0 100644 --- a/exwm-layout.el +++ b/exwm-layout.el @@ -396,13 +396,18 @@ See also `exwm-layout-enlarge-window'." "Hide mode-line." (interactive) (when (and (eq major-mode 'exwm-mode) mode-line-format) - (setq exwm--mode-line-format mode-line-format - mode-line-format nil) - (if (not exwm--floating-frame) - (exwm-layout--show exwm--id) - (exwm-floating--fit-frame-to-window) - (xcb:flush exwm--connection) - (setq window-size-fixed exwm--fixed-size)))) + (let (mode-line-height) + (when exwm--floating-frame + (setq mode-line-height (window-mode-line-height + (frame-root-window exwm--floating-frame)))) + (setq exwm--mode-line-format mode-line-format + mode-line-format nil) + (if (not exwm--floating-frame) + (exwm-layout--show exwm--id) + (set-frame-height exwm--floating-frame + (- (frame-pixel-height exwm--floating-frame) + mode-line-height) + nil t))))) (defun exwm-layout-show-mode-line () "Show mode-line." @@ -412,10 +417,12 @@ See also `exwm-layout-enlarge-window'." exwm--mode-line-format nil) (if (not exwm--floating-frame) (exwm-layout--show exwm--id) - (exwm-floating--fit-frame-to-window) - (exwm-input-grab-keyboard) - (xcb:flush exwm--connection) - (setq window-size-fixed exwm--fixed-size)))) + (set-frame-height exwm--floating-frame + (+ (frame-pixel-height exwm--floating-frame) + (window-mode-line-height (frame-root-window + exwm--floating-frame))) + nil t) + (exwm-input-grab-keyboard)))) ;;;###autoload (defun exwm-layout-toggle-mode-line () diff --git a/exwm-manage.el b/exwm-manage.el index 21e18ff..50784ce 100644 --- a/exwm-manage.el +++ b/exwm-manage.el @@ -103,7 +103,7 @@ corresponding buffer.") exwm-window-type)))) (exwm--log "No need to manage #x%x" id) ;; Remove all events - (xcb:+request-checked+request-check exwm--connection + (xcb:+request exwm--connection (make-instance 'xcb:ChangeWindowAttributes :window id :value-mask xcb:CW:EventMask :event-mask xcb:EventMask:NoEvent)) @@ -173,7 +173,7 @@ corresponding buffer.") :border-width 0)) (dolist (button ;grab buttons to set focus / move / resize (list xcb:ButtonIndex:1 xcb:ButtonIndex:2 xcb:ButtonIndex:3)) - (xcb:+request-checked+request-check exwm--connection + (xcb:+request exwm--connection (make-instance 'xcb:GrabButton :owner-events 0 :grab-window id :event-mask xcb:EventMask:ButtonPress @@ -206,13 +206,10 @@ corresponding buffer.") (let ((buffer (exwm--id->buffer id))) (exwm--log "Unmanage #x%x (buffer: %s)" id buffer) (setq exwm--id-buffer-alist (assq-delete-all id exwm--id-buffer-alist)) - (xcb:+request exwm--connection ;update _NET_CLIENT_LIST - (make-instance 'xcb:ewmh:set-_NET_CLIENT_LIST - :window exwm--root - :data (vconcat (mapcar #'car exwm--id-buffer-alist)))) - (xcb:flush exwm--connection) (when (buffer-live-p buffer) (with-current-buffer buffer + ;; Hide the floating frame as early as possible. + (when exwm--floating-frame (make-frame-invisible exwm--floating-frame)) (xcb:+request exwm--connection (make-instance 'xcb:UnmapWindow :window exwm--container)) (setq exwm-workspace--switch-history-outdated t) @@ -245,21 +242,29 @@ corresponding buffer.") ;; Delete WM_STATE property (xcb:+request exwm--connection (make-instance 'xcb:DeleteProperty - :window id :property xcb:Atom:WM_STATE)) - (xcb:flush exwm--connection)) + :window id :property xcb:Atom:WM_STATE))) ;; Destroy the container (it seems it has to be delayed). - (run-with-idle-timer 0 nil - `(lambda () - (xcb:+request exwm--connection - ,(make-instance 'xcb:DestroyWindow - :window exwm--container)) - (xcb:flush exwm--connection))) + (when exwm--floating-frame + (xcb:+request exwm--connection + (make-instance 'xcb:ReparentWindow + :window (frame-parameter exwm--floating-frame + 'exwm-outer-id) + :parent exwm--root + :x 0 :y 0))) + (xcb:+request exwm--connection + (make-instance 'xcb:DestroyWindow :window exwm--container)) + (xcb:flush exwm--connection) (let ((kill-buffer-query-functions nil) (floating exwm--floating-frame)) (kill-buffer) (when floating (select-window - (frame-selected-window exwm-workspace--current)))))))) + (frame-selected-window exwm-workspace--current)))))) + (xcb:+request exwm--connection ;update _NET_CLIENT_LIST + (make-instance 'xcb:ewmh:set-_NET_CLIENT_LIST + :window exwm--root + :data (vconcat (mapcar #'car exwm--id-buffer-alist)))) + (xcb:flush exwm--connection))) (defun exwm-manage--scan () "Search for existing windows and try to manage them." @@ -280,64 +285,77 @@ corresponding buffer.") (defvar exwm-manage-ping-timeout 3 "Seconds to wait before killing a client.") ;;;###autoload -(defun exwm-manage--close-window (id &optional buffer) - "Close window ID in a proper way." - (let (container) - (catch 'return - (unless (exwm--id->buffer id) - (throw 'return t)) - (unless buffer (setq buffer (exwm--id->buffer id))) - (when (buffer-live-p buffer) - (setq container exwm--container)) - ;; Destroy the client window if it does not support WM_DELETE_WINDOW - (unless (and (buffer-live-p buffer) - (with-current-buffer buffer - (memq xcb:Atom:WM_DELETE_WINDOW exwm--protocols))) +(defun exwm-manage--kill-buffer-query-function () + "Run in `kill-buffer-query-functions'." + (catch 'return + (when (xcb:+request-checked+request-check exwm--connection + (make-instance 'xcb:MapWindow :window exwm--id)) + ;; The X window is no longer alive so just close the buffer. + ;; Destroy the container. + (when exwm--floating-frame (xcb:+request exwm--connection - (make-instance 'xcb:DestroyWindow :window id)) - (xcb:flush exwm--connection) - (throw 'return nil)) - ;; Try to close the window with WM_DELETE_WINDOW client message + (make-instance 'xcb:ReparentWindow + :window (frame-parameter exwm--floating-frame + 'exwm-outer-id) + :parent exwm--root + :x 0 :y 0))) (xcb:+request exwm--connection - (make-instance 'xcb:icccm:SendEvent - :destination id - :event (xcb:marshal - (make-instance 'xcb:icccm:WM_DELETE_WINDOW - :window id) - exwm--connection))) + (make-instance 'xcb:DestroyWindow :window exwm--container)) (xcb:flush exwm--connection) - ;; Try to determine if the client stop responding - (with-current-buffer buffer - (unless (memq xcb:Atom:_NET_WM_PING exwm--protocols) - ;; Ensure it's dead - (run-with-timer exwm-manage-ping-timeout nil - `(lambda () (exwm-manage--kill-client ,id))) - (throw 'return nil)) - (setq exwm-manage--ping-lock t) - (xcb:+request exwm--connection - (make-instance 'xcb:SendEvent - :propagate 0 :destination id - :event-mask xcb:EventMask:NoEvent - :event (xcb:marshal - (make-instance 'xcb:ewmh:_NET_WM_PING - :window id :timestamp 0 - :client-window id) - exwm--connection))) - (xcb:flush exwm--connection) - (with-timeout (exwm-manage-ping-timeout - (if (yes-or-no-p (format "`%s' is not responding. \ -Would you like to kill it? " - (buffer-name buffer))) - (progn (exwm-manage--kill-client id) - (throw 'return nil)) - (throw 'return nil))) - (while (and exwm-manage--ping-lock - (exwm--id->buffer id)) ;may have been destroyed - (accept-process-output nil 0.1))))) - ;; Finally destroy the container + (throw 'return t)) + (unless (memq xcb:Atom:WM_DELETE_WINDOW exwm--protocols) + ;; The X window does not support WM_DELETE_WINDOW; destroy it. + (xcb:+request exwm--connection + (make-instance 'xcb:DestroyWindow :window exwm--id)) + (xcb:flush exwm--connection) + ;; Wait for DestroyNotify event. + (throw 'return nil)) + ;; Try to close the X window with WM_DELETE_WINDOW client message. (xcb:+request exwm--connection - (make-instance 'xcb:DestroyWindow :window container)) - (xcb:flush exwm--connection))) + (make-instance 'xcb:icccm:SendEvent + :destination exwm--id + :event (xcb:marshal + (make-instance 'xcb:icccm:WM_DELETE_WINDOW + :window exwm--id) + exwm--connection))) + (xcb:flush exwm--connection) + ;; + (unless (memq xcb:Atom:_NET_WM_PING exwm--protocols) + ;; The window does not support _NET_WM_PING. To make sure it'll die, + ;; kill it after the time runs out. + (run-with-timer exwm-manage-ping-timeout nil + `(lambda () (exwm-manage--kill-client ,exwm--id))) + ;; Wait for DestroyNotify event. + (throw 'return nil)) + ;; Try to determine if the X window is dead with _NET_WM_PING. + (setq exwm-manage--ping-lock t) + (xcb:+request exwm--connection + (make-instance 'xcb:SendEvent + :propagate 0 + :destination exwm--id + :event-mask xcb:EventMask:NoEvent + :event (xcb:marshal + (make-instance 'xcb:ewmh:_NET_WM_PING + :window exwm--id + :timestamp 0 + :client-window exwm--id) + exwm--connection))) + (xcb:flush exwm--connection) + (with-timeout (exwm-manage-ping-timeout + (if (yes-or-no-p (format "'%s' is not responding. \ +Would you like to kill it? " + (buffer-name))) + (progn (exwm-manage--kill-client exwm--id) + ;; Kill the unresponsive X window and + ;; wait for DestroyNotify event. + (throw 'return nil)) + ;; Give up. + (throw 'return nil))) + (while (and exwm-manage--ping-lock + (exwm--id->buffer exwm--id)) ;may have been destroyed. + (accept-process-output nil 0.1)) + ;; Give up. + (throw 'return nil)))) (defun exwm-manage--kill-client (&optional id) "Kill an X client."