diff options
-rw-r--r-- | exwm-cm.el | 1778 |
1 files changed, 1778 insertions, 0 deletions
diff --git a/exwm-cm.el b/exwm-cm.el new file mode 100644 index 000000000000..8e019bbc7523 --- /dev/null +++ b/exwm-cm.el @@ -0,0 +1,1778 @@ +;;; exwm-cm.el --- Compositing Manager for EXWM -*- lexical-binding: t -*- + +;; Copyright (C) 2016 Free Software Foundation, Inc. + +;; Author: Chris Feng <chris.w.feng@gmail.com> + +;; This file is part of GNU Emacs. + +;; GNU Emacs 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. + +;; GNU Emacs 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This module provides a compositing manager (CM) for EXWM, mainly to +;; enable transparency support. + +;; Usage: +;; Add following lines to .emacs and modify accordingly: +;; +;; (require 'exwm-cm) +;; ;; Make all Emacs frames opaque. +;; (setq window-system-default-frame-alist '((x . ((alpha . 100))))) +;; ;; Assign everything else a 80% opacity. +;; (setq exwm-cm-opacity 80) +;; (exwm-cm-enable) +;; +;; With the last line this CM would be started with EXWM. You can also +;; start and stop this CM with `exwm-cm-start' and `exwm-cm-stop' at any +;; time. + +;; Theory: +;; Due to its unique way of managing X windows, EXWM can not work with +;; any existing CMs. And this CM, designed specifically for EXWM, +;; probably won't work well with other WMs, too. The theories behind +;; all CMs are basically the same, some peculiarities of this CM are +;; summarized as the following sections. + +;; + Data structures: +;; This CM organizes all X windows concerned with compositing in a +;; tree hierarchy. Below is a stripped-down version of such tree with +;; each node representing an X window (except the root placeholder), +;; +;; (nil +;; (root-xwin +;; (unmanaged-xwin) +;; (workspace-container +;; (unmanaged-xwin) +;; (xwin-container +;; (xwin) +;; (floating-frame-container +;; (floating-frame))) +;; (xwin-container +;; (xwin)) +;; (workspace-frame-container +;; (workspace-frame))) +;; (minibuffer-frame-container +;; (minibuffer-frame)))) +;; +;; where +;; - nodes with non-nil CDRs are containers, +;; - siblings are arranged in stacking order (top to bottom), +;; - and "managed" and "unmanaged" are in WM's sense. +;; +;; During a painting process, the tree is traversed starting from the +;; root node, with each leaf visited and painted. The attributes of +;; each X window (position, size, etc) are recorded as an instance of +;; class `exwm-cm--attr'. Such instance is associated with the +;; corresponding X window ID through a hash table. The instance also +;; contains a slot pointing to a subtree of the aforementioned tree, +;; with the root node being the parent of the X window. This makes it +;; convenient to carry out operations such as insertion, deletion, +;; restacking and reparenting. + +;; + Compositing strategies: +;; - Only leaves are painted, since branches (containers) are always +;; invisible. +;; - The root X window is painted separately. +;; - Siblings below a workspace frame container are not painted; they +;; are considered hidden. +;; - Only the top workspace in one (RandR) output is painted. +;; - Workspace frames and floating frames are always clipped by its +;; Emacs windows displaying `exwm-mode' buffers, therefore they +;; don't block X windows. + +;; Reference: +;; + xcompmgr (http://cgit.freedesktop.org/xorg/app/xcompmgr/) + +;;; Code: + +(require 'xcb-composite) +(require 'xcb-damage) +(require 'xcb-ewmh) +(require 'xcb-icccm) +(require 'xcb-renderutil) +(require 'xcb-shape) + +(require 'exwm-core) +(require 'exwm-workspace) +(require 'exwm-manage) + +(defconst exwm-cm--OPAQUE (float #xFFFFFFFF) + "The opacity value of the _NET_WM_WINDOW_OPACITY property.") +(defvar exwm-cm--_NET_WM_WINDOW_OPACITY nil "The _NET_WM_WINDOW_OPACITY atom.") +(defvar exwm-cm-opacity nil + "The default value of opacity when it's not explicitly specified. + +The value should be a floating number between 0 (transparent) and 100 +(opaque). A value of nil also means opaque.") + +(defvar exwm-cm--hash nil + "The hash table associating X window IDs to their attributes.") + +(defvar exwm-cm--conn nil "The X connection used by the CM.") +(defvar exwm-cm--buffer nil "The rendering buffer.") +(defvar exwm-cm--depth nil "Default depth.") +(defvar exwm-cm--clip-changed t "Whether clip has changed.") +(defvar exwm-cm--damages nil "All damaged regions.") +(defvar exwm-cm--expose-rectangles nil + "Used by Expose event handler to collect exposed regions.") + +(defvar exwm-cm--background nil "The background (render) picture.") +(defvar exwm-cm--background-atom-names '("_XROOTPMAP_ID" "_XSETROOT_ID") + "Property names for background pixmap.") +(defvar exwm-cm--background-atoms nil "Interned atoms of the property names.") + +(defun exwm-cm--get-opacity (xwin) + "Get the opacity of X window XWIN. + +The value is between 0 (fully transparent) to #xFFFFFFFF (opaque)." + (let ((reply (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:icccm:-GetProperty-single + :window xwin + :property exwm-cm--_NET_WM_WINDOW_OPACITY + :type xcb:Atom:CARDINAL)))) + ;; The X window might have already been destroyed. + (when reply + (slot-value reply 'value)))) + +(defun exwm-cm-set-opacity (xwin opacity) + "Set the opacity of X window XWIN to OPACITY. + +The value is between 0 (fully transparent) to 100 (opaque). + +If called interactively, XWIN would be the selected X window." + (interactive + (list (exwm--buffer->id (window-buffer)) + (read-number "Opacity (0 ~ 100): " 100))) + (when (and xwin + (<= 0 opacity 100)) + (setq opacity (round (* exwm-cm--OPAQUE (/ opacity 100.0)))) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:icccm:-ChangeProperty-single + :window xwin + :property exwm-cm--_NET_WM_WINDOW_OPACITY + :type xcb:Atom:CARDINAL + :data opacity)) + (xcb:flush exwm-cm--conn))) + +(defclass exwm-cm--attr () + ( + ;; The entity associated with this X window; can be a frame, a buffer + ;; or nil. + (entity :initform nil) + ;; The subtree of which the root node is the parent of this X window. + (tree :initarg :tree) + ;; Geometry. + (x :initarg :x) + (y :initarg :y) + (width :initarg :width) + (height :initarg :height) + ;; X window attributes. + (visual :initarg :visual) + (class :initarg :class) + ;; The opacity of this X window; can be 0 ~ #xFFFE or nil. + (opacity :initform nil) + ;; Determine whether this X window should be treated as opaque or + ;; transparent; can be nil (opaque), 'argb or 'transparent (both + ;; should be treated as transparent). + (mode :initform nil) + ;; The (render) picture of this X window. + (picture :initform nil) + ;; The 1x1 (render) picture with only alpha channel. + (alpha-picture :initform nil) + ;; Whether this X window is ever damaged. + (damaged :initform nil) + ;; The damage object monitoring this X window. + (damage :initarg :damage) + ;; The bounding region of this X window (can be irregular). + (border-size :initform nil) + ;; The rectangular bounding region of this X window. + (extents :initform nil) + ;; The region require repainting (used for transparent X windows). + (border-clip :initform nil) + ;; Shape-related parameters. + (shaped :initform nil) + (shape-x :initarg :shape-x) + (shape-y :initarg :shape-y) + (shape-width :initarg :shape-width) + (shape-height :initarg :shape-height)) + :documentation "Attributes of an X window.") + +(defsubst exwm-cm--xwin->attr (xwin) + "Get the attributes of X window XWIN." + (gethash xwin exwm-cm--hash)) + +(defsubst exwm-cm--get-tree (xwin) + "Get the subtree of the parent of X window XWIN." + (slot-value (exwm-cm--xwin->attr xwin) 'tree)) + +(defsubst exwm-cm--set-tree (xwin tree) + "Reparent X window XWIN to another tree TREE." + (setf (slot-value (exwm-cm--xwin->attr xwin) 'tree) tree)) + +(defsubst exwm-cm--get-parent (xwin) + "Get the parent of X window XWIN." + (car (exwm-cm--get-tree xwin))) + +(defsubst exwm-cm--get-siblings (xwin) + "Get a list of subtrees of the siblings of X window XWIN" + (cdr (exwm-cm--get-tree xwin))) + +(defsubst exwm-cm--get-subtree (xwin) + "Get the subtree of which the X window XWIN is the root node." + (assq xwin (exwm-cm--get-siblings xwin))) + +(defun exwm-cm--create-attr (xwin tree x y width height) + "Create attributes for X window XWIN. + +TREE is the subtree and the parent of this X window is the tree's root. +X and Y specify the position with regard to the root X window. WIDTH +and HEIGHT specify the size of the X window." + (let (visual class map-state damage attr) + (cond + ((= xwin exwm--root) + ;; Redirect all subwindows to off-screen storage. + (xcb:+request exwm-cm--conn + (make-instance 'xcb:composite:RedirectSubwindows + :window exwm--root + :update xcb:composite:Redirect:Manual)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:ChangeWindowAttributes + :window xwin + :value-mask xcb:CW:EventMask + :event-mask (logior xcb:EventMask:StructureNotify + xcb:EventMask:PropertyChange + xcb:EventMask:SubstructureNotify + xcb:EventMask:Exposure))) + (setq visual (slot-value (car (slot-value (xcb:get-setup exwm-cm--conn) + 'roots)) + 'root-visual) + class xcb:WindowClass:InputOutput)) + ((eq xwin exwm-manage--desktop) + ;; Ignore any desktop; paint the background ourselves. + (setq visual 0 + class xcb:WindowClass:InputOnly + map-state xcb:MapState:Unmapped)) + (t + ;; Redirect this window to off-screen storage, or the content + ;; would be mirrored to its parent. + (xcb:+request exwm-cm--conn + (make-instance 'xcb:composite:RedirectWindow + :window xwin + :update xcb:composite:Redirect:Manual)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:ChangeWindowAttributes + :window xwin + :value-mask xcb:CW:EventMask + :event-mask (logior xcb:EventMask:StructureNotify + xcb:EventMask:PropertyChange))) + (let ((reply (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:GetWindowAttributes + :window xwin)))) + (if reply + (with-slots ((visual* visual) + (class* class) + (map-state* map-state)) + reply + (setq visual visual* + class class* + map-state map-state*)) + ;; The X window has been destroyed actually. It'll get + ;; removed by a DestroyNotify event. + (setq visual 0 + class xcb:WindowClass:InputOnly + map-state xcb:MapState:Unmapped))) + (when (/= class xcb:WindowClass:InputOnly) + (setq damage (xcb:generate-id exwm-cm--conn)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:damage:Create + :damage damage + :drawable xwin + :level xcb:damage:ReportLevel:NonEmpty)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:shape:SelectInput + :destination-window xwin + :enable 1))))) + (setq attr (make-instance 'exwm-cm--attr + :tree tree + :x x + :y y + :width width + :height height + :visual visual + :class class + :damage damage + :shape-x x + :shape-y y + :shape-width width + :shape-height height)) + (puthash xwin attr exwm-cm--hash) + (unless (or (= xwin exwm--root) + (= class xcb:WindowClass:InputOnly)) + (exwm-cm--update-opacity xwin) + (when (= map-state xcb:MapState:Viewable) + (exwm-cm--map-xwin xwin t))))) + +(defun exwm-cm--update-geometry (xwin x y width height &optional above-sibling) + "Update the geometry of X window XWIN. + +X, Y, WIDTH and HEIGHT have the same meaning with the arguments used in +`exwm-cm--create-attr'. If ABOVE-SIBLING is non-nil, restack XWIN with +`exwm-cm--restack.'" + (with-slots ((x* x) + (y* y) + (width* width) + (height* height) + extents shaped shape-x shape-y shape-width shape-height) + (exwm-cm--xwin->attr xwin) + (let ((stack-changed (and above-sibling + (exwm-cm--restack xwin above-sibling))) + (position-changed (or (and x (/= x x*)) + (and y (/= y y*)))) + (size-changed (or (and width (/= width width*)) + (and height (/= height height*)))) + subtree dx dy damage new-extents) + (when position-changed + (setq subtree (exwm-cm--get-subtree xwin) + dx (- x x*) + dy (- y y*)) + (dolist (node (cdr subtree)) + (with-slots (x y) (exwm-cm--xwin->attr (car node)) + (exwm--log "(CM) #x%X(*): @%+d%+d => @%+d%+d" + (car node) x y (+ x dx) (+ y dy)) + (exwm-cm--update-geometry (car node) (+ x dx) (+ y dy) nil nil))) + (exwm--log "(CM) #x%X: @%+d%+d => @%+d%+d" xwin x* y* x y) + (setf x* x + y* y) + (cl-incf shape-x dx) + (cl-incf shape-y dy)) + (when size-changed + (setf width* width + height* height) + (unless shaped + (setf shape-width width + shape-height height))) + (when (or stack-changed position-changed size-changed) + (setq damage (xcb:generate-id exwm-cm--conn) + new-extents (xcb:generate-id exwm-cm--conn)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CreateRegion + :region damage + :rectangles nil)) + (when extents + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CopyRegion + :source extents + :destination damage))) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CreateRegion + :region new-extents + :rectangles (list (make-instance 'xcb:RECTANGLE + :x x* + :y y* + :width width* + :height height*)))) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:UnionRegion + :source1 damage + :source2 new-extents + :destination damage)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:DestroyRegion + :region new-extents)) + (exwm-cm--add-damage damage))))) + +(defun exwm-cm--update-opacity (xwin) + "Update the opacity of X window XWIN." + (with-slots (visual opacity mode alpha-picture extents) + (exwm-cm--xwin->attr xwin) + (let (format forminfo) + ;; Get the opacity. + (setf opacity (exwm-cm--get-opacity xwin)) + (if opacity + (setf opacity (round (* #xFFFF (/ opacity exwm-cm--OPAQUE)))) + (when (numberp exwm-cm-opacity) + (setf opacity (round (* #xFFFF (/ exwm-cm-opacity 100.0)))))) + (when (and opacity + (>= opacity #xFFFF)) + (setf opacity nil)) + ;; Determine the mode of the X window. + (setq format (xcb:renderutil:find-visual-format + (xcb:renderutil:query-formats exwm-cm--conn) visual)) + (when format + (catch 'break + (dolist (f (slot-value (xcb:renderutil:query-formats exwm-cm--conn) + 'formats)) + (when (eq format (slot-value f 'id)) + (setq forminfo f) + (throw 'break nil))))) + (if (and forminfo + (eq xcb:render:PictType:Direct (slot-value forminfo 'type)) + (/= 0 (slot-value (slot-value forminfo 'direct) 'alpha-mask))) + (setf mode 'argb) + (if opacity + (setf mode 'transparent) + (setf mode nil))) + ;; Clear resources. + (when alpha-picture + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:FreePicture + :picture alpha-picture)) + (setf alpha-picture nil)) + (when extents + (let ((damage (xcb:generate-id exwm-cm--conn))) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CreateRegion + :region damage + :rectangles nil)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CopyRegion + :source extents + :destination damage)) + (exwm-cm--add-damage damage)))))) + +(defsubst exwm-cm--push (newelt place) + "Similar to `push' but preserve the reference." + (let ((oldelt (car place))) + (setf (car place) newelt + (cdr place) (cons oldelt (cdr place))))) + +(defsubst exwm-cm--delq (elt list) + "Similar to `delq' but preserve the reference." + (if (eq elt (car list)) + (setf (car list) (cadr list) + (cdr list) (cddr list)) + (delq elt list))) + +(defsubst exwm-cm--assq-delete-all (key alist) + "Similar to `assq-delete-all' but preserve the reference." + (when (eq key (caar alist)) + (setf (car alist) (cadr alist) + (cdr alist) (cddr alist))) + (assq-delete-all key alist)) + +(defun exwm-cm--create-tree (&optional xwin) + "Create a tree with XWIN being the root node." + (let (tree0 x0 y0 children containers) + ;; Get the position of this branch. + (if xwin + (with-slots (tree x y) (exwm-cm--xwin->attr xwin) + (setq tree0 (assq xwin (cdr tree)) + x0 x + y0 y)) + (setq tree0 (list nil) + x0 0 + y0 0)) + ;; Get children nodes. + (if (null xwin) + (setq children (list exwm--root)) + (setq children + (reverse (slot-value (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:QueryTree + :window xwin)) + 'children)))) + ;; Get container nodes. + ;; Floating frame containers are determined dynamically. + (cond + ((null xwin) + (setq containers `((,exwm--root)))) + ((= xwin exwm--root) + ;; Workspace containers and the minibuffer frame container. + (setq containers (mapcar (lambda (f) + (cons (frame-parameter f 'exwm-workspace) f)) + exwm-workspace--list)) + (when (exwm-workspace--minibuffer-own-frame-p) + (push (cons + (frame-parameter exwm-workspace--minibuffer 'exwm-container) + exwm-workspace--minibuffer) + containers))) + ;; No containers in the minibuffer container. + ((and (exwm-workspace--minibuffer-own-frame-p) + (= xwin + (frame-parameter exwm-workspace--minibuffer 'exwm-container)))) + ((= exwm--root + (slot-value (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:QueryTree + :window xwin)) + 'parent)) + ;; Managed X window containers and the workspace frame container. + (let (frame) + (catch 'break + (dolist (f exwm-workspace--list) + (when (= xwin (frame-parameter f 'exwm-workspace)) + (setq frame f) + (throw 'break nil)))) + (cl-assert frame) + (dolist (pair exwm--id-buffer-alist) + (with-current-buffer (cdr pair) + (when (eq frame exwm--frame) + (push (cons exwm--container (cdr pair)) containers)))) + (push (cons (frame-parameter frame 'exwm-container) frame) + containers)))) + ;; Create subnodes. + (dolist (xwin children) + ;; Create attributes. + (let ((reply (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:GetGeometry + :drawable xwin)))) + ;; It's possible the X window has been destroyed. + (if (null reply) + (setq xwin nil) + (when reply + (with-slots (x y width height) reply + (exwm-cm--create-attr xwin tree0 + (+ x x0) (+ y y0) width height)) + ;; Insert the node. + (setcdr (or (last (cdr tree0)) tree0) `((,xwin)))))) + (cond + ((null xwin)) + ((assq xwin containers) + ;; A branch. Repeat the process. + (exwm-cm--create-tree xwin) + (let ((entity (cdr (assq xwin containers))) + entity-xwin) + (when entity + (setq entity-xwin (if (framep entity) + (frame-parameter entity 'exwm-outer-id) + (buffer-local-value 'exwm--id entity))) + (setf (slot-value (exwm-cm--xwin->attr entity-xwin) 'entity) entity + (slot-value (exwm-cm--xwin->attr xwin) 'entity) entity) + (let ((tmp (exwm-cm--get-parent entity-xwin))) + (when (/= xwin tmp) + ;; Workspace frame container. + (setf (slot-value (exwm-cm--xwin->attr tmp) 'entity) + entity)))))) + ((and (null containers) + (exwm--id->buffer xwin)) + ;; A leaf but a floating frame container might follow. + (with-current-buffer (exwm--id->buffer xwin) + (when exwm--floating-frame + (push (cons (frame-parameter exwm--floating-frame 'exwm-container) + exwm--floating-frame) + containers)))))))) + +(defun exwm-cm--restack (xwin above-sibling) + "Restack X window XWIN so as to it's exactly on top of ABOVE-SIBLING." + (let ((siblings (exwm-cm--get-siblings xwin)) + node tmp) + (unless (= 1 (length siblings)) + (setq node (assq xwin siblings)) + (if (= above-sibling xcb:Window:None) + ;; Put at bottom. + (unless (eq node (cdr (last siblings))) + (exwm-cm--delq node siblings) + (setcdr (last siblings) (list node)) + ;; Set the return value. + t) + ;; Insert before the sibling. + (setq tmp siblings) + (while (and tmp + (/= above-sibling (caar tmp))) + (setq tmp (cdr tmp))) + (cl-assert tmp) + ;; Check if it's already at the requested position. + (unless (eq tmp (cdr siblings)) + (exwm-cm--delq node siblings) + (exwm-cm--push node tmp) + ;; Set the return value. + t))))) + +(declare-function exwm-layout--iconic-state-p "exwm-layout.el" (&optional id)) + +(defun exwm-cm--paint-tree (tree region &optional force-opaque frame-clip) + "Paint the tree TREE, with REGION specifying the clipping region. + +If FORCE-OPAQUE is non-nil, all X windows painted in this tree is +assumed opaque. FRAME-CLIP specifies the region should be clipped when +painting a frame." + (unless tree + (setq tree (exwm-cm--get-tree exwm--root))) + (let ((root (car tree)) + xwin attr entity current output outputs queue rectangles) + ;; Paint subtrees. + (catch 'break + (dolist (subtree (cdr tree)) + (setq xwin (car subtree) + attr (exwm-cm--xwin->attr xwin)) + (cond + ;; Skip destroyed X windows. + ((null attr)) + ;; Skip InputOnly X windows. + ((= xcb:WindowClass:InputOnly + (slot-value attr 'class))) + ((and (eq root exwm--root) + (frame-live-p (setq entity (slot-value attr 'entity))) + (if (eq entity exwm-workspace--minibuffer) + ;; Skip the minibuffer if the current workspace is + ;; already painted. + (unless (exwm-workspace--minibuffer-attached-p) + current) + ;; Skip lower workspaces on visited RandR output. + ;; If RandR is not enabled, it'll just paint the first. + (memq (setq output (frame-parameter entity + 'exwm-randr-output)) + outputs)))) + ((cdr subtree) + ;; Paint the subtree. + (setq entity (slot-value attr 'entity)) + (let (fullscreen clip) + (cond + ((buffer-live-p entity) + (with-current-buffer entity + ;; Collect frame clip but exclude fullscreen and + ;; floating X windows. + (setq fullscreen (memq xcb:Atom:_NET_WM_STATE_FULLSCREEN + exwm--ewmh-state)) + (when (and (null fullscreen) + ;; In case it's hidden. + (null (exwm-layout--iconic-state-p)) + ;; The buffer of a floating X windows is not + ;; displayed on a workspace frame. + (null exwm--floating-frame) + ;; Opaque regions are always clipped. + (slot-value (exwm-cm--xwin->attr xwin) 'mode)) + ;; Prepare rectangles to clip the workspace frame. + (with-slots (x y width height) (exwm-cm--xwin->attr xwin) + (push (make-instance 'xcb:RECTANGLE + :x x + :y y + :width width + :height height) + rectangles))))) + ((and rectangles + (frame-live-p entity)) + ;; Prepare region to clip the frame. + (setq clip (xcb:generate-id exwm-cm--conn)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CreateRegion + :region clip + :rectangles rectangles)))) + (setq queue + (nconc (exwm-cm--paint-tree subtree region fullscreen clip) + queue)) + (when fullscreen + ;; Fullscreen X windows are always opaque thus occludes + ;; anything in this workspace. + (throw 'break 'fullscreen)) + (when clip + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:DestroyRegion + :region clip)))) + (if (not (eq root exwm--root)) + ;; Avoid painting any siblings below the workspace frame + ;; container. + (when (exwm-workspace--workspace-p (slot-value attr 'entity)) + (throw 'break nil)) + ;; Save some status. + (when (and (frame-live-p entity) + (not (eq entity exwm-workspace--minibuffer))) + (push output outputs) + (when (eq entity exwm-workspace--current) + (setq current t))))) + ((and force-opaque + (slot-value attr 'damaged)) + (exwm-cm--paint-opaque xwin region t)) + ((slot-value attr 'damaged) + ;; Paint damaged leaf. + (setq entity (slot-value attr 'entity)) + (when (slot-value attr 'mode) + (push xwin queue)) + (cond + ((buffer-live-p entity) + (with-current-buffer entity + (cl-assert (= xwin exwm--id)) + (when (and exwm--floating-frame + ;; Opaque regions are always clipped. + (slot-value (exwm-cm--xwin->attr xwin) 'mode)) + ;; Prepare rectangles to clip the floating frame. + (with-slots (x y width height) (exwm-cm--xwin->attr xwin) + (push (make-instance 'xcb:RECTANGLE + :x x + :y y + :width width + :height height) + rectangles))))) + ((and frame-clip + (frame-live-p entity)) + ;; Apply frame clip. + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:IntersectRegion + :source1 region + :source2 frame-clip + :destination frame-clip)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:SubtractRegion + :source1 region + :source2 frame-clip + :destination region)))) + (exwm-cm--paint-opaque xwin region) + (when (and frame-clip + (frame-live-p entity)) + ;; Restore frame clip. + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:UnionRegion + :source1 region + :source2 frame-clip + :destination region))))))) + ;; Return the queue. + queue)) + +(defun exwm-cm--paint-opaque (xwin region &optional force-opaque) + "Paint an X window XWIN clipped by region REGION if XWIN is opaque. + +Also update the attributes of XWIN and clip the region." + (with-slots (x y width height visual mode picture + border-size extents border-clip) + (exwm-cm--xwin->attr xwin) + ;; Prepare the X window picture. + (unless picture + (setf picture (xcb:generate-id exwm-cm--conn)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:CreatePicture + :pid picture + :drawable xwin + :format (xcb:renderutil:find-visual-format + (xcb:renderutil:query-formats exwm-cm--conn) + visual) + :value-mask 0))) + ;; Clear cached resources if clip changed. + (when exwm-cm--clip-changed + (when border-size + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:DestroyRegion + :region border-size)) + (setf border-size nil)) + (when extents + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:DestroyRegion + :region extents)) + (setf extents nil)) + (when border-clip + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:DestroyRegion + :region border-clip)) + (setf border-clip nil))) + ;; Retrieve the border. + (unless border-size + (setf border-size (xcb:generate-id exwm-cm--conn)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CreateRegionFromWindow + :region border-size + :window xwin + :kind xcb:shape:SK:Bounding)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:TranslateRegion + :region border-size + :dx x + :dy y))) + ;; Retrieve the extents. + (unless extents + (setf extents (xcb:generate-id exwm-cm--conn)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CreateRegion + :region extents + :rectangles (list (make-instance 'xcb:RECTANGLE + :x x + :y y + :width width + :height height))))) + (cond + ((and mode + (null force-opaque)) + ;; Calculate clipped border for the transparent X window. + (unless border-clip + (setf border-clip (xcb:generate-id exwm-cm--conn)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CreateRegion + :region border-clip + :rectangles nil)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CopyRegion + :source region + :destination border-clip)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:IntersectRegion + :source1 border-clip + :source2 border-size + :destination border-clip)))) + (t + ;; Clip & render for the opaque X window. + ;; Set the clip region for the rendering buffer. + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:SetPictureClipRegion + :picture exwm-cm--buffer + :region region + :x-origin 0 + :y-origin 0)) + ;; Clip the region with border. + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:SubtractRegion + :source1 region + :source2 border-size + :destination region)) + ;; Render the picture to the buffer. + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:Composite + :op xcb:render:PictOp:Src + :src picture + :mask xcb:render:Picture:None + :dst exwm-cm--buffer + :src-x 0 + :src-y 0 + :mask-x 0 + :mask-y 0 + :dst-x x + :dst-y y + :width width + :height height)))))) + +(defun exwm-cm--paint-transparent (xwin) + "Paint a transparent X window XWIN." + (with-slots (x y width height opacity picture alpha-picture border-clip) + (exwm-cm--xwin->attr xwin) + ;; Prepare the alpha picture for transparent X windows. + (when (and opacity (null alpha-picture)) + (setf alpha-picture (xcb:generate-id exwm-cm--conn)) + (let ((pixmap (xcb:generate-id exwm-cm--conn))) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:CreatePixmap + :depth 8 + :pid pixmap + :drawable exwm--root + :width 1 + :height 1)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:CreatePicture + :pid alpha-picture + :drawable pixmap + :format (xcb:renderutil:find-standard + (xcb:renderutil:query-formats + exwm-cm--conn) + xcb:renderutil:PICT_STANDARD:A_8) + :value-mask xcb:render:CP:Repeat + :repeat 1)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:FillRectangles + :op xcb:render:PictOp:Src + :dst alpha-picture + :color (make-instance 'xcb:render:COLOR + :red 0 + :green 0 + :blue 0 + :alpha opacity) + :rects (list (make-instance 'xcb:RECTANGLE + :x 0 + :y 0 + :width 1 + :height 1)))))) + ;; Set the clip region for the rendering buffer. + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:SetPictureClipRegion + :picture exwm-cm--buffer + :region border-clip + :x-origin 0 + :y-origin 0)) + ;; Render the picture to the buffer. + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:Composite + :op xcb:render:PictOp:Over + :src picture + :mask (or alpha-picture xcb:render:Picture:None) + :dst exwm-cm--buffer + :src-x 0 + :src-y 0 + :mask-x 0 + :mask-y 0 + :dst-x x + :dst-y y + :width width + :height height)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:DestroyRegion + :region border-clip)) + (setf border-clip nil))) + +(defun exwm-cm--paint (&optional region) + "Paint the whole tree within clipping region REGION. + +If REGION is omitted, `exwm-cm--damages' is assumed. If it's t, paint +the whole screen." + ;; Prepare the clipping region. + (cond + ((null region) + (when exwm-cm--damages + (setq region exwm-cm--damages))) + ((eq region t) + (with-slots (width height) (exwm-cm--xwin->attr exwm--root) + (let ((rect (make-instance 'xcb:RECTANGLE + :x 0 + :y 0 + :width width + :height height))) + (setq region (xcb:generate-id exwm-cm--conn)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CreateRegion + :region region + :rectangles (list rect))))))) + (when region + ;; Prepare the rendering buffer. + (unless exwm-cm--buffer + (let ((pixmap (xcb:generate-id exwm-cm--conn)) + (picture (xcb:generate-id exwm-cm--conn))) + (setq exwm-cm--buffer picture) + (with-slots (width height visual) (exwm-cm--xwin->attr exwm--root) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:CreatePixmap + :depth exwm-cm--depth + :pid pixmap + :drawable exwm--root + :width width + :height height)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:CreatePicture + :pid picture + :drawable pixmap + :format (xcb:renderutil:find-visual-format + (xcb:renderutil:query-formats + exwm-cm--conn) + visual) + :value-mask 0))) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:FreePixmap + :pixmap pixmap)))) + (let (queue) + ;; Paint opaque X windows and update clipping region. + (setq queue (exwm-cm--paint-tree nil region)) + ;; Paint the background. + (exwm-cm--paint-background region) + ;; Paint transparent X windows. + (while queue + (exwm-cm--paint-transparent (pop queue)))) + ;; Submit changes. + (with-slots (width height picture) (exwm-cm--xwin->attr exwm--root) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:SetPictureClipRegion + :picture exwm-cm--buffer + :region xcb:xfixes:Region:None + :x-origin 0 + :y-origin 0)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:Composite + :op xcb:render:PictOp:Src + :src exwm-cm--buffer + :mask xcb:render:Picture:None + :dst picture + :src-x 0 + :src-y 0 + :mask-x 0 + :mask-y 0 + :dst-x 0 + :dst-y 0 + :width width + :height height))) + ;; Cleanup. + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:DestroyRegion + :region region)) + (when (eq region exwm-cm--damages) + (setq exwm-cm--damages nil)) + (setq exwm-cm--clip-changed nil) + (xcb:flush exwm-cm--conn))) + +(defun exwm-cm--paint-background (region) + "Paint the background." + (unless exwm-cm--background + (setq exwm-cm--background (xcb:generate-id exwm-cm--conn)) + (let (pixmap exist) + (catch 'break + (dolist (atom exwm-cm--background-atoms) + (with-slots (~lsb format value-len value) + (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:GetProperty + :delete 0 + :window exwm--root + :property atom + :type xcb:Atom:PIXMAP + :long-offset 0 + :long-length 4)) + (when (and (= format 32) + (= 1 value-len)) + (setq pixmap (if ~lsb + (xcb:-unpack-u4-lsb value 0) + (xcb:-unpack-u4 value 0))) + (setq exist t) + (throw 'break nil))))) + (unless pixmap + (setq pixmap (xcb:generate-id exwm-cm--conn)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:CreatePixmap + :depth exwm-cm--depth + :pid pixmap + :drawable exwm--root + :width 1 + :height 1))) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:CreatePicture + :pid exwm-cm--background + :drawable pixmap + :format (xcb:renderutil:find-visual-format + (xcb:renderutil:query-formats exwm-cm--conn) + (slot-value (exwm-cm--xwin->attr exwm--root) + 'visual)) + :value-mask xcb:render:CP:Repeat + :repeat 1)) + (unless exist + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:FillRectangles + :op xcb:render:PictOp:Src + :dst exwm-cm--background + :color (make-instance 'xcb:render:COLOR + :red #x8080 + :green #x8080 + :blue #x8080 + :alpha #xFFFF) + :rects (list (make-instance 'xcb:RECTANGLE + :x 0 + :y 0 + :width 1 + :height 1))))))) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:SetPictureClipRegion + :picture exwm-cm--buffer + :region region + :x-origin 0 + :y-origin 0)) + (with-slots (width height) (exwm-cm--xwin->attr exwm--root) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:Composite + :op xcb:render:PictOp:Src + :src exwm-cm--background + :mask xcb:render:Picture:None + :dst exwm-cm--buffer + :src-x 0 + :src-y 0 + :mask-x 0 + :mask-y 0 + :dst-x 0 + :dst-y 0 + :width width + :height height)))) + +(defun exwm-cm--map-xwin (xwin &optional silent) + "Prepare to map X window XWIN." + (let ((attr (exwm-cm--xwin->attr xwin))) + (setf (slot-value attr 'damaged) nil) + ;; Add to damage. + (when (slot-value attr 'extents) + (let ((damage (xcb:generate-id exwm-cm--conn))) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CreateRegion + :region damage + :rectangles nil)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CopyRegion + :source (slot-value attr 'extents) + :destination damage)) + (exwm-cm--add-damage damage)) + (unless silent + (exwm-cm--paint))))) + +(defun exwm-cm--on-MapNotify (data _synthetic) + "Handle MapNotify events." + (let ((obj (make-instance 'xcb:MapNotify)) + attr) + (xcb:unmarshal obj data) + (with-slots (event window) obj + (exwm--log "(CM) MapNotify: Try to map #x%X" window) + (setq attr (exwm-cm--xwin->attr window)) + (when (and attr + (/= (slot-value attr 'class) xcb:WindowClass:InputOnly) + (or (= event exwm--root) + ;; Filter out duplicated events. + (/= exwm--root (exwm-cm--get-parent window)))) + (exwm--log "(CM) MapNotify: Map") + (exwm-cm--map-xwin window))))) + +(defun exwm-cm--on-UnmapNotify (data _synthetic) + "Handle UnmapNotify events." + (let ((obj (make-instance 'xcb:UnmapNotify)) + attr) + (xcb:unmarshal obj data) + (with-slots (event window) obj + (exwm--log "(CM) UnmapNotify: Try to unmap #x%X" window) + (setq attr (exwm-cm--xwin->attr window)) + (when (and attr + (or (= event exwm--root) + ;; Filter out duplicated events. + (/= exwm--root (exwm-cm--get-parent window)))) + (exwm--log "(CM) UnmapNotify: Unmap") + (with-slots (picture damaged border-size extents border-clip) attr + (setf damaged nil) + (when picture + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:FreePicture + :picture picture)) + (setf picture nil)) + (when border-size + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:DestroyRegion + :region border-size)) + (setf border-size nil)) + (when extents + (exwm-cm--add-damage extents) + (setf extents nil)) + (when border-clip + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:DestroyRegion + :region border-clip)) + (setf border-clip nil))) + (setq exwm-cm--clip-changed t) + (exwm-cm--paint))))) + +(defun exwm-cm--on-CreateNotify (data _synthetic) + "Handle CreateNotify events." + (let ((obj (make-instance 'xcb:CreateNotify)) + tree0) + (xcb:unmarshal obj data) + (with-slots (window parent x y width height) obj + (exwm--log "(CM) CreateNotify: Create #x%X on #x%X @%sx%s%+d%+d" + window parent width height x y) + (cl-assert (= parent exwm--root)) + (cl-assert (null (exwm-cm--xwin->attr window))) + (setq tree0 (exwm-cm--get-subtree parent)) + (exwm-cm--create-attr window tree0 x y width height) + (if (cdr tree0) + (exwm-cm--push (list window) (cdr tree0)) + (setcdr tree0 `((,window))))))) + +(defun exwm-cm--on-ConfigureNotify (data synthetic) + "Handle ConfigureNotify events." + ;; Ignore synthetic ConfigureNotify events sent by the WM. + (unless synthetic + (let ((obj (make-instance 'xcb:ConfigureNotify))) + (xcb:unmarshal obj data) + (with-slots (event window above-sibling x y width height) obj + (exwm--log + "(CM) ConfigureNotify: Try to configure #x%X @%sx%s%+d%+d, above #x%X" + window width height x y above-sibling) + (cond + ((= window exwm--root) + (exwm--log "(CM) ConfigureNotify: Configure the root X window") + (when exwm-cm--buffer + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:FreePicture + :picture exwm-cm--buffer)) + (setq exwm-cm--buffer nil)) + (with-slots ((x* x) + (y* y) + (width* width) + (height* height)) + (exwm-cm--xwin->attr exwm--root) + (setf x* x + y* y + width* width + height* height)) + (exwm-cm--paint)) + ((null (exwm-cm--xwin->attr window))) + ((or (= event exwm--root) + ;; Filter out duplicated events. + (/= exwm--root (exwm-cm--get-parent window))) + (exwm--log "(CM) ConfigureNotify: Configure") + (with-slots ((x0 x) + (y0 y)) + (exwm-cm--xwin->attr (exwm-cm--get-parent window)) + (exwm-cm--update-geometry window (+ x x0) (+ y y0) width height + above-sibling)) + (setq exwm-cm--clip-changed t) + (exwm-cm--paint)) + (t + (exwm--log "(CM) ConfigureNotify: Skip event from #x%X" event))))))) + +(defun exwm-cm--destroy (xwin) + "Prepare to destroy X window XWIN." + (with-slots (tree picture alpha-picture damage + border-size extents border-clip) + (exwm-cm--xwin->attr xwin) + (cl-assert (assq xwin (cdr tree))) + (if (= 1 (length (cdr tree))) + (setcdr tree nil) + (exwm-cm--assq-delete-all xwin (cdr tree))) + (remhash xwin exwm-cm--hash) + ;; Release resources. + (when picture + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:FreePicture + :picture picture)) + (setf picture nil)) + (when alpha-picture + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:FreePicture + :picture alpha-picture)) + (setf alpha-picture nil)) + (when damage + (xcb:+request exwm-cm--conn + (make-instance 'xcb:damage:Destroy + :damage damage)) + (setf damage nil)) + (when border-size + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:DestroyRegion + :region border-size)) + (setf border-size nil)) + (when extents + (exwm-cm--add-damage extents) + (setf extents nil)) + (when border-clip + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:DestroyRegion + :region border-clip)) + (setf border-clip nil)))) + +(defun exwm-cm--on-DestroyNotify (data _synthetic) + "Handle DestroyNotify events." + (let ((obj (make-instance 'xcb:DestroyNotify)) + xwin) + (xcb:unmarshal obj data) + (setq xwin (slot-value obj 'window)) + (exwm--log "(CM) DestroyNotify: Try to destroy #x%X" xwin) + (when (exwm-cm--xwin->attr xwin) + (exwm--log "(CM) DestroyNotify: Destroy") + (exwm-cm--destroy xwin)))) + +(defun exwm-cm--on-CirculateNotify (data _synthetic) + "Handle CirculateNotify events." + (let ((obj (make-instance 'xcb:CirculateNotify)) + attr) + (xcb:unmarshal obj data) + (with-slots (event window place) obj + (setq attr (exwm-cm--xwin->attr window)) + (exwm--log "(CM) CirculateNotify: Try to circulate #x%X to %s" + window place) + (when (and attr + (or (= event exwm--root) + ;; Filter out duplicated events. + (/= exwm--root (exwm-cm--get-parent window)))) + (exwm--log "(CM) CirculateNotify: Circulate") + (exwm-cm--update-geometry window nil nil nil nil + (if (= place xcb:Circulate:LowerHighest) + xcb:Window:None + (caar (exwm-cm--get-siblings window)))) + (setq exwm-cm--clip-changed t) + (exwm-cm--paint))))) + +(defun exwm-cm--on-Expose (data _synthetic) + "Handle Expose events." + (let ((obj (make-instance 'xcb:Expose))) + (xcb:unmarshal obj data) + (with-slots (window x y width height count) obj + (when (eq window exwm--root) + (push (make-instance 'xcb:RECTANGLE + :x x + :y y + :width width + :height height) + exwm-cm--expose-rectangles)) + (when (= count 0) + (let ((region (xcb:generate-id exwm-cm--conn))) + (xcb:+request exwm-cm--conn + (xcb:xfixes:CreateRegion + :region region + :rectangles exwm-cm--expose-rectangles)) + (exwm-cm--add-damage region)) + (setq exwm-cm--expose-rectangles nil) + (exwm-cm--paint))))) + +(defun exwm-cm--on-PropertyNotify (data _synthetic) + "Handle PropertyNotify events." + (let ((obj (make-instance 'xcb:PropertyNotify))) + (xcb:unmarshal obj data) + (with-slots (window atom) obj + (cond + ((and (= window exwm--root) + (memq atom exwm-cm--background-atoms)) + (exwm--log "(CM) PropertyNotify: Update background") + (when exwm-cm--background + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:FreePicture + :picture exwm-cm--background)) + (setq exwm-cm--background nil) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:ClearArea + :exposures 1 + :window exwm--root + :x 0 + :y 0 + :width 0 + :height 0)) + (xcb:flush exwm-cm--conn))) + ((and (= atom exwm-cm--_NET_WM_WINDOW_OPACITY) + ;; Some applications also set this property on their parents. + (null (cdr (exwm-cm--get-subtree window)))) + (when (exwm-cm--xwin->attr window) + (exwm--log "(CM) PropertyNotify: Update opacity for #x%X" window) + (exwm-cm--update-opacity window) + (exwm-cm--paint))))))) + +(defun exwm-cm--prepare-container (xwin) + "Make X window XWIN a container by deselecting unnecessary events." + (with-slots (damage) (exwm-cm--xwin->attr xwin) + (when damage + (xcb:+request exwm-cm--conn + (make-instance 'xcb:damage:Destroy + :damage damage))) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:shape:SelectInput + :destination-window xwin + :enable 0)))) + +(defun exwm-cm--on-ReparentNotify (data _synthetic) + "Handle ReparentNotify events." + (let ((obj (make-instance 'xcb:ReparentNotify)) + tree tree0 grandparent great-grandparent entity) + (xcb:unmarshal obj data) + (with-slots (window parent x y) obj + (exwm--log "(CM) ReparentNotify: Try to reparent #x%X to #x%X @%+d%+d" + window parent x y) + (cond + ((null (exwm-cm--xwin->attr window)) + (when (eq parent exwm--root) + (exwm--log "(CM) ReparentNotify: Create on the root X window") + (let ((reply (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:GetGeometry + :drawable window)))) + (when reply + (with-slots (width height) reply + (setq tree0 (exwm-cm--get-subtree exwm--root)) + (exwm-cm--create-attr window tree0 x y width height) + (if (cdr tree0) + (exwm-cm--push (list window) (cdr tree0)) + (setcdr tree0 `((,window))))) + (exwm-cm--paint))))) + ((= parent (exwm-cm--get-parent window))) + (t + (unless (exwm-cm--xwin->attr parent) + ;; Only allow workspace frame here. + (setq grandparent + (slot-value (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:QueryTree + :window parent)) + 'parent)) + (cond + ((null (exwm-cm--xwin->attr grandparent)) + (exwm--log "(CM) ReparentNotify: Destroy (too deep)")) + ((and (= exwm--root + (setq great-grandparent (exwm-cm--get-parent grandparent))) + (setq tree0 (exwm-cm--get-subtree grandparent)) + (or (setq entity (exwm--id->buffer window)) + (null (cdr tree0)))) + ;; Reparent a workspace frame or an X window into its + ;; container. + (exwm--debug + (if entity + (exwm--log "(CM) ReparentNotify: \ +Create implicit X window container") + (exwm--log "(CM) ReparentNotify: \ +Create implicit workspace frame container"))) + (unless entity + (setq entity 'workspace-frame)) + (with-slots ((x0 x) + (y0 y)) + (exwm-cm--xwin->attr grandparent) + (with-slots (x y width height) + (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:GetGeometry + :drawable parent)) + (exwm-cm--create-attr parent tree0 + (+ x x0) (+ y y0) width height))) + (if (null (cdr tree0)) + (setcdr tree0 `((,parent))) + ;; The stacking order of the parent is unknown. + (let* ((siblings + (slot-value (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:QueryTree + :window grandparent)) + 'children))) + (cl-assert (memq parent siblings)) + (if (= parent (car siblings)) + ;; At the bottom. + (setcdr (last (cdr tree0)) `((,parent))) + ;; Insert it. + (exwm-cm--push (list parent) + ;; The stacking order is reversed. + (nthcdr (- (length siblings) 1 + (cl-position parent siblings)) + (cdr tree0))))))) + ((and (= exwm--root + (exwm-cm--get-parent great-grandparent)) + (setq tree0 (exwm-cm--get-subtree grandparent)) + (= 1 (length (cdr tree0))) + (exwm--id->buffer (caar (cdr tree0)))) + ;; Reparent a floating frame into its container. + (exwm--log "(CM) ReparentNotify: Create floating frame container") + (setq entity 'floating-frame) + (with-slots ((x0 x) + (y0 y)) + (exwm-cm--xwin->attr grandparent) + (with-slots (x y width height) + (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:GetGeometry + :drawable parent)) + (exwm-cm--create-attr parent tree0 + (+ x x0) (+ y y0) width height))) + (nconc (cdr tree0) `((,parent)))) + (t + (exwm--log "(CM) ReparentNotify: Destroy") + (exwm-cm--destroy window)))) + ;; Ensure there's a valid parent. + (when (exwm-cm--xwin->attr parent) + (exwm--log "(CM) ReparentNotify: Reparent") + (when (null (cdr (exwm-cm--get-subtree parent))) + ;; The parent is a new container. + (exwm-cm--prepare-container parent)) + (setq tree (exwm-cm--get-subtree window)) + (let ((tree (exwm-cm--get-tree window))) + (if (= 1 (length (cdr tree))) + (setcdr tree nil) + (exwm-cm--assq-delete-all window (cdr tree)))) + (setq tree0 (exwm-cm--get-subtree parent)) + (exwm-cm--set-tree window tree0) + ;; The size might have already changed (e.g. when reparenting + ;; a workspace frame). + (let ((reply (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:GetGeometry + :drawable window)))) + ;; The X window might have already been destroyed. + (when reply + (with-slots (width height) reply + (with-slots ((x0 x) + (y0 y)) + (exwm-cm--xwin->attr parent) + (exwm-cm--update-geometry window (+ x x0) (+ y y0) + width height))))) + (when entity + ;; Decide frame entity. + (when (symbolp entity) + (catch 'break + (dolist (f (if (eq entity 'workspace-frame) + exwm-workspace--list + (frame-list))) + (when (eq window (frame-parameter f 'exwm-outer-id)) + (setq entity f) + (throw 'break nil)))) + (when (exwm-workspace--workspace-p entity) + ;; The grandparent is a new workspace container. + (exwm-cm--prepare-container grandparent) + (setf (slot-value (exwm-cm--xwin->attr grandparent) 'entity) + entity))) + (setf (slot-value (exwm-cm--xwin->attr parent) 'entity) entity) + (setf (slot-value (exwm-cm--xwin->attr window) 'entity) entity)) + (if (cdr tree0) + (exwm-cm--push tree (cdr tree0)) + (setcdr tree0 `(,tree))) + (exwm-cm--paint))))))) + +(defun exwm-cm--add-damage (damage) + "Add region DAMAGE to `exwm-cm--damages'." + (if (not exwm-cm--damages) + (setq exwm-cm--damages damage) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:UnionRegion + :source1 exwm-cm--damages + :source2 damage + :destination exwm-cm--damages)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:DestroyRegion + :region damage)))) + +(defun exwm-cm--on-DamageNotify (data _synthetic) + "Handle DamageNotify events." + (let ((obj (make-instance 'xcb:damage:Notify)) + parts) + (xcb:unmarshal obj data) + (cl-assert (exwm-cm--xwin->attr (slot-value obj 'drawable))) + (with-slots (x y width height damaged damage) + (exwm-cm--xwin->attr (slot-value obj 'drawable)) + (setq parts (xcb:generate-id exwm-cm--conn)) + (cond + (damaged + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CreateRegion + :region parts + :rectangles nil)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:damage:Subtract + :damage damage + :repair xcb:xfixes:Region:None + :parts parts)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:TranslateRegion + :region parts + :dx x + :dy y))) + (t + (setf damaged t) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CreateRegion + :region parts + :rectangles (list (make-instance 'xcb:RECTANGLE + :width width + :height height + :x x + :y y)))) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:damage:Subtract + :damage damage + :repair xcb:xfixes:Region:None + :parts xcb:xfixes:Region:None)))) + (exwm-cm--add-damage parts)) + ;; Check if there are more damages immediately followed. + (unless (/= 0 (logand #x80 (slot-value obj 'level))) + (exwm-cm--paint)))) + +(defun exwm-cm--on-ShapeNotify (data _synthetic) + "Handle ShapeNotify events." + (let ((obj (make-instance 'xcb:shape:Notify)) + attr region1 region2) + (xcb:unmarshal obj data) + (with-slots (shape-kind affected-window shaped + extents-x extents-y extents-width extents-height) + obj + (exwm--log "(CM) ShapeNotify: #x%X" affected-window) + (when (and (or (eq shape-kind xcb:shape:SK:Clip) + (eq shape-kind xcb:shape:SK:Bounding)) + (setq attr (exwm-cm--xwin->attr affected-window))) + (with-slots ((shaped* shaped) + x y width height + shape-x shape-y shape-width shape-height) + attr + (setq region1 (xcb:generate-id exwm-cm--conn) + region2 (xcb:generate-id exwm-cm--conn)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CreateRegion + :region region1 + :rectangles `(,(make-instance 'xcb:RECTANGLE + :width shape-width + :height shape-height + :x shape-x + :y shape-y)))) + (if shaped + (setf shaped* t + shape-x (+ x extents-x) + shape-y (+ y extents-y) + shape-width (+ width extents-width) + shape-height (+ height extents-height)) + (setf shaped* nil + shape-x x + shape-y y + shape-width width + shape-height height)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:CreateRegion + :region region2 + :rectangles `(,(make-instance 'xcb:RECTANGLE + :width shape-width + :height shape-height + :x shape-x + :y shape-y)))) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:UnionRegion + :source1 region1 + :source2 region2 + :destination region1)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:xfixes:DestroyRegion + :region region2)) + (setq exwm-cm--clip-changed t) + (exwm-cm--paint region1)))))) + +(defun exwm-cm--init () + "Initialize EXWM compositing manager." + ;; Create a new connection. + (setq exwm-cm--conn (xcb:connect)) + (set-process-query-on-exit-flag (slot-value exwm-cm--conn 'process) nil) + ;; Initialize ICCCM/EWMH support. + (xcb:icccm:init exwm-cm--conn) + (xcb:ewmh:init exwm-cm--conn) + ;; Check for Render extension. + (let ((version (xcb:renderutil:query-version exwm-cm--conn))) + (unless (and version + (= 0 (slot-value version 'major-version)) + (<= 2 (slot-value version 'minor-version))) + (error "[EXWM] The server does not support Render extension"))) + ;; Check for Composite extension. + (when (or (= 0 + (slot-value (xcb:get-extension-data exwm-cm--conn + 'xcb:composite) + 'present)) + (with-slots (major-version minor-version) + (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:composite:QueryVersion + :client-major-version 0 + :client-minor-version 1)) + (or (/= major-version 0) (< minor-version 1)))) + (error "[EXWM] The server does not support Composite extension")) + ;; Check for Damage extension. + (when (or (= 0 (slot-value (xcb:get-extension-data exwm-cm--conn 'xcb:damage) + 'present)) + (with-slots (major-version minor-version) + (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:damage:QueryVersion + :client-major-version 1 + :client-minor-version 1)) + (or (/= major-version 1) (< minor-version 1)))) + (error "[EXWM] The server does not support Damage extension")) + ;; Check for XFixes extension. + (when (or (= 0 (slot-value (xcb:get-extension-data exwm-cm--conn 'xcb:xfixes) + 'present)) + (with-slots (major-version minor-version) + (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:xfixes:QueryVersion + :client-major-version 2 + :client-minor-version 0)) + (or (/= major-version 2) (/= minor-version 0)))) + (error "[EXWM] The server does not support XFixes extension")) + ;; Check for Shape extension. + (when (or (= 0 (slot-value (xcb:get-extension-data exwm-cm--conn 'xcb:shape) + 'present)) + (with-slots (major-version minor-version) + (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:shape:QueryVersion)) + (or (/= major-version 1) (< minor-version 1)))) + (error "[EXWM] The server does not support Shape extension")) + ;; Intern atoms. + (let ((atom-name "_NET_WM_WINDOW_OPACITY")) + (setq exwm-cm--_NET_WM_WINDOW_OPACITY + (slot-value (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:InternAtom + :only-if-exists 0 + :name-len (length atom-name) + :name atom-name)) + 'atom))) + (setq exwm-cm--background-atoms + (mapcar (lambda (atom-name) + (slot-value (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:InternAtom + :only-if-exists 0 + :name-len (length atom-name) + :name atom-name)) + 'atom)) + exwm-cm--background-atom-names)) + ;; Register CM. + (with-slots (owner) + (xcb:+request-unchecked+reply exwm-cm--conn + (make-instance 'xcb:GetSelectionOwner + :selection xcb:Atom:_NET_WM_CM_S0)) + (when (/= owner xcb:Window:None) + (error "[EXWM] Other compositing manager detected"))) + (let ((id (xcb:generate-id exwm-cm--conn))) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:CreateWindow + :depth 0 + :wid id + :parent exwm--root + :x 0 + :y 0 + :width 1 + :height 1 + :border-width 0 + :class xcb:WindowClass:InputOnly + :visual 0 + :value-mask xcb:CW:OverrideRedirect + :override-redirect 1)) + ;; Set _NET_WM_NAME. + (xcb:+request exwm-cm--conn + (make-instance 'xcb:ewmh:set-_NET_WM_NAME + :window id + :data "EXWM-CM")) + ;; Get the selection ownership. + (xcb:+request exwm-cm--conn + (make-instance 'xcb:SetSelectionOwner + :owner id + :selection xcb:Atom:_NET_WM_CM_S0 + :time xcb:Time:CurrentTime))) + ;; Attach event listeners. + (xcb:+event exwm-cm--conn 'xcb:MapNotify #'exwm-cm--on-MapNotify) + (xcb:+event exwm-cm--conn 'xcb:UnmapNotify #'exwm-cm--on-UnmapNotify) + (xcb:+event exwm-cm--conn 'xcb:CreateNotify #'exwm-cm--on-CreateNotify) + (xcb:+event exwm-cm--conn 'xcb:ConfigureNotify #'exwm-cm--on-ConfigureNotify) + (xcb:+event exwm-cm--conn 'xcb:DestroyNotify #'exwm-cm--on-DestroyNotify) + (xcb:+event exwm-cm--conn 'xcb:ReparentNotify #'exwm-cm--on-ReparentNotify) + (xcb:+event exwm-cm--conn 'xcb:CirculateNotify #'exwm-cm--on-CirculateNotify) + (xcb:+event exwm-cm--conn 'xcb:Expose #'exwm-cm--on-Expose) + (xcb:+event exwm-cm--conn 'xcb:PropertyNotify #'exwm-cm--on-PropertyNotify) + (xcb:+event exwm-cm--conn 'xcb:damage:Notify #'exwm-cm--on-DamageNotify) + (xcb:+event exwm-cm--conn 'xcb:shape:Notify #'exwm-cm--on-ShapeNotify) + ;; Scan the window tree. + (setq exwm-cm--hash (make-hash-table)) + (exwm-cm--create-tree) + ;; Set up the root X window. + (setq exwm-cm--depth + (slot-value (car (slot-value (xcb:get-setup exwm-cm--conn) 'roots)) + 'root-depth)) + (with-slots (visual picture) (exwm-cm--xwin->attr exwm--root) + (setf picture (xcb:generate-id exwm-cm--conn)) + (xcb:+request exwm-cm--conn + (make-instance 'xcb:render:CreatePicture + :pid picture + :drawable exwm--root + :format (xcb:renderutil:find-visual-format + (xcb:renderutil:query-formats exwm-cm--conn) + visual) + :value-mask xcb:render:CP:SubwindowMode + :subwindowmode xcb:SubwindowMode:IncludeInferiors))) + (xcb:flush exwm-cm--conn) + ;; Paint once. + (exwm-cm--paint t)) + +(defun exwm-cm--exit () + "Exit EXWM compositing manager." + (when exwm-cm--conn + (xcb:disconnect exwm-cm--conn) + (clrhash exwm-cm--hash) + (setq exwm-cm--hash nil + exwm-cm--conn nil + exwm-cm--buffer nil + exwm-cm--clip-changed t + exwm-cm--damages nil + exwm-cm--expose-rectangles nil + exwm-cm--background nil))) + +(defun exwm-cm-enable () + "Enable compositing support for EXWM." + (add-hook 'exwm-init-hook #'exwm-cm--init t) + (add-hook 'exwm-exit-hook #'exwm-cm--exit t)) + +(defun exwm-cm-start () + "Start EXWM composting manager." + (interactive) + (unless exwm-cm--conn + (exwm-cm--init))) + +(defun exwm-cm-stop () + "Stop EXWM compositing manager." + (interactive) + (exwm-cm--exit)) + +(defun exwm-cm-toggle () + "Toggle the running state of EXWM compositing manager." + (interactive) + (if exwm-cm--conn + (exwm-cm-stop) + (exwm-cm-start))) + + + +(provide 'exwm-cm) + +;;; exwm-cm.el ends here |