/* * MIT License * * Copyright (C) 2022 by wangwenx190 (Yuhang Zhao) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "utils.h" #include #include #include #include #include #include #include #if ((QT_VERSION >= QT_VERSION_CHECK(5, 9, 1)) && (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))) # include #endif //#include #include FRAMELESSHELPER_BEGIN_NAMESPACE using namespace Global; #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) static constexpr const auto _NET_WM_MOVERESIZE_SIZE_TOPLEFT = 0; static constexpr const auto _NET_WM_MOVERESIZE_SIZE_TOP = 1; static constexpr const auto _NET_WM_MOVERESIZE_SIZE_TOPRIGHT = 2; static constexpr const auto _NET_WM_MOVERESIZE_SIZE_RIGHT = 3; static constexpr const auto _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT = 4; static constexpr const auto _NET_WM_MOVERESIZE_SIZE_BOTTOM = 5; static constexpr const auto _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT = 6; static constexpr const auto _NET_WM_MOVERESIZE_SIZE_LEFT = 7; static constexpr const auto _NET_WM_MOVERESIZE_MOVE = 8; #endif FRAMELESSHELPER_BYTEARRAY_CONSTANT(display) FRAMELESSHELPER_BYTEARRAY_CONSTANT(x11screen) FRAMELESSHELPER_BYTEARRAY_CONSTANT(rootwindow) #if 0 template [[nodiscard]] static inline T gtkSetting(const gchar *propertyName) { Q_ASSERT(propertyName); if (!propertyName) { return {}; } GtkSettings * const settings = gtk_settings_get_default(); Q_ASSERT(settings); if (!settings) { return {}; } T result = {}; g_object_get(settings, propertyName, &result, nullptr); return result; } [[nodiscard]] static inline QString gtkSetting(const gchar *propertyName) { Q_ASSERT(propertyName); if (!propertyName) { return {}; } const auto value = gtkSetting(propertyName); const QString result = QString::fromUtf8(value); g_free(value); return result; } #endif #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) [[nodiscard]] static inline Qt::WindowFrameSection qtEdgesToQtWindowFrameSection(const Qt::Edges edges) { if (edges == Qt::Edges{}) { return Qt::NoSection; } if (edges & Qt::TopEdge) { if (edges & Qt::LeftEdge) { return Qt::TopLeftSection; } if (edges & Qt::RightEdge) { return Qt::TopRightSection; } return Qt::TopSection; } if (edges & Qt::BottomEdge) { if (edges & Qt::LeftEdge) { return Qt::BottomLeftSection; } if (edges & Qt::RightEdge) { return Qt::BottomRightSection; } return Qt::BottomSection; } if (edges & Qt::LeftEdge) { return Qt::LeftSection; } if (edges & Qt::RightEdge) { return Qt::RightSection; } return Qt::NoSection; } #endif [[nodiscard]] bool shouldAppsUseDarkMode_linux() { #if 0 /* https://docs.gtk.org/gtk3/running.html It's possible to set a theme variant after the theme name when using GTK_THEME: GTK_THEME=Adwaita:dark Some themes also have "-dark" as part of their name. We test this environment variable first because the documentation says it's mainly used for easy debugging, so it should be possible to use it to override any other settings. */ QString themeName = qEnvironmentVariable("GTK_THEME"); const QRegularExpression darkRegex(QStringLiteral("[:-]dark"), QRegularExpression::CaseInsensitiveOption); if (!themeName.isEmpty()) { return darkRegex.match(themeName).hasMatch(); } /* https://docs.gtk.org/gtk3/property.Settings.gtk-application-prefer-dark-theme.html This setting controls which theme is used when the theme specified by gtk-theme-name provides both light and dark variants. We can save a regex check by testing this property first. */ const auto preferDark = gtkSetting("gtk-application-prefer-dark-theme"); if (preferDark) { return true; } /* https://docs.gtk.org/gtk3/property.Settings.gtk-theme-name.html */ themeName = gtkSetting("gtk-theme-name"); if (!themeName.isEmpty()) { return darkRegex.match(themeName).hasMatch(); } return false; #else return false; #endif } #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) [[nodiscard]] static inline Display *x11_get_display() { if (!qGuiApp) { return nullptr; } QPlatformNativeInterface * const iface = qGuiApp->platformNativeInterface(); if (!iface) { return nullptr; } const auto display = iface->nativeResourceForIntegration(kdisplay); if (!display) { return nullptr; } return static_cast(display); } [[nodiscard]] static inline qintptr x11_get_desktop() { if (!qGuiApp) { return 0; } QPlatformNativeInterface * const iface = qGuiApp->platformNativeInterface(); if (!iface) { return 0; } const auto screen = iface->nativeResourceForIntegration(kx11screen); if (!screen) { return 0; } return reinterpret_cast(screen); } [[nodiscard]] static inline QScreen *x11_getScreenForVirtualDesktop(const int virtualDesktopNumber) { #if ((QT_VERSION >= QT_VERSION_CHECK(5, 9, 1)) && (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))) if (virtualDesktopNumber == -1) { return QGuiApplication::primaryScreen(); } const QList screens = QGuiApplication::screens(); if (screens.isEmpty()) { return nullptr; } for (auto &&screen : qAsConst(screens)) { if (QXcbScreenFunctions::virtualDesktopNumber(screen) == virtualDesktopNumber) { return screen; } } return nullptr; #else Q_UNUSED(virtualDesktopNumber); return QGuiApplication::primaryScreen(); #endif } [[nodiscard]] static inline quintptr x11_get_window(const int desktop) { if (!qGuiApp) { return 0; } QPlatformNativeInterface * const iface = qGuiApp->platformNativeInterface(); if (!iface) { return 0; } QScreen * const screen = x11_getScreenForVirtualDesktop(desktop); if (!screen) { return 0; } const auto rootWindow = iface->nativeResourceForScreen(krootwindow, screen); if (!rootWindow) { return 0; } return reinterpret_cast(rootWindow); } static inline void x11_emulateButtonRelease(const WId windowId, const QPoint &globalPos, const QPoint &localPos) { Q_ASSERT(windowId); if (!windowId) { return; } const Window window = windowId; Display * const display = x11_get_display(); XEvent event; memset(&event, 0, sizeof(event)); event.xbutton.button = 0; event.xbutton.same_screen = True; event.xbutton.send_event = True; event.xbutton.window = window; event.xbutton.root = x11_get_window(x11_get_desktop()); event.xbutton.x_root = globalPos.x(); event.xbutton.y_root = globalPos.y(); event.xbutton.x = localPos.x(); event.xbutton.y = localPos.y(); event.xbutton.type = ButtonRelease; event.xbutton.time = CurrentTime; if (XSendEvent(display, window, True, ButtonReleaseMask, &event) == 0) { qWarning() << "Failed to send ButtonRelease event for native dragging."; } XFlush(display); } static inline void x11_moveOrResizeWindow(const WId windowId, const QPoint &pos, const int section) { Q_ASSERT(windowId); if (!windowId) { return; } Display * const display = x11_get_display(); static const Atom netMoveResize = XInternAtom(display, "_NET_WM_MOVERESIZE", False); // First we need to ungrab the pointer that may have been // automatically grabbed by Qt on ButtonPressEvent XUngrabPointer(display, CurrentTime); XEvent event; memset(&event, 0, sizeof(event)); event.xclient.type = ClientMessage; event.xclient.window = windowId; event.xclient.message_type = netMoveResize; event.xclient.serial = 0; event.xclient.display = display; event.xclient.send_event = True; event.xclient.format = 32; event.xclient.data.l[0] = pos.x(); event.xclient.data.l[1] = pos.y(); event.xclient.data.l[2] = section; event.xclient.data.l[3] = Button1; event.xclient.data.l[4] = 0; // unused if (XSendEvent(display, x11_get_window(x11_get_desktop()), False, (SubstructureRedirectMask | SubstructureNotifyMask), &event) == 0) { qWarning() << "Failed to send _NET_WM_MOVERESIZE event for native dragging."; } XFlush(display); } static inline void x11_windowStartNativeDrag(const WId windowId, const QPoint &globalPos, const QPoint &localPos, const Qt::WindowFrameSection frameSection) { Q_ASSERT(windowId); if (!windowId) { return; } int nativeSection = -1; switch (frameSection) { case Qt::LeftSection: nativeSection = _NET_WM_MOVERESIZE_SIZE_LEFT; break; case Qt::TopLeftSection: nativeSection = _NET_WM_MOVERESIZE_SIZE_TOPLEFT; break; case Qt::TopSection: nativeSection = _NET_WM_MOVERESIZE_SIZE_TOP; break; case Qt::TopRightSection: nativeSection = _NET_WM_MOVERESIZE_SIZE_TOPRIGHT; break; case Qt::RightSection: nativeSection = _NET_WM_MOVERESIZE_SIZE_RIGHT; break; case Qt::BottomRightSection: nativeSection = _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT; break; case Qt::BottomSection: nativeSection = _NET_WM_MOVERESIZE_SIZE_BOTTOM; break; case Qt::BottomLeftSection: nativeSection = _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT; break; case Qt::TitleBarArea: nativeSection = _NET_WM_MOVERESIZE_MOVE; break; default: break; } if (nativeSection == -1) { return; } // Before start the drag we need to tell Qt that the mouse is Released! x11_emulateButtonRelease(windowId, globalPos, localPos); x11_moveOrResizeWindow(windowId, globalPos, nativeSection); } #endif SystemTheme Utils::getSystemTheme() { // ### TODO: how to detect high contrast mode on Linux? return (shouldAppsUseDarkMode() ? SystemTheme::Dark : SystemTheme::Light); } void Utils::startSystemMove(QWindow *window) { Q_ASSERT(window); if (!window) { return; } #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) window->startSystemMove(); #else const qreal dpr = window->devicePixelRatio(); const QPoint globalPos = QPointF(QPointF(QCursor::pos(window->screen())) * dpr).toPoint(); const QPoint localPos = QPointF(QPointF(window->mapFromGlobal(globalPos)) * dpr).toPoint(); x11_windowStartNativeDrag(window->winId(), globalPos, localPos, Qt::TitleBarArea); #endif } void Utils::startSystemResize(QWindow *window, const Qt::Edges edges) { Q_ASSERT(window); if (!window) { return; } if (edges == Qt::Edges{}) { return; } #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) window->startSystemResize(edges); #else const qreal dpr = window->devicePixelRatio(); const QPoint globalPos = QPointF(QPointF(QCursor::pos(window->screen())) * dpr).toPoint(); const QPoint localPos = QPointF(QPointF(window->mapFromGlobal(globalPos)) * dpr).toPoint(); x11_windowStartNativeDrag(window->winId(), globalPos, localPos, qtEdgesToQtWindowFrameSection(edges)); #endif } FRAMELESSHELPER_END_NAMESPACE