Merge branch 'medranocalvo/dynamic-workspaces' into externals/exwm

This commit is contained in:
Chris Feng 2016-07-19 11:01:19 +08:00
commit b409d873b6
11 changed files with 626 additions and 293 deletions

View file

@ -1,17 +1,20 @@
# Emacs X Window Manager # Emacs X Window Manager
EXWM (Emacs X Window Manager) is a full-featured tiling X window manager for EXWM (Emacs X Window Manager) is a full-featured tiling X window manager
Emacs built on top of [XELB](https://github.com/ch11ng/xelb). for Emacs built on top of [XELB](https://github.com/ch11ng/xelb).
It features: It features:
+ Fully keyboard-driven operations + Fully keyboard-driven operations
+ Hybrid layout modes (tiling & stacking) + Hybrid layout modes (tiling & stacking)
+ Workspace support + Dynamic workspace support
+ ICCCM/EWMH compliance + ICCCM/EWMH compliance
+ (Optional) RandR (multi-monitor) support + (Optional) RandR (multi-monitor) support
+ (Optional) system tray + (Optional) Built-in system tray
Please check the [User Guide](https://github.com/ch11ng/exwm/wiki) Please check out the
for more details. [screenshots](https://github.com/ch11ng/exwm/wiki/Screenshots)
to get an overview of what EXWM is capable of,
and the [user guide](https://github.com/ch11ng/exwm/wiki)
for a detailed explanation of its usage.
**Note**: If you install EXWM from source, you need to manually install XELB **Note**: If you install EXWM from source, it's recommended to install
(either from source or GNU ELPA). XELB also from source (otherwise install both from GNU ELPA).

View file

@ -39,7 +39,7 @@
;; 's-w': Switch workspace ;; 's-w': Switch workspace
(exwm-input-set-key (kbd "s-w") #'exwm-workspace-switch) (exwm-input-set-key (kbd "s-w") #'exwm-workspace-switch)
;; 's-N': Switch to certain workspace ;; 's-N': Switch to certain workspace
(dotimes (i exwm-workspace-number) (dotimes (i 10)
(exwm-input-set-key (kbd (format "s-%d" i)) (exwm-input-set-key (kbd (format "s-%d" i))
`(lambda () (interactive) (exwm-workspace-switch ,i)))) `(lambda () (interactive) (exwm-workspace-switch ,i))))
;; 's-&': Launch application ;; 's-&': Launch application

View file

@ -119,6 +119,15 @@
;; _MOTIF_WM_HINTS ;; _MOTIF_WM_HINTS
(defvar-local exwm--mwm-hints-decorations t) (defvar-local exwm--mwm-hints-decorations t)
(declare-function exwm-floating-hide "exwm-floating.el")
(declare-function exwm-floating-toggle-floating "exwm-floating.el")
(declare-function exwm-input-release-keyboard "exwm-input.el")
(declare-function exwm-input-send-next-key "exwm-input.el" (times))
(declare-function exwm-layout-set-fullscreen "exwm-layout.el" (&optional id))
(declare-function exwm-layout-toggle-mode-line "exwm-layout.el")
(declare-function exwm-workspace-move-window "exwm-workspace.el"
(frame-or-index &optional id))
(defvar exwm-mode-map (defvar exwm-mode-map
(let ((map (make-sparse-keymap))) (let ((map (make-sparse-keymap)))
(define-key map "\C-c\C-f" #'exwm-layout-set-fullscreen) (define-key map "\C-c\C-f" #'exwm-layout-set-fullscreen)
@ -184,8 +193,12 @@
"---" "---"
"*Workspace*" "*Workspace*"
"---" "---"
["Move window to" exwm-workspace-move-window :keys "C-c C-m"] ["Add workspace" exwm-workspace-add]
["Switch to buffer" exwm-workspace-switch-to-buffer] ["Delete current workspace" exwm-workspace-delete]
["Move workspace to" exwm-workspace-move]
["Swap workspaces" exwm-workspace-swap]
["Move X window to" exwm-workspace-move-window :keys "C-c C-m"]
["Move X window from" exwm-workspace-switch-to-buffer]
["Switch workspace" exwm-workspace-switch] ["Switch workspace" exwm-workspace-switch]
;; Place this entry at bottom to avoid selecting others by accident. ;; Place this entry at bottom to avoid selecting others by accident.
("Switch to" :filter ("Switch to" :filter
@ -196,7 +209,7 @@
(interactive) (interactive)
(exwm-workspace-switch ,i)) (exwm-workspace-switch ,i))
(/= ,i exwm-workspace-current-index)]) (/= ,i exwm-workspace-current-index)])
(number-sequence 0 (1- exwm-workspace-number))))))) (number-sequence 0 (1- (exwm-workspace--count))))))))
(declare-function exwm-manage--kill-buffer-query-function "exwm-manage.el") (declare-function exwm-manage--kill-buffer-query-function "exwm-manage.el")

View file

