Compare commits

...

24 commits

Author SHA1 Message Date
68fb0ca2d4 perspective-exwm: add Published-At 2023-12-26 02:13:11 +03:00
01d51f5c92 ci: fix recipe 2023-02-18 16:58:50 +03:00
0f92705ffb docs: update README 2023-02-18 16:56:10 +03:00
11177968d7 chore: version & CI 2023-02-18 16:52:29 +03:00
7b0a70b449 feat: add cycling for all buffers 2023-02-18 16:46:47 +03:00
7d5f0922a3 chore: update copyright 2023-01-05 23:46:58 +03:00
c320f0967d fix: wrap exwm-workspace-switch in exwm--defer 2023-01-05 23:28:36 +03:00
8afdbf894a docs: add issue 2022-02-08 12:10:26 +03:00
541946caa0 chore: update copyright year 2022-01-25 22:39:09 +03:00
c758a6d0c0 fix: correct perspective name for EXWM floating window 2022-01-23 18:29:15 +03:00
c6e99b8457 fix: correct perspective name after exwm-workspace-add 2022-01-23 18:11:41 +03:00
3a4d382a74 docs: add MELPA 2022-01-03 12:13:09 +03:00
42d5c44a1a fix: error -> user-error 2022-01-03 12:09:02 +03:00
26179187bb
Merge pull request #1 from syohex/byte-compile-warnings
Fix byte-compile warnings
2021-12-25 17:11:28 +03:00
Shohei YOSHIDA
f17263cac1
Fix byte-compile warnings 2021-12-25 22:55:32 +09:00
ebe6f50be2 chore: update docs & bump version 2021-12-24 23:21:32 +03:00
5b13dc7ecf fix: stabilize copying and moving perspectives 2021-12-24 22:56:07 +03:00
6d3caf4cd0 docs: better docs 2021-12-24 22:26:49 +03:00
c66fd7d763 feat: set custom faces for everything 2021-12-24 22:15:13 +03:00
1d42f244c6 docs: fix wording 2021-12-23 23:53:22 +03:00
c0a1348866 feat: checkdoc, simplify persp-init-frame 2021-12-23 23:45:40 +03:00
a9c2680d89 feat: override persp-set-buffer 2021-12-21 10:50:33 +03:00
4b48997f78 chore: bump version 2021-12-08 13:32:16 +03:00
57650131c6 feat: fix persp-set-buffer & perspective-exwm-assign-window 2021-12-08 13:17:28 +03:00
3 changed files with 394 additions and 81 deletions

30
.github/workflows/melpazoid.yml vendored Normal file
View file

@ -0,0 +1,30 @@
# melpazoid <https://github.com/riscy/melpazoid> build checks.
# If your package is on GitHub, enable melpazoid's checks by copying this file
# to .github/workflows/melpazoid.yml and modifying RECIPE and EXIST_OK below.
name: melpazoid
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v1
with: { python-version: 3.9 }
- name: Install
run: |
python -m pip install --upgrade pip
sudo apt-get install emacs && emacs --version
git clone https://github.com/riscy/melpazoid.git ~/melpazoid
pip install ~/melpazoid
- name: Run
env:
LOCAL_REPO: ${{ github.workspace }}
# RECIPE is your recipe as written for MELPA:
RECIPE: (perspective-exwm :repo "SqrtMinusOne/perspective-exwm" :fetcher github)
# set this to false (or remove it) if the package isn't on MELPA:
EXIST_OK: false
run: echo $GITHUB_REF && make -C ~/melpazoid

View file

