exwm/exwm-cm.el

1779 lines
74 KiB
EmacsLisp
Raw Normal View History

;;; 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