@ -67,14 +67,11 @@
xcb:Atom:_NET_WM_ACTION_CLOSE))))) xcb:Atom:_NET_WM_ACTION_CLOSE)))))
(defvar exwm-workspace--current) (defvar exwm-workspace--current)
(defvar exwm-workspace--list)
(defvar exwm-workspace-current-index)
(defvar exwm-workspace--switch-history-outdated)
(defvar exwm-workspace--struts)
(declare-function exwm-layout--refresh "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--show "exwm-layout.el" (id &optional window))
(declare-function exwm-layout--iconic-state-p "exwm-layout.el" (&optional id)) (declare-function exwm-layout--iconic-state-p "exwm-layout.el" (&optional id))
(declare-function exwm-workspace--minibuffer-own-frame-p "exwm-workspace.el")
(defun exwm-floating--set-floating (id) (defun exwm-floating--set-floating (id)
"Make window ID floating." "Make window ID floating."

View file

@ -102,6 +102,9 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.")
(declare-function exwm-layout--iconic-state-p "exwm-layout.el" (&optional id)) (declare-function exwm-layout--iconic-state-p "exwm-layout.el" (&optional id))
(declare-function exwm-layout--set-state "exwm-layout.el" (id state)) (declare-function exwm-layout--set-state "exwm-layout.el" (id state))
(declare-function exwm-workspace--minibuffer-own-frame-p "exwm-workspace.el")
(declare-function exwm-workspace-switch "exwm-workspace.el"
(frame-or-index &optional force))
(defun exwm-input--update-focus () (defun exwm-input--update-focus ()
"Update input focus." "Update input focus."
@ -117,7 +120,7 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.")
(setq exwm-workspace--switch-history-outdated t) (setq exwm-workspace--switch-history-outdated t)
(force-mode-line-update) (force-mode-line-update)
;; The application may have changed its input focus ;; The application may have changed its input focus
(exwm-workspace-switch exwm-workspace-current-index t)) (exwm-workspace-switch exwm-workspace--current t))
(exwm--log "Set focus on #x%x" exwm--id) (exwm--log "Set focus on #x%x" exwm--id)
(exwm-input--set-focus exwm--id) (exwm-input--set-focus exwm--id)
(when exwm--floating-frame (when exwm--floating-frame
@ -176,6 +179,8 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.")
(declare-function exwm-floating--start-moveresize "exwm-floating.el" (declare-function exwm-floating--start-moveresize "exwm-floating.el"
(id &optional type)) (id &optional type))
(declare-function exwm-workspace--position "exwm-workspace.el" (frame))
(declare-function exwm-workspace--workspace-p "exwm-workspace.el" (workspace))
(defvar exwm-workspace--list) (defvar exwm-workspace--list)
@ -202,17 +207,15 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.")
(unless (eq window (selected-window)) (unless (eq window (selected-window))
(setq frame (window-frame window)) (setq frame (window-frame window))
(unless (eq frame exwm-workspace--current) (unless (eq frame exwm-workspace--current)
(if (memq frame exwm-workspace--list) (if (exwm-workspace--workspace-p frame)
;; The X window is on another workspace ;; The X window is on another workspace
(exwm-workspace-switch (exwm-workspace-switch frame)
(cl-position frame exwm-workspace--list))
(with-current-buffer (window-buffer window) (with-current-buffer (window-buffer window)
(when (and (eq major-mode 'exwm-mode) (when (and (eq major-mode 'exwm-mode)
(not (eq exwm--frame (not (eq exwm--frame
exwm-workspace--current))) exwm-workspace--current)))
;; The floating X window is on another workspace ;; The floating X window is on another workspace
(exwm-workspace-switch (exwm-workspace-switch exwm--frame)))))
(cl-position exwm--frame exwm-workspace--list))))))
;; It has been reported that the `window' may have be deleted ;; It has been reported that the `window' may have be deleted
(if (window-live-p window) (if (window-live-p window)
(select-window window) (select-window window)

View file

@ -153,9 +153,16 @@
(xcb:flush exwm--connection)))) (xcb:flush exwm--connection))))
(defvar exwm-workspace--current) (defvar exwm-workspace--current)
(defvar exwm-workspace--list)
(declare-function exwm-input-grab-keyboard "exwm-input.el")
(declare-function exwm-input-release-keyboard "exwm-input.el")
(declare-function exwm-workspace--current-height "exwm-workspace.el")
(declare-function exwm-workspace--current-width "exwm-workspace.el")
(declare-function exwm-workspace--get-geometry "exwm-workspace.el" (frame))
(declare-function exwm-workspace--minibuffer-own-frame-p "exwm-workspace.el")
(declare-function exwm-workspace--set-fullscreen "exwm-workspace.el" (frame)) (declare-function exwm-workspace--set-fullscreen "exwm-workspace.el" (frame))
(declare-function exwm-workspace-move-window "exwm-workspace.el"
(frame-or-index &optional id))
;;;###autoload ;;;###autoload
(defun exwm-layout-set-fullscreen (&optional id) (defun exwm-layout-set-fullscreen (&optional id)
@ -273,6 +280,8 @@ selected by `other-buffer'."
(defvar exwm-layout-show-all-buffers nil (defvar exwm-layout-show-all-buffers nil
"Non-nil to allow switching to buffers on other workspaces.") "Non-nil to allow switching to buffers on other workspaces.")
(declare-function exwm-workspace--workspace-p "exwm-workspace.el"
(workspace))
(defun exwm-layout--set-client-list-stacking () (defun exwm-layout--set-client-list-stacking ()
"Set _NET_CLIENT_LIST_STACKING." "Set _NET_CLIENT_LIST_STACKING."
@ -303,7 +312,7 @@ selected by `other-buffer'."
covered-buffers ;EXWM-buffers covered by a new X window. covered-buffers ;EXWM-buffers covered by a new X window.
vacated-windows ;Windows previously displaying EXWM-buffers. vacated-windows ;Windows previously displaying EXWM-buffers.
windows) windows)
(if (not (memq frame exwm-workspace--list)) (if (not (exwm-workspace--workspace-p frame))
(if (frame-parameter frame 'exwm-outer-id) (if (frame-parameter frame 'exwm-outer-id)
;; Refresh a floating frame ;; Refresh a floating frame
(let ((window (frame-first-window frame))) (let ((window (frame-first-window frame)))
@ -337,8 +346,7 @@ selected by `other-buffer'."
(let ((window (car windows))) (let ((window (car windows)))
(if (eq frame exwm--frame) (if (eq frame exwm--frame)
(exwm-layout--show exwm--id window) (exwm-layout--show exwm--id window)
(exwm-workspace-move-window (exwm-workspace-move-window frame exwm--id))
(cl-position frame exwm-workspace--list) exwm--id))
;; Make sure this buffer is not displayed elsewhere. Note down ;; Make sure this buffer is not displayed elsewhere. Note down
;; windows displaying an EXWM-buffer now displayed elsewhere; we ;; windows displaying an EXWM-buffer now displayed elsewhere; we
;; need to display with some other buffer there. ;; need to display with some other buffer there.

View file

@ -89,7 +89,12 @@ corresponding buffer.")
(declare-function exwm--update-struts "exwm.el" (id)) (declare-function exwm--update-struts "exwm.el" (id))
(declare-function exwm-floating--set-floating "exwm-floating.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-floating--unset-floating "exwm-floating.el" (id))
(declare-function exwm-input-grab-keyboard "exwm-input.el")
(declare-function exwm-workspace--current-height "exwm-workspace.el")
(declare-function exwm-workspace--current-width "exwm-workspace.el")
(declare-function exwm-workspace--set-desktop "exwm-workspace.el" (id)) (declare-function exwm-workspace--set-desktop "exwm-workspace.el" (id))
(declare-function exwm-workspace-move-window "exwm-workspace.el"
(frame-or-index &optional id))
(defun exwm-manage--manage-window (id) (defun exwm-manage--manage-window (id)
"Manage window ID." "Manage window ID."

View file

@ -55,11 +55,11 @@
"Normal hook run when the RandR module just refreshed.") "Normal hook run when the RandR module just refreshed.")
(defvar exwm-workspace--fullscreen-frame-count) (defvar exwm-workspace--fullscreen-frame-count)
(defvar exwm-workspace-number)
(defvar exwm-workspace--list) (defvar exwm-workspace--list)
(declare-function exwm-workspace--update-workareas "exwm-workspace.el" ()) (declare-function exwm-workspace--count "exwm-workspace.el")
(declare-function exwm-workspace--set-fullscreen "exwm-workspace.el" (frame)) (declare-function exwm-workspace--set-fullscreen "exwm-workspace.el" (frame))
(declare-function exwm-workspace--update-workareas "exwm-workspace.el" ())
(declare-function exwm-workspace--set-desktop-geometry "exwm-workspace.el" ()) (declare-function exwm-workspace--set-desktop-geometry "exwm-workspace.el" ())
(defun exwm-randr--refresh () (defun exwm-randr--refresh ()
@ -97,7 +97,7 @@
(when exwm-workspace--fullscreen-frame-count (when exwm-workspace--fullscreen-frame-count
;; Not all workspaces are fullscreen; reset this counter. ;; Not all workspaces are fullscreen; reset this counter.
(setq exwm-workspace--fullscreen-frame-count 0)) (setq exwm-workspace--fullscreen-frame-count 0))
(dotimes (i exwm-workspace-number) (dotimes (i (exwm-workspace--count))
(let* ((output (plist-get exwm-randr-workspace-output-plist i)) (let* ((output (plist-get exwm-randr-workspace-output-plist i))
(geometry (lax-plist-get output-plist output)) (geometry (lax-plist-get output-plist output))
(frame (elt exwm-workspace--list i))) (frame (elt exwm-workspace--list i)))
@ -154,10 +154,12 @@
;; xcb:randr:NotifyMask:OutputProperty ;; xcb:randr:NotifyMask:OutputProperty
;; xcb:randr:NotifyMask:CrtcChange)) ;; xcb:randr:NotifyMask:CrtcChange))
)) ))
(xcb:flush exwm--connection))))) (xcb:flush exwm--connection)
(add-hook 'exwm-workspace-list-change-hook #'exwm-randr--refresh)))))
(defun exwm-randr--exit () (defun exwm-randr--exit ()
"Exit the RandR module.") "Exit the RandR module."
(remove-hook 'exwm-workspace-list-change-hook #'exwm-randr--refresh))
(defun exwm-randr-enable () (defun exwm-randr-enable ()
"Enable RandR support for EXWM." "Enable RandR support for EXWM."

View file

@ -65,6 +65,11 @@ You shall use the default value if using auto-hide minibuffer.")
"The selection owner window.") "The selection owner window.")
(defvar exwm-systemtray--embedder nil "The embedder window.") (defvar exwm-systemtray--embedder nil "The embedder window.")
(defvar exwm-workspace--current)
(declare-function exwm-workspace--current-height "exwm-workspace.el")
(declare-function exwm-workspace--current-width "exwm-workspace.el")
(declare-function exwm-workspace--minibuffer-own-frame-p "exwm-workspace.el")
(defun exwm-systemtray--embed (icon) (defun exwm-systemtray--embed (icon)
"Embed an icon." "Embed an icon."
(exwm--log "(System Tray) Try to embed #x%x" icon) (exwm--log "(System Tray) Try to embed #x%x" icon)
@ -266,8 +271,6 @@ You shall use the default value if using auto-hide minibuffer.")
(t (t
(exwm--log "(System Tray) Unknown opcode message: %s" obj))))))) (exwm--log "(System Tray) Unknown opcode message: %s" obj)))))))
(defvar exwm-workspace--current)
(defun exwm-systemtray--on-workspace-switch () (defun exwm-systemtray--on-workspace-switch ()
"Reparent/Refresh the system tray in `exwm-workspace-switch-hook'." "Reparent/Refresh the system tray in `exwm-workspace-switch-hook'."
(unless (exwm-workspace--minibuffer-own-frame-p) (unless (exwm-workspace--minibuffer-own-frame-p)

View file

@ -27,22 +27,51 @@
(require 'exwm-core) (require 'exwm-core)
(defvar exwm-workspace-number 4 "Number of workspaces (1 ~ 10).") (defvar exwm-workspace-number 1 "Initial number of workspaces.")
(defvar exwm-workspace--list nil "List of all workspaces (Emacs frames).") (defvar exwm-workspace--list nil "List of all workspaces (Emacs frames).")
(defvar exwm-workspace--current nil "Current active workspace.")
(defvar exwm-workspace-current-index 0 "Index of current active workspace.")
(defsubst exwm-workspace--position (frame)
"Retrieve index of given FRAME in workspace list.
NIL if FRAME is not a workspace"
(cl-position frame exwm-workspace--list))
(defsubst exwm-workspace--count ()
"Retrieve total number of workspaces."
(length exwm-workspace--list))
(defsubst exwm-workspace--workspace-p (frame)
"Return t if FRAME is a workspace."
(memq frame exwm-workspace--list))
(defun exwm-workspace--workspace-from-frame-or-index (frame-or-index)
"Retrieve the workspace frame from FRAME-OR-INDEX."
(cond
((framep frame-or-index)
(unless (exwm-workspace--position frame-or-index)
(user-error "[EXWM] Frame is not a workspace %S" frame-or-index))
frame-or-index)
((integerp frame-or-index)
(unless (and (<= 0 frame-or-index)
(< frame-or-index (exwm-workspace--count)))
(user-error "[EXWM] Workspace index out of range: %d" frame-or-index))
(elt exwm-workspace--list frame-or-index))
(t (user-error "[EXWM] Invalid workspace: %s" frame-or-index))))
(defvar exwm-workspace--switch-map (defvar exwm-workspace--switch-map
(let ((map (make-sparse-keymap))) (let ((map (make-sparse-keymap)))
(define-key map [t] (lambda () (interactive))) (define-key map [t] (lambda () (interactive)))
(define-key map "+" #'exwm-workspace-add)
(define-key map "-" #'exwm-workspace-delete)
(dotimes (i 10) (dotimes (i 10)
(define-key map (int-to-string i) (define-key map (int-to-string i)
`(lambda () #'exwm-workspace--switch-map-nth-prefix))
(interactive)
(when (< ,i exwm-workspace-number)
(goto-history-element ,(1+ i))
(exit-minibuffer)))))
(define-key map "\C-a" (lambda () (interactive) (goto-history-element 1))) (define-key map "\C-a" (lambda () (interactive) (goto-history-element 1)))
(define-key map "\C-e" (lambda () (define-key map "\C-e" (lambda ()
(interactive) (interactive)
(goto-history-element exwm-workspace-number))) (goto-history-element (exwm-workspace--count))))
(define-key map "\C-g" #'abort-recursive-edit) (define-key map "\C-g" #'abort-recursive-edit)
(define-key map "\C-]" #'abort-recursive-edit) (define-key map "\C-]" #'abort-recursive-edit)
(define-key map "\C-j" #'exit-minibuffer) (define-key map "\C-j" #'exit-minibuffer)
@ -62,17 +91,32 @@
(defvar exwm-workspace--switch-history-outdated nil (defvar exwm-workspace--switch-history-outdated nil
"Non-nil to indicate `exwm-workspace--switch-history' is outdated.") "Non-nil to indicate `exwm-workspace--switch-history' is outdated.")
(defun exwm-workspace--prompt-for-workspace (&optional prompt)
"Prompt for a workspace, returning the workspace frame."
(exwm-workspace--update-switch-history)
(let* ((current-idx (exwm-workspace--position exwm-workspace--current))
(history-add-new-input nil) ;prevent modifying history
(history-idx (read-from-minibuffer
(or prompt "Workspace: ")
(elt exwm-workspace--switch-history current-idx)
exwm-workspace--switch-map nil
`(exwm-workspace--switch-history . ,(1+ current-idx))))
(workspace-idx (cl-position history-idx exwm-workspace--switch-history
:test #'equal)))
(elt exwm-workspace--list workspace-idx)))
(defun exwm-workspace--update-switch-history () (defun exwm-workspace--update-switch-history ()
"Update the history for switching workspace to reflect the latest status." "Update the history for switching workspace to reflect the latest status."
(when exwm-workspace--switch-history-outdated (when exwm-workspace--switch-history-outdated
(setq exwm-workspace--switch-history-outdated nil) (setq exwm-workspace--switch-history-outdated nil)
(let ((sequence (number-sequence 0 (1- exwm-workspace-number))) (let* ((num (exwm-workspace--count))
(not-empty (make-vector exwm-workspace-number nil))) (sequence (number-sequence 0 (1- num)))
(not-empty (make-vector num nil)))
(dolist (i exwm--id-buffer-alist) (dolist (i exwm--id-buffer-alist)
(with-current-buffer (cdr i) (with-current-buffer (cdr i)
(when exwm--frame (when exwm--frame
(setf (aref not-empty (setf (aref not-empty
(cl-position exwm--frame exwm-workspace--list)) (exwm-workspace--position exwm--frame))
t)))) t))))
(setq exwm-workspace--switch-history (setq exwm-workspace--switch-history
(mapcar (mapcar
@ -91,8 +135,6 @@
sequence "")) sequence ""))
sequence))))) sequence)))))
(defvar exwm-workspace--current nil "Current active workspace.")
(defvar exwm-workspace-current-index 0 "Index of current active workspace.")
(defvar exwm-workspace-show-all-buffers nil (defvar exwm-workspace-show-all-buffers nil
"Non-nil to show buffers on other workspaces.") "Non-nil to show buffers on other workspaces.")
(defvar exwm-workspace--minibuffer nil (defvar exwm-workspace--minibuffer nil
@ -178,8 +220,7 @@ Value nil means to use the default position which is fixed at bottom, while
(list (vector x y width height)))))) (list (vector x y width height))))))
;; Fall back to use the screen size. ;; Fall back to use the screen size.
(let ((workarea (vector 0 0 root-width root-height))) (let ((workarea (vector 0 0 root-width root-height)))
(dotimes (_ exwm-workspace-number) (setq workareas (make-list (exwm-workspace--count) workarea))))
(push workarea workareas))))
;; Exclude areas occupied by struts. ;; Exclude areas occupied by struts.
(dolist (struts exwm-workspace--struts) (dolist (struts exwm-workspace--struts)
(setq edge (aref struts 0) (setq edge (aref struts 0)
@ -240,7 +281,7 @@ Value nil means to use the default position which is fixed at bottom, while
(defun exwm-workspace--set-fullscreen (frame) (defun exwm-workspace--set-fullscreen (frame)
"Make frame FRAME fullscreen according to `exwm-workspace--workareas'." "Make frame FRAME fullscreen according to `exwm-workspace--workareas'."
(let ((workarea (elt exwm-workspace--workareas (let ((workarea (elt exwm-workspace--workareas
(cl-position frame exwm-workspace--list))) (exwm-workspace--position frame)))
(id (frame-parameter frame 'exwm-outer-id)) (id (frame-parameter frame 'exwm-outer-id))
(container (frame-parameter frame 'exwm-container)) (container (frame-parameter frame 'exwm-container))
(workspace (frame-parameter frame 'exwm-workspace)) (workspace (frame-parameter frame 'exwm-workspace))
@ -282,33 +323,74 @@ Value nil means to use the default position which is fixed at bottom, while
:stack-mode xcb:StackMode:Above)) :stack-mode xcb:StackMode:Above))
(set-frame-width exwm-workspace--minibuffer width nil t))) (set-frame-width exwm-workspace--minibuffer width nil t)))
(defun exwm-workspace--switch-map-nth-prefix (&optional prefix-digits)
"Allow selecting a workspace by number.
PREFIX-DIGITS is a list of the digits introduced so far."
(interactive)
(let* ((k (aref (substring (this-command-keys-vector) -1) 0))
(d (- k ?0))
;; Convert prefix-digits to number. For example, '(2 1) to 120.
(o 1)
(pn (apply #'+ (mapcar (lambda (x)
(setq o (* 10 o))
(* o x))
prefix-digits)))
(n (+ pn d))
(num-workspaces (exwm-workspace--count)))
(if (= (length prefix-digits)
(floor (log (1- num-workspaces) 10)))
(exwm-workspace--switch-map-select-nth n)
;; go ahead if there are enough digits to select any workspace.
(set-transient-map
(let ((map (make-sparse-keymap))
(cmd `(lambda ()
(interactive)
(exwm-workspace--switch-map-nth-prefix
',(cons d prefix-digits)))))
(dotimes (i 10)
(define-key map (int-to-string i) cmd))
;; Accept
(define-key map [return]
`(lambda ()
(interactive)
(exwm-workspace--switch-map-select-nth ,n)))
map)))))
(defun exwm-workspace--switch-map-select-nth (n)
"Select Nth workspace."
(interactive)
(goto-history-element (1+ n))
(exit-minibuffer))
(defvar exwm-workspace-switch-hook nil (defvar exwm-workspace-switch-hook nil
"Normal hook run after switching workspace.") "Normal hook run after switching workspace.")
;;;###autoload ;;;###autoload
(defun exwm-workspace-switch (index &optional force) (cl-defun exwm-workspace-switch (frame-or-index &optional force)
"Switch to workspace INDEX. Query for INDEX if it's not specified. "Switch to workspace INDEX. Query for FRAME-OR-INDEX if it's not specified.
The optional FORCE option is for internal use only." The optional FORCE option is for internal use only."
(interactive (interactive
(list (list
(unless (and (eq major-mode 'exwm-mode) exwm--fullscreen) ;it's invisible (unless (and (eq major-mode 'exwm-mode) exwm--fullscreen) ;it's invisible
(exwm-workspace--update-switch-history) (exwm-workspace--prompt-for-workspace "Workspace [+/-]: "))))
(let* ((history-add-new-input nil) ;prevent modifying history ;; Try to create workspace(s) when INDEX is out-of-range.
(idx (read-from-minibuffer (when (and (integerp frame-or-index)
"Workspace: " (elt exwm-workspace--switch-history (>= frame-or-index (exwm-workspace--count)))
exwm-workspace-current-index) (run-with-idle-timer 0 nil
exwm-workspace--switch-map nil (lambda (times)
`(exwm-workspace--switch-history (dotimes (_ times)
. ,(1+ exwm-workspace-current-index))))) (make-frame)))
(cl-position idx exwm-workspace--switch-history :test #'equal))))) ;; Create no more than 9 workspaces.
(when index (min 9
(unless (and (<= 0 index) (< index exwm-workspace-number)) (1+ (- frame-or-index (exwm-workspace--count)))))
(user-error "[EXWM] Workspace index out of range: %d" index)) (cl-return-from exwm-workspace-switch))
(when (or force (/= exwm-workspace-current-index index)) (let* ((frame (exwm-workspace--workspace-from-frame-or-index frame-or-index))
(let* ((frame (elt exwm-workspace--list index)) (index (exwm-workspace--position frame))
(workspace (frame-parameter frame 'exwm-workspace)) (workspace (frame-parameter frame 'exwm-workspace))
(window (frame-parameter frame 'exwm-selected-window))) (window (frame-parameter frame 'exwm-selected-window)))
(when (or force (not (eq frame exwm-workspace--current)))
(unless (window-live-p window) (unless (window-live-p window)
(setq window (frame-selected-window frame))) (setq window (frame-selected-window frame)))
;; Raise the workspace container. ;; Raise the workspace container.
@ -327,7 +409,7 @@ The optional FORCE option is for internal use only."
:stack-mode xcb:StackMode:Above)))) :stack-mode xcb:StackMode:Above))))
(setq exwm-workspace--current frame (setq exwm-workspace--current frame
exwm-workspace-current-index index) exwm-workspace-current-index index)
(unless (memq (selected-frame) exwm-workspace--list) (unless (exwm-workspace--workspace-p (selected-frame))
;; Save the floating frame window selected on the previous workspace. ;; Save the floating frame window selected on the previous workspace.
(set-frame-parameter (with-current-buffer (window-buffer) (set-frame-parameter (with-current-buffer (window-buffer)
exwm--frame) exwm--frame)
@ -336,7 +418,10 @@ The optional FORCE option is for internal use only."
(set-frame-parameter frame 'exwm-selected-window nil) (set-frame-parameter frame 'exwm-selected-window nil)
;; Close the (possible) active minibuffer ;; Close the (possible) active minibuffer
(when (active-minibuffer-window) (when (active-minibuffer-window)
(run-with-idle-timer 0 nil (lambda () (abort-recursive-edit)))) (run-with-idle-timer 0 nil (lambda ()
;; Might be aborted by then.
(when (active-minibuffer-window)
(abort-recursive-edit)))))
(if (not (exwm-workspace--minibuffer-own-frame-p)) (if (not (exwm-workspace--minibuffer-own-frame-p))
(setq default-minibuffer-frame frame) (setq default-minibuffer-frame frame)
;; Resize/reposition the minibuffer frame ;; Resize/reposition the minibuffer frame
@ -361,18 +446,119 @@ The optional FORCE option is for internal use only."
(set-frame-parameter frame 'exwm--urgency nil) (set-frame-parameter frame 'exwm--urgency nil)
;; Update switch workspace history ;; Update switch workspace history
(setq exwm-workspace--switch-history-outdated t) (setq exwm-workspace--switch-history-outdated t)
;; Set _NET_CURRENT_DESKTOP. ;; Set _NET_CURRENT_DESKTOP
(xcb:+request exwm--connection (xcb:+request exwm--connection
(make-instance 'xcb:ewmh:set-_NET_CURRENT_DESKTOP (make-instance 'xcb:ewmh:set-_NET_CURRENT_DESKTOP
:window exwm--root :data index)) :window exwm--root :data index))
(xcb:flush exwm--connection)) (xcb:flush exwm--connection))
(run-hooks 'exwm-workspace-switch-hook)))) (run-hooks 'exwm-workspace-switch-hook)))
(defvar exwm-workspace-list-change-hook nil
"Normal hook run when the workspace list is changed (workspace added,
deleted, moved, etc).")
;;;###autoload
(defun exwm-workspace-swap (workspace1 workspace2)
"Interchange position of WORKSPACE1 with that of WORKSPACE2."
(interactive
(unless (and (eq major-mode 'exwm-mode) exwm--fullscreen) ;it's invisible
(let* ((w1 (exwm-workspace--prompt-for-workspace "Pick a workspace: "))
(w2 (exwm-workspace--prompt-for-workspace
(format "Swap workspace %d with: "
(exwm-workspace--position w1)))))
(list w1 w2))))
(let ((pos1 (exwm-workspace--position workspace1))
(pos2 (exwm-workspace--position workspace2)))
(if (or (not pos1) (not pos2) (= pos1 pos2))
(user-error "[EXWM] Cannot swap %s and %s" workspace1 workspace2)
(setf (elt exwm-workspace--list pos1) workspace2)
(setf (elt exwm-workspace--list pos2) workspace1)
;; Update the _NET_WM_DESKTOP property of each X window affected.
(dolist (pair exwm--id-buffer-alist)
(when (memq (buffer-local-value 'exwm--frame (cdr pair))
(list workspace1 workspace2))
(exwm-workspace--set-desktop (car pair))))
(xcb:flush exwm--connection)
(when (memq exwm-workspace--current (list workspace1 workspace2))
;; With the current workspace involved, lots of stuffs need refresh.
(set-frame-parameter exwm-workspace--current 'exwm-selected-window
(selected-window))
(exwm-workspace-switch exwm-workspace--current t))
(run-hooks 'exwm-workspace-list-change-hook))))
;;;###autoload
(defun exwm-workspace-move (workspace nth)
"Move WORKSPACE to the NTH position.
When called interactively, prompt for a workspace and move current one just
before it."
(interactive
(unless (and (eq major-mode 'exwm-mode) exwm--fullscreen) ;it's invisible
(list exwm-workspace--current
(exwm-workspace--position
(exwm-workspace--prompt-for-workspace "Move workspace to: ")))))
(let ((pos (exwm-workspace--position workspace))
flag start end index)
(if (= nth pos)
(user-error "[EXWM] Cannot move to same position")
;; Set if the current workspace is involved.
(setq flag (or (eq workspace exwm-workspace--current)
(eq (elt exwm-workspace--list nth)
exwm-workspace--current)))
;; Do the move.
(pop (nthcdr pos exwm-workspace--list))
(push workspace (nthcdr nth exwm-workspace--list))
;; Update the _NET_WM_DESKTOP property of each X window affected.
(setq start (min pos nth)
end (max pos nth))
(dolist (pair exwm--id-buffer-alist)
(setq index (exwm-workspace--position
(buffer-local-value 'exwm--frame (cdr pair))))
(unless (or (< index start) (> index end))
(exwm-workspace--set-desktop (car pair))))
(when flag
;; With the current workspace involved, lots of stuffs need refresh.
(set-frame-parameter exwm-workspace--current 'exwm-selected-window
(selected-window))
(exwm-workspace-switch exwm-workspace--current t))
(run-hooks 'exwm-workspace-list-change-hook))))
;;;###autoload
(defun exwm-workspace-add (&optional index)
"Add a workspace as the INDEX-th workspace, or the last one if INDEX is nil.
INDEX must not exceed the current number of workspaces."
(interactive)
(run-with-idle-timer
0 nil
(lambda (index)
(if (and index
;; No need to move if it's the last one.
(< index (exwm-workspace--count)))
(exwm-workspace-move (make-frame) index)
(make-frame)))
index)
(when (active-minibuffer-window)
(abort-recursive-edit)))
;;;###autoload
(defun exwm-workspace-delete (&optional frame-or-index)
"Delete the workspace FRAME-OR-INDEX."
(interactive)
(run-with-idle-timer 0 nil #'delete-frame
;; We should not simply delete the selected frame
;; since it can be e.g. a floating frame.
(if frame-or-index
(exwm-workspace--workspace-from-frame-or-index
frame-or-index)
exwm-workspace--current))
(when (active-minibuffer-window)
(abort-recursive-edit)))
(defun exwm-workspace--on-focus-in () (defun exwm-workspace--on-focus-in ()
"Handle unexpected frame switch." "Handle unexpected frame switch."
;; `focus-in-hook' is run by `handle-switch-frame'. ;; `focus-in-hook' is run by `handle-switch-frame'.
(unless (eq this-command #'handle-switch-frame) (unless (eq this-command #'handle-switch-frame)
(let ((index (cl-position (selected-frame) exwm-workspace--list))) (let ((index (exwm-workspace--position (selected-frame))))
(exwm--log "Focus on workspace %s" index) (exwm--log "Focus on workspace %s" index)
(when (and index (/= index exwm-workspace-current-index)) (when (and index (/= index exwm-workspace-current-index))
(exwm--log "Workspace was switched unexpectedly") (exwm--log "Workspace was switched unexpectedly")
@ -384,7 +570,7 @@ The optional FORCE option is for internal use only."
(xcb:+request exwm--connection (xcb:+request exwm--connection
(make-instance 'xcb:ewmh:set-_NET_WM_DESKTOP (make-instance 'xcb:ewmh:set-_NET_WM_DESKTOP
:window id :window id
:data (cl-position exwm--frame exwm-workspace--list))))) :data (exwm-workspace--position exwm--frame)))))
(defvar exwm-floating-border-width) (defvar exwm-floating-border-width)
(defvar exwm-floating-border-color) (defvar exwm-floating-border-color)
@ -392,33 +578,22 @@ The optional FORCE option is for internal use only."
(declare-function exwm-layout--show "exwm-layout.el" (id &optional window)) (declare-function exwm-layout--show "exwm-layout.el" (id &optional window))
(declare-function exwm-layout--hide "exwm-layout.el" (id)) (declare-function exwm-layout--hide "exwm-layout.el" (id))
(declare-function exwm-layout--refresh "exwm-layout.el") (declare-function exwm-layout--refresh "exwm-layout.el")
(declare-function exwm-layout--other-buffer-predicate "exwm-layout.el" (buffer)) (declare-function exwm-layout--other-buffer-predicate "exwm-layout.el"
(buffer))
;;;###autoload ;;;###autoload
(defun exwm-workspace-move-window (index &optional id) (defun exwm-workspace-move-window (frame-or-index &optional id)
"Move window ID to workspace INDEX." "Move window ID to workspace FRAME-OR-INDEX."
(interactive (interactive (list
(list (exwm-workspace--prompt-for-workspace "Move to: ")))
(progn (let ((frame (exwm-workspace--workspace-from-frame-or-index frame-or-index)))
(exwm-workspace--update-switch-history)
(let* ((history-add-new-input nil) ;prevent modifying history
(idx (read-from-minibuffer
"Workspace: " (elt exwm-workspace--switch-history
exwm-workspace-current-index)
exwm-workspace--switch-map nil
`(exwm-workspace--switch-history
. ,(1+ exwm-workspace-current-index)))))
(cl-position idx exwm-workspace--switch-history :test #'equal)))))
(unless id (setq id (exwm--buffer->id (window-buffer)))) (unless id (setq id (exwm--buffer->id (window-buffer))))
(unless (and (<= 0 index) (< index exwm-workspace-number))
(user-error "[EXWM] Workspace index out of range: %d" index))
(with-current-buffer (exwm--id->buffer id) (with-current-buffer (exwm--id->buffer id)
(let ((frame (elt exwm-workspace--list index)))
(unless (eq exwm--frame frame) (unless (eq exwm--frame frame)
(unless exwm-workspace-show-all-buffers (unless exwm-workspace-show-all-buffers
(let ((name (replace-regexp-in-string "^\\s-*" "" (buffer-name)))) (let ((name (replace-regexp-in-string "^\\s-*" "" (buffer-name))))
(exwm-workspace-rename-buffer (exwm-workspace-rename-buffer
(if (= index exwm-workspace-current-index) (if (eq frame exwm-workspace--current)
name name
(concat " " name))))) (concat " " name)))))
(setq exwm--frame frame) (setq exwm--frame frame)
@ -435,7 +610,7 @@ The optional FORCE option is for internal use only."
:x x :y y)) :x x :y y))
(xcb:flush exwm--connection) (xcb:flush exwm--connection)
(if (exwm-workspace--minibuffer-own-frame-p) (if (exwm-workspace--minibuffer-own-frame-p)
(when (= index exwm-workspace-current-index) (when (eq frame exwm-workspace--current)
(select-frame-set-input-focus exwm--floating-frame) (select-frame-set-input-focus exwm--floating-frame)
(exwm-layout--refresh)) (exwm-layout--refresh))
;; The frame needs to be recreated since it won't use the ;; The frame needs to be recreated since it won't use the
@ -491,18 +666,18 @@ The optional FORCE option is for internal use only."
(delete-frame old-frame) (delete-frame old-frame)
(set-window-dedicated-p window t) (set-window-dedicated-p window t)
(exwm-layout--show id window)) (exwm-layout--show id window))
(if (/= index exwm-workspace-current-index) (if (not (eq frame exwm-workspace--current))
(make-frame-visible new-frame) (make-frame-visible new-frame)
(select-frame-set-input-focus new-frame) (select-frame-set-input-focus new-frame)
(redisplay)))) (redisplay))))
;; Update the 'exwm-selected-window' frame parameter. ;; Update the 'exwm-selected-window' frame parameter.
(when (/= index exwm-workspace-current-index) (when (not (eq frame exwm-workspace--current))
(with-current-buffer (exwm--id->buffer id) (with-current-buffer (exwm--id->buffer id)
(set-frame-parameter frame 'exwm-selected-window (set-frame-parameter frame 'exwm-selected-window
(frame-root-window (frame-root-window
exwm--floating-frame))))) exwm--floating-frame)))))
;; Move the X window container. ;; Move the X window container.
(if (= index exwm-workspace-current-index) (if (eq frame exwm-workspace--current)
(set-window-buffer (get-buffer-window (current-buffer) t) (set-window-buffer (get-buffer-window (current-buffer) t)
(other-buffer)) (other-buffer))
(bury-buffer) (bury-buffer)
@ -566,7 +741,7 @@ The optional FORCE option is for internal use only."
(select-frame-set-input-focus exwm--floating-frame) (select-frame-set-input-focus exwm--floating-frame)
(select-window (frame-root-window exwm--floating-frame))) (select-window (frame-root-window exwm--floating-frame)))
;; On another workspace. ;; On another workspace.
(exwm-workspace-move-window exwm-workspace-current-index (exwm-workspace-move-window exwm-workspace--current
exwm--id)) exwm--id))
;; Ordinary buffer. ;; Ordinary buffer.
(switch-to-buffer buffer-or-name))))) (switch-to-buffer buffer-or-name)))))
@ -802,115 +977,43 @@ The optional FORCE option is for internal use only."
(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--init () (defun exwm-workspace--add-frame-as-workspace (frame)
"Initialize workspace module." "Configure frame FRAME to be treated as a workspace."
(cl-assert (and (< 0 exwm-workspace-number) (>= 10 exwm-workspace-number))) (cond
;; Prevent unexpected exit ((exwm-workspace--workspace-p frame)
(setq confirm-kill-emacs #'exwm-workspace--confirm-kill-emacs) (exwm--log "Frame `%s' is already a workspace" frame))
(if (not (exwm-workspace--minibuffer-own-frame-p)) ((not (display-graphic-p frame))
;; Initialize workspaces with minibuffers. (exwm--log "Frame `%s' is not graphical" frame))
(progn ((not (string-equal (slot-value exwm--connection 'display)
(setq exwm-workspace--list (frame-list)) (frame-parameter frame 'display)))
(when (< 1 (length exwm-workspace--list)) (exwm--log "Frame `%s' is on a different DISPLAY (%S instead of %S)"
;; Exclude the initial frame. frame
(dolist (i exwm-workspace--list) (frame-parameter frame 'display)
(unless (frame-parameter i 'window-id) (slot-value exwm--connection 'display)))
(setq exwm-workspace--list (delq i exwm-workspace--list)))) ((frame-parameter frame 'unsplittable)
(cl-assert (= 1 (length exwm-workspace--list))) ;; We create floating frames with the "unsplittable" parameter set.
(setq exwm-workspace--client ;; Though it may not be a floating frame, we won't treat an
(frame-parameter (car exwm-workspace--list) 'client)) ;; unsplittable frame as a workspace anyway.
(let ((f (car exwm-workspace--list))) (exwm--log "Frame `%s' is floating" frame))
;; Remove the possible internal border. (t
(set-frame-parameter f 'internal-border-width 0) (exwm--log "Adding frame `%s' as workspace" frame)
;; Prevent user from deleting this frame by accident. (setq exwm-workspace--list (nconc exwm-workspace--list (list frame))
(set-frame-parameter f 'client nil))) exwm-workspace--current frame)
;; Create remaining frames. (let ((outer-id (string-to-number (frame-parameter frame
(dotimes (_ (1- exwm-workspace-number))
(nconc exwm-workspace--list
(list (make-frame '((window-system . x)
(internal-border-width . 0)))))))
;; Initialize workspaces without minibuffers.
(let ((old-frames (frame-list)))
(setq exwm-workspace--minibuffer
(make-frame '((window-system . x) (minibuffer . only)
(left . 10000) (right . 10000)
(width . 0) (height . 0)
(internal-border-width . 0)
(client . nil))))
;; Remove/hide existing frames.
(dolist (f old-frames)
(if (frame-parameter f 'client)
(progn
(unless exwm-workspace--client
(setq exwm-workspace--client (frame-parameter f 'client)))
(make-frame-invisible f))
(when (eq 'x (framep f)) ;do not delete the initial frame.
(delete-frame f)))))
;; This is the only usable minibuffer frame.
(setq default-minibuffer-frame exwm-workspace--minibuffer)
(let ((outer-id (string-to-number
(frame-parameter exwm-workspace--minibuffer
'outer-window-id))) 'outer-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-container
container)
(xcb:+request exwm--connection
(make-instance 'xcb:CreateWindow
:depth 0 :wid container :parent exwm--root
:x -1 :y -1 :width 1 :height 1
: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 container
:data "Minibuffer container")))
(xcb:+request exwm--connection
(make-instance 'xcb:ReparentWindow
:window outer-id :parent container :x 0 :y 0))
;; Attach event listener for monitoring the frame
(xcb:+request exwm--connection
(make-instance 'xcb:ChangeWindowAttributes
:window outer-id
:value-mask xcb:CW:EventMask
:event-mask xcb:EventMask:StructureNotify))
(xcb:+event exwm--connection 'xcb:ConfigureNotify
#'exwm-workspace--on-ConfigureNotify))
;; Show/hide minibuffer / echo area when they're active/inactive.
(add-hook 'minibuffer-setup-hook #'exwm-workspace--on-minibuffer-setup)
(add-hook 'minibuffer-exit-hook #'exwm-workspace--on-minibuffer-exit)
(setq exwm-workspace--timer
(run-with-idle-timer 0 t #'exwm-workspace--on-echo-area-dirty))
(add-hook 'echo-area-clear-hook #'exwm-workspace--on-echo-area-clear)
;; Create workspace frames.
(dotimes (_ exwm-workspace-number)
(push (make-frame `((window-system . x)
(minibuffer . ,(minibuffer-window
exwm-workspace--minibuffer))
(internal-border-width . 0)
(client . nil)))
exwm-workspace--list))
;; The default behavior of `display-buffer' (indirectly called by
;; `minibuffer-completion-help') is not correct here.
(cl-pushnew '(exwm-workspace--display-buffer) display-buffer-alist
:test #'equal))
;; Handle unexpected frame switch.
(add-hook 'focus-in-hook #'exwm-workspace--on-focus-in)
;; Prevent `other-buffer' from selecting already displayed EXWM buffers.
(modify-all-frames-parameters
'((buffer-predicate . exwm-layout--other-buffer-predicate)))
;; Configure workspaces
(dolist (i exwm-workspace--list)
(let ((outer-id (string-to-number (frame-parameter i 'outer-window-id)))
(container (xcb:generate-id exwm--connection)) (container (xcb:generate-id exwm--connection))
(workspace (xcb:generate-id exwm--connection))) (workspace (xcb:generate-id exwm--connection)))
;; Save window IDs ;; Save window IDs
(set-frame-parameter i 'exwm-outer-id outer-id) (set-frame-parameter frame 'exwm-outer-id outer-id)
(set-frame-parameter i 'exwm-container container) (set-frame-parameter frame 'exwm-container container)
(set-frame-parameter i 'exwm-workspace workspace) (set-frame-parameter frame 'exwm-workspace workspace)
;; Copy RandR frame parameters from the first workspace to
;; prevent potential problems. The values do not matter here as
;; they'll be updated by the RandR module later.
(let ((w (car exwm-workspace--list)))
(dolist (param '(exwm-randr-output
exwm-geometry))
(set-frame-parameter frame param (frame-parameter w param))))
(xcb:+request exwm--connection (xcb:+request exwm--connection
(make-instance 'xcb:CreateWindow (make-instance 'xcb:CreateWindow
:depth 0 :wid workspace :parent exwm--root :depth 0 :wid workspace :parent exwm--root
@ -939,44 +1042,225 @@ The optional FORCE option is for internal use only."
:window workspace :window workspace
:data :data
(format "EXWM workspace %d" (format "EXWM workspace %d"
(cl-position i exwm-workspace--list)))) (exwm-workspace--position frame))))
(xcb:+request exwm--connection (xcb:+request exwm--connection
(make-instance 'xcb:ewmh:set-_NET_WM_NAME (make-instance 'xcb:ewmh:set-_NET_WM_NAME
:window container :window container
:data :data
(format "EXWM workspace %d frame container" (format "EXWM workspace %d frame container"
(cl-position i exwm-workspace--list))))) (exwm-workspace--position frame)))))
(xcb:+request exwm--connection (xcb:+request exwm--connection
(make-instance 'xcb:ReparentWindow (make-instance 'xcb:ReparentWindow
:window outer-id :parent container :x 0 :y 0)) :window outer-id :parent container :x 0 :y 0))
(xcb:+request exwm--connection (xcb:+request exwm--connection
(make-instance 'xcb:MapWindow :window container)) (make-instance 'xcb:MapWindow :window container))
(xcb:+request exwm--connection (xcb:+request exwm--connection
(make-instance 'xcb:MapWindow :window workspace)))) (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 ;; Delay making the workspace fullscreen until Emacs becomes idle
(advice-add 'x-create-frame :around #'exwm-workspace--x-create-frame) (run-with-idle-timer 0 nil
;; Set _NET_NUMBER_OF_DESKTOPS (it's currently fixed). `(lambda ()
(set-frame-parameter ,frame
'fullscreen 'fullboth)))
;; Update EWMH properties.
(exwm-workspace--update-ewmh-props)
(exwm-workspace-switch frame t)
(run-hooks 'exwm-workspace-list-change-hook))))
(defun exwm-workspace--remove-frame-as-workspace (frame)
"Stop treating frame FRAME as a workspace."
(cond
((not (exwm-workspace--workspace-p frame))
(exwm--log "Frame `%s' is not a workspace" frame))
((= 1 (exwm-workspace--count))
;; FIXME: When using dedicated minibuffer frame, deleting the last
;; frame hangs Emacs.
(user-error "[EXWM] Cannot remove last workspace"))
(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.
;; FIXME (ch11ng): Which direction is "left"?
(nextw (elt exwm-workspace--list (+ index (if lastp -1 +1)))))
;; Need to remove the workspace from the list in order for
;; the correct calculation of indexes.
(setq exwm-workspace--list (delete frame exwm-workspace--list))
;; 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))))
;; Update the _NET_WM_DESKTOP property of each X window affected.
(dolist (pair exwm--id-buffer-alist)
(when (<= (1- index)
(exwm-workspace--position (buffer-local-value 'exwm--frame
(cdr pair))))
(exwm-workspace--set-desktop (car pair))))
;; If the current workspace is deleted, switch to next one.
(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)
(run-hooks 'exwm-workspace-list-change-hook))))
(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.
(xcb:+request exwm--connection (xcb:+request exwm--connection
(make-instance 'xcb:ewmh:set-_NET_NUMBER_OF_DESKTOPS (make-instance 'xcb:ewmh:set-_NET_NUMBER_OF_DESKTOPS
:window exwm--root :data exwm-workspace-number)) :window exwm--root :data num-workspaces))
;; Set _NET_DESKTOP_GEOMETRY. ;; Set _NET_DESKTOP_GEOMETRY.
(exwm-workspace--set-desktop-geometry) (exwm-workspace--set-desktop-geometry)
;; Set _NET_DESKTOP_VIEWPORT (we don't support large desktop). ;; Set _NET_DESKTOP_VIEWPORT (we don't support large desktop).
(xcb:+request exwm--connection (xcb:+request exwm--connection
(make-instance 'xcb:ewmh:set-_NET_DESKTOP_VIEWPORT (make-instance 'xcb:ewmh:set-_NET_DESKTOP_VIEWPORT
:window exwm--root :window exwm--root
:data (make-vector (* 2 exwm-workspace-number) 0))) :data (make-vector (* 2 num-workspaces) 0)))
;; Update and set _NET_WORKAREA. ;; Update and set _NET_WORKAREA.
(exwm-workspace--update-workareas) (exwm-workspace--update-workareas)
;; Set _NET_VIRTUAL_ROOTS (it's currently fixed.) ;; Set _NET_VIRTUAL_ROOTS.
(xcb:+request exwm--connection (xcb:+request exwm--connection
(make-instance 'xcb:ewmh:set-_NET_VIRTUAL_ROOTS (make-instance 'xcb:ewmh:set-_NET_VIRTUAL_ROOTS
:window exwm--root :window exwm--root
:data (vconcat (mapcar :data (vconcat (mapcar
(lambda (i) (lambda (i)
(frame-parameter i 'exwm-workspace)) (frame-parameter i 'exwm-workspace))
exwm-workspace--list)))) exwm-workspace--list)))))
(xcb:flush exwm--connection))
(defun exwm-workspace--modify-all-x-frames-parameters (new-x-parameters)
"Modifies `window-system-default-frame-alist' for the X Window System.
NEW-X-PARAMETERS is an alist of frame parameters, merged into current
`window-system-default-frame-alist' for the X Window System. The parameters are
applied to all subsequently created X frames."
;; The parameters are modified in place; take current
;; ones or insert a new X-specific list.
(let ((x-parameters (or (assq 'x window-system-default-frame-alist)
(let ((new-x-parameters '(x)))
(push new-x-parameters
window-system-default-frame-alist)
new-x-parameters))))
(setf (cdr x-parameters)
(append new-x-parameters (cdr x-parameters)))))
(defun exwm-workspace--init ()
"Initialize workspace module."
;; Prevent unexpected exit
(setq confirm-kill-emacs #'exwm-workspace--confirm-kill-emacs)
(let ((initial-workspaces (frame-list)))
(if (not (exwm-workspace--minibuffer-own-frame-p))
;; Initialize workspaces with minibuffers.
(when (< 1 (length initial-workspaces))
;; Exclude the initial frame.
(dolist (i initial-workspaces)
(unless (frame-parameter i 'window-id)
(setq initial-workspaces (delq i initial-workspaces))))
(setq exwm-workspace--client
(frame-parameter (car exwm-workspace--list) 'client))
(let ((f (car initial-workspaces)))
;; Remove the possible internal border.
(set-frame-parameter f 'internal-border-width 0)
;; Prevent user from deleting the first frame by accident.
(set-frame-parameter f 'client nil)))
;; Initialize workspaces without minibuffers.
(setq exwm-workspace--minibuffer
(make-frame '((window-system . x) (minibuffer . only)
(left . 10000) (right . 10000)
(width . 0) (height . 0)
(internal-border-width . 0)
(client . nil))))
;; Remove/hide existing frames.
(dolist (f initial-workspaces)
(if (frame-parameter f 'client)
(progn
(unless exwm-workspace--client
(setq exwm-workspace--client (frame-parameter f 'client)))
(make-frame-invisible f))
(when (eq 'x (framep f)) ;do not delete the initial frame.
(delete-frame f))))
;; This is the only usable minibuffer frame.
(setq default-minibuffer-frame exwm-workspace--minibuffer)
(exwm-workspace--modify-all-x-frames-parameters
'((minibuffer . nil)))
(let ((outer-id (string-to-number
(frame-parameter exwm-workspace--minibuffer
'outer-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-container
container)
(xcb:+request exwm--connection
(make-instance 'xcb:CreateWindow
:depth 0 :wid container :parent exwm--root
:x -1 :y -1 :width 1 :height 1
: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 container
:data "Minibuffer container")))
(xcb:+request exwm--connection
(make-instance 'xcb:ReparentWindow
:window outer-id :parent container :x 0 :y 0))
;; Attach event listener for monitoring the frame
(xcb:+request exwm--connection
(make-instance 'xcb:ChangeWindowAttributes
:window outer-id
:value-mask xcb:CW:EventMask
:event-mask xcb:EventMask:StructureNotify))
(xcb:+event exwm--connection 'xcb:ConfigureNotify
#'exwm-workspace--on-ConfigureNotify))
;; Show/hide minibuffer / echo area when they're active/inactive.
(add-hook 'minibuffer-setup-hook #'exwm-workspace--on-minibuffer-setup)
(add-hook 'minibuffer-exit-hook #'exwm-workspace--on-minibuffer-exit)
(setq exwm-workspace--timer
(run-with-idle-timer 0 t #'exwm-workspace--on-echo-area-dirty))
(add-hook 'echo-area-clear-hook #'exwm-workspace--on-echo-area-clear)
;; Recreate frames with the external minibuffer set.
(setq initial-workspaces
(mapcar
(lambda (_)
(make-frame '((window-system . x)
(internal-border-width . 0)
(client . nil))))
initial-workspaces))
;; The default behavior of `display-buffer' (indirectly called by
;; `minibuffer-completion-help') is not correct here.
(cl-pushnew '(exwm-workspace--display-buffer) display-buffer-alist
:test #'equal))
;; Handle unexpected frame switch.
(add-hook 'focus-in-hook #'exwm-workspace--on-focus-in)
;; Prevent `other-buffer' from selecting already displayed EXWM buffers.
(modify-all-frames-parameters
'((buffer-predicate . exwm-layout--other-buffer-predicate)))
;; Create remaining workspaces.
(dotimes (_ (- exwm-workspace-number (length initial-workspaces)))
(nconc initial-workspaces (list (make-frame '((window-system . x)
(internal-border-width . 0)
(client . nil))))))
;; Configure workspaces
(dolist (i initial-workspaces)
(exwm-workspace--add-frame-as-workspace i)))
(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)
;; Make new frames create new workspaces.
(add-hook 'after-make-frame-functions
#'exwm-workspace--add-frame-as-workspace)
(add-hook 'delete-frame-functions
#'exwm-workspace--remove-frame-as-workspace)
;; Switch to the first workspace ;; Switch to the first workspace
(exwm-workspace-switch 0 t)) (exwm-workspace-switch 0 t))
@ -997,7 +1281,11 @@ The optional FORCE option is for internal use only."
(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."
@ -1006,7 +1294,7 @@ The optional FORCE option is for internal use only."
(set-frame-parameter i 'fullscreen 'fullboth)) (set-frame-parameter i 'fullscreen 'fullboth))
;; Wait until all workspace frames are resized. ;; Wait until all workspace frames are resized.
(with-timeout (1) (with-timeout (1)
(while (< exwm-workspace--fullscreen-frame-count exwm-workspace-number) (while (< exwm-workspace--fullscreen-frame-count (exwm-workspace--count))
(accept-process-output nil 0.1))) (accept-process-output nil 0.1)))
(setq exwm-workspace--fullscreen-frame-count nil)) (setq exwm-workspace--fullscreen-frame-count nil))

27
exwm.el
View file

@ -28,14 +28,15 @@
;; Overview ;; Overview
;; -------- ;; --------
;; EXWM (Emacs X Window Manager) is a full-featured tiling X window manager for ;; EXWM (Emacs X Window Manager) is a full-featured tiling X window manager
;; Emacs built on top of XELB. It features: ;; for Emacs built on top of [XELB](https://github.com/ch11ng/xelb).
;; It features:
;; + Fully keyboard-driven operations ;; + Fully keyboard-driven operations
;; + Hybrid layout modes (tiling & stacking) ;; + Hybrid layout modes (tiling & stacking)
;; + Workspace support ;; + Dynamic workspace support
;; + ICCCM/EWMH compliance ;; + ICCCM/EWMH compliance
;; ++ (Optional) RandR (multi-monitor) support ;; + (Optional) RandR (multi-monitor) support
;; ++ (Optional) system tray ;; + (Optional) Builtin system tray
;; Installation & configuration ;; Installation & configuration
;; ---------------------------- ;; ----------------------------
@ -325,6 +326,17 @@
id (slot-value obj 'window) id (slot-value obj 'window)
data (slot-value (slot-value obj 'data) 'data32)) data (slot-value (slot-value obj 'data) 'data32))
(cond (cond
;; _NET_NUMBER_OF_DESKTOPS.
((= type xcb:Atom:_NET_NUMBER_OF_DESKTOPS)
(let ((current (exwm-workspace--count))
(requested (elt data 0)))
;; Only allow increasing/decreasing the workspace number by 1.
(cond
((< current requested)
(make-frame))
((and (> current requested)
(> current 1))
(delete-frame (car (last exwm-workspace--list)))))))
;; _NET_CURRENT_DESKTOP. ;; _NET_CURRENT_DESKTOP.
((= type xcb:Atom:_NET_CURRENT_DESKTOP) ((= type xcb:Atom:_NET_CURRENT_DESKTOP)
(exwm-workspace-switch (elt data 0))) (exwm-workspace-switch (elt data 0)))
@ -438,10 +450,9 @@
;; FIXME: check (may require other properties set) ;; FIXME: check (may require other properties set)
(when (memq xcb:Atom:_NET_WM_STATE_DEMANDS_ATTENTION props) (when (memq xcb:Atom:_NET_WM_STATE_DEMANDS_ATTENTION props)
(when (= action xcb:ewmh:_NET_WM_STATE_ADD) (when (= action xcb:ewmh:_NET_WM_STATE_ADD)
(let ((idx (cl-position exwm--frame exwm-workspace--list))) (unless (eq exwm--frame exwm-workspace--current)
(unless (= idx exwm-workspace-current-index)
(set-frame-parameter exwm--frame 'exwm--urgency t) (set-frame-parameter exwm--frame 'exwm--urgency t)
(setq exwm-workspace--switch-history-outdated t)))) (setq exwm-workspace--switch-history-outdated t)))
;; xcb:ewmh:_NET_WM_STATE_REMOVE? ;; xcb:ewmh:_NET_WM_STATE_REMOVE?
;; xcb:ewmh:_NET_WM_STATE_TOGGLE? ;; xcb:ewmh:_NET_WM_STATE_TOGGLE?
) )