mirror of
https://github.com/emacs-exwm/exwm.git
synced 2024-11-27 14:57:59 +01:00
Support adding and removing workspaces
Frames created via `make-frame' are added to the workspace list; `delete-frame' removes them from the list. Floating frames, non-graphical frames, as well as those associated to different displays are ignored. When deleting a workspace, care is taken to reparent that all X clients another workspace. * exwm-workspace.el (exwm-workspace--add-frame-as-workspace) (exwm-workspace--remove-frame-as-workspace): New functions that intercept created and deleted frames and configure them as EXWM workspaces. (exwm-workspace--update-ewmh-props): New function to update desktop-related EWMH properties after workspace changes. (exwm-workspace--init): Use `exwm-workspace--add-frame-as-workspace' to create the initial workspaces.
This commit is contained in:
parent
f4b8cc47c7
commit
0fbc725de1
1 changed files with 223 additions and 145 deletions
|
@ -851,6 +851,149 @@ before it."
|
||||||
|
|
||||||
(defvar exwm-workspace--timer nil "Timer used to track echo area changes.")
|
(defvar exwm-workspace--timer nil "Timer used to track echo area changes.")
|
||||||
|
|
||||||
|
(defun exwm-workspace--add-frame-as-workspace (frame)
|
||||||
|
"Configure frame FRAME to be treated as a workspace."
|
||||||
|
(cond
|
||||||
|
((>= (exwm-workspace--count) exwm-workspace-number)
|
||||||
|
(delete-frame frame)
|
||||||
|
(user-error "[EXWM] Too many workspaces: maximum is %d" exwm-workspace-number))
|
||||||
|
((exwm-workspace--workspace-p frame)
|
||||||
|
(exwm--log "Frame `%s' is already a workspace" frame))
|
||||||
|
((not (display-graphic-p frame))
|
||||||
|
(exwm--log "Frame `%s' is not graphical" frame))
|
||||||
|
((not (string-equal (slot-value exwm--connection 'display)
|
||||||
|
(frame-parameter frame 'display)))
|
||||||
|
(exwm--log "Frame `%s' is on a different DISPLAY (%S instead of %S)"
|
||||||
|
frame
|
||||||
|
(frame-parameter frame 'display)
|
||||||
|
(slot-value exwm--connection 'display)))
|
||||||
|
((frame-parameter frame 'exwm-floating)
|
||||||
|
(exwm--log "Frame `%s' is floating" frame))
|
||||||
|
(t
|
||||||
|
(exwm--log "Adding frame `%s' as workspace" frame)
|
||||||
|
(setq exwm-workspace--list (nconc exwm-workspace--list (list frame))
|
||||||
|
exwm-workspace--current frame)
|
||||||
|
(let ((outer-id (string-to-number (frame-parameter frame 'outer-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-container container)
|
||||||
|
(set-frame-parameter frame 'exwm-workspace workspace)
|
||||||
|
;; Use same RandR output and geometry as previous workspace.
|
||||||
|
(let ((prev-workspace (selected-frame)))
|
||||||
|
(dolist (param '(exwm-randr-output
|
||||||
|
exwm-geometry))
|
||||||
|
(set-frame-parameter frame param
|
||||||
|
(frame-parameter prev-workspace param))))
|
||||||
|
(xcb:+request exwm--connection
|
||||||
|
(make-instance 'xcb:CreateWindow
|
||||||
|
:depth 0 :wid workspace :parent exwm--root
|
||||||
|
:x 0 :y 0
|
||||||
|
:width (x-display-pixel-width)
|
||||||
|
:height (x-display-pixel-height)
|
||||||
|
:border-width 0 :class xcb:WindowClass:CopyFromParent
|
||||||
|
:visual 0 ;CopyFromParent
|
||||||
|
:value-mask (logior xcb:CW:OverrideRedirect
|
||||||
|
xcb:CW:EventMask)
|
||||||
|
:override-redirect 1
|
||||||
|
:event-mask xcb:EventMask:SubstructureRedirect))
|
||||||
|
(xcb:+request exwm--connection
|
||||||
|
(make-instance 'xcb:CreateWindow
|
||||||
|
:depth 0 :wid container :parent workspace
|
||||||
|
:x 0 :y 0
|
||||||
|
:width (x-display-pixel-width)
|
||||||
|
:height (x-display-pixel-height)
|
||||||
|
: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 workspace
|
||||||
|
:data
|
||||||
|
(format "EXWM workspace %d"
|
||||||
|
(exwm-workspace--position frame))))
|
||||||
|
(xcb:+request exwm--connection
|
||||||
|
(make-instance 'xcb:ewmh:set-_NET_WM_NAME
|
||||||
|
:window container
|
||||||
|
:data
|
||||||
|
(format "EXWM workspace %d frame container"
|
||||||
|
(exwm-workspace--position frame)))))
|
||||||
|
(xcb:+request exwm--connection
|
||||||
|
(make-instance 'xcb:ReparentWindow
|
||||||
|
:window outer-id :parent container :x 0 :y 0))
|
||||||
|
(xcb:+request exwm--connection
|
||||||
|
(make-instance 'xcb:MapWindow :window container))
|
||||||
|
(xcb:+request exwm--connection
|
||||||
|
(make-instance 'xcb:MapWindow :window workspace)))
|
||||||
|
(xcb:flush exwm--connection)
|
||||||
|
;; Delay making the workspace fullscreen until Emacs becomes idle
|
||||||
|
(run-with-idle-timer 0 nil
|
||||||
|
`(lambda ()
|
||||||
|
(set-frame-parameter ,frame 'fullscreen 'fullboth)))
|
||||||
|
;; Update EWMH properties.
|
||||||
|
(exwm-workspace--update-ewmh-props)
|
||||||
|
(exwm-workspace-switch frame t))))
|
||||||
|
|
||||||
|
(defun exwm-workspace--remove-frame-as-workspace (frame)
|
||||||
|
"Stop treating frame FRAME as a workspace."
|
||||||
|
(cond
|
||||||
|
((= 1 (exwm-workspace--count))
|
||||||
|
(exwm--log "Cannot remove last workspace"))
|
||||||
|
((not (exwm-workspace--workspace-p frame))
|
||||||
|
(exwm--log "Frame `%s' is not a workspace" frame))
|
||||||
|
(t
|
||||||
|
(exwm--log "Removing frame `%s' as workspace" frame)
|
||||||
|
(let* ((index (exwm-workspace--position frame))
|
||||||
|
(lastp (= index (1- (exwm-workspace--count))))
|
||||||
|
;; As we are removing this workspace, the one on its left is its
|
||||||
|
;; natural substitutes... except when this is already the last one
|
||||||
|
;; and there is none on its left.
|
||||||
|
(nextw (elt exwm-workspace--list (+ index (if lastp -1 +1)))))
|
||||||
|
;; Clients need to be moved to some other workspace before this is being
|
||||||
|
;; removed.
|
||||||
|
(dolist (pair exwm--id-buffer-alist)
|
||||||
|
(with-current-buffer (cdr pair)
|
||||||
|
(when (eq exwm--frame frame)
|
||||||
|
(exwm-workspace-move-window nextw exwm--id))))
|
||||||
|
;; Need to remove the workspace from the list in order for
|
||||||
|
;; `exwm-workspace-switch' to calculate the right index.
|
||||||
|
(setq exwm-workspace--list (delete frame exwm-workspace--list))
|
||||||
|
(when (eq frame exwm-workspace--current)
|
||||||
|
(exwm-workspace-switch nextw)))
|
||||||
|
;; Update EWMH properties.
|
||||||
|
(exwm-workspace--update-ewmh-props)
|
||||||
|
;; Update switch history.
|
||||||
|
(setq exwm-workspace--switch-history-outdated t))))
|
||||||
|
|
||||||
|
(defun exwm-workspace--update-ewmh-props ()
|
||||||
|
"Update EWMH properties to match the workspace list."
|
||||||
|
(let ((num-workspaces (exwm-workspace--count)))
|
||||||
|
;; 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 num-workspaces))
|
||||||
|
;; 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 num-workspaces) 0)))
|
||||||
|
;; Update and set _NET_WORKAREA.
|
||||||
|
(exwm-workspace--update-workareas)
|
||||||
|
;; Set _NET_VIRTUAL_ROOTS (it's currently fixed.)
|
||||||
|
(xcb:+request exwm--connection
|
||||||
|
(make-instance 'xcb:ewmh:set-_NET_VIRTUAL_ROOTS
|
||||||
|
:window exwm--root
|
||||||
|
:data (vconcat (mapcar
|
||||||
|
(lambda (i)
|
||||||
|
(frame-parameter i 'exwm-workspace))
|
||||||
|
exwm-workspace--list)))))
|
||||||
|
(xcb:flush exwm--connection))
|
||||||
|
|
||||||
(defun exwm-workspace--modify-all-x-frames-parameters (new-x-parameters)
|
(defun exwm-workspace--modify-all-x-frames-parameters (new-x-parameters)
|
||||||
"Modifies `window-system-default-frame-alist' for the X Window System.
|
"Modifies `window-system-default-frame-alist' for the X Window System.
|
||||||
NEW-X-PARAMETERS is an alist of frame parameters, merged into current
|
NEW-X-PARAMETERS is an alist of frame parameters, merged into current
|
||||||
|
@ -870,30 +1013,29 @@ applied to all subsequently created X frames."
|
||||||
(cl-assert (and (< 0 exwm-workspace-number) (>= 10 exwm-workspace-number)))
|
(cl-assert (and (< 0 exwm-workspace-number) (>= 10 exwm-workspace-number)))
|
||||||
;; Prevent unexpected exit
|
;; Prevent unexpected exit
|
||||||
(setq confirm-kill-emacs #'exwm-workspace--confirm-kill-emacs)
|
(setq confirm-kill-emacs #'exwm-workspace--confirm-kill-emacs)
|
||||||
|
(let ((initial-workspaces (frame-list)))
|
||||||
(if (not (exwm-workspace--minibuffer-own-frame-p))
|
(if (not (exwm-workspace--minibuffer-own-frame-p))
|
||||||
;; Initialize workspaces with minibuffers.
|
;; Initialize workspaces with minibuffers.
|
||||||
(progn
|
(progn
|
||||||
(setq exwm-workspace--list (frame-list))
|
|
||||||
(when (< 1 (exwm-workspace--count))
|
(when (< 1 (exwm-workspace--count))
|
||||||
;; Exclude the initial frame.
|
;; Exclude the initial frame.
|
||||||
(dolist (i exwm-workspace--list)
|
(dolist (i initial-workspaces)
|
||||||
(unless (frame-parameter i 'window-id)
|
(unless (frame-parameter i 'window-id)
|
||||||
(setq exwm-workspace--list (delq i exwm-workspace--list))))
|
(setq initial-workspaces (delq i initial-workspaces))))
|
||||||
(cl-assert (= 1 (exwm-workspace--count)))
|
(cl-assert (= 1 (length initial-workspaces)))
|
||||||
(setq exwm-workspace--client
|
(setq exwm-workspace--client
|
||||||
(frame-parameter (car exwm-workspace--list) 'client))
|
(frame-parameter (car exwm-workspace--list) 'client))
|
||||||
(let ((f (car exwm-workspace--list)))
|
(let ((f (car initial-workspaces)))
|
||||||
;; Remove the possible internal border.
|
;; Remove the possible internal border.
|
||||||
(set-frame-parameter f 'internal-border-width 0)
|
(set-frame-parameter f 'internal-border-width 0)
|
||||||
;; Prevent user from deleting this frame by accident.
|
;; Prevent user from deleting this frame by accident.
|
||||||
(set-frame-parameter f 'client nil))
|
(set-frame-parameter f 'client nil)))
|
||||||
;; Create remaining frames.
|
;; Create remaining frames.
|
||||||
(dotimes (_ (1- exwm-workspace-number))
|
(dotimes (_ (1- exwm-workspace-number))
|
||||||
(nconc exwm-workspace--list
|
(nconc initial-workspaces
|
||||||
(list (make-frame '((window-system . x)
|
(list (make-frame '((window-system . x)
|
||||||
(internal-border-width . 0))))))))
|
(internal-border-width . 0)))))))
|
||||||
;; Initialize workspaces without minibuffers.
|
;; Initialize workspaces without minibuffers.
|
||||||
(let ((old-frames (frame-list)))
|
|
||||||
(setq exwm-workspace--minibuffer
|
(setq exwm-workspace--minibuffer
|
||||||
(make-frame '((window-system . x) (minibuffer . only)
|
(make-frame '((window-system . x) (minibuffer . only)
|
||||||
(left . 10000) (right . 10000)
|
(left . 10000) (right . 10000)
|
||||||
|
@ -901,14 +1043,14 @@ applied to all subsequently created X frames."
|
||||||
(internal-border-width . 0)
|
(internal-border-width . 0)
|
||||||
(client . nil))))
|
(client . nil))))
|
||||||
;; Remove/hide existing frames.
|
;; Remove/hide existing frames.
|
||||||
(dolist (f old-frames)
|
(dolist (f initial-workspaces)
|
||||||
(if (frame-parameter f 'client)
|
(if (frame-parameter f 'client)
|
||||||
(progn
|
(progn
|
||||||
(unless exwm-workspace--client
|
(unless exwm-workspace--client
|
||||||
(setq exwm-workspace--client (frame-parameter f 'client)))
|
(setq exwm-workspace--client (frame-parameter f 'client)))
|
||||||
(make-frame-invisible f))
|
(make-frame-invisible f))
|
||||||
(when (eq 'x (framep f)) ;do not delete the initial frame.
|
(when (eq 'x (framep f)) ;do not delete the initial frame.
|
||||||
(delete-frame f)))))
|
(delete-frame f))))
|
||||||
;; This is the only usable minibuffer frame.
|
;; This is the only usable minibuffer frame.
|
||||||
(setq default-minibuffer-frame exwm-workspace--minibuffer)
|
(setq default-minibuffer-frame exwm-workspace--minibuffer)
|
||||||
(exwm-workspace--modify-all-x-frames-parameters
|
(exwm-workspace--modify-all-x-frames-parameters
|
||||||
|
@ -966,80 +1108,14 @@ applied to all subsequently created X frames."
|
||||||
(modify-all-frames-parameters
|
(modify-all-frames-parameters
|
||||||
'((buffer-predicate . exwm-layout--other-buffer-predicate)))
|
'((buffer-predicate . exwm-layout--other-buffer-predicate)))
|
||||||
;; Configure workspaces
|
;; Configure workspaces
|
||||||
(dolist (i exwm-workspace--list)
|
(dolist (i initial-workspaces)
|
||||||
(let ((outer-id (string-to-number (frame-parameter i 'outer-window-id)))
|
(exwm-workspace--add-frame-as-workspace i)))
|
||||||
(container (xcb:generate-id exwm--connection))
|
|
||||||
(workspace (xcb:generate-id exwm--connection)))
|
|
||||||
;; Save window IDs
|
|
||||||
(set-frame-parameter i 'exwm-outer-id outer-id)
|
|
||||||
(set-frame-parameter i 'exwm-container container)
|
|
||||||
(set-frame-parameter i 'exwm-workspace workspace)
|
|
||||||
(xcb:+request exwm--connection
|
|
||||||
(make-instance 'xcb:CreateWindow
|
|
||||||
:depth 0 :wid workspace :parent exwm--root
|
|
||||||
:x 0 :y 0
|
|
||||||
:width (x-display-pixel-width)
|
|
||||||
:height (x-display-pixel-height)
|
|
||||||
:border-width 0 :class xcb:WindowClass:CopyFromParent
|
|
||||||
:visual 0 ;CopyFromParent
|
|
||||||
:value-mask (logior xcb:CW:OverrideRedirect
|
|
||||||
xcb:CW:EventMask)
|
|
||||||
:override-redirect 1
|
|
||||||
:event-mask xcb:EventMask:SubstructureRedirect))
|
|
||||||
(xcb:+request exwm--connection
|
|
||||||
(make-instance 'xcb:CreateWindow
|
|
||||||
:depth 0 :wid container :parent workspace
|
|
||||||
:x 0 :y 0
|
|
||||||
:width (x-display-pixel-width)
|
|
||||||
:height (x-display-pixel-height)
|
|
||||||
: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 workspace
|
|
||||||
:data
|
|
||||||
(format "EXWM workspace %d"
|
|
||||||
(exwm-workspace--position i))))
|
|
||||||
(xcb:+request exwm--connection
|
|
||||||
(make-instance 'xcb:ewmh:set-_NET_WM_NAME
|
|
||||||
:window container
|
|
||||||
:data
|
|
||||||
(format "EXWM workspace %d frame container"
|
|
||||||
(exwm-workspace--position i)))))
|
|
||||||
(xcb:+request exwm--connection
|
|
||||||
(make-instance 'xcb:ReparentWindow
|
|
||||||
:window outer-id :parent container :x 0 :y 0))
|
|
||||||
(xcb:+request exwm--connection
|
|
||||||
(make-instance 'xcb:MapWindow :window container))
|
|
||||||
(xcb:+request exwm--connection
|
|
||||||
(make-instance 'xcb:MapWindow :window workspace))))
|
|
||||||
(xcb:flush exwm--connection)
|
(xcb:flush exwm--connection)
|
||||||
;; We have to advice `x-create-frame' or every call to it would hang EXWM
|
;; 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)
|
(advice-add 'x-create-frame :around #'exwm-workspace--x-create-frame)
|
||||||
;; Set _NET_NUMBER_OF_DESKTOPS (it's currently fixed).
|
;; Make new frames create new workspaces.
|
||||||
(xcb:+request exwm--connection
|
(add-hook 'after-make-frame-functions #'exwm-workspace--add-frame-as-workspace)
|
||||||
(make-instance 'xcb:ewmh:set-_NET_NUMBER_OF_DESKTOPS
|
(add-hook 'delete-frame-functions #'exwm-workspace--remove-frame-as-workspace)
|
||||||
:window exwm--root :data (exwm-workspace--count)))
|
|
||||||
;; 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--count)) 0)))
|
|
||||||
;; Update and set _NET_WORKAREA.
|
|
||||||
(exwm-workspace--update-workareas)
|
|
||||||
;; Set _NET_VIRTUAL_ROOTS (it's currently fixed.)
|
|
||||||
(xcb:+request exwm--connection
|
|
||||||
(make-instance 'xcb:ewmh:set-_NET_VIRTUAL_ROOTS
|
|
||||||
:window exwm--root
|
|
||||||
:data (vconcat (mapcar
|
|
||||||
(lambda (i)
|
|
||||||
(frame-parameter i 'exwm-workspace))
|
|
||||||
exwm-workspace--list))))
|
|
||||||
;; Switch to the first workspace
|
;; Switch to the first workspace
|
||||||
(exwm-workspace-switch 0 t))
|
(exwm-workspace-switch 0 t))
|
||||||
|
|
||||||
|
@ -1060,7 +1136,9 @@ applied to all subsequently created X frames."
|
||||||
(cl-delete '(exwm-workspace--display-buffer) display-buffer-alist
|
(cl-delete '(exwm-workspace--display-buffer) display-buffer-alist
|
||||||
:test #'equal))
|
:test #'equal))
|
||||||
(remove-hook 'focus-in-hook #'exwm-workspace--on-focus-in)
|
(remove-hook 'focus-in-hook #'exwm-workspace--on-focus-in)
|
||||||
(advice-remove 'x-create-frame #'exwm-workspace--x-create-frame))
|
(advice-remove 'x-create-frame #'exwm-workspace--x-create-frame)
|
||||||
|
(remove-hook 'after-make-frame-functions #'exwm-workspace--add-frame-as-workspace)
|
||||||
|
(remove-hook 'delete-frame-functions #'exwm-workspace--remove-frame-as-workspace))
|
||||||
|
|
||||||
(defun exwm-workspace--post-init ()
|
(defun exwm-workspace--post-init ()
|
||||||
"The second stage in the initialization of the workspace module."
|
"The second stage in the initialization of the workspace module."
|
||||||
|
|
Loading…
Reference in a new issue