diff options
Diffstat (limited to 'third_party/exwm/exwm.el')
-rw-r--r-- | third_party/exwm/exwm.el | 194 |
1 files changed, 144 insertions, 50 deletions
diff --git a/third_party/exwm/exwm.el b/third_party/exwm/exwm.el index b025f6b49a..c4900eab48 100644 --- a/third_party/exwm/exwm.el +++ b/third_party/exwm/exwm.el @@ -1,13 +1,13 @@ ;;; exwm.el --- Emacs X Window Manager -*- lexical-binding: t -*- -;; Copyright (C) 2015-2021 Free Software Foundation, Inc. +;; Copyright (C) 2015-2024 Free Software Foundation, Inc. ;; Author: Chris Feng <chris.w.feng@gmail.com> -;; Maintainer: Adrián Medraño Calvo <adrian@medranocalvo.com> -;; Version: 0.26 -;; Package-Requires: ((xelb "0.18")) +;; Maintainer: Adrián Medraño Calvo <adrian@medranocalvo.com>, Steven Allen <steven@stebalien.com>, Daniel Mendler <mail@daniel-mendler.de> +;; Version: 0.28 +;; Package-Requires: ((emacs "27.1") (xelb "0.18")) ;; Keywords: unix -;; URL: https://github.com/ch11ng/exwm +;; URL: https://github.com/emacs-exwm/exwm ;; This file is part of GNU Emacs. @@ -29,14 +29,18 @@ ;; Overview ;; -------- ;; 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). +;; for Emacs built on top of [XELB](https://github.com/emacs-exwm/xelb). ;; It features: ;; + Fully keyboard-driven operations ;; + Hybrid layout modes (tiling & stacking) ;; + Dynamic workspace support ;; + ICCCM/EWMH compliance -;; + (Optional) RandR (multi-monitor) support -;; + (Optional) Built-in system tray +;; Optional features: +;; + RandR (multi-monitor) support +;; + System tray +;; + Input method +;; + Background setting support +;; + XSETTINGS server ;; Installation & configuration ;; ---------------------------- @@ -54,7 +58,7 @@ ;; xinit -- vt01 ;; ;; You should additionally hide the menu-bar, tool-bar, etc to increase the -;; usable space. Please check the wiki (https://github.com/ch11ng/exwm/wiki) +;; usable space. Please check the wiki (https://github.com/emacs-exwm/exwm/wiki) ;; for more detailed instructions on installation, configuration, usage, etc. ;; References: @@ -72,10 +76,11 @@ (require 'exwm-manage) (require 'exwm-input) +(declare-function x-get-atom-name "C source code" (VALUE &optional FRAME)) + (defgroup exwm nil "Emacs X Window Manager." :tag "EXWM" - :version "25.3" :group 'applications :prefix "exwm-") @@ -95,7 +100,10 @@ "Normal hook run when window title is updated." :type 'hook) -(defcustom exwm-blocking-subrs '(x-file-dialog x-popup-dialog x-select-font) +(defcustom exwm-blocking-subrs + ;; `x-file-dialog' and `x-select-font' are missing on some Emacs builds, for + ;; example on the X11 Lucid build. + '(x-file-dialog x-popup-dialog x-select-font message-box message-or-box) "Subrs (primitives) that would normally block EXWM." :type '(repeat function)) @@ -108,6 +116,10 @@ (defconst exwm--server-name "server-exwm" "Name of the subordinate Emacs server.") +(defvar exwm--server-timeout 1 + "Number of seconds to wait for the subordinate Emacs server to exit. +After this time, the server will be killed.") + (defvar exwm--server-process nil "Process of the subordinate Emacs server.") (defun exwm-reset () @@ -127,7 +139,7 @@ "Restart EXWM." (interactive) (exwm--log) - (when (exwm--confirm-kill-emacs "[EXWM] Restart? " 'no-check) + (when (exwm--confirm-kill-emacs "Restart?" 'no-check) (let* ((attr (process-attributes (emacs-pid))) (args (cdr (assq 'args attr))) (ppid (cdr (assq 'ppid attr))) @@ -153,7 +165,8 @@ (kill-emacs)))))) (defun exwm--update-desktop (xwin) - "Update _NET_WM_DESKTOP." + "Update _NET_WM_DESKTOP. +Argument XWIN contains the X window of the `exwm-mode' buffer." (exwm--log "#x%x" xwin) (with-current-buffer (exwm--id->buffer xwin) (let ((reply (xcb:+request-unchecked+reply exwm--connection @@ -163,7 +176,7 @@ (when reply (setq desktop (slot-value reply 'value)) (cond - ((eq desktop 4294967295.) + ((and desktop (= desktop 4294967295.)) (unless (or (not exwm--floating-frame) (eq exwm--frame exwm-workspace--current) (and exwm--desktop @@ -180,7 +193,11 @@ (exwm-workspace--set-desktop xwin))))))) (defun exwm--update-window-type (id &optional force) - "Update _NET_WM_WINDOW_TYPE." + "Update `exwm-window-type' from _NET_WM_WINDOW_TYPE. +Argument ID contains the X window of the `exwm-mode' buffer. + +When FORCE is nil the update only takes place if +`exwm-window-type' is unset." (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (unless (and exwm-window-type (not force)) @@ -191,7 +208,11 @@ (setq exwm-window-type (append (slot-value reply 'value) nil))))))) (defun exwm--update-class (id &optional force) - "Update WM_CLASS." + "Update `exwm-instance-name' and `exwm-class' from WM_CLASS. +Argument ID contains the X window of the `exwm-mode' buffer. + +When FORCE is nil the update only takes place if any of +`exwm-instance-name' or `exwm-class' is unset." (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (unless (and exwm-instance-name exwm-class-name (not force)) @@ -204,7 +225,11 @@ (run-hooks 'exwm-update-class-hook))))))) (defun exwm--update-utf8-title (id &optional force) - "Update _NET_WM_NAME." + "Update `exwm-title' from _NET_WM_NAME. +Argument ID contains the X window of the `exwm-mode' buffer. + +When FORCE is nil the update only takes place if `exwm-title' is +unset." (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (when (or force (not exwm-title)) @@ -217,7 +242,11 @@ (run-hooks 'exwm-update-title-hook))))))) (defun exwm--update-ctext-title (id &optional force) - "Update WM_NAME." + "Update `exwm-title' from WM_NAME. +Argument ID contains the X window of the `exwm-mode' buffer. + +When FORCE is nil the update only takes place if `exwm-title' is +unset." (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (unless (or exwm--title-is-utf8 @@ -230,13 +259,18 @@ (run-hooks 'exwm-update-title-hook))))))) (defun exwm--update-title (id) - "Update _NET_WM_NAME or WM_NAME." + "Update _NET_WM_NAME or WM_NAME. +Argument ID contains the X window of the `exwm-mode' buffer." (exwm--log "#x%x" id) (exwm--update-utf8-title id) (exwm--update-ctext-title id)) (defun exwm--update-transient-for (id &optional force) - "Update WM_TRANSIENT_FOR." + "Update `exwm-transient-for' from WM_TRANSIENT_FOR. +Argument ID contains the X window of the `exwm-mode' buffer. + +When FORCE is nil the update only takes place if `exwm-title' is +unset." (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (unless (and exwm-transient-for (not force)) @@ -247,7 +281,15 @@ (setq exwm-transient-for (slot-value reply 'value))))))) (defun exwm--update-normal-hints (id &optional force) - "Update WM_NORMAL_HINTS." + "Update normal hints from WM_NORMAL_HINTS. +Argument ID contains the X window of the `exwm-mode' buffer. + +When FORCE is nil the update only takes place all of +`exwm--normal-hints-x exwm--normal-hints-y', +`exwm--normal-hints-width exwm--normal-hints-height', +`exwm--normal-hints-min-width exwm--normal-hints-min-height' and +`exwm--normal-hints-max-width exwm--normal-hints-max-height' are +unset." (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (unless (and (not force) @@ -295,7 +337,11 @@ exwm--normal-hints-max-height))))))))) (defun exwm--update-hints (id &optional force) - "Update WM_HINTS." + "Update hints from WM_HINTS. +Argument ID contains the X window of the `exwm-mode' buffer. + +When FORCE is nil the update only takes place if both of +`exwm--hints-input' and `exwm--hints-urgency' are unset." (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (unless (and (not force) exwm--hints-input exwm--hints-urgency) @@ -317,7 +363,11 @@ (setq exwm-workspace--switch-history-outdated t)))))))) (defun exwm--update-protocols (id &optional force) - "Update WM_PROTOCOLS." + "Update `exwm--protocols' from WM_PROTOCOLS. +Argument ID contains the X window of the `exwm-mode' buffer. + +When FORCE is nil the update only takes place if `exwm--protocols' +is unset." (exwm--log "#x%x" id) (with-current-buffer (exwm--id->buffer id) (unless (and exwm--protocols (not force)) @@ -328,7 +378,7 @@ (setq exwm--protocols (append (slot-value reply 'value) nil))))))) (defun exwm--update-struts-legacy (id) - "Update _NET_WM_STRUT." + "Update struts of X window ID from _NET_WM_STRUT." (exwm--log "#x%x" id) (let ((pair (assq id exwm-workspace--id-struts-alist)) reply struts) @@ -349,7 +399,7 @@ (exwm-workspace--set-fullscreen f))))) (defun exwm--update-struts-partial (id) - "Update _NET_WM_STRUT_PARTIAL." + "Update struts of X window ID from _NET_WM_STRUT_PARTIAL." (exwm--log "#x%x" id) (let ((reply (xcb:+request-unchecked+reply exwm--connection (make-instance 'xcb:ewmh:get-_NET_WM_STRUT_PARTIAL @@ -369,13 +419,14 @@ (exwm-workspace--set-fullscreen f)))) (defun exwm--update-struts (id) - "Update _NET_WM_STRUT_PARTIAL or _NET_WM_STRUT." + "Update struts of X window ID from _NET_WM_STRUT_PARTIAL or _NET_WM_STRUT." (exwm--log "#x%x" id) (exwm--update-struts-partial id) (exwm--update-struts-legacy id)) (defun exwm--on-PropertyNotify (data _synthetic) - "Handle PropertyNotify event." + "Handle PropertyNotify event. +DATA contains unmarshalled PropertyNotify event data." (let ((obj (make-instance 'xcb:PropertyNotify)) atom id buffer) (xcb:unmarshal obj data) @@ -413,15 +464,16 @@ atom))))))) (defun exwm--on-ClientMessage (raw-data _synthetic) - "Handle ClientMessage event." + "Handle ClientMessage event. +RAW-DATA contains unmarshalled ClientMessage event data." (let ((obj (make-instance 'xcb:ClientMessage)) type id data) (xcb:unmarshal obj raw-data) (setq type (slot-value obj 'type) id (slot-value obj 'window) data (slot-value (slot-value obj 'data) 'data32)) - (exwm--log "atom=%s(%s)" (x-get-atom-name type exwm-workspace--current) - type) + (exwm--log "atom=%s(%s) id=#x%x data=%s" (x-get-atom-name type exwm-workspace--current) + type (or id 0) data) (cond ;; _NET_NUMBER_OF_DESKTOPS. ((= type xcb:Atom:_NET_NUMBER_OF_DESKTOPS) @@ -434,7 +486,6 @@ ((and (> current requested) (> current 1)) (let ((frame (car (last exwm-workspace--list)))) - (exwm-workspace--get-remove-frame-next-workspace frame) (delete-frame frame)))))) ;; _NET_CURRENT_DESKTOP. ((= type xcb:Atom:_NET_CURRENT_DESKTOP) @@ -443,7 +494,8 @@ ((= type xcb:Atom:_NET_ACTIVE_WINDOW) (let ((buffer (exwm--id->buffer id)) iconic window) - (when (buffer-live-p buffer) + (if (buffer-live-p buffer) + ;; Either an `exwm-mode' buffer (an X window) or a floating frame. (with-current-buffer buffer (when (eq exwm--frame exwm-workspace--current) (if exwm--floating-frame @@ -457,7 +509,11 @@ (setq window (get-buffer-window nil t)) (when (or iconic (not (eq window (selected-window)))) - (select-window window)))))))) + (select-window window))))) + ;; A workspace. + (dolist (f exwm-workspace--list) + (when (eq id (frame-parameter f 'exwm-outer-id)) + (x-focus-frame f t)))))) ;; _NET_CLOSE_WINDOW. ((= type xcb:Atom:_NET_CLOSE_WINDOW) (let ((buffer (exwm--id->buffer id))) @@ -594,7 +650,8 @@ (x-get-atom-name type exwm-workspace--current) type))))) (defun exwm--on-SelectionClear (data _synthetic) - "Handle SelectionClear events." + "Handle SelectionClear events. +DATA contains unmarshalled SelectionClear event data." (exwm--log) (let ((obj (make-instance 'xcb:SelectionClear)) owner selection) @@ -605,6 +662,17 @@ (eq selection xcb:Atom:WM_S0)) (exwm-exit)))) +(defun exwm--on-delete-terminal (terminal) + "Handle terminal being deleted without Emacs being killed. +This function is Hooked to `delete-terminal-functions'. + +TERMINAL is the terminal being (or that has been) deleted. + +This may happen when invoking `save-buffers-kill-terminal' within an emacsclient +session." + (when (eq terminal exwm--terminal) + (exwm-exit))) + (defun exwm--init-icccm-ewmh () "Initialize ICCCM/EWMH support." (exwm--log) @@ -825,7 +893,8 @@ manager. If t, replace it, if nil, abort and ask the user if `ask'." ;;;###autoload (cl-defun exwm-init (&optional frame) - "Initialize EXWM." + "Initialize EXWM. +FRAME, if given, indicates the X display EXWM should manage." (interactive) (exwm--log "%s" frame) (if frame @@ -841,6 +910,7 @@ manager. If t, replace it, if nil, abort and ask the user if `ask'." (condition-case err (progn (exwm-enable 'undo) ;never initialize again + (setq exwm--terminal (frame-terminal frame)) (setq exwm--connection (xcb:connect)) (set-process-query-on-exit-flag (slot-value exwm--connection 'process) nil) ;prevent query message on exit @@ -863,6 +933,10 @@ manager. If t, replace it, if nil, abort and ask the user if `ask'." ;; Disable some features not working well with EXWM (setq use-dialog-box nil confirm-kill-emacs #'exwm--confirm-kill-emacs) + (advice-add 'save-buffers-kill-terminal + :before-while #'exwm--confirm-kill-terminal) + ;; Clean up if the terminal is deleted. + (add-hook 'delete-terminal-functions 'exwm--on-delete-terminal) (exwm--lock) (exwm--init-icccm-ewmh) (exwm-layout--init) @@ -891,15 +965,17 @@ manager. If t, replace it, if nil, abort and ask the user if `ask'." (run-hooks 'exwm-exit-hook) (setq confirm-kill-emacs nil) ;; Exit modules. - (exwm-input--exit) - (exwm-manage--exit) - (exwm-workspace--exit) - (exwm-floating--exit) - (exwm-layout--exit) (when exwm--connection + (exwm-input--exit) + (exwm-manage--exit) + (exwm-workspace--exit) + (exwm-floating--exit) + (exwm-layout--exit) (xcb:flush exwm--connection) (xcb:disconnect exwm--connection)) - (setq exwm--connection nil)) + (setq exwm--connection nil) + (setq exwm--terminal nil) + (exwm--log "Exited")) ;;;###autoload (defun exwm-enable (&optional undo) @@ -933,15 +1009,21 @@ manager. If t, replace it, if nil, abort and ask the user if `ask'." (defun exwm--server-stop () "Stop the subordinate Emacs server." (exwm--log) - (server-force-delete exwm--server-name) (when exwm--server-process + (when (process-live-p exwm--server-process) + (cl-loop + initially (signal-process exwm--server-process 'TERM) + while (process-live-p exwm--server-process) + repeat (* 10 exwm--server-timeout) + do (sit-for 0.1))) (delete-process exwm--server-process) (setq exwm--server-process nil))) -(defun exwm--server-eval-at (&rest args) - "Wrapper of `server-eval-at' used to advice subrs." +(defun exwm--server-eval-at (function &rest args) + "Wrapper of `server-eval-at' used to advice subrs. +FUNCTION is the function to be evaluated, ARGS are the arguments." ;; Start the subordinate Emacs server if it's not alive - (exwm--log "%s" args) + (exwm--log "%s %s" function args) (unless (server-running-p exwm--server-name) (when exwm--server-process (delete-process exwm--server-process)) (setq exwm--server-process @@ -950,7 +1032,7 @@ manager. If t, replace it, if nil, abort and ask the user if `ask'." (car command-line-args) ;The executable file "-d" (frame-parameter nil 'display) "-Q" - (concat "--daemon=" exwm--server-name) + (concat "--fg-daemon=" exwm--server-name) "--eval" ;; Create an invisible frame "(make-frame '((window-system . x) (visibility)))")) @@ -959,8 +1041,8 @@ manager. If t, replace it, if nil, abort and ask the user if `ask'." (server-eval-at exwm--server-name `(progn (select-frame (car (frame-list))) - (let ((result ,(nconc (list (make-symbol (subr-name (car args)))) - (cdr args)))) + (let ((result ,(nconc (list (make-symbol (subr-name function))) + args))) (pcase (type-of result) ;; Return the name of a buffer (`buffer (buffer-name result)) @@ -978,8 +1060,20 @@ manager. If t, replace it, if nil, abort and ask the user if `ask'." ;; For other types, return the value as-is. (t result)))))) +(defun exwm--confirm-kill-terminal (&optional _) + "Confirm before killing terminal." + ;; This is invoked instead of `save-buffers-kill-emacs' (C-x C-c) on client + ;; frames. + (if (exwm--terminal-p) + (exwm--confirm-kill-emacs "Kill terminal?") + t)) + (defun exwm--confirm-kill-emacs (prompt &optional force) - "Confirm before exiting Emacs." + "Confirm before exiting Emacs. +PROMPT a reason to present to the user. +If FORCE is nil, ask the user for confirmation. +If FORCE is the symbol `no-check', ask if there are unsaved buffers. +If FORCE is any other non-nil value, force killing of Emacs." (exwm--log) (when (cond ((and force (not (eq force 'no-check))) @@ -996,7 +1090,7 @@ manager. If t, replace it, if nil, abort and ask the user if `ask'." (`break (y-or-n-p prompt)) (x x))) (t - (yes-or-no-p (format "[EXWM] %d window(s) will be destroyed. %s" + (yes-or-no-p (format "[EXWM] %d X window(s) will be destroyed. %s" (length exwm--id-buffer-alist) prompt)))) ;; Run `kill-emacs-hook' (`server-force-stop' excluded) before Emacs ;; frames are unmapped so that errors (if any) can be visible. |