From 810b4716a10169e1de29c52cf2e3aeb2e79f2018 Mon Sep 17 00:00:00 2001 From: Chris Feng Date: Tue, 9 Aug 2016 13:34:29 +0800 Subject: Update timestamp for WM_TAKE_FOCUS ClientMessage * exwm-input.el (exwm-input--timestamp-window) (exwm-input--timestamp-atom, exwm-input--timestamp-callback): New variables for updating timestamp. (exwm-input--set-focus): Send WM_TAKE_FOCUS ClientMessage with updated timestamp. (exwm-input--update-timestamp): New utility function for fetching timestamp. (exwm-input--on-PropertyNotify): New function for handling PropertyNotify event to extract the timestamp. (exwm-input--init): Create resources for updating timestamp; attach the event listener. (exwm-input--on-ButtonPress, exwm-input--on-KeyPress): * exwm.el (exwm--on-PropertyNotify): No longer update timestamp. * exwm-input.el (exwm-input--set-focus): Avoid setting input focus on already focused X windows, or when the input focus in not on a Emacs frame if globally active model is in use. * exwm-floating.el (exwm-floating--set-floating): * exwm-workspace.el (exwm-workspace-move-window) (exwm-workspace--add-frame-as-workspace, exwm-workspace--init): Set 'exwm-id' frame parameter as the numerical (inner) frame X ID. --- exwm-input.el | 129 ++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 98 insertions(+), 31 deletions(-) (limited to 'exwm-input.el') diff --git a/exwm-input.el b/exwm-input.el index 7506267ee7..668e49585e 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 -- cgit 1.4.1