diff options
Diffstat (limited to 'exwm.el')
-rw-r--r-- | exwm.el | 706 |
1 files changed, 706 insertions, 0 deletions
diff --git a/exwm.el b/exwm.el new file mode 100644 index 000000000000..49c4380405be --- /dev/null +++ b/exwm.el @@ -0,0 +1,706 @@ +;;; exwm.el --- Emacs X Window Manager -*- lexical-binding: t -*- + +;; Copyright (C) 2015 Chris Feng + +;; Author: Chris Feng <chris.w.feng@gmail.com> +;; Keywords: unix + +;; This file is not part of GNU Emacs. + +;; This file is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This file is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this file. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Overview +;; -------- +;; EXWM (Emacs X Window Manager) turns Emacs into a full-featured tiling X +;; window manager. It's built on top of XELB, thus purely consists of Elisp +;; codes. Some features include: +;; + It's fully keyboard-driven. +;; - You have full access to all key bindings in Emacs. +;; - It allows you to bind additional key sequences with `exwm-input-set-key' +;; (just like `global-set-key'). +;; - It supports simulation keys (i.e., map one key sequence to another). +;; + Workspace support. +;; - EXWM support up to 10 workspaces. +;; + ICCCM/EWMH compliance. +;; - Note that the support for EWMH can never be complete since EXWM is not a +;; conventional window manager. + +;; How it works +;; ------------ +;; Emacs itself is an tiling window manager, though unfortunately not for +;; managing X things. EXWM is therefore created to overcome this limitation by +;; relating X concepts to Emacs ones as shown in the following table. +;; +;; +=============+=========+ +;; | X Window | Emacs | +;; +=============+=========+ +;; | window | buffer | +;; +-------------+---------+ +;; | container* | window | +;; +-------------+---------+ +;; | workspace / | frame** | +;; | decoration | | +;; +=============+=========+ +;; * Here a container means the parent of an X client window created by window +;; manager for layout management. +;; ** In EXWM, A frame usually acts as a workspace. But for a floating window, +;; it's the decoration around the top-level window. +;; +;; Each X client window corresponds to a dedicated buffer in EXWM mode. When +;; such a buffer is buried or unburied, the attached X client window is hide or +;; shown accordingly. The position and size of the X client window is then +;; determined by the Emacs window its corresponding buffer displayed in. +;; +;; A buffer in EXWM mode also records which workspace it belongs to, and its +;; attached X client window is made a child (in the sense of X) of the +;; workspace frame. The switch between workspaces is simply done by switching +;; corresponding Emacs frames. + +;; Installation & configuration +;; ---------------------------- +;; Here are the minimal steps to get EXWM working: +;; 0. Install xelb and xelb-util first. +;; 1. Put EXWM somewhere on your disk and make sure it's in `load-path'. +;; 2. In your Emacs init file, add following lines (modify accordingly): +;; +;; (require 'exwm) +;; ;; We always need a way to go back from char-mode to line-mode +;; (exwm-input-set-key (kbd "s-r") 'exwm-reset) +;; ;; Bind a key to switch workspace interactively +;; (exwm-input-set-key (kbd "s-w") 'exwm-workspace-switch) +;; ;; Use class name to name an EXWM buffer +;; (add-hook 'exwm-update-class-hook +;; (lambda () (rename-buffer exwm-class-name t))) +;; ;; Enable EXWM +;; (exwm-enable) +;; +;; 3. Make a file '~/.xinitrc' with the content +;; +;; exec emacs +;; +;; 4. Launch EXWM in a console with +;; +;; xinit +;; +;; You should refer to other resources on how to further configure '~/.xinitrc' +;; and other init scripts to improve the working experience. Besides, you +;; should hide the menu-bar, tool-bar, etc to increase the usable space. + +;; Interactive modes +;; ----------------- +;; There are two modes in EXWM to interact with an X client window: line mode +;; and char mode. They are analogous to those concepts in `ansi-term'. EXWM +;; buffers are created in line mode by default. +;; +;; In line mode, all key events are intercepted and then forwarded to the X +;; client window except +;; + it forms a mode-specific key sequence (which begins with 'C-c'), or +;; + it forms a key sequence bound with `exwm-input-set-key', or +;; + it forms a key sequence starting with a line mode prefix key, or +;; + it forms a key sequence in line mode simulation keys. +;; You can use 'C-c q' (bound to `exwm-input-send-next-key', can be 'C-u' +;; prefixed) to send these keys to the client. +;; +;; In char mode however, no key event is intercepted except those bound with +;; `exwm-input-set-key'. Therefore you will almost always need to +;; 'exwm-input-set-key' a key sequence to go from char mode to line mode. + +;; Related projects +;; ---------------- +;; EXWM is not the first attempt to make Emacs an X window manger; there is +;; another ancient project called XWEM (http://www.nongnu.org/xwem/) for +;; XEmacs, though it seems nobody have ever got it working on GNU Emacs. + +;; Todo: +;; + Add RandR support. +;; + Investigate DnD support (e.g. drag a chromium tab to another window). +;; + Auto hide minibuffer, or allow users to place it elsewhere. +;; + Add system tray support. + +;; References: +;; + dwm (http://dwm.suckless.org/) +;; + i3 wm (https://i3wm.org/) +;; + Also see references within each required library. + +;;; Code: + +(require 'xcb) +(require 'xcb-icccm) +(require 'xcb-ewmh) +(require 'exwm-workspace) +(require 'exwm-layout) +(require 'exwm-floating) +(require 'exwm-manage) +(require 'exwm-input) + +(defvar exwm-debug-on nil "Non-nil to turn on debug for EXWM.") + +(defmacro exwm--log (format-string &rest args) + "Print debug message." + (when exwm-debug-on + `(message (concat "[EXWM] " ,format-string) ,@args))) + +(defconst exwm--client-event-mask + (logior xcb:EventMask:StructureNotify xcb:EventMask:PropertyChange) + "Event mask set on all managed windows.") + +(defvar exwm--connection nil "X connection.") +(defvar exwm--root nil "Root window.") +(defvar exwm--id-buffer-alist nil "Alist of (<X window ID> . <Emacs buffer>)") + +(defsubst exwm--id->buffer (id) + "X window ID => Emacs buffer." + (cdr (assoc id exwm--id-buffer-alist))) + +(defsubst exwm--buffer->id (buffer) + "Emacs buffer => X window ID." + (car (rassoc buffer exwm--id-buffer-alist))) + +(defun exwm--lock (&rest args) ;args are for abnormal hook + "Lock (disable all events)." + (xcb:+request exwm--connection + (make-instance 'xcb:ChangeWindowAttributes + :window exwm--root + :value-mask xcb:CW:EventMask + :event-mask xcb:EventMask:NoEvent)) + (xcb:flush exwm--connection)) + +(defun exwm--unlock (&rest args) ;args are for abnormal hook + "Unlock (enable all events)." + (xcb:+request exwm--connection + (make-instance 'xcb:ChangeWindowAttributes + :window exwm--root + :value-mask xcb:CW:EventMask + :event-mask (logior xcb:EventMask:StructureNotify + xcb:EventMask:SubstructureRedirect))) + (xcb:flush exwm--connection)) + +(defun exwm-reset () + "Reset window to standard state: non-fullscreen, line-mode." + (interactive) + (with-current-buffer (window-buffer (selected-window)) + (when exwm--fullscreen (exwm-layout-unset-fullscreen)) + (exwm-input-grab-keyboard))) + +(defmacro exwm--with-current-id (id &rest body) + "Evaluate BODY in the context of the buffer corresponding to window ID." + (declare (indent 1)) + `(when ,id + (let ((buffer (exwm--id->buffer ,id))) + (when buffer + (with-current-buffer buffer ,@body))))) + +(defun exwm--update-window-type (id &optional force) + "Update _NET_WM_WINDOW_TYPE." + (exwm--with-current-id id + (unless (and exwm-window-type (not force)) + (let ((reply (xcb:+request-unchecked+reply exwm--connection + (make-instance 'xcb:ewmh:get-_NET_WM_WINDOW_TYPE + :window id)))) + (when reply ;nil when destroyed + (setq exwm-window-type (append (slot-value reply 'value) nil))))))) + +(defvar exwm-update-class-hook nil + "Normal hook run when window class is updated.") + +(defun exwm--update-class (id &optional force) + "Update WM_CLASS." + (exwm--with-current-id id + (unless (and exwm-instance-name exwm-class-name (not force)) + (let ((reply (xcb:+request-unchecked+reply exwm--connection + (make-instance 'xcb:icccm:get-WM_CLASS :window id)))) + (when reply ;nil when destroyed + (setq exwm-instance-name (slot-value reply 'instance-name) + exwm-class-name (slot-value reply 'class-name)) + (when (and exwm-instance-name exwm-class-name) + (run-hooks 'exwm-update-class-hook))))))) + +(defvar exwm-update-title-hook nil + "Normal hook run when window title is updated.") + +(defun exwm--update-utf8-title (id &optional force) + "Update _NET_WM_NAME." + (exwm--with-current-id id + (when (or force (not exwm-title)) + (let ((reply (xcb:+request-unchecked+reply exwm--connection + (make-instance 'xcb:ewmh:get-_NET_WM_NAME :window id)))) + (when reply ;nil when destroyed + (setq exwm-title (slot-value reply 'value)) + (when exwm-title + (setq exwm--title-is-utf8 t) + (run-hooks 'exwm-update-title-hook))))))) + +(defun exwm--update-ctext-title (id &optional force) + "Update WM_NAME." + (exwm--with-current-id id + (unless (or exwm--title-is-utf8 + (and exwm-title (not force))) + (let ((reply (xcb:+request-unchecked+reply exwm--connection + (make-instance 'xcb:icccm:get-WM_NAME :window id)))) + (when reply ;nil when destroyed + (setq exwm-title (slot-value reply 'value)) + (when exwm-title + (run-hooks 'exwm-update-title-hook))))))) + +(defun exwm--update-title (id) + "Update _NET_WM_NAME or WM_NAME." + (exwm--update-utf8-title id) + (exwm--update-ctext-title id)) + +(defun exwm--update-transient-for (id &optional force) + "Update WM_TRANSIENT_FOR." + (exwm--with-current-id id + (unless (and exwm-transient-for (not force)) + (let ((reply (xcb:+request-unchecked+reply exwm--connection + (make-instance 'xcb:icccm:get-WM_TRANSIENT_FOR + :window id)))) + (when reply ;nil when destroyed + (setq exwm-transient-for (slot-value reply 'value))))))) + +(defun exwm--update-normal-hints (id &optional force) + "Update WM_NORMAL_HINTS." + (exwm--with-current-id id + (unless (and (not force) + (or 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 + exwm--normal-hints-max-width exwm--normal-hints-max-height + ;; FIXME: other fields + )) + (let ((reply (xcb:+request-unchecked+reply exwm--connection + (make-instance 'xcb:icccm:get-WM_NORMAL_HINTS + :window id)))) + (when (and reply (slot-value reply 'flags)) ;nil when destroyed + (with-slots (flags x y width height min-width min-height max-width + max-height base-width base-height ;; win-gravity + ) + reply + (unless (= 0 (logand flags xcb:icccm:WM_SIZE_HINTS:USPosition)) + (setq exwm--normal-hints-x x exwm--normal-hints-y y)) + (unless (= 0 (logand flags xcb:icccm:WM_SIZE_HINTS:USSize)) + (setq exwm--normal-hints-width width + exwm--normal-hints-height height)) + (unless (= 0 (logand flags xcb:icccm:WM_SIZE_HINTS:PMinSize)) + (setq exwm--normal-hints-min-width min-width + exwm--normal-hints-min-height min-height)) + (unless (= 0 (logand flags xcb:icccm:WM_SIZE_HINTS:PMaxSize)) + (setq exwm--normal-hints-max-width max-width + exwm--normal-hints-max-height max-height)) + (unless (or exwm--normal-hints-min-width + (= 0 (logand flags xcb:icccm:WM_SIZE_HINTS:PBaseSize))) + (setq exwm--normal-hints-min-width base-width + exwm--normal-hints-min-height base-height)) + ;; (unless (= 0 (logand flags xcb:icccm:WM_SIZE_HINTS:PWinGravity)) + ;; (setq exwm--normal-hints-win-gravity win-gravity)) + (setq exwm--fixed-size + (and exwm--normal-hints-min-width + exwm--normal-hints-min-height + exwm--normal-hints-max-width + exwm--normal-hints-max-height + (/= 0 exwm--normal-hints-min-width) + (/= 0 exwm--normal-hints-min-height) + (= exwm--normal-hints-min-width + exwm--normal-hints-max-width) + (= exwm--normal-hints-min-height + exwm--normal-hints-max-height))))))))) + +(defun exwm--update-hints (id &optional force) + "Update WM_HINTS." + (exwm--with-current-id id + (unless (and (not force) exwm--hints-input exwm--hints-urgency) + (let ((reply (xcb:+request-unchecked+reply exwm--connection + (make-instance 'xcb:icccm:get-WM_HINTS :window id)))) + (when (and reply (slot-value reply 'flags)) ;nil when destroyed + (with-slots (flags input) reply + (when flags + (unless (= 0 (logand flags xcb:icccm:WM_HINTS:InputHint)) + (setq exwm--hints-input (when input (= 1 input)))) + (unless (= 0 (logand flags xcb:icccm:WM_HINTS:UrgencyHint)) + (setq exwm--hints-urgency t)))) + (when (and exwm--hints-urgency + (not (eq exwm--frame exwm-workspace--current))) + (unless (frame-parameter exwm--frame 'exwm--urgency) + (set-frame-parameter exwm--frame 'exwm--urgency t) + (exwm-workspace--update-switch-history)))))))) + +(defun exwm--update-protocols (id &optional force) + "Update WM_PROTOCOLS." + (exwm--with-current-id id + (unless (and exwm--protocols (not force)) + (let ((reply (xcb:+request-unchecked+reply exwm--connection + (make-instance 'xcb:icccm:get-WM_PROTOCOLS + :window id)))) + (when reply ;nil when destroyed + (setq exwm--protocols (append (slot-value reply 'value) nil))))))) + +(defun exwm--update-state (id &optional force) + "Update WM_STATE." + (exwm--with-current-id id + (unless (and exwm-state (not force)) + (let ((reply (xcb:+request-unchecked+reply exwm--connection + (make-instance 'xcb:icccm:get-WM_STATE :window id)))) + (when reply ;nil when destroyed + (setq exwm-state (or (slot-value reply 'state) + ;; Default to normal state + xcb:icccm:WM_STATE:NormalState))))))) + +(defun exwm--on-PropertyNotify (data synthetic) + "Handle PropertyNotify event." + (let ((obj (make-instance 'xcb:PropertyNotify)) + atom window state + buffer) + (xcb:unmarshal obj data) + (setq id (slot-value obj 'window) + atom (slot-value obj 'atom) + exwm-input--timestamp (slot-value obj 'time) + state (slot-value obj 'state)) + (setq buffer (exwm--id->buffer id)) + (when (buffer-live-p buffer) + (with-current-buffer buffer + (cond ((= atom xcb:Atom:_NET_WM_WINDOW_TYPE) + (exwm--update-window-type id t)) + ((= atom xcb:Atom:WM_CLASS) + (exwm--update-class id t)) + ((= atom xcb:Atom:_NET_WM_NAME) + (exwm--update-utf8-title id t)) + ((= atom xcb:Atom:WM_NAME) + (exwm--update-ctext-title id t)) + ((= atom xcb:Atom:WM_TRANSIENT_FOR) + (exwm--update-transient-for id t)) + ((= atom xcb:Atom:WM_NORMAL_HINTS) + (exwm--update-normal-hints id t)) + ((= atom xcb:Atom:WM_HINTS) + (exwm--update-hints id t)) + ((= atom xcb:Atom:WM_PROTOCOLS) + (exwm--update-protocols id t)) + ((= atom xcb:Atom:WM_STATE) + (exwm--update-state id t)) + (t (exwm--log "Unhandled PropertyNotify: %s(%d)" + (x-get-atom-name atom) atom))))))) + +(defun exwm--on-ClientMessage (raw-data synthetic) + "Handle ClientMessage event." + (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)) + (cond + ;; _NET_WM_MOVERESIZE + ((= type xcb:Atom:_NET_WM_MOVERESIZE) + (let ((direction (elt data 2)) + (buffer (exwm--id->buffer id))) + (unless (and buffer (with-current-buffer buffer + (not exwm--floating-frame))) + (cond ((= direction + xcb:ewmh:_NET_WM_MOVERESIZE_SIZE_KEYBOARD) + ;; FIXME + ) + ((= direction + xcb:ewmh:_NET_WM_MOVERESIZE_MOVE_KEYBOARD) + ;; FIXME + ) + ((= direction xcb:ewmh:_NET_WM_MOVERESIZE_CANCEL) + (exwm-floating--stop-moveresize)) + (t (exwm-floating--start-moveresize id direction)))))) + ;; _NET_REQUEST_FRAME_EXTENTS + ((= type xcb:Atom:_NET_REQUEST_FRAME_EXTENTS) + (let ((buffer (exwm--id->buffer id)) + left right top bottom) + (if (or (not buffer) + (with-current-buffer buffer + (not exwm--floating-frame))) + (setq left 0 right 0 top 0 bottom 0) + (setq left exwm-floating-border-width + right exwm-floating-border-width + top (+ exwm-floating-border-width (window-header-line-height)) + bottom (+ exwm-floating-border-width + (window-mode-line-height)))) + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_FRAME_EXTENTS + :window id :left left :right right + :top top :bottom bottom))) + (xcb:flush exwm--connection)) + ;; _NET_WM_STATE + ((= type xcb:Atom:_NET_WM_STATE) + (let ((action (elt data 0)) + (props (list (elt data 1) (elt data 2))) + (buffer (exwm--id->buffer id)) + props-new) + (when buffer ;ensure it's managed + (with-current-buffer buffer + ;; _NET_WM_STATE_MODAL + (when (memq xcb:Atom:_NET_WM_STATE_MODAL props) + (cond ((= action xcb:ewmh:_NET_WM_STATE_ADD) + (unless exwm--floating-frame + (exwm-floating--set-floating id)) + (push xcb:Atom:_NET_WM_STATE_MODAL props-new)) + ((= action xcb:ewmh:_NET_WM_STATE_REMOVE) + (when exwm--floating-frame + (exwm-floating--unset-floating id))) + ((= action xcb:ewmh:_NET_WM_STATE_TOGGLE) + (if exwm--floating-frame + (exwm-floating--unset-floating id) + (exwm-floating--set-floating id) + (push xcb:Atom:_NET_WM_STATE_MODAL props-new))))) + ;; _NET_WM_STATE_FULLSCREEN + (when (memq xcb:Atom:_NET_WM_STATE_FULLSCREEN props) + (cond ((= action xcb:ewmh:_NET_WM_STATE_ADD) + (unless exwm--fullscreen (exwm-layout-set-fullscreen id)) + (push xcb:Atom:_NET_WM_STATE_FULLSCREEN props-new)) + ((= action xcb:ewmh:_NET_WM_STATE_REMOVE) + (when exwm--fullscreen (exwm-layout-unset-fullscreen id))) + ((= action xcb:ewmh:_NET_WM_STATE_TOGGLE) + (if exwm--fullscreen + (exwm-layout-unset-fullscreen id) + (exwm-layout-set-fullscreen id) + (push xcb:Atom:_NET_WM_STATE_FULLSCREEN props-new))))) + ;; _NET_WM_STATE_DEMANDS_ATTENTION + ;; 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))) + (unless (= idx exwm-workspace-current-index) + (set-frame-parameter exwm--frame 'exwm--urgency t) + (exwm-workspace--update-switch-history)))) + ;; xcb:ewmh:_NET_WM_STATE_REMOVE? + ;; xcb:ewmh:_NET_WM_STATE_TOGGLE? + ) + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_WM_STATE + :window id :data (vconcat props-new))) + (xcb:flush exwm--connection))))) + ((= type xcb:Atom:WM_PROTOCOLS) + (let ((type (elt data 0))) + (cond ((= type xcb:Atom:_NET_WM_PING) + (setq exwm--ping-lock nil)) + (t (exwm--log "Unhandled WM_PROTOCOLS of type: %d" type))))) + (t (exwm--log "Unhandled client message: %s" obj))))) + +(defun exwm--init-icccm-ewmh () + "Initialize ICCCM/EWMH support." + ;; Handle PropertyNotify event + (xcb:+event exwm--connection 'xcb:PropertyNotify 'exwm--on-PropertyNotify) + ;; Handle relevant client messages + ;; FIXME: WM_STATE client messages (normal => iconic) + ;; WM_COLORMAP_NOTIFY + (xcb:+event exwm--connection 'xcb:ClientMessage 'exwm--on-ClientMessage) + ;; Set _NET_SUPPORTED + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_SUPPORTED + :window exwm--root + :data (vector xcb:Atom:_NET_SUPPORTED + xcb:Atom:_NET_NUMBER_OF_DESKTOPS + xcb:Atom:_NET_DESKTOP_VIEWPORT + xcb:Atom:_NET_CURRENT_DESKTOP + xcb:Atom:_NET_WORKAREA + xcb:Atom:_NET_SUPPORTING_WM_CHECK + xcb:Atom:_NET_VIRTUAL_ROOTS + xcb:Atom:_NET_WM_MOVERESIZE + xcb:Atom:_NET_REQUEST_FRAME_EXTENTS + xcb:Atom:_NET_FRAME_EXTENTS + xcb:Atom:_NET_WM_NAME + ;; + xcb:Atom:_NET_WM_WINDOW_TYPE + xcb:Atom:_NET_WM_WINDOW_TYPE_TOOLBAR + xcb:Atom:_NET_WM_WINDOW_TYPE_MENU + xcb:Atom:_NET_WM_WINDOW_TYPE_UTILITY + xcb:Atom:_NET_WM_WINDOW_TYPE_SPLASH + xcb:Atom:_NET_WM_WINDOW_TYPE_DIALOG + xcb:Atom:_NET_WM_WINDOW_TYPE_DROPDOWN_MENU + xcb:Atom:_NET_WM_WINDOW_TYPE_POPUP_MENU + xcb:Atom:_NET_WM_WINDOW_TYPE_TOOLTIP + xcb:Atom:_NET_WM_WINDOW_TYPE_NOTIFICATION + xcb:Atom:_NET_WM_WINDOW_TYPE_COMBO + xcb:Atom:_NET_WM_WINDOW_TYPE_DND + xcb:Atom:_NET_WM_WINDOW_TYPE_NORMAL + ;; + xcb:Atom:_NET_WM_STATE + xcb:Atom:_NET_WM_STATE_MODAL + xcb:Atom:_NET_WM_STATE_FULLSCREEN + xcb:Atom:_NET_WM_STATE_DEMANDS_ATTENTION + ;; FIXME: more? + ))) + ;; Create a child window for setting _NET_SUPPORTING_WM_CHECK + (let ((new-id (xcb:generate-id exwm--connection))) + (xcb:+request exwm--connection + (make-instance 'xcb:CreateWindow + :depth 0 :wid new-id :parent exwm--root + :x -1 :y -1 :width 1 :height 1 + :border-width 0 :class xcb:WindowClass:CopyFromParent + :visual 0 :value-mask xcb:CW:OverrideRedirect + :override-redirect 1)) + (dolist (i (list exwm--root new-id)) + ;; Set _NET_SUPPORTING_WM_CHECK + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_SUPPORTING_WM_CHECK + :window i :data new-id)) + ;; Set _NET_WM_NAME + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_WM_NAME + :window i :data "EXWM")))) + ;; Set _NET_NUMBER_OF_DESKTOPS + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_NUMBER_OF_DESKTOPS + :window exwm--root :data exwm-workspace-number)) + ;; Set _NET_DESKTOP_VIEWPORT + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_DESKTOP_VIEWPORT + :window exwm--root + :data (make-vector (* 2 exwm-workspace-number) 0))) + ;; Set _NET_WORKAREA (with minibuffer and bottom mode-line excluded) + (let* ((workareas + (vconcat (window-absolute-pixel-edges (get-largest-window t)))) + (workareas (mapconcat (lambda (i) workareas) + (make-list exwm-workspace-number 0) []))) + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_WORKAREA + :window exwm--root :data workareas))) + ;; Set _NET_VIRTUAL_ROOTS + (xcb:+request exwm--connection + (make-instance 'xcb:ewmh:set-_NET_VIRTUAL_ROOTS + :window exwm--root + :data (vconcat (mapcar + (lambda (i) + (frame-parameter i 'exwm-window-id)) + exwm-workspace--list)))) + (xcb:flush exwm--connection)) + +(defvar exwm-init-hook nil + "Normal hook run when EXWM has just finished initialization.") + +(defun exwm-init (&optional frame) + "Initialize EXWM." + (if (not (eq 'x (framep (or frame (selected-frame))))) + (exwm--log "Not running under X environment") + (unless exwm--connection + (setq exwm--connection (xcb:connect-to-socket)) + (set-process-query-on-exit-flag (slot-value exwm--connection 'process) + nil) ;prevent query message on exit + (setq exwm--root + (slot-value (car (slot-value + (xcb:get-setup exwm--connection) 'roots)) + 'root)) + (if (xcb:+request-checked+request-check exwm--connection + (make-instance 'xcb:ChangeWindowAttributes + :window exwm--root :value-mask xcb:CW:EventMask + :event-mask xcb:EventMask:SubstructureRedirect)) + ;; Other window manager is running + (progn (xcb:disconnect exwm--connection) + (setq exwm--connection nil) + (exwm-enable 'undo) + (exwm--log "Other window manager detected")) + ;; Initialize ICCCM/EWMH support + ;; (xcb:icccm:init exwm--connection) + (xcb:ewmh:init exwm--connection) + (exwm--lock) + (exwm-workspace--init) + (exwm--init-icccm-ewmh) + (exwm-layout--init) + (exwm-floating--init) + (exwm-manage--init) + (exwm-input--init) + (exwm--unlock) + ;; Disable events during new frame creation + (add-hook 'before-make-frame-hook 'exwm--lock) + (add-hook 'after-make-frame-functions 'exwm--unlock) + ;; Manage exiting windows + (exwm-manage--scan) + (run-hooks 'exwm-init-hook))))) + +(defvar exwm-mode-map + (let ((map (make-sparse-keymap))) + (define-key map "\C-ck" 'exwm-input-release-keyboard) + (define-key map "\C-cf" 'exwm-layout-set-fullscreen) + (define-key map "\C-cm" 'exwm-floating-toggle-floating) + (define-key map "\C-cq" 'exwm-input-send-next-key) + (define-key map "\C-cv" 'exwm-workspace-move-window) + map) + "Keymap for `exwm-mode'.") + +(define-derived-mode exwm-mode nil "EXWM" + "Major mode for managing X windows. + +\\{exwm-mode-map}" + ;; Internal variables + (make-local-variable 'exwm--id) ;window id + (set (make-local-variable 'exwm--frame) nil) ;workspace frame + (set (make-local-variable 'exwm--floating-frame) nil) ;floating frame + (set (make-local-variable 'exwm--floating-edges) nil) ;four edges + (set (make-local-variable 'exwm--fullscreen) nil) ;used in fullscreen + (set (make-local-variable 'exwm--floating-frame-geometry) nil) ;in fullscreen + (set (make-local-variable 'exwm--fixed-size) nil) ;fixed size + ;; Properties + (set (make-local-variable 'exwm-window-type) nil) + (set (make-local-variable 'exwm--geometry) nil) + (set (make-local-variable 'exwm-class-name) nil) + (set (make-local-variable 'exwm-instance-name) nil) + (set (make-local-variable 'exwm-title) nil) + (set (make-local-variable 'exwm--title-is-utf8) nil) + (set (make-local-variable 'exwm-transient-for) nil) + ;; _NET_WM_NORMAL_HINTS + (set (make-local-variable 'exwm--normal-hints-x) nil) + (set (make-local-variable 'exwm--normal-hints-y) nil) + (set (make-local-variable 'exwm--normal-hints-width) nil) + (set (make-local-variable 'exwm--normal-hints-height) nil) + (set (make-local-variable 'exwm--normal-hints-min-width) nil) + (set (make-local-variable 'exwm--normal-hints-min-height) nil) + (set (make-local-variable 'exwm--normal-hints-max-width) nil) + (set (make-local-variable 'exwm--normal-hints-max-height) nil) + ;; (set (make-local-variable 'exwm--normal-hints-win-gravity) nil) + ;; WM_HINTS + (set (make-local-variable 'exwm--hints-input) nil) ;FIXME + (set (make-local-variable 'exwm--hints-urgency) nil) + ;; + (set (make-local-variable 'exwm--protocols) nil) + (set (make-local-variable 'exwm-state) nil) + ;; Change major-mode is not allowed + (set (make-local-variable 'change-major-mode-hook) 'kill-buffer) + ;; + (setq mode-name + '(:eval (propertize "EXWM" 'face + (when (cl-some (lambda (i) + (frame-parameter i + 'exwm--urgency)) + exwm-workspace--list) + 'font-lock-warning-face)))) + ;; Kill buffer -> close window + (set (make-local-variable 'kill-buffer-query-functions) + (list (lambda () + (exwm-manage--close-window exwm--id (current-buffer)) + nil))) + (setq buffer-read-only t + left-margin-width nil + right-margin-width nil + left-fringe-width 0 + right-fringe-width 0 + vertical-scroll-bar nil)) + +(defun exwm-enable (&optional undo) + "Enable/Disable EXWM" + (setq frame-resize-pixelwise t) ;mandatory; before init + (if (eq undo 'undo) + (progn (remove-hook 'window-setup-hook 'exwm-init) + (remove-hook 'after-make-frame-functions 'exwm-init)) + (add-hook 'window-setup-hook 'exwm-init t) ;for Emacs + (add-hook 'after-make-frame-functions 'exwm-init t))) ;for Emacs Client + + + +(provide 'exwm) + +;;; exwm.el ends here |