From 18fc95def2bc6a7c920967ada698d965c07cfd3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Medra=C3=B1o=20Calvo?= Date: Sun, 17 Jul 2016 12:00:00 +0000 Subject: [PATCH 01/18] Minor cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * exwm-workspace.el (exwm-workspace--update-workareas): Use `make-list' instead of looping. 2016-07-16 Adrián Medraño Calvo --- exwm-workspace.el | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/exwm-workspace.el b/exwm-workspace.el index 6228d99..82c054f 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -178,8 +178,7 @@ Value nil means to use the default position which is fixed at bottom, while (list (vector x y width height)))))) ;; Fall back to use the screen size. (let ((workarea (vector 0 0 root-width root-height))) - (dotimes (_ exwm-workspace-number) - (push workarea workareas)))) + (setq workareas (make-list exwm-workspace-number workarea)))) ;; Exclude areas occupied by struts. (dolist (struts exwm-workspace--struts) (setq edge (aref struts 0) From 983fd468dc8acb910e25178379750dec3300d35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Medra=C3=B1o=20Calvo?= Date: Sun, 17 Jul 2016 12:00:00 +0000 Subject: [PATCH 02/18] Add missing declarations * exwm-systemtray.el : * exwm-manage.el : * exwm-layout.el : * exwm-input.el : * exwm-floating.el : * exwm-core.el : Add missing function declarations. --- exwm-core.el | 9 +++++++++ exwm-floating.el | 4 +--- exwm-input.el | 3 +++ exwm-layout.el | 8 ++++++++ exwm-manage.el | 5 +++++ exwm-systemtray.el | 7 +++++-- 6 files changed, 31 insertions(+), 5 deletions(-) diff --git a/exwm-core.el b/exwm-core.el index 39964d2..9f869fe 100644 --- a/exwm-core.el +++ b/exwm-core.el @@ -119,6 +119,15 @@ ;; _MOTIF_WM_HINTS (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" + (index &optional id)) + (defvar exwm-mode-map (let ((map (make-sparse-keymap))) (define-key map "\C-c\C-f" #'exwm-layout-set-fullscreen) diff --git a/exwm-floating.el b/exwm-floating.el index 9d4d948..b58e71a 100644 --- a/exwm-floating.el +++ b/exwm-floating.el @@ -67,13 +67,11 @@ 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" (id &optional window)) (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) "Make window ID floating." diff --git a/exwm-input.el b/exwm-input.el index 6f18fbf..67e9a74 100644 --- a/exwm-input.el +++ b/exwm-input.el @@ -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--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" + (index &optional force)) (defun exwm-input--update-focus () "Update input focus." diff --git a/exwm-layout.el b/exwm-layout.el index 85e186f..92ca677 100644 --- a/exwm-layout.el +++ b/exwm-layout.el @@ -155,7 +155,15 @@ (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-move-window "exwm-workspace.el" + (index &optional id)) ;;;###autoload (defun exwm-layout-set-fullscreen (&optional id) diff --git a/exwm-manage.el b/exwm-manage.el index 18a6795..01d00b1 100644 --- a/exwm-manage.el +++ b/exwm-manage.el @@ -89,7 +89,12 @@ corresponding buffer.") (declare-function exwm--update-struts "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-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-move-window "exwm-workspace.el" + (index &optional id)) (defun exwm-manage--manage-window (id) "Manage window ID." diff --git a/exwm-systemtray.el b/exwm-systemtray.el index db0e045..56e5fb8 100644 --- a/exwm-systemtray.el +++ b/exwm-systemtray.el @@ -65,6 +65,11 @@ You shall use the default value if using auto-hide minibuffer.") "The selection owner 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) "Embed an 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 (exwm--log "(System Tray) Unknown opcode message: %s" obj))))))) -(defvar exwm-workspace--current) - (defun exwm-systemtray--on-workspace-switch () "Reparent/Refresh the system tray in `exwm-workspace-switch-hook'." (unless (exwm-workspace--minibuffer-own-frame-p) From cd1372eaec2521ea163c505b38d36b1f787d1944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Medra=C3=B1o=20Calvo?= Date: Sun, 17 Jul 2016 12:00:00 +0000 Subject: [PATCH 03/18] Apply minibuffer in own frame configuration to created frames * exwm-workspace.el (exwm-workspace--modify-all-x-frames-parameters): Helper function to non-destructively modify `window-system-default-frame-alist'. (exwm-workspace--init): Use above function to default all frames to the shared minibuffer when `exwm-workspace--minibuffer-own-frame-p' is true. --- exwm-workspace.el | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/exwm-workspace.el b/exwm-workspace.el index 82c054f..b2138aa 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -797,6 +797,20 @@ The optional FORCE option is for internal use only." (defvar exwm-workspace--timer nil "Timer used to track echo area changes.") +(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." (cl-assert (and (< 0 exwm-workspace-number) (>= 10 exwm-workspace-number))) @@ -843,6 +857,8 @@ The optional FORCE option is for internal use only." (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))) @@ -883,8 +899,6 @@ The optional FORCE option is for internal use only." ;; 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)) From ed6a18a697bc1eb8bf09a12c04ffa3200a5248cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Medra=C3=B1o=20Calvo?= Date: Sun, 17 Jul 2016 12:00:00 +0000 Subject: [PATCH 04/18] Add `exwm-workspace--position' helper * exwm-workspace.el (exwm-workspace--position): New function. * exwm-layout.el (exwm-layout--refresh): * exwm-input.el (exwm-input--on-ButtonPress): * exwm-workspace.el (exwm-workspace--update-switch-history): * exwm.el (exwm--on-ClientMessage, exwm-workspace--init): Use it. --- exwm-input.el | 5 +++-- exwm-layout.el | 3 ++- exwm-workspace.el | 13 ++++++++++--- exwm.el | 2 +- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/exwm-input.el b/exwm-input.el index 67e9a74..d581e7e 100644 --- a/exwm-input.el +++ b/exwm-input.el @@ -179,6 +179,7 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.") (declare-function exwm-floating--start-moveresize "exwm-floating.el" (id &optional type)) +(declare-function exwm-workspace--position "exwm-workspace.el" (frame)) (defvar exwm-workspace--list) @@ -208,14 +209,14 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.") (if (memq frame exwm-workspace--list) ;; The X window is on another workspace (exwm-workspace-switch - (cl-position frame exwm-workspace--list)) + (exwm-workspace--position frame)) (with-current-buffer (window-buffer window) (when (and (eq major-mode 'exwm-mode) (not (eq exwm--frame exwm-workspace--current))) ;; The floating X window is on another workspace (exwm-workspace-switch - (cl-position exwm--frame exwm-workspace--list)))))) + (exwm-workspace--position exwm--frame)))))) ;; It has been reported that the `window' may have be deleted (if (window-live-p window) (select-window window) diff --git a/exwm-layout.el b/exwm-layout.el index 92ca677..db1aa0d 100644 --- a/exwm-layout.el +++ b/exwm-layout.el @@ -161,6 +161,7 @@ (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--position "exwm-workspace.el" (frame)) (declare-function exwm-workspace--set-fullscreen "exwm-workspace.el" (frame)) (declare-function exwm-workspace-move-window "exwm-workspace.el" (index &optional id)) @@ -346,7 +347,7 @@ selected by `other-buffer'." (if (eq frame exwm--frame) (exwm-layout--show exwm--id window) (exwm-workspace-move-window - (cl-position frame exwm-workspace--list) exwm--id)) + (exwm-workspace--position frame) exwm--id)) ;; Make sure this buffer is not displayed elsewhere. Note down ;; windows displaying an EXWM-buffer now displayed elsewhere; we ;; need to display with some other buffer there. diff --git a/exwm-workspace.el b/exwm-workspace.el index b2138aa..641f3f2 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -29,6 +29,13 @@ (defvar exwm-workspace-number 4 "Number of workspaces (1 ~ 10).") (defvar exwm-workspace--list nil "List of all workspaces (Emacs frames).") + +(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)) + (defvar exwm-workspace--switch-map (let ((map (make-sparse-keymap))) (define-key map [t] (lambda () (interactive))) @@ -72,7 +79,7 @@ (with-current-buffer (cdr i) (when exwm--frame (setf (aref not-empty - (cl-position exwm--frame exwm-workspace--list)) + (exwm-workspace--position exwm--frame)) t)))) (setq exwm-workspace--switch-history (mapcar @@ -948,13 +955,13 @@ applied to all subsequently created X frames." :window workspace :data (format "EXWM workspace %d" - (cl-position i exwm-workspace--list)))) + (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" - (cl-position i exwm-workspace--list))))) + (exwm-workspace--position i))))) (xcb:+request exwm--connection (make-instance 'xcb:ReparentWindow :window outer-id :parent container :x 0 :y 0)) diff --git a/exwm.el b/exwm.el index 814104a..4a6e9fd 100644 --- a/exwm.el +++ b/exwm.el @@ -438,7 +438,7 @@ ;; FIXME: check (may require other properties set) (when (memq xcb:Atom:_NET_WM_STATE_DEMANDS_ATTENTION props) (when (= action xcb:ewmh:_NET_WM_STATE_ADD) - (let ((idx (cl-position exwm--frame exwm-workspace--list))) + (let ((idx (exwm-workspace--position exwm--frame))) (unless (= idx exwm-workspace-current-index) (set-frame-parameter exwm--frame 'exwm--urgency t) (setq exwm-workspace--switch-history-outdated t)))) From 8e2da00b6e7e530a53b584184dc94b9366ae7c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Medra=C3=B1o=20Calvo?= Date: Sun, 17 Jul 2016 12:00:00 +0000 Subject: [PATCH 05/18] Add helper for counting number of workspaces * exwm-workspace.el (exwm-workspace--count): New function. * exwm-randr.el (exwm-randr--refresh): * exwm-workspace.el (exwm-workspace--switch-map) (exwm-workspace--update-switch-history, exwm-workspace-switch) (exwm-workspace-move-window, exwm-workspace--init) (exwm-workspace--post-init): Use it. --- exwm-randr.el | 6 +++--- exwm-workspace.el | 29 +++++++++++++++++------------ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/exwm-randr.el b/exwm-randr.el index 85daff2..ac3341d 100644 --- a/exwm-randr.el +++ b/exwm-randr.el @@ -55,11 +55,11 @@ "Normal hook run when the RandR module just refreshed.") (defvar exwm-workspace--fullscreen-frame-count) -(defvar exwm-workspace-number) (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--update-workareas "exwm-workspace.el" ()) (declare-function exwm-workspace--set-desktop-geometry "exwm-workspace.el" ()) (defun exwm-randr--refresh () @@ -97,7 +97,7 @@ (when exwm-workspace--fullscreen-frame-count ;; Not all workspaces are fullscreen; reset this counter. (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)) (geometry (lax-plist-get output-plist output)) (frame (elt exwm-workspace--list i))) diff --git a/exwm-workspace.el b/exwm-workspace.el index 641f3f2..a3f4c2e 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -36,6 +36,10 @@ 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)) + (defvar exwm-workspace--switch-map (let ((map (make-sparse-keymap))) (define-key map [t] (lambda () (interactive))) @@ -43,13 +47,13 @@ NIL if FRAME is not a workspace" (define-key map (int-to-string i) `(lambda () (interactive) - (when (< ,i exwm-workspace-number) + (when (< ,i (exwm-workspace--count)) (goto-history-element ,(1+ i)) (exit-minibuffer))))) (define-key map "\C-a" (lambda () (interactive) (goto-history-element 1))) (define-key map "\C-e" (lambda () (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-]" #'abort-recursive-edit) (define-key map "\C-j" #'exit-minibuffer) @@ -73,8 +77,9 @@ NIL if FRAME is not a workspace" "Update the history for switching workspace to reflect the latest status." (when exwm-workspace--switch-history-outdated (setq exwm-workspace--switch-history-outdated nil) - (let ((sequence (number-sequence 0 (1- exwm-workspace-number))) - (not-empty (make-vector exwm-workspace-number nil))) + (let* ((num (exwm-workspace--count)) + (sequence (number-sequence 0 (1- num))) + (not-empty (make-vector num nil))) (dolist (i exwm--id-buffer-alist) (with-current-buffer (cdr i) (when exwm--frame @@ -185,7 +190,7 @@ Value nil means to use the default position which is fixed at bottom, while (list (vector x y width height)))))) ;; Fall back to use the screen size. (let ((workarea (vector 0 0 root-width root-height))) - (setq workareas (make-list exwm-workspace-number workarea)))) + (setq workareas (make-list (exwm-workspace--count) workarea)))) ;; Exclude areas occupied by struts. (dolist (struts exwm-workspace--struts) (setq edge (aref struts 0) @@ -305,7 +310,7 @@ The optional FORCE option is for internal use only." . ,(1+ exwm-workspace-current-index))))) (cl-position idx exwm-workspace--switch-history :test #'equal))))) (when index - (unless (and (<= 0 index) (< index exwm-workspace-number)) + (unless (and (<= 0 index) (< index (exwm-workspace--count))) (user-error "[EXWM] Workspace index out of range: %d" index)) (when (or force (/= exwm-workspace-current-index index)) (let* ((frame (elt exwm-workspace--list index)) @@ -412,7 +417,7 @@ The optional FORCE option is for internal use only." . ,(1+ exwm-workspace-current-index))))) (cl-position idx exwm-workspace--switch-history :test #'equal))))) (unless id (setq id (exwm--buffer->id (window-buffer)))) - (unless (and (<= 0 index) (< index exwm-workspace-number)) + (unless (and (<= 0 index) (< index (exwm-workspace--count))) (user-error "[EXWM] Workspace index out of range: %d" index)) (with-current-buffer (exwm--id->buffer id) (let ((frame (elt exwm-workspace--list index))) @@ -827,12 +832,12 @@ applied to all subsequently created X frames." ;; Initialize workspaces with minibuffers. (progn (setq exwm-workspace--list (frame-list)) - (when (< 1 (length exwm-workspace--list)) + (when (< 1 (exwm-workspace--count)) ;; Exclude the initial frame. (dolist (i exwm-workspace--list) (unless (frame-parameter i 'window-id) (setq exwm-workspace--list (delq i exwm-workspace--list)))) - (cl-assert (= 1 (length exwm-workspace--list))) + (cl-assert (= 1 (exwm-workspace--count))) (setq exwm-workspace--client (frame-parameter (car exwm-workspace--list) 'client)) (let ((f (car exwm-workspace--list))) @@ -975,14 +980,14 @@ applied to all subsequently created X frames." ;; 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)) + :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-number) 0))) + :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.) @@ -1022,7 +1027,7 @@ applied to all subsequently created X frames." (set-frame-parameter i 'fullscreen 'fullboth)) ;; Wait until all workspace frames are resized. (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))) (setq exwm-workspace--fullscreen-frame-count nil)) From 90185457261eb77ac80609d5d219e0837e81af82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Medra=C3=B1o=20Calvo?= Date: Sun, 17 Jul 2016 12:00:00 +0000 Subject: [PATCH 06/18] New function exwm-workspace--workspace-p * exwm-workspace.el (exwm-workspace--workspace-p): New function. * exwm-input.el (exwm-input--on-ButtonPress): * exwm-workspace.el (exwm-workspace-switch): * exwm-layout.el (exwm-layout--refresh): Use it. --- exwm-input.el | 3 ++- exwm-layout.el | 5 +++-- exwm-workspace.el | 6 +++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/exwm-input.el b/exwm-input.el index d581e7e..22c7a36 100644 --- a/exwm-input.el +++ b/exwm-input.el @@ -180,6 +180,7 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.") (declare-function exwm-floating--start-moveresize "exwm-floating.el" (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) @@ -206,7 +207,7 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.") (unless (eq window (selected-window)) (setq frame (window-frame window)) (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 (exwm-workspace-switch (exwm-workspace--position frame)) diff --git a/exwm-layout.el b/exwm-layout.el index db1aa0d..4d85390 100644 --- a/exwm-layout.el +++ b/exwm-layout.el @@ -153,7 +153,6 @@ (xcb:flush exwm--connection)))) (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") @@ -282,6 +281,8 @@ selected by `other-buffer'." (defvar exwm-layout-show-all-buffers nil "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 () "Set _NET_CLIENT_LIST_STACKING." @@ -312,7 +313,7 @@ selected by `other-buffer'." covered-buffers ;EXWM-buffers covered by a new X window. vacated-windows ;Windows previously displaying EXWM-buffers. windows) - (if (not (memq frame exwm-workspace--list)) + (if (not (exwm-workspace--workspace-p frame)) (if (frame-parameter frame 'exwm-outer-id) ;; Refresh a floating frame (let ((window (frame-first-window frame))) diff --git a/exwm-workspace.el b/exwm-workspace.el index a3f4c2e..e2df37e 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -40,6 +40,10 @@ NIL if FRAME is not a workspace" "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)) + (defvar exwm-workspace--switch-map (let ((map (make-sparse-keymap))) (define-key map [t] (lambda () (interactive))) @@ -334,7 +338,7 @@ The optional FORCE option is for internal use only." :stack-mode xcb:StackMode:Above)))) (setq exwm-workspace--current frame 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. (set-frame-parameter (with-current-buffer (window-buffer) exwm--frame) From 35e1655dc58d5653d4c3192d12cb5bfe61aa6960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Medra=C3=B1o=20Calvo?= Date: Sun, 17 Jul 2016 12:00:00 +0000 Subject: [PATCH 07/18] Move defvars to the top * exwm-workspace.el (exwm-workspace--current) (exwm-workspace-current-index): Do it. --- exwm-workspace.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exwm-workspace.el b/exwm-workspace.el index e2df37e..4d8380d 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -29,6 +29,8 @@ (defvar exwm-workspace-number 4 "Number of workspaces (1 ~ 10).") (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. @@ -107,8 +109,6 @@ NIL if FRAME is not a workspace" 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 "Non-nil to show buffers on other workspaces.") (defvar exwm-workspace--minibuffer nil From 07120a0562e4a09e189899ef8523aba927cbea0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Medra=C3=B1o=20Calvo?= Date: Sun, 17 Jul 2016 12:00:00 +0000 Subject: [PATCH 08/18] Work with workspace frames instead of indices * exwm.el (exwm--on-ClientMessage): * exwm-workspace.el (exwm-workspace-switch) (exwm-workspace-move-window, exwm-workspace-switch-to-buffer): * exwm-layout.el (exwm-layout--refresh): * exwm-input.el (exwm-input--update-focus) (exwm-input--on-ButtonPress): Accept frame as well as workspace index as argument. * exwm-workspace.el (exwm-workspace--workspace-from-frame-or-index): New function. --- exwm-core.el | 2 +- exwm-input.el | 10 +-- exwm-layout.el | 6 +- exwm-manage.el | 2 +- exwm-workspace.el | 197 ++++++++++++++++++++++++---------------------- exwm.el | 7 +- 6 files changed, 114 insertions(+), 110 deletions(-) diff --git a/exwm-core.el b/exwm-core.el index 9f869fe..70a759c 100644 --- a/exwm-core.el +++ b/exwm-core.el @@ -126,7 +126,7 @@ (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" - (index &optional id)) + (frame-or-index &optional id)) (defvar exwm-mode-map (let ((map (make-sparse-keymap))) diff --git a/exwm-input.el b/exwm-input.el index 22c7a36..b9e4705 100644 --- a/exwm-input.el +++ b/exwm-input.el @@ -104,7 +104,7 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.") (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" - (index &optional force)) + (frame-or-index &optional force)) (defun exwm-input--update-focus () "Update input focus." @@ -120,7 +120,7 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.") (setq exwm-workspace--switch-history-outdated t) (force-mode-line-update) ;; 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-input--set-focus exwm--id) (when exwm--floating-frame @@ -209,15 +209,13 @@ It's updated in several occasions, and only used by `exwm-input--set-focus'.") (unless (eq frame exwm-workspace--current) (if (exwm-workspace--workspace-p frame) ;; The X window is on another workspace - (exwm-workspace-switch - (exwm-workspace--position frame)) + (exwm-workspace-switch frame) (with-current-buffer (window-buffer window) (when (and (eq major-mode 'exwm-mode) (not (eq exwm--frame exwm-workspace--current))) ;; The floating X window is on another workspace - (exwm-workspace-switch - (exwm-workspace--position exwm--frame)))))) + (exwm-workspace-switch exwm--frame))))) ;; It has been reported that the `window' may have be deleted (if (window-live-p window) (select-window window) diff --git a/exwm-layout.el b/exwm-layout.el index 4d85390..e3d1d74 100644 --- a/exwm-layout.el +++ b/exwm-layout.el @@ -160,10 +160,9 @@ (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--position "exwm-workspace.el" (frame)) (declare-function exwm-workspace--set-fullscreen "exwm-workspace.el" (frame)) (declare-function exwm-workspace-move-window "exwm-workspace.el" - (index &optional id)) + (frame-or-index &optional id)) ;;;###autoload (defun exwm-layout-set-fullscreen (&optional id) @@ -347,8 +346,7 @@ selected by `other-buffer'." (let ((window (car windows))) (if (eq frame exwm--frame) (exwm-layout--show exwm--id window) - (exwm-workspace-move-window - (exwm-workspace--position frame) exwm--id)) + (exwm-workspace-move-window frame exwm--id)) ;; Make sure this buffer is not displayed elsewhere. Note down ;; windows displaying an EXWM-buffer now displayed elsewhere; we ;; need to display with some other buffer there. diff --git a/exwm-manage.el b/exwm-manage.el index 01d00b1..d67929a 100644 --- a/exwm-manage.el +++ b/exwm-manage.el @@ -94,7 +94,7 @@ corresponding buffer.") (declare-function exwm-workspace--current-width "exwm-workspace.el") (declare-function exwm-workspace--set-desktop "exwm-workspace.el" (id)) (declare-function exwm-workspace-move-window "exwm-workspace.el" - (index &optional id)) + (frame-or-index &optional id)) (defun exwm-manage--manage-window (id) "Manage window ID." diff --git a/exwm-workspace.el b/exwm-workspace.el index 4d8380d..346e898 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -46,6 +46,19 @@ NIL if FRAME is not a workspace" "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 (let ((map (make-sparse-keymap))) (define-key map [t] (lambda () (interactive))) @@ -297,87 +310,85 @@ Value nil means to use the default position which is fixed at bottom, while "Normal hook run after switching workspace.") ;;;###autoload -(defun exwm-workspace-switch (index &optional force) - "Switch to workspace INDEX. Query for INDEX if it's not specified. +(defun exwm-workspace-switch (frame-or-index &optional force) + "Switch to workspace INDEX. Query for FRAME-OR-INDEX if it's not specified. The optional FORCE option is for internal use only." (interactive (list (unless (and (eq major-mode 'exwm-mode) exwm--fullscreen) ;it's invisible (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))))) - (when index - (unless (and (<= 0 index) (< index (exwm-workspace--count))) - (user-error "[EXWM] Workspace index out of range: %d" index)) - (when (or force (/= exwm-workspace-current-index index)) - (let* ((frame (elt exwm-workspace--list index)) - (workspace (frame-parameter frame 'exwm-workspace)) - (window (frame-parameter frame 'exwm-selected-window))) - (unless (window-live-p window) - (setq window (frame-selected-window frame))) - ;; Raise the workspace container. - (xcb:+request exwm--connection - (make-instance 'xcb:ConfigureWindow - :window workspace - :value-mask xcb:ConfigWindow:StackMode - :stack-mode xcb:StackMode:Above)) - ;; Raise X windows with struts set if there's no fullscreen X window. - (unless (buffer-local-value 'exwm--fullscreen (window-buffer window)) - (dolist (pair exwm-workspace--id-struts-alist) - (xcb:+request exwm--connection - (make-instance 'xcb:ConfigureWindow - :window (car pair) - :value-mask xcb:ConfigWindow:StackMode - :stack-mode xcb:StackMode:Above)))) - (setq exwm-workspace--current frame - exwm-workspace-current-index index) - (unless (exwm-workspace--workspace-p (selected-frame)) - ;; Save the floating frame window selected on the previous workspace. - (set-frame-parameter (with-current-buffer (window-buffer) - exwm--frame) - 'exwm-selected-window (selected-window))) - (select-window window) - (set-frame-parameter frame 'exwm-selected-window nil) - ;; Close the (possible) active minibuffer - (when (active-minibuffer-window) - (run-with-idle-timer 0 nil (lambda () (abort-recursive-edit)))) - (if (not (exwm-workspace--minibuffer-own-frame-p)) - (setq default-minibuffer-frame frame) - ;; Resize/reposition the minibuffer frame + (let* ((current-idx (exwm-workspace--position exwm-workspace--current)) + (history-add-new-input nil) ;prevent modifying history + (history-idx (read-from-minibuffer + "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))))) + (let* ((frame (exwm-workspace--workspace-from-frame-or-index frame-or-index)) + (index (exwm-workspace--position frame)) + (workspace (frame-parameter frame 'exwm-workspace)) + (window (frame-parameter frame 'exwm-selected-window))) + (when (or force (not (eq frame exwm-workspace--current))) + (unless (window-live-p window) + (setq window (frame-selected-window frame))) + ;; Raise the workspace container. + (xcb:+request exwm--connection + (make-instance 'xcb:ConfigureWindow + :window workspace + :value-mask xcb:ConfigWindow:StackMode + :stack-mode xcb:StackMode:Above)) + ;; Raise X windows with struts set if there's no fullscreen X window. + (unless (buffer-local-value 'exwm--fullscreen (window-buffer window)) + (dolist (pair exwm-workspace--id-struts-alist) (xcb:+request exwm--connection - (make-instance 'xcb:ReparentWindow - :window - (frame-parameter exwm-workspace--minibuffer - 'exwm-container) - :parent (frame-parameter frame 'exwm-workspace) - :x 0 :y 0)) - (exwm-workspace--resize-minibuffer-frame)) - ;; Hide windows in other workspaces by preprending a space - (unless exwm-workspace-show-all-buffers - (dolist (i exwm--id-buffer-alist) - (with-current-buffer (cdr i) - (let ((name (replace-regexp-in-string "^\\s-*" "" - (buffer-name)))) - (exwm-workspace-rename-buffer (if (eq frame exwm--frame) - name - (concat " " name))))))) - ;; Update demands attention flag - (set-frame-parameter frame 'exwm--urgency nil) - ;; Update switch workspace history - (setq exwm-workspace--switch-history-outdated t) - ;; Set _NET_CURRENT_DESKTOP. + (make-instance 'xcb:ConfigureWindow + :window (car pair) + :value-mask xcb:ConfigWindow:StackMode + :stack-mode xcb:StackMode:Above)))) + (setq exwm-workspace--current frame + exwm-workspace-current-index index) + (unless (exwm-workspace--workspace-p (selected-frame)) + ;; Save the floating frame window selected on the previous workspace. + (set-frame-parameter (with-current-buffer (window-buffer) + exwm--frame) + 'exwm-selected-window (selected-window))) + (select-window window) + (set-frame-parameter frame 'exwm-selected-window nil) + ;; Close the (possible) active minibuffer + (when (active-minibuffer-window) + (run-with-idle-timer 0 nil (lambda () (abort-recursive-edit)))) + (if (not (exwm-workspace--minibuffer-own-frame-p)) + (setq default-minibuffer-frame frame) + ;; Resize/reposition the minibuffer frame (xcb:+request exwm--connection - (make-instance 'xcb:ewmh:set-_NET_CURRENT_DESKTOP - :window exwm--root :data index)) - (xcb:flush exwm--connection)) - (run-hooks 'exwm-workspace-switch-hook)))) + (make-instance 'xcb:ReparentWindow + :window + (frame-parameter exwm-workspace--minibuffer + 'exwm-container) + :parent (frame-parameter frame 'exwm-workspace) + :x 0 :y 0)) + (exwm-workspace--resize-minibuffer-frame)) + ;; Hide windows in other workspaces by preprending a space + (unless exwm-workspace-show-all-buffers + (dolist (i exwm--id-buffer-alist) + (with-current-buffer (cdr i) + (let ((name (replace-regexp-in-string "^\\s-*" "" + (buffer-name)))) + (exwm-workspace-rename-buffer (if (eq frame exwm--frame) + name + (concat " " name))))))) + ;; Update demands attention flag + (set-frame-parameter frame 'exwm--urgency nil) + ;; Update switch workspace history + (setq exwm-workspace--switch-history-outdated t) + ;; Set _NET_CURRENT_DESKTOP + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_CURRENT_DESKTOP + :window exwm--root :data index)) + (xcb:flush exwm--connection)) + (run-hooks 'exwm-workspace-switch-hook))) (defun exwm-workspace--on-focus-in () "Handle unexpected frame switch." @@ -406,30 +417,28 @@ The optional FORCE option is for internal use only." (declare-function exwm-layout--other-buffer-predicate "exwm-layout.el" (buffer)) ;;;###autoload -(defun exwm-workspace-move-window (index &optional id) - "Move window ID to workspace INDEX." +(defun exwm-workspace-move-window (frame-or-index &optional id) + "Move window ID to workspace FRAME-OR-INDEX." (interactive (list (progn (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 (and (<= 0 index) (< index (exwm-workspace--count))) - (user-error "[EXWM] Workspace index out of range: %d" index)) - (with-current-buffer (exwm--id->buffer id) - (let ((frame (elt exwm-workspace--list index))) + (let* ((current-idx (exwm-workspace--position exwm-workspace--current)) + (history-add-new-input nil) ;prevent modifying history + (history-idx (read-from-minibuffer + "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))))) + (let ((frame (exwm-workspace--workspace-from-frame-or-index frame-or-index))) + (unless id (setq id (exwm--buffer->id (window-buffer)))) + (with-current-buffer (exwm--id->buffer id) (unless (eq exwm--frame frame) (unless exwm-workspace-show-all-buffers (let ((name (replace-regexp-in-string "^\\s-*" "" (buffer-name)))) (exwm-workspace-rename-buffer - (if (= index exwm-workspace-current-index) + (if (eq frame exwm-workspace--current) name (concat " " name))))) (setq exwm--frame frame) @@ -446,7 +455,7 @@ The optional FORCE option is for internal use only." :x x :y y)) (xcb:flush exwm--connection) (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) (exwm-layout--refresh)) ;; The frame needs to be recreated since it won't use the @@ -502,18 +511,18 @@ The optional FORCE option is for internal use only." (delete-frame old-frame) (set-window-dedicated-p window t) (exwm-layout--show id window)) - (if (/= index exwm-workspace-current-index) + (if (not (eq frame exwm-workspace--current)) (make-frame-visible new-frame) (select-frame-set-input-focus new-frame) (redisplay)))) ;; 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) (set-frame-parameter frame 'exwm-selected-window (frame-root-window exwm--floating-frame))))) ;; 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) (other-buffer)) (bury-buffer) @@ -577,7 +586,7 @@ The optional FORCE option is for internal use only." (select-frame-set-input-focus exwm--floating-frame) (select-window (frame-root-window exwm--floating-frame))) ;; On another workspace. - (exwm-workspace-move-window exwm-workspace-current-index + (exwm-workspace-move-window exwm-workspace--current exwm--id)) ;; Ordinary buffer. (switch-to-buffer buffer-or-name))))) diff --git a/exwm.el b/exwm.el index 4a6e9fd..9151fdc 100644 --- a/exwm.el +++ b/exwm.el @@ -438,10 +438,9 @@ ;; FIXME: check (may require other properties set) (when (memq xcb:Atom:_NET_WM_STATE_DEMANDS_ATTENTION props) (when (= action xcb:ewmh:_NET_WM_STATE_ADD) - (let ((idx (exwm-workspace--position exwm--frame))) - (unless (= idx exwm-workspace-current-index) - (set-frame-parameter exwm--frame 'exwm--urgency t) - (setq exwm-workspace--switch-history-outdated t)))) + (unless (eq exwm--frame exwm-workspace--current) + (set-frame-parameter exwm--frame 'exwm--urgency t) + (setq exwm-workspace--switch-history-outdated t))) ;; xcb:ewmh:_NET_WM_STATE_REMOVE? ;; xcb:ewmh:_NET_WM_STATE_TOGGLE? ) From 2de2d42586a0a8866e7f5447d490a0c345c7ce10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Medra=C3=B1o=20Calvo?= Date: Sun, 17 Jul 2016 12:00:00 +0000 Subject: [PATCH 09/18] Factor out prompt for workspace * exwm-workspace.el (exwm-workspace--prompt-for-workspace): New function to interactively ask the user for a workspace. (exwm-workspace-switch, exwm-workspace-move-window): Use it. --- exwm-workspace.el | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/exwm-workspace.el b/exwm-workspace.el index 346e898..21a415e 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -92,6 +92,18 @@ NIL if FRAME is not a workspace" (defvar exwm-workspace--switch-history-outdated nil "Non-nil to indicate `exwm-workspace--switch-history' is outdated.") +(defun exwm-workspace--prompt-for-workspace () + "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 + "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 () "Update the history for switching workspace to reflect the latest status." (when exwm-workspace--switch-history-outdated @@ -317,15 +329,7 @@ The optional FORCE option is for internal use only." (interactive (list (unless (and (eq major-mode 'exwm-mode) exwm--fullscreen) ;it's invisible - (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 - "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))))) + (exwm-workspace--prompt-for-workspace)))) (let* ((frame (exwm-workspace--workspace-from-frame-or-index frame-or-index)) (index (exwm-workspace--position frame)) (workspace (frame-parameter frame 'exwm-workspace)) @@ -419,18 +423,7 @@ The optional FORCE option is for internal use only." ;;;###autoload (defun exwm-workspace-move-window (frame-or-index &optional id) "Move window ID to workspace FRAME-OR-INDEX." - (interactive - (list - (progn - (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 - "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))))) + (interactive (list (exwm-workspace--prompt-for-workspace))) (let ((frame (exwm-workspace--workspace-from-frame-or-index frame-or-index))) (unless id (setq id (exwm--buffer->id (window-buffer)))) (with-current-buffer (exwm--id->buffer id) From e4911181d317942e9946da4cbceeeb4c6ca98884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Medra=C3=B1o=20Calvo?= Date: Sun, 17 Jul 2016 12:00:00 +0000 Subject: [PATCH 10/18] Support swapping workspaces * exwm-workspace.el (exwm-workspace-swap-workspaces): New function to interchange the position of two workspaces. --- exwm-workspace.el | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/exwm-workspace.el b/exwm-workspace.el index 21a415e..8627432 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -394,6 +394,28 @@ The optional FORCE option is for internal use only." (xcb:flush exwm--connection)) (run-hooks 'exwm-workspace-switch-hook))) +;;;###autoload +(defun exwm-workspace-swap-workspaces (workspace1 workspace2) + "Swap position of WORKSPACE1 with that of WORKSPACE2." + (interactive + (unless (and (eq major-mode 'exwm-mode) exwm--fullscreen) ;it's invisible + (list + (exwm-workspace--prompt-for-workspace) + (exwm-workspace--prompt-for-workspace)))) + (let ((pos1 (exwm-workspace--position workspace1)) + (pos2 (exwm-workspace--position workspace2))) + (if (and pos1 pos2) + (progn + (setf (elt exwm-workspace--list pos1) workspace2) + (setf (elt exwm-workspace--list pos2) workspace1) + (cond + ((eq exwm-workspace--current workspace1) + (setq exwm-workspace-current-index pos2)) + ((eq exwm-workspace--current workspace2) + (setq exwm-workspace-current-index pos1)))) + (user-error "[EXWM] Frames are not workspaces")))) + + (defun exwm-workspace--on-focus-in () "Handle unexpected frame switch." ;; `focus-in-hook' is run by `handle-switch-frame'. From f4b8cc47c7a00456566a8a794cd9ee0bf06efaaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Medra=C3=B1o=20Calvo?= Date: Sun, 17 Jul 2016 12:00:00 +0000 Subject: [PATCH 11/18] Support moving workspaces * exwm-workspace.el (exwm-workspace-move-workspace): New function to move a workspace to a certain position. --- exwm-workspace.el | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/exwm-workspace.el b/exwm-workspace.el index 8627432..9bbbf2a 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -396,7 +396,7 @@ The optional FORCE option is for internal use only." ;;;###autoload (defun exwm-workspace-swap-workspaces (workspace1 workspace2) - "Swap position of WORKSPACE1 with that of WORKSPACE2." + "Interchange position of WORKSPACE1 with that of WORKSPACE2." (interactive (unless (and (eq major-mode 'exwm-mode) exwm--fullscreen) ;it's invisible (list @@ -415,6 +415,20 @@ The optional FORCE option is for internal use only." (setq exwm-workspace-current-index pos1)))) (user-error "[EXWM] Frames are not workspaces")))) +;;;###autoload +(defun exwm-workspace-move-workspace (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))))) + (let ((pos (exwm-workspace--position workspace))) + (if (= nth pos) + (user-error "[EXWM] Cannot move to same position") + (pop (nthcdr pos exwm-workspace--list)) + (push workspace (nthcdr nth exwm-workspace--list))))) (defun exwm-workspace--on-focus-in () "Handle unexpected frame switch." From 0fbc725de1b13572cfc7f4da58c89c576039f249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Medra=C3=B1o=20Calvo?= Date: Sun, 17 Jul 2016 12:00:00 +0000 Subject: [PATCH 12/18] 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. --- exwm-workspace.el | 368 ++++++++++++++++++++++++++++------------------ 1 file changed, 223 insertions(+), 145 deletions(-) diff --git a/exwm-workspace.el b/exwm-workspace.el index 9bbbf2a..2a11756 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -851,129 +851,41 @@ before it." (defvar exwm-workspace--timer nil "Timer used to track echo area changes.") -(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." - (cl-assert (and (< 0 exwm-workspace-number) (>= 10 exwm-workspace-number))) - ;; Prevent unexpected exit - (setq confirm-kill-emacs #'exwm-workspace--confirm-kill-emacs) - (if (not (exwm-workspace--minibuffer-own-frame-p)) - ;; Initialize workspaces with minibuffers. - (progn - (setq exwm-workspace--list (frame-list)) - (when (< 1 (exwm-workspace--count)) - ;; Exclude the initial frame. - (dolist (i exwm-workspace--list) - (unless (frame-parameter i 'window-id) - (setq exwm-workspace--list (delq i exwm-workspace--list)))) - (cl-assert (= 1 (exwm-workspace--count))) - (setq exwm-workspace--client - (frame-parameter (car exwm-workspace--list) 'client)) - (let ((f (car exwm-workspace--list))) - ;; Remove the possible internal border. - (set-frame-parameter f 'internal-border-width 0) - ;; Prevent user from deleting this frame by accident. - (set-frame-parameter f 'client nil)) - ;; Create remaining frames. - (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) - (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) - ;; Create workspace frames. - (dotimes (_ exwm-workspace-number) - (push (make-frame `((window-system . x) - (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))) +(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 i 'exwm-outer-id outer-id) - (set-frame-parameter i 'exwm-container container) - (set-frame-parameter i 'exwm-workspace workspace) + (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 @@ -1002,44 +914,208 @@ applied to all subsequently created X frames." :window workspace :data (format "EXWM workspace %d" - (exwm-workspace--position i)))) + (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 i))))) + (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)))) + (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) + "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." + (cl-assert (and (< 0 exwm-workspace-number) (>= 10 exwm-workspace-number))) + ;; 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. + (progn + (when (< 1 (exwm-workspace--count)) + ;; Exclude the initial frame. + (dolist (i initial-workspaces) + (unless (frame-parameter i 'window-id) + (setq initial-workspaces (delq i initial-workspaces)))) + (cl-assert (= 1 (length 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 this frame by accident. + (set-frame-parameter f 'client nil))) + ;; Create remaining frames. + (dotimes (_ (1- exwm-workspace-number)) + (nconc initial-workspaces + (list (make-frame '((window-system . x) + (internal-border-width . 0))))))) + ;; 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) + ;; Create workspace frames. + (dotimes (_ exwm-workspace-number) + (push (make-frame `((window-system . x) + (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 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) - ;; 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--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)))) + ;; 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 (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 :test #'equal)) (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 () "The second stage in the initialization of the workspace module." From d0797d03ded13aff09211ea77c6b37cccaf6ff12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Medra=C3=B1o=20Calvo?= Date: Sun, 17 Jul 2016 12:00:00 +0000 Subject: [PATCH 13/18] Remove `exwm-worspace-number' No longer fill initial workspace list, nor limit the number of workspaces to `exwm-worspace-number'. Users are free to create as many as they like by hitting 'C-x 5 2' or running `make-frame'. The initial workspace list can be set up by creating frames in a configuration file. For example, to start up with 4 workspaces: (dolist (i 3) (make-frame)) The interactive workspace switcher is improved to support selecting workspaces with a many-digits position. * exwm-workspace.el (exwm-workspace-number): Remove variable, as we no longer have a fixed number of workspaces. (exwm-workspace--switch-map) (exwm-workspace--switch-map-nth-prefix) (exwm-workspace--switch-map-select-nth): Improve support for selecting workspaces with multiple-digit positions (e.g. workspace number 12). (exwm-workspace--add-frame-as-workspace, exwm-workspace--init): Remove limit on number of workspaces. (exwm-workspace--init): Stop creating workspaces at startup. * exwm-config.el (exwm-config-default): Bind keys to namespaces 0-9 in the default configuration. --- exwm-config.el | 2 +- exwm-workspace.el | 99 ++++++++++++++++++++++++++++++----------------- 2 files changed, 64 insertions(+), 37 deletions(-) diff --git a/exwm-config.el b/exwm-config.el index d6e10f2..e1e5010 100644 --- a/exwm-config.el +++ b/exwm-config.el @@ -39,7 +39,7 @@ ;; 's-w': Switch workspace (exwm-input-set-key (kbd "s-w") #'exwm-workspace-switch) ;; 's-N': Switch to certain workspace - (dotimes (i exwm-workspace-number) + (dotimes (i 10) (exwm-input-set-key (kbd (format "s-%d" i)) `(lambda () (interactive) (exwm-workspace-switch ,i)))) ;; 's-&': Launch application diff --git a/exwm-workspace.el b/exwm-workspace.el index 2a11756..75fcc96 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -27,7 +27,6 @@ (require 'exwm-core) -(defvar exwm-workspace-number 4 "Number of workspaces (1 ~ 10).") (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.") @@ -63,12 +62,7 @@ NIL if FRAME is not a workspace" (let ((map (make-sparse-keymap))) (define-key map [t] (lambda () (interactive))) (dotimes (i 10) - (define-key map (int-to-string i) - `(lambda () - (interactive) - (when (< ,i (exwm-workspace--count)) - (goto-history-element ,(1+ i)) - (exit-minibuffer))))) + (define-key map (int-to-string i) #'exwm-workspace--switch-map-nth-prefix)) (define-key map "\C-a" (lambda () (interactive) (goto-history-element 1))) (define-key map "\C-e" (lambda () (interactive) @@ -318,6 +312,48 @@ Value nil means to use the default position which is fixed at bottom, while :stack-mode xcb:StackMode:Above)) (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* ((ev (this-command-keys-vector)) + (off (1- (length ev))) + (k (elt ev off)) + ;; 0 is ASCII 48. + (d (- k 48)) + ;; 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) ; Go ahead if there are enough + (floor (log num-workspaces 10))) ; digits to select any workspace. + (exwm-workspace--switch-map-select-nth n) + (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 "Normal hook run after switching workspace.") @@ -854,9 +890,6 @@ before it." (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)) @@ -1010,31 +1043,23 @@ applied to all subsequently created X frames." (defun exwm-workspace--init () "Initialize workspace module." - (cl-assert (and (< 0 exwm-workspace-number) (>= 10 exwm-workspace-number))) ;; 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. - (progn - (when (< 1 (exwm-workspace--count)) - ;; Exclude the initial frame. - (dolist (i initial-workspaces) - (unless (frame-parameter i 'window-id) - (setq initial-workspaces (delq i initial-workspaces)))) - (cl-assert (= 1 (length 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 this frame by accident. - (set-frame-parameter f 'client nil))) - ;; Create remaining frames. - (dotimes (_ (1- exwm-workspace-number)) - (nconc initial-workspaces - (list (make-frame '((window-system . x) - (internal-border-width . 0))))))) + (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) @@ -1092,12 +1117,14 @@ applied to all subsequently created X frames." (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) - (internal-border-width . 0) - (client . nil))) - exwm-workspace--list)) + ;; 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 From 73d890aad4e06cc722a00ed40532a7df07a83550 Mon Sep 17 00:00:00 2001 From: Chris Feng Date: Tue, 19 Jul 2016 10:24:23 +0800 Subject: [PATCH 14/18] Fix various minor issues ; Coding style fixes * exwm-core.el (exwm-mode-menu exwm-mode-map): Use `exwm-workspace--count'. * exwm-workspace.el (exwm-workspace--set-fullscreen) (exwm-workspace--on-focus-in, exwm-workspace--set-desktop): Use `exwm-workspace--position'. * exwm-workspace.el (exwm-workspace-swap, exwm-workspace-move): Renamed from `exwm-workspace-swap-workspace' and `exwm-workspace-move-workspace'. * exwm-workspace.el (exwm-workspace--update-ewmh-props): Update comments. * exwm-workspace.el (exwm-workspace--switch-map-nth-prefix): Fix a calculation. * exwm-workspace.el (exwm-workspace-switch): Fix a potential timer problem. * exwm-workspace.el (exwm-workspace-swap, exwm-workspace-move) (exwm-workspace--remove-frame-as-workspace): Update workspace and clients involved. * exwm-workspace.el (exwm-workspace--remove-frame-as-workspace): Remove workspace first. --- exwm-core.el | 2 +- exwm-layout.el | 2 +- exwm-manage.el | 2 +- exwm-workspace.el | 150 ++++++++++++++++++++++++++++++---------------- 4 files changed, 102 insertions(+), 54 deletions(-) diff --git a/exwm-core.el b/exwm-core.el index 70a759c..fd26d2c 100644 --- a/exwm-core.el +++ b/exwm-core.el @@ -205,7 +205,7 @@ (interactive) (exwm-workspace-switch ,i)) (/= ,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") diff --git a/exwm-layout.el b/exwm-layout.el index e3d1d74..667e2fa 100644 --- a/exwm-layout.el +++ b/exwm-layout.el @@ -304,7 +304,7 @@ selected by `other-buffer'." (make-instance 'xcb:ewmh:set-_NET_CLIENT_LIST_STACKING :window exwm--root :data (vconcat (append clients-other clients-iconic - clients clients-floating)))))) + clients clients-floating)))))) (defun exwm-layout--refresh () "Refresh layout." diff --git a/exwm-manage.el b/exwm-manage.el index d67929a..7a3a18b 100644 --- a/exwm-manage.el +++ b/exwm-manage.el @@ -515,7 +515,7 @@ border-width: %d; sibling: #x%x; stack-mode: %d" (< (abs width-delta) exwm-manage--width-delta-min)) (if (= 0 (logand value-mask - xcb:ConfigWindow:Height)) + xcb:ConfigWindow:Height)) t (< (abs height-delta) exwm-manage--height-delta-min)))))))) diff --git a/exwm-workspace.el b/exwm-workspace.el index 75fcc96..629f940 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -53,7 +53,8 @@ NIL if FRAME is not a workspace" (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))) + (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)))) @@ -62,7 +63,8 @@ NIL if FRAME is not a workspace" (let ((map (make-sparse-keymap))) (define-key map [t] (lambda () (interactive))) (dotimes (i 10) - (define-key map (int-to-string i) #'exwm-workspace--switch-map-nth-prefix)) + (define-key map (int-to-string i) + #'exwm-workspace--switch-map-nth-prefix)) (define-key map "\C-a" (lambda () (interactive) (goto-history-element 1))) (define-key map "\C-e" (lambda () (interactive) @@ -95,7 +97,8 @@ NIL if FRAME is not a workspace" "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))) + (workspace-idx (cl-position history-idx exwm-workspace--switch-history + :test #'equal))) (elt exwm-workspace--list workspace-idx))) (defun exwm-workspace--update-switch-history () @@ -270,7 +273,7 @@ Value nil means to use the default position which is fixed at bottom, while (defun exwm-workspace--set-fullscreen (frame) "Make frame FRAME fullscreen according to `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)) (container (frame-parameter frame 'exwm-container)) (workspace (frame-parameter frame 'exwm-workspace)) @@ -317,11 +320,8 @@ Value nil means to use the default position which is fixed at bottom, while PREFIX-DIGITS is a list of the digits introduced so far." (interactive) - (let* ((ev (this-command-keys-vector)) - (off (1- (length ev))) - (k (elt ev off)) - ;; 0 is ASCII 48. - (d (- k 48)) + (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) @@ -330,15 +330,16 @@ PREFIX-DIGITS is a list of the digits introduced so far." prefix-digits))) (n (+ pn d)) (num-workspaces (exwm-workspace--count))) - (if (= (length prefix-digits) ; Go ahead if there are enough - (floor (log num-workspaces 10))) ; digits to select any workspace. + (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)) - ))) + (exwm-workspace--switch-map-nth-prefix + ',(cons d prefix-digits))))) (dotimes (i 10) (define-key map (int-to-string i) cmd)) ;; Accept @@ -383,10 +384,10 @@ The optional FORCE option is for internal use only." (unless (buffer-local-value 'exwm--fullscreen (window-buffer window)) (dolist (pair exwm-workspace--id-struts-alist) (xcb:+request exwm--connection - (make-instance 'xcb:ConfigureWindow - :window (car pair) - :value-mask xcb:ConfigWindow:StackMode - :stack-mode xcb:StackMode:Above)))) + (make-instance 'xcb:ConfigureWindow + :window (car pair) + :value-mask xcb:ConfigWindow:StackMode + :stack-mode xcb:StackMode:Above)))) (setq exwm-workspace--current frame exwm-workspace-current-index index) (unless (exwm-workspace--workspace-p (selected-frame)) @@ -398,7 +399,10 @@ The optional FORCE option is for internal use only." (set-frame-parameter frame 'exwm-selected-window nil) ;; Close the (possible) active minibuffer (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)) (setq default-minibuffer-frame frame) ;; Resize/reposition the minibuffer frame @@ -428,10 +432,11 @@ The optional FORCE option is for internal use only." (make-instance 'xcb:ewmh:set-_NET_CURRENT_DESKTOP :window exwm--root :data index)) (xcb:flush exwm--connection)) - (run-hooks 'exwm-workspace-switch-hook))) + (run-hooks 'exwm-workspace-switch-hook))) + ;;;###autoload -(defun exwm-workspace-swap-workspaces (workspace1 workspace2) +(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 @@ -440,19 +445,24 @@ The optional FORCE option is for internal use only." (exwm-workspace--prompt-for-workspace)))) (let ((pos1 (exwm-workspace--position workspace1)) (pos2 (exwm-workspace--position workspace2))) - (if (and pos1 pos2) - (progn - (setf (elt exwm-workspace--list pos1) workspace2) - (setf (elt exwm-workspace--list pos2) workspace1) - (cond - ((eq exwm-workspace--current workspace1) - (setq exwm-workspace-current-index pos2)) - ((eq exwm-workspace--current workspace2) - (setq exwm-workspace-current-index pos1)))) - (user-error "[EXWM] Frames are not workspaces")))) + (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))))) ;;;###autoload -(defun exwm-workspace-move-workspace (workspace nth) +(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." @@ -461,16 +471,36 @@ before it." (list exwm-workspace--current (exwm-workspace--position (exwm-workspace--prompt-for-workspace))))) (let ((pos (exwm-workspace--position workspace))) + (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))))) + (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))))) (defun exwm-workspace--on-focus-in () "Handle unexpected frame switch." ;; `focus-in-hook' is run by `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) (when (and index (/= index exwm-workspace-current-index)) (exwm--log "Workspace was switched unexpectedly") @@ -482,7 +512,7 @@ before it." (xcb:+request exwm--connection (make-instance 'xcb:ewmh:set-_NET_WM_DESKTOP :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-color) @@ -490,7 +520,8 @@ before it." (declare-function exwm-layout--show "exwm-layout.el" (id &optional window)) (declare-function exwm-layout--hide "exwm-layout.el" (id)) (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 (defun exwm-workspace-move-window (frame-or-index &optional id) @@ -906,7 +937,8 @@ before it." (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))) + (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 @@ -965,7 +997,8 @@ before it." ;; Delay making the workspace fullscreen until Emacs becomes idle (run-with-idle-timer 0 nil `(lambda () - (set-frame-parameter ,frame 'fullscreen 'fullboth))) + (set-frame-parameter ,frame + 'fullscreen 'fullboth))) ;; Update EWMH properties. (exwm-workspace--update-ewmh-props) (exwm-workspace-switch frame t)))) @@ -984,16 +1017,24 @@ before it." ;; 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)))) - ;; 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)) + ;; 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. @@ -1004,7 +1045,7 @@ before it." (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). + ;; Set _NET_NUMBER_OF_DESKTOPS. (xcb:+request exwm--connection (make-instance 'xcb:ewmh:set-_NET_NUMBER_OF_DESKTOPS :window exwm--root :data num-workspaces)) @@ -1017,7 +1058,7 @@ before it." :data (make-vector (* 2 num-workspaces) 0))) ;; Update and set _NET_WORKAREA. (exwm-workspace--update-workareas) - ;; Set _NET_VIRTUAL_ROOTS (it's currently fixed.) + ;; Set _NET_VIRTUAL_ROOTS. (xcb:+request exwm--connection (make-instance 'xcb:ewmh:set-_NET_VIRTUAL_ROOTS :window exwm--root @@ -1036,7 +1077,8 @@ applied to all subsequently created X frames." ;; 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) + (push new-x-parameters + window-system-default-frame-alist) new-x-parameters)))) (setf (cdr x-parameters) (append new-x-parameters (cdr x-parameters))))) @@ -1084,14 +1126,16 @@ applied to all subsequently created X frames." (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-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 + :border-width 0 + :class xcb:WindowClass:CopyFromParent :visual 0 ;CopyFromParent :value-mask xcb:CW:OverrideRedirect :override-redirect 1)) @@ -1115,13 +1159,13 @@ applied to all subsequently created X frames." (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)) + (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) + (make-frame '((window-system . x) (internal-border-width . 0) (client . nil)))) initial-workspaces)) @@ -1141,8 +1185,10 @@ applied to all subsequently created X frames." ;; 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) + (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 (exwm-workspace-switch 0 t)) @@ -1164,8 +1210,10 @@ applied to all subsequently created X frames." :test #'equal)) (remove-hook 'focus-in-hook #'exwm-workspace--on-focus-in) (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)) + (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 () "The second stage in the initialization of the workspace module." From 622618ac6e401857b335b7e5dd1969cb9e1f948b Mon Sep 17 00:00:00 2001 From: Chris Feng Date: Tue, 19 Jul 2016 10:30:21 +0800 Subject: [PATCH 15/18] Improve the handling of workspaces * exwm-workspace.el (exwm-workspace--prompt-for-workspace): Add an optional argument to modify prompt. (exwm-workspace-switch, exwm-workspace-swap) (exwm-workspace-move, exwm-workspace-move-window): Use it. * exwm-workspace.el (exwm-workspace-number): Re-introduce the variable (now it stands for the initial workspace number). (exwm-workspace--init): Create remaining initial workspaces. * exwm-workspace.el (exwm-workspace-add, exwm-workspace-delete): New commands for adding/deleting workspaces. (exwm-workspace--switch-map): Add "+"/"-" to increase/descrease workspace number. * exwm-workspace.el (exwm-workspace-switch): Automatically add missing workspaces. * exwm.el (exwm--on-ClientMessage): Support _NET_NUMBER_OF_DESKTOPS client message for adjusting workspace number. --- exwm-workspace.el | 75 ++++++++++++++++++++++++++++++++++++++++------- exwm.el | 11 +++++++ 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/exwm-workspace.el b/exwm-workspace.el index 629f940..0843c27 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -27,6 +27,7 @@ (require 'exwm-core) +(defvar exwm-workspace-number 1 "Initial number of workspaces.") (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.") @@ -62,6 +63,8 @@ NIL if FRAME is not a workspace" (defvar exwm-workspace--switch-map (let ((map (make-sparse-keymap))) (define-key map [t] (lambda () (interactive))) + (define-key map "+" #'exwm-workspace-add) + (define-key map "-" #'exwm-workspace-delete) (dotimes (i 10) (define-key map (int-to-string i) #'exwm-workspace--switch-map-nth-prefix)) @@ -88,13 +91,14 @@ NIL if FRAME is not a workspace" (defvar exwm-workspace--switch-history-outdated nil "Non-nil to indicate `exwm-workspace--switch-history' is outdated.") -(defun exwm-workspace--prompt-for-workspace () +(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 - "Workspace: " (elt exwm-workspace--switch-history current-idx) + (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 @@ -359,14 +363,25 @@ PREFIX-DIGITS is a list of the digits introduced so far." "Normal hook run after switching workspace.") ;;;###autoload -(defun exwm-workspace-switch (frame-or-index &optional force) +(cl-defun exwm-workspace-switch (frame-or-index &optional force) "Switch to workspace INDEX. Query for FRAME-OR-INDEX if it's not specified. The optional FORCE option is for internal use only." (interactive (list (unless (and (eq major-mode 'exwm-mode) exwm--fullscreen) ;it's invisible - (exwm-workspace--prompt-for-workspace)))) + (exwm-workspace--prompt-for-workspace "Workspace [+/-]: ")))) + ;; Try to create workspace(s) when INDEX is out-of-range. + (when (and (integerp frame-or-index) + (>= frame-or-index (exwm-workspace--count))) + (run-with-idle-timer 0 nil + (lambda (times) + (dotimes (_ times) + (make-frame))) + ;; Create no more than 9 workspaces. + (min 9 + (1+ (- frame-or-index (exwm-workspace--count))))) + (cl-return-from exwm-workspace-switch)) (let* ((frame (exwm-workspace--workspace-from-frame-or-index frame-or-index)) (index (exwm-workspace--position frame)) (workspace (frame-parameter frame 'exwm-workspace)) @@ -440,9 +455,11 @@ The optional FORCE option is for internal use only." "Interchange position of WORKSPACE1 with that of WORKSPACE2." (interactive (unless (and (eq major-mode 'exwm-mode) exwm--fullscreen) ;it's invisible - (list - (exwm-workspace--prompt-for-workspace) - (exwm-workspace--prompt-for-workspace)))) + (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)) @@ -469,8 +486,8 @@ 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))))) - (let ((pos (exwm-workspace--position workspace))) + (exwm-workspace--position + (exwm-workspace--prompt-for-workspace "Move workspace to: "))))) (let ((pos (exwm-workspace--position workspace)) flag start end index) (if (= nth pos) @@ -496,6 +513,38 @@ before it." (selected-window)) (exwm-workspace-switch exwm-workspace--current t))))) +;;;###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 () "Handle unexpected frame switch." ;; `focus-in-hook' is run by `handle-switch-frame'. @@ -526,7 +575,8 @@ before it." ;;;###autoload (defun exwm-workspace-move-window (frame-or-index &optional id) "Move window ID to workspace FRAME-OR-INDEX." - (interactive (list (exwm-workspace--prompt-for-workspace))) + (interactive (list + (exwm-workspace--prompt-for-workspace "Move to: "))) (let ((frame (exwm-workspace--workspace-from-frame-or-index frame-or-index))) (unless id (setq id (exwm--buffer->id (window-buffer)))) (with-current-buffer (exwm--id->buffer id) @@ -1178,6 +1228,11 @@ applied to all subsequently created X frames." ;; 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))) diff --git a/exwm.el b/exwm.el index 9151fdc..f7d027e 100644 --- a/exwm.el +++ b/exwm.el @@ -325,6 +325,17 @@ id (slot-value obj 'window) data (slot-value (slot-value obj 'data) 'data32)) (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. ((= type xcb:Atom:_NET_CURRENT_DESKTOP) (exwm-workspace-switch (elt data 0))) From 4c9afc25b322d30b21eeea2cf3af7def0fc1dbb4 Mon Sep 17 00:00:00 2001 From: Chris Feng Date: Tue, 19 Jul 2016 10:33:11 +0800 Subject: [PATCH 16/18] Adapt dynamic workspace for floating module * exwm-workspace.el (exwm-workspace--add-frame-as-workspace): Fix the checking criteria of floating frames; Copy RandR frame parameters from the first workspace frame (rather than the selected one which can be a floating frame). * exwm-workspace.el (exwm-workspace--remove-frame-as-workspace): Check frame type first. --- exwm-workspace.el | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/exwm-workspace.el b/exwm-workspace.el index 0843c27..36502a2 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -981,7 +981,10 @@ INDEX must not exceed the current number of workspaces." frame (frame-parameter frame 'display) (slot-value exwm--connection 'display))) - ((frame-parameter frame 'exwm-floating) + ((frame-parameter frame 'unsplittable) + ;; We create floating frames with the "unsplittable" parameter set. + ;; Though it may not be a floating frame, we won't treat an + ;; unsplittable frame as a workspace anyway. (exwm--log "Frame `%s' is floating" frame)) (t (exwm--log "Adding frame `%s' as workspace" frame) @@ -995,12 +998,13 @@ INDEX must not exceed the current number of workspaces." (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))) + ;; 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 prev-workspace param)))) + (set-frame-parameter frame param (frame-parameter w param)))) (xcb:+request exwm--connection (make-instance 'xcb:CreateWindow :depth 0 :wid workspace :parent exwm--root @@ -1056,10 +1060,12 @@ INDEX must not exceed the current number of workspaces." (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)) + ((= 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)) From 2ebeec12570636e673d08c8b4d47e12ce0da048b Mon Sep 17 00:00:00 2001 From: Chris Feng Date: Tue, 19 Jul 2016 10:34:38 +0800 Subject: [PATCH 17/18] Adapt dynamic workspace for RandR module * exwm-workspace.el (exwm-workspace-list-change-hook): New hook run when the workspace list is modified. * exwm-randr.el (exwm-randr--init, exwm-randr--exit): * exwm-workspace.el (exwm-workspace-swap, exwm-workspace-move) (exwm-workspace--add-frame-as-workspace) (exwm-workspace--remove-frame-as-workspace): Use it. --- exwm-randr.el | 6 ++++-- exwm-workspace.el | 15 +++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/exwm-randr.el b/exwm-randr.el index ac3341d..709469a 100644 --- a/exwm-randr.el +++ b/exwm-randr.el @@ -154,10 +154,12 @@ ;; xcb:randr:NotifyMask:OutputProperty ;; 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 () - "Exit the RandR module.") + "Exit the RandR module." + (remove-hook 'exwm-workspace-list-change-hook #'exwm-randr--refresh)) (defun exwm-randr-enable () "Enable RandR support for EXWM." diff --git a/exwm-workspace.el b/exwm-workspace.el index 36502a2..d575058 100644 --- a/exwm-workspace.el +++ b/exwm-workspace.el @@ -449,6 +449,9 @@ The optional FORCE option is for internal use only." (xcb:flush exwm--connection)) (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) @@ -476,7 +479,8 @@ The optional FORCE option is for internal use only." ;; 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))))) + (exwm-workspace-switch exwm-workspace--current t)) + (run-hooks 'exwm-workspace-list-change-hook)))) ;;;###autoload (defun exwm-workspace-move (workspace nth) @@ -511,7 +515,8 @@ before it." ;; 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))))) + (exwm-workspace-switch exwm-workspace--current t)) + (run-hooks 'exwm-workspace-list-change-hook)))) ;;;###autoload (defun exwm-workspace-add (&optional index) @@ -1055,7 +1060,8 @@ INDEX must not exceed the current number of workspaces." 'fullscreen 'fullboth))) ;; Update EWMH properties. (exwm-workspace--update-ewmh-props) - (exwm-workspace-switch frame t)))) + (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." @@ -1096,7 +1102,8 @@ INDEX must not exceed the current number of workspaces." ;; Update EWMH properties. (exwm-workspace--update-ewmh-props) ;; Update switch history. - (setq exwm-workspace--switch-history-outdated t)))) + (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." From b51f3e65f15385da70b659ef093230913429572b Mon Sep 17 00:00:00 2001 From: Chris Feng Date: Tue, 19 Jul 2016 10:36:14 +0800 Subject: [PATCH 18/18] Mention dynamic workspace in various places * README.md: * exwm.el: Update README and comments. * exwm-core.el (exwm-mode-menu exwm-mode-map): Add menu entries for dynamic workspace feature. --- README.md | 19 +++++++++++-------- exwm-core.el | 8 ++++++-- exwm.el | 11 ++++++----- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7f918bd..103948c 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,20 @@ # Emacs X Window Manager -EXWM (Emacs X Window Manager) is a full-featured tiling X window manager for -Emacs built on top of [XELB](https://github.com/ch11ng/xelb). +EXWM (Emacs X Window Manager) is a full-featured tiling X window manager +for Emacs built on top of [XELB](https://github.com/ch11ng/xelb). It features: + Fully keyboard-driven operations + Hybrid layout modes (tiling & stacking) -+ Workspace support ++ Dynamic workspace support + ICCCM/EWMH compliance + (Optional) RandR (multi-monitor) support -+ (Optional) system tray ++ (Optional) Built-in system tray -Please check the [User Guide](https://github.com/ch11ng/exwm/wiki) -for more details. +Please check out the +[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 -(either from source or GNU ELPA). +**Note**: If you install EXWM from source, it's recommended to install +XELB also from source (otherwise install both from GNU ELPA). diff --git a/exwm-core.el b/exwm-core.el index fd26d2c..fe46c8b 100644 --- a/exwm-core.el +++ b/exwm-core.el @@ -193,8 +193,12 @@ "---" "*Workspace*" "---" - ["Move window to" exwm-workspace-move-window :keys "C-c C-m"] - ["Switch to buffer" exwm-workspace-switch-to-buffer] + ["Add workspace" exwm-workspace-add] + ["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] ;; Place this entry at bottom to avoid selecting others by accident. ("Switch to" :filter diff --git a/exwm.el b/exwm.el index f7d027e..b6bc164 100644 --- a/exwm.el +++ b/exwm.el @@ -28,14 +28,15 @@ ;; Overview ;; -------- -;; EXWM (Emacs X Window Manager) is a full-featured tiling X window manager for -;; Emacs built on top of XELB. It features: +;; EXWM (Emacs X Window Manager) is a full-featured tiling X window manager +;; for Emacs built on top of [XELB](https://github.com/ch11ng/xelb). +;; It features: ;; + Fully keyboard-driven operations ;; + Hybrid layout modes (tiling & stacking) -;; + Workspace support +;; + Dynamic workspace support ;; + ICCCM/EWMH compliance -;; ++ (Optional) RandR (multi-monitor) support -;; ++ (Optional) system tray +;; + (Optional) RandR (multi-monitor) support +;; + (Optional) Builtin system tray ;; Installation & configuration ;; ----------------------------