mirror of
https://github.com/emacs-exwm/exwm.git
synced 2024-11-30 16:07:59 +01:00
1778 lines
74 KiB
EmacsLisp
1778 lines
74 KiB
EmacsLisp
;;; 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 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
|