about summary refs log tree commit diff
path: root/third_party/exwm/exwm.el
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/exwm/exwm.el')
-rw-r--r--third_party/exwm/exwm.el194
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.