diff --git a/exwm-floating.el b/exwm-floating.el index 56d2932..7c5d811 100644 --- a/exwm-floating.el +++ b/exwm-floating.el @@ -99,7 +99,9 @@ context of the corresponding buffer.") (height . ,window-min-height) (unsplittable . t))))) ;and fix the size later (outer-id (string-to-number (frame-parameter frame 'outer-window-id))) - (container (buffer-local-value 'exwm--container (exwm--id->buffer id))) + (window-id (string-to-number (frame-parameter frame 'window-id))) + (container (buffer-local-value 'exwm--container + (exwm--id->buffer id))) (frame-container (xcb:generate-id exwm--connection)) (window (frame-first-window frame)) ;and it's the only window (x (slot-value exwm--geometry 'x)) @@ -118,6 +120,7 @@ context of the corresponding buffer.") width height x y) ;; Save frame parameters. (set-frame-parameter frame 'exwm-outer-id outer-id) + (set-frame-parameter frame 'exwm-id window-id) (set-frame-parameter frame 'exwm-container frame-container) ;; Fix illegal parameters ;; FIXME: check normal hints restrictions diff --git a/exwm-input.el b/exwm-input.el index 7506267..668e495 100644 --- a/exwm-input.el +++ b/exwm-input.el @@ -48,10 +48,9 @@ (defvar exwm-input--resize-keysym nil) (defvar exwm-input--resize-mask nil) -(defvar exwm-input--timestamp xcb:Time:CurrentTime - "A recent timestamp received from X server. - -It's updated in several occasions, and only used by `exwm-input--set-focus'.") +(defvar exwm-input--timestamp-window nil) +(defvar exwm-input--timestamp-atom nil) +(defvar exwm-input--timestamp-callback nil) (defvar exwm-workspace--current) (defvar exwm-workspace--switch-history-outdated) @@ -62,37 +61,81 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.") (defun exwm-input--set-focus (id) "Set input focus to window ID in a proper way." (when (exwm--id->buffer id) - (with-current-buffer (exwm--id->buffer id) - (if (and (not exwm--hints-input) - (memq xcb:Atom:WM_TAKE_FOCUS exwm--protocols)) - (progn - (exwm--log "Focus on #x%x with WM_TAKE_FOCUS" id) + (let ((focus (slot-value (xcb:+request-unchecked+reply exwm--connection + (make-instance 'xcb:GetInputFocus)) + 'focus))) + (unless (= focus id) + (with-current-buffer (exwm--id->buffer id) + (cond + ((and (not exwm--hints-input) + (memq xcb:Atom:WM_TAKE_FOCUS exwm--protocols)) + (when (= focus (frame-parameter nil 'exwm-id)) + (exwm--log "Focus on #x%x with WM_TAKE_FOCUS" id) + (exwm-input--update-timestamp + (lambda (timestamp id) + (let ((event (make-instance 'xcb:icccm:WM_TAKE_FOCUS + :window id + :time timestamp))) + (setq event (xcb:marshal event exwm--connection)) + (xcb:+request exwm--connection + (make-instance 'xcb:icccm:SendEvent + :destination id + :event event)) + (exwm-input--set-active-window id) + (xcb:flush exwm--connection))) + id))) + (t + (exwm--log "Focus on #x%x with SetInputFocus" id) (xcb:+request exwm--connection - (make-instance 'xcb:icccm:SendEvent - :destination id - :event (xcb:marshal - (make-instance 'xcb:icccm:WM_TAKE_FOCUS - :window id - :time - exwm-input--timestamp) - exwm--connection)))) - (exwm--log "Focus on #x%x with SetInputFocus" id) - (xcb:+request exwm--connection - (make-instance 'xcb:SetInputFocus - :revert-to xcb:InputFocus:Parent - :focus id - :time xcb:Time:CurrentTime))) - (exwm-input--set-active-window id) - (xcb:flush exwm--connection)))) + (make-instance 'xcb:SetInputFocus + :revert-to xcb:InputFocus:Parent + :focus id + :time xcb:Time:CurrentTime)) + (exwm-input--set-active-window id) + (xcb:flush exwm--connection)))))))) + +(defun exwm-input--update-timestamp (callback &rest args) + "Fetch the latest timestamp from the server and feed it to CALLBACK. + +ARGS are additional arguments to CALLBACK." + (setq exwm-input--timestamp-callback (cons callback args)) + (xcb:+request exwm--connection + (make-instance 'xcb:ChangeProperty + :mode xcb:PropMode:Replace + :window exwm-input--timestamp-window + :property exwm-input--timestamp-atom + :type xcb:Atom:CARDINAL + :format 32 + :data-len 0 + :data nil)) + (xcb:flush exwm--connection)) + +(defun exwm-input--on-PropertyNotify (data _synthetic) + "Handle PropertyNotify events." + (when exwm-input--timestamp-callback + (let ((obj (make-instance 'xcb:PropertyNotify))) + (xcb:unmarshal obj data) + (when (= exwm-input--timestamp-window + (slot-value obj 'window)) + (apply (car exwm-input--timestamp-callback) + (slot-value obj 'time) + (cdr exwm-input--timestamp-callback)) + (setq exwm-input--timestamp-callback nil))))) (defun exwm-input--on-FocusIn (data _synthetic) "Handle FocusIn events." (let ((obj (make-instance 'xcb:FocusIn))) (xcb:unmarshal obj data) - (when (= (slot-value obj 'detail) xcb:NotifyDetail:Inferior) - ;; Transfer input focus back to the workspace when the workspace - ;; container unexpectedly receives it. - (x-focus-frame exwm-workspace--current)))) + ;; Not sure if this is the right thing to do but the point is the + ;; input focus should not stay at the root window or any container, + ;; or the result would be unpredictable. `x-focus-frame' would + ;; first set the input focus to the (previously) selected frame, and + ;; then `select-window' would further update the input focus if the + ;; selected window is displaying an `exwm-mode' buffer. Perhaps we + ;; should carefully filter out FocusIn events with certain 'detail' + ;; and 'mode' combinations, but this just works. + (x-focus-frame (selected-frame)) + (select-window (selected-window)))) (defun exwm-input--on-workspace-list-change () "Run in `exwm-input--update-global-prefix-keys'." @@ -247,7 +290,6 @@ This value should always be overwritten.") window buffer frame) (xcb:unmarshal obj data) (with-slots (detail time event state) obj - (setq exwm-input--timestamp time) (setq window (get-buffer-window (exwm--id->buffer event) t) buffer (window-buffer window)) (cond ((and (= state exwm-input--move-mask) @@ -296,7 +338,6 @@ This value should always be overwritten.") "Handle KeyPress event." (let ((obj (make-instance 'xcb:KeyPress))) (xcb:unmarshal obj data) - (setq exwm-input--timestamp (slot-value obj 'time)) (if (eq major-mode 'exwm-mode) (funcall exwm--on-KeyPress obj data) (exwm-input--on-KeyPress-char-mode obj)))) @@ -657,7 +698,33 @@ Its usage is the same with `exwm-input-set-simulation-keys'." exwm-input--move-mask (cdr move-key) exwm-input--resize-keysym (car resize-key) exwm-input--resize-mask (cdr resize-key))) + ;; Create the X window and intern the atom used to fetch timestamp. + (setq exwm-input--timestamp-window (xcb:generate-id exwm--connection)) + (xcb:+request exwm--connection + (make-instance 'xcb:CreateWindow + :depth 0 + :wid exwm-input--timestamp-window + :parent exwm--root + :x -1 + :y -1 + :width 1 + :height 1 + :border-width 0 + :class xcb:WindowClass:CopyFromParent + :visual 0 + :value-mask xcb:CW:EventMask + :event-mask xcb:EventMask:PropertyChange)) + (let ((atom "_TIME")) + (setq exwm-input--timestamp-atom + (slot-value (xcb:+request-unchecked+reply exwm--connection + (make-instance 'xcb:InternAtom + :only-if-exists 0 + :name-len (length atom) + :name atom)) + 'atom))) ;; Attach event listeners + (xcb:+event exwm--connection 'xcb:PropertyNotify + #'exwm-input--on-PropertyNotify) (xcb:+event exwm--connection 'xcb:KeyPress #'exwm-input--on-KeyPress) (xcb:+event exwm--connection 'xcb:ButtonPress #'exwm-input--on-ButtonPress) (xcb:+event exwm--connection 'xcb:ButtonRelease diff --git a/exwm-workspace.el b/exwm-workspace.el index 4789aeb..977cfe6 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -691,10 +691,13 @@ INDEX must not exceed the current number of workspaces." (outer-id (string-to-number (frame-parameter new-frame 'outer-window-id))) + (window-id (string-to-number + (frame-parameter new-frame 'window-id))) (frame-container (frame-parameter old-frame 'exwm-container)) (window (frame-root-window new-frame))) (set-frame-parameter new-frame 'exwm-outer-id outer-id) + (set-frame-parameter new-frame 'exwm-id window-id) (set-frame-parameter new-frame 'exwm-container frame-container) (make-frame-invisible new-frame) @@ -1149,10 +1152,12 @@ Please check `exwm-workspace--minibuffer-own-frame-p' first." (setq exwm-workspace--list (nconc exwm-workspace--list (list frame))) (let ((outer-id (string-to-number (frame-parameter frame 'outer-window-id))) + (window-id (string-to-number (frame-parameter frame 'window-id))) (container (xcb:generate-id exwm--connection)) (workspace (xcb:generate-id exwm--connection))) ;; Save window IDs (set-frame-parameter frame 'exwm-outer-id outer-id) + (set-frame-parameter frame 'exwm-id window-id) (set-frame-parameter frame 'exwm-container container) (set-frame-parameter frame 'exwm-workspace workspace) ;; In case it's created by emacsclient. @@ -1356,9 +1361,13 @@ applied to all subsequently created X frames." (let ((outer-id (string-to-number (frame-parameter exwm-workspace--minibuffer 'outer-window-id))) + (window-id (string-to-number + (frame-parameter exwm-workspace--minibuffer + '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-id window-id) (set-frame-parameter exwm-workspace--minibuffer 'exwm-container container) (xcb:+request exwm--connection diff --git a/exwm.el b/exwm.el index dd279bb..afec152 100644 --- a/exwm.el +++ b/exwm.el @@ -315,8 +315,7 @@ atom id buffer) (xcb:unmarshal obj data) (setq id (slot-value obj 'window) - atom (slot-value obj 'atom) - exwm-input--timestamp (slot-value obj 'time)) + atom (slot-value obj 'atom)) (setq buffer (exwm--id->buffer id)) (if (not (buffer-live-p buffer)) ;; Properties of unmanaged X windows.