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