@ -1,12 +1,14 @@
#+TITLE: perspective-exwm
[[https://melpa.org/#/perspective-exwm][file:https://melpa.org/packages/perspective-exwm-badge.svg]]
A couple of tricks and fixes to make using [[https://github.com/ch11ng/exwm][EXWM]] and [[https://github.com/nex3/perspective-el][perspective.el]] a better experience.
* Installation
While this package isn't available anywhere but here, you can install it directly from the repo, e.g.:
This package is available on MELPA. Install it however you usually install packages, I use [[https://github.com/jwiegley/use-package][use-package]] and [[https://github.com/raxod502/straight.el][straight.el]]:
#+begin_src emacs-lisp
(use-package perspective-exwm
:straight (:host github :repo "SqrtMinusOne/perspective-ewxm.el"))
:straight t)
#+end_src
Or clone the repository, add the package to the =load-path= and load it with =require=.
@ -20,12 +22,14 @@ The package provides a minor mode, =perspective-exwm-mode=, which is meant to be
#+end_src
* Usage and details
- =perspective-exwm-mode=
- =perspective-exwm-mode=\\
The mode does a couple of things:
- advises away a bug with half-killing the current perspective when closing a floating window. I haven't tested this as thoroughly, so run =M-x perspective-exwm-revive-perspectives= if the problem arises anyway.
- adjusts the name of the inital perspective in the new workspace. It tries to get the name from the =perspective-exwm-override-initial-name= variable and falls back to `main-<index>`.
- advises away a bug with half-killing the current perspective when closing a floating window. +I haven't tested this as thoroughly+ I haven't run into this issue for nearly a month, so it seems to be fixed. But there's =M-x perspective-exwm-revive-perspectives= if the problem arises anyway.
- fixes a bug with running =persp-set-buffer= on an EXWM buffer that was moved between workspaces by advising =persp-buffer-in-other-p=.
- fixes a bug with =persp-set-buffer= copying all the perspectives from other workspaces to the current one.
- adjusts the name of the initial perspective in the new workspace. It tries to get the name from the =perspective-exwm-override-initial-name= variable and fallbacks to =main-<index>=.
I have the following in my configuration:
For the last point, I have the following in my configuration:
#+begin_src emacs-lisp
(setq perspective-exwm-override-initial-name
'((0 . "misc")
@ -35,9 +39,9 @@ The package provides a minor mode, =perspective-exwm-mode=, which is meant to be
(4 . "dev")))
#+end_src
This also serves a purpose, because otherwise there are issues with multiple perspectives sharing the same scratch buffer.
- =M-x perspective-exwm-cycle-exwm-buffers-forward=, =perspective-exwm-cycle-exwm-buffers-backward=
Cycles EXWM buffers in the current perspective.
Having distinct perspective names between frames also serves a purpose, because otherwise there are issues with multiple perspectives sharing the same scratch buffer.
- =M-x perspective-exwm-cycle-exwm-buffers-forward=, =perspective-exwm-cycle-exwm-buffers-backward=\\
Cycle EXWM buffers in the current perspective.
[[./img/cycle-buffers.png]]
@ -45,11 +49,37 @@ The package provides a minor mode, =perspective-exwm-mode=, which is meant to be
Set =perspective-exwm-get-exwm-buffer-name= to customize the displayed name, by default it's =exwm-class-name=.
- =M-x perspective-exwm-switch-perspective=
- =M-x perspective-exwm-cycle-all-buffers-forward=, =perspective-exwm-cycle-exwm-all-backward=\\
The same as above, but not restricted to EXWM buffers.
- =M-x perspective-exwm-switch-perspective=\\
Select a perspective from the list of all perspectives on all workspaces.
[[./img/switch-perspective.png]]
- =M-x perspective-exwm-copy-to-workspace=
- =M-x perspective-exwm-copy-to-workspace=\\
Copy the current perspective to another EXWM workspace.
- =M-x perspective-exwm-move-to-workspace=
- =M-x perspective-exwm-move-to-workspace=\\
Move the current perspective to another EXWM workspace.
- =perspective-exwm-assign-windows=\\
A handy function to move the current window to a given workspace and/or perspective. Example usage:
#+begin_src emacs-lisp
(defun my/exwm-configure-window ()
(interactive)
(pcase exwm-class-name
((or "Firefox" "Nightly")
(perspective-exwm-assign-window
:workspace-index 2
:persp-name "browser"))
("Alacritty"
(perspective-exwm-assign-window
:persp-name "term"))
((or "VK" "Slack" "Discord" "TelegramDesktop")
(perspective-exwm-assign-window
:workspace-index 3
:persp-name "comms"))))
(add-hook 'exwm-manage-finish-hook #'my/exwm-configure-window)
#+end_src
* Known issues
- =perspective-exwm-move-to-workspace= kills X windows in the perspective it tries to move. Have no idea how to fix this at the moment.

View file

@ -1,12 +1,14 @@
;;; perspective-exwm.el --- Better integration for perspective.el and EXWM -*- lexical-binding: t -*-
;; Copyright (C) 2021 Korytov Pavel
;; Copyright (C) 2021-2023 Korytov Pavel
;; Copyright (C) 2008-2020 Natalie Weizenbaum <nex342@gmail.com>
;; Author: Korytov Pavel <thexcloud@gmail.com>
;; Maintainer: Korytov Pavel <thexcloud@gmail.com>
;; Version: 0.1.0
;; Version: 0.2.0
;; Package-Requires: ((emacs "27.1") (burly "0.2-pre") (exwm "0.26") (perspective "2.17"))
;; Homepage: https://github.com/SqrtMinusOne/perspective-exwm.el
;; Published-At: 2021-12-01
;; This file is NOT part of GNU Emacs.
@ -25,7 +27,25 @@
;;; Commentary:
;; TODO
;; A couple of tricks and fixes to make using EXWM and perspective.el
;; a better experience.
;;
;; Most importantly, this package provides `perspective-exwm-mode',
;; which fixes certain annoying issues between the two packages. Take
;; a look at its docstring for more info.
;;
;; Other useful functions are:
;; - `perspective-exwm-cycle-exwm-buffers-backward' and
;; `perspective-exwm-cycle-exwm-buffers-backward'
;; - `perspective-exwm-cycle-all-buffers-backward' and
;; `perspective-exwm-cycle-all-buffers-forward'
;; - `perspective-exwm-switch-perspective'
;; - `perspective-exwm-copy-to-workspace' and
;; `perspective-exwm-move-to-workspace'
;;
;; Finally, take a look at the package README at
;; <https://github.com/SqrtMinusOne/perspective-exwm.el> for more
;; information.
;;; Code:
(require 'burly)
@ -34,37 +54,144 @@
(require 'cl-lib)
(defgroup perspective-exwm nil
"Integration between perspective.el and EXWM"
"Integration between perspective.el and EXWM."
:group 'frames)
(defun perspective-exwm--get-class ()
"A function to return the current EXWM class name."
exwm-class-name)
(defun perspective-exwm--get-title ()
"A function to return the current EXWM window title."
exwm-title)
(defcustom perspective-exwm-get-exwm-buffer-name #'perspective-exwm--get-class
"A function to get the EXWM buffer title.
(defcustom perspective-exwm-get-buffer-name #'perspective-exwm--get-class
"Retrieve buffer name for the cycle commands.
Meant to be ran in the context of the target buffer, e.g. with
`with-current-buffer'."
`with-current-buffer'.
The two default options are:
- `perspective-exwm--get-class' - returns EXWM class
- `perspective-exwm--get-title' - returns EXWM title"
:group 'perspective-exwm
:type 'function
:options '(perspective-exwm--get-class perspective-exwm--get-title))
(defcustom perspective-exwm-cycle-max-message-length
(- (frame-width) 10)
"Maximum length of the message displayed by the cycle commands."
:group 'perspective-exwm
:type 'integer)
(defcustom perspective-exwm-override-initial-name nil
"Set initial perspective name for a particular EXWM workspace."
:group 'perspective-exwm
:type '(alist :key-type (integer :tag "EXWM workspace index")
:value-type (string :tag "Initial perspective name")))
(defun perspective-exwm--cycle-exwm-buffers (dir)
"Cycle EXWM buffers in the current perspective.
(defface perspective-exwm-cycle-current-face
'((t (:inherit warning)))
"Face for the current buffer in the buffer cycling message."
:group 'perspective-exwm)
DIR is either 'forward or 'backward. A buffer is skipped if it is
(defface perspective-exwm-cycle-skip-face
'((t (:inherit persp-selected-face)))
"Face for the buffer to skip in the buffer cycling message."
:group 'perspective-exwm)
(defface perspective-exwm-current-workspace-face
'((t (:inherit warning)))
"Face for the current workspace number.
Used in `perspective-exwm-switch-perspective'."
:group 'perspective-exwm)
(defface perspective-exwm-selected-pespective-face
'((t (:inherit persp-selected-face)))
"Face for the selected perspective.
Used in `perspective-exwm-switch-perspective'."
:group 'perspective-exwm)
(defun perspective-exwm--cycle-get-message (all-buffers cycle-buffers)
"Return the display message for the buffer cycling commands.
ALL-BUFFERS is the list of all buffers in the current perspective.
CYCLE-BUFFERS are the buffers to cycle through."
;; Iterate over all buffers
(cl-loop with seen-current = nil
for buf in all-buffers
for name = (with-current-buffer buf
(or (funcall perspective-exwm-get-buffer-name)
(buffer-name)))
for is-current = (eq (current-buffer) buf)
for is-skip = (not (member buf cycle-buffers))
if is-current do (setq seen-current t)
if is-current
collect (concat "[" (propertize name 'face 'perspective-exwm-cycle-current-face) "] ") into current-list
else if is-skip
collect (concat "[" (propertize name 'face 'perspective-exwm-cycle-skip-face) "] ") into skip-list
else if seen-current
collect (format " %s " name) into after-list
else
collect (format " %s " name) into before-list
;; 4 list:
;; - current-list - current buffers
;; - skip-list - buffers displayed in other windows
;; - before-list - buffers before current
;; - after-list - buffers after current
;; We want to display them in the following order:
;; skip-list before-list current-list after-list
;; And trim before-list and after-list to fit the message
;; length; that means trimming the end of before-list and
;; the beginning of after-list.
finally return
(let* ((skip-msg (mapconcat #'identity skip-list ""))
(current-msg (mapconcat #'identity current-list ""))
(len (+ (length skip-msg) (length current-msg) 8))
(before-stack (reverse before-list))
(after-stack after-list))
;; Length of nil is 0 :'(
(cl-loop for before-elem-len = (if before-stack (length (car before-stack)) 10000)
for after-elem-len = (if after-stack (length (car after-stack)) 10000)
while (and (or before-stack after-stack)
(< (+ len (min before-elem-len after-elem-len)) perspective-exwm-cycle-max-message-length))
for before = (when (and before-stack
(< (+ len before-elem-len) perspective-exwm-cycle-max-message-length))
(pop before-stack))
if before collect before into before-msg-list
if before do (setq len (+ len before-elem-len))
for after = (when (and after-stack
(< (+ len after-elem-len) perspective-exwm-cycle-max-message-length))
(pop after-stack))
if after concat after into after-msg
if after do (setq len (+ len after-elem-len))
finally return
(concat
skip-msg
(when before-stack
(format " (%s) "
(propertize (number-to-string (length before-stack))
'face 'perspective-exwm-cycle-skip-face)))
(mapconcat #'identity (reverse before-msg-list) "")
current-msg
after-msg
(when after-stack
(format " (%s) "
(propertize (number-to-string (length after-stack))
'face 'perspective-exwm-cycle-skip-face))))))))
(defun perspective-exwm--cycle-exwm-buffers (dir &optional all)
"Cycle buffers in the current perspective.
DIR is either 'forward or 'backward. A buffer is skipped if it is
already displayed in some other window of the current
perspective. The buffer name comes from
`perspective-exwm-get-exwm-buffer-name'.
perspective. The buffer name comes from
`perspective-exwm-get-buffer-name'.
If ALL is nil, then cycle only EXWM buffers. Otherwise, cycle
all.
The function prints out the state to the messages. The current
buffer after the switch is highlighted with `warning', skipped
@ -75,7 +202,7 @@ buffer is highlighted with `persp-selected-face'"
(cl-loop for buf in (persp-current-buffers)
for is-another = (and (get-buffer-window buf) (not (eq current buf)))
if (and (buffer-live-p buf)
(eq 'exwm-mode (buffer-local-value 'major-mode buf))
(or all (eq 'exwm-mode (buffer-local-value 'major-mode buf)))
(not (string-match-p ignore-rx (buffer-name buf))))
collect buf into all-buffers
and if (not is-another) collect buf into cycle-buffers
@ -90,19 +217,8 @@ buffer is highlighted with `persp-selected-face'"
(length cycle-buffers)))
(next-buffer (nth next-pos cycle-buffers)))
(switch-to-buffer next-buffer)
(message
"%s"
(cl-loop for buf in all-buffers
for name = (with-current-buffer buf (funcall perspective-exwm-get-exwm-buffer-name))
for is-current = (eq (current-buffer) buf)
for is-skip = (not (member buf cycle-buffers))
if is-current
concat (concat "[" (propertize name 'face 'warning) "] ") into res
else if is-skip
concat (concat "[" (propertize name 'face 'persp-selected-face) "] ") into res
else
concat (format " %s " name) into res
finally return res))))))
(let ((msg (perspective-exwm--cycle-get-message all-buffers cycle-buffers)))
(message msg))))))
;;;###autoload
(defun perspective-exwm-cycle-exwm-buffers-forward ()
@ -122,6 +238,18 @@ detail."
(interactive)
(perspective-exwm--cycle-exwm-buffers 'backward))
;;;###autoload
(defun perspective-exwm-cycle-all-buffers-forward ()
"Cycle all buffers in the current perspective forward."
(interactive)
(perspective-exwm--cycle-exwm-buffers 'forward t))
;;;###autoload
(defun perspective-exwm-cycle-all-buffers-backward ()
"Cycle all buffers in the current perspective backward."
(interactive)
(perspective-exwm--cycle-exwm-buffers 'backward t))
;;;###autoload
(defun perspective-exwm-switch-perspective ()
"Switch to a perspective on any workspace."
@ -142,18 +270,20 @@ detail."
(propertize
(format "[%s]" workspace-name)
'face
'warning)
'perspective-exwm-current-workspace-face)
(format "[%s]" workspace-name))
(if (string-equal persp-name current-persp-name)
(propertize
persp-name
'face
'persp-selected-face)
'perspective-exwm-selected-pespective-face)
persp-name))
(cons i persp-name))))))
(choice (cdr (assoc (completing-read "Select a perspective: " choices) choices))))
(exwm-workspace-switch (car choice))
(persp-switch (cdr choice))))
(exwm--defer
0 (lambda ()
(exwm-workspace-switch (car choice))
(persp-switch (cdr choice))))))
;;;###autoload
(defun perspective-exwm-copy-to-workspace (&optional move)
@ -162,26 +292,24 @@ detail."
If MOVE is t, move the perspective instead."
(interactive)
(when (and move (= 1 (hash-table-count (perspectives-hash))))
(error "Can't move the only workspace"))
(user-error "Can't move the only workspace"))
(let* ((target-workspace (exwm-workspace--prompt-for-workspace))
(persp (persp-curr))
(persp-name (persp-current-name))
(url (burly-windows-url)))
(url (burly-windows-url))
(buffers (persp-current-buffers)))
(unless (= (cl-position target-workspace exwm-workspace--list)
exwm-workspace-current-index)
(when (gethash persp-name (perspectives-hash target-workspace))
(user-error "Perspective with name \"%s\" already exists on the target workspace" persp-name))
(with-selected-frame target-workspace
(when (gethash persp-name (perspectives-hash))
(error "Perspective with name \"%s\" already exists on the target workspace" persp-name))
(puthash persp-name (copy-tree (copy-perspective persp)) (perspectives-hash))
(with-perspective persp-name
;; (run-hooks 'persp-created-hook)
(persp-update-modestring)))
(mapc #'persp-add-buffer buffers)
(burly-open-url url))
(persp-switch persp-name)
(persp-update-modestring))
(when move
(persp-kill persp-name))
(exwm-workspace-switch target-workspace)
(persp-switch persp-name)
(burly-open-url url)
(persp-update-modestring))))
(exwm-workspace-switch target-workspace))))
;;;###autoload
(defun perspective-exwm-move-to-workspace ()
@ -192,51 +320,82 @@ If MOVE is t, move the perspective instead."
(defun perspective-exwm--delete-frame-around (fun &rest args)
"An advice around `persp-delete-frame'.
FUN is `persp-delete-frame', ARGS are passed to FUN with `apply'.
Do not run the function if the frame is floating, because it
occasionally breaks the current perspective in the \"parent\"
frame."
(unless (and (derived-mode-p 'exwm-mode) exwm--floating-frame)
(apply fun args)))
(defvar perspective-exwm-workspace--create-index nil
"Index of an EXWM workspace under creation.")
(defvar perspective-exwm--override-current-index nil
"The true index of the workspace under creation.
(defun perspective-exwm--workspace-switch-create-around (fun &rest args)
"An advice around `exwm-workspace-switch-create'.
Overrides the index in `perspective-exwm--init-frame-around'.")
This is necessary because the frame is created with `make-frame', and
`exwm-workspace-current-index' is getting set with a hook in
`after-make-frame-functions'. However, the inital perspective is also
initalized with the same hook, so if perspective.el is loaded before
EXWM (which is generally the case), the advice
`perspective-exwm--init-frame-around' won't have any way to know the
actual index of the current workspace.
So this advice binds the index of the workspace to be created to the
variable `perspective-exwm-workspace--create-index'."
(let ((perspective-exwm-workspace--create-index (nth 0 args)))
(apply fun args)))
(defvar perspective-exwm--is-floating nil
"If true, the frame under creation is floating.")
(defun perspective-exwm--init-frame-around (fun &rest args)
"An advice around `persp-init-frame'.
Overrides `persp-initial-frame-name' according to
`perspective-exwm-override-initial-name'."
`perspective-exwm-override-initial-name'.
FUN should be `persp-init-frame', ARGS are passed to FUN with `apply'.
The first argument of ARGS is frame. Thus, the workspace index is
either the position of the frame in `exwm-workspace--list', or a
length of that list if it's not yet there. This approach seems
to work best, e.g. when doing `exwm-workspace-switch-create' and
creating multiple workspaces at once."
(let* ((workspace-index
(or (and (numberp perspective-exwm-workspace--create-index)
perspective-exwm-workspace--create-index)
exwm-workspace-current-index))
(or perspective-exwm--override-current-index
(cl-position (car args) exwm-workspace--list)
(length exwm-workspace--list)))
(persp-initial-frame-name
(or
(cdr (assoc workspace-index
perspective-exwm-override-initial-name))
(format "main-%s" (funcall exwm-workspace-index-map workspace-index)))))
(format "main-%s" (funcall exwm-workspace-index-map workspace-index))))
(persp-initial-frame-name
(if perspective-exwm--is-floating
(format "%s-floating" persp-initial-frame-name)
persp-initial-frame-name)))
(apply fun args)))
(defun perspective-exwm--floating-set-floating-around (fun &rest args)
"An advice around `exwm-floating--set-floating'.
FUN should be `exwm-floating--set-floating', ARGS are passed to
FUN with `apply'.
This function creates a floating window, so this advice indicates
that with seting `perspective-exwm--is-floating'"
(let ((perspective-exwm--override-current-index exwm-workspace-current-index)
(perspective-exwm--is-floating t))
(apply fun args)))
(defun perspective-exwm--workspace-add-around (fun &rest args)
"An advice around `exwm-workspace-add'.
FUN should be `exwm-workspace-add', ARGS are passed to FUN with
`apply'.
This is necessary because `exwm-workspace-add' first calls
`make-frame' and only then moves it to the right index,
i.e. there is no way to determine the true index of workspace
under creation `persp-init-frame'."
(let ((perspective-exwm--override-current-index (car args)))
(apply fun args)))
(defun perspective-exwm--after-exwm-init ()
"Create perspectives in workspaces in accordance with `perspective-exwm-override-initial-name'.
"Create perspectives in workspaces.
This is meant to be run from `exwm-init-hook'."
`perspective-exwm-override-initial-name' determines initial names
of perspectives.
The function is meant to be run from `exwm-init-hook'."
(cl-loop for workspace-index from 0 to (exwm-workspace--count)
for frame in exwm-workspace--list
for target-name = (or
@ -248,6 +407,57 @@ This is meant to be run from `exwm-init-hook'."
(unless (string-equal current-name target-name)
(persp-switch target-name)
(persp-kill current-name))))))
(cl-defun perspective-exwm--persp-buffer-in-other-p (buffer)
"Return nil if BUFFER is only in the current perspective.
Otherwise, returns (FRAME . NAME), the frame and name of another
perspective that has the buffer.
Prefers perspectives in the selected frame.
This version of the function also excludes EXWM buffers from
appearing to be in other frames, because an EXWM buffer can be
only in one frame."
(cl-loop for frame in
(sort (frame-list)
(lambda (_frame1 frame2) (not (eq frame2 (selected-frame)))))
do (cl-loop for persp being the hash-values of (perspectives-hash frame)
if (and (not (and (equal frame (selected-frame))
(equal (persp-name persp) (persp-name (persp-curr frame)))))
(not (and (not (equal frame (selected-frame)))
(eq (buffer-local-value 'major-mode buffer) 'exwm-mode)))
(memq buffer (persp-buffers persp)))
do (cl-return-from perspective-exwm--persp-buffer-in-other-p
(cons frame (persp-name persp)))))
nil)
(defun perspective-exwm--persp-set-buffer-override (buffer-or-name)
"Move BUFFER-OR-NAME to the current perspective.
The original version `persp-set-buffer' from perspective.el
sometimes copies perspectives from all other workspaces to the
current one. This version stabilizes that behaviour by wrapping
calls to `persp-forget-buffer' in `with-selected-frame'."
(interactive
(list
(let ((read-buffer-function nil))
(read-buffer "Set buffer to perspective: "))))
(let ((buffer (get-buffer buffer-or-name)))
(if (not (buffer-live-p buffer))
(message "buffer %s doesn't exist" buffer-or-name)
(persp-add-buffer buffer)
;; Do not use the combination "while `persp-buffer-in-other-p'",
;; if the buffer is not removed from other perspectives, it will
;; go into an infinite loop.
(cl-loop with current-persp-name = (persp-current-name)
for frame in exwm-workspace--list
do (with-selected-frame frame
(cl-loop for persp-name being the hash-keys of (perspectives-hash)
unless (string-equal persp-name current-persp-name)
do (with-perspective persp-name
(persp-forget-buffer buffer))))))))
;;;###autoload
(defun perspective-exwm-revive-perspectives ()
"Make perspectives in the current frame not killed."
@ -262,6 +472,33 @@ This is meant to be run from `exwm-init-hook'."
(when to-switch
(persp-switch (persp-name to-switch)))))
;;;###autoload
(cl-defun perspective-exwm-assign-window (&key workspace-index persp-name)
"Move current the buffer to another workspace and/or perspective.
WORKSPACE-INDEX is index of the workspace as in
`exwm-workspace--list'. PERSP-NAME is the name of the target
perspective.
The function is useful to be ran from `exwm-manage-finish-hook',
e.g. like this:
\(defun my/exwm-configure-window ()
(interactive)
(pcase exwm-class-name
((or \"Firefox\" \"Nightly\") (perspective-exwm-assign-window
:workspace-index 2
:persp-name \"browser\"))))"
(let ((buffer (current-buffer)))
(when (and workspace-index (not (= workspace-index exwm-workspace-current-index)))
(exwm-workspace-move-window workspace-index))
(when persp-name
(with-selected-frame (nth (or workspace-index
exwm-workspace-current-index)
exwm-workspace--list)
(with-perspective persp-name
(persp-set-buffer buffer))
(persp-switch-to-buffer buffer)))))
;;;###autoload
(define-minor-mode perspective-exwm-mode
"A minor mode for intergrating perspective.el and EXWM.
@ -271,6 +508,10 @@ The mode does a couple of things:
a floating window. I haven't tested this as thoroughly, so run
`perspective-exwm-revive-perspectives' if the problem arises
anyway.
- fixes a bug with running `persp-set-buffer' on an EXWM buffer that
was moved between workspaces by advising `persp-buffer-in-other-p'.
- fixes a bug with `persp-set-buffer' copying all the
perspectives from other workspaces to the current one
- adjusts the name of the inital perspective in the new workspace.
It tries to get the name from the
`perspective-exwm-override-initial-name' variable and falls back to
@ -291,15 +532,27 @@ inital workspaces are created with the new perspective names."
:around #'perspective-exwm--delete-frame-around)
(advice-add #'persp-init-frame
:around #'perspective-exwm--init-frame-around)
(advice-add #'exwm-workspace-switch-create
:around #'perspective-exwm--workspace-switch-create-around)
(advice-add #'persp-buffer-in-other-p
:override #'perspective-exwm--persp-buffer-in-other-p)
(advice-add #'persp-set-buffer
:override #'perspective-exwm--persp-set-buffer-override)
(advice-add #'exwm-workspace-add
:around #'perspective-exwm--workspace-add-around)
(advice-add #'exwm-floating--set-floating
:around #'perspective-exwm--floating-set-floating-around)
(add-hook 'exwm-init-hook #'perspective-exwm--after-exwm-init))
(advice-remove #'persp-delete-frame
#'perspective-exwm--delete-frame-around)
(advice-remove #'persp-init-frame
#'perspective-exwm--init-frame-around)
(advice-remove #'exwm-workspace-switch-create
#'perspective-exwm--workspace-switch-create-around)
(advice-remove #'persp-buffer-in-other-p
#'perspective-exwm--persp-buffer-in-other-p)
(advice-remove #'persp-set-buffer
#'perspective-exwm--persp-set-buffer-override)
(advice-remove #'exwm-workspace-add
#'perspective-exwm--workspace-add-around)
(advice-remove #'exwm-floating--set-floating
#'perspective-exwm--floating-set-floating-around)
(remove-hook 'exwm-init-hook #'perspective-exwm--after-exwm-init))))
(provide 'perspective-exwm)