From 6c8255bf3978a4df3d76ffd6f7d6bbdbba8bba19 Mon Sep 17 00:00:00 2001 From: Chris Feng Date: Wed, 13 Jul 2016 18:51:32 +0800 Subject: [PATCH] Add/improve some ICCCM/EWMH features * exwm-floating.el (exwm-floating--set-allowed-actions) (exwm-floating--set-floating, exwm-floating--unset-floating): Add _NET_WM_ALLOWED_ACTIONS support. * exwm-floating.el (exwm-floating--set-floating) (exwm-floating--unset-floating): Support initial state hint. * exwm.el (exwm--update-hints): Fetch initial state. (exwm--update-state, exwm--on-PropertyNotify): WM_STATE is not intended to be read. * exwm-core.el (exwm-state): * exwm-floating.el (exwm-floating-hide): * exwm-input.el (exwm-input--update-focus): * exwm-layout.el (exwm-layout--set-state) (exwm-layout--iconic-state-p, exwm-layout--show, exwm-layout--hide): * exwm-manage.el (exwm-manage--on-MapRequest): Improve WM_STATE support. * exwm-input.el (exwm-input--set-focus): * exwm-input.el (exwm-input--update-focus) (exwm-input--set-active-window): * exwm.el (exwm--on-ClientMessage): Add _NET_ACTIVE_WINDOW support. * exwm-layout.el (exwm-layout--set-client-list-stacking): Improve _NET_CLIENT_LIST_STACKING support. * exwm-manage.el (exwm-manage--set-client-list) (exwm-manage--manage-window, exwm-manage--unmanage-window): Improve _NET_CLIENT_LIST support. * exwm-manage.el (exwm-manage--manage-window): * exwm-workspace.el (exwm-workspace--set-desktop) (exwm-workspace-move-window): * exwm.el (exwm--on-ClientMessage): Add _NET_WM_DESKTOP support. * exwm-randr.el (exwm-randr--refresh): * exwm-workspace.el (exwm-workspace--set-desktop-geometry) (exwm-workspace--init): Add _NET_DESKTOP_GEOMETRY support. * exwm-workspace.el (exwm-workspace--set-desktop-geometry): Renamed from `exwm-workspace--update-desktop-geometry'. * exwm-randr.el (exwm-randr--refresh): Improve _NET_WORKAREA support. * exwm-workspace.el (exwm-workspace--set-fullscreen): Correct variables names. * exwm-workspace.el (exwm-workspace--init): * exwm.el (exwm--init-icccm-ewmh): Set _NET_NUMBER_OF_DESKTOPS in workspace module. * exwm-workspace.el (exwm-workspace--init): * exwm.el (exwm--init-icccm-ewmh): Set _NET_DESKTOP_VIEWPORT in workspace module. * exwm.el (exwm--on-ClientMessage): Improve _NET_CURRENT_DESKTOP support. * exwm.el (exwm--on-ClientMessage): Add _NET_CLOSE_WINDOW support. * exwm.el (exwm--on-ClientMessage): Add WM_CHANGE_STATE support. * exwm.el (exwm--init-icccm-ewmh): Update supported atoms. --- exwm-core.el | 2 +- exwm-floating.el | 46 ++++++++--- exwm-input.el | 19 ++++- exwm-layout.el | 68 ++++++++++------ exwm-manage.el | 46 ++++++++--- exwm-randr.el | 21 ++--- exwm-workspace.el | 45 +++++++++-- exwm.el | 194 +++++++++++++++++++++++++++++++--------------- 8 files changed, 310 insertions(+), 131 deletions(-) diff --git a/exwm-core.el b/exwm-core.el index 9eb6b62..c07c069 100644 --- a/exwm-core.el +++ b/exwm-core.el @@ -101,7 +101,7 @@ (defvar-local exwm--title-is-utf8 nil) (defvar-local exwm-transient-for nil "WM_TRANSIENT_FOR.") (defvar-local exwm--protocols nil) -(defvar-local exwm-state nil "WM_STATE.") +(defvar-local exwm-state xcb:icccm:WM_STATE:NormalState "WM_STATE.") ;; _NET_WM_NORMAL_HINTS (defvar-local exwm--normal-hints-x nil) (defvar-local exwm--normal-hints-y nil) diff --git a/exwm-floating.el b/exwm-floating.el index d4f57b7..9d4d948 100644 --- a/exwm-floating.el +++ b/exwm-floating.el @@ -49,13 +49,31 @@ (defvar exwm-floating--cursor-bottom-left nil) (defvar exwm-floating--cursor-left nil) +(defun exwm-floating--set-allowed-actions (id tilling) + "Set _NET_WM_ALLOWED_ACTIONS." + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_WM_ALLOWED_ACTIONS + :window id + :data (if tilling + (vector xcb:Atom:_NET_WM_ACTION_MINIMIZE + xcb:Atom:_NET_WM_ACTION_FULLSCREEN + xcb:Atom:_NET_WM_ACTION_CHANGE_DESKTOP + xcb:Atom:_NET_WM_ACTION_CLOSE) + (vector xcb:Atom:_NET_WM_ACTION_MOVE + xcb:Atom:_NET_WM_ACTION_RESIZE + xcb:Atom:_NET_WM_ACTION_MINIMIZE + xcb:Atom:_NET_WM_ACTION_FULLSCREEN + xcb:Atom:_NET_WM_ACTION_CHANGE_DESKTOP + xcb:Atom:_NET_WM_ACTION_CLOSE))))) + (defvar exwm-workspace--current) (defvar exwm-workspace--list) (defvar exwm-workspace-current-index) (defvar exwm-workspace--switch-history-outdated) -(declare-function exwm-layout--refresh "exwm-layout.el") -(declare-function exwm-layout--show "exwm-layout.el") +(declare-function exwm-layout--refresh "exwm-layout.el" ()) +(declare-function exwm-layout--show "exwm-layout.el" (id &optional window)) +(declare-function exwm-layout--iconic-state-p "exwm-layout.el" (&optional id)) (defun exwm-floating--set-floating (id) "Make window ID floating." @@ -205,6 +223,7 @@ xcb:ConfigWindow:Y)) :x (- x exwm-floating-border-width) :y (- y exwm-floating-border-width))) + (exwm-floating--set-allowed-actions id nil) (xcb:flush exwm--connection) ;; Set window/buffer (with-current-buffer (exwm--id->buffer id) @@ -217,9 +236,13 @@ (add-hook 'window-configuration-change-hook #'exwm-layout--refresh) (set-window-dedicated-p window t) (exwm-layout--show id window)) - (with-selected-frame exwm-workspace--current - (exwm-layout--refresh)) - (select-frame-set-input-focus frame)) + (if (exwm-layout--iconic-state-p id) + ;; Hide iconic floating X windows. + (with-current-buffer (exwm--id->buffer id) + (exwm-floating-hide)) + (with-selected-frame exwm-workspace--current + (exwm-layout--refresh)) + (select-frame-set-input-focus frame))) (run-hooks 'exwm-floating-setup-hook) ;; Redraw the frame. (redisplay)) @@ -269,6 +292,7 @@ :sibling (frame-parameter exwm-workspace--current 'exwm-container) :stack-mode xcb:StackMode:Above))) + (exwm-floating--set-allowed-actions id t) (xcb:flush exwm--connection) (with-current-buffer buffer (when exwm--floating-frame ;from floating to non-floating @@ -278,9 +302,11 @@ (setq window-size-fixed nil exwm--floating-frame nil exwm--frame exwm-workspace--current)) - (let ((window (frame-selected-window exwm-workspace--current))) - (set-window-buffer window buffer) - (select-window window))) + (unless (exwm-layout--iconic-state-p) + ;; Only show X windows in normal state. + (let ((window (frame-selected-window exwm-workspace--current))) + (set-window-buffer window buffer) + (select-window window)))) (run-hooks 'exwm-floating-exit-hook)) ;;;###autoload @@ -292,6 +318,8 @@ (exwm-floating--unset-floating exwm--id) (exwm-floating--set-floating exwm--id)))) +(declare-function exwm-layout--set-state "exwm-layout.el" (id state)) + ;;;###autoload (defun exwm-floating-hide () "Hide the current floating X window (which would show again when selected)." @@ -304,7 +332,7 @@ :window exwm--container :value-mask xcb:ConfigWindow:StackMode :stack-mode xcb:StackMode:Below)) - ;; FIXME: Should it be put into iconic state? + (exwm-layout--set-state exwm--id xcb:icccm:WM_STATE:IconicState) (xcb:flush exwm--connection) (select-frame-set-input-focus exwm-workspace--current))) diff --git a/exwm-input.el b/exwm-input.el index 1c711b3..3cb189b 100644 --- a/exwm-input.el +++ b/exwm-input.el @@ -76,6 +76,7 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.") :revert-to xcb:InputFocus:PointerRoot :focus id :time xcb:Time:CurrentTime))) + (exwm-input--set-active-window id) (xcb:flush exwm--connection)))) (defvar exwm-input--focus-window nil "The (Emacs) window to be focused.") @@ -99,6 +100,9 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.") (defvar exwm-workspace-current-index) (defvar exwm-workspace--minibuffer) +(declare-function exwm-layout--iconic-state-p "exwm-layout.el" (&optional id)) +(declare-function exwm-layout--set-state "exwm-layout.el" (id state)) + (defun exwm-input--update-focus () "Update input focus." (when (and (window-live-p exwm-input--focus-window) @@ -136,13 +140,26 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.") :window exwm--container :value-mask xcb:ConfigWindow:StackMode :stack-mode xcb:StackMode:Above))) + ;; This floating X window might be hide by `exwm-floating-hide'. + (when (exwm-layout--iconic-state-p) + (exwm-layout--set-state exwm--id + xcb:icccm:WM_STATE:NormalState)) (xcb:flush exwm--connection))) (when (eq (selected-window) exwm-input--focus-window) (exwm--log "Focus on %s" exwm-input--focus-window) (select-frame-set-input-focus (window-frame exwm-input--focus-window) - t))) + t) + (exwm-input--set-active-window) + (xcb:flush exwm--connection))) (setq exwm-input--focus-window nil)))) +(defun exwm-input--set-active-window (&optional id) + "Set _NET_ACTIVE_WINDOW." + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_ACTIVE_WINDOW + :window exwm--root + :data (or id xcb:Window:None)))) + (defvar exwm-input--during-key-sequence nil "Non-nil indicates Emacs is waiting for more keys to form a key sequence.") (defvar exwm-input--temp-line-mode nil diff --git a/exwm-layout.el b/exwm-layout.el index 2d85580..259788f 100644 --- a/exwm-layout.el +++ b/exwm-layout.el @@ -50,6 +50,20 @@ xcb:ConfigWindow:Height)) :width width :height height)))) +(defun exwm-layout--set-state (id state) + "Set WM_STATE." + (xcb:+request exwm--connection + (make-instance 'xcb:icccm:set-WM_STATE + :window id :state state :icon xcb:Window:None)) + (with-current-buffer (exwm--id->buffer id) + (setq exwm-state state))) + +(defun exwm-layout--iconic-state-p (&optional id) + (= xcb:icccm:WM_STATE:IconicState + (if id + (buffer-local-value 'exwm-state (exwm--id->buffer id)) + exwm-state))) + (defun exwm-layout--show (id &optional window) "Show window ID exactly fit in the Emacs window WINDOW." (exwm--log "Show #x%x in %s" id window) @@ -101,11 +115,7 @@ (xcb:+request exwm--connection (make-instance 'xcb:MapWindow :window id)) (xcb:+request exwm--connection (make-instance 'xcb:MapWindow :window exwm--container)) - (xcb:+request exwm--connection - (make-instance 'xcb:icccm:set-WM_STATE - :window id :state xcb:icccm:WM_STATE:NormalState - :icon xcb:Window:None)) - (setq exwm-state xcb:icccm:WM_STATE:NormalState)) + (exwm-layout--set-state id xcb:icccm:WM_STATE:NormalState)) (xcb:+request exwm--connection (make-instance 'xcb:SendEvent :propagate 0 :destination id @@ -125,25 +135,21 @@ (defun exwm-layout--hide (id) "Hide window ID." (with-current-buffer (exwm--id->buffer id) - (unless (eq xcb:icccm:WM_STATE:IconicState exwm-state) ;already hidden + (unless (exwm-layout--iconic-state-p) ;already hidden (exwm--log "Hide #x%x" id) (xcb:+request exwm--connection (make-instance 'xcb:ChangeWindowAttributes :window id :value-mask xcb:CW:EventMask :event-mask xcb:EventMask:NoEvent)) - (xcb:+request exwm--connection (make-instance 'xcb:UnmapWindow :window id)) + (xcb:+request exwm--connection + (make-instance 'xcb:UnmapWindow :window id)) (xcb:+request exwm--connection (make-instance 'xcb:ChangeWindowAttributes :window id :value-mask xcb:CW:EventMask :event-mask exwm--client-event-mask)) (xcb:+request exwm--connection (make-instance 'xcb:UnmapWindow :window exwm--container)) - (xcb:+request exwm--connection - (make-instance 'xcb:icccm:set-WM_STATE - :window id - :state xcb:icccm:WM_STATE:IconicState - :icon xcb:Window:None)) - (setq exwm-state xcb:icccm:WM_STATE:IconicState) + (exwm-layout--set-state id xcb:icccm:WM_STATE:IconicState) (xcb:flush exwm--connection)))) (defvar exwm-workspace--current) @@ -243,6 +249,29 @@ selected by `other-buffer'." (defvar exwm-layout-show-all-buffers nil "Non-nil to allow switching to buffers on other workspaces.") +(defun exwm-layout--set-client-list-stacking () + "Set _NET_CLIENT_LIST_STACKING." + (let (id clients-floating clients clients-iconic clients-other) + (dolist (pair exwm--id-buffer-alist) + (setq id (car pair)) + (with-current-buffer (cdr pair) + (if (eq exwm--frame exwm-workspace--current) + (if exwm--floating-frame + ;; A floating X window on the current workspace. + (setq clients-floating (cons id clients-floating)) + (if (get-buffer-window (cdr pair) exwm-workspace--current) + ;; A normal tilling X window on the current workspace. + (setq clients (cons id clients)) + ;; An iconic tilling X window on the current workspace. + (setq clients-iconic (cons id clients-iconic)))) + ;; X window on other workspaces. + (setq clients-other (cons id clients-other))))) + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_CLIENT_LIST_STACKING + :window exwm--root + :data (vconcat (append clients-other clients-iconic + clients clients-floating)))))) + (defun exwm-layout--refresh () "Refresh layout." (let ((frame (selected-frame)) @@ -310,18 +339,7 @@ selected by `other-buffer'." (when (and (eq major-mode 'exwm-mode) (or exwm--floating-frame (not (eq frame exwm--frame)))) (switch-to-prev-buffer window))))) - ;; Update _NET_CLIENT_LIST_STACKING - (xcb:+request exwm--connection - (make-instance 'xcb:ewmh:set-_NET_CLIENT_LIST_STACKING - :window exwm--root - :data (vconcat - (delq nil - (mapcar - (lambda (buffer) - (with-current-buffer buffer - (when (eq major-mode 'exwm-mode) - exwm--id))) - (buffer-list)))))) + (exwm-layout--set-client-list-stacking) (xcb:flush exwm--connection)))) (defun exwm-layout--on-minibuffer-setup () diff --git a/exwm-manage.el b/exwm-manage.el index 924103e..d4b3de4 100644 --- a/exwm-manage.el +++ b/exwm-manage.el @@ -68,8 +68,16 @@ corresponding buffer.") (elt value 2))) ;MotifWmHints.decorations (setq exwm--mwm-hints-decorations nil)))))))) +(defun exwm-manage--set-client-list () + "Set _NET_CLIENT_LIST." + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_CLIENT_LIST + :window exwm--root + :data (vconcat (mapcar #'car exwm--id-buffer-alist))))) + (defvar exwm-workspace--current) (defvar exwm-workspace--switch-history-outdated) +(defvar exwm-workspace-current-index) (declare-function exwm--update-window-type "exwm.el" (id &optional force)) (declare-function exwm--update-class "exwm.el" (id &optional force)) @@ -78,10 +86,10 @@ corresponding buffer.") (declare-function exwm--update-title "exwm.el" (id)) (declare-function exwm--update-hints "exwm.el" (id &optional force)) (declare-function exwm--update-protocols "exwm.el" (id &optional force)) -(declare-function exwm--update-state "exwm.el" (id &optional force)) (declare-function exwm--update-strut "exwm.el" (id)) (declare-function exwm-floating--set-floating "exwm-floating.el" (id)) (declare-function exwm-floating--unset-floating "exwm-floating.el" (id)) +(declare-function exwm-workspace--set-desktop "exwm-workspace.el" (id)) (defun exwm-manage--manage-window (id) "Manage window ID." @@ -94,7 +102,9 @@ corresponding buffer.") :event-mask exwm--client-event-mask)) (throw 'return 'dead)) (with-current-buffer (generate-new-buffer "*EXWM*") - (push `(,id . ,(current-buffer)) exwm--id-buffer-alist) + ;; Keep the oldest X window first. + (setq exwm--id-buffer-alist + (nconc exwm--id-buffer-alist `((,id . ,(current-buffer))))) (exwm-mode) (setq exwm--id id) (exwm--update-window-type id) @@ -213,14 +223,10 @@ corresponding buffer.") :keyboard-mode xcb:GrabMode:Async :confine-to xcb:Window:None :cursor xcb:Cursor:None :button button :modifiers xcb:ModMask:Any))) - (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)))) + (exwm-manage--set-client-list) (xcb:flush exwm--connection) (exwm--update-title id) (exwm--update-protocols id) - (exwm--update-state id) (if (or exwm-transient-for exwm--fixed-size (memq xcb:Atom:_NET_WM_WINDOW_TYPE_UTILITY exwm-window-type) (memq xcb:Atom:_NET_WM_WINDOW_TYPE_DIALOG exwm-window-type)) @@ -228,6 +234,16 @@ corresponding buffer.") (exwm-floating--unset-floating id)) (exwm-input-grab-keyboard id) (setq exwm-workspace--switch-history-outdated t) + ;; Set _NET_WM_DESKTOP or move window. + (let ((reply (xcb:+request-unchecked+reply exwm--connection + (make-instance 'xcb:ewmh:get-_NET_WM_DESKTOP + :window id))) + desktop) + (when reply + (setq desktop (slot-value reply 'value))) + (if (and desktop (/= desktop exwm-workspace-current-index)) + (exwm-workspace-move-window desktop id) + (exwm-workspace--set-desktop id))) (with-current-buffer (exwm--id->buffer id) (run-hooks 'exwm-manage-finish-hook))))) @@ -297,10 +313,7 @@ corresponding buffer.") (when floating (select-window (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)))) + (exwm-manage--set-client-list) (xcb:flush exwm--connection)))) (defun exwm-manage--scan () @@ -523,13 +536,22 @@ border-width: %d; sibling: #x%x; stack-mode: %d" :stack-mode stack-mode)))))) (xcb:flush exwm--connection)) +(declare-function exwm-layout--iconic-state-p "exwm-layout.el" (&optional id)) + (defun exwm-manage--on-MapRequest (data _synthetic) "Handle MapRequest event." (let ((obj (make-instance 'xcb:MapRequest))) (xcb:unmarshal obj data) (with-slots (parent window) obj (if (assoc window exwm--id-buffer-alist) - (exwm--log "#x%x is already managed" window) + (with-current-buffer (exwm--id->buffer window) + (if (exwm-layout--iconic-state-p) + ;; State change: iconic => normal. + (when (eq exwm--frame exwm-workspace--current) + (set-window-buffer (frame-selected-window exwm--frame) + (current-buffer)) + (select-window (frame-selected-window exwm--frame))) + (exwm--log "#x%x is already managed" window))) (if (/= exwm--root parent) (progn (xcb:+request exwm--connection (make-instance 'xcb:MapWindow :window window)) diff --git a/exwm-randr.el b/exwm-randr.el index d30f687..f71120a 100644 --- a/exwm-randr.el +++ b/exwm-randr.el @@ -59,13 +59,13 @@ (defvar exwm-workspace--list) (declare-function exwm-workspace--set-fullscreen "exwm-workspace.el" (frame)) -(declare-function exwm-workspace--update-workareas "exwm-workspace.el" +(declare-function exwm-workspace--set-workareas "exwm-workspace.el" (&optional workareas)) +(declare-function exwm-workspace--set-desktop-geometry "exwm-workspace.el" ()) (defun exwm-randr--refresh () "Refresh workspaces according to the updated RandR info." - (let (output-name geometry output-plist default-geometry workareas - viewports) + (let (output-name geometry output-plist default-geometry workareas) ;; Query all outputs (with-slots (config-timestamp outputs) (xcb:+request-unchecked+reply exwm--connection @@ -107,16 +107,11 @@ (set-frame-parameter frame 'exwm-geometry geometry) (exwm-workspace--set-fullscreen frame) (with-slots (x y width height) geometry - (setq workareas - (nconc workareas (list x y width height)) - viewports (nconc viewports (list x y)))))) - ;; Update _NET_WORKAREA - (exwm-workspace--update-workareas (vconcat workareas)) - ;; Update _NET_DESKTOP_VIEWPORT - (xcb:+request exwm--connection - (make-instance 'xcb:ewmh:set-_NET_DESKTOP_VIEWPORT - :window exwm--root - :data (vconcat viewports))) + (setq workareas (nconc workareas (list x y width height)))))) + ;; Set _NET_DESKTOP_GEOMETRY. + (exwm-workspace--set-desktop-geometry) + ;; Set _NET_WORKAREA. + (exwm-workspace--set-workareas (vconcat workareas)) (xcb:flush exwm--connection) (run-hooks 'exwm-randr-refresh-hook)))) diff --git a/exwm-workspace.el b/exwm-workspace.el index 64c636e..bde423d 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -166,7 +166,7 @@ Value nil means to use the default position which is fixed at bottom, while height* height)) (when (and (eq frame exwm-workspace--current) (exwm-workspace--minibuffer-own-frame-p)) - (exwm-workspace--resize-minibuffer-frame width* height*)) + (exwm-workspace--resize-minibuffer-frame width height)) (exwm-layout--resize-container id container 0 0 width* height*) (exwm-layout--resize-container nil workspace x* y* width* height* t) (xcb:flush exwm--connection))) @@ -275,7 +275,7 @@ The optional FORCE option is for internal use only." (set-frame-parameter frame 'exwm--urgency nil) ;; Update switch workspace history (setq exwm-workspace--switch-history-outdated t) - ;; Update _NET_CURRENT_DESKTOP + ;; Set _NET_CURRENT_DESKTOP. (xcb:+request exwm--connection (make-instance 'xcb:ewmh:set-_NET_CURRENT_DESKTOP :window exwm--root :data index)) @@ -292,6 +292,14 @@ The optional FORCE option is for internal use only." (exwm--log "Workspace was switched unexpectedly") (exwm-workspace-switch index))))) +(defun exwm-workspace--set-desktop (id) + "Set _NET_WM_DESKTOP for X window ID." + (with-current-buffer (exwm--id->buffer id) + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_WM_DESKTOP + :window id + :data (cl-position exwm--frame exwm-workspace--list))))) + (defvar exwm-floating-border-width) (defvar exwm-floating-border-color) @@ -433,7 +441,10 @@ The optional FORCE option is for internal use only." :stack-mode xcb:StackMode:Above))) (xcb:flush exwm--connection) (set-window-buffer (frame-selected-window frame) - (exwm--id->buffer id))))) + (exwm--id->buffer id))) + ;; Set _NET_WM_DESKTOP. + (exwm-workspace--set-desktop id) + (xcb:flush exwm--connection))) (setq exwm-workspace--switch-history-outdated t))) ;;;###autoload @@ -696,8 +707,17 @@ The optional FORCE option is for internal use only." (server-save-buffers-kill-terminal nil) nil))) -(defun exwm-workspace--update-workareas (&optional workareas) - "Update _NET_WORKAREA." +(defun exwm-workspace--set-desktop-geometry () + "Set _NET_DESKTOP_GEOMETRY." + ;; We don't support large desktop so it's the same with screen size. + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_DESKTOP_GEOMETRY + :window exwm--root + :width (x-display-pixel-width) + :height (x-display-pixel-height)))) + +(defun exwm-workspace--set-workareas (&optional workareas) + "Set _NET_WORKAREA." ;; Calculate workareas if not present. (unless workareas (if (frame-parameter (car exwm-workspace--list) 'exwm-geometry) @@ -884,9 +904,20 @@ The optional FORCE option is for internal use only." (xcb:flush exwm--connection) ;; We have to advice `x-create-frame' or every call to it would hang EXWM (advice-add 'x-create-frame :around #'exwm-workspace--x-create-frame) + ;; Set _NET_NUMBER_OF_DESKTOPS (it's currently fixed). + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_NUMBER_OF_DESKTOPS + :window exwm--root :data exwm-workspace-number)) + ;; Set _NET_DESKTOP_GEOMETRY. + (exwm-workspace--set-desktop-geometry) + ;; Set _NET_DESKTOP_VIEWPORT (we don't support large desktop). + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_DESKTOP_VIEWPORT + :window exwm--root + :data (make-vector (* 2 exwm-workspace-number) 0))) ;; Set _NET_WORKAREA. - (exwm-workspace--update-workareas) - ;; Set _NET_VIRTUAL_ROOTS + (exwm-workspace--set-workareas) + ;; Set _NET_VIRTUAL_ROOTS (it's currently fixed.) (xcb:+request exwm--connection (make-instance 'xcb:ewmh:set-_NET_VIRTUAL_ROOTS :window exwm--root diff --git a/exwm.el b/exwm.el index 3b06878..232c863 100644 --- a/exwm.el +++ b/exwm.el @@ -203,10 +203,12 @@ (let ((reply (xcb:+request-unchecked+reply exwm--connection (make-instance 'xcb:icccm:get-WM_HINTS :window id)))) (when (and reply (slot-value reply 'flags)) ;nil when destroyed - (with-slots (flags input) reply + (with-slots (flags input initial-state) reply (when flags (unless (= 0 (logand flags xcb:icccm:WM_HINTS:InputHint)) (setq exwm--hints-input (when input (= 1 input)))) + (unless (= 0 (logand flags xcb:icccm:WM_HINTS:StateHint)) + (setq exwm-state initial-state)) (unless (= 0 (logand flags xcb:icccm:WM_HINTS:UrgencyHint)) (setq exwm--hints-urgency t)))) (when (and exwm--hints-urgency @@ -225,17 +227,6 @@ (when reply ;nil when destroyed (setq exwm--protocols (append (slot-value reply 'value) nil))))))) -(defun exwm--update-state (id &optional force) - "Update WM_STATE." - (with-current-buffer (exwm--id->buffer id) - (unless (and exwm-state (not force)) - (let ((reply (xcb:+request-unchecked+reply exwm--connection - (make-instance 'xcb:icccm:get-WM_STATE :window id)))) - (when reply ;nil when destroyed - (setq exwm-state (or (slot-value reply 'state) - ;; Default to normal state - xcb:icccm:WM_STATE:NormalState))))))) - (defun exwm--update-strut-legacy (id) "Update _NET_WM_STRUT." (unless exwm-workspace--strut-is-partial @@ -250,7 +241,7 @@ (when (exwm-workspace--minibuffer-own-frame-p) (exwm-workspace--resize-minibuffer-frame)) ;; Update _NET_WORKAREA. - (exwm-workspace--update-workareas)))) + (exwm-workspace--set-workareas)))) (defun exwm--update-strut-partial (id) "Update _NET_WM_STRUT_PARTIAL." @@ -269,7 +260,7 @@ (when (exwm-workspace--minibuffer-own-frame-p) (exwm-workspace--resize-minibuffer-frame)) ;; Update _NET_WORKAREA. - (exwm-workspace--update-workareas))) + (exwm-workspace--set-workareas))) (defun exwm--update-strut (id) "Update _NET_WM_STRUT_PARTIAL or _NET_WM_STRUT." @@ -308,8 +299,6 @@ (exwm--update-hints id t)) ((= atom xcb:Atom:WM_PROTOCOLS) (exwm--update-protocols id t)) - ((= atom xcb:Atom:WM_STATE) - (exwm--update-state id t)) ((= atom xcb:Atom:_NET_WM_USER_TIME)) ;ignored (t (exwm--log "Unhandled PropertyNotify: %s(%d)" (x-get-atom-name atom exwm-workspace--current) @@ -324,6 +313,26 @@ id (slot-value obj 'window) data (slot-value (slot-value obj 'data) 'data32)) (cond + ;; _NET_CURRENT_DESKTOP. + ((= type xcb:Atom:_NET_CURRENT_DESKTOP) + (exwm-workspace-switch (elt data 0))) + ;; _NET_ACTIVE_WINDOW. + ((= type xcb:Atom:_NET_ACTIVE_WINDOW) + (let ((buffer (exwm--id->buffer id))) + (when (buffer-live-p buffer) + (with-current-buffer buffer + (when (eq exwm--frame exwm-workspace--current) + (when (exwm-layout--iconic-state-p) + ;; State change: iconic => normal. + (set-window-buffer (frame-selected-window exwm--frame) + (current-buffer))) + ;; Focus transfer. + (select-window (get-buffer-window))))))) + ;; _NET_CLOSE_WINDOW. + ((= type xcb:Atom:_NET_CLOSE_WINDOW) + (let ((buffer (exwm--id->buffer id))) + (when (buffer-live-p buffer) + (kill-buffer buffer)))) ;; _NET_WM_MOVERESIZE ((= type xcb:Atom:_NET_WM_MOVERESIZE) (let ((direction (elt data 2)) @@ -359,6 +368,11 @@ :window id :left left :right right :top top :bottom btm))) (xcb:flush exwm--connection)) + ;; _NET_WM_DESKTOP. + ((= type xcb:Atom:_NET_WM_DESKTOP) + (let ((buffer (exwm--id->buffer id))) + (when (buffer-live-p buffer) + (exwm-workspace-move-window (elt data 0) id)))) ;; _NET_WM_STATE ((= type xcb:Atom:_NET_WM_STATE) (let ((action (elt data 0)) @@ -428,6 +442,12 @@ (cond ((= type xcb:Atom:_NET_WM_PING) (setq exwm-manage--ping-lock nil)) (t (exwm--log "Unhandled WM_PROTOCOLS of type: %d" type))))) + ((= type xcb:Atom:WM_CHANGE_STATE) + (let ((buffer (exwm--id->buffer id))) + (when (and (buffer-live-p buffer) + (= (elt data 0) xcb:icccm:WM_STATE:IconicState)) + (with-current-buffer buffer + (bury-buffer))))) (t (exwm--log "Unhandled client message: %s" obj))))) (defun exwm--init-icccm-ewmh () @@ -435,49 +455,106 @@ ;; Handle PropertyNotify event (xcb:+event exwm--connection 'xcb:PropertyNotify #'exwm--on-PropertyNotify) ;; Handle relevant client messages - ;; FIXME: WM_STATE client messages (normal => iconic) - ;; WM_COLORMAP_NOTIFY (xcb:+event exwm--connection 'xcb:ClientMessage #'exwm--on-ClientMessage) ;; Set _NET_SUPPORTED (xcb:+request exwm--connection (make-instance 'xcb:ewmh:set-_NET_SUPPORTED :window exwm--root - :data (vector xcb:Atom:_NET_SUPPORTED - xcb:Atom:_NET_CLIENT_LIST - xcb:Atom:_NET_CLIENT_LIST_STACKING - xcb:Atom:_NET_NUMBER_OF_DESKTOPS - xcb:Atom:_NET_DESKTOP_VIEWPORT - xcb:Atom:_NET_CURRENT_DESKTOP - xcb:Atom:_NET_WORKAREA - xcb:Atom:_NET_SUPPORTING_WM_CHECK - xcb:Atom:_NET_VIRTUAL_ROOTS - xcb:Atom:_NET_WM_MOVERESIZE - xcb:Atom:_NET_REQUEST_FRAME_EXTENTS - xcb:Atom:_NET_FRAME_EXTENTS - xcb:Atom:_NET_WM_NAME - xcb:Atom:_NET_WM_STRUT - xcb:Atom:_NET_WM_STRUT_PARTIAL - ;; - xcb:Atom:_NET_WM_WINDOW_TYPE - xcb:Atom:_NET_WM_WINDOW_TYPE_TOOLBAR - xcb:Atom:_NET_WM_WINDOW_TYPE_MENU - xcb:Atom:_NET_WM_WINDOW_TYPE_UTILITY - xcb:Atom:_NET_WM_WINDOW_TYPE_SPLASH - xcb:Atom:_NET_WM_WINDOW_TYPE_DIALOG - xcb:Atom:_NET_WM_WINDOW_TYPE_DROPDOWN_MENU - xcb:Atom:_NET_WM_WINDOW_TYPE_POPUP_MENU - xcb:Atom:_NET_WM_WINDOW_TYPE_TOOLTIP - xcb:Atom:_NET_WM_WINDOW_TYPE_NOTIFICATION - xcb:Atom:_NET_WM_WINDOW_TYPE_COMBO - xcb:Atom:_NET_WM_WINDOW_TYPE_DND - xcb:Atom:_NET_WM_WINDOW_TYPE_NORMAL - ;; - xcb:Atom:_NET_WM_STATE - xcb:Atom:_NET_WM_STATE_MODAL - xcb:Atom:_NET_WM_STATE_FULLSCREEN - xcb:Atom:_NET_WM_STATE_DEMANDS_ATTENTION - ;; FIXME: more? - ))) + :data (vector + ;; Root windows properties. + xcb:Atom:_NET_SUPPORTED + xcb:Atom:_NET_CLIENT_LIST + xcb:Atom:_NET_CLIENT_LIST_STACKING + xcb:Atom:_NET_NUMBER_OF_DESKTOPS + xcb:Atom:_NET_DESKTOP_GEOMETRY + xcb:Atom:_NET_DESKTOP_VIEWPORT + xcb:Atom:_NET_CURRENT_DESKTOP + ;; xcb:Atom:_NET_DESKTOP_NAMES + xcb:Atom:_NET_ACTIVE_WINDOW + xcb:Atom:_NET_WORKAREA + xcb:Atom:_NET_SUPPORTING_WM_CHECK + xcb:Atom:_NET_VIRTUAL_ROOTS + ;; xcb:Atom:_NET_DESKTOP_LAYOUT + ;; xcb:Atom:_NET_SHOWING_DESKTOP + + ;; Other root window messages. + xcb:Atom:_NET_CLOSE_WINDOW + ;; xcb:Atom:_NET_MOVERESIZE_WINDOW + xcb:Atom:_NET_WM_MOVERESIZE + ;; xcb:Atom:_NET_RESTACK_WINDOW + xcb:Atom:_NET_REQUEST_FRAME_EXTENTS + + ;; Application window properties. + xcb:Atom:_NET_WM_NAME + ;; xcb:Atom:_NET_WM_VISIBLE_NAME + ;; xcb:Atom:_NET_WM_ICON_NAME + ;; xcb:Atom:_NET_WM_VISIBLE_ICON_NAME + xcb:Atom:_NET_WM_DESKTOP + ;; + xcb:Atom:_NET_WM_WINDOW_TYPE + ;; xcb:Atom:_NET_WM_WINDOW_TYPE_DESKTOP + xcb:Atom:_NET_WM_WINDOW_TYPE_DOCK + xcb:Atom:_NET_WM_WINDOW_TYPE_TOOLBAR + xcb:Atom:_NET_WM_WINDOW_TYPE_MENU + xcb:Atom:_NET_WM_WINDOW_TYPE_UTILITY + xcb:Atom:_NET_WM_WINDOW_TYPE_SPLASH + xcb:Atom:_NET_WM_WINDOW_TYPE_DIALOG + xcb:Atom:_NET_WM_WINDOW_TYPE_DROPDOWN_MENU + xcb:Atom:_NET_WM_WINDOW_TYPE_POPUP_MENU + xcb:Atom:_NET_WM_WINDOW_TYPE_TOOLTIP + xcb:Atom:_NET_WM_WINDOW_TYPE_NOTIFICATION + xcb:Atom:_NET_WM_WINDOW_TYPE_COMBO + xcb:Atom:_NET_WM_WINDOW_TYPE_DND + xcb:Atom:_NET_WM_WINDOW_TYPE_NORMAL + ;; + xcb:Atom:_NET_WM_STATE + xcb:Atom:_NET_WM_STATE_MODAL + ;; xcb:Atom:_NET_WM_STATE_STICKY + ;; xcb:Atom:_NET_WM_STATE_MAXIMIZED_VERT + ;; xcb:Atom:_NET_WM_STATE_MAXIMIZED_HORZ + ;; xcb:Atom:_NET_WM_STATE_SHADED + ;; xcb:Atom:_NET_WM_STATE_SKIP_TASKBAR + ;; xcb:Atom:_NET_WM_STATE_SKIP_PAGER + ;; xcb:Atom:_NET_WM_STATE_HIDDEN + xcb:Atom:_NET_WM_STATE_FULLSCREEN + ;; xcb:Atom:_NET_WM_STATE_ABOVE + ;; xcb:Atom:_NET_WM_STATE_BELOW + xcb:Atom:_NET_WM_STATE_DEMANDS_ATTENTION + ;; xcb:Atom:_NET_WM_STATE_FOCUSED + ;; + xcb:Atom:_NET_WM_ALLOWED_ACTIONS + xcb:Atom:_NET_WM_ACTION_MOVE + xcb:Atom:_NET_WM_ACTION_RESIZE + xcb:Atom:_NET_WM_ACTION_MINIMIZE + ;; xcb:Atom:_NET_WM_ACTION_SHADE + ;; xcb:Atom:_NET_WM_ACTION_STICK + ;; xcb:Atom:_NET_WM_ACTION_MAXIMIZE_HORZ + ;; xcb:Atom:_NET_WM_ACTION_MAXIMIZE_VERT + xcb:Atom:_NET_WM_ACTION_FULLSCREEN + xcb:Atom:_NET_WM_ACTION_CHANGE_DESKTOP + xcb:Atom:_NET_WM_ACTION_CLOSE + ;; xcb:Atom:_NET_WM_ACTION_ABOVE + ;; xcb:Atom:_NET_WM_ACTION_BELOW + ;; + xcb:Atom:_NET_WM_STRUT + xcb:Atom:_NET_WM_STRUT_PARTIAL + ;; xcb:Atom:_NET_WM_ICON_GEOMETRY + ;; xcb:Atom:_NET_WM_ICON + xcb:Atom:_NET_WM_PID + ;; xcb:Atom:_NET_WM_HANDLED_ICONS + ;; xcb:Atom:_NET_WM_USER_TIME + ;; xcb:Atom:_NET_WM_USER_TIME_WINDOW + xcb:Atom:_NET_FRAME_EXTENTS + ;; xcb:Atom:_NET_WM_OPAQUE_REGION + ;; xcb:Atom:_NET_WM_BYPASS_COMPOSITOR + + ;; Window manager protocols. + xcb:Atom:_NET_WM_PING + ;; xcb:Atom:_NET_WM_SYNC_REQUEST + ;; xcb:Atom:_NET_WM_FULLSCREEN_MONITORS + + ;; Other properties. + xcb:Atom:_NET_WM_FULL_PLACEMENT))) ;; Create a child window for setting _NET_SUPPORTING_WM_CHECK (let ((new-id (xcb:generate-id exwm--connection))) (xcb:+request exwm--connection @@ -496,15 +573,6 @@ (xcb:+request exwm--connection (make-instance 'xcb:ewmh:set-_NET_WM_NAME :window i :data "EXWM")))) - ;; Set _NET_NUMBER_OF_DESKTOPS - (xcb:+request exwm--connection - (make-instance 'xcb:ewmh:set-_NET_NUMBER_OF_DESKTOPS - :window exwm--root :data exwm-workspace-number)) - ;; Set _NET_DESKTOP_VIEWPORT - (xcb:+request exwm--connection - (make-instance 'xcb:ewmh:set-_NET_DESKTOP_VIEWPORT - :window exwm--root - :data (make-vector (* 2 exwm-workspace-number) 0))) (xcb:flush exwm--connection)) (defvar exwm-init-hook nil