Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwaylandadwaitadecoration.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 Jan Grulich <jgrulich@redhat.com>
2// Copyright (C) 2023 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:significant reason:default
5
7
8// QtCore
9#include <QtCore/QLoggingCategory>
10#include <QScopeGuard>
11
12// QtDBus
13#include <QtDBus/QDBusArgument>
14#include <QtDBus/QDBusConnection>
15#include <QtDBus/QDBusMessage>
16#include <QtDBus/QDBusPendingCall>
17#include <QtDBus/QDBusPendingCallWatcher>
18#include <QtDBus/QDBusPendingReply>
19#include <QtDBus/QDBusVariant>
20#include <QtDBus/QtDBus>
21
22// QtGui
23#include <QtGui/QColor>
24#include <QtGui/QPainter>
25#include <QtGui/QPainterPath>
26
27#include <QtGui/private/qguiapplication_p.h>
28#include <QtGui/qpa/qplatformtheme.h>
29
30// QtSvg
31#include <QtSvg/QSvgRenderer>
32
33// QtWayland
34#include <QtWaylandClient/private/qwaylandshmbackingstore_p.h>
35#include <QtWaylandClient/private/qwaylandwindow_p.h>
36
38
39using namespace Qt::StringLiterals;
40
41namespace QtWaylandClient {
42
43static constexpr int ceButtonSpacing = 12;
44static constexpr int ceButtonWidth = 24;
45static constexpr int ceCornerRadius = 12;
46static constexpr int ceShadowsWidth = 10;
47static constexpr int ceTitlebarHeight = 38;
48static constexpr int ceWindowBorderWidth = 1;
49
51 { QWaylandAdwaitaDecoration::CloseIcon, "window-close-symbolic"_L1 },
52 { QWaylandAdwaitaDecoration::MinimizeIcon, "window-minimize-symbolic"_L1 },
53 { QWaylandAdwaitaDecoration::MaximizeIcon, "window-maximize-symbolic"_L1 },
54 { QWaylandAdwaitaDecoration::RestoreIcon, "window-restore-symbolic"_L1 }
55};
56
57const QDBusArgument &operator>>(const QDBusArgument &argument, QMap<QString, QVariantMap> &map)
58{
59 argument.beginMap();
60 map.clear();
61
62 while (!argument.atEnd()) {
63 QString key;
64 QVariantMap value;
65 argument.beginMapEntry();
66 argument >> key >> value;
67 argument.endMapEntry();
68 map.insert(key, value);
69 }
70
71 argument.endMap();
72 return argument;
73}
74
75Q_LOGGING_CATEGORY(lcQWaylandAdwaitaDecorationLog, "qt.qpa.qwaylandadwaitadecoration", QtWarningMsg)
76
95
96QMargins QWaylandAdwaitaDecoration::margins(QWaylandAbstractDecoration::MarginsType marginsType) const
97{
98 const bool onlyShadows = marginsType == QWaylandAbstractDecoration::ShadowsOnly;
99 const bool shadowsExcluded = marginsType == ShadowsExcluded;
100
101 if (waylandWindow()->windowStates() & Qt::WindowMaximized) {
102 // Maximized windows don't have anything around, no shadows, border,
103 // etc. Only report titlebar height in case we are not asking for shadow
104 // margins.
105 return QMargins(0, onlyShadows ? 0 : ceTitlebarHeight, 0, 0);
106 }
107
108 const QWaylandWindow::ToplevelWindowTilingStates tilingStates = waylandWindow()->toplevelWindowTilingStates();
109
110 // Since all sides (left, right, bottom) are going to be same
111 const int marginsBase = shadowsExcluded ? ceWindowBorderWidth : ceShadowsWidth + ceWindowBorderWidth;
112 const int sideMargins = onlyShadows ? ceShadowsWidth : marginsBase;
113 const int topMargins = onlyShadows ? ceShadowsWidth : ceTitlebarHeight + marginsBase;
114
115 return QMargins(tilingStates & QWaylandWindow::WindowTiledLeft ? 0 : sideMargins,
116 tilingStates & QWaylandWindow::WindowTiledTop ? onlyShadows ? 0 : ceTitlebarHeight : topMargins,
117 tilingStates & QWaylandWindow::WindowTiledRight ? 0 : sideMargins,
118 tilingStates & QWaylandWindow::WindowTiledBottom ? 0 : sideMargins);
119}
120
121void QWaylandAdwaitaDecoration::paint(QPaintDevice *device)
122{
123 const QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(ShadowsOnly);
124
125 QPainter p(device);
126 p.setRenderHint(QPainter::Antialiasing);
127
128 /*
129 * Titlebar and window border
130 */
131 QPainterPath path;
132 const QPointF topLeft = { margins(ShadowsOnly).left() + qreal(0.5),
133 margins(ShadowsOnly).top() - qreal(0.5) };
134 const int frameWidth = surfaceRect.width() - margins(ShadowsOnly).left()
135 - margins(ShadowsOnly).right() - qreal(0.5);
136 const int frameHeight = surfaceRect.height() - margins(ShadowsOnly).top()
137 - margins(ShadowsOnly).bottom() + qreal(0.5);
138 const QRectF fullFrameRect = QRectF(topLeft, QSizeF(frameWidth, frameHeight));
139
140 // Maximized or tiled won't have rounded corners
141 if (waylandWindow()->windowStates() & Qt::WindowMaximized
142 || waylandWindow()->toplevelWindowTilingStates() != QWaylandWindow::WindowNoState) {
143 path.addRect(fullFrameRect);
144 } else {
145 const QSizeF radiusRectSize = QSizeF(ceCornerRadius * 2, ceCornerRadius * 2);
146 path.moveTo(fullFrameRect.bottomLeft());
147 path.lineTo(fullFrameRect.topLeft() + QPointF(0, ceCornerRadius));
148 path.arcTo(QRectF(fullFrameRect.topLeft(), radiusRectSize), 180, -90);
149 path.lineTo(fullFrameRect.topRight() - QPointF(ceCornerRadius, 0));
150 path.arcTo(QRectF(fullFrameRect.topRight() - QPointF(ceCornerRadius * 2, 0),
151 radiusRectSize),
152 90, -90);
153 path.lineTo(fullFrameRect.bottomRight());
154 path.closeSubpath();
155 }
156
157 p.save();
158 p.setPen(color(Border));
159 p.fillPath(path.simplified(), color(Background));
160 p.drawPath(path);
161 p.restore();
162
163 /*
164 * Window title
165 */
166 const QRect top = QRect(margins().left(), margins().bottom(), surfaceRect.width(),
167 margins().top() - margins().bottom());
168 const QString windowTitleText = waylandWindow()->windowTitle();
169 if (!windowTitleText.isEmpty()) {
170 if (m_windowTitle.text() != windowTitleText) {
171 m_windowTitle.setText(windowTitleText);
172 m_windowTitle.prepare();
173 }
174
175 QRect titleBar = top;
176 if (m_placement == Right) {
177 titleBar.setLeft(margins().left());
178 titleBar.setRight(static_cast<int>(buttonRect(Minimize).left()) - 8);
179 } else {
180 titleBar.setLeft(static_cast<int>(buttonRect(Minimize).right()) + 8);
181 titleBar.setRight(surfaceRect.width() - margins().right());
182 }
183
184 p.save();
185 p.setClipRect(titleBar);
186 p.setPen(color(Foreground));
187 QSize size = m_windowTitle.size().toSize();
188 int dx = (top.width() - size.width()) / 2;
189 int dy = (top.height() - size.height()) / 2;
190 p.setFont(*m_font);
191 QPoint windowTitlePoint(top.topLeft().x() + dx, top.topLeft().y() + dy);
192 p.drawStaticText(windowTitlePoint, m_windowTitle);
193 p.restore();
194 }
195
196
197 /*
198 * Buttons
199 */
200 if (m_buttons.contains(Close))
201 drawButton(Close, &p);
202
203 if (m_buttons.contains(Maximize))
204 drawButton(Maximize, &p);
205
206 if (m_buttons.contains(Minimize))
207 drawButton(Minimize, &p);
208}
209
210bool QWaylandAdwaitaDecoration::handleMouse(QWaylandInputDevice *inputDevice, const QPointF &local,
211 const QPointF &global, Qt::MouseButtons b,
212 Qt::KeyboardModifiers mods)
213{
214 Q_UNUSED(global)
215
216 if (local.y() > margins().top())
217 updateButtonHoverState(Button::None);
218
219 // Figure out what area mouse is in
220 QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(ShadowsOnly);
221 if (local.y() <= surfaceRect.top() + margins().top())
222 processMouseTop(inputDevice, local, b, mods);
223 else if (local.y() > surfaceRect.bottom() - margins().bottom())
224 processMouseBottom(inputDevice, local, b, mods);
225 else if (local.x() <= surfaceRect.left() + margins().left())
226 processMouseLeft(inputDevice, local, b, mods);
227 else if (local.x() > surfaceRect.right() - margins().right())
228 processMouseRight(inputDevice, local, b, mods);
229 else {
230#if QT_CONFIG(cursor)
231 waylandWindow()->restoreMouseCursor(inputDevice);
232#endif
233 }
234
235 // Reset clicking state in case a button press is released outside
236 // the button area
237 if (isLeftReleased(b)) {
238 m_clicking = None;
239 requestRepaint();
240 }
241
242 setMouseButtons(b);
243 return false;
244}
245
246bool QWaylandAdwaitaDecoration::handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local,
247 const QPointF &global, QEventPoint::State state,
248 Qt::KeyboardModifiers mods)
249{
250 Q_UNUSED(inputDevice)
251 Q_UNUSED(global)
252 Q_UNUSED(mods)
253
254 bool handled = state == QEventPoint::Pressed;
255
256 if (handled) {
257 if (buttonRect(Close).contains(local))
258 QWindowSystemInterface::handleCloseEvent(window());
259 else if (m_buttons.contains(Maximize) && buttonRect(Maximize).contains(local))
260 window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized);
261 else if (m_buttons.contains(Minimize) && buttonRect(Minimize).contains(local))
262 window()->setWindowState(Qt::WindowMinimized);
263 else if (local.y() <= margins().top())
264 waylandWindow()->shellSurface()->move(inputDevice);
265 else
266 handled = false;
267 }
268
269 return handled;
270}
271
272QString getIconSvg(const QString &iconName)
273{
274 const QStringList themeNames = { QIcon::themeName(), QIcon::fallbackThemeName(), "Adwaita"_L1 };
275
276 qCDebug(lcQWaylandAdwaitaDecorationLog) << "Searched icon themes: " << themeNames;
277
278 for (const QString &themeName : themeNames) {
279 if (themeName.isEmpty())
280 continue;
281
282 for (const QString &path : QIcon::themeSearchPaths()) {
283 if (path.startsWith(QLatin1Char(':')))
284 continue;
285
286 const QString fullPath = QString("%1/%2").arg(path).arg(themeName);
287 QDirIterator dirIt(fullPath, {"*.svg"}, QDir::Files, QDirIterator::Subdirectories);
288 while (dirIt.hasNext()) {
289 const QString fileName = dirIt.next();
290 const QFileInfo fileInfo(fileName);
291
292 if (fileInfo.fileName() == iconName) {
293 qCDebug(lcQWaylandAdwaitaDecorationLog) << "Using " << iconName << " from " << themeName << " theme";
294 QFile readFile(fileInfo.filePath());
295 if (!readFile.open(QFile::ReadOnly)) {
296 qCWarning(lcQWaylandAdwaitaDecorationLog, "Failed to open %ls: %ls",
297 qUtf16Printable(readFile.fileName()),
298 qUtf16Printable(readFile.errorString()));
299 }
300 return readFile.readAll();
301 }
302 }
303 }
304 }
305
306 qCWarning(lcQWaylandAdwaitaDecorationLog) << "Failed to find an svg icon for " << iconName;
307
308 return QString();
309}
310
311void QWaylandAdwaitaDecoration::loadConfiguration()
312{
313 qRegisterMetaType<QDBusVariant>();
314 qDBusRegisterMetaType<QMap<QString, QVariantMap>>();
315
316 QDBusConnection connection = QDBusConnection::sessionBus();
317
318 QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1,
319 "/org/freedesktop/portal/desktop"_L1,
320 "org.freedesktop.portal.Settings"_L1,
321 "ReadAll"_L1);
322 message << QStringList{ { "org.gnome.desktop.wm.preferences"_L1 },
323 { "org.freedesktop.appearance"_L1 } };
324
325 QDBusPendingCall pendingCall = connection.asyncCall(message);
326 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
327 QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
328 QDBusPendingReply<QMap<QString, QVariantMap>> reply = *watcher;
329 if (reply.isValid()) {
330 QMap<QString, QVariantMap> settings = reply.value();
331 if (!settings.isEmpty()) {
332 const uint colorScheme = settings.value("org.freedesktop.appearance"_L1).value("color-scheme"_L1).toUInt();
333 updateColors(colorScheme == 1); // 1 == Prefer Dark
334 const QString buttonLayout = settings.value("org.gnome.desktop.wm.preferences"_L1).value("button-layout"_L1).toString();
335 if (!buttonLayout.isEmpty())
336 updateTitlebarLayout(buttonLayout);
337 // Do not rely on titlebar-font in case titlebar-uses-desktop-font is set to true
338 // https://gitlab.gnome.org/GNOME/gsettings-desktop-schemas/-/blob/main/schemas/org.gnome.desktop.wm.preferences.gschema.xml.in
339 const bool titlebarUseDesktopFont = settings.value("org.gnome.desktop.wm.preferences"_L1).value("titlebar-uses-desktop-font"_L1).toBool();
340 if (!titlebarUseDesktopFont) {
341 // Workaround for QGtkStyle not having correct titlebar font
342 const QString titlebarFont =
343 settings.value("org.gnome.desktop.wm.preferences"_L1).value("titlebar-font"_L1).toString();
344 if (titlebarFont.contains("bold"_L1, Qt::CaseInsensitive)) {
345 m_font->setBold(true);
346 }
347 }
348 }
349 }
350 watcher->deleteLater();
351 });
352
353 QDBusConnection::sessionBus().connect(QString(), "/org/freedesktop/portal/desktop"_L1,
354 "org.freedesktop.portal.Settings"_L1, "SettingChanged"_L1, this,
355 SLOT(settingChanged(QString,QString,QDBusVariant)));
356
357 // Load SVG icons
358 for (auto mapIt = buttonMap.constBegin(); mapIt != buttonMap.constEnd(); mapIt++) {
359 const QString fullName = mapIt.value() + QStringLiteral(".svg");
360 m_icons[mapIt.key()] = getIconSvg(fullName);
361 }
362
363 updateColors(false);
364}
365
366void QWaylandAdwaitaDecoration::updateColors(bool isDark)
367{
368 qCDebug(lcQWaylandAdwaitaDecorationLog) << "Color scheme changed to: " << (isDark ? "dark" : "light");
369
370 m_colors = { { Background, isDark ? QColor(0x2e2e32) : QColor(0xffffff) },
371 { BackgroundInactive, isDark ? QColor(0x222226) : QColor(0xfafafb) },
372 { Foreground, isDark ? QColor(0xffffff) : QColor(0x333338) },
373 { ForegroundInactive, isDark ? QColor(0x919193) : QColor(0x969699) },
374 { Border, isDark ? QColor(0x2e2e32) : QColor(0xffffff) },
375 { BorderInactive, isDark ? QColor(0x2e2e32) : QColor(0xffffff) },
376 { ButtonBackground, isDark ? QColor(0x434347) : QColor(0xebebeb) },
377 { ButtonBackgroundInactive, isDark ? QColor(0x2d2d31) : QColor(0xf0f0f1) },
378 { HoveredButtonBackground, isDark ? QColor(0x4d4d51) : QColor(0xe0e0e1) },
379 { PressedButtonBackground, isDark ? QColor(0x6c6c6f) : QColor(0xc2c2c3) } };
380 requestRepaint();
381}
382
383void QWaylandAdwaitaDecoration::updateTitlebarLayout(const QString &layout)
384{
385 const QStringList layouts = layout.split(QLatin1Char(':'));
386 if (layouts.count() != 2)
387 return;
388
389 // Remove previous configuration
390 m_buttons.clear();
391
392 const QString &leftLayout = layouts.at(0);
393 const QString &rightLayout = layouts.at(1);
394 m_placement = leftLayout.contains("close"_L1) ? Left : Right;
395
396 int pos = 1;
397 const QString &buttonLayout = m_placement == Right ? rightLayout : leftLayout;
398
399 QStringList buttonList = buttonLayout.split(QLatin1Char(','));
400 if (m_placement == Right)
401 std::reverse(buttonList.begin(), buttonList.end());
402
403 for (const QString &button : buttonList) {
404 if (button == "close"_L1)
405 m_buttons.insert(Close, pos);
406 else if (button == "maximize"_L1)
407 m_buttons.insert(Maximize, pos);
408 else if (button == "minimize"_L1)
409 m_buttons.insert(Minimize, pos);
410
411 pos++;
412 }
413
414 qCDebug(lcQWaylandAdwaitaDecorationLog) << "Button layout changed to: " << layout;
415
416 requestRepaint();
417}
418
419void QWaylandAdwaitaDecoration::settingChanged(const QString &group, const QString &key,
420 const QDBusVariant &value)
421{
422 if (group == "org.gnome.desktop.wm.preferences"_L1 && key == "button-layout"_L1) {
423 const QString layout = value.variant().toString();
424 updateTitlebarLayout(layout);
425 } else if (group == "org.freedesktop.appearance"_L1 && key == "color-scheme"_L1) {
426 const uint colorScheme = value.variant().toUInt();
427 updateColors(colorScheme == 1); // 1 == Prefer Dark
428 }
429}
430
431QRectF QWaylandAdwaitaDecoration::buttonRect(Button button) const
432{
433 int xPos;
434 int yPos;
435 const int btnPos = m_buttons.value(button);
436
437 const QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(QWaylandAbstractDecoration::ShadowsOnly);
438 if (m_placement == Right) {
439 xPos = surfaceRect.width();
440 xPos -= ceButtonWidth * btnPos;
441 xPos -= ceButtonSpacing * btnPos;
442 xPos -= margins(ShadowsOnly).right();
443 } else {
444 xPos = 0;
445 xPos += ceButtonWidth * btnPos;
446 xPos += ceButtonSpacing * btnPos;
447 xPos += margins(ShadowsOnly).left();
448 // We are painting from the left to the right so the real
449 // position doesn't need to by moved by the size of the button.
450 xPos -= ceButtonWidth;
451 }
452
453 yPos = margins().top();
454 yPos += margins().bottom();
455 yPos -= ceButtonWidth;
456 yPos /= 2;
457
458 return QRectF(xPos, yPos, ceButtonWidth, ceButtonWidth);
459}
460
461static void renderFlatRoundedButtonFrame(QPainter *painter, const QRect &rect, const QColor &color)
462{
463 painter->save();
464 painter->setRenderHint(QPainter::Antialiasing, true);
465 painter->setPen(Qt::NoPen);
466 painter->setBrush(color);
467 painter->drawEllipse(rect);
468 painter->restore();
469}
470
471static void renderButtonIcon(const QString &svgIcon, QPainter *painter, const QRect &rect, const QColor &color)
472{
473 painter->save();
474 painter->setRenderHints(QPainter::Antialiasing, true);
475
476 QString icon = svgIcon;
477 QRegularExpression regexp("fill=[\"']#[0-9A-F]{6}[\"']", QRegularExpression::CaseInsensitiveOption);
478 QRegularExpression regexpAlt("fill:#[0-9A-F]{6}", QRegularExpression::CaseInsensitiveOption);
479 QRegularExpression regexpCurrentColor("fill=[\"']currentColor[\"']");
480 icon.replace(regexp, QString("fill=\"%1\"").arg(color.name()));
481 icon.replace(regexpAlt, QString("fill:%1").arg(color.name()));
482 icon.replace(regexpCurrentColor, QString("fill=\"%1\"").arg(color.name()));
483 QSvgRenderer svgRenderer(icon.toLocal8Bit());
484 svgRenderer.render(painter, rect);
485
486 painter->restore();
487}
488
489static void renderButtonIcon(QWaylandAdwaitaDecoration::ButtonIcon buttonIcon, QPainter *painter, const QRect &rect)
490{
491 QString iconName = buttonMap[buttonIcon];
492
493 painter->save();
494 painter->setRenderHints(QPainter::Antialiasing, true);
495 painter->drawPixmap(rect, QIcon::fromTheme(iconName).pixmap(ceButtonWidth, ceButtonWidth));
496 painter->restore();
497}
498
510
511void QWaylandAdwaitaDecoration::drawButton(Button button, QPainter *painter)
512{
513 const Qt::WindowStates windowStates = waylandWindow()->windowStates();
514 const bool maximized = windowStates & Qt::WindowMaximized;
515
516 const QRect btnRect = buttonRect(button).toRect();
517 renderFlatRoundedButtonFrame(painter, btnRect, color(ButtonBackground, button));
518
519 QRect adjustedBtnRect = btnRect;
520 adjustedBtnRect.setSize(QSize(16, 16));
521 adjustedBtnRect.translate(4, 4);
522 const QString svgIcon = m_icons[iconFromButtonAndState(button, maximized)];
523 if (!svgIcon.isEmpty())
524 renderButtonIcon(svgIcon, painter, adjustedBtnRect, color(Foreground));
525 else // Fallback to use QIcon
526 renderButtonIcon(iconFromButtonAndState(button, maximized), painter, adjustedBtnRect);
527}
528
529static QColor makeTransparent(const QColor &color, qreal level)
530{
531 QColor transparentColor = color;
532 transparentColor.setAlphaF(level);
533 return transparentColor;
534}
535
536QColor QWaylandAdwaitaDecoration::color(ColorType type, Button button)
537{
538 const bool active = waylandWindow()->windowStates() & Qt::WindowActive;
539
540 switch (type) {
541 case Background:
542 case BackgroundInactive:
543 return active ? m_colors[Background] : m_colors[BackgroundInactive];
544 case Foreground:
545 case ForegroundInactive:
546 return active ? m_colors[Foreground] : m_colors[ForegroundInactive];
547 case Border:
548 case BorderInactive:
549 return active ? makeTransparent(m_colors[Border], 0.5) : makeTransparent(m_colors[BorderInactive], 0.5);
550 case ButtonBackground:
551 case ButtonBackgroundInactive:
552 case HoveredButtonBackground: {
553 if (m_clicking == button) {
554 return m_colors[PressedButtonBackground];
555 } else if (m_hoveredButtons.testFlag(button)) {
556 return m_colors[HoveredButtonBackground];
557 }
558 return active ? m_colors[ButtonBackground] : m_colors[ButtonBackgroundInactive];
559 }
560 default:
561 return m_colors[Background];
562 }
563}
564
565bool QWaylandAdwaitaDecoration::clickButton(Qt::MouseButtons b, Button btn)
566{
567 auto repaint = qScopeGuard([this] { requestRepaint(); });
568
569 if (isLeftClicked(b)) {
570 m_clicking = btn;
571 return false;
572 } else if (isLeftReleased(b)) {
573 if (m_clicking == btn) {
574 m_clicking = None;
575 return true;
576 } else {
577 m_clicking = None;
578 }
579 }
580 return false;
581}
582
583bool QWaylandAdwaitaDecoration::doubleClickButton(Qt::MouseButtons b, const QPointF &local,
584 const QDateTime &currentTime)
585{
586 if (isLeftClicked(b)) {
587 const qint64 clickInterval = m_lastButtonClick.msecsTo(currentTime);
588 m_lastButtonClick = currentTime;
589 const int doubleClickDistance = 5;
590 const QPointF posDiff = m_lastButtonClickPosition - local;
591 if ((clickInterval <= 500)
592 && ((posDiff.x() <= doubleClickDistance && posDiff.x() >= -doubleClickDistance)
593 && ((posDiff.y() <= doubleClickDistance && posDiff.y() >= -doubleClickDistance)))) {
594 return true;
595 }
596
597 m_lastButtonClickPosition = local;
598 }
599
600 return false;
601}
602
603void QWaylandAdwaitaDecoration::updateButtonHoverState(Button hoveredButton)
604{
605 bool currentCloseButtonState = m_hoveredButtons.testFlag(Close);
606 bool currentMaximizeButtonState = m_hoveredButtons.testFlag(Maximize);
607 bool currentMinimizeButtonState = m_hoveredButtons.testFlag(Minimize);
608
609 m_hoveredButtons.setFlag(Close, hoveredButton == Button::Close);
610 m_hoveredButtons.setFlag(Maximize, hoveredButton == Button::Maximize);
611 m_hoveredButtons.setFlag(Minimize, hoveredButton == Button::Minimize);
612
613 if (m_hoveredButtons.testFlag(Close) != currentCloseButtonState
614 || m_hoveredButtons.testFlag(Maximize) != currentMaximizeButtonState
615 || m_hoveredButtons.testFlag(Minimize) != currentMinimizeButtonState) {
616 requestRepaint();
617 }
618}
619
620void QWaylandAdwaitaDecoration::processMouseTop(QWaylandInputDevice *inputDevice, const QPointF &local,
621 Qt::MouseButtons b, Qt::KeyboardModifiers mods)
622{
623 Q_UNUSED(mods)
624
625 QDateTime currentDateTime = QDateTime::currentDateTime();
626 QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(ShadowsOnly);
627
628 if (!buttonRect(Close).contains(local) && !buttonRect(Maximize).contains(local)
629 && !buttonRect(Minimize).contains(local))
630 updateButtonHoverState(Button::None);
631
632 if (local.y() <= surfaceRect.top() + margins().bottom()) {
633 if (local.x() <= margins().left()) {
634 // top left bit
635#if QT_CONFIG(cursor)
636 waylandWindow()->applyCursor(inputDevice, Qt::SizeFDiagCursor);
637#endif
638 startResize(inputDevice, Qt::TopEdge | Qt::LeftEdge, b);
639 } else if (local.x() > surfaceRect.right() - margins().left()) {
640 // top right bit
641#if QT_CONFIG(cursor)
642 waylandWindow()->applyCursor(inputDevice, Qt::SizeBDiagCursor);
643#endif
644 startResize(inputDevice, Qt::TopEdge | Qt::RightEdge, b);
645 } else {
646 // top resize bit
647#if QT_CONFIG(cursor)
648 waylandWindow()->applyCursor(inputDevice, Qt::SizeVerCursor);
649#endif
650 startResize(inputDevice, Qt::TopEdge, b);
651 }
652 } else if (local.x() <= surfaceRect.left() + margins().left()) {
653 processMouseLeft(inputDevice, local, b, mods);
654 } else if (local.x() > surfaceRect.right() - margins().right()) {
655 processMouseRight(inputDevice, local, b, mods);
656 } else if (buttonRect(Close).contains(local)) {
657 if (clickButton(b, Close)) {
658 QWindowSystemInterface::handleCloseEvent(window());
659 m_hoveredButtons.setFlag(Close, false);
660 }
661 updateButtonHoverState(Close);
662 } else if (m_buttons.contains(Maximize) && buttonRect(Maximize).contains(local)) {
663 updateButtonHoverState(Maximize);
664 if (clickButton(b, Maximize)) {
665 window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized);
666 m_hoveredButtons.setFlag(Maximize, false);
667 }
668 } else if (m_buttons.contains(Minimize) && buttonRect(Minimize).contains(local)) {
669 updateButtonHoverState(Minimize);
670 if (clickButton(b, Minimize)) {
671 window()->setWindowState(Qt::WindowMinimized);
672 m_hoveredButtons.setFlag(Minimize, false);
673 }
674 } else if (doubleClickButton(b, local, currentDateTime)) {
675 window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized);
676 } else {
677 // Show window menu
678 if (b == Qt::MouseButton::RightButton)
679 waylandWindow()->shellSurface()->showWindowMenu(inputDevice);
680#if QT_CONFIG(cursor)
681 waylandWindow()->restoreMouseCursor(inputDevice);
682#endif
683 startMove(inputDevice, b);
684 }
685}
686
687void QWaylandAdwaitaDecoration::processMouseBottom(QWaylandInputDevice *inputDevice, const QPointF &local,
688 Qt::MouseButtons b, Qt::KeyboardModifiers mods)
689{
690 Q_UNUSED(mods)
691 if (local.x() <= margins().left()) {
692 // bottom left bit
693#if QT_CONFIG(cursor)
694 waylandWindow()->applyCursor(inputDevice, Qt::SizeBDiagCursor);
695#endif
696 startResize(inputDevice, Qt::BottomEdge | Qt::LeftEdge, b);
697 } else if (local.x() > window()->width() + margins().right()) {
698 // bottom right bit
699#if QT_CONFIG(cursor)
700 waylandWindow()->applyCursor(inputDevice, Qt::SizeFDiagCursor);
701#endif
702 startResize(inputDevice, Qt::BottomEdge | Qt::RightEdge, b);
703 } else {
704 // bottom bit
705#if QT_CONFIG(cursor)
706 waylandWindow()->applyCursor(inputDevice, Qt::SizeVerCursor);
707#endif
708 startResize(inputDevice, Qt::BottomEdge, b);
709 }
710}
711
712void QWaylandAdwaitaDecoration::processMouseLeft(QWaylandInputDevice *inputDevice, const QPointF &local,
713 Qt::MouseButtons b, Qt::KeyboardModifiers mods)
714{
715 Q_UNUSED(local)
716 Q_UNUSED(mods)
717#if QT_CONFIG(cursor)
718 waylandWindow()->applyCursor(inputDevice, Qt::SizeHorCursor);
719#endif
720 startResize(inputDevice, Qt::LeftEdge, b);
721}
722
723void QWaylandAdwaitaDecoration::processMouseRight(QWaylandInputDevice *inputDevice, const QPointF &local,
724 Qt::MouseButtons b, Qt::KeyboardModifiers mods)
725{
726 Q_UNUSED(local)
727 Q_UNUSED(mods)
728#if QT_CONFIG(cursor)
729 waylandWindow()->applyCursor(inputDevice, Qt::SizeHorCursor);
730#endif
731 startResize(inputDevice, Qt::RightEdge, b);
732}
733
734void QWaylandAdwaitaDecoration::requestRepaint() const
735{
736 // Set dirty flag
737 if (waylandWindow()->decoration())
738 waylandWindow()->decoration()->update();
739
740 // Request re-paint
741 waylandWindow()->window()->requestUpdate();
742}
743
744} // namespace QtWaylandClient
745
746QT_END_NAMESPACE
747
748#include "moc_qwaylandadwaitadecoration_p.cpp"
\inmodule QtDBus
friend class QPainter
bool handleMouse(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, Qt::MouseButtons b, Qt::KeyboardModifiers mods) override
bool handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, QEventPoint::State state, Qt::KeyboardModifiers mods) override
QMargins margins(MarginsType marginsType=Full) const override
Combined button and popup list for selecting options.
Q_LOGGING_CATEGORY(lcQpaWayland, "qt.qpa.wayland")
static QWaylandAdwaitaDecoration::ButtonIcon iconFromButtonAndState(QWaylandAdwaitaDecoration::Button button, bool maximized)
static constexpr int ceTitlebarHeight
static constexpr int ceButtonWidth
static QMap< QWaylandAdwaitaDecoration::ButtonIcon, QString > buttonMap
static constexpr int ceButtonSpacing
static constexpr int ceWindowBorderWidth
static constexpr int ceShadowsWidth
static QColor makeTransparent(const QColor &color, qreal level)
const QDBusArgument & operator>>(const QDBusArgument &argument, QMap< QString, QVariantMap > &map)
static void renderButtonIcon(const QString &svgIcon, QPainter *painter, const QRect &rect, const QColor &color)
QString getIconSvg(const QString &iconName)
static constexpr int ceCornerRadius
static void renderButtonIcon(QWaylandAdwaitaDecoration::ButtonIcon buttonIcon, QPainter *painter, const QRect &rect)
static void renderFlatRoundedButtonFrame(QPainter *painter, const QRect &rect, const QColor &color)