exwm2/exwm-cm.el
2017-12-31 20:49:37 +08:00

1784 lines
75 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; exwm-cm.el --- Compositing Manager for EXWM -*- lexical-binding: t -*-
;; Copyright (C) 2016-2018 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 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))
(defun exwm-cm-start ()
"Start EXWM compositing 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