From 4c070c5d3619416d5c7866ce29f546911ffe8faa Mon Sep 17 00:00:00 2001 From: Tasos Sahanidis Date: Wed, 5 Aug 2020 06:02:44 +0300 Subject: [PATCH] Replace AppIndicator with minimal KStatusNotifier --- (daviddavid) Rebase patch --- CMakeLists.txt | 2 +- cmake/modules/FindAppindicator.cmake | 69 -- src/gui/CMakeLists.txt | 51 +- src/gui/ckbsystemtrayicon.cpp | 59 +- src/gui/ckbsystemtrayicon.h | 40 +- src/gui/kstatusnotifier/COPYING.LIB | 510 +++++++++++ .../kstatusnotifier/kstatusnotifieritem.cpp | 829 ++++++++++++++++++ src/gui/kstatusnotifier/kstatusnotifieritem.h | 483 ++++++++++ .../kstatusnotifieritemdbus_p.cpp | 292 ++++++ .../kstatusnotifieritemdbus_p.h | 246 ++++++ .../kstatusnotifieritemprivate_p.h | 171 ++++ .../org.freedesktop.Notifications.xml | 37 + .../org.kde.StatusNotifierItem.xml | 96 ++ .../org.kde.StatusNotifierWatcher.xml | 42 + src/gui/mainwindow.cpp | 103 +-- src/gui/mainwindow.h | 25 +- 16 files changed, 2838 insertions(+), 217 deletions(-) delete mode 100644 cmake/modules/FindAppindicator.cmake create mode 100644 src/gui/kstatusnotifier/COPYING.LIB create mode 100644 src/gui/kstatusnotifier/kstatusnotifieritem.cpp create mode 100644 src/gui/kstatusnotifier/kstatusnotifieritem.h create mode 100644 src/gui/kstatusnotifier/kstatusnotifieritemdbus_p.cpp create mode 100644 src/gui/kstatusnotifier/kstatusnotifieritemdbus_p.h create mode 100644 src/gui/kstatusnotifier/kstatusnotifieritemprivate_p.h create mode 100644 src/gui/kstatusnotifier/org.freedesktop.Notifications.xml create mode 100644 src/gui/kstatusnotifier/org.kde.StatusNotifierItem.xml create mode 100644 src/gui/kstatusnotifier/org.kde.StatusNotifierWatcher.xml diff --git a/CMakeLists.txt b/CMakeLists.txt index eb0c937d..107c87fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,8 @@ cmake_dependent_option(WITH_PIPE "Bu if (LINUX) cmake_dependent_option(WITH_MVIZ "Build with music visualizer." ON "WITH_ANIMATIONS;WITH_GUI" OFF) + cmake_dependent_option(USE_DBUS_MENU "Build with DBus support for the tray." ON + "WITH_GUI" OFF) option(USE_PORTAUDIO "Use portaudio for music visualizer." OFF) else () cmake_dependent_option(WITH_MVIZ "Build with music visualizer." OFF "WITH_ANIMATIONS;WITH_GUI" OFF) @@ -136,7 +138,6 @@ option(SAFE_UNINSTALL "Execute pre-unins Intended to be used with direct removals without package manager." OFF) option(WITH_SHIPPED_QUAZIP "Use shipped QuaZip5 instead of system package" OFF) -option(USE_APPINDICATOR_OVERRIDE "Use gtk2 Appindicator for Qt > 5.7." OFF) if (NOT WITH_GUI) message(WARNING "Building without GUI. Proceed only if you know what you are doing.") diff --git a/cmake/modules/FindAppindicator.cmake b/cmake/modules/FindAppindicator.cmake deleted file mode 100644 index b5f541d1..00000000 --- a/cmake/modules/FindAppindicator.cmake +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright 2017-2018 ckb-next Development Team -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from this -# software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - -#.rst: -# FindAppindicator -# ----------- -# -# Find any available appindicator (gtk2, gtk3, sharp). -# -# Result variables -# ^^^^^^^^^^^^^^^^ -# -# This module will set the following variables in your project: -# -# ``Appindicator_FOUND`` -# true if appindicator headers and libraries were found -# ``Appindicator_INCLUDE_DIRS`` -# list of the include directories needed to use appindicator -# ``Appindicator_LIBRARIES`` -# appindicator libraries to be linked -# ``Appindicator_DEFINITIONS`` -# the compiler switches required for using appindicator -# ``Appindicator_VERSION`` -# the version of appindicator found - -find_package(PkgConfig REQUIRED) -pkg_search_module(Appindicator appindicator-0.1) -set(Appindicator_DEFINITIONS ${Appindicator_CFLAGS_OTHER}) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args( - Appindicator - REQUIRED_VARS - Appindicator_LIBRARIES - Appindicator_INCLUDE_DIRS - Appindicator_DEFINITIONS - VERSION_VAR - Appindicator_VERSION) - -mark_as_advanced(Appindicator_LIBRARIES Appindicator_DEFINITIONS Appindicator_INCLUDE_DIRS Appindicator_VERSION) - -if(NOT Appindicator_FOUND) - message(WARNING "Appindicator was not found.\n" - "If you encounter issues with the tray icon, please install it.") -endif() diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index b1abd554..e39810a3 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -88,16 +88,8 @@ AudioToolbox: ${AUDIOTOOLBOX_LIBRARY}") endif () mark_as_advanced(FOUNDATION_LIBRARY COREAUDIO_LIBRARY AUDIOTOOLBOX_LIBRARY) elseif (LINUX) - if ((Qt5Core_VERSION VERSION_LESS 5.7.0) OR USE_APPINDICATOR_OVERRIDE OR "$ENV{QT_QPA_PLATFORMTHEME}" STREQUAL "gtk2" ) - find_package(Appindicator) - if (Appindicator_FOUND) - target_compile_definitions( - ckb-next - PRIVATE - USE_LIBAPPINDICATOR) - endif () - endif () find_package(X11 REQUIRED) + find_package(Qt5 5.2.0 COMPONENTS DBus) endif () # Automatically handle MOC, UIC and RCC @@ -121,7 +113,6 @@ if (MACOS OR LINUX) autorun.cpp ckbsettings.cpp ckbsettingswriter.cpp - ckbsystemtrayicon.cpp ckbupdater.cpp ckbupdaterwidget.cpp colorbutton.cpp @@ -240,6 +231,40 @@ elseif (LINUX) ckb-next PRIVATE media_linux.cpp) + + if(USE_DBUS_MENU) + find_package(dbusmenu-qt5 CONFIG) + if(NOT dbusmenu-qt5_FOUND) + message(FATAL_ERROR "dbusmenu-qt5 was not found. Either install it or pass -DUSE_DBUS_MENU=0 to fall back to the default Qt tray icon.") + endif() + target_link_libraries( + ckb-next + PRIVATE + Qt5::DBus + dbusmenu-qt5 + ) + target_compile_definitions( + ckb-next + PRIVATE + USE_DBUS_MENU) + + include_directories(${dbusmenu-qt5_INCLUDE_DIRS}) + qt5_add_dbus_adaptor(statusNotifierAutogen "kstatusnotifier/org.kde.StatusNotifierItem.xml" + "kstatusnotifier/kstatusnotifieritemdbus_p.h" KStatusNotifierItemDBus) + qt5_add_dbus_interface(statusNotifierAutogen "kstatusnotifier/org.freedesktop.Notifications.xml" notifications_interface) + qt5_add_dbus_interface(statusNotifierAutogen "kstatusnotifier/org.kde.StatusNotifierWatcher.xml" statusnotifierwatcher_interface) + set_property(SOURCE ${statusNotifierAutogen} PROPERTY SKIP_AUTOGEN ON) + target_sources( + ckb-next + PRIVATE + kstatusnotifier/kstatusnotifieritem.cpp + kstatusnotifier/kstatusnotifieritemdbus_p.cpp + kstatusnotifier/kstatusnotifieritemdbus_p.h + kstatusnotifier/kstatusnotifieritem.h + kstatusnotifier/kstatusnotifieritemprivate_p.h + ckbsystemtrayicon.cpp + ${statusNotifierAutogen}) + endif() endif () if (MAC_LEGACY) @@ -375,8 +400,7 @@ elseif (LINUX) ckb-next SYSTEM PRIVATE - "${X11_INCLUDE_DIR}" - "${Appindicator_INCLUDE_DIRS}") + "${X11_INCLUDE_DIR}") endif () if (NOT WITH_SHIPPED_QUAZIP) @@ -412,14 +436,12 @@ elseif (LINUX) ckb-next PRIVATE "${X11_LIBRARIES}" - "${Appindicator_LIBRARIES}" "${PULSEAUDIO_LIBRARIES}") else() target_link_libraries( ckb-next PRIVATE - "${X11_LIBRARIES}" - "${Appindicator_LIBRARIES}") + "${X11_LIBRARIES}") endif() endif() @@ -444,7 +466,6 @@ set_target_properties( target_compile_options( ckb-next PRIVATE - "${Appindicator_DEFINITIONS}" "${CKB_NEXT_COMMON_COMPILE_FLAGS}") if (CMAKE_BUILD_TYPE STREQUAL "Debug") diff --git a/src/gui/ckbsystemtrayicon.cpp b/src/gui/ckbsystemtrayicon.cpp index 3e98f066..89eb6939 100644 --- a/src/gui/ckbsystemtrayicon.cpp +++ b/src/gui/ckbsystemtrayicon.cpp @@ -1,15 +1,50 @@ +// Cmake will not build this file if USE_DBUS_MENU is not set #include "ckbsystemtrayicon.h" -#include -#include - -bool CkbSystemTrayIcon::event(QEvent* evt){ - if(evt->type() == QEvent::Wheel) { - QWheelEvent* wheelEvt = static_cast(evt); - if(wheelEvt->delta() > 0) - emit wheelScrolled(true); - else - emit wheelScrolled(false); - return true; +#include +#include +CkbSystemTrayIcon::CkbSystemTrayIcon(const QIcon& icon, QObject*parent) : KStatusNotifierItem("ckb-next", parent), previousPath("") +{ + setIcon(icon); +} + +// Not all implementations support passing icons by pixmap +// Save the icon to /tmp/ and use that instead +void CkbSystemTrayIcon::setIcon(QIcon icon) +{ + QList availSizes = icon.availableSizes(); + if(!availSizes.length()) + return; + + QPixmap pm = icon.pixmap(availSizes.at(0)); + QString path; + // Keep trying until we find a path that doesn't conflict + int i; + for(i = 0; i < 5; i++) + { + path = QDir::tempPath() + QString("/ckb-next-tray-%1.png").arg(qrand()); + if(!QFile::exists(path)) + break; } - return QSystemTrayIcon::event(evt); + + if(i == 5) + return; + + if(!pm.save(path)) + return; + + setIconByName(path); + + // Delete the old file + QFile f(previousPath); + if(!previousPath.isEmpty() && f.exists()) + f.remove(); + + previousPath = path; +} + +CkbSystemTrayIcon::~CkbSystemTrayIcon() +{ + QFile f(previousPath); + if(f.exists()) + f.remove(); } diff --git a/src/gui/ckbsystemtrayicon.h b/src/gui/ckbsystemtrayicon.h index c1c442bc..66980e13 100644 --- a/src/gui/ckbsystemtrayicon.h +++ b/src/gui/ckbsystemtrayicon.h @@ -1,18 +1,46 @@ #ifndef CKBSYSTEMTRAYICON_H #define CKBSYSTEMTRAYICON_H +#include #include +#ifdef USE_DBUS_MENU +#include "kstatusnotifier/kstatusnotifieritem.h" +class CkbSystemTrayIcon : public KStatusNotifierItem { +#else +#include +#include class CkbSystemTrayIcon : public QSystemTrayIcon { +#endif Q_OBJECT public: - CkbSystemTrayIcon(const QIcon& icon, QObject* parent = 0) - : QSystemTrayIcon(icon, parent) {} +#ifdef USE_DBUS_MENU + CkbSystemTrayIcon(const QIcon& icon, QObject* parent = 0); + inline void show() { setStatus(KStatusNotifierItem::Active); } + inline void setVisible(bool visible) { setStatus((visible ? KStatusNotifierItem::Active : KStatusNotifierItem::Passive)); } + void setIcon(QIcon icon); + ~CkbSystemTrayIcon(); +signals: + // This is never emitted by KStatusNotifierItem + void activated(QSystemTrayIcon::ActivationReason); + private: + QString previousPath; +#else + CkbSystemTrayIcon(const QIcon& icon, QObject* parent = 0) : QSystemTrayIcon(icon, parent) {} + virtual bool event(QEvent* evt) + { + if(evt->type() == QEvent::Wheel) { + QWheelEvent* wheelEvt = static_cast(evt); + emit scrollRequested(wheelEvt->delta(), wheelEvt->orientation()); + return true; + } + return QSystemTrayIcon::event(evt); + } - virtual bool event(QEvent* evt); - - signals: - void wheelScrolled(bool up); + signals: + void scrollRequested(int delta, Qt::Orientation orientation); +#endif }; + #endif // CKBSYSTEMTRAYICON_H diff --git a/src/gui/kstatusnotifier/COPYING.LIB b/src/gui/kstatusnotifier/COPYING.LIB new file mode 100644 index 00000000..2d2d780e --- /dev/null +++ b/src/gui/kstatusnotifier/COPYING.LIB @@ -0,0 +1,510 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/src/gui/kstatusnotifier/kstatusnotifieritem.cpp b/src/gui/kstatusnotifier/kstatusnotifieritem.cpp new file mode 100644 index 00000000..42e4cc0a --- /dev/null +++ b/src/gui/kstatusnotifier/kstatusnotifieritem.cpp @@ -0,0 +1,829 @@ +/* This file is part of the KDE libraries + Copyright 2009 by Marco Martin + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kstatusnotifieritem.h" +#include "kstatusnotifieritemprivate_p.h" +#include "kstatusnotifieritemdbus_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) +#define QIMAGE_SIZE(x) (x.byteCount()) +#else +#define QIMAGE_SIZE(x) (x.sizeInBytes()) +#endif + +static const char s_statusNotifierWatcherServiceName[] = "org.kde.StatusNotifierWatcher"; +static const int s_legacyTrayIconSize = 24; + +#include + +KStatusNotifierItem::KStatusNotifierItem(QObject *parent) + : QObject(parent), + d(new KStatusNotifierItemPrivate(this)) +{ + d->init(QString()); +} + +KStatusNotifierItem::KStatusNotifierItem(const QString &id, QObject *parent) + : QObject(parent), + d(new KStatusNotifierItemPrivate(this)) +{ + d->init(id); +} + +KStatusNotifierItem::~KStatusNotifierItem() +{ + delete d->statusNotifierWatcher; + delete d->notificationsClient; + delete d->systemTrayIcon; + if (!qApp->closingDown()) { + delete d->menu; + } + delete d; +} + +QString KStatusNotifierItem::id() const +{ + //qDebug() << "id requested" << d->id; + return d->id; +} + +void KStatusNotifierItem::setCategory(const ItemCategory category) +{ + d->category = category; +} + +KStatusNotifierItem::ItemStatus KStatusNotifierItem::status() const +{ + return d->status; +} + +KStatusNotifierItem::ItemCategory KStatusNotifierItem::category() const +{ + return d->category; +} + +void KStatusNotifierItem::setTitle(const QString &title) +{ + d->title = title; +} + +void KStatusNotifierItem::setStatus(const ItemStatus status) +{ + if (d->status == status) { + return; + } + + d->status = status; + emit d->statusNotifierItemDBus->NewStatus(QString::fromLatin1(metaObject()->enumerator(metaObject()->indexOfEnumerator("ItemStatus")).valueToKey(d->status))); + + if (d->systemTrayIcon) { + d->syncLegacySystemTrayIcon(); + } +} + +//normal icon + +void KStatusNotifierItem::setIconByName(const QString &name) +{ + if (d->iconName == name) { + return; + } + + d->serializedIcon = KDbusImageVector(); + d->iconName = name; + emit d->statusNotifierItemDBus->NewIcon(); + if (d->systemTrayIcon) { + // It's possible to pass paths as well + QIcon trayIcon = QIcon::fromTheme(name); + if(trayIcon.isNull()) + trayIcon = QIcon(name); + d->systemTrayIcon->setIcon(trayIcon); + } +} + +QString KStatusNotifierItem::iconName() const +{ + return d->iconName; +} + +void KStatusNotifierItem::setIconByPixmap(const QIcon &icon) +{ + if (d->iconName.isEmpty() && d->icon.cacheKey() == icon.cacheKey()) { + return; + } + + d->iconName.clear(); + d->serializedIcon = d->iconToVector(icon); + emit d->statusNotifierItemDBus->NewIcon(); + + d->icon = icon; + if (d->systemTrayIcon) { + d->systemTrayIcon->setIcon(icon); + } +} + +QIcon KStatusNotifierItem::iconPixmap() const +{ + return d->icon; +} + +void KStatusNotifierItem::setOverlayIconByName(const QString &name) +{ + if (d->overlayIconName == name) { + return; + } + + d->overlayIconName = name; + emit d->statusNotifierItemDBus->NewOverlayIcon(); + if (d->systemTrayIcon) { + QPixmap iconPixmap = QIcon::fromTheme(d->iconName).pixmap(s_legacyTrayIconSize, s_legacyTrayIconSize); + if (!name.isEmpty()) { + QPixmap overlayPixmap = QIcon::fromTheme(d->overlayIconName).pixmap(s_legacyTrayIconSize / 2, s_legacyTrayIconSize / 2); + QPainter p(&iconPixmap); + p.drawPixmap(iconPixmap.width() - overlayPixmap.width(), iconPixmap.height() - overlayPixmap.height(), overlayPixmap); + p.end(); + } + d->systemTrayIcon->setIcon(iconPixmap); + } +} + +QString KStatusNotifierItem::overlayIconName() const +{ + return d->overlayIconName; +} + +void KStatusNotifierItem::setOverlayIconByPixmap(const QIcon &icon) +{ + if (d->overlayIconName.isEmpty() && d->overlayIcon.cacheKey() == icon.cacheKey()) { + return; + } + + d->overlayIconName.clear(); + d->serializedOverlayIcon = d->iconToVector(icon); + emit d->statusNotifierItemDBus->NewOverlayIcon(); + + d->overlayIcon = icon; + if (d->systemTrayIcon) { + QPixmap iconPixmap = d->icon.pixmap(s_legacyTrayIconSize, s_legacyTrayIconSize); + QPixmap overlayPixmap = d->overlayIcon.pixmap(s_legacyTrayIconSize / 2, s_legacyTrayIconSize / 2); + + QPainter p(&iconPixmap); + p.drawPixmap(iconPixmap.width() - overlayPixmap.width(), iconPixmap.height() - overlayPixmap.height(), overlayPixmap); + p.end(); + d->systemTrayIcon->setIcon(iconPixmap); + } +} + +QIcon KStatusNotifierItem::overlayIconPixmap() const +{ + return d->overlayIcon; +} + +//Icons and movie for requesting attention state + +void KStatusNotifierItem::setAttentionIconByName(const QString &name) +{ + if (d->attentionIconName == name) { + return; + } + + d->serializedAttentionIcon = KDbusImageVector(); + d->attentionIconName = name; + emit d->statusNotifierItemDBus->NewAttentionIcon(); +} + +QString KStatusNotifierItem::attentionIconName() const +{ + return d->attentionIconName; +} + +void KStatusNotifierItem::setAttentionIconByPixmap(const QIcon &icon) +{ + if (d->attentionIconName.isEmpty() && d->attentionIcon.cacheKey() == icon.cacheKey()) { + return; + } + + d->attentionIconName.clear(); + d->serializedAttentionIcon = d->iconToVector(icon); + d->attentionIcon = icon; + emit d->statusNotifierItemDBus->NewAttentionIcon(); +} + +QIcon KStatusNotifierItem::attentionIconPixmap() const +{ + return d->attentionIcon; +} + +void KStatusNotifierItem::setAttentionMovieByName(const QString &name) +{ + if (d->movieName == name) { + return; + } + + d->movieName = name; + + delete d->movie; + d->movie = nullptr; + + emit d->statusNotifierItemDBus->NewAttentionIcon(); + + if (d->systemTrayIcon) { + d->movie = new QMovie(d->movieName); + d->systemTrayIcon->setMovie(d->movie); + } +} + +QString KStatusNotifierItem::attentionMovieName() const +{ + return d->movieName; +} + +//ToolTip +static void setTrayToolTip(KStatusNotifierLegacyIcon *systemTrayIcon, const QString &title, const QString &) +{ + if (systemTrayIcon) { + systemTrayIcon->setToolTip(title); + } +} + +void KStatusNotifierItem::setToolTip(const QString &iconName, const QString &title, const QString &subTitle) +{ + if (d->toolTipIconName == iconName && + d->toolTipTitle == title && + d->toolTipSubTitle == subTitle) { + return; + } + + d->serializedToolTipIcon = KDbusImageVector(); + d->toolTipIconName = iconName; + + d->toolTipTitle = title; + setTrayToolTip(d->systemTrayIcon, title, subTitle); + d->toolTipSubTitle = subTitle; + emit d->statusNotifierItemDBus->NewToolTip(); +} + +void KStatusNotifierItem::setToolTip(const QIcon &icon, const QString &title, const QString &subTitle) +{ + if (d->toolTipIconName.isEmpty() && d->toolTipIcon.cacheKey() == icon.cacheKey() && + d->toolTipTitle == title && + d->toolTipSubTitle == subTitle) { + return; + } + + d->toolTipIconName.clear(); + d->serializedToolTipIcon = d->iconToVector(icon); + d->toolTipIcon = icon; + + d->toolTipTitle = title; + setTrayToolTip(d->systemTrayIcon, title, subTitle); + + d->toolTipSubTitle = subTitle; + emit d->statusNotifierItemDBus->NewToolTip(); +} + +void KStatusNotifierItem::setToolTipIconByName(const QString &name) +{ + if (d->toolTipIconName == name) { + return; + } + + d->serializedToolTipIcon = KDbusImageVector(); + d->toolTipIconName = name; + emit d->statusNotifierItemDBus->NewToolTip(); +} + +QString KStatusNotifierItem::toolTipIconName() const +{ + return d->toolTipIconName; +} + +void KStatusNotifierItem::setToolTipIconByPixmap(const QIcon &icon) +{ + if (d->toolTipIconName.isEmpty() && d->toolTipIcon.cacheKey() == icon.cacheKey()) { + return; + } + + d->toolTipIconName.clear(); + d->serializedToolTipIcon = d->iconToVector(icon); + d->toolTipIcon = icon; + emit d->statusNotifierItemDBus->NewToolTip(); +} + +QIcon KStatusNotifierItem::toolTipIconPixmap() const +{ + return d->toolTipIcon; +} + +void KStatusNotifierItem::setToolTipTitle(const QString &title) +{ + if (d->toolTipTitle == title) { + return; + } + + d->toolTipTitle = title; + emit d->statusNotifierItemDBus->NewToolTip(); + setTrayToolTip(d->systemTrayIcon, title, d->toolTipSubTitle); +} + +QString KStatusNotifierItem::toolTipTitle() const +{ + return d->toolTipTitle; +} + +void KStatusNotifierItem::setToolTipSubTitle(const QString &subTitle) +{ + if (d->toolTipSubTitle == subTitle) { + return; + } + + d->toolTipSubTitle = subTitle; + emit d->statusNotifierItemDBus->NewToolTip(); +} + +QString KStatusNotifierItem::toolTipSubTitle() const +{ + return d->toolTipSubTitle; +} + +void KStatusNotifierItem::setContextMenu(QMenu *menu) +{ + if (d->menu && d->menu != menu) { + d->menu->removeEventFilter(this); + delete d->menu; + } + + if (!menu) { + d->menu = nullptr; + return; + } + + if (d->systemTrayIcon) { + d->systemTrayIcon->setContextMenu(menu); + } else if (d->menu != menu) { + d->menuObjectPath = QStringLiteral("/MenuBar"); + new DBusMenuExporter(d->menuObjectPath, menu, d->statusNotifierItemDBus->dbusConnection()); + + connect(menu, SIGNAL(aboutToShow()), this, SLOT(contextMenuAboutToShow())); + } + + d->menu = menu; + Qt::WindowFlags oldFlags = d->menu->windowFlags(); + d->menu->setParent(nullptr); + d->menu->setWindowFlags(oldFlags); +} + +QMenu *KStatusNotifierItem::contextMenu() const +{ + return d->menu; +} + +void KStatusNotifierItem::setAssociatedWidget(QWidget *associatedWidget) +{ + if (associatedWidget) { + d->associatedWidget = associatedWidget->window(); + d->associatedWidgetPos = QPoint(-1, -1); + } else if (d->associatedWidget) { + d->associatedWidget = nullptr; + } + + if (d->systemTrayIcon) { + delete d->systemTrayIcon; + d->systemTrayIcon = nullptr; + d->setLegacySystemTrayEnabled(true); + } +} + +QWidget *KStatusNotifierItem::associatedWidget() const +{ + return d->associatedWidget; +} + +QList KStatusNotifierItem::actionCollection() const +{ + return d->actionCollection.values(); +} + +void KStatusNotifierItem::addAction(const QString &name, QAction *action) +{ + d->actionCollection.insert(name, action); +} + +void KStatusNotifierItem::removeAction(const QString &name) +{ + d->actionCollection.remove(name); +} + +QAction* KStatusNotifierItem::action(const QString &name) const +{ + return d->actionCollection.value(name); +} + +void KStatusNotifierItem::showMessage(const QString &title, const QString &message, const QString &icon, int timeout) +{ + if (!d->notificationsClient) { + d->notificationsClient = new org::freedesktop::Notifications(QStringLiteral("org.freedesktop.Notifications"), QStringLiteral("/org/freedesktop/Notifications"), + QDBusConnection::sessionBus()); + } + + uint id = 0; + { + QVariantMap hints; + +#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) + QString desktopFileName = QGuiApplication::desktopFileName(); +#else + QString desktopFileName = QFileInfo(QCoreApplication::applicationFilePath()).fileName(); +#endif + if (!desktopFileName.isEmpty()) { + // handle apps which set the desktopFileName property with filename suffix, + // due to unclear API dox (https://bugreports.qt.io/browse/QTBUG-75521) + if (desktopFileName.endsWith(QLatin1String(".desktop"))) { + desktopFileName.chop(8); + } + hints.insert(QStringLiteral("desktop-entry"), desktopFileName); + } + + d->notificationsClient->Notify(d->title, id, icon, title, message, QStringList(), hints, timeout); + } +} + +QString KStatusNotifierItem::title() const +{ + return d->title; +} + +void KStatusNotifierItem::activate(const QPoint &pos) +{ + //if the user activated the icon the NeedsAttention state is no longer necessary + //FIXME: always true? + if (d->status == NeedsAttention) { + d->status = Active; + emit d->statusNotifierItemDBus->NewStatus(QString::fromLatin1(metaObject()->enumerator(metaObject()->indexOfEnumerator("ItemStatus")).valueToKey(d->status))); + } + + if (d->associatedWidget && d->associatedWidget == d->menu) { + d->statusNotifierItemDBus->ContextMenu(pos.x(), pos.y()); + return; + } + + if (d->menu && d->menu->isVisible()) { + d->menu->hide(); + } + + if (!d->associatedWidget) { + emit activateRequested(true, pos); + return; + } + + d->checkVisibility(pos); +} + +bool KStatusNotifierItemPrivate::checkVisibility(QPoint pos, bool perform) +{ + const bool unmapped = !(associatedWidget->isVisible() && !associatedWidget->isMinimized()); + if (perform) { + minimizeRestore(unmapped); + emit q->activateRequested(unmapped, pos); + } + return unmapped; +} + +bool KStatusNotifierItem::eventFilter(QObject *watched, QEvent *event) +{ + if (d->systemTrayIcon == nullptr) { + //FIXME: ugly ugly workaround to weird QMenu's focus problems + if (watched == d->menu && + (event->type() == QEvent::WindowDeactivate || (event->type() == QEvent::MouseButtonRelease && static_cast(event)->button() == Qt::LeftButton))) { + //put at the back of even queue to let the action activate anyways + QTimer::singleShot(0, this, [this]() { d->hideMenu(); }); + } + } + return false; +} + +//KStatusNotifierItemPrivate + +const int KStatusNotifierItemPrivate::s_protocolVersion = 0; + +KStatusNotifierItemPrivate::KStatusNotifierItemPrivate(KStatusNotifierItem *item) + : q(item), + category(KStatusNotifierItem::ApplicationStatus), + status(KStatusNotifierItem::Passive), + movie(nullptr), + menu(nullptr), + associatedWidget(nullptr), + titleAction(nullptr), + statusNotifierWatcher(nullptr), + notificationsClient(nullptr), + systemTrayIcon(nullptr) +{ +} + +void KStatusNotifierItemPrivate::init(const QString &extraId) +{ + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + + statusNotifierItemDBus = new KStatusNotifierItemDBus(q); + q->setAssociatedWidget(qobject_cast(q->parent())); + + QDBusServiceWatcher *watcher = new QDBusServiceWatcher(QString::fromLatin1(s_statusNotifierWatcherServiceName), + QDBusConnection::sessionBus(), + QDBusServiceWatcher::WatchForOwnerChange, + q); + QObject::connect(watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)), + q, SLOT(serviceChange(QString,QString,QString))); + + //create a default menu, just like in KSystemtrayIcon + QMenu *m = new QMenu(associatedWidget); + + title = QGuiApplication::applicationDisplayName(); + if (title.isEmpty()) { + title = QCoreApplication::applicationName(); + } + titleAction = m->addSection(qApp->windowIcon(), title); + m->setTitle(title); + q->setContextMenu(m); + + QAction *action = new QAction(q); + action->setText(KStatusNotifierItem::tr("Quit")); + action->setIcon(QIcon::fromTheme(QStringLiteral("application-exit"))); + // cannot yet convert to function-pointer-based connect: + // some apps like kalarm or korgac have a hack to rewire the connection + // of the "quit" action to a own slot, and rely on the name-based slot to disconnect + // TODO: extend KStatusNotifierItem API to support such needs + QObject::connect(action, SIGNAL(triggered()), q, SLOT(maybeQuit())); + actionCollection.insert(QStringLiteral("quit"), action); + + id = title; + if (!extraId.isEmpty()) { + id.append(QLatin1Char('_')).append(extraId); + } + + // Init iconThemePath to the app folder for now + iconThemePath = QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("icons"), QStandardPaths::LocateDirectory); + + registerToDaemon(); +} + +void KStatusNotifierItemPrivate::registerToDaemon() +{ + //qDebug() << "Registering a client interface to the KStatusNotifierWatcher"; + if (!statusNotifierWatcher) { + statusNotifierWatcher = new org::kde::StatusNotifierWatcher(QString::fromLatin1(s_statusNotifierWatcherServiceName), QStringLiteral("/StatusNotifierWatcher"), + QDBusConnection::sessionBus()); + } + + if (statusNotifierWatcher->isValid()) { + // get protocol version in async way + QDBusMessage msg = QDBusMessage::createMethodCall(QString::fromLatin1(s_statusNotifierWatcherServiceName), + QStringLiteral("/StatusNotifierWatcher"), + QStringLiteral("org.freedesktop.DBus.Properties"), + QStringLiteral("Get")); + msg.setArguments(QVariantList{QStringLiteral("org.kde.StatusNotifierWatcher"), QStringLiteral("ProtocolVersion")}); + QDBusPendingCall async = QDBusConnection::sessionBus().asyncCall(msg); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, q); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q, + [this, watcher] { + watcher->deleteLater(); + QDBusPendingReply reply = *watcher; + if (reply.isError()) { + qDebug() << "Failed to read protocol version of KStatusNotifierWatcher"; + setLegacySystemTrayEnabled(true); + } else { + bool ok = false; + const int protocolVersion = reply.value().toInt(&ok); + if (ok && protocolVersion == s_protocolVersion) { + statusNotifierWatcher->RegisterStatusNotifierItem(statusNotifierItemDBus->service()); + setLegacySystemTrayEnabled(false); + } else { + qDebug() << "KStatusNotifierWatcher has incorrect protocol version"; + setLegacySystemTrayEnabled(true); + } + } + } + ); + } else { + qDebug() << "KStatusNotifierWatcher not reachable"; + setLegacySystemTrayEnabled(true); + } +} + +void KStatusNotifierItemPrivate::serviceChange(const QString &name, const QString &oldOwner, const QString &newOwner) +{ + Q_UNUSED(name) + if (newOwner.isEmpty()) { + //unregistered + qDebug() << "Connection to the KStatusNotifierWatcher lost"; + setLegacyMode(true); + delete statusNotifierWatcher; + statusNotifierWatcher = nullptr; + } else if (oldOwner.isEmpty()) { + //registered + setLegacyMode(false); + } +} + +void KStatusNotifierItemPrivate::setLegacyMode(bool legacy) +{ + if (legacy) { + //unregistered + setLegacySystemTrayEnabled(true); + } else { + //registered + registerToDaemon(); + } +} + +void KStatusNotifierItemPrivate::legacyWheelEvent(int delta) +{ + statusNotifierItemDBus->Scroll(-delta, QStringLiteral("vertical")); +} + +void KStatusNotifierItemPrivate::legacyActivated(QSystemTrayIcon::ActivationReason reason) +{ + if (reason == QSystemTrayIcon::MiddleClick) { + emit q->secondaryActivateRequested(systemTrayIcon->geometry().topLeft()); + } else if (reason == QSystemTrayIcon::Trigger) { + q->activate(systemTrayIcon->geometry().topLeft()); + } +} + +void KStatusNotifierItemPrivate::setLegacySystemTrayEnabled(bool enabled) +{ + if (enabled == (systemTrayIcon != nullptr)) { + // already in the correct state + return; + } + + if (enabled) { + bool isKde = !qEnvironmentVariableIsEmpty("KDE_FULL_SESSION") || qgetenv("XDG_CURRENT_DESKTOP") == "KDE"; + if (!systemTrayIcon && !isKde) { + if (!QSystemTrayIcon::isSystemTrayAvailable()) { + return; + } + systemTrayIcon = new KStatusNotifierLegacyIcon(associatedWidget); + syncLegacySystemTrayIcon(); + systemTrayIcon->setToolTip(toolTipTitle); + // silence the "icon not set" warning + systemTrayIcon->setIcon(QIcon(QPixmap(16, 16))); + systemTrayIcon->show(); + QObject::connect(systemTrayIcon, SIGNAL(wheel(int)), q, SLOT(legacyWheelEvent(int))); + QObject::connect(systemTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), q, SLOT(legacyActivated(QSystemTrayIcon::ActivationReason))); + } else if (isKde) { + // prevent infinite recursion if the KDE platform plugin is loaded + // but SNI is not available; see bug 350785 + qDebug() << "env says KDE is running but SNI unavailable -- check " + "KDE_FULL_SESSION and XDG_CURRENT_DESKTOP"; + return; + } + + if (menu) { + menu->setWindowFlags(Qt::Popup); + } + } else { + delete systemTrayIcon; + systemTrayIcon = nullptr; + + if (menu) { + menu->setWindowFlags(Qt::Window); + } + } + + if (menu) { + QMenu *m = menu; + menu = nullptr; + q->setContextMenu(m); + } +} + +void KStatusNotifierItemPrivate::syncLegacySystemTrayIcon() +{ + // Hide the icon if passive + systemTrayIcon->setVisible(status != KStatusNotifierItem::Passive); + if (status == KStatusNotifierItem::NeedsAttention) { + { + if (!movieName.isNull()) { + if (!movie) { + movie = new QMovie(movieName); + } + systemTrayIcon->setMovie(movie); + } else if (!attentionIconName.isNull()) { + systemTrayIcon->setIcon(QIcon::fromTheme(attentionIconName)); + } else { + systemTrayIcon->setIcon(attentionIcon); + } + } + } else { + if (!iconName.isNull()) { + systemTrayIcon->setIcon(QIcon::fromTheme(iconName)); + } else { + systemTrayIcon->setIcon(icon); + } + } + + systemTrayIcon->setToolTip(toolTipTitle); +} + +void KStatusNotifierItemPrivate::contextMenuAboutToShow() +{ + +} + +void KStatusNotifierItemPrivate::maybeQuit() +{ + +} + +void KStatusNotifierItemPrivate::minimizeRestore() +{ + q->activate(systemTrayIcon ? systemTrayIcon->geometry().topLeft() : QPoint(0, 0)); +} + +void KStatusNotifierItemPrivate::hideMenu() +{ + menu->hide(); +} + +void KStatusNotifierItemPrivate::minimizeRestore(bool show) +{ + if (show) { + auto state = (associatedWidget->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive; + associatedWidget->setWindowState(state); + associatedWidget->show(); + associatedWidget->raise(); + } else { + associatedWidget->hide(); + } +} + +KDbusImageStruct KStatusNotifierItemPrivate::imageToStruct(const QImage &image) +{ + KDbusImageStruct icon; + icon.width = image.size().width(); + icon.height = image.size().height(); + if (image.format() == QImage::Format_ARGB32) { + icon.data = QByteArray((char *)image.bits(), QIMAGE_SIZE(image)); + } else { + QImage image32 = image.convertToFormat(QImage::Format_ARGB32); + icon.data = QByteArray((char *)image32.bits(), QIMAGE_SIZE(image32)); + } + + //swap to network byte order if we are little endian + if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) { + quint32 *uintBuf = (quint32 *) icon.data.data(); + for (uint i = 0; i < icon.data.size() / sizeof(quint32); ++i) { + *uintBuf = qToBigEndian(*uintBuf); + ++uintBuf; + } + } + + return icon; +} + +KDbusImageVector KStatusNotifierItemPrivate::iconToVector(const QIcon &icon) +{ + KDbusImageVector iconVector; + + QPixmap iconPixmap; + + //if an icon exactly that size wasn't found don't add it to the vector + const auto lstSizes = icon.availableSizes(); + for (QSize size : lstSizes) { + iconPixmap = icon.pixmap(size); + iconVector.append(imageToStruct(iconPixmap.toImage())); + } + return iconVector; +} + +#include "moc_kstatusnotifieritem.cpp" +#include "moc_kstatusnotifieritemprivate_p.cpp" diff --git a/src/gui/kstatusnotifier/kstatusnotifieritem.h b/src/gui/kstatusnotifier/kstatusnotifieritem.h new file mode 100644 index 00000000..99e490ca --- /dev/null +++ b/src/gui/kstatusnotifier/kstatusnotifieritem.h @@ -0,0 +1,483 @@ +/* This file is part of the KDE libraries + Copyright 2009 by Marco Martin + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KSTATUSNOTIFIERITEM_H +#define KSTATUSNOTIFIERITEM_H + +#include +#include +#include + +class QAction; +class QMenu; + +class KStatusNotifierItemPrivate; + +/** + * @class KStatusNotifierItem kstatusnotifieritem.h KStatusNotifierItem + * + * \brief %KDE Status notifier Item protocol implementation + * + * This class implements the Status notifier Item D-Bus specification. + * It provides an icon similar to the classical systemtray icons, + * with some key differences: + * + * - the actual representation is done by the systemtray (or the app behaving + * like it) itself, not by this app. Since 4.5 this also includes the menu, + * which means you cannot use embed widgets in the menu. + * + * - there is communication between the systemtray and the icon owner, so the + * system tray can know if the application is in a normal or in a requesting + * attention state. + * + * - icons are divided in categories, so the systemtray can represent in a + * different way the icons from normal applications and for instance the ones + * about hardware status. + * + * Whenever possible you should prefer passing icon by name rather than by + * pixmap because: + * + * - it is much lighter on D-Bus (no need to pass all image pixels). + * + * - it makes it possible for the systemtray to load an icon of the appropriate + * size or to replace your icon with a systemtray specific icon which matches + * with the desktop theme. + * + * - some implementations of the system tray do not support passing icons by + * pixmap and will show a blank icon instead. + * + * @author Marco Martin + * @since 4.4 + */ +class KStatusNotifierItem : public QObject +{ + Q_OBJECT + + Q_PROPERTY(ItemCategory category READ category WRITE setCategory) + Q_PROPERTY(QString title READ title WRITE setTitle) + Q_PROPERTY(ItemStatus status READ status WRITE setStatus) + Q_PROPERTY(QString iconName READ iconName WRITE setIconByName) + Q_PROPERTY(QString overlayIconName READ overlayIconName WRITE setOverlayIconByName) + Q_PROPERTY(QString attentionIconName READ attentionIconName WRITE setAttentionIconByName) + Q_PROPERTY(QString toolTipIconName READ toolTipIconName WRITE setToolTipIconByName) + Q_PROPERTY(QString toolTipTitle READ toolTipTitle WRITE setToolTipTitle) + Q_PROPERTY(QString toolTipSubTitle READ toolTipSubTitle WRITE setToolTipSubTitle) + + friend class KStatusNotifierItemDBus; + friend class KStatusNotifierItemPrivate; +public: + /** + * All the possible status this icon can have, depending on the + * importance of the events that happens in the parent application + */ + enum ItemStatus { + /// Nothing is happening in the application, so showing this icon is not required + Passive = 1, + /// The application is doing something, or it is important that the + /// icon is always reachable from the user + Active = 2, + /// The application requests the attention of the user, for instance + /// battery running out or a new IM message was received + NeedsAttention = 3 + }; + Q_ENUM(ItemStatus) + + /** + * Different kinds of applications announce their type to the systemtray, + * so can be drawn in a different way or in a different place + */ + enum ItemCategory { + /// An icon for a normal application, can be seen as its taskbar entry + ApplicationStatus = 1, + /// This is a communication oriented application; this icon will be used + /// for things such as the notification of a new message + Communications = 2, + /// This is a system service, it can show itself in the system tray if + /// it requires interaction from the user or wants to inform him about + /// something + SystemServices = 3, + /// This application shows hardware status or a means to control it + Hardware = 4, + Reserved = 129 + }; + Q_ENUM(ItemCategory) + + /** + * Construct a new status notifier item + * + * @param parent the parent object for this object. If the object passed in as + * a parent is also a QWidget, it will be used as the main application window + * represented by this icon and will be shown/hidden when an activation is requested. + * @see associatedWidget + **/ + explicit KStatusNotifierItem(QObject *parent = nullptr); + + /** + * Construct a new status notifier item with a unique identifier. + * If your application has more than one status notifier item and the user + * should be able to manipulate them separately (e.g. mark them for hiding + * in a user interface), the id can be used to differentiate between them. + * + * The id should remain consistent even between application restarts. + * Status notifier items without ids default to the application's name for the id. + * This id may be used, for instance, by hosts displaying status notifier items to + * associate configuration information with this item in a way that can persist + * between sessions or application restarts. + * + * @param id the unique id for this icon + * @param parent the parent object for this object. If the object passed in as + * a parent is also a QWidget, it will be used as the main application window + * represented by this icon and will be shown/hidden when an activation is requested. + * @see associatedWidget + **/ + explicit KStatusNotifierItem(const QString &id, QObject *parent = nullptr); + + ~KStatusNotifierItem() override; + + /** + * @return The id which was specified in the constructor. This should be + * guaranteed to be consistent between application starts and + * untranslated, as host applications displaying items may use it for + * storing configuration related to this item. + */ + QString id() const; + + /** + * Sets the category for this icon, usually it's needed to call this function only once + * + * @param category the new category for this icon + */ + void setCategory(const ItemCategory category); + + /** + * @return the application category + */ + ItemCategory category() const; + + /** + * Sets a title for this icon + */ + void setTitle(const QString &title); + + /** + * @return the title of this icon + */ + QString title() const; + + /** + * Sets a new status for this icon. + */ + void setStatus(const ItemStatus status); + + /** + * @return the current application status + */ + ItemStatus status() const; + + //Main icon related functions + /** + * Sets a new main icon for the system tray + * + * @param name it must be a QIcon::fromTheme compatible name, this is + * the preferred way to set an icon + */ + void setIconByName(const QString &name); + + /** + * @return the name of the main icon to be displayed + * if image() is not empty this will always return an empty string + */ + QString iconName() const; + + /** + * Sets a new main icon for the system tray + * + * @param pixmap our icon, use setIcon(const QString) when possible + */ + void setIconByPixmap(const QIcon &icon); + + /** + * @return a pixmap of the icon + */ + QIcon iconPixmap() const; + + /** + * Sets an icon to be used as overlay for the main one + * @param icon name, if name is and empty QString() + * (and overlayIconPixmap() is empty too) the icon will be removed + */ + void setOverlayIconByName(const QString &name); + + /** + * @return the name of the icon to be used as overlay fr the main one + */ + QString overlayIconName() const; + + /** + * Sets an icon to be used as overlay for the main one + * setOverlayIconByPixmap(QIcon()) will remove the overlay when + * overlayIconName() is empty too. + * + * @param pixmap our overlay icon, use setOverlayIcon(const QString) when possible. + */ + void setOverlayIconByPixmap(const QIcon &icon); + + /** + * @return a pixmap of the icon + */ + QIcon overlayIconPixmap() const; + + //Requesting attention icon + + /** + * Sets a new icon that should be used when the application + * wants to request attention (usually the systemtray + * will blink between this icon and the main one) + * + * @param name QIcon::fromTheme compatible name of icon to use + */ + void setAttentionIconByName(const QString &name); + + /** + * @return the name of the icon to be displayed when the application + * is requesting the user's attention + * if attentionImage() is not empty this will always return an empty string + */ + QString attentionIconName() const; + + /** + * Sets the pixmap of the requesting attention icon. + * Use setAttentionIcon(const QString) instead when possible. + * + * @param icon QIcon to use for requesting attention. + */ + void setAttentionIconByPixmap(const QIcon &icon); + + /** + * @return a pixmap of the requesting attention icon + */ + QIcon attentionIconPixmap() const; + + /** + * Sets a movie as the requesting attention icon. + * This overrides anything set in setAttentionIcon() + */ + void setAttentionMovieByName(const QString &name); + + /** + * @return the name of the movie to be displayed when the application is + * requesting the user attention + */ + QString attentionMovieName() const; + + //ToolTip handling + /** + * Sets a new toolTip or this icon, a toolTip is composed of an icon, + * a title and a text, all fields are optional. + * + * @param iconName a QIcon::fromTheme compatible name for the tootip icon + * @param title tootip title + * @param subTitle subtitle for the toolTip + */ + void setToolTip(const QString &iconName, const QString &title, const QString &subTitle); + + /** + * Sets a new toolTip or this status notifier item. + * This is an overloaded member provided for convenience + */ + void setToolTip(const QIcon &icon, const QString &title, const QString &subTitle); + + /** + * Set a new icon for the toolTip + * + * @param name the name for the icon + */ + void setToolTipIconByName(const QString &name); + + /** + * @return the name of the toolTip icon + * if toolTipImage() is not empty this will always return an empty string + */ + QString toolTipIconName() const; + + /** + * Set a new icon for the toolTip. + * + * Use setToolTipIconByName(QString) if possible. + * @param pixmap representing the icon + */ + void setToolTipIconByPixmap(const QIcon &icon); + + /** + * @return a serialization of the toolTip icon data + */ + QIcon toolTipIconPixmap() const; + + /** + * Sets a new title for the toolTip + */ + void setToolTipTitle(const QString &title); + + /** + * @return the title of the main icon toolTip + */ + QString toolTipTitle() const; + + /** + * Sets a new subtitle for the toolTip + */ + void setToolTipSubTitle(const QString &subTitle); + + /** + * @return the subtitle of the main icon toolTip + */ + QString toolTipSubTitle() const; + + /** + * Sets a new context menu for this StatusNotifierItem. + * the menu will be shown with a contextMenu(int,int) + * call by the systemtray over D-Bus + * usually you don't need to call this unless you want to use + * a custom QMenu subclass as context menu. + * + * The KStatusNotifierItem instance takes ownership of the menu, + * and will delete it upon its destruction. + */ + void setContextMenu(QMenu *menu); + + /** + * Access the context menu associated to this status notifier item + */ + QMenu *contextMenu() const; + + /** + * Sets the main widget associated with this StatusNotifierItem + * + * If you pass contextMenu() as a parent then the menu will be displayed + * when the user activate the icon. In this case the activate() method will + * not be called and the activateRequested() signal will not be emitted + * + * @param parent the new main widget: must be a top level window, + * if it's not parent->window() will be used instead. + */ + void setAssociatedWidget(QWidget *parent); + + /** + * Access the main widget associated with this StatusNotifierItem + */ + QWidget *associatedWidget() const; + + /** + * All the actions present in the menu + */ + QList actionCollection() const; + + /** + * Adds an action to the actionCollection() + * + * @param name the name of the action + * @param action the action we want to add + */ + void addAction(const QString &name, QAction *action); + + /** + * Removes an action from the collection + * + * @param name the name of the action + */ + void removeAction(const QString &name); + + /** + * Retrieves an action from the action collection + * by the action name + * + * @param name the name of the action to retrieve + * @since 5.12 + */ + QAction *action(const QString &name) const; + + /** + * Shows the user a notification. If possible use KNotify instead + * + * @param title message title + * @param message the actual text shown to the user + * @param icon icon to be shown to the user + * @param timeout how much time will elaps before hiding the message + */ + void showMessage(const QString &title, const QString &message, const QString &icon, int timeout = 10000); + +public Q_SLOTS: + + /** + * Shows the main widget and try to position it on top + * of the other windows, if the widget is already visible, hide it. + * + * @param pos if it's a valid position it represents the mouse coordinates when the event was triggered + */ + virtual void activate(const QPoint &pos = QPoint()); + +Q_SIGNALS: + /** + * Inform the host application that the mouse wheel + * (or another mean of scrolling that the visualization provides) has been used + * + * @param delta the amount of scrolling, can be either positive or negative + * @param orientation direction of the scrolling, can be either horizontal or vertical + */ + void scrollRequested(int delta, Qt::Orientation orientation); + + /** + * Inform the host application that an activation has been requested, + * for instance left mouse click, but this is not guaranteed since + * it's dependent from the visualization + * @param active if it's true the application asked for the activation + * of the main window, if it's false it asked for hiding + * @param pos the position in the screen where the user clicked to + * trigger this signal, QPoint() if it's not the consequence of a mouse click. + */ + void activateRequested(bool active, const QPoint &pos); + + /** + * Alternate activate action, + * for instance right mouse click, but this is not guaranteed since + * it's dependent from the visualization + * + * @param pos the position in the screen where the user clicked to + * trigger this signal, QPoint() if it's not the consequence of a mouse click. + */ + void secondaryActivateRequested(const QPoint &pos); + +protected: + bool eventFilter(QObject *watched, QEvent *event) override; + +private: + KStatusNotifierItemPrivate *const d; + + Q_PRIVATE_SLOT(d, void serviceChange(const QString &name, + const QString &oldOwner, + const QString &newOwner)) + Q_PRIVATE_SLOT(d, void contextMenuAboutToShow()) + Q_PRIVATE_SLOT(d, void maybeQuit()) + Q_PRIVATE_SLOT(d, void minimizeRestore()) + Q_PRIVATE_SLOT(d, void legacyWheelEvent(int)) + Q_PRIVATE_SLOT(d, void legacyActivated(QSystemTrayIcon::ActivationReason)) +}; + +#endif diff --git a/src/gui/kstatusnotifier/kstatusnotifieritemdbus_p.cpp b/src/gui/kstatusnotifier/kstatusnotifieritemdbus_p.cpp new file mode 100644 index 00000000..3f89c66c --- /dev/null +++ b/src/gui/kstatusnotifier/kstatusnotifieritemdbus_p.cpp @@ -0,0 +1,292 @@ +/* This file is part of the KDE libraries + Copyright 2009 by Marco Martin + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kstatusnotifieritemdbus_p.h" +#include "kstatusnotifieritemprivate_p.h" +#include "kstatusnotifieritem.h" + +#include +#include + +#include "statusnotifierwatcher_interface.h" +#include "statusnotifieritemadaptor.h" + +__inline int toInt(WId wid) +{ + return (int)wid; +} + +// Marshall the ImageStruct data into a D-BUS argument +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageStruct &icon) +{ + argument.beginStructure(); + argument << icon.width; + argument << icon.height; + argument << icon.data; + argument.endStructure(); + return argument; +} + +// Retrieve the ImageStruct data from the D-BUS argument +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageStruct &icon) +{ + qint32 width; + qint32 height; + QByteArray data; + + argument.beginStructure(); + argument >> width; + argument >> height; + argument >> data; + argument.endStructure(); + + icon.width = width; + icon.height = height; + icon.data = data; + + return argument; +} + +// Marshall the ImageVector data into a D-BUS argument +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageVector &iconVector) +{ + argument.beginArray(qMetaTypeId()); + for (int i = 0; i < iconVector.size(); ++i) { + argument << iconVector[i]; + } + argument.endArray(); + return argument; +} + +// Retrieve the ImageVector data from the D-BUS argument +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageVector &iconVector) +{ + argument.beginArray(); + iconVector.clear(); + + while (!argument.atEnd()) { + KDbusImageStruct element; + argument >> element; + iconVector.append(element); + } + + argument.endArray(); + + return argument; +} + +// Marshall the ToolTipStruct data into a D-BUS argument +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusToolTipStruct &toolTip) +{ + argument.beginStructure(); + argument << toolTip.icon; + argument << toolTip.image; + argument << toolTip.title; + argument << toolTip.subTitle; + argument.endStructure(); + return argument; +} + +// Retrieve the ToolTipStruct data from the D-BUS argument +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusToolTipStruct &toolTip) +{ + QString icon; + KDbusImageVector image; + QString title; + QString subTitle; + + argument.beginStructure(); + argument >> icon; + argument >> image; + argument >> title; + argument >> subTitle; + argument.endStructure(); + + toolTip.icon = icon; + toolTip.image = image; + toolTip.title = title; + toolTip.subTitle = subTitle; + + return argument; +} + +int KStatusNotifierItemDBus::s_serviceCount = 0; + +KStatusNotifierItemDBus::KStatusNotifierItemDBus(KStatusNotifierItem *parent) + : QObject(parent), + m_statusNotifierItem(parent), + m_connId(QStringLiteral("org.kde.StatusNotifierItem-%1-%2") + .arg(QCoreApplication::applicationPid()) + .arg(++s_serviceCount)), + m_dbus(QDBusConnection(m_connId)) +{ + m_dbus = QDBusConnection::connectToBus(QDBusConnection::SessionBus, m_connId); + + new StatusNotifierItemAdaptor(this); + m_dbus.registerObject(QStringLiteral("/StatusNotifierItem"), this); +} + +KStatusNotifierItemDBus::~KStatusNotifierItemDBus() +{ + m_dbus.unregisterObject(QStringLiteral("/StatusNotifierItem")); + m_dbus.disconnectFromBus(m_connId); +} + +QDBusConnection KStatusNotifierItemDBus::dbusConnection() const +{ + return m_dbus; +} + +QString KStatusNotifierItemDBus::service() const +{ + return m_dbus.baseService(); +} + +bool KStatusNotifierItemDBus::ItemIsMenu() const +{ + return (m_statusNotifierItem->d->associatedWidget == m_statusNotifierItem->d->menu); +} + +//DBUS slots + +QString KStatusNotifierItemDBus::Category() const +{ + return QLatin1String(m_statusNotifierItem->metaObject()->enumerator(m_statusNotifierItem->metaObject()->indexOfEnumerator("ItemCategory")).valueToKey(m_statusNotifierItem->category())); +} + +QString KStatusNotifierItemDBus::Title() const +{ + return m_statusNotifierItem->title(); +} + +QString KStatusNotifierItemDBus::Id() const +{ + return m_statusNotifierItem->id(); +} + +QString KStatusNotifierItemDBus::Status() const +{ + return QLatin1String(m_statusNotifierItem->metaObject()->enumerator(m_statusNotifierItem->metaObject()->indexOfEnumerator("ItemStatus")).valueToKey(m_statusNotifierItem->status())); +} + +int KStatusNotifierItemDBus::WindowId() const +{ + if (m_statusNotifierItem->d->associatedWidget && m_statusNotifierItem->d->associatedWidget != m_statusNotifierItem->d->menu) { + return toInt(m_statusNotifierItem->d->associatedWidget->winId()); + } else { + return 0; + } +} + +//Icon + +QString KStatusNotifierItemDBus::IconName() const +{ + return m_statusNotifierItem->iconName(); +} + +KDbusImageVector KStatusNotifierItemDBus::IconPixmap() const +{ + return m_statusNotifierItem->d->serializedIcon; +} + +QString KStatusNotifierItemDBus::OverlayIconName() const +{ + return m_statusNotifierItem->overlayIconName(); +} + +KDbusImageVector KStatusNotifierItemDBus::OverlayIconPixmap() const +{ + return m_statusNotifierItem->d->serializedOverlayIcon; +} + +//Requesting attention icon and movie + +QString KStatusNotifierItemDBus::AttentionIconName() const +{ + return m_statusNotifierItem->attentionIconName(); +} + +KDbusImageVector KStatusNotifierItemDBus::AttentionIconPixmap() const +{ + return m_statusNotifierItem->d->serializedAttentionIcon; +} + +QString KStatusNotifierItemDBus::AttentionMovieName() const +{ + return m_statusNotifierItem->d->movieName; +} + +//ToolTip + +KDbusToolTipStruct KStatusNotifierItemDBus::ToolTip() const +{ + KDbusToolTipStruct toolTip; + toolTip.icon = m_statusNotifierItem->toolTipIconName(); + toolTip.image = m_statusNotifierItem->d->serializedToolTipIcon; + toolTip.title = m_statusNotifierItem->toolTipTitle(); + toolTip.subTitle = m_statusNotifierItem->toolTipSubTitle(); + + return toolTip; +} + +QString KStatusNotifierItemDBus::IconThemePath() const +{ + return m_statusNotifierItem->d->iconThemePath; +} + +//Menu +QDBusObjectPath KStatusNotifierItemDBus::Menu() const +{ + return QDBusObjectPath(m_statusNotifierItem->d->menuObjectPath); +} + +//Interaction + +void KStatusNotifierItemDBus::ContextMenu(int x, int y) +{ + if (!m_statusNotifierItem->d->menu) { + return; + } + + //TODO: nicer placement, possible? + if (!m_statusNotifierItem->d->menu->isVisible()) { + m_statusNotifierItem->d->menu->popup(QPoint(x, y)); + } else { + m_statusNotifierItem->d->menu->hide(); + } +} + +void KStatusNotifierItemDBus::Activate(int x, int y) +{ + m_statusNotifierItem->activate(QPoint(x, y)); +} + +void KStatusNotifierItemDBus::SecondaryActivate(int x, int y) +{ + emit m_statusNotifierItem->secondaryActivateRequested(QPoint(x, y)); +} + +void KStatusNotifierItemDBus::Scroll(int delta, const QString &orientation) +{ + Qt::Orientation dir = (orientation.toLower() == QLatin1String("horizontal") ? Qt::Horizontal : Qt::Vertical); + emit m_statusNotifierItem->scrollRequested(-delta, dir); +} + diff --git a/src/gui/kstatusnotifier/kstatusnotifieritemdbus_p.h b/src/gui/kstatusnotifier/kstatusnotifieritemdbus_p.h new file mode 100644 index 00000000..068aa2f3 --- /dev/null +++ b/src/gui/kstatusnotifier/kstatusnotifieritemdbus_p.h @@ -0,0 +1,246 @@ +/* This file is part of the KDE libraries + Copyright 2009 by Marco Martin + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KSTATUSNOTIFIERITEMDBUS_H +#define KSTATUSNOTIFIERITEMDBUS_H + +#include +#include +#include +#include +#include +#include + +//Custom message type for DBus +struct KDbusImageStruct { + int width; + int height; + QByteArray data; +}; + +typedef QVector KDbusImageVector; + +struct KDbusToolTipStruct { + QString icon; + KDbusImageVector image; + QString title; + QString subTitle; +}; + +class KStatusNotifierItem; + +class KStatusNotifierItemDBus : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString Category READ Category) + Q_PROPERTY(QString Id READ Id) + Q_PROPERTY(QString Title READ Title) + Q_PROPERTY(QString Status READ Status) + Q_PROPERTY(int WindowId READ WindowId) + Q_PROPERTY(bool ItemIsMenu READ ItemIsMenu) + Q_PROPERTY(QString IconName READ IconName) + Q_PROPERTY(KDbusImageVector IconPixmap READ IconPixmap) + Q_PROPERTY(QString OverlayIconName READ OverlayIconName) + Q_PROPERTY(KDbusImageVector OverlayIconPixmap READ OverlayIconPixmap) + Q_PROPERTY(QString AttentionIconName READ AttentionIconName) + Q_PROPERTY(KDbusImageVector AttentionIconPixmap READ AttentionIconPixmap) + Q_PROPERTY(QString AttentionMovieName READ AttentionMovieName) + Q_PROPERTY(KDbusToolTipStruct ToolTip READ ToolTip) + Q_PROPERTY(QString IconThemePath READ IconThemePath) + Q_PROPERTY(QDBusObjectPath Menu READ Menu) + + friend class KStatusNotifierItem; +public: + explicit KStatusNotifierItemDBus(KStatusNotifierItem *parent); + ~KStatusNotifierItemDBus(); + + /** + * @return the dbus connection used by this object + */ + QDBusConnection dbusConnection() const; + + /** + * @return the service this object is registered on the bus under + */ + QString service() const; + + /** + * @return the category of the application associated to this item + * @see Category + */ + QString Category() const; + + /** + * @return the id of this item + */ + QString Id() const; + + /** + * @return the title of this item + */ + QString Title() const; + + /** + * @return The status of this item + * @see Status + */ + QString Status() const; + + /** + * @return The id of the main window of the application that controls the item + */ + int WindowId() const; + + /** + * @return The item only support the context menu, the visualization should prefer sending ContextMenu() instead of Activate() + */ + bool ItemIsMenu() const; + + /** + * @return the name of the main icon to be displayed + * if image() is not empty this will always return an empty string + */ + QString IconName() const; + + /** + * @return a serialization of the icon data + */ + KDbusImageVector IconPixmap() const; + + /** + * @return the name of the overlay of the main icon to be displayed + * if image() is not empty this will always return an empty string + */ + QString OverlayIconName() const; + + /** + * @return a serialization of the icon data + */ + KDbusImageVector OverlayIconPixmap() const; + + /** + * @return the name of the icon to be displayed when the application + * is requesting the user's attention + * if attentionImage() is not empty this will always return an empty string + */ + QString AttentionIconName() const; + + /** + * @return a serialization of the requesting attention icon data + */ + KDbusImageVector AttentionIconPixmap() const; + + /** + * @return the name of the attention movie + */ + QString AttentionMovieName() const; + + /** + * all the data needed for a tooltip + */ + KDbusToolTipStruct ToolTip() const; + + /** + * @return path to extra icon theme, to load application specific icons + */ + QString IconThemePath() const; + + /** + * @return object path to the dbusmenu object + */ + QDBusObjectPath Menu() const; + +public Q_SLOTS: + //interaction + /** + * Shows the context menu associated to this item + * at the desired screen position + */ + void ContextMenu(int x, int y); + + /** + * Shows the main widget and try to position it on top + * of the other windows, if the widget is already visible, hide it. + */ + void Activate(int x, int y); + + /** + * The user activated the item in an alternate way (for instance with middle mouse button, this depends from the systray implementation) + */ + void SecondaryActivate(int x, int y); + + /** + * Inform this item that the mouse wheel was used on its representation + */ + void Scroll(int delta, const QString &orientation); + +Q_SIGNALS: + /** + * Inform the systemtray that the own main icon has been changed, + * so should be reloaded + */ + void NewIcon(); + + /** + * Inform the systemtray that there is a new icon to be used as overlay + */ + void NewOverlayIcon(); + + /** + * Inform the systemtray that the requesting attention icon + * has been changed, so should be reloaded + */ + void NewAttentionIcon(); + + /** + * Inform the systemtray that something in the tooltip has been changed + */ + void NewToolTip(); + + /** + * Signal the new status when it has been changed + * @see Status + */ + void NewStatus(const QString &status); + +private: + KStatusNotifierItem *m_statusNotifierItem; + QString m_connId; + QDBusConnection m_dbus; + static int s_serviceCount; +}; + +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageStruct &icon); +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageStruct &icon); + +Q_DECLARE_METATYPE(KDbusImageStruct) + +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageVector &iconVector); +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageVector &iconVector); + +Q_DECLARE_METATYPE(KDbusImageVector) + +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusToolTipStruct &toolTip); +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusToolTipStruct &toolTip); + +Q_DECLARE_METATYPE(KDbusToolTipStruct) + +#endif diff --git a/src/gui/kstatusnotifier/kstatusnotifieritemprivate_p.h b/src/gui/kstatusnotifier/kstatusnotifieritemprivate_p.h new file mode 100644 index 00000000..c3275aeb --- /dev/null +++ b/src/gui/kstatusnotifier/kstatusnotifieritemprivate_p.h @@ -0,0 +1,171 @@ +/* This file is part of the KDE libraries + Copyright 2009 by Marco Martin + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KSTATUSNOTIFIERITEMPRIVATE_H +#define KSTATUSNOTIFIERITEMPRIVATE_H + +#include +#include +#include +#include +#include + +#include "kstatusnotifieritem.h" +#include "kstatusnotifieritemdbus_p.h" + +#include "statusnotifierwatcher_interface.h" +#include "notifications_interface.h" + +class KSystemTrayIcon; +class QMenu; +class QAction; + +// this class is needed because we can't just put an event filter on it: +// the events that are passed to QSystemTrayIcon are done so in a way that +// bypasses the usual event filtering mechanisms *sigh* +class KStatusNotifierLegacyIcon : public QSystemTrayIcon +{ + Q_OBJECT + +public: + KStatusNotifierLegacyIcon(QObject *parent) + : QSystemTrayIcon(parent) + { + } + + bool event(QEvent *e) override + { + if (e->type() == QEvent::Wheel) { + QWheelEvent *wheelEvent = static_cast(e); + emit wheel(wheelEvent->angleDelta().y()); + } + + return false; + } + + void setMovie(QMovie *movie) + { + if (m_movie.data() == movie) { + return; + } + + delete m_movie.data(); + m_movie = movie; + + if (!movie) { + return; + } + + movie->setParent(this); + movie->setCacheMode(QMovie::CacheAll); + connect(movie, &QMovie::frameChanged, this, &KStatusNotifierLegacyIcon::slotNewFrame); + } + + void setIconWithMask(QIcon &icon, bool isMask) + { +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) + icon.setIsMask(isMask); +#endif + QSystemTrayIcon::setIcon(icon); + } + +Q_SIGNALS: + void wheel(int); + +private Q_SLOTS: + void slotNewFrame() + { + if (m_movie) { + setIcon(QIcon(m_movie.data()->currentPixmap())); + } + } + +private: + QPointer m_movie; +}; + +class KStatusNotifierItemPrivate +{ +public: + KStatusNotifierItemPrivate(KStatusNotifierItem *item); + + void init(const QString &extraId); + void registerToDaemon(); + void serviceChange(const QString &name, const QString &oldOwner, const QString &newOwner); + void setLegacySystemTrayEnabled(bool enabled); + void syncLegacySystemTrayIcon(); + void contextMenuAboutToShow(); + void maybeQuit(); + void minimizeRestore(); + void minimizeRestore(bool show); + void hideMenu(); + void setLegacyMode(bool legacy); + void checkForRegisteredHosts(); + void legacyWheelEvent(int delta); + void legacyActivated(QSystemTrayIcon::ActivationReason reason); + + KDbusImageStruct imageToStruct(const QImage &image); + KDbusImageVector iconToVector(const QIcon &icon); + bool checkVisibility(QPoint pos, bool perform = true); + + static const int s_protocolVersion; + + KStatusNotifierItem *q; + + KStatusNotifierItem::ItemCategory category; + QString id; + QString title; + KStatusNotifierItem::ItemStatus status; + + QString iconName; + KDbusImageVector serializedIcon; + QIcon icon; + + QString overlayIconName; + KDbusImageVector serializedOverlayIcon; + QIcon overlayIcon; + + QString attentionIconName; + QIcon attentionIcon; + KDbusImageVector serializedAttentionIcon; + QString movieName; + QPointer movie; + + QString toolTipIconName; + KDbusImageVector serializedToolTipIcon; + QIcon toolTipIcon; + QString toolTipTitle; + QString toolTipSubTitle; + QString iconThemePath; + QString menuObjectPath; + + QMenu *menu; + QHash actionCollection; + QWidget *associatedWidget; + QPoint associatedWidgetPos; + QAction *titleAction; + org::kde::StatusNotifierWatcher *statusNotifierWatcher; + org::freedesktop::Notifications *notificationsClient; + + KStatusNotifierLegacyIcon *systemTrayIcon; + KStatusNotifierItemDBus *statusNotifierItemDBus; +}; + +#endif diff --git a/src/gui/kstatusnotifier/org.freedesktop.Notifications.xml b/src/gui/kstatusnotifier/org.freedesktop.Notifications.xml new file mode 100644 index 00000000..62345f2b --- /dev/null +++ b/src/gui/kstatusnotifier/org.freedesktop.Notifications.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/gui/kstatusnotifier/org.kde.StatusNotifierItem.xml b/src/gui/kstatusnotifier/org.kde.StatusNotifierItem.xml new file mode 100644 index 00000000..7866a746 --- /dev/null +++ b/src/gui/kstatusnotifier/org.kde.StatusNotifierItem.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/gui/kstatusnotifier/org.kde.StatusNotifierWatcher.xml b/src/gui/kstatusnotifier/org.kde.StatusNotifierWatcher.xml new file mode 100644 index 00000000..2eb1a7a0 --- /dev/null +++ b/src/gui/kstatusnotifier/org.kde.StatusNotifierWatcher.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index d16f9877..0da7c8e7 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -31,36 +31,6 @@ int MainWindow::signalHandlerFd[2] = {0, 0}; MainWindow* MainWindow::mainWindow = 0; -#ifdef USE_LIBAPPINDICATOR -extern "C" { - void quitIndicator(GtkMenu* menu, gpointer data) { - Q_UNUSED(menu); - MainWindow* window = static_cast(data); - window->quitApp(); - } - - void restoreIndicator(GtkMenu* menu, gpointer data) { - Q_UNUSED(menu); - MainWindow* window = static_cast(data); - window->showWindow(); - } - - void indicatorScroll(AppIndicator* indicator, guint steps, GdkScrollDirection direction, gpointer data) { - MainWindow* window = static_cast(data); - switch(direction) { - case GDK_SCROLL_UP: - emit window->trayIconScrolled(true); - break; - case GDK_SCROLL_DOWN: - emit window->trayIconScrolled(false); - break; - default: - break; - } - } -} -#endif - #if defined(Q_OS_MACOS) && !defined(OS_MAC_LEGACY) bool is_catalina_or_higher(){ // Get macOS version. If Catalina or higher, start the daemon agent as the current user to request for HID permission. @@ -161,60 +131,15 @@ MainWindow::MainWindow(QWidget *parent) : restoreAction = new QAction(tr("Restore"), this); closeAction = new QAction(tr("Quit"), this); changeTrayIconAction = new QAction(tr("Monochrome Tray Icon"), this); - -#ifdef USE_LIBAPPINDICATOR - QProcessEnvironment procEnv = QProcessEnvironment::systemEnvironment(); - - QString desktop = procEnv.value("XDG_CURRENT_DESKTOP", QString("")).toLower(); - QString qpaTheme = procEnv.value("QT_QPA_PLATFORMTHEME", QString("")).toLower(); - QString ckbnextAppindicator = procEnv.value("CKB_NEXT_USE_APPINDICATOR", QString("")).toLower(); - QStringList appIndicatorOn = QStringList() << "yes" << "on" << "1"; - QStringList appIndicatorOff = QStringList() << "no" << "off" << "0"; - - useAppindicator = false; - trayIcon = 0; - - if(((desktop == "unity" || qpaTheme == "appmenu-qt5" || qpaTheme == "gtk2") && !appIndicatorOff.contains(ckbnextAppindicator)) || appIndicatorOn.contains(ckbnextAppindicator)){ - qDebug() << "Using AppIndicator"; - useAppindicator = true; - - indicatorMenu = gtk_menu_new(); - indicatorMenuRestoreItem = gtk_menu_item_new_with_label("Restore"); - indicatorMenuQuitItem = gtk_menu_item_new_with_label("Quit"); - - gtk_menu_shell_append(GTK_MENU_SHELL(indicatorMenu), indicatorMenuRestoreItem); - gtk_menu_shell_append(GTK_MENU_SHELL(indicatorMenu), indicatorMenuQuitItem); - - g_signal_connect(indicatorMenuQuitItem, "activate", - G_CALLBACK(quitIndicator), this); - g_signal_connect(indicatorMenuRestoreItem, "activate", - G_CALLBACK(restoreIndicator), this); - - gtk_widget_show(indicatorMenuRestoreItem); - gtk_widget_show(indicatorMenuQuitItem); - - indicator = app_indicator_new("ckb-next", "indicator-messages", APP_INDICATOR_CATEGORY_APPLICATION_STATUS); - - app_indicator_set_status(indicator, APP_INDICATOR_STATUS_ACTIVE); - app_indicator_set_menu(indicator, GTK_MENU(indicatorMenu)); - app_indicator_set_icon(indicator, "ckb-next"); - - // Catch scroll events - g_signal_connect(indicator, "scroll-event", G_CALLBACK(indicatorScroll), this); - } else -#endif - { - qDebug() << "Using QSytemTrayIcon"; - trayIconMenu = new QMenu(this); - trayIconMenu->addAction(changeTrayIconAction); - trayIconMenu->addAction(restoreAction); - trayIconMenu->addAction(closeAction); - trayIcon = new CkbSystemTrayIcon(getIcon(), this); - trayIcon->setContextMenu(trayIconMenu); - trayIcon->show(); - connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(iconClicked(QSystemTrayIcon::ActivationReason))); - connect(trayIcon, &CkbSystemTrayIcon::wheelScrolled, this, &MainWindow::handleTrayScrollEvt); - } + trayIconMenu = new QMenu(this); + trayIconMenu->addAction(changeTrayIconAction); + trayIconMenu->addAction(restoreAction); + trayIconMenu->addAction(closeAction); + trayIcon = new CkbSystemTrayIcon(getIcon(), this); + trayIcon->setContextMenu(trayIconMenu); + trayIcon->show(); + connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(iconClicked(QSystemTrayIcon::ActivationReason))); + connect(trayIcon, &CkbSystemTrayIcon::scrollRequested, this, &MainWindow::handleTrayScrollEvt); toggleTrayIcon(!CkbSettings::get("Program/SuppressTrayIcon").toBool()); #ifdef Q_OS_MACOS @@ -307,7 +232,8 @@ MainWindow::MainWindow(QWidget *parent) : #endif } -void MainWindow::handleTrayScrollEvt(bool up){ +void MainWindow::handleTrayScrollEvt(int delta, Qt::Orientation orientation){ + bool up = delta > 0; emit trayIconScrolled(up); } @@ -320,12 +246,7 @@ void MainWindow::checkForCkbUpdates(){ } void MainWindow::toggleTrayIcon(bool visible){ -#ifdef USE_LIBAPPINDICATOR - if(useAppindicator) - app_indicator_set_status(indicator, visible ? APP_INDICATOR_STATUS_ACTIVE : APP_INDICATOR_STATUS_PASSIVE); - else -#endif - trayIcon->setVisible(visible); + trayIcon->setVisible(visible); } void MainWindow::addDevice(Kb* device){ diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index b9021420..c668d5ba 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -15,17 +15,6 @@ #include "ckbupdater.h" #endif -#ifdef USE_LIBAPPINDICATOR -// 'signals' has to be undefined as GTK has its own signal mechanism -#undef signals -extern "C" { - #include - #include -} -// Redefine QT signals as per qtbase/src/corelib/kernel/qobjectdefs.h -#define signals Q_SIGNALS -#endif - namespace Ui { class MainWindow; } @@ -51,15 +40,6 @@ class MainWindow : public QMainWindow QAction* restoreAction; QAction* closeAction; QAction* changeTrayIconAction; - -#ifdef USE_LIBAPPINDICATOR - bool useAppindicator; - AppIndicator* indicator; - GtkWidget* indicatorMenu; - GtkWidget* indicatorMenuQuitItem; - GtkWidget* indicatorMenuRestoreItem; -#endif - QMenu* trayIconMenu; CkbSystemTrayIcon* trayIcon; void closeEvent(QCloseEvent *event); @@ -84,7 +64,7 @@ public slots: void checkForCkbUpdates(); void changeTrayIconToMonochrome(); void changeTrayIconToRGB(); - void handleTrayScrollEvt(bool up); + void handleTrayScrollEvt(int delta, Qt::Orientation orientation); signals: void switchToProfileCLI(QString profile); @@ -96,7 +76,6 @@ private slots: void updateVersion(); void checkFwUpdates(); void timerTick(); - void iconClicked(QSystemTrayIcon::ActivationReason reason); void cleanup(); void showFwUpdateNotification(QWidget* widget, float version); void QSignalHandler(); @@ -104,7 +83,7 @@ private slots: #if defined(Q_OS_MACOS) && !defined(OS_MAC_LEGACY) void appleRequestHidTimer(); #endif - + void iconClicked(QSystemTrayIcon::ActivationReason reason); private: Ui::MainWindow *ui; QSocketNotifier* sigNotifier;