Compare commits

...

14 commits

2 changed files with 129 additions and 36 deletions

View file

@ -1,5 +1,7 @@
#+TITLE: exwm-modeline #+TITLE: exwm-modeline
[[https://melpa.org/#/exwm-modeline][file:https://melpa.org/packages/exwm-modeline-badge.svg]]
A modeline segment to display exwm workspaces. A modeline segment to display exwm workspaces.
Here's how it looks near the list of [[https://github.com/nex3/perspective-el][perspectives]] (the segment of the current package is to the left): Here's how it looks near the list of [[https://github.com/nex3/perspective-el][perspectives]] (the segment of the current package is to the left):
@ -13,15 +15,11 @@ Features:
- Numbers are clickable. - Numbers are clickable.
* Installation * Installation
As the package isn't yet available anywhere but in this repository, you can clone the repository, add it to the =load-path=, and =require= the package: The 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
(require 'exwm-modeline)
#+end_src
My preferred way is to use =use-package= with =straight=:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package exwm-modeline (use-package exwm-modeline
:straight (:host github :repo "SqrtMinusOne/exwm-modeline") :straight t
:after (exwm)) :after (exwm))
#+end_src #+end_src
@ -35,6 +33,8 @@ Set =exwm-modeline-randr= to nil to turn off filtering of workspaces by monitor.
Set =exwm-modeline-short= to =t= display only the current workspace in the modeline. Set =exwm-modeline-short= to =t= display only the current workspace in the modeline.
Set =exwm-modeline-display-urgent= to nil to turn off displaying whether a workspace has an urgent window. This will significantly decrease the number of modeline updates, which may help with performance issues.
* Credits * Credits
[[https://github.com/nex3/perspective-el][perspective.el]] by [[https://github.com/nex3][@nex3]] was extremely instructive on how to make a modeline segment individual to a particular frame and avoid recalculating it too often. [[https://github.com/nex3/perspective-el][perspective.el]] by [[https://github.com/nex3][@nex3]] was extremely instructive on how to make a modeline segment individual to a particular frame and avoid recalculating it too often.

View file

@ -1,14 +1,15 @@
;;; exwm-modeline.el --- A modeline segment for EXWM workspaces -*- lexical-binding: t -*- ;;; exwm-modeline.el --- A modeline segment for EXWM workspaces -*- lexical-binding: t -*-
;; Copyright (C) 2021 Korytov Pavel ;; Copyright (C) 2021-2023 Korytov Pavel
;; Copyright (C) 2021 Ellis Kenyő ;; Copyright (C) 2021 Ellis Kenyő
;; Copyright (C) 2008-2020 Natalie Weizenbaum <nex342@gmail.com> ;; Copyright (C) 2008-2020 Natalie Weizenbaum <nex342@gmail.com>
;; Author: Korytov Pavel <thexcloud@gmail.com> ;; Author: Korytov Pavel <thexcloud@gmail.com>
;; Maintainer: Korytov Pavel <thexcloud@gmail.com> ;; Maintainer: Korytov Pavel <thexcloud@gmail.com>
;; Version: 0.1.1 ;; Version: 0.1.3
;; Package-Requires: ((emacs "27.1") (exwm "0.26")) ;; Package-Requires: ((emacs "27.1") (exwm "0.26"))
;; Homepage: https://github.com/SqrtMinusOne/pomm.el ;; Homepage: https://github.com/SqrtMinusOne/exwm-modeline
;; Published-At: 2021-12-22
;; This file is NOT part of GNU Emacs. ;; This file is NOT part of GNU Emacs.
@ -37,6 +38,10 @@
;; Take a look at `exwm-modeline-mode' for more info. ;; Take a look at `exwm-modeline-mode' for more info.
;;; Code: ;;; Code:
(eval-when-compile
(require 'cl-lib))
(require 'exwm)
(require 'exwm-randr)
(require 'exwm-workspace) (require 'exwm-workspace)
(require 'exwm-manage) (require 'exwm-manage)
@ -47,7 +52,7 @@
(defcustom exwm-modeline-dividers '("[" "]" "|") (defcustom exwm-modeline-dividers '("[" "]" "|")
"Plist of strings used to create the string shown in the modeline. "Plist of strings used to create the string shown in the modeline.
First string is the start of the modestring, second is the The first string is the start of the modestring, second is the
closing of the modestring, and the last is the divider between closing of the modestring, and the last is the divider between
workspaces." workspaces."
:group 'exwm-modeline :group 'exwm-modeline
@ -56,20 +61,59 @@ workspaces."
(string :tag "Divider"))) (string :tag "Divider")))
(defcustom exwm-modeline-short nil (defcustom exwm-modeline-short nil
"When t, display only the current workspace in the modeline." "When set, display only the current workspace in the modeline."
:group 'exwm-modeline :group 'exwm-modeline
:type 'boolean) :type 'boolean
:set (lambda (sym value)
(set-default sym value)
(when (bound-and-true-p exwm-modeline-mode)
(exwm-modeline-update))))
(defcustom exwm-modeline-randr t (defcustom exwm-modeline-randr t
"When t, show only workspaces on the current monitor." "When set, only show workspaces on the current monitor."
:group 'exwm-modeline :group 'exwm-modeline
:type 'boolean) :type 'boolean
:set (lambda (sym value)
(set-default sym value)
(when (bound-and-true-p exwm-modeline-mode)
(exwm-modeline-update))))
(defcustom exwm-modeline-display-urgent t
"When set, display the urgent status in the modeline.
With that set, the modeline will be updated on every workspace
switch, so the number of updates is increased significantly."
:group 'exwm-modeline
:type 'boolean
:set (lambda (sym value)
(set-default sym value)
(when (bound-and-true-p exwm-modeline-mode)
(exwm-modeline-update)
(if value
(progn
(advice-add #'exwm--update-hints :after #'exwm-modeline--urgency-advice)
(add-hook 'exwm-workspace-switch-hook #'exwm-modeline--urgency-advice))
(advice-remove #'exwm--update-hints #'exwm-modeline--urgency-advice)
(remove-hook 'exwm-workspace-switch-hook #'exwm-modeline--urgency-advice)))))
(defcustom exwm-modeline-workspace-index-map 'exwm-workspace-index-map
"Function for mapping a workspace index to a string for display.
If the value if 'exwm-workspace-index-map, dereference the
`exwm-workspace-index-map'variable. Otherwise, use a function."
:group 'exwm-workspace
:type '(choice
(symbol exwm-workspace-index-map :tag "Use EXWM default")
(function))
:set (lambda (sym value)
(set-default sym value)
(when (bound-and-true-p exwm-modeline-mode)
(exwm-modeline-update))))
(defface exwm-modeline-current-workspace (defface exwm-modeline-current-workspace
;; I'd rather :inherit warning there, but that could lead to ;; I'd rather :inherit and override warning there, but well
;; unexpected effects.
`((t :foreground ,(face-foreground 'warning) :weight bold)) `((t :foreground ,(face-foreground 'warning) :weight bold))
"Face for the current workspace. " "Face for the current workspace."
:group 'exwm-modeline) :group 'exwm-modeline)
(defface exwm-modeline-populated-workspace (defface exwm-modeline-populated-workspace
@ -78,18 +122,21 @@ workspaces."
:group 'exwm-modeline) :group 'exwm-modeline)
(defface exwm-modeline-empty-workspace (defface exwm-modeline-empty-workspace
`((t (:foreground ,(face-foreground 'mode-line)))) `((t (:inherit mode-line)))
"Face for any workspace without an X window." "Face for any workspace without an X window."
:group 'exwm-modeline) :group 'exwm-modeline)
(defface exwm-modeline-urgent-workspace (defface exwm-modeline-urgent-workspace
'((t (:inherit custom-invalid))) '((t (:inherit error)))
"Face for any workspace that is tagged as urgent by X." "Face for any workspace that is tagged as urgent by X."
:group 'exwm-modeline) :group 'exwm-modeline)
(defun exwm-modeline--urgent-p (frame) (defun exwm-modeline--urgent-p (frame)
"Determine if FRAME is tagged as urgent." "Determine if FRAME is tagged as urgent.
(frame-parameter frame 'exwm-urgency))
Always return nil if `exwm-modeline-display-urgent' is not set."
(when exwm-modeline-display-urgent
(frame-parameter frame 'exwm-urgency)))
(defun exwm-modeline--populated-p (frame) (defun exwm-modeline--populated-p (frame)
"Determine if FRAME has any X windows." "Determine if FRAME has any X windows."
@ -97,6 +144,14 @@ workspaces."
if (eq frame (buffer-local-value 'exwm--frame (cdr item))) if (eq frame (buffer-local-value 'exwm--frame (cdr item)))
return t)) return t))
(defun exwm-modeline--workspace-index-map (i)
"Map the workspace index I to a string for display.
See `exwm-modeline-workspace-index-map' for behaviour."
(if (eq exwm-modeline-workspace-index-map 'exwm-workspace-index-map)
(funcall exwm-workspace-index-map i)
(funcall exwm-modeline-workspace-index-map i)))
(defun exwm-modeline--click (event) (defun exwm-modeline--click (event)
"Process a click EVENT on the modeline segment." "Process a click EVENT on the modeline segment."
(interactive "e") (interactive "e")
@ -104,7 +159,7 @@ workspaces."
(target (target
(cl-loop with name = (format "%s" (car (posn-string (event-start event)))) (cl-loop with name = (format "%s" (car (posn-string (event-start event))))
for i from 0 to (1- (length exwm-workspace--list)) for i from 0 to (1- (length exwm-workspace--list))
if (string-equal (funcall exwm-workspace-index-map i) name) if (string-equal (exwm-modeline--workspace-index-map i) name)
return i)) return i))
(exwm-workspace-switch target))) (exwm-workspace-switch target)))
@ -120,8 +175,8 @@ workspaces."
WORKSPACE-LIST is the list of frames to display." WORKSPACE-LIST is the list of frames to display."
(cl-loop for frame in workspace-list (cl-loop for frame in workspace-list
for i from 0 to (length workspace-list) for i from 0 to (length workspace-list)
for workspace-name = (funcall exwm-workspace-index-map for workspace-name = (exwm-modeline--workspace-index-map
(exwm-workspace--position frame)) (exwm-workspace--position frame))
with current-frame = (selected-frame) with current-frame = (selected-frame)
if (= i 0) collect (nth 0 exwm-modeline-dividers) if (= i 0) collect (nth 0 exwm-modeline-dividers)
collect collect
@ -144,13 +199,13 @@ WORKSPACE-LIST is the list of frames to display."
(defun exwm-modeline--randr-workspaces () (defun exwm-modeline--randr-workspaces ()
"Get workspaces on the same monitor as current frame." "Get workspaces on the same monitor as current frame."
(if-let ((monitor (plist-get exwm-randr-workspace-output-plist (if-let ((monitor (plist-get exwm-randr-workspace-monitor-plist
(cl-position (selected-frame) (cl-position (selected-frame)
exwm-workspace--list))) exwm-workspace--list)))
;; This list of frame actually can be wrongly ordered, ;; This list of frame actually can be wrongly ordered,
;; hence the second loop. The number of values is quite ;; hence the second loop. The number of values is quite
;; small, so it's not like o(n^2) can cause any issues. ;; small, so it's not like o(n^2) can cause any issues.
(frames (cl-loop for (key value) on exwm-randr-workspace-output-plist (frames (cl-loop for (key value) on exwm-randr-workspace-monitor-plist
by 'cddr by 'cddr
if (string-equal value monitor) if (string-equal value monitor)
collect (nth key exwm-workspace--list)))) collect (nth key exwm-workspace--list))))
@ -158,7 +213,7 @@ WORKSPACE-LIST is the list of frames to display."
if (member frame frames) if (member frame frames)
collect frame) collect frame)
(cl-loop with indices = (cl-loop (cl-loop with indices = (cl-loop
for (key value) on exwm-randr-workspace-output-plist for (key _) on exwm-randr-workspace-monitor-plist
by 'cddr collect key) by 'cddr collect key)
for i from 0 to (1- (length exwm-workspace--list)) for i from 0 to (1- (length exwm-workspace--list))
for frame in exwm-workspace--list for frame in exwm-workspace--list
@ -186,14 +241,41 @@ WORKSPACE-LIST is the list of frames to display."
(frame-parameter nil 'exwm-modeline--string)) (frame-parameter nil 'exwm-modeline--string))
(defun exwm-modeline--unmanage-advice (&rest _) (defun exwm-modeline--unmanage-advice (&rest _)
"An advice to update the modeline. "Update the modeline after unmanaging a window.
This one is meant to be attached :after This function is meant to be advised :after
`exwm-manage--unmanage-window', because that's when a workspace `exwm-manage--unmanage-window', because that's when a workspace can
can lose all its X windows and thus may become \"unpopulated\", lose all its X windows and thus may become \"unpopulated\",i.e. the
i.e. the face in the segment has to change." face in the segment has to change."
(exwm-modeline-update)) (exwm-modeline-update))
(defun exwm-modeline--urgency-advice (&rest _)
"Update the modeline after a change in the urgency status.
This function is meant to be advised :after `exwm--update-hints' and
be in the hook `exwm-workspace-switch-hook'.
The modeline is updated if `exwm-workspace--switch-history-outdated'
is set to t, because EXWM sets that variable whenever a window updates
it urgency status. To avoid running the function too often,
`exwm-workspace--update-switch-history' is also called, which resets
the variable.
However, one issue with the mentioned variable is that it is also
set whenever the workspace is switched, so using that advice also
increases the number of required updates. Optimizing that would
require more substantial modifications to EXWM code, so in this
package applying that advice is made optional with the
`exwm-modeline-display-urgent' variable.
Because the first function is very much critical for the normal
functioning of EXWM, the entire thing is wrapped in
`with-demoted-errors'."
(with-demoted-errors "Error in exwm-modeline--urgency-advice: %S"
(when exwm-workspace--switch-history-outdated
(exwm-modeline-update)
(exwm-workspace--update-switch-history))))
;;;###autoload ;;;###autoload
(define-minor-mode exwm-modeline-mode (define-minor-mode exwm-modeline-mode
"A mode for displaying EXWM workspaces in the modeline. "A mode for displaying EXWM workspaces in the modeline.
@ -206,10 +288,16 @@ monitor. To display only the current workspace, enable
`exwm-modeline-short', and to disable the filtering by the `exwm-modeline-short', and to disable the filtering by the
monitor, disable `exwm-modeline-randr'. monitor, disable `exwm-modeline-randr'.
Also take a look at the `exwm-modeline' group for faces If `exwm-modeline-display-urgent' is set, the mode also displays
if the workspace has a window market as urgent. However, this
option forces the modeline to update after every workspace
switch, so it may be wise to disable that in case of performance
issues.
Also, take a look at the `exwm-modeline' group for faces
customization. customization.
This implementation indents to reduce the count of times of This implementation intends to reduce the count of times of
evaluating the modestring; the rendered modestring is saved as a evaluating the modestring; the rendered modestring is saved as a
frame parameter, and `exwm-modeline-segment' just returns it. frame parameter, and `exwm-modeline-segment' just returns it.
@ -226,14 +314,19 @@ cases when the workspace list changes."
(add-hook 'exwm-randr-refresh-hook #'exwm-modeline-update) (add-hook 'exwm-randr-refresh-hook #'exwm-modeline-update)
(add-hook 'exwm-manage-finish-hook #'exwm-modeline-update) (add-hook 'exwm-manage-finish-hook #'exwm-modeline-update)
(advice-add #'exwm-manage--unmanage-window (advice-add #'exwm-manage--unmanage-window
:after #'exwm-modeline--unmanage-advice)) :after #'exwm-modeline--unmanage-advice)
(when exwm-modeline-display-urgent
(advice-add #'exwm--update-hints :after #'exwm-modeline--urgency-advice)
(add-hook 'exwm-workspace-switch-hook #'exwm-modeline--urgency-advice)))
(setq global-mode-string (delete '(:eval (exwm-modeline-segment)) (setq global-mode-string (delete '(:eval (exwm-modeline-segment))
global-mode-string)) global-mode-string))
(remove-hook 'exwm-workspace-list-change-hook #'exwm-modeline-update) (remove-hook 'exwm-workspace-list-change-hook #'exwm-modeline-update)
(remove-hook 'exwm-randr-refresh-hook #'exwm-modeline-update) (remove-hook 'exwm-randr-refresh-hook #'exwm-modeline-update)
(remove-hook 'exwm-manage-finish-hook #'exwm-modeline-update) (remove-hook 'exwm-manage-finish-hook #'exwm-modeline-update)
(advice-remove #'exwm-manage--unmanage-window (advice-remove #'exwm-manage--unmanage-window
#'exwm-modeline--unmanage-advice))) #'exwm-modeline--unmanage-advice)
(advice-remove #'exwm--update-hints #'exwm-modeline--urgency-advice)
(remove-hook 'exwm-workspace-switch-hook #'exwm-modeline--urgency-advice)))
(provide 'exwm-modeline) (provide 'exwm-modeline)
;;; exwm-modeline.el ends here ;;; exwm-modeline.el ends here