1 /*
2 * Copyright (C) 2017-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "WindowDecorator.h"
10
11 #include "Util.h"
12 #include "threads/SingleLock.h"
13 #include "utils/EndianSwap.h"
14 #include "utils/log.h"
15
16 #include <algorithm>
17 #include <cassert>
18 #include <cmath>
19 #include <vector>
20
21 #include <linux/input-event-codes.h>
22
23 using namespace KODI::UTILS::POSIX;
24 using namespace KODI::WINDOWING::WAYLAND;
25 using namespace std::placeholders;
26
27 namespace
28 {
29
30 /// Bytes per pixel in shm storage
31 constexpr int BYTES_PER_PIXEL{4};
32 /// Width of the visible border around the whole window
33 constexpr int VISIBLE_BORDER_WIDTH{5};
34 /// Width of the invisible border around the whole window for easier resizing
35 constexpr int RESIZE_BORDER_WIDTH{10};
36 /// Total width of the border around the window
37 constexpr int BORDER_WIDTH{VISIBLE_BORDER_WIDTH + RESIZE_BORDER_WIDTH};
38 /// Height of the top bar
39 constexpr int TOP_BAR_HEIGHT{33};
40 /// Maximum distance from the window corner to consider position valid for resize
41 constexpr int RESIZE_MAX_CORNER_DISTANCE{BORDER_WIDTH};
42 /// Distance of buttons from edges of the top bar
43 constexpr int BUTTONS_EDGE_DISTANCE{6};
44 /// Distance from button inner edge to button content
45 constexpr int BUTTON_INNER_SEPARATION{4};
46 /// Button size
47 constexpr int BUTTON_SIZE{21};
48
49 constexpr std::uint32_t TRANSPARENT{0x00000000u};
50 constexpr std::uint32_t BORDER_COLOR{0xFF000000u};
51 constexpr std::uint32_t BUTTON_COLOR_ACTIVE{0xFFFFFFFFu};
52 constexpr std::uint32_t BUTTON_COLOR_INACTIVE{0xFF777777u};
53 constexpr std::uint32_t BUTTON_HOVER_COLOR{0xFF555555u};
54
55 static_assert(BUTTON_SIZE <= TOP_BAR_HEIGHT - BUTTONS_EDGE_DISTANCE * 2, "Buttons must fit in top bar");
56
57 /*
58 * Decorations consist of four surfaces, one for each edge of the window. It would
59 * also be possible to position one single large surface behind the main surface
60 * instead, but that would waste a lot of memory on big/high-density screens.
61 *
62 * The four surfaces are laid out as follows: Top and bottom surfaces go over the
63 * whole width of the main surface plus the left and right borders.
64 * Left and right surfaces only go over the height of the main surface without
65 * the top and bottom borders.
66 *
67 * ---------------------------------------------
68 * | TOP |
69 * ---------------------------------------------
70 * | | | |
71 * | L | | R |
72 * | E | | I |
73 * | F | Main surface | G |
74 * | T | | H |
75 * | | | T |
76 * | | | |
77 * ---------------------------------------------
78 * | BOTTOM |
79 * ---------------------------------------------
80 */
81
82
SurfaceGeometry(SurfaceIndex type,CSizeInt mainSurfaceSize)83 CRectInt SurfaceGeometry(SurfaceIndex type, CSizeInt mainSurfaceSize)
84 {
85 // Coordinates are relative to main surface
86 switch (type)
87 {
88 case SURFACE_TOP:
89 return {
90 CPointInt{-BORDER_WIDTH, -(BORDER_WIDTH + TOP_BAR_HEIGHT)},
91 CSizeInt{mainSurfaceSize.Width() + 2 * BORDER_WIDTH, TOP_BAR_HEIGHT + BORDER_WIDTH}
92 };
93 case SURFACE_RIGHT:
94 return {
95 CPointInt{mainSurfaceSize.Width(), 0},
96 CSizeInt{BORDER_WIDTH, mainSurfaceSize.Height()}
97 };
98 case SURFACE_BOTTOM:
99 return {
100 CPointInt{-BORDER_WIDTH, mainSurfaceSize.Height()},
101 CSizeInt{mainSurfaceSize.Width() + 2 * BORDER_WIDTH, BORDER_WIDTH}
102 };
103 case SURFACE_LEFT:
104 return {
105 CPointInt{-BORDER_WIDTH, 0},
106 CSizeInt{BORDER_WIDTH, mainSurfaceSize.Height()}
107 };
108 default:
109 throw std::logic_error("Invalid surface type");
110 }
111 }
112
SurfaceOpaqueRegion(SurfaceIndex type,CSizeInt mainSurfaceSize)113 CRectInt SurfaceOpaqueRegion(SurfaceIndex type, CSizeInt mainSurfaceSize)
114 {
115 // Coordinates are relative to main surface
116 auto size = SurfaceGeometry(type, mainSurfaceSize).ToSize();
117 switch (type)
118 {
119 case SURFACE_TOP:
120 return {
121 CPointInt{RESIZE_BORDER_WIDTH, RESIZE_BORDER_WIDTH},
122 size - CSizeInt{RESIZE_BORDER_WIDTH * 2, RESIZE_BORDER_WIDTH}
123 };
124 case SURFACE_RIGHT:
125 return {
126 CPointInt{},
127 size - CSizeInt{RESIZE_BORDER_WIDTH, 0}
128 };
129 case SURFACE_BOTTOM:
130 return {
131 CPointInt{RESIZE_BORDER_WIDTH, 0},
132 size - CSizeInt{RESIZE_BORDER_WIDTH * 2, RESIZE_BORDER_WIDTH}
133 };
134 case SURFACE_LEFT:
135 return {
136 CPointInt{RESIZE_BORDER_WIDTH, 0},
137 size - CSizeInt{RESIZE_BORDER_WIDTH, 0}
138 };
139 default:
140 throw std::logic_error("Invalid surface type");
141 }
142 }
143
SurfaceWindowRegion(SurfaceIndex type,CSizeInt mainSurfaceSize)144 CRectInt SurfaceWindowRegion(SurfaceIndex type, CSizeInt mainSurfaceSize)
145 {
146 return SurfaceOpaqueRegion(type, mainSurfaceSize);
147 }
148
149 /**
150 * Full size of decorations to be added to the main surface size
151 */
DecorationSize()152 CSizeInt DecorationSize()
153 {
154 return {2 * VISIBLE_BORDER_WIDTH, 2 * VISIBLE_BORDER_WIDTH + TOP_BAR_HEIGHT};
155 }
156
MemoryBytesForSize(CSizeInt windowSurfaceSize,int scale)157 std::size_t MemoryBytesForSize(CSizeInt windowSurfaceSize, int scale)
158 {
159 std::size_t size{};
160
161 for (auto surface : { SURFACE_TOP, SURFACE_RIGHT, SURFACE_BOTTOM, SURFACE_LEFT })
162 {
163 size += SurfaceGeometry(surface, windowSurfaceSize).Area();
164 }
165
166 size *= scale * scale;
167
168 size *= BYTES_PER_PIXEL;
169
170 return size;
171 }
172
PositionInBuffer(CWindowDecorator::Buffer & buffer,CPointInt position)173 std::size_t PositionInBuffer(CWindowDecorator::Buffer& buffer, CPointInt position)
174 {
175 if (position.x < 0 || position.y < 0)
176 {
177 throw std::invalid_argument("Position out of bounds");
178 }
179 std::size_t offset{static_cast<std::size_t> (buffer.size.Width() * position.y + position.x)};
180 if (offset * BYTES_PER_PIXEL >= buffer.dataSize)
181 {
182 throw std::invalid_argument("Position out of bounds");
183 }
184 return offset;
185 }
186
DrawHorizontalLine(CWindowDecorator::Buffer & buffer,std::uint32_t color,CPointInt position,int length)187 void DrawHorizontalLine(CWindowDecorator::Buffer& buffer, std::uint32_t color, CPointInt position, int length)
188 {
189 auto offsetStart = PositionInBuffer(buffer, position);
190 auto offsetEnd = PositionInBuffer(buffer, position + CPointInt{length - 1, 0});
191 if (offsetEnd < offsetStart)
192 {
193 throw std::invalid_argument("Invalid drawing coordinates");
194 }
195 std::fill(buffer.RgbaBuffer() + offsetStart, buffer.RgbaBuffer() + offsetEnd + 1, Endian_SwapLE32(color));
196 }
197
DrawLineWithStride(CWindowDecorator::Buffer & buffer,std::uint32_t color,CPointInt position,int length,int stride)198 void DrawLineWithStride(CWindowDecorator::Buffer& buffer, std::uint32_t color, CPointInt position, int length, int stride)
199 {
200 auto offsetStart = PositionInBuffer(buffer, position);
201 auto offsetEnd = offsetStart + stride * (length - 1);
202 if (offsetEnd * BYTES_PER_PIXEL >= buffer.dataSize)
203 {
204 throw std::invalid_argument("Position out of bounds");
205 }
206 if (offsetEnd < offsetStart)
207 {
208 throw std::invalid_argument("Invalid drawing coordinates");
209 }
210 auto* memory = buffer.RgbaBuffer();
211 for (std::size_t offset = offsetStart; offset <= offsetEnd; offset += stride)
212 {
213 *(memory + offset) = Endian_SwapLE32(color);
214 }
215 }
216
DrawVerticalLine(CWindowDecorator::Buffer & buffer,std::uint32_t color,CPointInt position,int length)217 void DrawVerticalLine(CWindowDecorator::Buffer& buffer, std::uint32_t color, CPointInt position, int length)
218 {
219 DrawLineWithStride(buffer, color, position, length, buffer.size.Width());
220 }
221
222 /**
223 * Draw rectangle inside the specified buffer coordinates
224 */
DrawRectangle(CWindowDecorator::Buffer & buffer,std::uint32_t color,CRectInt rect)225 void DrawRectangle(CWindowDecorator::Buffer& buffer, std::uint32_t color, CRectInt rect)
226 {
227 DrawHorizontalLine(buffer, color, rect.P1(), rect.Width());
228 DrawVerticalLine(buffer, color, rect.P1(), rect.Height());
229 DrawHorizontalLine(buffer, color, rect.P1() + CPointInt{1, rect.Height() - 1}, rect.Width() - 1);
230 DrawVerticalLine(buffer, color, rect.P1() + CPointInt{rect.Width() - 1, 1}, rect.Height() - 1);
231 }
232
FillRectangle(CWindowDecorator::Buffer & buffer,std::uint32_t color,CRectInt rect)233 void FillRectangle(CWindowDecorator::Buffer& buffer, std::uint32_t color, CRectInt rect)
234 {
235 for (int y{rect.y1}; y <= rect.y2; y++)
236 {
237 DrawHorizontalLine(buffer, color, {rect.x1, y}, rect.Width() + 1);
238 }
239 }
240
DrawHorizontalLine(CWindowDecorator::Surface & surface,std::uint32_t color,CPointInt position,int length)241 void DrawHorizontalLine(CWindowDecorator::Surface& surface, std::uint32_t color, CPointInt position, int length)
242 {
243 for (int i{0}; i < surface.scale; i++)
244 {
245 DrawHorizontalLine(surface.buffer, color, position * surface.scale + CPointInt{0, i}, length * surface.scale);
246 }
247 }
248
DrawAngledLine(CWindowDecorator::Surface & surface,std::uint32_t color,CPointInt position,int length,int stride)249 void DrawAngledLine(CWindowDecorator::Surface& surface, std::uint32_t color, CPointInt position, int length, int stride)
250 {
251 for (int i{0}; i < surface.scale; i++)
252 {
253 DrawLineWithStride(surface.buffer, color, position * surface.scale + CPointInt{i, 0}, length * surface.scale, surface.buffer.size.Width() + stride);
254 }
255 }
256
DrawVerticalLine(CWindowDecorator::Surface & surface,std::uint32_t color,CPointInt position,int length)257 void DrawVerticalLine(CWindowDecorator::Surface& surface, std::uint32_t color, CPointInt position, int length)
258 {
259 DrawAngledLine(surface, color, position, length, 0);
260 }
261
262 /**
263 * Draw rectangle inside the specified surface coordinates
264 */
DrawRectangle(CWindowDecorator::Surface & surface,std::uint32_t color,CRectInt rect)265 void DrawRectangle(CWindowDecorator::Surface& surface, std::uint32_t color, CRectInt rect)
266 {
267 for (int i{0}; i < surface.scale; i++)
268 {
269 DrawRectangle(surface.buffer, color, {rect.P1() * surface.scale + CPointInt{i, i}, (rect.P2() + CPointInt{1, 1}) * surface.scale - CPointInt{i, i} - CPointInt{1, 1}});
270 }
271 }
272
FillRectangle(CWindowDecorator::Surface & surface,std::uint32_t color,CRectInt rect)273 void FillRectangle(CWindowDecorator::Surface& surface, std::uint32_t color, CRectInt rect)
274 {
275 FillRectangle(surface.buffer, color, {rect.P1() * surface.scale, (rect.P2() + CPointInt{1, 1}) * surface.scale - CPointInt{1, 1}});
276 }
277
DrawButton(CWindowDecorator::Surface & surface,std::uint32_t lineColor,CRectInt rect,bool hover)278 void DrawButton(CWindowDecorator::Surface& surface, std::uint32_t lineColor, CRectInt rect, bool hover)
279 {
280 if (hover)
281 {
282 FillRectangle(surface, BUTTON_HOVER_COLOR, rect);
283 }
284 DrawRectangle(surface, lineColor, rect);
285 }
286
ResizeEdgeForPosition(SurfaceIndex surface,CSizeInt surfaceSize,CPointInt position)287 wayland::shell_surface_resize ResizeEdgeForPosition(SurfaceIndex surface, CSizeInt surfaceSize, CPointInt position)
288 {
289 switch (surface)
290 {
291 case SURFACE_TOP:
292 if (position.y <= RESIZE_MAX_CORNER_DISTANCE)
293 {
294 if (position.x <= RESIZE_MAX_CORNER_DISTANCE)
295 {
296 return wayland::shell_surface_resize::top_left;
297 }
298 else if (position.x >= surfaceSize.Width() - RESIZE_MAX_CORNER_DISTANCE)
299 {
300 return wayland::shell_surface_resize::top_right;
301 }
302 else
303 {
304 return wayland::shell_surface_resize::top;
305 }
306 }
307 else
308 {
309 if (position.x <= RESIZE_MAX_CORNER_DISTANCE)
310 {
311 return wayland::shell_surface_resize::left;
312 }
313 else if (position.x >= surfaceSize.Width() - RESIZE_MAX_CORNER_DISTANCE)
314 {
315 return wayland::shell_surface_resize::right;
316 }
317 else
318 {
319 // Inside title bar, not resizing
320 return wayland::shell_surface_resize::none;
321 }
322 }
323 case SURFACE_RIGHT:
324 if (position.y >= surfaceSize.Height() - RESIZE_MAX_CORNER_DISTANCE)
325 {
326 return wayland::shell_surface_resize::bottom_right;
327 }
328 else
329 {
330 return wayland::shell_surface_resize::right;
331 }
332 case SURFACE_BOTTOM:
333 if (position.x <= RESIZE_MAX_CORNER_DISTANCE)
334 {
335 return wayland::shell_surface_resize::bottom_left;
336 }
337 else if (position.x >= surfaceSize.Width() - RESIZE_MAX_CORNER_DISTANCE)
338 {
339 return wayland::shell_surface_resize::bottom_right;
340 }
341 else
342 {
343 return wayland::shell_surface_resize::bottom;
344 }
345 case SURFACE_LEFT:
346 if (position.y >= surfaceSize.Height() - RESIZE_MAX_CORNER_DISTANCE)
347 {
348 return wayland::shell_surface_resize::bottom_left;
349 }
350 else
351 {
352 return wayland::shell_surface_resize::left;
353 }
354
355 default:
356 return wayland::shell_surface_resize::none;
357 }
358 }
359
360 /**
361 * Get name for resize cursor according to xdg cursor-spec
362 */
CursorForResizeEdge(wayland::shell_surface_resize edge)363 std::string CursorForResizeEdge(wayland::shell_surface_resize edge)
364 {
365 if (edge == wayland::shell_surface_resize::top)
366 return "n-resize";
367 else if (edge == wayland::shell_surface_resize::bottom)
368 return "s-resize";
369 else if (edge == wayland::shell_surface_resize::left)
370 return "w-resize";
371 else if (edge == wayland::shell_surface_resize::top_left)
372 return "nw-resize";
373 else if (edge == wayland::shell_surface_resize::bottom_left)
374 return "sw-resize";
375 else if (edge == wayland::shell_surface_resize::right)
376 return "e-resize";
377 else if (edge == wayland::shell_surface_resize::top_right)
378 return "ne-resize";
379 else if (edge == wayland::shell_surface_resize::bottom_right)
380 return "se-resize";
381 else
382 return "";
383 }
384
385 template<typename T, typename InstanceProviderT>
HandleCapabilityChange(const wayland::seat_capability & caps,const wayland::seat_capability & cap,T & proxy,InstanceProviderT instanceProvider)386 bool HandleCapabilityChange(const wayland::seat_capability& caps,
387 const wayland::seat_capability& cap,
388 T& proxy,
389 InstanceProviderT instanceProvider)
390 {
391 bool hasCapability = caps & cap;
392
393 if ((!!proxy) != hasCapability)
394 {
395 // Capability changed
396
397 if (hasCapability)
398 {
399 // The capability was added
400 proxy = instanceProvider();
401 return true;
402 }
403 else
404 {
405 // The capability was removed
406 proxy.proxy_release();
407 }
408 }
409
410 return false;
411 }
412
413 }
414
CWindowDecorator(IWindowDecorationHandler & handler,CConnection & connection,wayland::surface_t const & mainSurface)415 CWindowDecorator::CWindowDecorator(IWindowDecorationHandler& handler, CConnection& connection, wayland::surface_t const& mainSurface)
416 : m_handler{handler}, m_registry{connection}, m_mainSurface{mainSurface}, m_buttonColor{BUTTON_COLOR_ACTIVE}
417 {
418 static_assert(std::tuple_size<decltype(m_borderSurfaces)>::value == SURFACE_COUNT, "SURFACE_COUNT must match surfaces array size");
419
420 m_registry.RequestSingleton(m_compositor, 1, 4);
421 m_registry.RequestSingleton(m_subcompositor, 1, 1, false);
422 m_registry.RequestSingleton(m_shm, 1, 1);
423
424 m_registry.Bind();
425 }
426
AddSeat(CSeat * seat)427 void CWindowDecorator::AddSeat(CSeat* seat)
428 {
429 m_seats.emplace(std::piecewise_construct, std::forward_as_tuple(seat->GetGlobalName()), std::forward_as_tuple(seat));
430 seat->AddRawInputHandlerTouch(this);
431 seat->AddRawInputHandlerPointer(this);
432 }
433
RemoveSeat(CSeat * seat)434 void CWindowDecorator::RemoveSeat(CSeat* seat)
435 {
436 seat->RemoveRawInputHandlerTouch(this);
437 seat->RemoveRawInputHandlerPointer(this);
438 m_seats.erase(seat->GetGlobalName());
439 UpdateButtonHoverState();
440 }
441
OnPointerEnter(CSeat * seat,std::uint32_t serial,const wayland::surface_t & surface,double surfaceX,double surfaceY)442 void CWindowDecorator::OnPointerEnter(CSeat* seat,
443 std::uint32_t serial,
444 const wayland::surface_t& surface,
445 double surfaceX,
446 double surfaceY)
447 {
448 auto seatStateI = m_seats.find(seat->GetGlobalName());
449 if (seatStateI == m_seats.end())
450 {
451 return;
452 }
453 auto& seatState = seatStateI->second;
454 // Reset first so we ignore events for surfaces we don't handle
455 seatState.currentSurface = SURFACE_COUNT;
456 CSingleLock lock(m_mutex);
457 for (std::size_t i{0}; i < m_borderSurfaces.size(); i++)
458 {
459 if (m_borderSurfaces[i].surface.wlSurface == surface)
460 {
461 seatState.pointerEnterSerial = serial;
462 seatState.currentSurface = static_cast<SurfaceIndex> (i);
463 seatState.pointerPosition = {static_cast<float> (surfaceX), static_cast<float> (surfaceY)};
464 UpdateSeatCursor(seatState);
465 UpdateButtonHoverState();
466 break;
467 }
468 }
469 }
470
OnPointerLeave(CSeat * seat,std::uint32_t serial,const wayland::surface_t & surface)471 void CWindowDecorator::OnPointerLeave(CSeat* seat,
472 std::uint32_t serial,
473 const wayland::surface_t& surface)
474 {
475 auto seatStateI = m_seats.find(seat->GetGlobalName());
476 if (seatStateI == m_seats.end())
477 {
478 return;
479 }
480 auto& seatState = seatStateI->second;
481 seatState.currentSurface = SURFACE_COUNT;
482 // Recreate cursor surface on reenter
483 seatState.cursorName.clear();
484 seatState.cursor.proxy_release();
485 UpdateButtonHoverState();
486 }
487
OnPointerMotion(CSeat * seat,std::uint32_t time,double surfaceX,double surfaceY)488 void CWindowDecorator::OnPointerMotion(CSeat* seat, std::uint32_t time, double surfaceX, double surfaceY)
489 {
490 auto seatStateI = m_seats.find(seat->GetGlobalName());
491 if (seatStateI == m_seats.end())
492 {
493 return;
494 }
495 auto& seatState = seatStateI->second;
496 if (seatState.currentSurface != SURFACE_COUNT)
497 {
498 seatState.pointerPosition = {static_cast<float> (surfaceX), static_cast<float> (surfaceY)};
499 UpdateSeatCursor(seatState);
500 UpdateButtonHoverState();
501 }
502 }
503
OnPointerButton(CSeat * seat,std::uint32_t serial,std::uint32_t time,std::uint32_t button,wayland::pointer_button_state state)504 void CWindowDecorator::OnPointerButton(CSeat* seat, std::uint32_t serial, std::uint32_t time, std::uint32_t button, wayland::pointer_button_state state)
505 {
506 auto seatStateI = m_seats.find(seat->GetGlobalName());
507 if (seatStateI == m_seats.end())
508 {
509 return;
510 }
511 auto& seatState = seatStateI->second;
512 if (seatState.currentSurface != SURFACE_COUNT && state == wayland::pointer_button_state::pressed)
513 {
514 HandleSeatClick(seatState, seatState.currentSurface, serial, button, seatState.pointerPosition);
515 }
516 }
517
OnTouchDown(CSeat * seat,std::uint32_t serial,std::uint32_t time,const wayland::surface_t & surface,std::int32_t id,double x,double y)518 void CWindowDecorator::OnTouchDown(CSeat* seat,
519 std::uint32_t serial,
520 std::uint32_t time,
521 const wayland::surface_t& surface,
522 std::int32_t id,
523 double x,
524 double y)
525 {
526 auto seatStateI = m_seats.find(seat->GetGlobalName());
527 if (seatStateI == m_seats.end())
528 {
529 return;
530 }
531 auto& seatState = seatStateI->second;
532 CSingleLock lock(m_mutex);
533 for (std::size_t i{0}; i < m_borderSurfaces.size(); i++)
534 {
535 if (m_borderSurfaces[i].surface.wlSurface == surface)
536 {
537 HandleSeatClick(seatState, static_cast<SurfaceIndex> (i), serial, BTN_LEFT, {static_cast<float> (x), static_cast<float> (y)});
538 }
539 }
540 }
541
UpdateSeatCursor(SeatState & seatState)542 void CWindowDecorator::UpdateSeatCursor(SeatState& seatState)
543 {
544 if (seatState.currentSurface == SURFACE_COUNT)
545 {
546 // Don't set anything if not on any surface
547 return;
548 }
549
550 LoadCursorTheme();
551
552 std::string cursorName{"default"};
553
554 {
555 CSingleLock lock(m_mutex);
556 auto resizeEdge = ResizeEdgeForPosition(seatState.currentSurface, SurfaceGeometry(seatState.currentSurface, m_mainSurfaceSize).ToSize(), CPointInt{seatState.pointerPosition});
557 if (resizeEdge != wayland::shell_surface_resize::none)
558 {
559 cursorName = CursorForResizeEdge(resizeEdge);
560 }
561 }
562
563 if (cursorName == seatState.cursorName)
564 {
565 // Don't reload cursor all the time when nothing is changing
566 return;
567 }
568 seatState.cursorName = cursorName;
569
570 wayland::cursor_t cursor;
571 try
572 {
573 cursor = CCursorUtil::LoadFromTheme(m_cursorTheme, cursorName);
574 }
575 catch (std::exception const& e)
576 {
577 CLog::LogF(LOGERROR, "Could not get required cursor %s from cursor theme: %s", cursorName.c_str(), e.what());
578 return;
579 }
580 auto cursorImage = cursor.image(0);
581
582 if (!seatState.cursor)
583 {
584 seatState.cursor = m_compositor.create_surface();
585 }
586 int calcScale{seatState.cursor.can_set_buffer_scale() ? m_scale : 1};
587
588 seatState.seat->SetCursor(seatState.pointerEnterSerial, seatState.cursor, cursorImage.hotspot_x() / calcScale, cursorImage.hotspot_y() / calcScale);
589 seatState.cursor.attach(cursorImage.get_buffer(), 0, 0);
590 seatState.cursor.damage(0, 0, cursorImage.width() / calcScale, cursorImage.height() / calcScale);
591 if (seatState.cursor.can_set_buffer_scale())
592 {
593 seatState.cursor.set_buffer_scale(m_scale);
594 }
595 seatState.cursor.commit();
596 }
597
UpdateButtonHoverState()598 void CWindowDecorator::UpdateButtonHoverState()
599 {
600 std::vector<CPoint> pointerPositions;
601
602 CSingleLock lock(m_mutex);
603
604 for (auto const& seatPair : m_seats)
605 {
606 auto const& seat = seatPair.second;
607 if (seat.currentSurface == SURFACE_TOP)
608 {
609 pointerPositions.emplace_back(seat.pointerPosition);
610 }
611 }
612
613 bool changed{false};
614 for (auto& button : m_buttons)
615 {
616 bool wasHovered{button.hovered};
617 button.hovered = std::any_of(pointerPositions.cbegin(), pointerPositions.cend(), [&](CPoint point) { return button.position.PtInRect(CPointInt{point}); });
618 changed = changed || (button.hovered != wasHovered);
619 }
620
621 if (changed)
622 {
623 // Repaint!
624 Reset(false);
625 }
626 }
627
HandleSeatClick(SeatState const & seatState,SurfaceIndex surface,std::uint32_t serial,std::uint32_t button,CPoint position)628 void CWindowDecorator::HandleSeatClick(SeatState const& seatState, SurfaceIndex surface, std::uint32_t serial, std::uint32_t button, CPoint position)
629 {
630 switch (button)
631 {
632 case BTN_LEFT:
633 {
634 CSingleLock lock(m_mutex);
635 auto resizeEdge = ResizeEdgeForPosition(surface, SurfaceGeometry(surface, m_mainSurfaceSize).ToSize(), CPointInt{position});
636 if (resizeEdge == wayland::shell_surface_resize::none)
637 {
638 for (auto const& button : m_buttons)
639 {
640 if (button.position.PtInRect(CPointInt{position}))
641 {
642 button.onClick();
643 return;
644 }
645 }
646
647 m_handler.OnWindowMove(seatState.seat->GetWlSeat(), serial);
648 }
649 else
650 {
651 m_handler.OnWindowResize(seatState.seat->GetWlSeat(), serial, resizeEdge);
652 }
653 }
654 break;
655 case BTN_RIGHT:
656 if (surface == SURFACE_TOP)
657 {
658 m_handler.OnWindowShowContextMenu(seatState.seat->GetWlSeat(), serial, CPointInt{position} - CPointInt{BORDER_WIDTH, BORDER_WIDTH + TOP_BAR_HEIGHT});
659 }
660 break;
661 }
662 }
663
MakeBorderSurface()664 CWindowDecorator::BorderSurface CWindowDecorator::MakeBorderSurface()
665 {
666 Surface surface;
667 surface.wlSurface = m_compositor.create_surface();
668 auto subsurface = m_subcompositor.get_subsurface(surface.wlSurface, m_mainSurface);
669
670 return {surface, subsurface};
671 }
672
IsDecorationActive() const673 bool CWindowDecorator::IsDecorationActive() const
674 {
675 return StateHasWindowDecorations(m_windowState);
676 }
677
StateHasWindowDecorations(IShellSurface::StateBitset state) const678 bool CWindowDecorator::StateHasWindowDecorations(IShellSurface::StateBitset state) const
679 {
680 // No decorations possible if subcompositor not available
681 return m_subcompositor && !state.test(IShellSurface::STATE_FULLSCREEN);
682 }
683
CalculateMainSurfaceSize(CSizeInt size,IShellSurface::StateBitset state) const684 CSizeInt CWindowDecorator::CalculateMainSurfaceSize(CSizeInt size, IShellSurface::StateBitset state) const
685 {
686 if (StateHasWindowDecorations(state))
687 {
688 // Subtract decorations
689 return size - DecorationSize();
690 }
691 else
692 {
693 // Fullscreen -> no decorations
694 return size;
695 }
696 }
697
CalculateFullSurfaceSize(CSizeInt size,IShellSurface::StateBitset state) const698 CSizeInt CWindowDecorator::CalculateFullSurfaceSize(CSizeInt size, IShellSurface::StateBitset state) const
699 {
700 if (StateHasWindowDecorations(state))
701 {
702 // Add decorations
703 return size + DecorationSize();
704 }
705 else
706 {
707 // Fullscreen -> no decorations
708 return size;
709 }
710 }
711
SetState(CSizeInt size,int scale,IShellSurface::StateBitset state)712 void CWindowDecorator::SetState(CSizeInt size, int scale, IShellSurface::StateBitset state)
713 {
714 CSizeInt mainSurfaceSize{CalculateMainSurfaceSize(size, state)};
715 CSingleLock lock(m_mutex);
716 if (mainSurfaceSize == m_mainSurfaceSize && scale == m_scale && state == m_windowState)
717 {
718 return;
719 }
720
721 bool wasDecorations{IsDecorationActive()};
722 m_windowState = state;
723
724 m_buttonColor = m_windowState.test(IShellSurface::STATE_ACTIVATED) ? BUTTON_COLOR_ACTIVE : BUTTON_COLOR_INACTIVE;
725
726 CLog::Log(LOGDEBUG, "CWindowDecorator::SetState: Setting full surface size %dx%d scale %d (main surface size %dx%d), decorations active: %u", size.Width(), size.Height(), scale, mainSurfaceSize.Width(), mainSurfaceSize.Height(), IsDecorationActive());
727
728 if (mainSurfaceSize != m_mainSurfaceSize || scale != m_scale || wasDecorations != IsDecorationActive())
729 {
730 if (scale != m_scale)
731 {
732 // Reload cursor theme
733 CLog::Log(LOGDEBUG, "CWindowDecorator::SetState: Buffer scale changed, reloading cursor theme");
734 m_cursorTheme = wayland::cursor_theme_t{};
735 for (auto& seat : m_seats)
736 {
737 UpdateSeatCursor(seat.second);
738 }
739 }
740
741 m_mainSurfaceSize = mainSurfaceSize;
742 m_scale = scale;
743 CLog::Log(LOGDEBUG, "CWindowDecorator::SetState: Resetting decorations");
744 Reset(true);
745 }
746 else if (IsDecorationActive())
747 {
748 CLog::Log(LOGDEBUG, "CWindowDecorator::SetState: Repainting decorations");
749 // Only state differs, no reallocation needed
750 Reset(false);
751 }
752 }
753
Reset(bool reallocate)754 void CWindowDecorator::Reset(bool reallocate)
755 {
756 // The complete reset operation should be seen as one atomic update to the
757 // internal state, otherwise buffer/surface state might be mismatched
758 CSingleLock lock(m_mutex);
759
760 if (reallocate)
761 {
762 ResetButtons();
763 ResetSurfaces();
764 ResetShm();
765 if (IsDecorationActive())
766 {
767 AllocateBuffers();
768 ReattachSubsurfaces();
769 PositionButtons();
770 }
771 }
772
773 if (IsDecorationActive())
774 {
775 Repaint();
776 }
777 }
778
ResetButtons()779 void CWindowDecorator::ResetButtons()
780 {
781 if (IsDecorationActive())
782 {
783 if (m_buttons.empty())
784 {
785 // Minimize
786 m_buttons.emplace_back();
787 Button& minimize = m_buttons.back();
788 minimize.draw = [this](Surface& surface, CRectInt position, bool hover)
789 {
790 DrawButton(surface, m_buttonColor, position, hover);
791 DrawHorizontalLine(surface, m_buttonColor, position.P1() + CPointInt{BUTTON_INNER_SEPARATION, position.Height() - BUTTON_INNER_SEPARATION - 1}, position.Width() - 2 * BUTTON_INNER_SEPARATION);
792 };
793 minimize.onClick = [this] { m_handler.OnWindowMinimize(); };
794
795 // Maximize
796 m_buttons.emplace_back();
797 Button& maximize = m_buttons.back();
798 maximize.draw = [this](Surface& surface, CRectInt position, bool hover)
799 {
800 DrawButton(surface, m_buttonColor, position, hover);
801 DrawRectangle(surface, m_buttonColor, {position.P1() + CPointInt{BUTTON_INNER_SEPARATION, BUTTON_INNER_SEPARATION}, position.P2() - CPointInt{BUTTON_INNER_SEPARATION, BUTTON_INNER_SEPARATION}});
802 DrawHorizontalLine(surface, m_buttonColor, position.P1() + CPointInt{BUTTON_INNER_SEPARATION, BUTTON_INNER_SEPARATION + 1}, position.Width() - 2 * BUTTON_INNER_SEPARATION);
803 };
804 maximize.onClick = [this] { m_handler.OnWindowMaximize(); };
805
806 // Close
807 m_buttons.emplace_back();
808 Button& close = m_buttons.back();
809 close.draw = [this](Surface& surface, CRectInt position, bool hover)
810 {
811 DrawButton(surface, m_buttonColor, position, hover);
812 auto height = position.Height() - 2 * BUTTON_INNER_SEPARATION;
813 DrawAngledLine(surface, m_buttonColor, position.P1() + CPointInt{BUTTON_INNER_SEPARATION, BUTTON_INNER_SEPARATION}, height, 1);
814 DrawAngledLine(surface, m_buttonColor, position.P1() + CPointInt{position.Width() - BUTTON_INNER_SEPARATION - 1, BUTTON_INNER_SEPARATION}, height, -1);
815 };
816 close.onClick = [this] { m_handler.OnWindowClose(); };
817 }
818 }
819 else
820 {
821 m_buttons.clear();
822 }
823 }
824
ResetSurfaces()825 void CWindowDecorator::ResetSurfaces()
826 {
827 if (IsDecorationActive())
828 {
829 if (!m_borderSurfaces.front().surface.wlSurface)
830 {
831 std::generate(m_borderSurfaces.begin(), m_borderSurfaces.end(), std::bind(&CWindowDecorator::MakeBorderSurface, this));
832 }
833 }
834 else
835 {
836 for (auto& borderSurface : m_borderSurfaces)
837 {
838 auto& wlSurface = borderSurface.surface.wlSurface;
839 if (wlSurface)
840 {
841 // Destroying the surface would cause some flicker because it takes effect
842 // immediately, before the next commit on the main surface - just make it
843 // invisible by attaching a NULL buffer
844 wlSurface.attach(wayland::buffer_t{}, 0, 0);
845 wlSurface.set_opaque_region(wayland::region_t{});
846 wlSurface.commit();
847 }
848 }
849 }
850 }
851
ReattachSubsurfaces()852 void CWindowDecorator::ReattachSubsurfaces()
853 {
854 for (auto& surface : m_borderSurfaces)
855 {
856 surface.subsurface.set_position(surface.geometry.x1, surface.geometry.y1);
857 }
858 }
859
GetWindowGeometry() const860 CRectInt CWindowDecorator::GetWindowGeometry() const
861 {
862 CRectInt geometry{{0, 0}, m_mainSurfaceSize};
863
864 if (IsDecorationActive())
865 {
866 for (auto const& surface : m_borderSurfaces)
867 {
868 geometry.Union(surface.windowRect + surface.geometry.P1());
869 }
870 }
871
872 return geometry;
873 }
874
ResetShm()875 void CWindowDecorator::ResetShm()
876 {
877 if (IsDecorationActive())
878 {
879 m_memory.reset(new CSharedMemory(MemoryBytesForSize(m_mainSurfaceSize, m_scale)));
880 m_memoryAllocatedSize = 0;
881 m_shmPool = m_shm.create_pool(m_memory->Fd(), m_memory->Size());
882 }
883 else
884 {
885 m_memory.reset();
886 m_shmPool.proxy_release();
887 }
888
889 for (auto& borderSurface : m_borderSurfaces)
890 {
891 // Buffers are invalid now, reset
892 borderSurface.surface.buffer = Buffer{};
893 }
894 }
895
PositionButtons()896 void CWindowDecorator::PositionButtons()
897 {
898 CPointInt position{m_borderSurfaces[SURFACE_TOP].surface.size.Width() - BORDER_WIDTH, BORDER_WIDTH + BUTTONS_EDGE_DISTANCE};
899 for (auto iter = m_buttons.rbegin(); iter != m_buttons.rend(); iter++)
900 {
901 position.x -= (BUTTONS_EDGE_DISTANCE + BUTTON_SIZE);
902 // Clamp if not enough space
903 position.x = std::max(0, position.x);
904
905 iter->position = CRectInt{position, position + CPointInt{BUTTON_SIZE, BUTTON_SIZE}};
906 }
907 }
908
GetBuffer(CSizeInt size)909 CWindowDecorator::Buffer CWindowDecorator::GetBuffer(CSizeInt size)
910 {
911 // We ignore tearing on the decorations for now.
912 // We can always implement a clever buffer management scheme later... :-)
913
914 auto totalSize{size.Area() * BYTES_PER_PIXEL};
915 if (m_memory->Size() < m_memoryAllocatedSize + totalSize)
916 {
917 // We miscalculated something
918 throw std::logic_error("Remaining SHM pool size is too small for requested buffer");
919 }
920 // argb8888 support is mandatory
921 auto buffer = m_shmPool.create_buffer(m_memoryAllocatedSize, size.Width(), size.Height(), size.Width() * BYTES_PER_PIXEL, wayland::shm_format::argb8888);
922
923 void* data{static_cast<std::uint8_t*> (m_memory->Data()) + m_memoryAllocatedSize};
924 m_memoryAllocatedSize += totalSize;
925
926 return { data, static_cast<std::size_t> (totalSize), size, std::move(buffer) };
927 }
928
AllocateBuffers()929 void CWindowDecorator::AllocateBuffers()
930 {
931 for (std::size_t i{0}; i < m_borderSurfaces.size(); i++)
932 {
933 auto& borderSurface = m_borderSurfaces[i];
934 if (!borderSurface.surface.buffer.data)
935 {
936 borderSurface.geometry = SurfaceGeometry(static_cast<SurfaceIndex> (i), m_mainSurfaceSize);
937 borderSurface.windowRect = SurfaceWindowRegion(static_cast<SurfaceIndex> (i), m_mainSurfaceSize);
938 borderSurface.surface.buffer = GetBuffer(borderSurface.geometry.ToSize() * m_scale);
939 borderSurface.surface.scale = m_scale;
940 borderSurface.surface.size = borderSurface.geometry.ToSize();
941 auto opaqueRegionGeometry = SurfaceOpaqueRegion(static_cast<SurfaceIndex> (i), m_mainSurfaceSize);
942 auto region = m_compositor.create_region();
943 region.add(opaqueRegionGeometry.x1, opaqueRegionGeometry.y1, opaqueRegionGeometry.Width(), opaqueRegionGeometry.Height());
944 borderSurface.surface.wlSurface.set_opaque_region(region);
945 if (borderSurface.surface.wlSurface.can_set_buffer_scale())
946 {
947 borderSurface.surface.wlSurface.set_buffer_scale(m_scale);
948 }
949 }
950 }
951 }
952
Repaint()953 void CWindowDecorator::Repaint()
954 {
955 // Fill transparent (outer) and color (inner)
956 for (auto& borderSurface : m_borderSurfaces)
957 {
958 std::fill_n(static_cast<std::uint32_t*> (borderSurface.surface.buffer.data), borderSurface.surface.buffer.size.Area(), Endian_SwapLE32(TRANSPARENT));
959 FillRectangle(borderSurface.surface, BORDER_COLOR, borderSurface.windowRect - CSizeInt{1, 1});
960 }
961 auto& topSurface = m_borderSurfaces[SURFACE_TOP].surface;
962 auto innerBorderColor = m_buttonColor;
963 // Draw rectangle
964 DrawHorizontalLine(topSurface, innerBorderColor, {BORDER_WIDTH - 1, BORDER_WIDTH - 1}, topSurface.size.Width() - 2 * BORDER_WIDTH + 2);
965 DrawVerticalLine(topSurface, innerBorderColor, {BORDER_WIDTH - 1, BORDER_WIDTH - 1}, topSurface.size.Height() - BORDER_WIDTH + 1);
966 DrawVerticalLine(topSurface, innerBorderColor, {topSurface.size.Width() - BORDER_WIDTH, BORDER_WIDTH - 1}, topSurface.size.Height() - BORDER_WIDTH + 1);
967 DrawVerticalLine(m_borderSurfaces[SURFACE_LEFT].surface, innerBorderColor, {BORDER_WIDTH - 1, 0}, m_borderSurfaces[SURFACE_LEFT].surface.size.Height());
968 DrawVerticalLine(m_borderSurfaces[SURFACE_RIGHT].surface, innerBorderColor, {0, 0}, m_borderSurfaces[SURFACE_RIGHT].surface.size.Height());
969 DrawHorizontalLine(m_borderSurfaces[SURFACE_BOTTOM].surface, innerBorderColor, {BORDER_WIDTH - 1, 0}, m_borderSurfaces[SURFACE_BOTTOM].surface.size.Width() - 2 * BORDER_WIDTH + 2);
970 // Draw white line into top bar as separator
971 DrawHorizontalLine(topSurface, innerBorderColor, {BORDER_WIDTH - 1, topSurface.size.Height() - 1}, topSurface.size.Width() - 2 * BORDER_WIDTH + 2);
972 // Draw buttons
973 for (auto& button : m_buttons)
974 {
975 button.draw(topSurface, button.position, button.hovered);
976 }
977
978 // Finally make everything visible
979 CommitAllBuffers();
980 }
981
CommitAllBuffers()982 void CWindowDecorator::CommitAllBuffers()
983 {
984 CSingleLock lock(m_pendingBuffersMutex);
985
986 for (auto& borderSurface : m_borderSurfaces)
987 {
988 auto& wlSurface = borderSurface.surface.wlSurface;
989 auto& wlBuffer = borderSurface.surface.buffer.wlBuffer;
990 // Keep buffers in list so they are kept alive even when the Buffer gets
991 // recreated on size change
992 auto emplaceResult = m_pendingBuffers.emplace(wlBuffer);
993 if (emplaceResult.second)
994 {
995 // Buffer was not pending already
996 auto wlBufferC = reinterpret_cast<wl_buffer*> (wlBuffer.c_ptr());
997 // We can refer to the buffer neither by iterator (might be invalidated) nor by
998 // capturing the C++ instance in the lambda (would create a reference loop and
999 // never allow the object to be freed), so use the raw pointer for now
1000 wlBuffer.on_release() = [this, wlBufferC]()
1001 {
1002 CSingleLock lock(m_pendingBuffersMutex);
1003 // Construct a dummy object for searching the set
1004 wayland::buffer_t findDummy(wlBufferC, wayland::proxy_t::wrapper_type::foreign);
1005 auto iter = m_pendingBuffers.find(findDummy);
1006 if (iter == m_pendingBuffers.end())
1007 {
1008 throw std::logic_error("Cannot release buffer that is not pending");
1009 }
1010
1011 // Do not erase again until buffer is reattached (should not happen anyway, just to be safe)
1012 // const_cast is OK since changing the function pointer does not affect
1013 // the key in the set
1014 const_cast<wayland::buffer_t&>(*iter).on_release() = nullptr;
1015 m_pendingBuffers.erase(iter);
1016 };
1017 }
1018
1019 wlSurface.attach(wlBuffer, 0, 0);
1020 wlSurface.damage(0, 0, borderSurface.surface.size.Width(), borderSurface.surface.size.Height());
1021 wlSurface.commit();
1022 }
1023 }
1024
LoadCursorTheme()1025 void CWindowDecorator::LoadCursorTheme()
1026 {
1027 CSingleLock lock(m_mutex);
1028 if (!m_cursorTheme)
1029 {
1030 // Load default cursor theme
1031 // Base size of 24px is what most cursor themes seem to have
1032 m_cursorTheme = wayland::cursor_theme_t("", 24 * m_scale, m_shm);
1033 }
1034 }
1035