1 /*
2     SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "surfaceitem_x11.h"
8 #include "composite.h"
9 #include "scene.h"
10 #include "x11syncmanager.h"
11 
12 namespace KWin
13 {
14 
SurfaceItemX11(Toplevel * window,Item * parent)15 SurfaceItemX11::SurfaceItemX11(Toplevel *window, Item *parent)
16     : SurfaceItem(window, parent)
17 {
18     connect(window, &Toplevel::bufferGeometryChanged,
19             this, &SurfaceItemX11::handleBufferGeometryChanged);
20     connect(window, &Toplevel::geometryShapeChanged,
21             this, &SurfaceItemX11::discardQuads);
22 
23     m_damageHandle = xcb_generate_id(kwinApp()->x11Connection());
24     xcb_damage_create(kwinApp()->x11Connection(), m_damageHandle, window->frameId(),
25                       XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY);
26 
27     setSize(window->bufferGeometry().size());
28 }
29 
~SurfaceItemX11()30 SurfaceItemX11::~SurfaceItemX11()
31 {
32     // destroyDamage() will be called by the associated Toplevel.
33 }
34 
preprocess()35 void SurfaceItemX11::preprocess()
36 {
37     if (!damage().isEmpty()) {
38         X11Compositor *compositor = X11Compositor::self();
39         if (X11SyncManager *syncManager = compositor->syncManager()) {
40             syncManager->insertWait();
41         }
42     }
43     SurfaceItem::preprocess();
44 }
45 
processDamage()46 void SurfaceItemX11::processDamage()
47 {
48     m_isDamaged = true;
49     scheduleFrame();
50 }
51 
fetchDamage()52 bool SurfaceItemX11::fetchDamage()
53 {
54     if (!m_isDamaged) {
55         return false;
56     }
57     m_isDamaged = false;
58 
59     if (m_damageHandle == XCB_NONE) {
60         return true;
61     }
62 
63     xcb_xfixes_region_t region = xcb_generate_id(kwinApp()->x11Connection());
64     xcb_xfixes_create_region(kwinApp()->x11Connection(), region, 0, nullptr);
65     xcb_damage_subtract(kwinApp()->x11Connection(), m_damageHandle, 0, region);
66 
67     m_damageCookie = xcb_xfixes_fetch_region_unchecked(kwinApp()->x11Connection(), region);
68     xcb_xfixes_destroy_region(kwinApp()->x11Connection(), region);
69 
70     m_havePendingDamageRegion = true;
71 
72     return true;
73 }
74 
waitForDamage()75 void SurfaceItemX11::waitForDamage()
76 {
77     if (!m_havePendingDamageRegion) {
78         return;
79     }
80     m_havePendingDamageRegion = false;
81 
82     xcb_xfixes_fetch_region_reply_t *reply =
83             xcb_xfixes_fetch_region_reply(kwinApp()->x11Connection(), m_damageCookie, nullptr);
84     if (!reply) {
85         qCDebug(KWIN_CORE) << "Failed to check damage region";
86         return;
87     }
88 
89     const int rectCount = xcb_xfixes_fetch_region_rectangles_length(reply);
90     QRegion region;
91 
92     if (rectCount > 1 && rectCount < 16) {
93         xcb_rectangle_t *rects = xcb_xfixes_fetch_region_rectangles(reply);
94 
95         QVector<QRect> qtRects;
96         qtRects.reserve(rectCount);
97 
98         for (int i = 0; i < rectCount; ++i) {
99             qtRects << QRect(rects[i].x, rects[i].y, rects[i].width, rects[i].height);
100         }
101         region.setRects(qtRects.constData(), rectCount);
102     } else {
103         region = QRect(reply->extents.x, reply->extents.y, reply->extents.width, reply->extents.height);
104     }
105     free(reply);
106 
107     addDamage(region);
108 }
109 
destroyDamage()110 void SurfaceItemX11::destroyDamage()
111 {
112     if (m_damageHandle != XCB_NONE) {
113         xcb_damage_destroy(kwinApp()->x11Connection(), m_damageHandle);
114         m_damageHandle = XCB_NONE;
115     }
116 }
117 
handleBufferGeometryChanged(Toplevel * toplevel,const QRect & old)118 void SurfaceItemX11::handleBufferGeometryChanged(Toplevel *toplevel, const QRect &old)
119 {
120     if (toplevel->bufferGeometry().size() != old.size()) {
121         discardPixmap();
122     }
123     setSize(toplevel->bufferGeometry().size());
124 }
125 
shape() const126 QRegion SurfaceItemX11::shape() const
127 {
128     const QRect clipRect = window()->clientGeometry().translated(-window()->bufferGeometry().topLeft());
129     const QRegion shape = window()->shapeRegion();
130 
131     return shape & clipRect;
132 }
133 
opaque() const134 QRegion SurfaceItemX11::opaque() const
135 {
136     return window()->opaqueRegion();
137 }
138 
createPixmap()139 SurfacePixmap *SurfaceItemX11::createPixmap()
140 {
141     return new SurfacePixmapX11(this);
142 }
143 
SurfacePixmapX11(SurfaceItemX11 * item,QObject * parent)144 SurfacePixmapX11::SurfacePixmapX11(SurfaceItemX11 *item, QObject *parent)
145     : SurfacePixmap(Compositor::self()->scene()->createPlatformSurfaceTextureX11(this), parent)
146     , m_item(item)
147 {
148 }
149 
~SurfacePixmapX11()150 SurfacePixmapX11::~SurfacePixmapX11()
151 {
152     if (m_pixmap != XCB_PIXMAP_NONE) {
153         xcb_free_pixmap(kwinApp()->x11Connection(), m_pixmap);
154     }
155 }
156 
isValid() const157 bool SurfacePixmapX11::isValid() const
158 {
159     return m_pixmap != XCB_PIXMAP_NONE;
160 }
161 
pixmap() const162 xcb_pixmap_t SurfacePixmapX11::pixmap() const
163 {
164     return m_pixmap;
165 }
166 
visual() const167 xcb_visualid_t SurfacePixmapX11::visual() const
168 {
169     return m_item->window()->visual();
170 }
171 
create()172 void SurfacePixmapX11::create()
173 {
174     const Toplevel *toplevel = m_item->window();
175     if (toplevel->isDeleted()) {
176         return;
177     }
178 
179     XServerGrabber grabber;
180     xcb_connection_t *connection = kwinApp()->x11Connection();
181     xcb_window_t frame = toplevel->frameId();
182     xcb_pixmap_t pixmap = xcb_generate_id(connection);
183     xcb_void_cookie_t namePixmapCookie = xcb_composite_name_window_pixmap_checked(connection,
184                                                                                   frame,
185                                                                                   pixmap);
186     Xcb::WindowAttributes windowAttributes(frame);
187     Xcb::WindowGeometry windowGeometry(frame);
188     if (xcb_generic_error_t *error = xcb_request_check(connection, namePixmapCookie)) {
189         qCDebug(KWIN_CORE, "Failed to create window pixmap for window 0x%x (error code %d)",
190                 toplevel->window(), error->error_code);
191         free(error);
192         return;
193     }
194     // check that the received pixmap is valid and actually matches what we
195     // know about the window (i.e. size)
196     if (!windowAttributes || windowAttributes->map_state != XCB_MAP_STATE_VIEWABLE) {
197         qCDebug(KWIN_CORE, "Failed to create window pixmap for window 0x%x (not viewable)",
198                 toplevel->window());
199         xcb_free_pixmap(connection, pixmap);
200         return;
201     }
202     const QRect bufferGeometry = toplevel->bufferGeometry();
203     if (windowGeometry.size() != bufferGeometry.size()) {
204         qCDebug(KWIN_CORE, "Failed to create window pixmap for window 0x%x (mismatched geometry)",
205                 toplevel->window());
206         xcb_free_pixmap(connection, pixmap);
207         return;
208     }
209 
210     m_pixmap = pixmap;
211     m_hasAlphaChannel = toplevel->hasAlpha();
212     m_size = bufferGeometry.size();
213     m_contentsRect = QRect(toplevel->clientPos(), toplevel->clientSize());
214 }
215 
216 } // namespace KWin
217