├── src ├── qadwaitadecorations.json ├── CMakeLists.txt ├── qadwaitadecorationsplugin.cpp ├── qadwaitadecorationsplugin.h ├── qadwaitadecorations.h └── qadwaitadecorations.cpp ├── .github └── workflows │ ├── clang-format-check.yaml │ └── build.yaml ├── README.md ├── CMakeLists.txt ├── .clang-format └── LICENSE /src/qadwaitadecorations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Keys": [ "adwaita", "gnome" ] 3 | } 4 | 5 | 6 | -------------------------------------------------------------------------------- /.github/workflows/clang-format-check.yaml: -------------------------------------------------------------------------------- 1 | name: clang-format Check 2 | on: [pull_request] 3 | jobs: 4 | formatting-check: 5 | name: Formatting Check 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - name: Run clang-format style check for C/C++/Protobuf programs. 10 | uses: jidicula/clang-format-action@v4.5.0 11 | with: 12 | clang-format-version: '16' 13 | check-path: 'src' 14 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(qadwaitadecorations_SRCS 3 | qadwaitadecorationsplugin.cpp 4 | qadwaitadecorations.cpp 5 | ) 6 | 7 | add_library(qadwaitadecorations MODULE ${qadwaitadecorations_SRCS}) 8 | target_link_libraries(qadwaitadecorations 9 | Qt${QT_VERSION_MAJOR}::Core 10 | Qt${QT_VERSION_MAJOR}::Gui 11 | Qt${QT_VERSION_MAJOR}::GuiPrivate 12 | Qt${QT_VERSION_MAJOR}::Svg 13 | Qt${QT_VERSION_MAJOR}::WaylandClientPrivate 14 | Qt${QT_VERSION_MAJOR}::Widgets 15 | ) 16 | 17 | if (NOT USE_QT6) 18 | if (${Qt5XkbCommonSupport_FOUND}) 19 | target_link_libraries(qadwaitadecorations 20 | Qt${QT_VERSION_MAJOR}::XkbCommonSupportPrivate 21 | Qt${QT_VERSION_MAJOR}::ThemeSupportPrivate 22 | ) 23 | endif() 24 | endif() 25 | 26 | install(TARGETS qadwaitadecorations DESTINATION ${QT_PLUGINS_DIR}/wayland-decoration-client) 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QAdwaitaDecorations 2 | Qt decoration plugin implementing Adwaita-like client-side decorations. 3 | 4 | ## How to compile 5 | This library uses private Qt headers and will likely not be forward nor 6 | backward compatible. This library will have to be recompiled with every 7 | Qt update. While it can be build using Qt 5, it is recommended to get 8 | backported changes from Qt 6. You can get these [here](https://src.fedoraproject.org/rpms/qt5-qtwayland/blob/rawhide/f/qtwayland-decoration-support-backports-from-qt6.patch). 9 | 10 | Build instructions: 11 | 12 | ``` 13 | mkdir build 14 | cd build 15 | cmake [OPTIONS] [-DUSE_QT6=true] [-HAS_QT6_SUPPORT] .. 16 | make && make install 17 | ``` 18 | 19 | ## Usage 20 | It can be used by setting the QT_WAYLAND_DECORATION environment variable: 21 | 22 | ``` 23 | export QT_WAYLAND_DECORATION=adwaita 24 | ``` 25 | 26 | ## License 27 | The code is under [LGPL 2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html) with the "or any later version" clause. 28 | 29 | -------------------------------------------------------------------------------- /src/qadwaitadecorationsplugin.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Jan Grulich 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | * 18 | */ 19 | 20 | #include "qadwaitadecorationsplugin.h" 21 | #include "qadwaitadecorations.h" 22 | 23 | QWaylandAbstractDecoration *QAdwaitaDecorationsPlugin::create(const QString &system, 24 | const QStringList ¶mList) 25 | { 26 | Q_UNUSED(paramList) 27 | if (system == "adwaita" || system == "gnome") 28 | return new QAdwaitaDecorations(); 29 | return nullptr; 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Automatic build 2 | on: 3 | push: 4 | branches: 5 | - '**' 6 | pull_request: 7 | branches: 8 | - '**' 9 | release: 10 | types: [ created ] 11 | env: 12 | BUILD_TYPE: Release 13 | 14 | jobs: 15 | Linux_Qt5: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Install base dependencies 21 | run: | 22 | sudo apt update 23 | sudo apt install cmake make pkg-config libx11-dev xcb libx11-xcb-dev libxkbcommon-dev libwayland-bin libwayland-dev wayland-protocols 24 | 25 | - name: Install Qt 26 | uses: jurplel/install-qt-action@v3 27 | with: 28 | version: 5.15.2 29 | 30 | - name: Build 31 | run: | 32 | mkdir build 33 | cd build 34 | cmake .. -DCMAKE_INSTALL_PREFIX=/usr 35 | make -j4 36 | 37 | Linux_Qt6: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v2 41 | 42 | - name: Install base dependencies 43 | run: | 44 | sudo apt update 45 | sudo apt install cmake make pkg-config libx11-dev xcb libx11-xcb-dev libxkbcommon-dev libwayland-bin libwayland-dev wayland-protocols 46 | 47 | - name: Install Qt 48 | uses: jurplel/install-qt-action@v3 49 | with: 50 | version: 6.5.0 51 | 52 | - name: Build 53 | run: | 54 | mkdir build 55 | cd build 56 | cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DUSE_QT6=ON 57 | make -j4 58 | -------------------------------------------------------------------------------- /src/qadwaitadecorationsplugin.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Jan Grulich 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | * 18 | */ 19 | 20 | #ifndef QADWAITA_DECORATIONS_PLUGIN_H 21 | #define QADWAITA_DECORATIONS_PLUGIN_H 22 | 23 | #include 24 | 25 | using namespace QtWaylandClient; 26 | 27 | class QAdwaitaDecorationsPlugin : public QWaylandDecorationPlugin 28 | { 29 | Q_OBJECT 30 | Q_PLUGIN_METADATA(IID QWaylandDecorationFactoryInterface_iid FILE "qadwaitadecorations.json") 31 | public: 32 | QWaylandAbstractDecoration *create(const QString &system, 33 | const QStringList ¶mList) override; 34 | }; 35 | 36 | #endif // QADWAITA_DECORATIONS_PLUGIN_H 37 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(QAdwaitaDecorations 4 | VERSION 0.1.7 5 | DESCRIPTION "Qt Wayland decoration plugin using libadwaita style" 6 | LANGUAGES CXX C) 7 | 8 | option(USE_QT6 "Use Qt6 instead of Qt5" OFF) 9 | 10 | set(CMAKE_AUTOMOC ON) 11 | 12 | if (USE_QT6) 13 | set(QT_MIN_VERSION "6.5.0") 14 | set(CMAKE_CXX_STANDARD 17) 15 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 16 | else() 17 | set(QT_MIN_VERSION "5.15.2") 18 | set(CMAKE_CXX_STANDARD 14) 19 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 20 | endif() 21 | 22 | include(GNUInstallDirs) 23 | include(FeatureSummary) 24 | 25 | if (USE_QT6) 26 | find_package(QT NAMES Qt6 COMPONENTS Core Gui Svg Wayland Widgets REQUIRED) 27 | if (Qt6Gui_VERSION VERSION_GREATER_EQUAL "6.10.0") 28 | find_package(Qt6GuiPrivate REQUIRED) 29 | find_package(Qt6WaylandClientPrivate REQUIRED) 30 | endif() 31 | else() 32 | find_package(QT NAMES Qt5 COMPONENTS Core Gui Svg Wayland Widgets REQUIRED) 33 | endif() 34 | 35 | find_package(Qt${QT_VERSION_MAJOR} ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS 36 | Core 37 | Gui 38 | Svg 39 | WaylandClient 40 | Widgets 41 | ) 42 | if (Qt6Gui_VERSION VERSION_GREATER_EQUAL "6.10.0") 43 | find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS 44 | GuiPrivate 45 | WaylandClientPrivate 46 | ) 47 | endif() 48 | 49 | find_package(Qt${QT_VERSION_MAJOR}Gui ${QT_MIN_VERSION} CONFIG REQUIRED Private) 50 | if (NOT USE_QT6) 51 | find_package(Qt${QT_VERSION_MAJOR}ThemeSupport REQUIRED) 52 | endif() 53 | 54 | # NOTE: I don't know how to do this only in case of qt_config(xkbcommon). 55 | # We would miss an include in QWaylandDisplay header file. 56 | if (NOT USE_QT6) 57 | find_package(Qt${QT_VERSION_MAJOR}XkbCommonSupport ${QT_MIN_VERSION}) 58 | endif() 59 | 60 | if (HAS_QT6_SUPPORT OR USE_QT6) 61 | message(STATUS "Enabling Qt Wayland decoration shadows support") 62 | message(STATUS "NOTE: This support requires changes in Qt Wayland from Qt 6.2") 63 | add_definitions(-DHAS_QT6_SUPPORT) 64 | endif() 65 | 66 | if (NOT QT_PLUGINS_DIR) 67 | if (NOT USE_QT6) 68 | get_target_property(REAL_QMAKE_EXECUTABLE ${Qt5Core_QMAKE_EXECUTABLE} 69 | IMPORTED_LOCATION) 70 | execute_process(COMMAND "${REAL_QMAKE_EXECUTABLE}" -query QT_INSTALL_PLUGINS 71 | OUTPUT_VARIABLE QT_PLUGINS_DIR 72 | ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) 73 | else() 74 | set(QT_PLUGINS_DIR ${QT6_INSTALL_PLUGINS}) 75 | endif() 76 | endif() 77 | 78 | add_subdirectory(src) 79 | 80 | feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) 81 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Olivier Goffart 2 | # 3 | # You may use this file under the terms of the 3-clause BSD license. 4 | # See the file LICENSE from this package for details. 5 | 6 | # This is the clang-format configuration style to be used by Qt, 7 | # based on the rules from https://wiki.qt.io/Qt_Coding_Style and 8 | # https://wiki.qt.io/Coding_Conventions 9 | 10 | --- 11 | # Webkit style was loosely based on the Qt style 12 | BasedOnStyle: WebKit 13 | 14 | Standard: c++17 15 | 16 | # Column width is limited to 100 in accordance with Qt Coding Style. 17 | # https://wiki.qt.io/Qt_Coding_Style 18 | # Note that this may be changed at some point in the future. 19 | ColumnLimit: 100 20 | # How much weight do extra characters after the line length limit have. 21 | # PenaltyExcessCharacter: 4 22 | 23 | # Disable reflow of some specific comments 24 | # qdoc comments: indentation rules are different. 25 | # Translation comments and SPDX license identifiers are also excluded. 26 | CommentPragmas: "^!|^:|^ SPDX-License-Identifier:" 27 | 28 | # We want a space between the type and the star for pointer types. 29 | PointerBindsToType: false 30 | 31 | # We use template< without space. 32 | SpaceAfterTemplateKeyword: false 33 | 34 | # We want to break before the operators, but not before a '='. 35 | BreakBeforeBinaryOperators: NonAssignment 36 | 37 | # Braces are usually attached, but not after functions or class declarations. 38 | BreakBeforeBraces: Custom 39 | BraceWrapping: 40 | AfterClass: true 41 | AfterControlStatement: false 42 | AfterEnum: false 43 | AfterFunction: true 44 | AfterNamespace: false 45 | AfterObjCDeclaration: false 46 | AfterStruct: true 47 | AfterUnion: false 48 | BeforeCatch: false 49 | BeforeElse: false 50 | IndentBraces: false 51 | 52 | # When constructor initializers do not fit on one line, put them each on a new line. 53 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 54 | # Indent initializers by 4 spaces 55 | ConstructorInitializerIndentWidth: 4 56 | 57 | # Indent width for line continuations. 58 | ContinuationIndentWidth: 8 59 | 60 | # No indentation for namespaces. 61 | NamespaceIndentation: None 62 | 63 | # Allow indentation for preprocessing directives (if/ifdef/endif). https://reviews.llvm.org/rL312125 64 | IndentPPDirectives: AfterHash 65 | # We only indent with 2 spaces for preprocessor directives 66 | PPIndentWidth: 2 67 | 68 | # Horizontally align arguments after an open bracket. 69 | # The coding style does not specify the following, but this is what gives 70 | # results closest to the existing code. 71 | AlignAfterOpenBracket: true 72 | AlwaysBreakTemplateDeclarations: true 73 | 74 | # Ideally we should also allow less short function in a single line, but 75 | # clang-format does not handle that. 76 | AllowShortFunctionsOnASingleLine: Inline 77 | 78 | # The coding style specifies some include order categories, but also tells to 79 | # separate categories with an empty line. It does not specify the order within 80 | # the categories. Since the SortInclude feature of clang-format does not 81 | # re-order includes separated by empty lines, the feature is not used. 82 | SortIncludes: false 83 | 84 | # macros for which the opening brace stays attached. 85 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ] 86 | 87 | # Break constructor initializers before the colon and after the commas. 88 | BreakConstructorInitializers: BeforeColon 89 | 90 | # Add "// namespace " comments on closing brace for a namespace 91 | # Ignored for namespaces that qualify as a short namespace, 92 | # see 'ShortNamespaceLines' 93 | FixNamespaceComments: true 94 | 95 | # Definition of how short a short namespace is, default 1 96 | ShortNamespaceLines: 1 97 | 98 | # When escaping newlines in a macro attach the '\' as far left as possible, e.g. 99 | ##define a \ 100 | # something; \ 101 | # other; \ 102 | # thelastlineislong; 103 | AlignEscapedNewlines: Left 104 | 105 | # Avoids the addition of a space between an identifier and the 106 | # initializer list in list-initialization. 107 | SpaceBeforeCpp11BracedList: false 108 | -------------------------------------------------------------------------------- /src/qadwaitadecorations.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Jan Grulich 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | * 18 | */ 19 | 20 | #ifndef QADWAITA_DECORATIONS_H 21 | #define QADWAITA_DECORATIONS_H 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include 29 | 30 | using namespace QtWaylandClient; 31 | 32 | class QDBusVariant; 33 | class QPainter; 34 | 35 | class QAdwaitaDecorations : public QWaylandAbstractDecoration 36 | { 37 | Q_OBJECT 38 | public: 39 | enum ColorType { 40 | Background, 41 | BackgroundInactive, 42 | Foreground, 43 | ForegroundInactive, 44 | Border, 45 | BorderInactive, 46 | ButtonBackground, 47 | ButtonBackgroundInactive, 48 | HoveredButtonBackground, 49 | PressedButtonBackground 50 | }; 51 | enum Placement { Left = 0, Right = 1 }; 52 | enum Button { None = 0x0, Close = 0x1, Minimize = 0x02, Maximize = 0x04 }; 53 | Q_DECLARE_FLAGS(Buttons, Button); 54 | enum ButtonIcon { CloseIcon, MinimizeIcon, MaximizeIcon, RestoreIcon }; 55 | 56 | QAdwaitaDecorations(); 57 | virtual ~QAdwaitaDecorations() = default; 58 | 59 | protected: 60 | #ifdef HAS_QT6_SUPPORT 61 | QMargins margins(MarginsType marginsType = Full) const override; 62 | #else 63 | QMargins margins() const override; 64 | #endif 65 | void paint(QPaintDevice *device) override; 66 | void paintButton(Button button, QPainter *painter); 67 | bool handleMouse(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, 68 | Qt::MouseButtons b, Qt::KeyboardModifiers mods) override; 69 | #if QT_VERSION >= 0x060000 70 | bool handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, 71 | QEventPoint::State state, Qt::KeyboardModifiers mods) override; 72 | #else 73 | bool handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, 74 | Qt::TouchPointState state, Qt::KeyboardModifiers mods) override; 75 | #endif 76 | 77 | private Q_SLOTS: 78 | void settingChanged(const QString &group, const QString &key, const QDBusVariant &value); 79 | 80 | private: 81 | void initConfiguration(); 82 | void updateColors(bool useDarkColors); 83 | void updateIcons(); 84 | void updateTitlebarLayout(const QString &layout); 85 | QRect windowContentGeometry() const; 86 | 87 | void forceRepaint(); 88 | 89 | void processMouseTop(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b, 90 | Qt::KeyboardModifiers mods); 91 | void processMouseBottom(QWaylandInputDevice *inputDevice, const QPointF &local, 92 | Qt::MouseButtons b, Qt::KeyboardModifiers mods); 93 | void processMouseLeft(QWaylandInputDevice *inputDevice, const QPointF &local, 94 | Qt::MouseButtons b, Qt::KeyboardModifiers mods); 95 | void processMouseRight(QWaylandInputDevice *inputDevice, const QPointF &local, 96 | Qt::MouseButtons b, Qt::KeyboardModifiers mods); 97 | 98 | bool clickButton(Qt::MouseButtons b, Button btn); 99 | bool doubleClickButton(Qt::MouseButtons b, const QPointF &local, const QDateTime ¤tTime); 100 | bool updateButtonHoverState(Button hoveredButton); 101 | 102 | QRectF buttonRect(Button button) const; 103 | 104 | // Default GNOME configuraiton 105 | Placement m_placement = Right; 106 | QMap m_buttons; 107 | 108 | QStaticText m_windowTitle; 109 | Button m_clicking = None; 110 | 111 | Buttons m_hoveredButtons = None; 112 | QDateTime m_lastButtonClick; 113 | QPointF m_lastButtonClickPosition; 114 | 115 | QMap m_colors; 116 | std::unique_ptr m_font; 117 | QPixmap m_shadowPixmap; 118 | QMap m_icons; 119 | }; 120 | 121 | Q_DECLARE_OPERATORS_FOR_FLAGS(QAdwaitaDecorations::Buttons) 122 | 123 | #endif // QADWAITA_DECORATIONS_H 124 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | , 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | -------------------------------------------------------------------------------- /src/qadwaitadecorations.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Jan Grulich 3 | * 4 | * This library is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU Lesser General Public 6 | * License as published by the Free Software Foundation; either 7 | * version 2.1 of the License, or (at your option) any later version. 8 | * 9 | * This library is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public 15 | * License along with this library; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | * 18 | */ 19 | 20 | #include "qadwaitadecorations.h" 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | 36 | #include 37 | 38 | // QtDBus 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | static constexpr int ceButtonSpacing = 12; 49 | static constexpr int ceButtonWidth = 24; 50 | static constexpr int ceCornerRadius = 12; 51 | static constexpr int ceShadowsWidth = 10; 52 | static constexpr int ceTitlebarHeight = 38; 53 | static constexpr int ceWindowBorderWidth = 1; 54 | 55 | static QMap buttonMap = { 56 | { QAdwaitaDecorations::CloseIcon, QStringLiteral("window-close-symbolic") }, 57 | { QAdwaitaDecorations::MinimizeIcon, QStringLiteral("window-minimize-symbolic") }, 58 | { QAdwaitaDecorations::MaximizeIcon, QStringLiteral("window-maximize-symbolic") }, 59 | { QAdwaitaDecorations::RestoreIcon, QStringLiteral("window-restore-symbolic") } 60 | }; 61 | 62 | Q_DECL_IMPORT void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, 63 | bool alphaOnly, int transposed = 0); 64 | 65 | Q_LOGGING_CATEGORY(QAdwaitaDecorationsLog, "qt.qpa.qadwaitadecorations", QtWarningMsg) 66 | 67 | const QDBusArgument &operator>>(const QDBusArgument &argument, QMap &map) 68 | { 69 | argument.beginMap(); 70 | map.clear(); 71 | 72 | while (!argument.atEnd()) { 73 | QString key; 74 | QVariantMap value; 75 | argument.beginMapEntry(); 76 | argument >> key >> value; 77 | argument.endMapEntry(); 78 | map.insert(key, value); 79 | } 80 | 81 | argument.endMap(); 82 | return argument; 83 | } 84 | 85 | QAdwaitaDecorations::QAdwaitaDecorations() 86 | { 87 | #ifdef HAS_QT6_SUPPORT 88 | # if QT_VERSION >= 0x060000 89 | qCDebug(QAdwaitaDecorationsLog) << "Using Qt6 version"; 90 | # else 91 | qCDebug(QAdwaitaDecorationsLog) << "Using Qt5 version with Qt6 backports"; 92 | # endif 93 | #else 94 | qCDebug(QAdwaitaDecorationsLog) << "Using Qt5 version"; 95 | #endif 96 | 97 | m_lastButtonClick = QDateTime::currentDateTime(); 98 | 99 | QTextOption option(Qt::AlignHCenter | Qt::AlignVCenter); 100 | option.setWrapMode(QTextOption::NoWrap); 101 | m_windowTitle.setTextOption(option); 102 | m_windowTitle.setTextFormat(Qt::PlainText); 103 | 104 | const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme(); 105 | if (const QFont *font = theme->font(QPlatformTheme::TitleBarFont)) 106 | m_font = std::make_unique(*font); 107 | if (!m_font) 108 | m_font = std::make_unique(QLatin1String("Sans"), 10); 109 | 110 | QTimer::singleShot(0, this, &QAdwaitaDecorations::initConfiguration); 111 | } 112 | 113 | void QAdwaitaDecorations::initConfiguration() 114 | { 115 | qRegisterMetaType(); 116 | qDBusRegisterMetaType>(); 117 | 118 | QDBusConnection connection = QDBusConnection::sessionBus(); 119 | 120 | QDBusMessage message = QDBusMessage::createMethodCall( 121 | QLatin1String("org.freedesktop.portal.Desktop"), 122 | QLatin1String("/org/freedesktop/portal/desktop"), 123 | QLatin1String("org.freedesktop.portal.Settings"), QLatin1String("ReadAll")); 124 | message << QStringList{ { QLatin1String("org.gnome.desktop.wm.preferences") }, 125 | { QLatin1String("org.freedesktop.appearance") } }; 126 | 127 | QDBusPendingCall pendingCall = connection.asyncCall(message); 128 | QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall); 129 | QObject::connect( 130 | watcher, &QDBusPendingCallWatcher::finished, this, 131 | [this](QDBusPendingCallWatcher *watcher) { 132 | QDBusPendingReply> reply = *watcher; 133 | if (reply.isValid()) { 134 | QMap settings = reply.value(); 135 | if (!settings.isEmpty()) { 136 | const uint colorScheme = 137 | settings.value(QLatin1String("org.freedesktop.appearance")) 138 | .value(QLatin1String("color-scheme")) 139 | .toUInt(); 140 | updateColors(colorScheme == 1); // 1 == Prefer Dark 141 | const QString buttonLayout = 142 | settings.value(QLatin1String("org.gnome.desktop.wm.preferences")) 143 | .value(QLatin1String("button-layout")) 144 | .toString(); 145 | if (!buttonLayout.isEmpty()) { 146 | updateTitlebarLayout(buttonLayout); 147 | } 148 | 149 | // Workaround for QGtkStyle not having correct titlebar font 150 | // This is not going to be very precise as I want to avoid dependency on 151 | // Pango which we had in QGnomePlatform, but at least make the font bold 152 | // if detected. 153 | const bool titlebarUseDesktopFont = 154 | settings.value(QLatin1String("org.gnome.desktop.wm.preferences")) 155 | .value(QLatin1String("titlebar-uses-desktop-font")) 156 | .toBool(); 157 | if (!titlebarUseDesktopFont) { 158 | const QString titlebarFont = 159 | settings.value(QLatin1String( 160 | "org.gnome.desktop.wm.preferences")) 161 | .value(QLatin1String("titlebar-font")) 162 | .toString(); 163 | if (titlebarFont.contains(QLatin1String("bold"), Qt::CaseInsensitive)) { 164 | m_font->setBold(true); 165 | } 166 | } 167 | } 168 | } 169 | watcher->deleteLater(); 170 | }); 171 | 172 | QDBusConnection::sessionBus().connect( 173 | QString(), QLatin1String("/org/freedesktop/portal/desktop"), 174 | QLatin1String("org.freedesktop.portal.Settings"), QLatin1String("SettingChanged"), this, 175 | SLOT(settingChanged(QString, QString, QDBusVariant))); 176 | 177 | updateColors(false); 178 | updateIcons(); 179 | } 180 | 181 | void QAdwaitaDecorations::updateColors(bool useDarkColors) 182 | { 183 | qCDebug(QAdwaitaDecorationsLog) 184 | << "Changing color scheme to " << (useDarkColors ? "dark" : "light"); 185 | 186 | m_colors = { { Background, useDarkColors ? QColor(0x2e2e32) : QColor(0xffffff) }, 187 | { BackgroundInactive, useDarkColors ? QColor(0x222226) : QColor(0xfafafb) }, 188 | { Foreground, useDarkColors ? QColor(0xffffff) : QColor(0x333338) }, 189 | { ForegroundInactive, useDarkColors ? QColor(0x919193) : QColor(0x969699) }, 190 | { Border, useDarkColors ? QColor(0x2e2e32) : QColor(0xffffff) }, 191 | { BorderInactive, useDarkColors ? QColor(0x2e2e32) : QColor(0xffffff) }, 192 | { ButtonBackground, useDarkColors ? QColor(0x434347) : QColor(0xebebeb) }, 193 | { ButtonBackgroundInactive, useDarkColors ? QColor(0x2d2d31) : QColor(0xf0f0f1) }, 194 | { HoveredButtonBackground, useDarkColors ? QColor(0x4d4d51) : QColor(0xe0e0e1) }, 195 | { PressedButtonBackground, useDarkColors ? QColor(0x6c6c6f) : QColor(0xc2c2c3) } }; 196 | forceRepaint(); 197 | } 198 | 199 | QString getIconSvg(const QString &iconName) 200 | { 201 | const QStringList themeNames = { QIcon::themeName(), QIcon::fallbackThemeName(), 202 | QLatin1String("Adwaita") }; 203 | qCDebug(QAdwaitaDecorationsLog) << "Icon themes: " << themeNames; 204 | 205 | for (const QString &themeName : themeNames) { 206 | for (const QString &path : QIcon::themeSearchPaths()) { 207 | if (path.startsWith(QLatin1Char(':'))) 208 | continue; 209 | 210 | const QString fullPath = QString("%1/%2").arg(path).arg(themeName); 211 | QDirIterator dirIt(fullPath, QDirIterator::Subdirectories); 212 | while (dirIt.hasNext()) { 213 | const QString fileName = dirIt.next(); 214 | const QFileInfo fileInfo(fileName); 215 | 216 | if (fileInfo.isDir()) 217 | continue; 218 | 219 | if (fileInfo.fileName() == iconName) { 220 | qCDebug(QAdwaitaDecorationsLog) 221 | << "Using " << iconName << " from " << themeName << " theme"; 222 | QFile readFile(fileInfo.filePath()); 223 | readFile.open(QFile::ReadOnly); 224 | return readFile.readAll(); 225 | } 226 | } 227 | } 228 | } 229 | 230 | qCWarning(QAdwaitaDecorationsLog) << "Failed to find an svg icon for " << iconName; 231 | 232 | return QString(); 233 | } 234 | 235 | void QAdwaitaDecorations::updateIcons() 236 | { 237 | for (auto mapIt = buttonMap.constBegin(); mapIt != buttonMap.constEnd(); mapIt++) { 238 | const QString fullName = mapIt.value() + QStringLiteral(".svg"); 239 | m_icons[mapIt.key()] = getIconSvg(fullName); 240 | } 241 | 242 | forceRepaint(); 243 | } 244 | 245 | void QAdwaitaDecorations::updateTitlebarLayout(const QString &layout) 246 | { 247 | qCDebug(QAdwaitaDecorationsLog) << "Changing titlebar layout to " << layout; 248 | 249 | const QStringList layouts = layout.split(QLatin1Char(':')); 250 | if (layouts.count() != 2) { 251 | return; 252 | } 253 | 254 | // Remove previous configuration 255 | m_buttons.clear(); 256 | 257 | const QString &leftLayout = layouts.at(0); 258 | const QString &rightLayout = layouts.at(1); 259 | m_placement = leftLayout.contains(QLatin1String("close")) ? Left : Right; 260 | 261 | int pos = 1; 262 | const QString &buttonLayout = m_placement == Right ? rightLayout : leftLayout; 263 | 264 | QStringList buttonList = buttonLayout.split(QLatin1Char(',')); 265 | if (m_placement == Right) { 266 | std::reverse(buttonList.begin(), buttonList.end()); 267 | } 268 | 269 | for (const QString &button : buttonList) { 270 | if (button == QLatin1String("close")) { 271 | m_buttons.insert(Close, pos); 272 | } else if (button == QLatin1String("maximize")) { 273 | m_buttons.insert(Maximize, pos); 274 | } else if (button == QLatin1String("minimize")) { 275 | m_buttons.insert(Minimize, pos); 276 | } else { 277 | continue; 278 | } 279 | pos++; 280 | } 281 | 282 | forceRepaint(); 283 | } 284 | 285 | void QAdwaitaDecorations::settingChanged(const QString &group, const QString &key, 286 | const QDBusVariant &value) 287 | { 288 | if (group == QLatin1String("org.gnome.desktop.wm.preferences") 289 | && key == QLatin1String("button-layout")) { 290 | const QString layout = value.variant().toString(); 291 | updateTitlebarLayout(layout); 292 | } else if (group == QLatin1String("org.freedesktop.appearance") 293 | && key == QLatin1String("color-scheme")) { 294 | const uint colorScheme = value.variant().toUInt(); 295 | updateColors(colorScheme == 1); // 1 == Prefer Dark 296 | } 297 | } 298 | 299 | QRectF QAdwaitaDecorations::buttonRect(Button button) const 300 | { 301 | int xPos; 302 | int yPos; 303 | const int btnPos = m_buttons.value(button); 304 | 305 | if (m_placement == Right) { 306 | xPos = windowContentGeometry().width(); 307 | xPos -= ceButtonWidth * btnPos; 308 | xPos -= ceButtonSpacing * btnPos; 309 | #ifdef HAS_QT6_SUPPORT 310 | xPos -= margins(ShadowsOnly).right(); 311 | #endif 312 | } else { 313 | xPos = 0; 314 | xPos += ceButtonWidth * btnPos; 315 | xPos += ceButtonSpacing * btnPos; 316 | #ifdef HAS_QT6_SUPPORT 317 | xPos += margins(ShadowsOnly).left(); 318 | #endif 319 | // We are painting from the left to the right so the real 320 | // position doesn't need to by moved by the size of the button. 321 | xPos -= ceButtonWidth; 322 | } 323 | 324 | yPos = margins().top(); 325 | yPos += margins().bottom(); 326 | yPos -= ceButtonWidth; 327 | yPos /= 2; 328 | 329 | return QRectF(xPos, yPos, ceButtonWidth, ceButtonWidth); 330 | } 331 | 332 | #ifdef HAS_QT6_SUPPORT 333 | QMargins QAdwaitaDecorations::margins(MarginsType marginsType) const 334 | { 335 | const bool onlyShadows = marginsType == ShadowsOnly; 336 | const bool shadowsExcluded = marginsType == ShadowsExcluded; 337 | 338 | if (waylandWindow()->windowStates() & Qt::WindowMaximized) { 339 | // Maximized windows don't have anything around, no shadows, border, 340 | // etc. Only report titlebar height in case we are not asking for shadow 341 | // margins. 342 | return QMargins(0, onlyShadows ? 0 : ceTitlebarHeight, 0, 0); 343 | } 344 | 345 | const QWaylandWindow::ToplevelWindowTilingStates tilingStates = 346 | waylandWindow()->toplevelWindowTilingStates(); 347 | 348 | // Since all sides (left, right, bottom) are going to be same 349 | const int marginsBase = 350 | shadowsExcluded ? ceWindowBorderWidth : ceShadowsWidth + ceWindowBorderWidth; 351 | const int sideMargins = onlyShadows ? ceShadowsWidth : marginsBase; 352 | const int topMargins = onlyShadows ? ceShadowsWidth : ceTitlebarHeight + marginsBase; 353 | 354 | return QMargins(tilingStates & QWaylandWindow::WindowTiledLeft ? 0 : sideMargins, 355 | tilingStates & QWaylandWindow::WindowTiledTop 356 | ? onlyShadows ? 0 : ceTitlebarHeight 357 | : topMargins, 358 | tilingStates & QWaylandWindow::WindowTiledRight ? 0 : sideMargins, 359 | tilingStates & QWaylandWindow::WindowTiledBottom ? 0 : sideMargins); 360 | } 361 | #else 362 | QMargins QAdwaitaDecorations::margins() const 363 | { 364 | if (window()->windowStates() & Qt::WindowMaximized) { 365 | // Maximized windows don't have anything around, no shadows, border, 366 | // etc. Only report titlebar height. 367 | return QMargins(0, ceTitlebarHeight, 0, 0); 368 | } 369 | 370 | return QMargins(ceWindowBorderWidth, ceWindowBorderWidth + ceTitlebarHeight, 371 | ceWindowBorderWidth, ceWindowBorderWidth); 372 | } 373 | #endif 374 | 375 | static QColor makeTransparent(const QColor &color, qreal level) 376 | { 377 | QColor transparentColor = color; 378 | transparentColor.setAlphaF(level); 379 | return transparentColor; 380 | } 381 | 382 | void QAdwaitaDecorations::paint(QPaintDevice *device) 383 | { 384 | #ifdef HAS_QT6_SUPPORT 385 | const Qt::WindowStates windowStates = waylandWindow()->windowStates(); 386 | const bool active = windowStates & Qt::WindowActive; 387 | const bool tiled = 388 | waylandWindow()->toplevelWindowTilingStates() != QWaylandWindow::WindowNoState; 389 | #else 390 | const Qt::WindowStates windowStates = window()->windowStates(); 391 | const bool active = window()->handle()->isActive(); 392 | const bool tiled = false; 393 | #endif 394 | const bool maximized = windowStates & Qt::WindowMaximized; 395 | 396 | const QRect surfaceRect = windowContentGeometry(); 397 | 398 | const QColor borderColor = active ? makeTransparent(m_colors[Border], 0.5) 399 | : makeTransparent(m_colors[BorderInactive], 0.5); 400 | const QColor backgroundColor = active ? m_colors[Background] : m_colors[BackgroundInactive]; 401 | const QColor foregroundColor = active ? m_colors[Foreground] : m_colors[ForegroundInactive]; 402 | 403 | QPainter p(device); 404 | p.setRenderHint(QPainter::Antialiasing); 405 | 406 | #ifdef HAS_QT6_SUPPORT 407 | // Shadows 408 | if (active && !(maximized || tiled)) { 409 | if (m_shadowPixmap.size() != surfaceRect.size()) { 410 | QPixmap source = QPixmap(surfaceRect.size()); 411 | source.fill(Qt::transparent); 412 | { 413 | QRect topHalf = surfaceRect.translated(ceShadowsWidth, ceShadowsWidth); 414 | topHalf.setSize(QSize(surfaceRect.width() - (2 * ceShadowsWidth), 415 | surfaceRect.height() / 2)); 416 | 417 | QRect bottomHalf = surfaceRect.translated(ceShadowsWidth, surfaceRect.height() / 2); 418 | bottomHalf.setSize(QSize(surfaceRect.width() - (2 * ceShadowsWidth), 419 | (surfaceRect.height() / 2) - ceShadowsWidth)); 420 | 421 | QPainter tmpPainter(&source); 422 | tmpPainter.setBrush(borderColor); 423 | tmpPainter.drawRoundedRect(topHalf, ceCornerRadius, ceCornerRadius); 424 | tmpPainter.drawRect(bottomHalf); 425 | tmpPainter.end(); 426 | } 427 | 428 | QImage backgroundImage(surfaceRect.size(), QImage::Format_ARGB32_Premultiplied); 429 | backgroundImage.fill(0); 430 | 431 | QPainter backgroundPainter(&backgroundImage); 432 | backgroundPainter.drawPixmap(QPointF(), source); 433 | backgroundPainter.end(); 434 | 435 | QImage blurredImage(surfaceRect.size(), QImage::Format_ARGB32_Premultiplied); 436 | blurredImage.fill(0); 437 | { 438 | QPainter blurPainter(&blurredImage); 439 | qt_blurImage(&blurPainter, backgroundImage, 12, false, false); 440 | blurPainter.end(); 441 | } 442 | backgroundImage = blurredImage; 443 | 444 | backgroundPainter.begin(&backgroundImage); 445 | backgroundPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); 446 | QRect rect = backgroundImage.rect().marginsRemoved(QMargins(8, 8, 8, 8)); 447 | backgroundPainter.fillRect(rect, QColor(0, 0, 0, 160)); 448 | backgroundPainter.end(); 449 | 450 | m_shadowPixmap = QPixmap::fromImage(backgroundImage); 451 | } 452 | 453 | QRect clips[] = { QRect(0, 0, surfaceRect.width(), margins().top()), 454 | QRect(0, margins().top(), margins().left(), 455 | surfaceRect.height() - margins().top() - margins().bottom()), 456 | QRect(0, surfaceRect.height() - margins().bottom(), surfaceRect.width(), 457 | margins().bottom()), 458 | QRect(surfaceRect.width() - margins().right(), margins().top(), 459 | margins().right(), 460 | surfaceRect.height() - margins().top() - margins().bottom()) }; 461 | 462 | for (int i = 0; i < 4; ++i) { 463 | p.save(); 464 | p.setClipRect(clips[i]); 465 | p.drawPixmap(QPoint(), m_shadowPixmap); 466 | p.restore(); 467 | } 468 | } 469 | #endif 470 | 471 | // Titlebar and window border 472 | { 473 | QPainterPath path; 474 | #ifdef HAS_QT6_SUPPORT 475 | const QPointF topLeft = { margins(ShadowsOnly).left() + 0.5, 476 | margins(ShadowsOnly).top() - 0.5 }; 477 | const int frameWidth = surfaceRect.width() - margins(ShadowsOnly).left() 478 | - margins(ShadowsOnly).right() - 0.5; 479 | const int frameHeight = surfaceRect.height() - margins(ShadowsOnly).top() 480 | - margins(ShadowsOnly).bottom() + 0.5; 481 | #else 482 | const QPointF topLeft = { 0.5, -0.5 }; 483 | const int frameWidth = surfaceRect.width() - 0.5; 484 | const int frameHeight = surfaceRect.height() + 0.5; 485 | #endif 486 | const QRectF fullFrameRect = QRectF(topLeft, QSizeF(frameWidth, frameHeight)); 487 | 488 | if (maximized || tiled) { 489 | path.addRect(fullFrameRect); 490 | } else { 491 | const QSizeF radiusRectSize = QSizeF(ceCornerRadius * 2, ceCornerRadius * 2); 492 | path.moveTo(fullFrameRect.bottomLeft()); 493 | path.lineTo(fullFrameRect.topLeft() + QPointF(0, ceCornerRadius)); 494 | path.arcTo(QRectF(fullFrameRect.topLeft(), radiusRectSize), 180, -90); 495 | path.lineTo(fullFrameRect.topRight() - QPointF(ceCornerRadius, 0)); 496 | path.arcTo(QRectF(fullFrameRect.topRight() - QPointF(ceCornerRadius * 2, 0), 497 | radiusRectSize), 498 | 90, -90); 499 | path.lineTo(fullFrameRect.bottomRight()); 500 | path.closeSubpath(); 501 | } 502 | 503 | p.save(); 504 | p.setPen(borderColor); 505 | p.fillPath(path.simplified(), backgroundColor); 506 | p.drawPath(path); 507 | p.restore(); 508 | } 509 | 510 | // Window title 511 | { 512 | const QRect top = QRect(margins().left(), margins().bottom(), surfaceRect.width(), 513 | margins().top() - margins().bottom()); 514 | #if QT_VERSION >= 0x060700 515 | const QString windowTitleText = waylandWindow()->windowTitle(); 516 | #else 517 | const QString windowTitleText = window()->title(); 518 | #endif 519 | if (!windowTitleText.isEmpty()) { 520 | if (m_windowTitle.text() != windowTitleText) { 521 | m_windowTitle.setText(windowTitleText); 522 | m_windowTitle.prepare(); 523 | } 524 | 525 | QRect titleBar = top; 526 | if (m_placement == Right) { 527 | titleBar.setLeft(margins().left()); 528 | titleBar.setRight(static_cast(buttonRect(Minimize).left()) - 8); 529 | } else { 530 | titleBar.setLeft(static_cast(buttonRect(Minimize).right()) + 8); 531 | titleBar.setRight(surfaceRect.width() - margins().right()); 532 | } 533 | 534 | p.save(); 535 | p.setClipRect(titleBar); 536 | p.setPen(foregroundColor); 537 | QSize size = m_windowTitle.size().toSize(); 538 | int dx = (top.width() - size.width()) / 2; 539 | int dy = (top.height() - size.height()) / 2; 540 | p.setFont(*m_font); 541 | QPoint windowTitlePoint(top.topLeft().x() + dx, top.topLeft().y() + dy); 542 | p.drawStaticText(windowTitlePoint, m_windowTitle); 543 | p.restore(); 544 | } 545 | } 546 | 547 | // Buttons 548 | { 549 | if (m_buttons.contains(Close)) 550 | paintButton(Close, &p); 551 | 552 | if (m_buttons.contains(Maximize)) 553 | paintButton(Maximize, &p); 554 | 555 | if (m_buttons.contains(Minimize)) 556 | paintButton(Minimize, &p); 557 | } 558 | } 559 | 560 | static void renderFlatRoundedButtonFrame(QAdwaitaDecorations::Button button, QPainter *painter, 561 | const QRect &rect, const QColor &color) 562 | { 563 | painter->save(); 564 | painter->setRenderHint(QPainter::Antialiasing, true); 565 | painter->setPen(Qt::NoPen); 566 | painter->setBrush(color); 567 | painter->drawEllipse(rect); 568 | painter->restore(); 569 | } 570 | 571 | static void renderButtonIcon(const QString &svgIcon, QPainter *painter, const QRect &rect, 572 | const QColor &color) 573 | { 574 | painter->save(); 575 | painter->setRenderHints(QPainter::Antialiasing, true); 576 | 577 | QString icon = svgIcon; 578 | QRegularExpression regexp("fill=[\"']#[0-9A-F]{6}[\"']", 579 | QRegularExpression::CaseInsensitiveOption); 580 | QRegularExpression regexpAlt("fill:#[0-9A-F]{6}", QRegularExpression::CaseInsensitiveOption); 581 | QRegularExpression regexpCurrentColor("fill=[\"']currentColor[\"']"); 582 | icon.replace(regexp, QString("fill=\"%1\"").arg(color.name())); 583 | icon.replace(regexpAlt, QString("fill:%1").arg(color.name())); 584 | icon.replace(regexpCurrentColor, QString("fill=\"%1\"").arg(color.name())); 585 | QSvgRenderer svgRenderer(icon.toLocal8Bit()); 586 | svgRenderer.render(painter, rect); 587 | 588 | painter->restore(); 589 | } 590 | 591 | static void renderButtonIcon(QAdwaitaDecorations::ButtonIcon buttonIcon, QPainter *painter, 592 | const QRect &rect) 593 | { 594 | QString iconName = buttonMap[buttonIcon]; 595 | 596 | painter->save(); 597 | painter->setRenderHints(QPainter::Antialiasing, true); 598 | painter->drawPixmap(rect, QIcon::fromTheme(iconName).pixmap(ceButtonWidth, ceButtonWidth)); 599 | 600 | painter->restore(); 601 | } 602 | 603 | static QAdwaitaDecorations::ButtonIcon iconFromButtonAndState(QAdwaitaDecorations::Button button, 604 | bool maximized) 605 | { 606 | if (button == QAdwaitaDecorations::Close) 607 | return QAdwaitaDecorations::CloseIcon; 608 | else if (button == QAdwaitaDecorations::Minimize) 609 | return QAdwaitaDecorations::MinimizeIcon; 610 | else if (button == QAdwaitaDecorations::Maximize && maximized) 611 | return QAdwaitaDecorations::RestoreIcon; 612 | else 613 | return QAdwaitaDecorations::MaximizeIcon; 614 | } 615 | 616 | void QAdwaitaDecorations::paintButton(Button button, QPainter *painter) 617 | { 618 | #ifdef HAS_QT6_SUPPORT 619 | const Qt::WindowStates windowStates = waylandWindow()->windowStates(); 620 | const bool active = windowStates & Qt::WindowActive; 621 | #else 622 | const Qt::WindowStates windowStates = window()->windowStates(); 623 | const bool active = window()->handle()->isActive(); 624 | #endif 625 | const bool maximized = windowStates & Qt::WindowMaximized; 626 | 627 | QColor activeBackgroundColor; 628 | if (m_clicking == button) 629 | activeBackgroundColor = m_colors[PressedButtonBackground]; 630 | else if (m_hoveredButtons.testFlag(button)) 631 | activeBackgroundColor = m_colors[HoveredButtonBackground]; 632 | else 633 | activeBackgroundColor = m_colors[ButtonBackground]; 634 | 635 | const QColor buttonBackgroundColor = 636 | active ? activeBackgroundColor : m_colors[ButtonBackgroundInactive]; 637 | const QColor foregroundColor = active ? m_colors[Foreground] : m_colors[ForegroundInactive]; 638 | 639 | const QRect btnRect = buttonRect(button).toRect(); 640 | renderFlatRoundedButtonFrame(button, painter, btnRect, buttonBackgroundColor); 641 | 642 | QRect adjustedBtnRect = btnRect; 643 | adjustedBtnRect.setSize(QSize(16, 16)); 644 | adjustedBtnRect.translate(4, 4); 645 | const QString svgIcon = m_icons[iconFromButtonAndState(button, maximized)]; 646 | if (!svgIcon.isEmpty()) 647 | renderButtonIcon(svgIcon, painter, adjustedBtnRect, foregroundColor); 648 | else // Fallback to use QIcon 649 | renderButtonIcon(iconFromButtonAndState(button, maximized), painter, adjustedBtnRect); 650 | } 651 | 652 | bool QAdwaitaDecorations::clickButton(Qt::MouseButtons b, Button btn) 653 | { 654 | auto repaint = qScopeGuard([this] { forceRepaint(); }); 655 | 656 | if (isLeftClicked(b)) { 657 | m_clicking = btn; 658 | return false; 659 | } else if (isLeftReleased(b)) { 660 | if (m_clicking == btn) { 661 | m_clicking = None; 662 | return true; 663 | } else { 664 | m_clicking = None; 665 | } 666 | } 667 | return false; 668 | } 669 | 670 | bool QAdwaitaDecorations::doubleClickButton(Qt::MouseButtons b, const QPointF &local, 671 | const QDateTime ¤tTime) 672 | { 673 | if (isLeftClicked(b)) { 674 | const qint64 clickInterval = m_lastButtonClick.msecsTo(currentTime); 675 | m_lastButtonClick = currentTime; 676 | const int doubleClickDistance = 5; 677 | const QPointF posDiff = m_lastButtonClickPosition - local; 678 | if ((clickInterval <= 500) 679 | && ((posDiff.x() <= doubleClickDistance && posDiff.x() >= -doubleClickDistance) 680 | && ((posDiff.y() <= doubleClickDistance && posDiff.y() >= -doubleClickDistance)))) { 681 | return true; 682 | } 683 | 684 | m_lastButtonClickPosition = local; 685 | } 686 | 687 | return false; 688 | } 689 | 690 | bool QAdwaitaDecorations::handleMouse(QWaylandInputDevice *inputDevice, const QPointF &local, 691 | const QPointF &global, Qt::MouseButtons b, 692 | Qt::KeyboardModifiers mods) 693 | { 694 | Q_UNUSED(global) 695 | 696 | if (local.y() > margins().top()) { 697 | updateButtonHoverState(Button::None); 698 | } 699 | 700 | // Figure out what area mouse is in 701 | QRect surfaceRect = windowContentGeometry(); 702 | if (local.y() <= surfaceRect.top() + margins().top()) { 703 | processMouseTop(inputDevice, local, b, mods); 704 | } else if (local.y() > surfaceRect.bottom() - margins().bottom()) { 705 | processMouseBottom(inputDevice, local, b, mods); 706 | } else if (local.x() <= surfaceRect.left() + margins().left()) { 707 | processMouseLeft(inputDevice, local, b, mods); 708 | } else if (local.x() > surfaceRect.right() - margins().right()) { 709 | processMouseRight(inputDevice, local, b, mods); 710 | } else { 711 | #if QT_CONFIG(cursor) 712 | waylandWindow()->restoreMouseCursor(inputDevice); 713 | #endif 714 | } 715 | 716 | // Reset clicking state in case a button press is released outside 717 | // the button area 718 | if (isLeftReleased(b)) { 719 | m_clicking = None; 720 | forceRepaint(); 721 | } 722 | 723 | setMouseButtons(b); 724 | return false; 725 | } 726 | 727 | #if QT_VERSION >= 0x060000 728 | bool QAdwaitaDecorations::handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, 729 | const QPointF &global, QEventPoint::State state, 730 | Qt::KeyboardModifiers mods) 731 | #else 732 | bool QAdwaitaDecorations::handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, 733 | const QPointF &global, Qt::TouchPointState state, 734 | Qt::KeyboardModifiers mods) 735 | #endif 736 | { 737 | Q_UNUSED(inputDevice) 738 | Q_UNUSED(global) 739 | Q_UNUSED(mods) 740 | #if QT_VERSION >= 0x060000 741 | bool handled = state == QEventPoint::Pressed; 742 | #else 743 | bool handled = state == Qt::TouchPointPressed; 744 | #endif 745 | if (handled) { 746 | if (buttonRect(Close).contains(local)) { 747 | QWindowSystemInterface::handleCloseEvent(window()); 748 | } else if (m_buttons.contains(Maximize) && buttonRect(Maximize).contains(local)) { 749 | window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized); 750 | } else if (m_buttons.contains(Minimize) && buttonRect(Minimize).contains(local)) { 751 | window()->setWindowState(Qt::WindowMinimized); 752 | } else if (local.y() <= margins().top()) { 753 | waylandWindow()->shellSurface()->move(inputDevice); 754 | } else { 755 | handled = false; 756 | } 757 | } 758 | 759 | return handled; 760 | } 761 | 762 | QRect QAdwaitaDecorations::windowContentGeometry() const 763 | { 764 | #ifdef HAS_QT6_SUPPORT 765 | return waylandWindow()->windowContentGeometry() + margins(ShadowsOnly); 766 | #else 767 | return waylandWindow()->windowContentGeometry(); 768 | #endif 769 | } 770 | 771 | void QAdwaitaDecorations::forceRepaint() 772 | { 773 | // Set dirty flag 774 | if (waylandWindow()->decoration()) { 775 | waylandWindow()->decoration()->update(); 776 | } 777 | // Force re-paint 778 | // NOTE: not sure it's correct, but it's the only way to make it work 779 | if (waylandWindow()->backingStore()) { 780 | waylandWindow()->backingStore()->flush(window(), QRegion(), QPoint()); 781 | } 782 | } 783 | 784 | void QAdwaitaDecorations::processMouseTop(QWaylandInputDevice *inputDevice, const QPointF &local, 785 | Qt::MouseButtons b, Qt::KeyboardModifiers mods) 786 | { 787 | Q_UNUSED(mods) 788 | 789 | QDateTime currentDateTime = QDateTime::currentDateTime(); 790 | QRect surfaceRect = windowContentGeometry(); 791 | 792 | if (!buttonRect(Close).contains(local) && !buttonRect(Maximize).contains(local) 793 | && !buttonRect(Minimize).contains(local)) { 794 | updateButtonHoverState(Button::None); 795 | } 796 | 797 | if (local.y() <= surfaceRect.top() + margins().bottom()) { 798 | if (local.x() <= margins().left()) { 799 | // top left bit 800 | #if QT_CONFIG(cursor) 801 | # if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) 802 | waylandWindow()->applyCursor(inputDevice, Qt::SizeFDiagCursor); 803 | # else 804 | waylandWindow()->setMouseCursor(inputDevice, Qt::SizeFDiagCursor); 805 | # endif 806 | #endif 807 | startResize(inputDevice, Qt::TopEdge | Qt::LeftEdge, b); 808 | } else if (local.x() > surfaceRect.right() - margins().left()) { 809 | // top right bit 810 | #if QT_CONFIG(cursor) 811 | # if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) 812 | waylandWindow()->applyCursor(inputDevice, Qt::SizeBDiagCursor); 813 | # else 814 | waylandWindow()->setMouseCursor(inputDevice, Qt::SizeBDiagCursor); 815 | # endif 816 | #endif 817 | startResize(inputDevice, Qt::TopEdge | Qt::RightEdge, b); 818 | } else { 819 | // top resize bit 820 | #if QT_CONFIG(cursor) 821 | # if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) 822 | waylandWindow()->applyCursor(inputDevice, Qt::SizeVerCursor); 823 | # else 824 | waylandWindow()->setMouseCursor(inputDevice, Qt::SizeVerCursor); 825 | # endif 826 | #endif 827 | startResize(inputDevice, Qt::TopEdge, b); 828 | } 829 | } else if (local.x() <= surfaceRect.left() + margins().left()) { 830 | processMouseLeft(inputDevice, local, b, mods); 831 | } else if (local.x() > surfaceRect.right() - margins().right()) { 832 | processMouseRight(inputDevice, local, b, mods); 833 | } else if (buttonRect(Close).contains(local)) { 834 | if (clickButton(b, Close)) { 835 | QWindowSystemInterface::handleCloseEvent(window()); 836 | m_hoveredButtons.setFlag(Close, false); 837 | } 838 | updateButtonHoverState(Close); 839 | } else if (m_buttons.contains(Maximize) && buttonRect(Maximize).contains(local)) { 840 | updateButtonHoverState(Maximize); 841 | if (clickButton(b, Maximize)) { 842 | window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized); 843 | m_hoveredButtons.setFlag(Maximize, false); 844 | } 845 | } else if (m_buttons.contains(Minimize) && buttonRect(Minimize).contains(local)) { 846 | updateButtonHoverState(Minimize); 847 | if (clickButton(b, Minimize)) { 848 | window()->setWindowState(Qt::WindowMinimized); 849 | m_hoveredButtons.setFlag(Minimize, false); 850 | } 851 | } else if (doubleClickButton(b, local, currentDateTime)) { 852 | window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized); 853 | } else { 854 | // Show window menu 855 | if (b == Qt::MouseButton::RightButton) { 856 | waylandWindow()->shellSurface()->showWindowMenu(inputDevice); 857 | } 858 | #if QT_CONFIG(cursor) 859 | waylandWindow()->restoreMouseCursor(inputDevice); 860 | #endif 861 | startMove(inputDevice, b); 862 | } 863 | } 864 | 865 | void QAdwaitaDecorations::processMouseBottom(QWaylandInputDevice *inputDevice, const QPointF &local, 866 | Qt::MouseButtons b, Qt::KeyboardModifiers mods) 867 | { 868 | Q_UNUSED(mods) 869 | if (local.x() <= margins().left()) { 870 | // bottom left bit 871 | #if QT_CONFIG(cursor) 872 | # if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) 873 | waylandWindow()->applyCursor(inputDevice, Qt::SizeBDiagCursor); 874 | # else 875 | waylandWindow()->setMouseCursor(inputDevice, Qt::SizeBDiagCursor); 876 | # endif 877 | #endif 878 | startResize(inputDevice, Qt::BottomEdge | Qt::LeftEdge, b); 879 | } else if (local.x() > window()->width() + margins().right()) { 880 | // bottom right bit 881 | #if QT_CONFIG(cursor) 882 | # if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) 883 | waylandWindow()->applyCursor(inputDevice, Qt::SizeFDiagCursor); 884 | # else 885 | waylandWindow()->setMouseCursor(inputDevice, Qt::SizeFDiagCursor); 886 | # endif 887 | #endif 888 | startResize(inputDevice, Qt::BottomEdge | Qt::RightEdge, b); 889 | } else { 890 | // bottom bit 891 | #if QT_CONFIG(cursor) 892 | # if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) 893 | waylandWindow()->applyCursor(inputDevice, Qt::SizeVerCursor); 894 | # else 895 | waylandWindow()->setMouseCursor(inputDevice, Qt::SizeVerCursor); 896 | # endif 897 | #endif 898 | startResize(inputDevice, Qt::BottomEdge, b); 899 | } 900 | } 901 | 902 | void QAdwaitaDecorations::processMouseLeft(QWaylandInputDevice *inputDevice, const QPointF &local, 903 | Qt::MouseButtons b, Qt::KeyboardModifiers mods) 904 | { 905 | Q_UNUSED(local) 906 | Q_UNUSED(mods) 907 | #if QT_CONFIG(cursor) 908 | # if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) 909 | waylandWindow()->applyCursor(inputDevice, Qt::SizeHorCursor); 910 | # else 911 | waylandWindow()->setMouseCursor(inputDevice, Qt::SizeHorCursor); 912 | # endif 913 | #endif 914 | startResize(inputDevice, Qt::LeftEdge, b); 915 | } 916 | 917 | void QAdwaitaDecorations::processMouseRight(QWaylandInputDevice *inputDevice, const QPointF &local, 918 | Qt::MouseButtons b, Qt::KeyboardModifiers mods) 919 | { 920 | Q_UNUSED(local) 921 | Q_UNUSED(mods) 922 | #if QT_CONFIG(cursor) 923 | # if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) 924 | waylandWindow()->applyCursor(inputDevice, Qt::SizeHorCursor); 925 | # else 926 | waylandWindow()->setMouseCursor(inputDevice, Qt::SizeHorCursor); 927 | # endif 928 | #endif 929 | startResize(inputDevice, Qt::RightEdge, b); 930 | } 931 | 932 | bool QAdwaitaDecorations::updateButtonHoverState(Button hoveredButton) 933 | { 934 | bool currentCloseButtonState = m_hoveredButtons.testFlag(Close); 935 | bool currentMaximizeButtonState = m_hoveredButtons.testFlag(Maximize); 936 | bool currentMinimizeButtonState = m_hoveredButtons.testFlag(Minimize); 937 | 938 | m_hoveredButtons.setFlag(Close, hoveredButton == Button::Close); 939 | m_hoveredButtons.setFlag(Maximize, hoveredButton == Button::Maximize); 940 | m_hoveredButtons.setFlag(Minimize, hoveredButton == Button::Minimize); 941 | 942 | if (m_hoveredButtons.testFlag(Close) != currentCloseButtonState 943 | || m_hoveredButtons.testFlag(Maximize) != currentMaximizeButtonState 944 | || m_hoveredButtons.testFlag(Minimize) != currentMinimizeButtonState) { 945 | forceRepaint(); 946 | return true; 947 | } 948 | 949 | return false; 950 | } 951 | --------------------------------------------------------------------------------