;;; exwm-cm.el --- Compositing Manager for EXWM -*- lexical-binding: t -*- ;; Copyright (C) 2016-2018 Free Software Foundation, Inc. ;; Author: Chris Feng ;; 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 . ;;; 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)))) ;;;###autoload (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 clip (xcb:+request exwm-cm--conn (make-instance 'xcb:xfixes:DestroyRegion :region clip))) (when fullscreen ;; Fullscreen X windows are always opaque thus occludes ;; anything in this workspace. (throw 'break 'fullscreen))) (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:FreePixmap :pixmap pixmap)) (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:FreePixmap :pixmap pixmap)) (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)) ;;;###autoload (defun exwm-cm-start () "Start EXWM compositing manager." (interactive) (unless exwm-cm--conn (exwm-cm--init))) ;;;###autoload (defun exwm-cm-stop () "Stop EXWM compositing manager." (interactive) (exwm-cm--exit)) ;;;###autoload (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