--- /dev/null
+#clang-format/tidy
+964b4dae478d9b7a03949718f18afa002286f433
+9b987f0e87a88896d549249577cce51ceb13a543
+e2abd678e4fc38552d3360759e4e47b02de0bf9d
+07a6b88bbd4c74d0bae94111770ba5020def0fef
--- /dev/null
+.debug
+tags
+_tests
+.videproject/ctags
+*swp
+*~
+.kdev4
+.cmake-params
+.ycm_extra_conf.py
+.ycm_extra_conf.pyc
+.clang_complete
+kactivities.kdev4
+compile_commands.json
+/apidocs
+GPATH
+GRTAGS
+GSYMS
+GTAGS
+.cmake/
+/.clang-format
+/build*
+.idea
+.clangd
+/cmake-build*
+.cache
--- /dev/null
+# SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
+# SPDX-License-Identifier: CC0-1.0
+
+include:
+ - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
+ - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
+ - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
+ - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml
--- /dev/null
+Dependencies:
+- 'on': ['Linux', 'FreeBSD', 'Windows', 'macOS']
+ 'require':
+ 'frameworks/extra-cmake-modules': '@same'
+ 'frameworks/kconfig' : '@same'
+ 'frameworks/kwindowsystem' : '@same'
+ 'frameworks/kcoreaddons' : '@same'
+
+Options:
+ test-before-installing: True
+ require-passing-tests-on: [ 'Linux', 'FreeBSD' ]
--- /dev/null
+[Project]
+Name=KDE Activities
+Location=/opt/kde/src/libs/kactivities
+
+MakeCommand='OBJ_REPLACEMENT="s:/opt/kde/src/core/libs/kactivities:/opt/kde/build-clang/core/libs/kactivities:" makeobj'
+
+RunLocation=./
+RunCommand=./
+
+[General]
+OverrideVimCommands=1
+AutoInsertModeOnOpen=0
+Tags=
+SourcePaths=.
+
+[KeyBindings]
+SidePanelOpen='<C-A>'
+MakeProject='<F5>'
+RunProject='<F6>'
+
+[Grep]
+Root=.
+
+[Find]
+Root=.
+
+[QuickBrowser]
+iNotifyEnabled=0
+Root=.
+TagsSystem=ctags
--- /dev/null
+
+set tags+=/opt/kde4trunk/ctags/libQt.tags
--- /dev/null
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+
+cmake_minimum_required(VERSION 3.16)
+
+# KDE Frameworks
+set(KF_VERSION "5.107.0") # handled by release scripts
+set(KF_DEP_VERSION "5.107.0") # handled by release scripts
+project (KActivities VERSION ${KF_VERSION})
+
+option (KACTIVITIES_LIBRARY_ONLY "If true, compiles only the KActivities library, without the QML imports." OFF)
+option (KACTIVITIES_ENABLE_EXCEPTIONS "If you have Boost 1.53, you need to build KActivities with exceptions enabled. This is UNTESTED and EXPERIMENTAL!" OFF)
+
+set (REQUIRED_QT_VERSION 5.15.2)
+
+# We don't build in-source
+if ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
+ message (
+ FATAL_ERROR
+ "kactivities require an out of source build. Please create a separate build directory and run 'cmake path_to_sources [options]' there."
+ )
+endif ()
+
+set (KACTIVITIES_CURRENT_ROOT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+
+# Extra CMake stuff
+include(FeatureSummary)
+find_package(ECM 5.107.0 NO_MODULE)
+set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
+feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
+
+set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH})
+
+include (KDEInstallDirs)
+include (KDECMakeSettings)
+include(KDEGitCommitHooks)
+include (KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
+include (GenerateExportHeader)
+include (ECMGenerateHeaders)
+include (ECMGeneratePkgConfigFile)
+include (ECMQtDeclareLoggingCategory)
+include (ECMAddQch)
+include (ECMMarkNonGuiExecutable)
+include(ECMDeprecationSettings)
+include (ECMQmlModule)
+
+option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF)
+add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)")
+
+# Qt
+set (CMAKE_AUTOMOC ON)
+find_package (Qt${QT_MAJOR_VERSION} ${REQUIRED_QT_VERSION} CONFIG REQUIRED COMPONENTS Core DBus)
+
+# Basic includes
+include (CPack)
+
+include (CMakePackageConfigHelpers)
+include (ECMSetupVersion)
+
+message ("We are using the ${CMAKE_CXX_COMPILER_ID} compiler")
+if ((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") OR (KACTIVITIES_OVERRIDE_VISIBILITY STREQUAL "default"))
+ message ("Setting visibility preset to default")
+ set(CMAKE_CXX_VISIBILITY_PRESET default)
+ set(CMAKE_VISIBILITY_INLINES_HIDDEN 0)
+ string (REPLACE "-fvisibility-inlines-hidden" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+ string (REPLACE "-fvisibility=hidden" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+endif ()
+
+# libKActivities
+
+ecm_setup_version (
+ PROJECT
+ VARIABLE_PREFIX KACTIVITIES
+ VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kactivities_version.h"
+ PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5ActivitiesConfigVersion.cmake"
+ SOVERSION 5
+ )
+
+ecm_set_disabled_deprecation_versions(
+ QT 5.15.2
+ KF 5.97.0
+)
+
+add_subdirectory (src)
+if (BUILD_TESTING)
+ add_subdirectory (autotests)
+ add_subdirectory (tests)
+endif()
+
+set (CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5Activities")
+
+if (BUILD_QCH)
+ ecm_install_qch_export(
+ TARGETS KF5Activities_QCH
+ FILE KF5ActivitiesLibraryQchTargets.cmake
+ DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
+ COMPONENT Devel
+ )
+ set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5ActivitiesLibraryQchTargets.cmake\")")
+endif()
+
+install (
+ EXPORT KF5ActivitiesLibraryTargets
+ DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
+ FILE KF5ActivitiesLibraryTargets.cmake
+ NAMESPACE KF5::
+ )
+
+configure_package_config_file (
+ "${CMAKE_CURRENT_SOURCE_DIR}/KF5ActivitiesConfig.cmake.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/KF5ActivitiesConfig.cmake"
+ INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
+ )
+
+install (
+ FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5ActivitiesConfig.cmake"
+ "${CMAKE_CURRENT_BINARY_DIR}/KF5ActivitiesConfigVersion.cmake"
+ DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
+ COMPONENT Devel
+ )
+
+install (
+ FILES ${CMAKE_CURRENT_BINARY_DIR}/kactivities_version.h
+ DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KActivities COMPONENT Devel
+ )
+
+# Write out the features
+feature_summary (WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
+
+
+kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
--- /dev/null
+@PACKAGE_INIT@
+
+include(CMakeFindDependencyMacro)
+find_dependency(Qt@QT_MAJOR_VERSION@Core @REQUIRED_QT_VERSION@)
+
+include("${CMAKE_CURRENT_LIST_DIR}/KF5ActivitiesLibraryTargets.cmake")
+@PACKAGE_INCLUDE_QCHTARGETS@
--- /dev/null
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
--- /dev/null
+GNU GENERAL PUBLIC LICENSE
+
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+
+51 Franklin Street, 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.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public License is intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users. This General Public License applies to
+most of the Free Software Foundation's software and to any other program whose
+authors commit to using it. (Some other Free Software Foundation software
+is covered by the GNU Lesser General Public License instead.) You can apply
+it to your programs, too.
+
+When we speak of free software, we are referring to freedom, 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 or use pieces of it in new free programs; and that
+you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to
+deny you these rights or to ask you to surrender the rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of
+the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or
+for a fee, you must give the recipients all the rights that you have. You
+must make sure that they, too, receive or can get the source code. And you
+must show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2)
+offer you this license which gives you legal permission to copy, distribute
+and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If
+the software is modified by someone else and passed on, we want its recipients
+to know that what they have is not the original, so that any problems introduced
+by others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We
+wish to avoid the danger that redistributors of a free program will individually
+obtain patent licenses, in effect making the program proprietary. To prevent
+this, we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms
+of this General Public License. The "Program", below, refers to any such program
+or work, and a "work based on the Program" means either the Program or any
+derivative work under copyright law: that is to say, a work containing the
+Program or a portion of it, either verbatim or with modifications and/or translated
+into another language. (Hereinafter, translation is included without limitation
+in the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not covered
+by this License; they are outside its scope. The act of running the Program
+is not restricted, and the output from the Program is covered only if its
+contents constitute a work based on the Program (independent of having been
+made by running the Program). Whether that is true depends on what the Program
+does.
+
+1. You may copy and distribute verbatim copies of the Program's 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 give any other recipients of the Program a copy of this
+License along with the Program.
+
+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 Program or any portion of it,
+thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices stating that
+you changed the files and the date of any change.
+
+b) You must cause any work that you distribute or publish, that in whole or
+in part contains or is derived from the Program or any part thereof, to be
+licensed as a whole at no charge to all third parties under the terms of this
+License.
+
+c) If the modified program normally reads commands interactively when run,
+you must cause it, when started running for such interactive use in the most
+ordinary way, to print or display an announcement including an appropriate
+copyright notice and a notice that there is no warranty (or else, saying that
+you provide a warranty) and that users may redistribute the program under
+these conditions, and telling the user how to view a copy of this License.
+(Exception: if the Program itself is interactive but does not normally print
+such an announcement, your work based on the Program is not required to print
+an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, 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 Program, 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 Program.
+
+In addition, mere aggregation of another work not based on the Program with
+the Program (or with a work based on the Program) on a volume of a storage
+or distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may copy and distribute the Program (or a work based on it, under Section
+2) in object code or executable form under the terms of Sections 1 and 2 above
+provided that you also do one of the following:
+
+a) 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; or,
+
+b) Accompany it with a written offer, valid for at least three years, to give
+any third party, for a charge no more than your cost of physically performing
+source distribution, a complete machine-readable copy of the corresponding
+source code, to be distributed under the terms of Sections 1 and 2 above on
+a medium customarily used for software interchange; or,
+
+c) Accompany it with the information you received as to the offer to distribute
+corresponding source code. (This alternative is allowed only for noncommercial
+distribution and only if you received the program in object code or executable
+form with such an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it. For an executable work, 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 executable. However, as a special exception, the source code 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.
+
+If distribution of executable or 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 counts as distribution of the source code,
+even though third parties are not compelled to copy the source along with
+the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except
+as expressly provided under this License. Any attempt otherwise to copy, modify,
+sublicense or distribute the Program 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.
+
+5. 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
+Program or its derivative works. These actions are prohibited by law if you
+do not accept this License. Therefore, by modifying or distributing the Program
+(or any work based on the Program), you indicate your acceptance of this License
+to do so, and all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program),
+the recipient automatically receives a license from the original licensor
+to copy, distribute or modify the Program 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 to this License.
+
+7. 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 Program at all. For example, if a
+patent license would not permit royalty-free redistribution of the Program
+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 Program.
+
+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.
+
+8. If the distribution and/or use of the Program is restricted in certain
+countries either by patents or by copyrighted interfaces, the original copyright
+holder who places the Program 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.
+
+9. The Free Software Foundation may publish revised and/or new versions of
+the 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 Program 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
+Program does not specify a version number of this License, you may choose
+any version ever published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, 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
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
+THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
+STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
+"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 PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 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 Programs
+
+If you develop a new program, and you want it to be of the greatest possible
+use to the public, the best way to achieve this is to make it free software
+which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program. 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.
+
+<one line to give the program's name and an idea of what it does.>
+
+Copyright (C) <yyyy> <name of author>
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; either version 2 of the License, or (at your option) any later
+version.
+
+This program 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this when
+it starts in an interactive mode:
+
+Gnomovision version 69, Copyright (C) year name of author Gnomovision comes
+with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software,
+and you are welcome to redistribute it under certain conditions; type `show
+c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may be
+called something other than `show w' and `show c'; they could even be mouse-clicks
+or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your school,
+if any, to sign a "copyright disclaimer" for the program, if necessary. Here
+is a sample; alter the names:
+
+Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision'
+(which makes passes at compilers) written by James Hacker.
+
+<signature of Ty Coon>, 1 April 1989 Ty Coon, President of Vice This General
+Public License does not permit incorporating your program into proprietary
+programs. If your program is a subroutine library, you may consider it more
+useful to permit linking proprietary applications with the library. If this
+is what you want to do, use the GNU Lesser General Public License instead
+of this License.
--- /dev/null
+GNU LIBRARY GENERAL PUBLIC LICENSE
+
+Version 2, June 1991 Copyright (C) 1991 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 library GPL. It is numbered 2 because
+it goes with version 2 of the ordinary GPL.]
+
+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 Library General Public License, applies to some specially
+designated Free Software Foundation software, and to any other libraries whose
+authors decide to use it. You can use it for your libraries, too.
+
+When we speak of free software, we are referring to freedom, 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 or use pieces of it in new free programs; and that
+you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to
+deny you these rights or to ask you to surrender the 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
+a program 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.
+
+Our method of protecting your rights has two steps: (1) copyright the library,
+and (2) offer you this license which gives you legal permission to copy, distribute
+and/or modify the library.
+
+Also, for each distributor's protection, we want to make certain that everyone
+understands that there is no warranty for this free library. If the library
+is modified by someone else and passed on, we want its recipients to know
+that what they have is not the original version, so that any problems introduced
+by others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We
+wish to avoid the danger that companies distributing free software will individually
+obtain patent licenses, thus in effect transforming the program into proprietary
+software. To prevent this, we have made it clear that any patent must be licensed
+for everyone's free use or not licensed at all.
+
+Most GNU software, including some libraries, is covered by the ordinary GNU
+General Public License, which was designed for utility programs. This license,
+the GNU Library General Public License, applies to certain designated libraries.
+This license is quite different from the ordinary one; be sure to read it
+in full, and don't assume that anything in it is the same as in the ordinary
+license.
+
+The reason we have a separate public license for some libraries is that they
+blur the distinction we usually make between modifying or adding to a program
+and simply using it. Linking a program with a library, without changing the
+library, is in some sense simply using the library, and is analogous to running
+a utility program or application program. However, in a textual and legal
+sense, the linked executable is a combined work, a derivative of the original
+library, and the ordinary General Public License treats it as such.
+
+Because of this blurred distinction, using the ordinary General Public License
+for libraries did not effectively promote software sharing, because most developers
+did not use the libraries. We concluded that weaker conditions might promote
+sharing better.
+
+However, unrestricted linking of non-free programs would deprive the users
+of those programs of all benefit from the free status of the libraries themselves.
+This Library General Public License is intended to permit developers of non-free
+programs to use free libraries, while preserving your freedom as a user of
+such programs to change the free libraries that are incorporated in them.
+(We have not seen how to achieve this as regards changes in header files,
+but we have achieved it as regards changes in the actual functions of the
+Library.) The hope is that this will lead to faster development of free libraries.
+
+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, while the latter only works together with the library.
+
+Note that it is possible for a library to be covered by the ordinary General
+Public License rather than by this special one.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License Agreement applies to any software library which contains a
+notice placed by the copyright holder or other authorized party saying it
+may be distributed under the terms of this Library 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 compile 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) 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.
+
+c) 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.
+
+d) 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 source code 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 to 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 Library 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.
+
+one line to give the library's name and an idea of what it does.
+
+Copyright (C) year name of author
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Library General Public License 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; 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.
+
+signature of Ty Coon, 1 April 1990
+
+Ty Coon, President of Vice
+
+That's all there is to it!
--- /dev/null
+GNU LESSER GENERAL PUBLIC LICENSE
+
+Version 2.1, February 1999
+
+Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+
+51 Franklin Street, 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.
+
+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.
+
+< one line to give the library's name and an idea of what it does. >
+
+Copyright (C) < year > < name of author >
+
+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 Street, 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.
+
+< signature of Ty Coon > , 1 April 1990
+
+Ty Coon, President of Vice
+
+That's all there is to it!
--- /dev/null
+GNU LESSER GENERAL PUBLIC LICENSE
+
+Version 3, 29 June 2007
+
+Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
+
+This version of the GNU Lesser General Public License incorporates the terms
+and conditions of version 3 of the GNU General Public License, supplemented
+by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+
+
+As used herein, "this License" refers to version 3 of the GNU Lesser General
+Public License, and the "GNU GPL" refers to version 3 of the GNU General Public
+License.
+
+
+
+"The Library" refers to a covered work governed by this License, other than
+an Application or a Combined Work as defined below.
+
+
+
+An "Application" is any work that makes use of an interface provided by the
+Library, but which is not otherwise based on the Library. Defining a subclass
+of a class defined by the Library is deemed a mode of using an interface provided
+by the Library.
+
+
+
+A "Combined Work" is a work produced by combining or linking an Application
+with the Library. The particular version of the Library with which the Combined
+Work was made is also called the "Linked Version".
+
+
+
+The "Minimal Corresponding Source" for a Combined Work means the Corresponding
+Source for the Combined Work, excluding any source code for portions of the
+Combined Work that, considered in isolation, are based on the Application,
+and not on the Linked Version.
+
+
+
+The "Corresponding Application Code" for a Combined Work means the object
+code and/or source code for the Application, including any data and utility
+programs needed for reproducing the Combined Work from the Application, but
+excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+You may convey a covered work under sections 3 and 4 of this License without
+being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+If you modify a copy of the Library, and, in your modifications, a facility
+refers to a function or data to be supplied by an Application that uses the
+facility (other than as an argument passed when the facility is invoked),
+then you may convey a copy of the modified version:
+
+a) under this License, provided that you make a good faith effort to ensure
+that, in the event an Application does not supply the function or data, the
+facility still operates, and performs whatever part of its purpose remains
+meaningful, or
+
+b) under the GNU GPL, with none of the additional permissions of this License
+applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+The object code form of an Application may incorporate material from a header
+file that is part of the Library. You may convey such object code under terms
+of your choice, provided that, if the incorporated material is not limited
+to numerical parameters, data structure layouts and accessors, or small macros,
+inline functions and templates (ten or fewer lines in length), you do both
+of the following:
+
+a) Give prominent notice with each copy of the object code that the Library
+is used in it and that the Library and its use are covered by this License.
+
+b) Accompany the object code with a copy of the GNU GPL and this license document.
+
+ 4. Combined Works.
+
+You may convey a Combined Work under terms of your choice that, taken together,
+effectively do not restrict modification of the portions of the Library contained
+in the Combined Work and reverse engineering for debugging such modifications,
+if you also do each of the following:
+
+a) Give prominent notice with each copy of the Combined Work that the Library
+is used in it and that the Library and its use are covered by this License.
+
+b) Accompany the Combined Work with a copy of the GNU GPL and this license
+document.
+
+c) For a Combined Work that displays copyright notices during execution, include
+the copyright notice for the Library among these notices, as well as a reference
+directing the user to the copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+0) Convey the Minimal Corresponding Source under the terms of this License,
+and the Corresponding Application Code in a form suitable for, and under terms
+that permit, the user to recombine or relink the Application with a modified
+version of the Linked Version to produce a modified Combined Work, in the
+manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
+
+1) Use a suitable shared library mechanism for linking with the Library. A
+suitable mechanism is one that (a) uses at run time a copy of the Library
+already present on the user's computer system, and (b) will operate properly
+with a modified version of the Library that is interface-compatible with the
+Linked Version.
+
+e) Provide Installation Information, but only if you would otherwise be required
+to provide such information under section 6 of the GNU GPL, and only to the
+extent that such information is necessary to install and execute a modified
+version of the Combined Work produced by recombining or relinking the Application
+with a modified version of the Linked Version. (If you use option 4d0, the
+Installation Information must accompany the Minimal Corresponding Source and
+Corresponding Application Code. If you use option 4d1, you must provide the
+Installation Information in the manner specified by section 6 of the GNU GPL
+for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+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 that are
+not Applications and are not covered by this License, and convey such a combined
+library under terms of your choice, if you do both of the following:
+
+a) Accompany the combined library with a copy of the same work based on the
+Library, uncombined with any other library facilities, conveyed under the
+terms of this License.
+
+b) Give prominent notice with the combined library that part of it is a work
+based on the Library, and explaining where to find the accompanying uncombined
+form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+The Free Software Foundation may publish revised and/or new versions of the
+GNU 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 as you
+received it specifies that a certain numbered version of the GNU Lesser General
+Public License "or any later version" applies to it, you have the option of
+following the terms and conditions either of that published version or of
+any later version published by the Free Software Foundation. If the Library
+as you received it does not specify a version number of the GNU Lesser General
+Public License, you may choose any version of the GNU Lesser General Public
+License ever published by the Free Software Foundation.
+
+If the Library as you received it specifies that a proxy can decide whether
+future versions of the GNU Lesser General Public License shall apply, that
+proxy's public statement of acceptance of any version is permanent authorization
+for you to choose that version for the Library.
--- /dev/null
+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 3 of the license or (at your option) any later version
+that is accepted by the membership of KDE e.V. (or its successor
+approved by the membership of KDE e.V.), which shall act as a
+proxy as defined in Section 6 of version 3 of the license.
+
+This program 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 General Public License for more details.
--- /dev/null
+MIT License Copyright (c) <year> <copyright holders>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice (including the next
+paragraph) shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- /dev/null
+
+Current kactivities maintainer is: Ivan Čukić <ivan.cukic@kde.org>
+
--- /dev/null
+See README.developers and README.packagers (the former is also for users).
+
+In order to properly display the files, use the GNU man command.
+
+man README.developers
+man README.packagers
--- /dev/null
+
+
+.\" " " " " " " " " " " " " " " " " " " " " " " " " " " "
+.\" "
+.\" It is best to view this file with the man tool: "
+.\" man ./README.developers "
+.\" "
+.\" " " " " " " " " " " " " " " " " " " " " " " " " " " "
+
+
+.TH README KAMD 2012-08-29 "KDE" "KActivities Developers"
+
+.SH COMMIT POLICY
+
+Every non-trivial patch must go through the review before it goes into the
+master branch.
+
+ http://git.reviewboard.kde.org
+ repository: kactivities
+ groups: plasma
+ people: Ivan Cukic
+
+If you don't have an account for identity.kde.org, you can send smaller
+patches to the plasma-devel@kde.org mailing list, or (please don't) directly
+to the repository maintainer (see MAINTAINER file).
+
+
+.SH CODE POLICY
+
+The code needs to follow KDElibs coding style in *all* parts of the project,
+not only the library. You can find more information about the style here:
+http://techbase.kde.org/Policies/Kdelibs_Coding_Style
+
+Macros in CMakeLists.txt should be lowercase throughout the project,
+and indentation should be 4, with the closing parenthesis in the same level
+as the content.
+
+.SH COMPILER COMPATIBILITY
+
+The library (src/lib) needs to be compilable with these:
+ - GCC 4.5
+ - MSVC 2010
+ - Clang 3.1
+ (or newer)
+This is the same policy the KDE Frameworks have.
+
+Other parts require modern compilers. You can (and should) use more modern
+C++ coding practices. Including auto, lambdas, smart pointers etc. You can
+use anything that GCC 4.7 can compile.
+
+These are the compilers you need to test your patches against:
+ - GCC 4.7
+ - LLVM/Clang 3.1
+
+When you set up different builds alongside the main one, you can use
+scripts/commit.sh to build them all before committing. The script
+calls git commit if all builds finished successfully. See the script
+for more info.
+
+
+.SH FILE NAMING
+
+The library files are lower-case, apart from the "pretty" headers.
+The service, and the rest of the repository should be in camel-case
+(with the exception of source files that don't have corresponding
+headers, or vice-versa).
+
+
+.SH CONVENIENCE MACROS AND METHODS
+
+There are some convenience macros and methods defined in the headers placed
+in the service/utils/ directory.
+
+.TP
+.B D_PTR
+d_ptr.h and d_ptr_implementation.h define a smart pointer way of doing
+the d-ptr (aka pimpl) idiom.
+
+.TP
+.B remove_if
+remove_if.h is a generic implementation of the erase-remove idiom
+
+.TP
+.B for_each_assoc, find_if_assoc
+for_each_assoc.h and find_if_assoc.h define the for_each and find_if
+algorithms for associative containers. Works with both Qt and STL containers.
+
--- /dev/null
+# KActivities
+
+Core components for the KDE Activity concept
+
+## Introduction
+
+When a user is interacting with a computer, there are three main areas of
+contextual information that may affect the behaviour of the system: who the user
+is, where they are, and what they are doing.
+
+*Activities* deal with the last one. An activity might be "developing a KDE
+application", "studying 19th century art", "composing music" or "watching funny
+videos". Each of these activities may involve multiple applications, and a single
+application may be used in multiple activities (for example, most activities are
+likely to involve using a web browser, but different activities will probably
+involve different websites).
+
+KActivities provides the infrastructure needed to manage a user's activities,
+allowing them to switch between tasks, and for applications to update their
+state to match the user's current activity. This includes a daemon, a library
+for interacting with that daemon, and plugins for integration with other
+frameworks.
+
+## Usage
+
+Most applications that wish to be activity-aware will want to use
+KActivities::Consumer to keep track of the user's current activity, and
+KActivities::ResourceInstance to notify the activity manager of resources the
+user has accessed (this is not necessary for resources accessed via KIO, as a
+plugin is provided to do that automatically).
+
+The other classes available in the API are primarily intended for use by the
+workspace to allow the user to view and manage available activities.
--- /dev/null
+
+
+.\" " " " " " " " " " " " " " " " " " " " " " " " " " " "
+.\" "
+.\" It is best to view this file with the man tool: "
+.\" man ./README.packagers "
+.\" "
+.\" " " " " " " " " " " " " " " " " " " " " " " " " " " "
+
+
+.\" Best to view this file with the man tool
+.TH README KAMD 2012-08-29 "KDE" "KActivities Packagers"
+
+.SH LIBRARY
+
+The library can be used even if the service is not running nor installed.
+The dependencies are minimal since it is only a thin wrapper for the d-bus
+service.
+
+.SH SERVICE
+
+.TP
+
--- /dev/null
+src/lib/resourceinstance.cpp:149:
+ TODO: update the service info
+src/lib/resourceinstance.cpp:161:
+ TODO: update the service info
+
+src/imports/resourcemodel.cpp:103:
+ NOTE: What to do if the file does not exist?
+ Ignoring that case since the daemon creates it on startup.
+ Is it plausible that somebody will instantiate the ResourceModel
+ before the daemon is started?
+src/imports/resourcemodel.cpp:133:
+ TODO: Database connection naming could be smarter (thread-id-based,
+ reusing connections...?)
+src/imports/resourcemodel.cpp:335:
+ TODO: Will probably need some more special handling -
+ for application:/ and a few more
+src/imports/resourcemodel.cpp:551:
+ TODO: This might be smarter possibly, but might collide
+ with the SQL model. Implement a custom model with internal
+ cache instead of basing it on QSqlModel.
+
+src/imports/activitiesextensionplugin.cpp:30:
+ TODO: Clean up unused classes from the imports module
+src/imports/activitiesextensionplugin.cpp:32:
+ TODO: Since plasma is now dealing with activity model wallpapers,
+ replace ActivityModel with the KActivities::ActivitiesModel
+ (but keep the name)
+
--- /dev/null
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+
+string (REPLACE "-fno-exceptions" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+add_definitions (-fexceptions)
+
+add_subdirectory (core)
+
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2013, 2014, 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "test.h"
+
+#include <QDBusConnection>
+#include <QDBusConnectionInterface>
+
+#include "common/dbus/common.h"
+
+Test::Test(QObject *parent)
+ : QObject(parent)
+{
+}
+
+bool Test::inEmptySession()
+{
+ const QStringList services = QDBusConnection::sessionBus().interface()->registeredServiceNames();
+
+ for (const QString &service : services) {
+ bool kdeServiceAndNotKAMD = service.startsWith(QLatin1String("org.kde")) && service != KAMD_DBUS_SERVICE;
+
+ if (kdeServiceAndNotKAMD) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool Test::isActivityManagerRunning()
+{
+ return QDBusConnection::sessionBus().interface()->isServiceRegistered(KAMD_DBUS_SERVICE);
+}
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2013, 2014, 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef COMMON_TEST_H
+#define COMMON_TEST_H
+
+#include <QCoreApplication>
+#include <QFuture>
+#include <QFutureWatcher>
+#include <QObject>
+#include <QTest>
+
+class Test : public QObject
+{
+ Q_OBJECT
+public:
+ Test(QObject *parent = nullptr);
+
+protected:
+ enum WhenToFail {
+ DontFail = 0,
+ FailIfTrue = 1,
+ FailIfFalse = 2,
+ };
+
+ template<typename _ReturnType, typename _Continuation>
+ void continue_future(const QFuture<_ReturnType> &future, _Continuation &&continuation)
+ {
+ if (!future.isFinished()) {
+ auto watcher = new QFutureWatcher<decltype(future.result())>();
+ QObject::connect(
+ watcher,
+ &QFutureWatcherBase::finished,
+ watcher,
+ [=] {
+ continuation(watcher->result());
+ watcher->deleteLater();
+ },
+ Qt::QueuedConnection);
+
+ watcher->setFuture(future);
+
+ } else {
+ continuation(future.result());
+ }
+ }
+
+ template<typename _Continuation>
+ void continue_future(const QFuture<void> &future, _Continuation &&continuation)
+ {
+ if (!future.isFinished()) {
+ auto watcher = new QFutureWatcher<void>();
+ QObject::connect(
+ watcher,
+ &QFutureWatcherBase::finished,
+ watcher,
+ [=] {
+ continuation();
+ watcher->deleteLater();
+ },
+ Qt::QueuedConnection);
+
+ watcher->setFuture(future);
+
+ } else {
+ continuation();
+ }
+ }
+
+ template<typename T>
+ static inline void wait_until(T condition, const char *msg, int msecs = 300)
+ {
+ auto start = QTime::currentTime();
+
+ while (!condition()) {
+ QCoreApplication::processEvents();
+
+ auto now = QTime::currentTime();
+ QVERIFY2(start.msecsTo(now) < msecs, msg);
+ if (start.msecsTo(now) >= msecs)
+ break;
+ }
+ }
+// clang-format off
+#define TEST_WAIT_UNTIL(C) \
+ wait_until([&] () -> bool { return C; }, "Timeout waiting for: " #C);
+#define TEST_WAIT_UNTIL_WITH_TIMEOUT(C, T) \
+ wait_until([&] () ->bool { return C; }, "Timeout waiting for: " #C, T);
+ // clang-format on
+
+ template<typename T>
+ static bool check(T what, WhenToFail wtf = DontFail, const char *msg = nullptr)
+ {
+ bool result = what();
+
+ if ((wtf == FailIfTrue && result) || (wtf == FailIfFalse && !result)) {
+ qFatal(
+ "\n"
+ "\n"
+ "!!! > \n"
+ "!!! > %s\n"
+ "!!! > \n",
+ msg);
+ }
+
+ return result;
+ }
+
+ static bool inEmptySession();
+ static bool isActivityManagerRunning();
+
+Q_SIGNALS:
+ void testFinished();
+};
+
+#define CHECK_CONDITION(A, B) check(A, B, #A " raised " #B)
+
+// Pretty print
+#include <iostream>
+
+#if defined(Q_NO_DEBUG) || !defined(Q_OS_LINUX)
+#define TEST_CHUNK(Name)
+#else
+inline void _test_chunk(const QString &message)
+{
+ std::cerr << '\n' << message.toStdString() << "\n" << std::string(message.length(), '-') << '\n';
+}
+#define TEST_CHUNK(Name) _test_chunk(Name);
+#endif
+
+#endif /* TEST_H */
--- /dev/null
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+project (KActivitiesTest)
+
+find_package (Qt${QT_MAJOR_VERSION} REQUIRED NO_MODULE COMPONENTS Test Core DBus)
+
+include_directories (
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/tests/
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/autotests/
+ ${CMAKE_BINARY_DIR}/src/
+ )
+
+set (
+ KActivitiesTest_SRCS
+ main.cpp
+ Process.cpp
+ OfflineTest.cpp
+ CleanOnlineTest.cpp
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/autotests/common/test.cpp
+ )
+
+if (NOT WIN32)
+
+ add_executable (
+ KActivitiesTest
+ ${KActivitiesTest_SRCS}
+ )
+
+ target_link_libraries (
+ KActivitiesTest
+ Qt${QT_MAJOR_VERSION}::Core
+ Qt${QT_MAJOR_VERSION}::Test
+ Qt${QT_MAJOR_VERSION}::DBus
+ KF5::Activities
+ )
+
+endif ()
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2013 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "CleanOnlineTest.h"
+
+#include <QDBusConnection>
+#include <QDBusConnectionInterface>
+#include <QDebug>
+#include <QString>
+#include <QTest>
+
+QString CleanOnlineSetup::id1;
+QString CleanOnlineSetup::id2;
+
+CleanOnlineTest::CleanOnlineTest(QObject *parent)
+ : Test(parent)
+{
+ activities.reset(new KActivities::Controller());
+}
+
+CleanOnlineSetup::CleanOnlineSetup(QObject *parent)
+ : Test(parent)
+{
+ activities.reset(new KActivities::Controller());
+}
+
+OnlineTest::OnlineTest(QObject *parent)
+ : Test(parent)
+{
+ activities.reset(new KActivities::Controller());
+}
+
+void CleanOnlineTest::testCleanOnlineActivityListing()
+{
+ // Waiting for the service to start, and for us to sync
+ TEST_WAIT_UNTIL(activities->serviceStatus() == KActivities::Consumer::Running);
+
+ QCOMPARE(activities->activities(), QStringList());
+ QCOMPARE(activities->serviceStatus(), KActivities::Consumer::Running);
+ QCOMPARE(activities->currentActivity(), QString());
+
+ QCOMPARE(activities->activities(), QStringList());
+ QCOMPARE(activities->activities(KActivities::Info::Running), QStringList());
+ QCOMPARE(activities->activities(KActivities::Info::Stopped), QStringList());
+}
+
+void CleanOnlineSetup::testCleanOnlineActivityControl()
+{
+ // Waiting for the service to start, and for us to sync
+ TEST_WAIT_UNTIL(activities->serviceStatus() == KActivities::Consumer::Running);
+
+ auto activity1 = activities->addActivity(QStringLiteral("The first one"));
+ TEST_WAIT_UNTIL(activity1.isFinished());
+ id1 = activity1.result();
+
+ auto activity2 = activities->addActivity(QStringLiteral("The second one"));
+ TEST_WAIT_UNTIL(activity2.isFinished());
+ id2 = activity2.result();
+
+ TEST_WAIT_UNTIL(activities->currentActivity() == id1);
+
+ auto f1 = activities->setCurrentActivity(activity2.result());
+ TEST_WAIT_UNTIL(f1.isFinished());
+ TEST_WAIT_UNTIL(activities->currentActivity() == id2);
+
+ auto f2 = activities->setCurrentActivity(id1);
+ TEST_WAIT_UNTIL(f2.isFinished());
+ TEST_WAIT_UNTIL(activities->currentActivity() == id1);
+
+ auto f3 = activities->setActivityName(id1, QStringLiteral("Renamed activity"));
+ TEST_WAIT_UNTIL(f3.isFinished());
+
+ KActivities::Info ac(id1);
+ TEST_WAIT_UNTIL(ac.name() == QLatin1String("Renamed activity"));
+
+ TEST_WAIT_UNTIL(activities->activities().size() == 2);
+}
+
+void OnlineTest::testOnlineActivityListing()
+{
+ // Waiting for the service to start, and for us to sync
+ TEST_WAIT_UNTIL(activities->serviceStatus() == KActivities::Consumer::Running);
+
+ KActivities::Info i1(CleanOnlineSetup::id1);
+ KActivities::Info i2(CleanOnlineSetup::id2);
+
+ TEST_WAIT_UNTIL(i1.name() == QLatin1String("Renamed activity"));
+ TEST_WAIT_UNTIL(i2.name() == QLatin1String("The second one"));
+
+ qDebug() << CleanOnlineSetup::id1 << i1.name();
+ qDebug() << CleanOnlineSetup::id2 << i2.name();
+}
+
+void CleanOnlineTest::cleanupTestCase()
+{
+ Q_EMIT testFinished();
+}
+
+void CleanOnlineSetup::cleanupTestCase()
+{
+ Q_EMIT testFinished();
+}
+
+void OnlineTest::cleanupTestCase()
+{
+ Q_EMIT testFinished();
+}
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2013 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef CLEANONLINETEST_H
+#define CLEANONLINETEST_H
+
+#include <common/test.h>
+
+#include <controller.h>
+
+#include <QScopedPointer>
+
+class CleanOnlineTest : public Test
+{
+ Q_OBJECT
+public:
+ CleanOnlineTest(QObject *parent = nullptr);
+
+private Q_SLOTS:
+ void testCleanOnlineActivityListing();
+
+ void cleanupTestCase();
+
+private:
+ QScopedPointer<KActivities::Controller> activities;
+ QString id1;
+ QString id2;
+};
+
+class CleanOnlineSetup : public Test
+{
+ Q_OBJECT
+public:
+ CleanOnlineSetup(QObject *parent = nullptr);
+
+private Q_SLOTS:
+ void testCleanOnlineActivityControl();
+
+ void cleanupTestCase();
+
+private:
+ QScopedPointer<KActivities::Controller> activities;
+
+public:
+ static QString id1;
+ static QString id2;
+};
+
+class OnlineTest : public Test
+{
+ Q_OBJECT
+public:
+ OnlineTest(QObject *parent = nullptr);
+
+private Q_SLOTS:
+ void testOnlineActivityListing();
+
+ void cleanupTestCase();
+
+private:
+ QScopedPointer<KActivities::Controller> activities;
+};
+
+#endif /* CLEANONLINETEST_H */
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2013 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "OfflineTest.h"
+
+#include <QDBusConnection>
+#include <QDBusConnectionInterface>
+#include <QString>
+#include <QTest>
+
+QString nulluuid = QStringLiteral("00000000-0000-0000-0000-000000000000");
+
+OfflineTest::OfflineTest(QObject *parent)
+ : Test(parent)
+{
+ QCoreApplication::instance()->setProperty("org.kde.KActivities.core.disableAutostart", true);
+
+ activities.reset(new KActivities::Controller());
+}
+
+void OfflineTest::testOfflineActivityListing()
+{
+ // The service is not running
+
+ TEST_WAIT_UNTIL(activities->serviceStatus() == KActivities::Consumer::NotRunning);
+ QCOMPARE(activities->currentActivity(), nulluuid);
+
+ QCOMPARE(activities->activities(), QStringList() << nulluuid);
+ QCOMPARE(activities->activities(KActivities::Info::Running), QStringList() << nulluuid);
+ QCOMPARE(activities->activities(KActivities::Info::Stopped), QStringList());
+}
+
+void OfflineTest::testOfflineActivityControl()
+{
+ continue_future(activities->addActivity(QStringLiteral("Activity")), [](const QString &newid) {
+ QCOMPARE(newid, QString());
+ });
+
+ continue_future(activities->setCurrentActivity(QStringLiteral("Activity")), [](bool success) {
+ QCOMPARE(success, false);
+ });
+
+ // Test whether the responses are immediate
+ static bool inMethod = false;
+
+ inMethod = true;
+
+ continue_future(activities->setActivityName(QStringLiteral("Activity"), QStringLiteral("Activity")), []() {
+ QCOMPARE(inMethod, true);
+ });
+ continue_future(activities->setActivityIcon(QStringLiteral("Activity"), QStringLiteral("Activity")), []() {
+ QCOMPARE(inMethod, true);
+ });
+ continue_future(activities->removeActivity(QStringLiteral("Activity")), []() {
+ QCOMPARE(inMethod, true);
+ });
+ continue_future(activities->startActivity(QStringLiteral("Activity")), []() {
+ QCOMPARE(inMethod, true);
+ });
+ continue_future(activities->stopActivity(QStringLiteral("Activity")), []() {
+ QCOMPARE(inMethod, true);
+ });
+
+ inMethod = false;
+}
+
+void OfflineTest::initTestCase()
+{
+ CHECK_CONDITION(inEmptySession, FailIfFalse);
+ CHECK_CONDITION(isActivityManagerRunning, FailIfTrue);
+}
+
+void OfflineTest::cleanupTestCase()
+{
+ Q_EMIT testFinished();
+}
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2013 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef OFFLINETEST_H
+#define OFFLINETEST_H
+
+#include <common/test.h>
+
+#include <controller.h>
+
+#include <QScopedPointer>
+
+class OfflineTest : public Test
+{
+ Q_OBJECT
+public:
+ OfflineTest(QObject *parent = nullptr);
+
+private Q_SLOTS:
+ void initTestCase();
+
+ void testOfflineActivityListing();
+ void testOfflineActivityControl();
+
+ void cleanupTestCase();
+
+private:
+ QScopedPointer<KActivities::Controller> activities;
+};
+
+#endif /* OFFLINETEST_H */
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2013 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "Process.h"
+
+#include <QDBusConnection>
+#include <QDBusConnectionInterface>
+#include <QDebug>
+#include <QProcess>
+#include <QRegularExpression>
+#include <QStandardPaths>
+#include <QString>
+#include <QTemporaryDir>
+#include <QTest>
+
+#include <signal.h>
+#include <sys/types.h>
+
+#include "common/dbus/common.h"
+
+namespace Process
+{
+QProcess *Modifier::s_process = nullptr;
+QTemporaryDir *Modifier::s_tempDir = nullptr;
+QString nulluuid = QStringLiteral("00000000-0000-0000-0000-000000000000");
+
+Modifier::Modifier(Action action)
+ : Test()
+ , m_action(action)
+{
+ if (!s_process) {
+ s_process = new QProcess();
+ s_tempDir = new QTemporaryDir();
+
+ if (!s_tempDir->isValid()) {
+ qFatal("Can not create a temporary dir");
+ }
+
+ qDebug() << "Running KAMD in " << s_tempDir->path();
+ s_process->setProcessChannelMode(QProcess::ForwardedChannels);
+ }
+}
+
+void Modifier::initTestCase()
+{
+ const auto state = s_process->state();
+
+ if (state != QProcess::NotRunning && m_action == Start) {
+ qFatal("Already running");
+ }
+
+ switch (m_action) {
+ case Start: {
+ qDebug() << "Starting...";
+
+ QRegularExpression nonxdg(QStringLiteral("^[^X][^D][^G].*$"));
+
+ auto env = QProcessEnvironment::systemEnvironment().toStringList().filter(nonxdg)
+ << QStringLiteral("XDG_DATA_HOME=") + s_tempDir->path() + QStringLiteral("/")
+ << QStringLiteral("XDG_CONFIG_HOME=") + s_tempDir->path() + QStringLiteral("/")
+ << QStringLiteral("XDG_CACHE_HOME=") + s_tempDir->path() + QStringLiteral("/");
+
+ // qDebug() << env;
+
+ s_process->setEnvironment(env);
+ const QString exec = QStandardPaths::findExecutable(QStringLiteral("kactivitymanagerd"));
+ QVERIFY(!exec.isEmpty());
+ s_process->start(exec, QStringList());
+ s_process->waitForStarted();
+
+ break;
+ }
+
+ case Stop:
+ case Kill:
+ case Crash: {
+ qDebug() << "Stopping...";
+
+ const auto dbus = QDBusConnection::sessionBus().interface();
+ const auto kamd = KAMD_DBUS_SERVICE;
+
+ if (!dbus->isServiceRegistered(kamd)) {
+ break;
+ }
+
+ uint pid = dbus->servicePid(kamd);
+ // clang-format off
+ ::kill(pid,
+ m_action == Stop ? SIGQUIT :
+ m_action == Kill ? SIGKILL :
+ /* else */ SIGSEGV
+ );
+ // clang-format on
+
+ while (Test::isActivityManagerRunning()) {
+ QCoreApplication::processEvents();
+ }
+
+ if (s_process->state() == QProcess::Running) {
+ s_process->terminate();
+ s_process->waitForFinished();
+ }
+
+ break;
+ }
+ }
+}
+
+void Modifier::testProcess()
+{
+ const auto state = s_process->state();
+
+ switch (m_action) {
+ case Start:
+ QCOMPARE(state, QProcess::Running);
+ break;
+
+ case Stop:
+ QCOMPARE(state, QProcess::NotRunning);
+ break;
+
+ case Kill:
+ QCOMPARE(state, QProcess::NotRunning);
+ break;
+
+ case Crash:
+ break;
+ }
+}
+
+void Modifier::cleanupTestCase()
+{
+ Q_EMIT testFinished();
+}
+
+Modifier *exec(Action action)
+{
+ return new Modifier(action);
+}
+
+} // namespace Process
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2013 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef PROCESS_H
+#define PROCESS_H
+
+#include <common/test.h>
+
+#include <controller.h>
+
+class QProcess;
+class QTemporaryDir;
+
+namespace Process
+{
+enum Action {
+ Start,
+ Stop,
+ Kill,
+ Crash,
+};
+
+class Modifier : public Test
+{
+ Q_OBJECT
+public:
+ Modifier(Action action);
+
+private Q_SLOTS:
+ void initTestCase();
+ void testProcess();
+ void cleanupTestCase();
+
+private:
+ Action m_action;
+ static QProcess *s_process;
+ static QTemporaryDir *s_tempDir;
+};
+
+Modifier *exec(Action action);
+
+} // namespace Process
+
+#endif /* PROCESS_H */
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2013 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <QCoreApplication>
+#include <QList>
+
+#include <common/test.h>
+
+#include "CleanOnlineTest.h"
+#include "OfflineTest.h"
+#include "Process.h"
+
+class TestRunner : public QObject
+{
+public:
+ TestRunner()
+ : m_nextToStart(0)
+ {
+ }
+
+ TestRunner &addTest(Test *test)
+ {
+ if (m_nextToStart == 0) {
+ m_tests << test;
+ }
+ return *this;
+ }
+
+ TestRunner &operator<<(Test *test)
+ {
+ addTest(test);
+ return *this;
+ }
+
+ void start()
+ {
+ if (m_nextToStart) {
+ return;
+ }
+
+ next();
+ }
+
+private:
+ void next()
+ {
+ if (m_nextToStart >= m_tests.size()) {
+ QCoreApplication::exit(0);
+ return;
+ }
+
+ Test *test = m_tests[m_nextToStart++];
+
+ QObject::connect(test, &Test::testFinished, this, &TestRunner::next, Qt::QueuedConnection);
+
+ QTest::qExec(test);
+ }
+
+private:
+ QList<Test *> m_tests;
+ int m_nextToStart;
+};
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+
+ TestRunner &runner = *(new TestRunner());
+
+ (runner << Process::exec(Process::Kill)
+
+ // Running the tests for when the service is offline
+ << new OfflineTest()
+
+ // Running the offline tests again so that we are sure
+ // nothing has changed -- no activities created, changed etc.
+ << new OfflineTest()
+
+ // Starting the manager
+ << Process::exec(Process::Start)
+
+ // Starting the online tests
+ << new CleanOnlineTest() << new CleanOnlineSetup()
+ << new OnlineTest()
+
+ // Starting the manager
+ << Process::exec(Process::Stop)
+
+ << new OfflineTest() << new OfflineTest()
+
+ << Process::exec(Process::Start) << new OnlineTest()
+
+ << Process::exec(Process::Stop)
+
+ )
+ .start();
+
+ return app.exec();
+ // QTest::qExec(&tc, argc, argv);
+}
--- /dev/null
+#! /bin/bash
+#
+# next-activity.sh
+# SPDX-FileCopyrightText: 2016 Ivan Čukić <ivan.cukic(at)kde.org>
+#
+# SPDX-License-Identifier: MIT
+#
+
+current_activity=($(qdbus org.kde.ActivityManager /ActivityManager/Activities CurrentActivity))
+activities=($(qdbus org.kde.ActivityManager /ActivityManager/Activities ListActivities))
+found="0"
+
+for ((i=0; i < ${#activities[@]}; ++i)); do
+ if [ "$current_activity" = "${activities[$i]}" ]; then
+ found="1"
+ else
+ if [ "$found" == "1" ]; then
+ echo "Switching to ${activities[$i]}"
+ qdbus org.kde.ActivityManager /ActivityManager/Activities SetCurrentActivity ${activities[$i]}
+ found="0"
+ fi
+ fi
+done
+
+if [ "$found" == "1" ]; then
+ echo "Switching to ${activities[0]}"
+ qdbus org.kde.ActivityManager /ActivityManager/Activities SetCurrentActivity ${activities[0]}
+fi
+
+
--- /dev/null
+#! /bin/bash
+#
+# next-activity.sh
+# SPDX-FileCopyrightText: 2016 Ivan Čukić <ivan.cukic(at)kde.org>
+#
+# SPDX-License-Identifier: MIT
+#
+
+current_activity=($(qdbus org.kde.ActivityManager /ActivityManager/Activities CurrentActivity))
+activities=($(qdbus org.kde.ActivityManager /ActivityManager/Activities ListActivities))
+found="0"
+
+previous_activity=""
+
+for ((i=0; i < ${#activities[@]}; ++i)); do
+ if [ "$current_activity" = "${activities[$i]}" ]; then
+ if [ "$previous_activity" != "" ]; then
+ echo "Switching to $previous_activity"
+ qdbus org.kde.ActivityManager /ActivityManager/Activities SetCurrentActivity $previous_activity
+ exit
+ else
+ echo "Switching to ${activities[-1]}"
+ qdbus org.kde.ActivityManager /ActivityManager/Activities SetCurrentActivity ${activities[-1]}
+ exit
+ fi
+ else
+ previous_activity="${activities[$i]}"
+ fi
+done
+
--- /dev/null
+#!/bin/bash
+
+# The script finds build directories for the current
+# src directory and builds them
+#
+# For example, for the source dir:
+# /some/path/kde/src/project/whatever
+# It finds:
+# /some/path/kde/build*/project/whatever
+
+current_dir=`pwd`
+
+all_root_dir=`pwd | sed 's#/src/.*##'`
+src_root_dir=$all_root_dir/src
+
+echo "src: $src_root_dir"
+
+for build_root_dir in $all_root_dir/build*; do
+ echo "building in $build_root_dir"
+
+ cd $current_dir
+ current_dir_log=`OBJ_REPLACEMENT=s#$src_root_dir#$build_root_dir# makeobj`
+ if [ "$?" = "0" ]
+ then
+ echo "... success"
+ else
+ echo "... FAILED"
+ echo $current_dir_log
+ exit
+ fi
+
+done
+
+git commit
+
--- /dev/null
+#!/bin/bash
+
+if [ ! -f "contrib/run-krazy.sh" ];
+then
+ echo "This script needs to be started from KAMD's root directory"
+ exit
+fi
+
+DIRS=$1
+
+if [ ! -n "$1" ];
+then
+ DIRS="lib service utils workspace"
+fi
+
+
+
+echo $DIRS
+CURRENT_DIRECTORY=$PWD
+
+for dir in $DIRS;
+do
+ echo "Running krazy2 on $dir ..."
+ cd $CURRENT_DIRECTORY/src/$dir && krazy2all --exclude license > /tmp/$dir.krazy
+done
--- /dev/null
+#! /usr/bin/env runhaskell
+
+{-# LANGUAGE LambdaCase #-}
+
+import System.Directory (doesFileExist, getDirectoryContents)
+import System.FilePath ((</>))
+
+import Data.String.Utils
+import Data.List
+
+
+-- Util methods
+
+mapSnd :: (a -> b) -> [(c, a)] -> [(c, b)]
+mapSnd f xys = map ( \case (x, y) -> (x, f y) ) xys
+
+mapDir :: (FilePath -> IO ()) -> FilePath -> IO ()
+mapDir proc fp = do
+ isFile <- doesFileExist fp -- is a file of fp
+ if isFile then proc fp -- process the file
+ else getDirectoryContents fp >>=
+ mapM_ (mapDir proc . (fp </>)) . filter (`notElem` [".", ".."])
+
+printTitle :: String -> IO()
+printTitle title = do
+ putStrLn ""
+ putStrLn title
+ putStrLn $ map (\_ -> '=') title
+
+main :: IO ()
+main = do
+ printTitle "libKActivities"
+ mapDir process "src/lib/core"
+
+ printTitle "libKActivitiesStats"
+ mapDir process "src/lib/stats"
+
+ printTitle "KActivityManagerD"
+ mapDir process "src/service"
+
+ printTitle "QML imports"
+ mapDir process "src/imports"
+
+ printTitle "Workspace plugins"
+ mapDir process "src/workspace"
+
+ printTitle "Other"
+ mapDir process "src/common"
+ mapDir process "src/utils"
+
+-- Parsing methods
+
+extractBlock :: [String] -> [String]
+extractBlock =
+ takeWhile (startswith "//")
+
+isTodoBlock :: (Integer, [String]) -> Bool
+isTodoBlock (_, block) =
+ (not $ null block) && (
+ (startswith "// TODO: " $ head block) ||
+ (startswith "// FIXME: " $ head block) ||
+ (startswith "// NOTE: " $ head block)
+ )
+
+joinBlock :: [String] -> String
+joinBlock block =
+ unlines $
+ map ( \line ->
+ ( dropWhile (== '/') line )
+ ) $
+ block
+
+
+-- File processing
+
+process :: FilePath -> IO ()
+process filename = do
+ -- Getting the file contents
+ content <- readFile filename
+
+ -- Items with line numbers
+ let items :: [(Integer, [String])]
+ items =
+ zip [1..] $
+ tails $
+ map strip $
+ lines content
+
+ -- Only those starting with TODO
+ let todoBlocks :: [(Integer, [String])]
+ todoBlocks =
+ -- Getting only the comment block
+ mapSnd extractBlock $
+ -- Getting comment blocks that define a todo item
+ filter isTodoBlock $
+ items
+
+ -- Todo items
+ let todoItems :: [(Integer, String)]
+ todoItems =
+ -- Getting the item blocks into actual items
+ mapSnd joinBlock todoBlocks
+
+
+ if (not $ null todoItems)
+ then
+ putStrLn $
+ concat $
+ map (\case (lineNo, todoItem) ->
+ filename ++ ":" ++ (show lineNo) ++ ":\n" ++ todoItem
+ ) todoItems
+ else
+ return ()
+
+
--- /dev/null
+# Module: KDE Aliases
+# Priority: 10
+
+autoload -U colors
+colors
+
+# Defining aliases for common kamd dbus objects
+alias kamd_dbus="qdbus org.kde.ActivityManager"
+
+alias kamd_activities="qdbus org.kde.ActivityManager /ActivityManager/Activities"
+alias kamd_resources="qdbus org.kde.ActivityManager /ActivityManager/Resources"
+alias kamd_resources_linking="qdbus org.kde.ActivityManager /ActivityManager/Resources/Linking"
+alias kamd_features="qdbus org.kde.ActivityManager /ActivityManager/Features"
+
+alias kamd_addactivity="qdbus org.kde.ActivityManager /ActivityManager/Activities AddActivity"
+alias kamd_removeactivity="qdbus org.kde.ActivityManager /ActivityManager/Activities RemoveActivity"
+
+# Open the KAMD database
+alias kamd_database="sqlite3 ~/.local/share/kactivitymanagerd/resources/database"
+
+# Lists the existing activities, along with their state
+function kamd_listactivities() {
+ echo -n "Service version: "
+ qdbus org.kde.ActivityManager /ActivityManager serviceVersion
+
+ CURRENT_ACTIVITY=`qdbus org.kde.ActivityManager /ActivityManager/Activities CurrentActivity`
+
+ for activity in `qdbus org.kde.ActivityManager /ActivityManager/Activities ListActivities`; do
+
+ STATE=""
+
+ if [ "$CURRENT_ACTIVITY" = "$activity" ]; then
+ STATE="$fg[green][CURRENT]"
+
+ else
+ STATE=`qdbus org.kde.ActivityManager /ActivityManager/Activities ActivityState $activity`
+
+ case "state$STATE" in
+ state0)
+ STATE="$fg[red]$bg[black][INVALID]"
+ ;;
+ state2)
+ STATE="$fg[blue][RUNNING]"
+ ;;
+ state3)
+ STATE="$fg[red]$bg[black][STARTING]"
+ ;;
+ state4)
+ STATE="$fg[black][STOPPED]"
+ ;;
+ state5)
+ STATE="$fg[red]$bg[black][STOPPING]"
+ ;;
+ esac
+ fi
+
+ echo -n "$STATE$reset_color $activity "
+
+ ACTIVITY_NAME=`qdbus org.kde.ActivityManager /ActivityManager/Activities ActivityName $activity`
+ ACTIVITY_DESC=`qdbus org.kde.ActivityManager /ActivityManager/Activities ActivityDescription $activity`
+ ACTIVITY_ICON=`qdbus org.kde.ActivityManager /ActivityManager/Activities ActivityIcon $activity`
+
+ if [ -n "$ACTIVITY_DESC" ]; then
+ echo "$ACTIVITY_NAME ($ACTIVITY_DESC, $ACTIVITY_ICON)"
+ else
+ echo "$ACTIVITY_NAME ($ACTIVITY_ICON)"
+ fi
+ done
+}
+
+# Shows the information about the current activity
+function kamd_currentactivity() {
+ for activity in `qdbus org.kde.ActivityManager /ActivityManager/Activities CurrentActivity`; do
+ STATE=`qdbus org.kde.ActivityManager /ActivityManager/Activities ActivityState $activity`
+
+ case "state$STATE" in
+ state0)
+ STATE="[INVALID] "
+ ;;
+ state2)
+ STATE="[RUNNING] "
+ ;;
+ state3)
+ STATE="[STARTING]"
+ ;;
+ state4)
+ STATE="[STOPPED] "
+ ;;
+ state5)
+ STATE="[STOPPING]"
+ ;;
+ esac
+
+ echo -n "$STATE $activity "
+ qdbus org.kde.ActivityManager /ActivityManager/Activities ActivityName $activity
+ done
+}
+
+# Returns the current activity ID
+function kamd_get_currentactivity_id() {
+ qdbus org.kde.ActivityManager /ActivityManager/Activities CurrentActivity
+}
+
+# Returns the current activity name
+function kamd_get_currentactivity_name() {
+ CURRENT_ACTIVITY_ID=$(kamd_get_currentactivity_id)
+ qdbus org.kde.ActivityManager /ActivityManager/Activities ActivityName $CURRENT_ACTIVITY_ID
+}
+
+# Returns the current activity name
+function kamd_get_currentactivity_name_normalized() {
+ CURRENT_ACTIVITY_NAME=$(kamd_get_currentactivity_name)
+ echo $CURRENT_ACTIVITY_NAME | tr '[:upper:] ' '[:lower:]-'
+}
+
+# Returns the current activity ID
+function kamd_STOP_ALL_BUT_CURRENT() {
+ CURRENT_ACTIVITY_ID=$(kamd_get_currentactivity_id)
+
+ for activity in `qdbus org.kde.ActivityManager /ActivityManager/Activities ListActivities`; do
+ if [ "$activity" != "$CURRENT_ACTIVITY_ID" ]; then
+ echo "Stop: $activity"
+ qdbus org.kde.ActivityManager /ActivityManager/Activities StopActivity $activity
+ sleep 1
+ fi
+ done
+}
+
+# Returns the current activity ID
+function kamd_REMOVE_ALL_BUT_CURRENT() {
+ CURRENT_ACTIVITY_ID=$(kamd_get_currentactivity_id)
+
+ for activity in `qdbus org.kde.ActivityManager /ActivityManager/Activities ListActivities`; do
+ if [ "$activity" != "$CURRENT_ACTIVITY_ID" ]; then
+ echo "Remove: $activity"
+ qdbus org.kde.ActivityManager /ActivityManager/Activities RemoveActivity $activity
+ sleep 1
+ fi
+ done
+}
+
--- /dev/null
+EXCLUDE_PATTERNS += */service/* */scripts/* */workspace/*
--- /dev/null
+maintainer: ivan
+description: Runtime and library to organize the user work in separate activities
+tier: 2
+type: solution
+platforms:
+ - name: Linux
+ - name: FreeBSD
+ - name: Windows
+ - name: macOS
+ note: Needs QtDBus
+portingAid: false
+deprecated: false
+release: true
+libraries:
+ - qmake: KActivities
+ cmake: "KF5::Activities"
+cmakename: KF5Activities
+
+public_lib: true
+group: Frameworks
+subgroup: Tier 2
--- /dev/null
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+
+# Boosting us a bit
+
+if (NOT KACTIVITIES_LIBRARY_ONLY)
+ find_package (Boost 1.49 REQUIRED)
+
+ string (REGEX MATCH "1053.." BOOST_VERSION_BLACKLISTED ${Boost_VERSION})
+
+ if (BOOST_VERSION_BLACKLISTED AND NOT KACTIVITIES_ENABLE_EXCEPTIONS)
+ message (
+ WARNING
+ "Boost.Container 1.53 has issues when exceptions are disabled. "
+ "We will set the KACTIVITIES_ENABLE_EXCEPTIONS option."
+ )
+ set (KACTIVITIES_ENABLE_EXCEPTIONS ON)
+ endif ()
+endif ()
+
+if (KACTIVITIES_ENABLE_EXCEPTIONS)
+ string (REPLACE "-fno-exceptions" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+ add_definitions (-fexceptions)
+endif ()
+
+# =======================================================
+# Starting the actual project definition
+
+# The libraries do not depend on any compile-time features
+add_subdirectory (lib)
+
+if (NOT KACTIVITIES_LIBRARY_ONLY)
+ include_directories (
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+ add_subdirectory (imports)
+endif ()
+
+add_subdirectory (cli)
+
+ecm_qt_install_logging_categories(
+ EXPORT KACTIVITIES
+ FILE kactivities.categories
+ DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
+)
--- /dev/null
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+project (KActivitiesCLI)
+
+find_package (Qt${QT_MAJOR_VERSION} REQUIRED NO_MODULE COMPONENTS Core Gui Widgets)
+find_package (Qt${QT_MAJOR_VERSION} REQUIRED NO_MODULE COMPONENTS Core Gui Widgets)
+find_package (KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS WindowSystem)
+
+include_directories (
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/autotests/
+ )
+
+set (
+ KActivitiesCLI_SRCS
+ main.cpp
+ )
+
+qt_wrap_ui(
+ KActivitiesCLI_SRCS
+ )
+
+add_executable (
+ kactivities-cli
+ ${KActivitiesCLI_SRCS}
+ )
+
+target_link_libraries (
+ kactivities-cli
+ Qt${QT_MAJOR_VERSION}::Core
+ KF5::Activities
+ )
+
+ecm_mark_nongui_executable(
+ kactivities-cli
+ )
+
+install (TARGETS
+ kactivities-cli
+ ${KF_INSTALL_TARGETS_DEFAULT_ARGS}
+ )
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include <QCoreApplication>
+#include <QDebug>
+#include <QTimer>
+
+#include <KActivities/Controller>
+
+#include "utils.h"
+
+// Output modifiers
+
+DEFINE_COMMAND(bare, 0)
+{
+ flags.bare = true;
+ return 0;
+}
+
+DEFINE_COMMAND(noBare, 0)
+{
+ flags.bare = false;
+ return 0;
+}
+
+DEFINE_COMMAND(color, 0)
+{
+ flags.color = true;
+ return 0;
+}
+
+DEFINE_COMMAND(noColor, 0)
+{
+ flags.color = false;
+ return 0;
+}
+
+// Activity management
+
+DEFINE_COMMAND(createActivity, 1)
+{
+ auto result = awaitFuture(controller->addActivity(args(1)));
+
+ qDebug().noquote() << result;
+
+ return 1;
+}
+
+DEFINE_COMMAND(removeActivity, 1)
+{
+ awaitFuture(controller->removeActivity(args(1)));
+
+ return 1;
+}
+
+DEFINE_COMMAND(startActivity, 1)
+{
+ awaitFuture(controller->startActivity(args(1)));
+
+ return 1;
+}
+
+DEFINE_COMMAND(stopActivity, 1)
+{
+ awaitFuture(controller->stopActivity(args(1)));
+
+ return 1;
+}
+
+DEFINE_COMMAND(listActivities, 0)
+{
+ for (const auto &activity : controller->activities()) {
+ printActivity(activity);
+ }
+
+ return 0;
+}
+
+DEFINE_COMMAND(currentActivity, 0)
+{
+ printActivity(controller->currentActivity());
+
+ return 0;
+}
+
+DEFINE_COMMAND(setActivityProperty, 3)
+{
+ const auto what = args(1);
+ const auto id = args(2);
+ const auto value = args(3);
+
+ // clang-format off
+ awaitFuture(
+ what == QLatin1String("name") ? controller->setActivityName(id, value) :
+ what == QLatin1String("description") ? controller->setActivityDescription(id, value) :
+ what == QLatin1String("icon") ? controller->setActivityIcon(id, value) :
+ QFuture<void>()
+ );
+ // clang-format on
+
+ return 3;
+}
+
+DEFINE_COMMAND(activityProperty, 2)
+{
+ const auto what = args(1);
+ const auto id = args(2);
+
+ KActivities::Info info(id);
+ // clang-format off
+ out << (
+ what == QLatin1String("name") ? info.name() :
+ what == QLatin1String("description") ? info.description() :
+ what == QLatin1String("icon") ? info.icon() :
+ QString()
+ ) << "\n";
+ // clang-format on
+ return 2;
+}
+
+// Activity switching
+
+DEFINE_COMMAND(setCurrentActivity, 1)
+{
+ switchToActivity(args(1));
+
+ return 1;
+}
+
+DEFINE_COMMAND(nextActivity, 0)
+{
+ controller->nextActivity();
+ return 0;
+}
+
+DEFINE_COMMAND(previousActivity, 0)
+{
+ controller->previousActivity();
+ return 0;
+}
+
+void printHelp()
+{
+ if (!flags.bare) {
+ qDebug() << "\nModifiers (applied only to trailing commands):"
+ << "\n --bare, --no-bare - show minimal info vs show everything"
+ << "\n --color, --no-color - make the output pretty"
+
+ << "\n\nCommands:"
+ << "\n --list-activities - lists all activities"
+ << "\n --create-activity Name - creates a new activity with the specified name"
+ << "\n --remove-activity ID - removes the activity with the specified id"
+ << "\n --start-activity ID - starts the specified activity"
+ << "\n --stop-activity ID - stops the specified activity"
+
+ << "\n --current-activity - show the current activity"
+ << "\n --set-current-activity - sets the current activity"
+ << "\n --next-activity - switches to the next activity (in list-activities order)"
+ << "\n --previous-activity - switches to the previous activity (in list-activities order)"
+
+ << "\n --activity-property What ID"
+ << "\n - gets activity name, icon or description"
+ << "\n --set-activity-property What ID Value"
+ << "\n - changes activity name, icon or description";
+
+ } else {
+ qDebug() << "\n--bare"
+ << "\n--no-bare"
+ << "\n--color"
+ << "\n--no-color"
+ << "\n--list-activities"
+ << "\n--create-activity NAME"
+ << "\n--remove-activity ID"
+
+ << "\n--current-activity"
+ << "\n--set-current-activity"
+ << "\n--next-activity"
+ << "\n--previous-activity";
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+
+ QTimer::singleShot(0, &app, [] {
+ const auto args = QCoreApplication::arguments();
+
+ controller = new KActivities::Controller();
+
+ while (controller->serviceStatus() != KActivities::Controller::Running) {
+ QCoreApplication::processEvents();
+ }
+
+// clang-format off
+ #define MATCH_COMMAND(Command) \
+ else if (args[argId] == QLatin1String("--") + toDashes(QStringLiteral(#Command))) \
+ { \
+ argId += 1 + Command##_command({ args, argId })(); \
+ }
+ // clang-format on
+ if (args.count() <= 1) {
+ printHelp();
+
+ } else {
+ for (int argId = 1; argId < args.count();) {
+ if (args[argId] == QLatin1String("--help")) {
+ printHelp();
+ argId++;
+ }
+
+ MATCH_COMMAND(bare)
+ MATCH_COMMAND(noBare)
+ MATCH_COMMAND(color)
+ MATCH_COMMAND(noColor)
+
+ MATCH_COMMAND(listActivities)
+
+ MATCH_COMMAND(currentActivity)
+ MATCH_COMMAND(setCurrentActivity)
+ MATCH_COMMAND(activityProperty)
+ MATCH_COMMAND(setActivityProperty)
+ MATCH_COMMAND(nextActivity)
+ MATCH_COMMAND(previousActivity)
+
+ MATCH_COMMAND(createActivity)
+ MATCH_COMMAND(removeActivity)
+ MATCH_COMMAND(startActivity)
+ MATCH_COMMAND(stopActivity)
+
+ else
+ {
+ qDebug() << "Skipping unknown argument" << args[argId];
+ argId++;
+ }
+ }
+ }
+
+ delete controller;
+
+ QCoreApplication::quit();
+ });
+
+ return app.exec();
+}
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2016 Ivan Čukić <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef KACTIVITIES_UTILS_H
+#define KACTIVITIES_UTILS_H
+
+QTextStream out(stdout);
+
+class StringListView
+{
+public:
+ StringListView(const QStringList &list, int start, int end = -1)
+ : m_list(list)
+ , m_start(start)
+ , m_size((end == -1 ? list.count() : end) - start)
+ {
+ }
+
+ const QString &operator()(int index) const
+ {
+ return m_list[m_start + index];
+ }
+
+ int count() const
+ {
+ return m_size;
+ }
+
+private:
+ const QStringList &m_list;
+ int m_start;
+ int m_size;
+};
+
+KActivities::Controller *controller = nullptr;
+
+class Flags
+{
+public:
+ Flags()
+ : bare(false)
+ , color(true)
+ {
+ }
+
+ bool bare;
+ bool color;
+
+} flags;
+
+QString toDashes(const QString &command)
+{
+ QString result(command);
+
+ for (int i = 0; i < result.size() - 1; ++i) {
+ if (result[i].isLower() && result[i + 1].isUpper()) {
+ result[i + 1] = result[i + 1].toLower();
+ result.insert(i + 1, QStringLiteral("-"));
+ }
+ }
+
+ return result;
+}
+
+void printActivity(const QString &id)
+{
+ // clang-format off
+ if (flags.bare) {
+ out << id << "\n";
+
+ } else {
+ using namespace KActivities;
+ Info info(id);
+
+ out
+ << (
+ info.id() == controller->currentActivity() ? "[CURRENT] " :
+ info.state() == Info::Running ? "[RUNNING] " :
+ info.state() == Info::Stopped ? "[STOPPED] " :
+ info.state() == Info::Starting ? "[STARTING]" :
+ info.state() == Info::Stopping ? "[STOPPING]" :
+ "unknown "
+ )
+ << info.id()
+ << " "
+ << info.name()
+ << " ("
+ << info.icon()
+ << ")\n"
+ ;
+
+ if (info.id() == controller->currentActivity()
+ && info.state() != Info::Running) {
+ qWarning()
+ << "Activity is the current one, but its state is"
+ << (
+ info.state() == Info::Running ? "running" :
+ info.state() == Info::Stopped ? "stopped" :
+ info.state() == Info::Starting ? "starting" :
+ info.state() == Info::Stopping ? "stopping" :
+ "unknown "
+ );
+ }
+ }
+ // clang-format on
+}
+
+template<typename T>
+T awaitFuture(const QFuture<T> &future)
+{
+ while (!future.isFinished()) {
+ QCoreApplication::processEvents();
+ }
+
+ return future.result();
+}
+
+void awaitFuture(const QFuture<void> &future)
+{
+ while (!future.isFinished()) {
+ QCoreApplication::processEvents();
+ }
+}
+
+void switchToActivity(const QString &id)
+{
+ auto result = awaitFuture(controller->setCurrentActivity(id));
+
+ if (!flags.bare) {
+ if (result) {
+ qDebug() << "Current activity is" << id;
+ } else {
+ qDebug() << "Failed to change the activity";
+ }
+ }
+}
+
+// clang-format off
+#define DEFINE_COMMAND(Command, MinArgCount) \
+ struct Command##_command { \
+ const StringListView &args; \
+ Command##_command(const StringListView &args) \
+ : args(args) \
+ { \
+ if (args.count() < MinArgCount + 1) { \
+ qFatal("not enough arguments for " #Command); \
+ } \
+ } \
+ \
+ int operator()(); \
+ }; \
+ \
+ int Command##_command::operator()()
+
+#endif
+// clang-format on
\ No newline at end of file
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef DBUS_COMMON_H
+#define DBUS_COMMON_H
+
+#include <QDBusConnection>
+#include <QDBusInterface>
+
+// clang-format off
+
+#define KAMD_DBUS_SERVICE \
+ QStringLiteral("org.kde.ActivityManager")
+
+#define KAMD_DBUS_OBJECT_PATH(A) \
+ (sizeof(A) > 2 ? QLatin1String("/ActivityManager/" A) \
+ : QLatin1String("/ActivityManager"))
+
+#define KAMD_DBUS_OBJECT(A) \
+ QLatin1String("org.kde.ActivityManager." A)
+
+#define KAMD_DBUS_INTERFACE(OBJECT_PATH, OBJECT, PARENT) \
+ QDBusInterface(KAMD_DBUS_SERVICE, \
+ KAMD_DBUS_OBJECT_PATH(OBJECT_PATH), \
+ KAMD_DBUS_OBJECT(OBJECT), \
+ QDBusConnection::sessionBus(), \
+ PARENT)
+
+#define KAMD_DBUS_DECL_INTERFACE(VAR, OBJECT_PATH, OBJECT) \
+ QDBusInterface VAR(KAMD_DBUS_SERVICE, \
+ KAMD_DBUS_OBJECT_PATH(OBJECT_PATH), \
+ KAMD_DBUS_OBJECT(OBJECT), \
+ QDBusConnection::sessionBus(), \
+ nullptr)
+
+#define KAMD_DBUS_CLASS_INTERFACE(OBJECT_PATH, OBJECT, PARENT) \
+ org::kde::ActivityManager::OBJECT( \
+ KAMD_DBUS_SERVICE, \
+ KAMD_DBUS_OBJECT_PATH(OBJECT_PATH), \
+ QDBusConnection::sessionBus(), \
+ PARENT)
+
+#endif // DBUS_COMMON_H
+
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "org.kde.ActivityManager.Activities.h"
+
+#include <QDBusMetaType>
+#include <QMetaType>
+
+namespace details
+{
+class ActivityInfoStaticInit
+{
+public:
+ ActivityInfoStaticInit()
+ {
+ qDBusRegisterMetaType<ActivityInfo>();
+ qDBusRegisterMetaType<ActivityInfoList>();
+ }
+
+ static ActivityInfoStaticInit _instance;
+};
+
+ActivityInfoStaticInit ActivityInfoStaticInit::_instance;
+
+} // namespace details
+
+QDBusArgument &operator<<(QDBusArgument &arg, const ActivityInfo r)
+{
+ arg.beginStructure();
+
+ arg << r.id;
+ arg << r.name;
+ arg << r.description;
+ arg << r.icon;
+ arg << r.state;
+
+ arg.endStructure();
+
+ return arg;
+}
+
+const QDBusArgument &operator>>(const QDBusArgument &arg, ActivityInfo &r)
+{
+ arg.beginStructure();
+
+ arg >> r.id;
+ arg >> r.name;
+ arg >> r.description;
+ arg >> r.icon;
+ arg >> r.state;
+
+ arg.endStructure();
+
+ return arg;
+}
+
+QDebug operator<<(QDebug dbg, const ActivityInfo &r)
+{
+ dbg << "ActivityInfo(" << r.id << r.name << ")";
+ return dbg.space();
+}
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef KAMD_ORG_KDE_ACTIVITYMANAGER_ACTIVITIES_H
+#define KAMD_ORG_KDE_ACTIVITYMANAGER_ACTIVITIES_H
+
+#include <QDBusArgument>
+#include <QDebug>
+#include <QList>
+#include <QString>
+
+struct ActivityInfo {
+ QString id;
+ QString name;
+ QString description;
+ QString icon;
+ int state;
+
+ ActivityInfo(const QString &id = QString(),
+ const QString &name = QString(),
+ const QString &description = QString(),
+ const QString &icon = QString(),
+ int state = 0)
+ : id(id)
+ , name(name)
+ , description(description)
+ , icon(icon)
+ , state(state)
+ {
+ }
+};
+
+typedef QList<ActivityInfo> ActivityInfoList;
+
+Q_DECLARE_METATYPE(ActivityInfo)
+Q_DECLARE_METATYPE(ActivityInfoList)
+
+QDBusArgument &operator<<(QDBusArgument &arg, const ActivityInfo);
+const QDBusArgument &operator>>(const QDBusArgument &arg, ActivityInfo &rec);
+
+QDebug operator<<(QDebug dbg, const ActivityInfo &r);
+
+#endif // KAMD_ORG_KDE_ACTIVITYMANAGER_ACTIVITIES_H
--- /dev/null
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.kde.ActivityManager.Activities">
+
+ <method name="CurrentActivity">
+ <arg type="s" direction="out"/>
+ </method>
+ <method name="SetCurrentActivity">
+ <arg type="b" direction="out"/>
+ <arg name="activity" type="s" direction="in"/>
+ </method>
+
+ <method name="AddActivity">
+ <arg type="s" direction="out"/>
+ <arg name="name" type="s" direction="in"/>
+ </method>
+ <method name="StartActivity">
+ <arg name="activity" type="s" direction="in"/>
+ </method>
+ <method name="StopActivity">
+ <arg name="activity" type="s" direction="in"/>
+ </method>
+ <method name="ActivityState">
+ <arg type="i" direction="out"/>
+ <arg name="activity" type="s" direction="in"/>
+ </method>
+ <method name="RemoveActivity">
+ <arg name="activity" type="s" direction="in"/>
+ </method>
+
+ <method name="ListActivities">
+ <arg type="as" direction="out"/>
+ </method>
+ <method name="ListActivities">
+ <arg type="as" direction="out"/>
+ <arg name="state" type="i" direction="in"/>
+ </method>
+
+ <method name="ListActivitiesWithInformation">
+ <arg type="a(sssd)" direction="out"/>
+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ActivityInfoList" />
+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ActivityInfoList" />
+ </method>
+ <method name="ActivityInformation">
+ <arg type="(sssd)" direction="out"/>
+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ActivityInfo" />
+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ActivityInfo" />
+ <arg name="activity" type="s" direction="in"/>
+ </method>
+
+ <method name="ActivityName">
+ <arg type="s" direction="out"/>
+ <arg name="activity" type="s" direction="in"/>
+ </method>
+ <method name="SetActivityName">
+ <arg name="activity" type="s" direction="in"/>
+ <arg name="name" type="s" direction="in"/>
+ </method>
+
+ <method name="ActivityDescription">
+ <arg type="s" direction="out"/>
+ <arg name="activity" type="s" direction="in"/>
+ </method>
+ <method name="SetActivityDescription">
+ <arg name="activity" type="s" direction="in"/>
+ <arg name="description" type="s" direction="in"/>
+ </method>
+
+ <method name="ActivityIcon">
+ <arg type="s" direction="out"/>
+ <arg name="activity" type="s" direction="in"/>
+ </method>
+ <method name="SetActivityIcon">
+ <arg name="activity" type="s" direction="in"/>
+ <arg name="icon" type="s" direction="in"/>
+ </method>
+
+ <signal name="CurrentActivityChanged">
+ <arg name="activity" type="s" direction="out"/>
+ </signal>
+
+ <signal name="ActivityAdded">
+ <arg name="activity" type="s" direction="out"/>
+ </signal>
+ <signal name="ActivityStarted">
+ <arg name="activity" type="s" direction="out"/>
+ </signal>
+ <signal name="ActivityStopped">
+ <arg name="activity" type="s" direction="out"/>
+ </signal>
+ <signal name="ActivityRemoved">
+ <arg name="activity" type="s" direction="out"/>
+ </signal>
+ <signal name="ActivityChanged">
+ <arg name="activity" type="s" direction="out"/>
+ </signal>
+ <signal name="ActivityNameChanged">
+ <arg name="activity" type="s" direction="out"/>
+ <arg name="name" type="s" direction="out"/>
+ </signal>
+ <signal name="ActivityDescriptionChanged">
+ <arg name="activity" type="s" direction="out"/>
+ <arg name="description" type="s" direction="out"/>
+ </signal>
+ <signal name="ActivityIconChanged">
+ <arg name="activity" type="s" direction="out"/>
+ <arg name="icon" type="s" direction="out"/>
+ </signal>
+ <signal name="ActivityStateChanged">
+ <arg name="activity" type="s" direction="out"/>
+ <arg name="state" type="i" direction="out"/>
+ </signal>
+
+ </interface>
+</node>
--- /dev/null
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.kde.ActivityManager.Application">
+ <method name="quit">
+ </method>
+ <method name="serviceVersion">
+ <arg type="s" direction="out"/>
+ </method>
+ <method name="loadPlugin">
+ <arg type="b" direction="out"/>
+ <arg name="plugin" type="s" direction="in"/>
+ </method>
+ </interface>
+</node>
--- /dev/null
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.kde.ActivityManager.Features">
+ <method name="IsFeatureOperational">
+ <arg type="b" direction="out"/>
+ <arg name="feature" type="s" direction="in"/>
+ </method>
+ <method name="ListFeatures">
+ <arg type="as" direction="out"/>
+ <arg name="module" type="s" direction="in"/>
+ </method>
+ <method name="GetValue">
+ <arg type="v" direction="out"/>
+ <arg name="property" type="s" direction="in"/>
+ </method>
+ <method name="SetValue">
+ <arg name="property" type="s" direction="in"/>
+ <arg name="value" type="v" direction="in"/>
+ </method>
+ </interface>
+</node>
--- /dev/null
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.kde.ActivityManager.Resources">
+
+ <method name="RegisterResourceEvent">
+ <arg name="application" type="s" direction="in"/>
+ <arg name="windowId" type="u" direction="in"/>
+ <arg name="uri" type="s" direction="in"/>
+ <arg name="event" type="u" direction="in"/>
+ </method>
+
+ <method name="RegisterResourceMimetype">
+ <arg name="uri" type="s" direction="in"/>
+ <arg name="mimetype" type="s" direction="in"/>
+ </method>
+
+ <method name="RegisterResourceTitle">
+ <arg name="uri" type="s" direction="in"/>
+ <arg name="title" type="s" direction="in"/>
+ </method>
+
+ </interface>
+</node>
--- /dev/null
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.kde.ActivityManager.ResourcesLinking">
+
+ <method name="LinkResourceToActivity">
+ <arg name="agent" type="s" direction="in"/>
+ <arg name="resource" type="s" direction="in"/>
+ <arg name="activity" type="s" direction="in"/>
+ </method>
+ <method name="LinkResourceToActivity">
+ <arg name="agent" type="s" direction="in"/>
+ <arg name="resource" type="s" direction="in"/>
+ </method>
+
+ <method name="UnlinkResourceFromActivity">
+ <arg name="agent" type="s" direction="in"/>
+ <arg name="resource" type="s" direction="in"/>
+ <arg name="activity" type="s" direction="in"/>
+ </method>
+ <method name="UnlinkResourceFromActivity">
+ <arg name="agent" type="s" direction="in"/>
+ <arg name="resource" type="s" direction="in"/>
+ </method>
+
+ <method name="IsResourceLinkedToActivity">
+ <arg name="agent" type="s" direction="in"/>
+ <arg name="resource" type="s" direction="in"/>
+ <arg name="activity" type="s" direction="in"/>
+ <arg type="b" direction="out"/>
+ </method>
+ <method name="IsResourceLinkedToActivity">
+ <arg name="agent" type="s" direction="in"/>
+ <arg name="resource" type="s" direction="in"/>
+ <arg type="b" direction="out"/>
+ </method>
+
+ <signal name="ResourceLinkedToActivity">
+ <arg name="agent" type="s" direction="out"/>
+ <arg name="resource" type="s" direction="out"/>
+ <arg name="activity" type="s" direction="out"/>
+ </signal>
+ <signal name="ResourceUnlinkedFromActivity">
+ <arg name="agent" type="s" direction="out"/>
+ <arg name="resource" type="s" direction="out"/>
+ <arg name="activity" type="s" direction="out"/>
+ </signal>
+
+ </interface>
+</node>
--- /dev/null
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.kde.ActivityManager.ResourcesScoring">
+
+ <signal name="ResourceScoreUpdated">
+ <arg name="activity" type="s" direction="out"/>
+ <arg name="client" type="s" direction="out"/>
+ <arg name="resource" type="s" direction="out"/>
+ <arg name="score" type="d" direction="out"/>
+ <arg name="lastUpdate" type="u" direction="out"/>
+ <arg name="firstUpdate" type="u" direction="out"/>
+ </signal>
+ <signal name="ResourceScoreDeleted">
+ <arg name="activity" type="s" direction="out"/>
+ <arg name="client" type="s" direction="out"/>
+ <arg name="resource" type="s" direction="out"/>
+ </signal>
+
+ <signal name="RecentStatsDeleted">
+ <arg name="activity" type="s" direction="out"/>
+ <arg name="count" type="i" direction="out"/>
+ <arg name="what" type="s" direction="out"/>
+ </signal>
+ <signal name="EarlierStatsDeleted">
+ <arg name="activity" type="s" direction="out"/>
+ <arg name="months" type="i" direction="out"/>
+ </signal>
+
+ <method name="DeleteStatsForResource">
+ <arg name="activity" type="s" direction="in"/>
+ <arg name="client" type="s" direction="in"/>
+ <arg name="resource" type="s" direction="in"/>
+ </method>
+ <method name="DeleteRecentStats">
+ <arg name="activity" type="s" direction="in"/>
+ <arg name="count" type="i" direction="in"/>
+ <arg name="what" type="s" direction="in"/>
+ </method>
+ <method name="DeleteEarlierStats">
+ <arg name="activity" type="s" direction="in"/>
+ <arg name="months" type="i" direction="in"/>
+ </method>
+
+ </interface>
+</node>
--- /dev/null
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+
+project (kactivities-imports)
+
+find_package (Qt${QT_MAJOR_VERSION} REQUIRED NO_MODULE COMPONENTS Gui Qml Quick Sql)
+find_package (KF5Config ${KF_DEP_VERSION} CONFIG REQUIRED)
+find_package (KF5CoreAddons ${KF_DEP_VERSION} CONFIG REQUIRED)
+
+ecm_add_qml_module(kactivitiesextensionplugin URI "org.kde.activities" VERSION 0.1)
+
+target_sources(kactivitiesextensionplugin PRIVATE
+ activitiesextensionplugin.cpp
+ activitymodel.cpp
+ activityinfo.cpp
+# resourcemodel.cpp
+ resourceinstance.cpp
+
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/utils/dbusfuture_p.cpp
+)
+
+target_link_libraries(
+ kactivitiesextensionplugin
+ Qt${QT_MAJOR_VERSION}::Core
+ Qt${QT_MAJOR_VERSION}::DBus
+ Qt${QT_MAJOR_VERSION}::Gui
+ Qt${QT_MAJOR_VERSION}::Qml
+ Qt${QT_MAJOR_VERSION}::Quick
+ Qt${QT_MAJOR_VERSION}::Sql
+ KF5::Activities
+ KF5::ConfigCore
+ KF5::CoreAddons
+ Boost::headers
+)
+
+ecm_finalize_qml_module(kactivitiesextensionplugin DESTINATION ${KDE_INSTALL_QMLDIR})
--- /dev/null
+org.kde.activities namespace does not guarantee a
+stable api.
+
+If you want to use components from it, make sure
+you follow the further developments closely.
+
+
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2012, 2013, 2014, 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include "activitiesextensionplugin.h"
+
+#include "activityinfo.h"
+#include "activitymodel.h"
+#include "resourceinstance.h"
+
+// #include "resourcemodel.h"
+
+// TODO: Clean up unused classes from the imports module
+
+// TODO: Since plasma is now dealing with activity model wallpapers,
+// replace ActivityModel with the KActivities::ActivitiesModel
+// (but keep the name)
+
+ActivitiesExtensionPlugin::ActivitiesExtensionPlugin(QObject *parent)
+ : QQmlExtensionPlugin(parent)
+{
+}
+
+void ActivitiesExtensionPlugin::registerTypes(const char *uri)
+{
+ Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.activities"));
+
+ // Used by applets/activitybar
+ qmlRegisterType<KActivities::Imports::ActivityModel>(uri, 0, 1, "ActivityModel");
+
+ qmlRegisterType<KActivities::Imports::ActivityInfo>(uri, 0, 1, "ActivityInfo");
+ qmlRegisterType<KActivities::Imports::ResourceInstance>(uri, 0, 1, "ResourceInstance");
+
+ // This one is removed in favor of KActivities::Stats::ResultModel.
+ // Subclass it, and make it do what you want.
+ // qmlRegisterType<KActivities::Imports::ResourceModel>(uri, 0, 1, "ResourceModel");
+}
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2011, 2012, 2013, 2014, 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef KACTIVITIES_ACTIVITIES_EXTENSION_PLUGIN_H
+#define KACTIVITIES_ACTIVITIES_EXTENSION_PLUGIN_H
+
+#include <QQmlExtensionPlugin>
+
+class ActivitiesExtensionPlugin : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.kde.activities")
+
+public:
+ explicit ActivitiesExtensionPlugin(QObject *parent = nullptr);
+ void registerTypes(const char *uri) override;
+};
+
+#endif // KACTIVITIES_ACTIVITIES_EXTENSION_PLUGIN_H
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2012, 2013, 2014, 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+// Self
+#include "activityinfo.h"
+
+namespace KActivities
+{
+namespace Imports
+{
+ActivityInfo::ActivityInfo(QObject *parent)
+ : QObject(parent)
+ , m_showCurrentActivity(false)
+{
+ connect(&m_service, &KActivities::Controller::currentActivityChanged, this, &ActivityInfo::setCurrentActivity);
+}
+
+ActivityInfo::~ActivityInfo()
+{
+}
+
+void ActivityInfo::setCurrentActivity(const QString &id)
+{
+ if (!m_showCurrentActivity) {
+ return;
+ }
+
+ setIdInternal(id);
+
+ Q_EMIT nameChanged(m_info->name());
+ Q_EMIT descriptionChanged(m_info->description());
+ Q_EMIT iconChanged(m_info->icon());
+}
+
+void ActivityInfo::setActivityId(const QString &id)
+{
+ m_showCurrentActivity = (id == QLatin1String(":current"));
+
+ setIdInternal(m_showCurrentActivity ? m_service.currentActivity() : id);
+}
+
+void ActivityInfo::setIdInternal(const QString &id)
+{
+ using namespace KActivities;
+
+ // We are killing the old info object, if any
+ m_info.reset(new KActivities::Info(id));
+
+ auto ptr = m_info.get();
+
+ connect(ptr, &Info::nameChanged, this, &ActivityInfo::nameChanged);
+ connect(ptr, &Info::descriptionChanged, this, &ActivityInfo::descriptionChanged);
+ connect(ptr, &Info::iconChanged, this, &ActivityInfo::iconChanged);
+}
+// clang-format off
+#define CREATE_GETTER_AND_SETTER(WHAT, What) \
+ QString ActivityInfo::What() const \
+ { \
+ return m_info ? m_info->What() : QString(); \
+ } \
+ \
+ void ActivityInfo::set##WHAT(const QString &value) \
+ { \
+ if (!m_info) \
+ return; \
+ \
+ m_service.setActivity##WHAT(m_info->id(), value); \
+ }
+// clang-format on
+
+CREATE_GETTER_AND_SETTER(Name, name)
+CREATE_GETTER_AND_SETTER(Description, description)
+CREATE_GETTER_AND_SETTER(Icon, icon)
+
+#undef CREATE_GETTER_AND_SETTER
+
+QString ActivityInfo::activityId() const
+{
+ return m_info ? m_info->id() : QString();
+}
+
+bool ActivityInfo::valid() const
+{
+ return true;
+}
+
+} // namespace Imports
+} // namespace KActivities
+
+// #include "activityinfo.moc"
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2012, 2013, 2014, 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef KACTIVITIES_IMPORTS_ACTIVITY_INFO_H
+#define KACTIVITIES_IMPORTS_ACTIVITY_INFO_H
+
+// Qt
+#include <QObject>
+
+// STL
+#include <memory>
+
+// Local
+#include <lib/controller.h>
+#include <lib/info.h>
+
+namespace KActivities
+{
+namespace Imports
+{
+/**
+ * ActivityInfo
+ */
+
+class ActivityInfo : public QObject
+{
+ Q_OBJECT
+
+ /**
+ * Unique identifier of the activity
+ */
+ Q_PROPERTY(QString activityId READ activityId WRITE setActivityId NOTIFY activityIdChanged)
+
+ /**
+ * Name of the activity
+ */
+ Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
+
+ /**
+ * Name of the activity
+ */
+ Q_PROPERTY(QString description READ description WRITE setDescription NOTIFY descriptionChanged)
+
+ /**
+ * Activity icon
+ */
+ Q_PROPERTY(QString icon READ icon WRITE setIcon NOTIFY iconChanged)
+
+ /**
+ * Is the activity a valid one - does it exist?
+ */
+ Q_PROPERTY(bool valid READ valid NOTIFY validChanged)
+
+public:
+ explicit ActivityInfo(QObject *parent = nullptr);
+ ~ActivityInfo() override;
+
+public Q_SLOTS:
+ void setActivityId(const QString &id);
+ QString activityId() const;
+
+ void setName(const QString &name);
+ QString name() const;
+
+ void setDescription(const QString &description);
+ QString description() const;
+
+ void setIcon(const QString &icon);
+ QString icon() const;
+
+ bool valid() const;
+
+Q_SIGNALS:
+ void activityIdChanged(const QString &id);
+ void nameChanged(const QString &name);
+ void descriptionChanged(const QString &description);
+ void iconChanged(const QString &icon);
+ void validChanged(bool valid);
+
+private Q_SLOTS:
+ void setCurrentActivity(const QString &id);
+
+private:
+ void setIdInternal(const QString &id);
+
+ KActivities::Controller m_service;
+ std::unique_ptr<KActivities::Info> m_info;
+ bool m_showCurrentActivity;
+};
+
+} // namespace Imports
+} // namespace KActivities
+
+#endif // KACTIVITIES_IMPORTS_ACTIVITY_INFO_H
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2012, 2013, 2014, 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+// Self
+#include "activitymodel.h"
+
+// Qt
+#include <QByteArray>
+#include <QDBusPendingCall>
+#include <QDBusPendingCallWatcher>
+#include <QDebug>
+#include <QFutureWatcher>
+#include <QHash>
+#include <QIcon>
+#include <QList>
+#include <QModelIndex>
+
+// KDE
+#include <KConfig>
+#include <KConfigGroup>
+#include <KDirWatch>
+
+// Boost
+#include <boost/optional.hpp>
+#include <boost/range/adaptor/filtered.hpp>
+#include <boost/range/algorithm/binary_search.hpp>
+#include <boost/range/algorithm/find_if.hpp>
+
+// Local
+#include "utils/remove_if.h"
+#define ENABLE_QJSVALUE_CONTINUATION
+#include "utils/continue_with.h"
+#include "utils/model_updaters.h"
+
+using kamd::utils::continue_with;
+
+namespace KActivities
+{
+namespace Imports
+{
+class ActivityModel::Private
+{
+public:
+ DECLARE_RAII_MODEL_UPDATERS(ActivityModel)
+
+ /**
+ * Returns whether the activity has a desired state.
+ * If the state is 0, returns true
+ */
+ template<typename T>
+ static inline bool matchingState(InfoPtr activity, T states)
+ {
+ // Are we filtering activities on their states?
+ if (!states.empty() && !boost::binary_search(states, activity->state())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Searches for the activity.
+ * Returns an option(index, iterator) for the found activity.
+ */
+ template<typename _Container>
+ static inline boost::optional<std::pair<unsigned int, typename _Container::const_iterator>> activityPosition(const _Container &container,
+ const QString &activityId)
+ {
+ using ActivityPosition = decltype(activityPosition(container, activityId));
+ using ContainerElement = typename _Container::value_type;
+
+ auto position = boost::find_if(container, [&](const ContainerElement &activity) {
+ return activity->id() == activityId;
+ });
+
+ return (position != container.end()) ? ActivityPosition(std::make_pair(position - container.begin(), position)) : ActivityPosition();
+ }
+
+ /**
+ * Notifies the model that an activity was updated
+ */
+ template<typename _Model, typename _Container>
+ static inline void emitActivityUpdated(_Model *model, const _Container &container, QObject *activityInfo, int role)
+ {
+ const auto activity = static_cast<Info *>(activityInfo);
+ emitActivityUpdated(model, container, activity->id(), role);
+ }
+
+ /**
+ * Notifies the model that an activity was updated
+ */
+ template<typename _Model, typename _Container>
+ static inline void emitActivityUpdated(_Model *model, const _Container &container, const QString &activity, int role)
+ {
+ auto position = Private::activityPosition(container, activity);
+
+ if (position) {
+ Q_EMIT model->dataChanged(model->index(position->first),
+ model->index(position->first),
+ role == Qt::DecorationRole ? QVector<int>{role, ActivityModel::ActivityIcon} : QVector<int>{role});
+ }
+ }
+
+ class BackgroundCache
+ {
+ public:
+ BackgroundCache()
+ : initialized(false)
+ , plasmaConfig(QStringLiteral("plasma-org.kde.plasma.desktop-appletsrc"))
+ {
+ using namespace std::placeholders;
+
+ const QString configFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + plasmaConfig.name();
+
+ KDirWatch::self()->addFile(configFile);
+
+ connect(KDirWatch::self(), &KDirWatch::dirty, std::bind(&BackgroundCache::settingsFileChanged, this, _1));
+ connect(KDirWatch::self(), &KDirWatch::created, std::bind(&BackgroundCache::settingsFileChanged, this, _1));
+ }
+
+ void settingsFileChanged(const QString &file)
+ {
+ if (!file.endsWith(plasmaConfig.name())) {
+ return;
+ }
+
+ plasmaConfig.reparseConfiguration();
+
+ if (initialized) {
+ reload(false);
+ }
+ }
+
+ void subscribe(ActivityModel *model)
+ {
+ if (!initialized) {
+ reload(true);
+ }
+
+ models << model;
+ }
+
+ void unsubscribe(ActivityModel *model)
+ {
+ models.removeAll(model);
+
+ if (models.isEmpty()) {
+ initialized = false;
+ forActivity.clear();
+ }
+ }
+
+ QString backgroundFromConfig(const KConfigGroup &config) const
+ {
+ auto wallpaperPlugin = config.readEntry("wallpaperplugin");
+ auto wallpaperConfig = config.group("Wallpaper").group(wallpaperPlugin).group("General");
+
+ if (wallpaperConfig.hasKey("Image")) {
+ // Trying for the wallpaper
+ auto wallpaper = wallpaperConfig.readEntry("Image", QString());
+ if (!wallpaper.isEmpty()) {
+ return wallpaper;
+ }
+ }
+ if (wallpaperConfig.hasKey("Color")) {
+ auto backgroundColor = wallpaperConfig.readEntry("Color", QColor(0, 0, 0));
+ return backgroundColor.name();
+ }
+
+ return QString();
+ }
+
+ void reload(bool fullReload)
+ {
+ QHash<QString, QString> newBackgrounds;
+
+ if (fullReload) {
+ forActivity.clear();
+ }
+
+ QStringList changedBackgrounds;
+
+ for (const auto &cont : plasmaConfigContainments().groupList()) {
+ auto config = plasmaConfigContainments().group(cont);
+ auto activityId = config.readEntry("activityId", QString());
+
+ // Ignore if it has no assigned activity
+ if (activityId.isEmpty()) {
+ continue;
+ }
+
+ // Ignore if we have already found the background
+ if (newBackgrounds.contains(activityId) && newBackgrounds[activityId][0] != QLatin1Char('#')) {
+ continue;
+ }
+
+ auto newBackground = backgroundFromConfig(config);
+
+ if (forActivity[activityId] != newBackground) {
+ changedBackgrounds << activityId;
+ if (!newBackground.isEmpty()) {
+ newBackgrounds[activityId] = newBackground;
+ }
+ }
+ }
+
+ initialized = true;
+
+ if (!changedBackgrounds.isEmpty()) {
+ forActivity = newBackgrounds;
+
+ for (auto model : models) {
+ model->backgroundsUpdated(changedBackgrounds);
+ }
+ }
+ }
+
+ KConfigGroup plasmaConfigContainments()
+ {
+ return plasmaConfig.group("Containments");
+ }
+
+ QHash<QString, QString> forActivity;
+ QList<ActivityModel *> models;
+
+ bool initialized;
+ KConfig plasmaConfig;
+ };
+
+ static BackgroundCache &backgrounds()
+ {
+ // If you convert this to a shared pointer,
+ // fix the connections to KDirWatcher
+ static BackgroundCache cache;
+ return cache;
+ }
+};
+
+ActivityModel::ActivityModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+ // Initializing role names for qml
+ connect(&m_service, &Consumer::serviceStatusChanged, this, &ActivityModel::setServiceStatus);
+
+ connect(&m_service, &KActivities::Consumer::activityAdded, this, [this](const QString &id) {
+ onActivityAdded(id);
+ });
+ connect(&m_service, &KActivities::Consumer::activityRemoved, this, &ActivityModel::onActivityRemoved);
+ connect(&m_service, &KActivities::Consumer::currentActivityChanged, this, &ActivityModel::onCurrentActivityChanged);
+
+ setServiceStatus(m_service.serviceStatus());
+
+ Private::backgrounds().subscribe(this);
+}
+
+ActivityModel::~ActivityModel()
+{
+ Private::backgrounds().unsubscribe(this);
+}
+
+QHash<int, QByteArray> ActivityModel::roleNames() const
+{
+ return {{Qt::DisplayRole, "name"},
+ {Qt::DecorationRole, "icon"},
+
+ {ActivityState, "state"},
+ {ActivityId, "id"},
+ {ActivityIcon, "iconSource"},
+ {ActivityDescription, "description"},
+ {ActivityBackground, "background"},
+ {ActivityCurrent, "current"}};
+}
+
+void ActivityModel::setServiceStatus(Consumer::ServiceStatus)
+{
+ replaceActivities(m_service.activities());
+}
+
+void ActivityModel::replaceActivities(const QStringList &activities)
+{
+ // qDebug() << m_shownStatesString << "New list of activities: "
+ // << activities;
+ // qDebug() << m_shownStatesString << " -- RESET MODEL -- ";
+
+ Private::model_reset m(this);
+
+ m_knownActivities.clear();
+ m_shownActivities.clear();
+
+ for (const QString &activity : activities) {
+ onActivityAdded(activity, false);
+ }
+}
+
+void ActivityModel::onActivityAdded(const QString &id, bool notifyClients)
+{
+ auto info = registerActivity(id);
+
+ // qDebug() << m_shownStatesString << "Added a new activity:" << info->id()
+ // << " " << info->name();
+
+ showActivity(info, notifyClients);
+}
+
+void ActivityModel::onActivityRemoved(const QString &id)
+{
+ // qDebug() << m_shownStatesString << "Removed an activity:" << id;
+
+ hideActivity(id);
+ unregisterActivity(id);
+}
+
+void ActivityModel::onCurrentActivityChanged(const QString &id)
+{
+ Q_UNUSED(id);
+
+ for (const auto &activity : m_shownActivities) {
+ Private::emitActivityUpdated(this, m_shownActivities, activity->id(), ActivityCurrent);
+ }
+}
+
+ActivityModel::InfoPtr ActivityModel::registerActivity(const QString &id)
+{
+ auto position = Private::activityPosition(m_knownActivities, id);
+
+ // qDebug() << m_shownStatesString << "Registering activity: " << id
+ // << " new? not " << (bool)position;
+
+ if (position) {
+ return *(position->second);
+
+ } else {
+ auto activityInfo = std::make_shared<Info>(id);
+
+ auto ptr = activityInfo.get();
+
+ connect(ptr, &Info::nameChanged, this, &ActivityModel::onActivityNameChanged);
+ connect(ptr, &Info::descriptionChanged, this, &ActivityModel::onActivityDescriptionChanged);
+ connect(ptr, &Info::iconChanged, this, &ActivityModel::onActivityIconChanged);
+ connect(ptr, &Info::stateChanged, this, &ActivityModel::onActivityStateChanged);
+
+ m_knownActivities.insert(InfoPtr(activityInfo));
+
+ return activityInfo;
+ }
+}
+
+void ActivityModel::unregisterActivity(const QString &id)
+{
+ // qDebug() << m_shownStatesString << "Deregistering activity: " << id;
+
+ auto position = Private::activityPosition(m_knownActivities, id);
+
+ if (position) {
+ if (auto shown = Private::activityPosition(m_shownActivities, id)) {
+ Private::model_remove(this, QModelIndex(), shown->first, shown->first);
+ m_shownActivities.erase(shown->second);
+ }
+
+ m_knownActivities.erase(position->second);
+ }
+}
+
+void ActivityModel::showActivity(InfoPtr activityInfo, bool notifyClients)
+{
+ // Should it really be shown?
+ if (!Private::matchingState(activityInfo, m_shownStates)) {
+ return;
+ }
+
+ // Is it already shown?
+ if (boost::binary_search(m_shownActivities, activityInfo, InfoPtrComparator())) {
+ return;
+ }
+
+ auto registeredPosition = Private::activityPosition(m_knownActivities, activityInfo->id());
+
+ if (!registeredPosition) {
+ qDebug() << "Got a request to show an unknown activity, ignoring";
+ return;
+ }
+
+ auto activityInfoPtr = *(registeredPosition->second);
+
+ // qDebug() << m_shownStatesString << "Setting activity visibility to true:"
+ // << activityInfoPtr->id() << activityInfoPtr->name();
+
+ auto position = m_shownActivities.insert(activityInfoPtr);
+
+ if (notifyClients) {
+ unsigned int index = (position.second ? position.first : m_shownActivities.end()) - m_shownActivities.begin();
+
+ // qDebug() << m_shownStatesString << " -- MODEL INSERT -- " << index;
+ Private::model_insert(this, QModelIndex(), index, index);
+ }
+}
+
+void ActivityModel::hideActivity(const QString &id)
+{
+ auto position = Private::activityPosition(m_shownActivities, id);
+
+ // qDebug() << m_shownStatesString
+ // << "Setting activity visibility to false: " << id;
+
+ if (position) {
+ // qDebug() << m_shownStatesString << " -- MODEL REMOVE -- "
+ // << position->first;
+ Private::model_remove(this, QModelIndex(), position->first, position->first);
+ m_shownActivities.erase(position->second);
+ }
+}
+// clang-format off
+#define CREATE_SIGNAL_EMITTER(What,Role) \
+ void ActivityModel::onActivity##What##Changed(const QString &) \
+ { \
+ Private::emitActivityUpdated(this, m_shownActivities, sender(), Role); \
+ }
+// clang-format on
+
+CREATE_SIGNAL_EMITTER(Name, Qt::DisplayRole)
+CREATE_SIGNAL_EMITTER(Description, ActivityDescription)
+CREATE_SIGNAL_EMITTER(Icon, Qt::DecorationRole)
+
+#undef CREATE_SIGNAL_EMITTER
+
+void ActivityModel::onActivityStateChanged(Info::State state)
+{
+ if (m_shownStates.empty()) {
+ Private::emitActivityUpdated(this, m_shownActivities, sender(), ActivityState);
+
+ } else {
+ auto info = findActivity(sender());
+
+ if (!info) {
+ return;
+ }
+
+ if (boost::binary_search(m_shownStates, state)) {
+ showActivity(info, true);
+ } else {
+ hideActivity(info->id());
+ }
+ }
+}
+
+void ActivityModel::backgroundsUpdated(const QStringList &activities)
+{
+ for (const auto &activity : activities) {
+ Private::emitActivityUpdated(this, m_shownActivities, activity, ActivityBackground);
+ }
+}
+
+void ActivityModel::setShownStates(const QString &states)
+{
+ m_shownStates.clear();
+ m_shownStatesString = states;
+
+ for (const auto &state : states.split(QLatin1Char(','))) {
+ if (state == QLatin1String("Running")) {
+ m_shownStates.insert(Running);
+
+ } else if (state == QLatin1String("Starting")) {
+ m_shownStates.insert(Starting);
+
+ } else if (state == QLatin1String("Stopped")) {
+ m_shownStates.insert(Stopped);
+
+ } else if (state == QLatin1String("Stopping")) {
+ m_shownStates.insert(Stopping);
+ }
+ }
+
+ replaceActivities(m_service.activities());
+
+ Q_EMIT shownStatesChanged(states);
+}
+
+QString ActivityModel::shownStates() const
+{
+ return m_shownStatesString;
+}
+
+int ActivityModel::rowCount(const QModelIndex &parent) const
+{
+ Q_UNUSED(parent);
+
+ return m_shownActivities.size();
+}
+
+QVariant ActivityModel::data(const QModelIndex &index, int role) const
+{
+ const int row = index.row();
+ const auto &item = *(m_shownActivities.cbegin() + row);
+
+ switch (role) {
+ case Qt::DisplayRole:
+ return item->name();
+
+ case Qt::DecorationRole:
+ return QIcon::fromTheme(data(index, ActivityIcon).toString());
+
+ case ActivityId:
+ return item->id();
+
+ case ActivityState:
+ return item->state();
+
+ case ActivityIcon: {
+ const QString &icon = item->icon();
+
+ // We need a default icon for activities
+ return icon.isEmpty() ? QStringLiteral("activities") : icon;
+ }
+
+ case ActivityDescription:
+ return item->description();
+
+ case ActivityCurrent:
+ return m_service.currentActivity() == item->id();
+
+ case ActivityBackground:
+ return Private::backgrounds().forActivity[item->id()];
+
+ default:
+ return QVariant();
+ }
+}
+
+QVariant ActivityModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ Q_UNUSED(section);
+ Q_UNUSED(orientation);
+ Q_UNUSED(role);
+
+ return QVariant();
+}
+
+ActivityModel::InfoPtr ActivityModel::findActivity(QObject *ptr) const
+{
+ auto info = boost::find_if(m_knownActivities, [ptr](const InfoPtr &info) {
+ return ptr == info.get();
+ });
+
+ if (info == m_knownActivities.end()) {
+ return nullptr;
+ } else {
+ return *info;
+ }
+}
+
+// clang-format off
+// QFuture<void> Controller::setActivityWhat(id, value)
+#define CREATE_SETTER(What) \
+ void ActivityModel::setActivity##What( \
+ const QString &id, const QString &value, const QJSValue &callback) \
+ { \
+ continue_with(m_service.setActivity##What(id, value), callback); \
+ }
+// clang-format on
+
+CREATE_SETTER(Name)
+CREATE_SETTER(Description)
+CREATE_SETTER(Icon)
+
+#undef CREATE_SETTER
+
+// QFuture<bool> Controller::setCurrentActivity(id)
+void ActivityModel::setCurrentActivity(const QString &id, const QJSValue &callback)
+{
+ continue_with(m_service.setCurrentActivity(id), callback);
+}
+
+// QFuture<QString> Controller::addActivity(name)
+void ActivityModel::addActivity(const QString &name, const QJSValue &callback)
+{
+ continue_with(m_service.addActivity(name), callback);
+}
+
+// QFuture<void> Controller::removeActivity(id)
+void ActivityModel::removeActivity(const QString &id, const QJSValue &callback)
+{
+ continue_with(m_service.removeActivity(id), callback);
+}
+
+// QFuture<void> Controller::stopActivity(id)
+void ActivityModel::stopActivity(const QString &id, const QJSValue &callback)
+{
+ continue_with(m_service.stopActivity(id), callback);
+}
+
+// QFuture<void> Controller::startActivity(id)
+void ActivityModel::startActivity(const QString &id, const QJSValue &callback)
+{
+ continue_with(m_service.startActivity(id), callback);
+}
+
+} // namespace Imports
+} // namespace KActivities
+
+// #include "activitymodel.moc"
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2012, 2013, 2014 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef KACTIVITIES_IMPORTS_ACTIVITY_MODEL_H
+#define KACTIVITIES_IMPORTS_ACTIVITY_MODEL_H
+
+// Qt
+#include <QAbstractListModel>
+#include <QCollator>
+#include <QJSValue>
+#include <QObject>
+
+// STL and Boost
+#include <boost/container/flat_set.hpp>
+#include <memory>
+
+// Local
+#include <lib/consumer.h>
+#include <lib/controller.h>
+#include <lib/info.h>
+
+class QModelIndex;
+class QDBusPendingCallWatcher;
+
+namespace KActivities
+{
+namespace Imports
+{
+/**
+ * ActivityModel
+ */
+
+class ActivityModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString shownStates READ shownStates WRITE setShownStates NOTIFY shownStatesChanged)
+
+public:
+ explicit ActivityModel(QObject *parent = nullptr);
+ ~ActivityModel() override;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+
+ QHash<int, QByteArray> roleNames() const override;
+
+ enum Roles {
+ ActivityId = Qt::UserRole,
+ ActivityDescription = Qt::UserRole + 1,
+ ActivityIcon = Qt::UserRole + 2,
+ ActivityState = Qt::UserRole + 3,
+ ActivityBackground = Qt::UserRole + 4,
+ ActivityCurrent = Qt::UserRole + 5,
+ };
+
+ enum State {
+ All = 0,
+ Invalid = 0,
+ Running = 2,
+ Starting = 3,
+ Stopped = 4,
+ Stopping = 5,
+ };
+ Q_ENUM(State)
+
+public Q_SLOTS:
+ // Activity control methods
+ void setActivityName(const QString &id, const QString &name, const QJSValue &callback);
+ void setActivityDescription(const QString &id, const QString &description, const QJSValue &callback);
+ void setActivityIcon(const QString &id, const QString &icon, const QJSValue &callback);
+
+ void setCurrentActivity(const QString &id, const QJSValue &callback);
+
+ void addActivity(const QString &name, const QJSValue &callback);
+ void removeActivity(const QString &id, const QJSValue &callback);
+
+ void stopActivity(const QString &id, const QJSValue &callback);
+ void startActivity(const QString &id, const QJSValue &callback);
+
+ // Model property getters and setters
+ void setShownStates(const QString &states);
+ QString shownStates() const;
+
+Q_SIGNALS:
+ void shownStatesChanged(const QString &state);
+
+private Q_SLOTS:
+ void onActivityNameChanged(const QString &name);
+ void onActivityDescriptionChanged(const QString &description);
+ void onActivityIconChanged(const QString &icon);
+ void onActivityStateChanged(KActivities::Info::State state);
+
+ void replaceActivities(const QStringList &activities);
+ void onActivityAdded(const QString &id, bool notifyClients = true);
+ void onActivityRemoved(const QString &id);
+ void onCurrentActivityChanged(const QString &id);
+
+ void setServiceStatus(KActivities::Consumer::ServiceStatus status);
+
+private:
+ KActivities::Controller m_service;
+ boost::container::flat_set<State> m_shownStates;
+ QString m_shownStatesString;
+
+ typedef std::shared_ptr<Info> InfoPtr;
+
+ struct InfoPtrComparator {
+ bool operator()(const InfoPtr &left, const InfoPtr &right) const
+ {
+ QCollator c;
+ c.setCaseSensitivity(Qt::CaseInsensitive);
+ c.setNumericMode(true);
+ int rc = c.compare(left->name(), right->name());
+ if (rc == 0) {
+ return left->id() < right->id();
+ }
+ return rc < 0;
+ }
+ };
+
+ boost::container::flat_set<InfoPtr, InfoPtrComparator> m_knownActivities;
+ boost::container::flat_set<InfoPtr, InfoPtrComparator> m_shownActivities;
+
+ InfoPtr registerActivity(const QString &id);
+ void unregisterActivity(const QString &id);
+ void showActivity(InfoPtr activityInfo, bool notifyClients);
+ void hideActivity(const QString &id);
+ void backgroundsUpdated(const QStringList &activities);
+
+ InfoPtr findActivity(QObject *ptr) const;
+
+ class Private;
+ friend class Private;
+};
+
+} // namespace Imports
+} // namespace KActivities
+
+#endif // KACTIVITIES_IMPORTS_ACTIVITY_MODEL_H
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2011-2015 Marco Martin <mart@kde.org>
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include "resourceinstance.h"
+
+#include <QQuickWindow>
+#include <QTimer>
+
+#include <KActivities/ResourceInstance>
+#include <QDebug>
+
+namespace KActivities
+{
+namespace Imports
+{
+ResourceInstance::ResourceInstance(QQuickItem *parent)
+ : QQuickItem(parent)
+{
+ m_syncTimer = new QTimer(this);
+ m_syncTimer->setSingleShot(true);
+ connect(m_syncTimer, &QTimer::timeout, this, &ResourceInstance::syncWid);
+}
+
+ResourceInstance::~ResourceInstance()
+{
+}
+
+void ResourceInstance::syncWid()
+{
+ QWindow *w = window();
+ if (!w) {
+ return;
+ }
+
+ WId wid = w->winId();
+ if (!m_resourceInstance || m_resourceInstance->winId() != wid) {
+ // qDebug() << "Creating a new instance of the resource" << m_uri << "window id" << wid;
+ m_resourceInstance.reset(new KActivities::ResourceInstance(wid, m_uri, m_mimetype, m_title));
+ } else {
+ m_resourceInstance->setUri(m_uri);
+ m_resourceInstance->setMimetype(m_mimetype);
+ m_resourceInstance->setTitle(m_title);
+ }
+}
+
+QUrl ResourceInstance::uri() const
+{
+ return m_uri;
+}
+
+void ResourceInstance::setUri(const QUrl &uri)
+{
+ if (m_uri == uri) {
+ return;
+ }
+
+ m_uri = uri.adjusted(QUrl::StripTrailingSlash);
+ m_syncTimer->start(100);
+}
+
+QString ResourceInstance::mimetype() const
+{
+ return m_mimetype;
+}
+
+void ResourceInstance::setMimetype(const QString &mimetype)
+{
+ if (m_mimetype == mimetype) {
+ return;
+ }
+ m_mimetype = mimetype;
+ m_syncTimer->start(100);
+}
+
+QString ResourceInstance::title() const
+{
+ return m_title;
+}
+
+void ResourceInstance::setTitle(const QString &title)
+{
+ if (m_title == title) {
+ return;
+ }
+ m_title = title;
+ m_syncTimer->start(100);
+}
+
+void ResourceInstance::notifyModified()
+{
+ // ensure the resource instance exists
+ syncWid();
+ m_resourceInstance->notifyModified();
+}
+
+void ResourceInstance::notifyFocusedIn()
+{
+ // ensure the resource instance exists
+ syncWid();
+ m_resourceInstance->notifyFocusedIn();
+}
+
+void ResourceInstance::notifyFocusedOut()
+{
+ // ensure the resource instance exists
+ syncWid();
+ m_resourceInstance->notifyFocusedOut();
+}
+
+}
+}
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2011-2015 Marco Martin <mart@kde.org>
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef RESOURCEINSTANCE_H
+#define RESOURCEINSTANCE_H
+
+// Qt
+#include <QQuickItem>
+#include <QUrl>
+
+// STL
+#include <memory>
+
+namespace KActivities
+{
+class ResourceInstance;
+}
+
+class QTimer;
+
+namespace KActivities
+{
+namespace Imports
+{
+class ResourceInstance : public QQuickItem
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QUrl uri READ uri WRITE setUri NOTIFY uriChanged)
+ Q_PROPERTY(QString mimetype READ mimetype WRITE setMimetype NOTIFY mimetypeChanged)
+ Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
+
+public:
+ explicit ResourceInstance(QQuickItem *parent = nullptr);
+ ~ResourceInstance() override;
+
+ QUrl uri() const;
+ void setUri(const QUrl &uri);
+
+ QString mimetype() const;
+ void setMimetype(const QString &mimetype);
+
+ QString title() const;
+ void setTitle(const QString &title);
+
+protected Q_SLOTS:
+ void syncWid();
+
+Q_SIGNALS:
+ void uriChanged();
+ void mimetypeChanged();
+ void titleChanged();
+
+public Q_SLOTS:
+ /**
+ * Call this method to notify the system that you modified
+ * (the contents of) the resource
+ */
+ void notifyModified();
+
+ /**
+ * Call this method to notify the system that the resource
+ * has the focus in your application
+ * @note You only need to call this in MDI applications
+ */
+ void notifyFocusedIn();
+
+ /**
+ * Call this method to notify the system that the resource
+ * lost the focus in your application
+ * @note You only need to call this in MDI applications
+ */
+ void notifyFocusedOut();
+
+private:
+ std::unique_ptr<KActivities::ResourceInstance> m_resourceInstance;
+ QUrl m_uri;
+ QString m_mimetype;
+ QString m_title;
+ QTimer *m_syncTimer;
+};
+
+}
+}
+
+#endif
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2012, 2013, 2014 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+// Self
+#include "resourcemodel.h"
+
+// Qt
+#include <QByteArray>
+#include <QCoreApplication>
+#include <QDebug>
+#include <QModelIndex>
+#include <QSqlQuery>
+#include <QUuid>
+
+// KDE
+#include <KConfig>
+#include <KDesktopFile>
+#include <KFileItem>
+#include <ksharedconfig.h>
+
+// STL and Boost
+#include <boost/algorithm/string/join.hpp>
+#include <boost/range/adaptor/filtered.hpp>
+#include <boost/range/adaptor/transformed.hpp>
+#include <boost/range/algorithm/find_if.hpp>
+#include <boost/range/numeric.hpp>
+#include <mutex>
+
+// Local
+#include "common/dbus/common.h"
+#include "utils/dbusfuture_p.h"
+#include "utils/range.h"
+
+#define ENABLE_QJSVALUE_CONTINUATION
+#include "utils/continue_with.h"
+
+#define ACTIVITY_COLUMN 0
+#define AGENT_COLUMN 1
+#define RESOURCE_COLUMN 2
+#define UNKNOWN_COLUMN 3
+
+using kamd::utils::continue_with;
+
+namespace KActivities
+{
+namespace Imports
+{
+class ResourceModel::LinkerService : public QDBusInterface
+{
+private:
+ LinkerService()
+ : KAMD_DBUS_INTERFACE("Resources/Linking", ResourcesLinking, nullptr)
+ {
+ }
+
+public:
+ static std::shared_ptr<LinkerService> self()
+ {
+ static std::weak_ptr<LinkerService> s_instance;
+ static std::mutex singleton;
+
+ std::lock_guard<std::mutex> singleton_lock(singleton);
+
+ auto result = s_instance.lock();
+
+ if (s_instance.expired()) {
+ result.reset(new LinkerService());
+ s_instance = result;
+ }
+
+ return result;
+ }
+};
+
+ResourceModel::ResourceModel(QObject *parent)
+ : QSortFilterProxyModel(parent)
+ , m_shownActivities(QStringLiteral(":current"))
+ , m_shownAgents(QStringLiteral(":current"))
+ , m_defaultItemsLoaded(false)
+ , m_linker(LinkerService::self())
+ , m_config(KSharedConfig::openConfig("kactivitymanagerd-resourcelinkingrc")->group("Order"))
+{
+ // NOTE: What to do if the file does not exist?
+ // Ignoring that case since the daemon creates it on startup.
+ // Is it plausible that somebody will instantiate the ResourceModel
+ // before the daemon is started?
+
+ const QString databaseDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kactivitymanagerd/resources/");
+
+ m_databaseFile = databaseDir + QStringLiteral("database");
+
+ loadDatabase();
+
+ connect(&m_service, &KActivities::Consumer::currentActivityChanged, this, &ResourceModel::onCurrentActivityChanged);
+
+ connect(m_linker.get(), SIGNAL(ResourceLinkedToActivity(QString, QString, QString)), this, SLOT(onResourceLinkedToActivity(QString, QString, QString)));
+ connect(m_linker.get(),
+ SIGNAL(ResourceUnlinkedFromActivity(QString, QString, QString)),
+ this,
+ SLOT(onResourceUnlinkedFromActivity(QString, QString, QString)));
+
+ setDynamicSortFilter(true);
+ sort(0);
+}
+
+bool ResourceModel::loadDatabase()
+{
+ if (m_database.isValid())
+ return true;
+ if (!QFile(m_databaseFile).exists())
+ return false;
+
+ // TODO: Database connection naming could be smarter (thread-id-based,
+ // reusing connections...?)
+ m_database = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), QStringLiteral("kactivities_db_resources_") + QString::number((quintptr)this));
+
+ // qDebug() << "Database file is: " << m_databaseFile;
+ m_database.setDatabaseName(m_databaseFile);
+
+ m_database.open();
+
+ m_databaseModel = new QSqlTableModel(this, m_database);
+ m_databaseModel->setTable("ResourceLink");
+ m_databaseModel->select();
+
+ setSourceModel(m_databaseModel);
+
+ reloadData();
+
+ return true;
+}
+
+ResourceModel::~ResourceModel()
+{
+}
+
+QVariant ResourceModel::dataForColumn(const QModelIndex &index, int column) const
+{
+ if (!m_database.isValid())
+ return QVariant();
+
+ return m_databaseModel->data(index.sibling(index.row(), column), Qt::DisplayRole);
+}
+
+bool ResourceModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
+{
+ const auto leftResource = dataForColumn(left, RESOURCE_COLUMN).toString();
+ const auto rightResource = dataForColumn(right, RESOURCE_COLUMN).toString();
+
+ const bool hasLeft = m_sorting.contains(leftResource);
+ const bool hasRight = m_sorting.contains(rightResource);
+
+ return (hasLeft && !hasRight) ? true
+ : (!hasLeft && hasRight) ? false
+ : (hasLeft && hasRight) ? m_sorting.indexOf(leftResource) < m_sorting.indexOf(rightResource)
+ : QString::compare(leftResource, rightResource, Qt::CaseInsensitive) < 0;
+}
+
+QHash<int, QByteArray> ResourceModel::roleNames() const
+{
+ return {{Qt::DisplayRole, "display"},
+ {Qt::DecorationRole, "decoration"},
+ {ResourceRole, "uri"},
+ {AgentRole, "agent"},
+ {ActivityRole, "activity"},
+ {DescriptionRole, "subtitle"}};
+}
+
+template<typename Validator>
+inline QStringList validateList(const QString &values, Validator validator)
+{
+ using boost::adaptors::filtered;
+ using kamd::utils::as_collection;
+
+ auto result = as_collection<QStringList>(values.split(',') | filtered(validator));
+
+ if (result.isEmpty()) {
+ result.append(QStringLiteral(":current"));
+ }
+
+ return result;
+}
+
+void ResourceModel::setShownActivities(const QString &activities)
+{
+ m_shownActivities = validateList(activities, [&](const QString &activity) {
+ return activity == ":current" || activity == ":any" || activity == ":global" || !QUuid(activity).isNull();
+ });
+
+ reloadData();
+ Q_EMIT shownActivitiesChanged();
+}
+
+void ResourceModel::setShownAgents(const QString &agents)
+{
+ m_shownAgents = validateList(agents, [&](const QString &agent) {
+ return agent == ":current" || agent == ":any" || agent == ":global" || (!agent.isEmpty() && !agent.contains('\'') && !agent.contains('"'));
+ });
+
+ loadDefaultsIfNeeded();
+ reloadData();
+ Q_EMIT shownAgentsChanged();
+}
+
+QString ResourceModel::shownActivities() const
+{
+ return m_shownActivities.join(',');
+}
+
+QString ResourceModel::shownAgents() const
+{
+ return m_shownAgents.join(',');
+}
+
+QString ResourceModel::defaultItemsConfig() const
+{
+ return m_defaultItemsConfig;
+}
+
+void ResourceModel::setDefaultItemsConfig(const QString &defaultItemsConfig)
+{
+ m_defaultItemsConfig = defaultItemsConfig;
+ loadDefaultsIfNeeded();
+}
+
+QString ResourceModel::activityToWhereClause(const QString &shownActivity) const
+{
+ return QStringLiteral(" OR usedActivity=")
+ + (shownActivity == ":current" ? "'" + m_service.currentActivity() + "'"
+ : shownActivity == ":any" ? "usedActivity"
+ : shownActivity == ":global" ? "''"
+ : "'" + shownActivity + "'");
+}
+
+QString ResourceModel::agentToWhereClause(const QString &shownAgent) const
+{
+ return QStringLiteral(" OR initiatingAgent=")
+ + (shownAgent == ":current" ? "'" + QCoreApplication::applicationName() + "'"
+ : shownAgent == ":any" ? "initiatingAgent"
+ : shownAgent == ":global" ? "''"
+ : "'" + shownAgent + "'");
+}
+
+QString ResourceModel::whereClause(const QStringList &activities, const QStringList &agents) const
+{
+ using boost::accumulate;
+ using namespace kamd::utils;
+
+ // qDebug() << "Getting the where clause for: " << activities << " " << agents;
+
+ // Defining the transformation functions for generating the SQL WHERE clause
+ // from the specified activity/agent. They also resolve the special values
+ // like :current, :any and :global.
+
+ auto activityToWhereClause = transformed(&ResourceModel::activityToWhereClause, this);
+ auto agentToWhereClause = transformed(&ResourceModel::agentToWhereClause, this);
+
+ // Generating the SQL WHERE part by concatenating the generated clauses.
+ // The generated query will be in the form of '0 OR clause1 OR clause2 ...'
+
+ const QString whereActivity = accumulate(activities | activityToWhereClause, QStringLiteral("0"));
+
+ const QString whereAgent = accumulate(agents | agentToWhereClause, QStringLiteral("0"));
+
+ // qDebug() << "This is the filter: " << '(' + whereActivity + ") AND (" + whereAgent + ')';
+
+ return '(' + whereActivity + ") AND (" + whereAgent + ')';
+}
+
+void ResourceModel::reloadData()
+{
+ m_sorting = m_config.readEntry(m_shownAgents.first(), QStringList());
+
+ if (!m_database.isValid())
+ return;
+ m_databaseModel->setFilter(whereClause(m_shownActivities, m_shownAgents));
+}
+
+void ResourceModel::onCurrentActivityChanged(const QString &activity)
+{
+ Q_UNUSED(activity);
+
+ if (m_shownActivities.contains(":current")) {
+ reloadData();
+ }
+}
+
+QVariant ResourceModel::data(const QModelIndex &proxyIndex, int role) const
+{
+ auto index = mapToSource(proxyIndex);
+
+ if (role == Qt::DisplayRole || role == DescriptionRole || role == Qt::DecorationRole) {
+ auto uri = dataForColumn(index, RESOURCE_COLUMN).toString();
+
+ // TODO: Will probably need some more special handling -
+ // for application:/ and a few more
+
+ if (uri.startsWith('/')) {
+ uri = QLatin1String("file://") + uri;
+ }
+
+ KFileItem file(uri);
+ // clang-format off
+ if (file.mimetype() == "application/x-desktop") {
+ KDesktopFile desktop(file.localPath());
+
+ return role == Qt::DisplayRole ? desktop.readGenericName() :
+ role == DescriptionRole ? desktop.readName() :
+ role == Qt::DecorationRole ? desktop.readIcon() : QVariant();
+ }
+
+ return role == Qt::DisplayRole ? file.name() :
+ role == Qt::DecorationRole ? file.iconName() : QVariant();
+ }
+
+ return dataForColumn(index,
+ role == ResourceRole ? RESOURCE_COLUMN :
+ role == AgentRole ? AGENT_COLUMN :
+ role == ActivityRole ? ACTIVITY_COLUMN :
+ UNKNOWN_COLUMN
+ );
+ // clang-format on
+}
+
+void ResourceModel::linkResourceToActivity(const QString &resource, const QJSValue &callback) const
+{
+ linkResourceToActivity(resource, m_shownActivities.first(), callback);
+}
+
+void ResourceModel::linkResourceToActivity(const QString &resource, const QString &activity, const QJSValue &callback) const
+{
+ linkResourceToActivity(m_shownAgents.first(), resource, activity, callback);
+}
+
+void ResourceModel::linkResourceToActivity(const QString &agent, const QString &_resource, const QString &activity, const QJSValue &callback) const
+{
+ if (activity == ":any") {
+ qWarning() << ":any is not a valid activity specification for linking";
+ return;
+ }
+
+ auto resource = validateResource(_resource);
+
+ // qDebug() << "ResourceModel: Linking resource to activity: --------------------------------------------------\n"
+ // << "ResourceModel: Resource: " << resource << "\n"
+ // << "ResourceModel: Agents: " << agent << "\n"
+ // << "ResourceModel: Activities: " << activity << "\n";
+
+ kamd::utils::continue_with(DBusFuture::asyncCall<void>(m_linker.get(),
+ QStringLiteral("LinkResourceToActivity"),
+ agent,
+ resource,
+ activity == ":current" ? m_service.currentActivity()
+ : activity == ":global" ? ""
+ : activity),
+ callback);
+}
+
+void ResourceModel::unlinkResourceFromActivity(const QString &resource, const QJSValue &callback)
+{
+ unlinkResourceFromActivity(m_shownAgents, resource, m_shownActivities, callback);
+}
+
+void ResourceModel::unlinkResourceFromActivity(const QString &resource, const QString &activity, const QJSValue &callback)
+{
+ unlinkResourceFromActivity(m_shownAgents, resource, QStringList() << activity, callback);
+}
+
+void ResourceModel::unlinkResourceFromActivity(const QString &agent, const QString &resource, const QString &activity, const QJSValue &callback)
+{
+ unlinkResourceFromActivity(QStringList() << agent, resource, QStringList() << activity, callback);
+}
+
+void ResourceModel::unlinkResourceFromActivity(const QStringList &agents, const QString &_resource, const QStringList &activities, const QJSValue &callback)
+{
+ auto resource = validateResource(_resource);
+
+ // qDebug() << "ResourceModel: Unlinking resource from activity: ----------------------------------------------\n"
+ // << "ResourceModel: Resource: " << resource << "\n"
+ // << "ResourceModel: Agents: " << agents << "\n"
+ // << "ResourceModel: Activities: " << activities << "\n";
+
+ for (const auto &agent : agents) {
+ for (const auto &activity : activities) {
+ if (activity == ":any") {
+ qWarning() << ":any is not a valid activity specification for linking";
+ return;
+ }
+
+ // We might want to compose the continuations into one
+ // so that the callback gets called only once,
+ // but we don't care about that at the moment
+ kamd::utils::continue_with(DBusFuture::asyncCall<void>(m_linker.get(),
+ QStringLiteral("UnlinkResourceFromActivity"),
+ agent,
+ resource,
+ activity == ":current" ? m_service.currentActivity()
+ : activity == ":global" ? ""
+ : activity),
+ callback);
+ }
+ }
+}
+
+bool ResourceModel::isResourceLinkedToActivity(const QString &resource)
+{
+ return isResourceLinkedToActivity(m_shownAgents, resource, m_shownActivities);
+}
+
+bool ResourceModel::isResourceLinkedToActivity(const QString &resource, const QString &activity)
+{
+ return isResourceLinkedToActivity(m_shownAgents, resource, QStringList() << activity);
+}
+
+bool ResourceModel::isResourceLinkedToActivity(const QString &agent, const QString &resource, const QString &activity)
+{
+ return isResourceLinkedToActivity(QStringList() << agent, resource, QStringList() << activity);
+}
+
+bool ResourceModel::isResourceLinkedToActivity(const QStringList &agents, const QString &_resource, const QStringList &activities)
+{
+ if (!m_database.isValid())
+ return false;
+
+ auto resource = validateResource(_resource);
+
+ // qDebug() << "ResourceModel: Testing whether the resource is linked to activity: ----------------------------\n"
+ // << "ResourceModel: Resource: " << resource << "\n"
+ // << "ResourceModel: Agents: " << agents << "\n"
+ // << "ResourceModel: Activities: " << activities << "\n";
+
+ QSqlQuery query(m_database);
+ query.prepare(
+ "SELECT targettedResource "
+ "FROM ResourceLink "
+ "WHERE targettedResource=:resource AND "
+ + whereClause(activities, agents));
+ query.bindValue(":resource", resource);
+ query.exec();
+
+ auto result = query.next();
+
+ // qDebug() << "Query: " << query.lastQuery();
+ //
+ // if (query.lastError().isValid()) {
+ // qDebug() << "Error: " << query.lastError();
+ // }
+ //
+ // qDebug() << "Result: " << result;
+
+ return result;
+}
+
+void ResourceModel::onResourceLinkedToActivity(const QString &initiatingAgent, const QString &targettedResource, const QString &usedActivity)
+{
+ Q_UNUSED(targettedResource);
+
+ if (!loadDatabase())
+ return;
+
+ auto matchingActivity = boost::find_if(m_shownActivities, [&](const QString &shownActivity) {
+ return
+ // If the activity is not important
+ shownActivity == ":any" ||
+ // or we are listening for the changes for the current activity
+ (shownActivity == ":current" && usedActivity == m_service.currentActivity()) ||
+ // or we want the globally linked resources
+ (shownActivity == ":global" && usedActivity.isEmpty()) ||
+ // or we have a specific activity in mind
+ shownActivity == usedActivity;
+ });
+
+ auto matchingAgent = boost::find_if(m_shownAgents, [&](const QString &shownAgent) {
+ return
+ // If the agent is not important
+ shownAgent == ":any" ||
+ // or we are listening for the changes for the current agent
+ (shownAgent == ":current" && initiatingAgent == QCoreApplication::applicationName()) ||
+ // or for links that are global, and not related to a specific agent
+ (shownAgent == ":global" && initiatingAgent.isEmpty()) ||
+ // or we have a specific agent to listen for
+ shownAgent == initiatingAgent;
+ });
+
+ if (matchingActivity != m_shownActivities.end() && matchingAgent != m_shownAgents.end()) {
+ // TODO: This might be smarter possibly, but might collide
+ // with the SQL model. Implement a custom model with internal
+ // cache instead of basing it on QSqlModel.
+ reloadData();
+ }
+}
+
+void ResourceModel::onResourceUnlinkedFromActivity(const QString &initiatingAgent, const QString &targettedResource, const QString &usedActivity)
+{
+ // These are the same at the moment
+ onResourceLinkedToActivity(initiatingAgent, targettedResource, usedActivity);
+}
+
+void ResourceModel::setOrder(const QStringList &resources)
+{
+ m_sorting = resources;
+ m_config.writeEntry(m_shownAgents.first(), m_sorting);
+ m_config.sync();
+ invalidate();
+}
+
+void ResourceModel::move(int sourceItem, int destinationItem)
+{
+ QStringList resources;
+ const int rows = rowCount();
+
+ for (int row = 0; row < rows; row++) {
+ resources << resourceAt(row);
+ }
+
+ if (sourceItem < 0 || sourceItem >= rows || destinationItem < 0 || destinationItem >= rows) {
+ return;
+ }
+
+ // Moving one item from the source item's location to the location
+ // after the destination item
+ std::rotate(resources.begin() + sourceItem, resources.begin() + sourceItem + 1, resources.begin() + destinationItem + 1);
+
+ setOrder(resources);
+}
+
+void ResourceModel::sortItems(Qt::SortOrder sortOrder)
+{
+ typedef QPair<QString, QString> Resource;
+ QList<Resource> resources;
+ const int rows = rowCount();
+
+ for (int row = 0; row < rows; ++row) {
+ resources << qMakePair(resourceAt(row), displayAt(row));
+ }
+
+ std::sort(resources.begin(), resources.end(), [sortOrder](const Resource &left, const Resource &right) {
+ return sortOrder == Qt::AscendingOrder ? left.second < right.second : right.second < left.second;
+ });
+
+ QStringList result;
+
+ for (const auto &resource : std::as_const(resources)) {
+ result << resource.first;
+ }
+
+ setOrder(result);
+}
+
+KConfigGroup ResourceModel::config() const
+{
+ return KSharedConfig::openConfig("kactivitymanagerd-resourcelinkingrc")->group("Order");
+}
+
+int ResourceModel::count() const
+{
+ return QSortFilterProxyModel::rowCount();
+}
+
+QString ResourceModel::displayAt(int row) const
+{
+ return data(index(row, 0), Qt::DisplayRole).toString();
+}
+
+QString ResourceModel::resourceAt(int row) const
+{
+ return validateResource(data(index(row, 0), ResourceRole).toString());
+}
+
+void ResourceModel::loadDefaultsIfNeeded() const
+{
+ // Did we get a request to actually do anything?
+ if (m_defaultItemsConfig.isEmpty())
+ return;
+ if (m_shownAgents.size() == 0)
+ return;
+
+ // If we have already loaded the items, just exit
+ if (m_defaultItemsLoaded)
+ return;
+ m_defaultItemsLoaded = true;
+
+ // If there are items in the model, no need to load the defaults
+ if (count() != 0)
+ return;
+
+ // Did we already load the defaults for this agent?
+ QStringList alreadyInitialized = m_config.readEntry("defaultItemsProcessedFor", QStringList());
+ if (alreadyInitialized.contains(m_shownAgents.first()))
+ return;
+ alreadyInitialized << m_shownAgents.first();
+ m_config.writeEntry("defaultItemsProcessedFor", alreadyInitialized);
+ m_config.sync();
+
+ QStringList args = m_defaultItemsConfig.split("/");
+ QString configField = args.takeLast();
+ QString configGroup = args.takeLast();
+ QString configFile = args.join("/");
+
+ // qDebug() << "Config"
+ // << configFile << " "
+ // << configGroup << " "
+ // << configField << " ";
+
+ QStringList items = KSharedConfig::openConfig(configFile)->group(configGroup).readEntry(configField, QStringList());
+
+ for (const auto &item : items) {
+ // qDebug() << "Adding: " << item;
+ linkResourceToActivity(item, ":global", QJSValue());
+ }
+}
+
+QString ResourceModel::validateResource(const QString &resource) const
+{
+ return resource.startsWith(QLatin1String("file://")) ? QUrl(resource).toLocalFile() : resource;
+}
+
+} // namespace Imports
+} // namespace KActivities
+
+// #include "resourcemodel.moc"
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2012, 2013, 2014 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef KACTIVITIES_IMPORTS_RESOURCE_MODEL_H
+#define KACTIVITIES_IMPORTS_RESOURCE_MODEL_H
+
+// Qt
+#include <QJSValue>
+#include <QObject>
+#include <QSortFilterProxyModel>
+#include <QSqlDatabase>
+#include <QSqlTableModel>
+
+// KDE
+#include <KConfigGroup>
+
+// STL and Boost
+#include <memory>
+
+// Local
+#include <lib/consumer.h>
+#include <lib/controller.h>
+#include <lib/info.h>
+
+class QModelIndex;
+class QDBusPendingCallWatcher;
+
+namespace KActivities
+{
+namespace Imports
+{
+/**
+ * ResourceModel
+ */
+
+class ResourceModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+ /**
+ * Sets for which activities should the resources be shown for.
+ * Coma-separated values.
+ * Special values are:
+ * - ":current" for the current activity
+ * - ":any" show resources that are linked to any activity, including "global"
+ * - ":global" show resources that are globally linked
+ */
+ Q_PROPERTY(QString shownActivities READ shownActivities WRITE setShownActivities NOTIFY shownActivitiesChanged)
+
+ /**
+ * Sets for which agents should the resources be shown for.
+ * Coma-separated values.
+ * Special values are:
+ * - ":current" for the current application
+ * - ":any" show resources that are linked to any agent, including "global"
+ * - ":global" show resources that are globally linked
+ */
+ Q_PROPERTY(QString shownAgents READ shownAgents WRITE setShownAgents NOTIFY shownAgentsChanged)
+
+ /**
+ * If the model is empty, use this config file to read the default items.
+ * The default items are automatically linked globally, not per-activity.
+ * It needs to have the following format: 'config-namerc/ConfigGroup/ConfigEntry'.
+ * The config entry needs to be a list of strings.
+ */
+ Q_PROPERTY(QString defaultItemsConfig READ defaultItemsConfig WRITE setDefaultItemsConfig)
+
+public:
+ explicit ResourceModel(QObject *parent = nullptr);
+ ~ResourceModel() override;
+
+ enum Roles {
+ ResourceRole = Qt::UserRole,
+ ActivityRole = Qt::UserRole + 1,
+ AgentRole = Qt::UserRole + 2,
+ DescriptionRole = Qt::UserRole + 3,
+ };
+
+ QHash<int, QByteArray> roleNames() const override;
+
+ virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const override;
+
+public Q_SLOTS:
+ // Resource linking control methods
+ void linkResourceToActivity(const QString &resource, const QJSValue &callback) const;
+ void linkResourceToActivity(const QString &resource, const QString &activity, const QJSValue &callback) const;
+ void linkResourceToActivity(const QString &agent, const QString &resource, const QString &activity, const QJSValue &callback) const;
+
+ void unlinkResourceFromActivity(const QString &resource, const QJSValue &callback);
+ void unlinkResourceFromActivity(const QString &resource, const QString &activity, const QJSValue &callback);
+ void unlinkResourceFromActivity(const QString &agent, const QString &resource, const QString &activity, const QJSValue &callback);
+ void unlinkResourceFromActivity(const QStringList &agents, const QString &resource, const QStringList &activities, const QJSValue &callback);
+
+ bool isResourceLinkedToActivity(const QString &resource);
+ bool isResourceLinkedToActivity(const QString &resource, const QString &activity);
+ bool isResourceLinkedToActivity(const QString &agent, const QString &resource, const QString &activity);
+ bool isResourceLinkedToActivity(const QStringList &agents, const QString &resource, const QStringList &activities);
+
+ // Model property getters and setters
+ void setShownActivities(const QString &activities);
+ QString shownActivities() const;
+
+ void setShownAgents(const QString &agents);
+ QString shownAgents() const;
+
+ QString defaultItemsConfig() const;
+ void setDefaultItemsConfig(const QString &defaultItemsConfig);
+
+ void setOrder(const QStringList &resources);
+ void move(int sourceItem, int destinationItem);
+ void sortItems(Qt::SortOrder sortOrder);
+
+ KConfigGroup config() const;
+
+ int count() const;
+ QString displayAt(int row) const;
+ QString resourceAt(int row) const;
+
+protected:
+ bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
+
+Q_SIGNALS:
+ void shownActivitiesChanged();
+ void shownAgentsChanged();
+
+private Q_SLOTS:
+ void onCurrentActivityChanged(const QString &activity);
+
+ void onResourceLinkedToActivity(const QString &initiatingAgent, const QString &targettedResource, const QString &usedActivity);
+ void onResourceUnlinkedFromActivity(const QString &initiatingAgent, const QString &targettedResource, const QString &usedActivity);
+
+private:
+ KActivities::Consumer m_service;
+
+ inline QVariant dataForColumn(const QModelIndex &index, int column) const;
+
+ QString activityToWhereClause(const QString &activity) const;
+ QString agentToWhereClause(const QString &agent) const;
+ QString whereClause(const QStringList &activities, const QStringList &agents) const;
+
+ void loadDefaultsIfNeeded() const;
+
+ bool loadDatabase();
+ QString m_databaseFile;
+ QSqlDatabase m_database;
+ QSqlTableModel *m_databaseModel;
+
+ QStringList m_shownActivities;
+ QStringList m_shownAgents;
+ QStringList m_sorting;
+
+ QString m_defaultItemsConfig;
+ mutable bool m_defaultItemsLoaded;
+
+ void reloadData();
+ QString validateResource(const QString &resource) const;
+
+ class LinkerService;
+ std::shared_ptr<LinkerService> m_linker;
+
+ mutable KConfigGroup m_config;
+};
+
+} // namespace Imports
+} // namespace KActivities
+
+#endif // KACTIVITIES_IMPORTS_RESOURCE_MODEL_H
--- /dev/null
+#ifndef CONFIG_FEATURES_H_
+#define CONFIG_FEATURES_H_
+
+#cmakedefine KAMD_DATA_DIR "@KAMD_DATA_DIR@"
+
+#cmakedefine KAMD_PLUGIN_DIR "@KAMD_PLUGIN_DIR@"
+#cmakedefine KAMD_FULL_PLUGIN_DIR "@KAMD_FULL_PLUGIN_DIR@"
+
+#cmakedefine KAMD_INSTALL_PREFIX "@KAMD_INSTALL_PREFIX@"
+
+#cmakedefine01 HAVE_CXX11_AUTO
+#cmakedefine01 HAVE_CXX11_NULLPTR
+#cmakedefine01 HAVE_CXX11_LAMBDA
+#cmakedefine01 HAVE_CXX11_OVERRIDE
+#cmakedefine01 HAVE_CXX_OVERRIDE_ATTR
+
+#endif
--- /dev/null
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+
+# =======================================================
+# Now that we finished with the boilerplate, start
+# with the library definition
+
+set (
+ KActivities_LIB_SRCS
+
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.cpp
+
+ consumer.cpp
+ controller.cpp
+ info.cpp
+ resourceinstance.cpp
+ activitiesmodel.cpp
+
+ mainthreadexecutor_p.cpp
+ manager_p.cpp
+ activitiescache_p.cpp
+
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/utils/dbusfuture_p.cpp
+
+ version.cpp
+ )
+
+set_source_files_properties (
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.xml
+ PROPERTIES
+ INCLUDE ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.h
+ )
+
+qt_add_dbus_interface (
+ KActivities_LIB_SRCS
+
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.xml
+ activities_interface
+ )
+
+qt_add_dbus_interface (
+ KActivities_LIB_SRCS
+
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Resources.xml
+ resources_interface
+ )
+
+qt_add_dbus_interface (
+ KActivities_LIB_SRCS
+
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Features.xml
+ features_interface
+ )
+
+qt_add_dbus_interface (
+ KActivities_LIB_SRCS
+
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml
+ resources_linking_interface
+ )
+
+qt_add_dbus_interface (
+ KActivities_LIB_SRCS
+
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Application.xml
+ application_interface
+ )
+
+ecm_qt_declare_logging_category(KActivities_LIB_SRCS
+ HEADER debug_p.h
+ IDENTIFIER KAMD_CORELIB
+ CATEGORY_NAME kf.activities
+ OLD_CATEGORY_NAMES org.kde.kactivities.lib.core
+ DEFAULT_SEVERITY Warning
+ DESCRIPTION "kactivities core lib"
+ EXPORT KACTIVITIES
+)
+
+
+add_library (
+ KF5Activities SHARED
+ ${KActivities_LIB_SRCS}
+ )
+add_library (KF5::Activities ALIAS KF5Activities)
+
+set(KACTIVITIES_BUILD_INCLUDE_DIRS
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src
+ ${CMAKE_BINARY_DIR}/
+ )
+include_directories (${KACTIVITIES_BUILD_INCLUDE_DIRS})
+
+set_target_properties (
+ KF5Activities
+ PROPERTIES
+ VERSION ${KACTIVITIES_VERSION}
+ SOVERSION ${KACTIVITIES_SOVERSION}
+ EXPORT_NAME Activities
+ )
+
+target_link_libraries (
+ KF5Activities
+ PUBLIC
+ Qt${QT_MAJOR_VERSION}::Core
+ PRIVATE
+ Qt${QT_MAJOR_VERSION}::DBus
+ )
+
+target_include_directories (
+ KF5Activities
+ INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KActivities>"
+ )
+
+# install
+generate_export_header (KF5Activities BASE_NAME KActivities)
+
+ecm_generate_headers (
+ KActivities_CamelCase_HEADERS
+ HEADER_NAMES
+ Consumer
+ Controller
+ Info
+ ResourceInstance
+ ActivitiesModel
+ Version
+ PREFIX KActivities
+ REQUIRED_HEADERS KActivities_HEADERS
+ )
+install (
+ FILES ${KActivities_CamelCase_HEADERS}
+ DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KActivities/KActivities
+ COMPONENT Devel
+ )
+
+install (
+ FILES ${KActivities_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kactivities_export.h
+ DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KActivities/kactivities
+ COMPONENT Devel
+ )
+
+install (
+ TARGETS KF5Activities
+ EXPORT KF5ActivitiesLibraryTargets
+ ${KF_INSTALL_TARGETS_DEFAULT_ARGS}
+ )
+
+if(BUILD_QCH)
+ ecm_add_qch(
+ KF5Activities_QCH
+ NAME KActivities
+ BASE_NAME KF5Activities
+ VERSION ${KF_VERSION}
+ ORG_DOMAIN org.kde
+ SOURCES # using only public headers, to cover only public API
+ ${KActivities_HEADERS}
+ MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
+ LINK_QCHS
+ Qt5Core_QCH
+ INCLUDE_DIRS
+ ${KACTIVITIES_BUILD_INCLUDE_DIRS}
+ BLANK_MACROS
+ KACTIVITIES_EXPORT
+ KACTIVITIES_DEPRECATED
+ KACTIVITIES_DEPRECATED_EXPORT
+ TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
+ QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
+ COMPONENT Devel
+ )
+endif()
+
+
+if (NOT WIN32)
+ ecm_generate_pkgconfig_file(BASE_NAME libKActivities
+ LIB_NAME KF5Activities
+ INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF}/KActivities
+ DEPS Qt${QT_MAJOR_VERSION}Core
+ DESCRIPTION "libKActivities is a C++ library for using KDE activities"
+ INSTALL
+ )
+endif ()
+
+include (ECMGeneratePriFile)
+ecm_generate_pri_file (
+ BASE_NAME KActivities
+ LIB_NAME KF5Activities
+ FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF}/KActivities
+ )
+install (
+ FILES ${PRI_FILENAME}
+ DESTINATION ${ECM_MKSPECS_INSTALL_DIR}
+ )
+
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2013-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "activitiescache_p.h"
+#include "manager_p.h"
+
+#include <mutex>
+
+#include <QString>
+
+#include "mainthreadexecutor_p.h"
+
+namespace KActivities
+{
+static QString nulluuid = QStringLiteral("00000000-0000-0000-0000-000000000000");
+
+using kamd::utils::Mutable;
+
+std::shared_ptr<ActivitiesCache> ActivitiesCache::self()
+{
+ static std::weak_ptr<ActivitiesCache> s_instance;
+ static std::mutex singleton;
+ std::lock_guard<std::mutex> singleton_lock(singleton);
+
+ auto result = s_instance.lock();
+
+ if (s_instance.expired()) {
+ runInMainThread([&result] {
+ result.reset(new ActivitiesCache());
+ s_instance = result;
+ });
+ }
+
+ return result;
+}
+
+ActivitiesCache::ActivitiesCache()
+ : m_status(Consumer::NotRunning)
+{
+ // qDebug() << "ActivitiesCache: Creating a new instance";
+ using org::kde::ActivityManager::Activities;
+
+ auto activities = Manager::self()->activities();
+
+ connect(activities, &Activities::ActivityAdded, this, &ActivitiesCache::updateActivity);
+ connect(activities, &Activities::ActivityChanged, this, &ActivitiesCache::updateActivity);
+ connect(activities, &Activities::ActivityRemoved, this, &ActivitiesCache::removeActivity);
+
+ connect(activities, &Activities::ActivityStateChanged, this, &ActivitiesCache::updateActivityState);
+ connect(activities, &Activities::ActivityNameChanged, this, &ActivitiesCache::setActivityName);
+ connect(activities, &Activities::ActivityDescriptionChanged, this, &ActivitiesCache::setActivityDescription);
+ connect(activities, &Activities::ActivityIconChanged, this, &ActivitiesCache::setActivityIcon);
+
+ connect(activities, &Activities::CurrentActivityChanged, this, &ActivitiesCache::setCurrentActivity);
+
+ connect(Manager::self(), &Manager::serviceStatusChanged, this, &ActivitiesCache::setServiceStatus);
+
+ // These are covered by ActivityStateChanged
+ // signal void org.kde.ActivityManager.Activities.ActivityStarted(QString activity)
+ // signal void org.kde.ActivityManager.Activities.ActivityStopped(QString activity)
+
+ setServiceStatus(Manager::self()->isServiceRunning());
+}
+
+void ActivitiesCache::setServiceStatus(bool status)
+{
+ // qDebug() << "Setting service status to:" << status;
+ loadOfflineDefaults();
+
+ if (status) {
+ updateAllActivities();
+ }
+}
+
+void ActivitiesCache::loadOfflineDefaults()
+{
+ m_status = Consumer::NotRunning;
+
+ m_activities.clear();
+ m_activities << ActivityInfo(nulluuid, QString(), QString(), QString(), Info::Running);
+ m_currentActivity = nulluuid;
+
+ Q_EMIT serviceStatusChanged(m_status);
+ Q_EMIT activityListChanged();
+}
+
+ActivitiesCache::~ActivitiesCache()
+{
+ // qDebug() << "ActivitiesCache: Destroying the instance";
+}
+
+void ActivitiesCache::removeActivity(const QString &id)
+{
+ // qDebug() << "Removing the activity";
+
+ // Since we are sorting the activities by name now,
+ // we can not use lower_bound to search for an activity
+ // with a specified id
+ const auto where = find(id);
+
+ if (where != m_activities.end() && where->id == id) {
+ m_activities.erase(where);
+ Q_EMIT activityRemoved(id);
+ Q_EMIT activityListChanged();
+
+ } else {
+ // qFatal("Requested to delete an non-existent activity");
+ }
+}
+
+void ActivitiesCache::updateAllActivities()
+{
+ // qDebug() << "Updating all";
+ m_status = Consumer::Unknown;
+ Q_EMIT serviceStatusChanged(m_status);
+
+ // Loading the current activity
+ auto call = Manager::self()->activities()->asyncCall(QStringLiteral("CurrentActivity"));
+
+ onCallFinished(call, SLOT(setCurrentActivityFromReply(QDBusPendingCallWatcher *)));
+
+ // Loading all the activities
+ call = Manager::self()->activities()->asyncCall(QStringLiteral("ListActivitiesWithInformation"));
+
+ onCallFinished(call, SLOT(setAllActivitiesFromReply(QDBusPendingCallWatcher *)));
+}
+
+void ActivitiesCache::updateActivity(const QString &id)
+{
+ // qDebug() << "Updating activity" << id;
+
+ auto call = Manager::self()->activities()->asyncCall(QStringLiteral("ActivityInformation"), id);
+
+ onCallFinished(call, SLOT(setActivityInfoFromReply(QDBusPendingCallWatcher *)));
+}
+
+void ActivitiesCache::updateActivityState(const QString &id, int state)
+{
+ auto where = getInfo<Mutable>(id);
+
+ if (where && where->state != state) {
+ auto isInvalid = [](int state) {
+ return state == Info::Invalid || state == Info::Unknown;
+ };
+ auto isStopped = [](int state) {
+ return state == Info::Stopped || state == Info::Starting;
+ };
+ auto isRunning = [](int state) {
+ return state == Info::Running || state == Info::Stopping;
+ };
+
+ const bool runningStateChanged =
+ (isInvalid(state) || isInvalid(where->state) || (isStopped(state) && isRunning(where->state)) || (isRunning(state) && isStopped(where->state)));
+
+ where->state = state;
+
+ if (runningStateChanged) {
+ Q_EMIT runningActivityListChanged();
+ }
+
+ Q_EMIT activityStateChanged(id, state);
+
+ } else {
+ // qFatal("Requested to update the state of an non-existent activity");
+ }
+}
+
+template<typename _Result, typename _Functor>
+void ActivitiesCache::passInfoFromReply(QDBusPendingCallWatcher *watcher, _Functor f)
+{
+ QDBusPendingReply<_Result> reply = *watcher;
+
+ if (!reply.isError()) {
+ auto replyValue = reply.template argumentAt<0>();
+ // qDebug() << "Got some reply" << replyValue;
+
+ ((*this).*f)(replyValue);
+ }
+
+ watcher->deleteLater();
+}
+
+void ActivitiesCache::setActivityInfoFromReply(QDBusPendingCallWatcher *watcher)
+{
+ // qDebug() << "reply...";
+ passInfoFromReply<ActivityInfo>(watcher, &ActivitiesCache::setActivityInfo);
+}
+
+void ActivitiesCache::setAllActivitiesFromReply(QDBusPendingCallWatcher *watcher)
+{
+ // qDebug() << "reply...";
+ passInfoFromReply<ActivityInfoList>(watcher, &ActivitiesCache::setAllActivities);
+}
+
+void ActivitiesCache::setCurrentActivityFromReply(QDBusPendingCallWatcher *watcher)
+{
+ // qDebug() << "reply...";
+ passInfoFromReply<QString>(watcher, &ActivitiesCache::setCurrentActivity);
+}
+
+void ActivitiesCache::setActivityInfo(const ActivityInfo &info)
+{
+ // qDebug() << "Setting activity info" << info.id;
+
+ // Are we updating an existing activity, or adding a new one?
+ const auto iter = find(info.id);
+ const auto present = iter != m_activities.end();
+ bool runningChanged = true;
+ // If there is an activity with the specified id,
+ // we are going to remove it, temporarily.
+ if (present) {
+ runningChanged = (*iter).state != info.state;
+ m_activities.erase(iter);
+ }
+
+ // Now, we need to find where to insert the activity
+ // and keep the cache sorted by name
+ const auto where = lower_bound(info);
+
+ m_activities.insert(where, info);
+
+ if (present) {
+ Q_EMIT activityChanged(info.id);
+ } else {
+ Q_EMIT activityAdded(info.id);
+ Q_EMIT activityListChanged();
+ if (runningChanged) {
+ Q_EMIT runningActivityListChanged();
+ }
+ }
+}
+// clang-format off
+#define CREATE_SETTER(WHAT, What) \
+ void ActivitiesCache::setActivity##WHAT(const QString &id, \
+ const QString &value) \
+ { \
+ auto where = getInfo<Mutable>(id); \
+ \
+ if (where) { \
+ where->What = value; \
+ Q_EMIT activity##WHAT##Changed(id, value); \
+ } \
+ }
+// clang-format on
+
+CREATE_SETTER(Name, name)
+CREATE_SETTER(Description, description)
+CREATE_SETTER(Icon, icon)
+
+#undef CREATE_SETTER
+
+void ActivitiesCache::setAllActivities(const ActivityInfoList &_activities)
+{
+ // qDebug() << "Setting all activities";
+
+ m_activities.clear();
+
+ const ActivityInfoList activities = _activities;
+
+ for (const ActivityInfo &info : activities) {
+ m_activities << info;
+ }
+
+ std::sort(m_activities.begin(), m_activities.end(), &infoLessThan);
+
+ m_status = Consumer::Running;
+ Q_EMIT serviceStatusChanged(m_status);
+ Q_EMIT activityListChanged();
+}
+
+void ActivitiesCache::setCurrentActivity(const QString &activity)
+{
+ // qDebug() << "Setting current activity to" << activity;
+
+ if (m_currentActivity == activity) {
+ return;
+ }
+
+ m_currentActivity = activity;
+
+ Q_EMIT currentActivityChanged(activity);
+}
+
+} // namespace KActivities
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2013-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_CACHE_P_H
+#define ACTIVITIES_CACHE_P_H
+
+#include <memory>
+
+#include <QObject>
+
+#include <common/dbus/org.kde.ActivityManager.Activities.h>
+#include <utils/ptr_to.h>
+
+#include "activities_interface.h"
+#include "consumer.h"
+
+namespace KActivities
+{
+class ActivitiesCache : public QObject
+{
+ Q_OBJECT
+
+public:
+ static std::shared_ptr<ActivitiesCache> self();
+
+ ~ActivitiesCache() override;
+
+Q_SIGNALS:
+ void activityAdded(const QString &id);
+ void activityChanged(const QString &id);
+ void activityRemoved(const QString &id);
+
+ void activityStateChanged(const QString &id, int state);
+ void activityNameChanged(const QString &id, const QString &name);
+ void activityDescriptionChanged(const QString &id, const QString &description);
+ void activityIconChanged(const QString &id, const QString &icon);
+
+ void currentActivityChanged(const QString &id);
+ void serviceStatusChanged(Consumer::ServiceStatus status);
+ void activityListChanged();
+ void runningActivityListChanged();
+
+private Q_SLOTS:
+ void updateAllActivities();
+ void loadOfflineDefaults();
+
+ void updateActivity(const QString &id);
+ void updateActivityState(const QString &id, int state);
+ void removeActivity(const QString &id);
+
+ void setActivityInfoFromReply(QDBusPendingCallWatcher *watcher);
+ void setAllActivitiesFromReply(QDBusPendingCallWatcher *watcher);
+ void setCurrentActivityFromReply(QDBusPendingCallWatcher *watcher);
+
+ void setActivityName(const QString &id, const QString &name);
+ void setActivityDescription(const QString &id, const QString &description);
+ void setActivityIcon(const QString &id, const QString &icon);
+
+ void setActivityInfo(const ActivityInfo &info);
+ void setAllActivities(const ActivityInfoList &activities);
+ void setCurrentActivity(const QString &activity);
+
+ void setServiceStatus(bool status);
+
+public:
+ template<typename _Result, typename _Functor>
+ void passInfoFromReply(QDBusPendingCallWatcher *watcher, _Functor f);
+
+ static bool infoLessThan(const ActivityInfo &info, const ActivityInfo &other)
+ {
+ const auto comp = QString::compare(info.name, other.name, Qt::CaseInsensitive);
+ return comp < 0 || (comp == 0 && info.id < other.id);
+ }
+
+ ActivityInfoList::iterator find(const QString &id)
+ {
+ return std::find_if(m_activities.begin(), m_activities.end(), [&id](const ActivityInfo &info) {
+ return info.id == id;
+ });
+ }
+
+ ActivityInfoList::iterator lower_bound(const ActivityInfo &info)
+ {
+ return std::lower_bound(m_activities.begin(), m_activities.end(), info, &infoLessThan);
+ }
+
+ template<int Policy = kamd::utils::Const>
+ inline typename kamd::utils::ptr_to<ActivityInfo, Policy>::type getInfo(const QString &id)
+ {
+ const auto where = find(id);
+
+ if (where != m_activities.end()) {
+ return &(*where);
+ }
+
+ return nullptr;
+ }
+
+ template<typename TargetSlot>
+ void onCallFinished(QDBusPendingCall &call, TargetSlot slot)
+ {
+ auto watcher = new QDBusPendingCallWatcher(call, this);
+
+ connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher *)), this, slot);
+ }
+
+ ActivitiesCache();
+
+ QList<ActivityInfo> m_activities;
+ QString m_currentActivity;
+ Consumer::ServiceStatus m_status;
+};
+
+} // namespace KActivities
+
+#endif /* ACTIVITIES_CACHE_P_H */
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2012-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+// Self
+#include "activitiesmodel.h"
+#include "activitiesmodel_p.h"
+
+// Qt
+#include <QByteArray>
+#include <QDBusPendingCall>
+#include <QDBusPendingCallWatcher>
+#include <QDebug>
+#include <QFutureWatcher>
+#include <QHash>
+#include <QModelIndex>
+
+// Local
+#include "utils/remove_if.h"
+
+namespace KActivities
+{
+namespace Private
+{
+template<typename _Container>
+struct ActivityPosition {
+ ActivityPosition()
+ : isValid(false)
+ , index(0)
+ , iterator()
+ {
+ }
+
+ ActivityPosition(unsigned int index, typename _Container::const_iterator iterator)
+ : isValid(true)
+ , index(index)
+ , iterator(iterator)
+ {
+ }
+
+ operator bool() const
+ {
+ return isValid;
+ }
+
+ const bool isValid;
+ const unsigned int index;
+ const typename _Container::const_iterator iterator;
+
+ typedef typename _Container::value_type ContainerElement;
+};
+
+/**
+ * Returns whether the activity has a desired state.
+ * If the state is 0, returns true
+ */
+template<typename T>
+inline bool matchingState(ActivitiesModelPrivate::InfoPtr activity, const T &states)
+{
+ return states.empty() || states.contains(activity->state());
+}
+
+/**
+ * Searches for the activity.
+ * Returns an option(index, iterator) for the found activity.
+ */
+template<typename _Container>
+inline ActivityPosition<_Container> activityPosition(const _Container &container, const QString &activityId)
+{
+ auto position = std::find_if(container.begin(), container.end(), [&](const typename ActivityPosition<_Container>::ContainerElement &activity) {
+ return activity->id() == activityId;
+ });
+
+ return (position != container.end()) ? ActivityPosition<_Container>(position - container.begin(), position) : ActivityPosition<_Container>();
+}
+
+/**
+ * Notifies the model that an activity was updated
+ */
+template<typename _Model, typename _Container>
+inline void emitActivityUpdated(_Model *model, const _Container &container, const QString &activity, int role)
+{
+ auto position = Private::activityPosition(container, activity);
+
+ if (position) {
+ Q_EMIT model->q->dataChanged(model->q->index(position.index),
+ model->q->index(position.index),
+ role == Qt::DecorationRole ? QVector<int>{role, ActivitiesModel::ActivityIconSource} : QVector<int>{role});
+ }
+}
+
+/**
+ * Notifies the model that an activity was updated
+ */
+template<typename _Model, typename _Container>
+inline void emitActivityUpdated(_Model *model, const _Container &container, QObject *activityInfo, int role)
+{
+ const auto activity = static_cast<Info *>(activityInfo);
+ emitActivityUpdated(model, container, activity->id(), role);
+}
+
+}
+
+ActivitiesModelPrivate::ActivitiesModelPrivate(ActivitiesModel *parent)
+ : q(parent)
+{
+}
+
+ActivitiesModel::ActivitiesModel(QObject *parent)
+ : QAbstractListModel(parent)
+ , d(new ActivitiesModelPrivate(this))
+{
+ // Initializing role names for qml
+ connect(&d->activities, &Consumer::serviceStatusChanged, this, [this](Consumer::ServiceStatus status) {
+ d->setServiceStatus(status);
+ });
+
+ connect(&d->activities, &Consumer::activityAdded, this, [this](const QString &activity) {
+ d->onActivityAdded(activity);
+ });
+ connect(&d->activities, &Consumer::activityRemoved, this, [this](const QString &activity) {
+ d->onActivityRemoved(activity);
+ });
+ connect(&d->activities, &Consumer::currentActivityChanged, this, [this](const QString &activity) {
+ d->onCurrentActivityChanged(activity);
+ });
+
+ d->setServiceStatus(d->activities.serviceStatus());
+}
+
+ActivitiesModel::ActivitiesModel(QVector<Info::State> shownStates, QObject *parent)
+ : QAbstractListModel(parent)
+ , d(new ActivitiesModelPrivate(this))
+{
+ d->shownStates = shownStates;
+
+ // Initializing role names for qml
+ connect(&d->activities, &Consumer::serviceStatusChanged, this, [this](Consumer::ServiceStatus status) {
+ d->setServiceStatus(status);
+ });
+
+ connect(&d->activities, &Consumer::activityAdded, this, [this](const QString &activity) {
+ d->onActivityAdded(activity);
+ });
+ connect(&d->activities, &Consumer::activityRemoved, this, [this](const QString &activity) {
+ d->onActivityRemoved(activity);
+ });
+ connect(&d->activities, &Consumer::currentActivityChanged, this, [this](const QString &activity) {
+ d->onCurrentActivityChanged(activity);
+ });
+
+ d->setServiceStatus(d->activities.serviceStatus());
+}
+
+ActivitiesModel::~ActivitiesModel()
+{
+ delete d;
+}
+
+QHash<int, QByteArray> ActivitiesModel::roleNames() const
+{
+ return {{ActivityName, "name"},
+ {ActivityState, "state"},
+ {ActivityId, "id"},
+ {ActivityIconSource, "iconSource"},
+ {ActivityDescription, "description"},
+ {ActivityBackground, "background"},
+ {ActivityIsCurrent, "isCurrent"}};
+}
+
+void ActivitiesModelPrivate::setServiceStatus(Consumer::ServiceStatus)
+{
+ replaceActivities(activities.activities());
+}
+
+void ActivitiesModelPrivate::replaceActivities(const QStringList &activities)
+{
+ q->beginResetModel();
+
+ knownActivities.clear();
+ shownActivities.clear();
+
+ for (const QString &activity : activities) {
+ onActivityAdded(activity, false);
+ }
+
+ q->endResetModel();
+}
+
+void ActivitiesModelPrivate::onActivityAdded(const QString &id, bool notifyClients)
+{
+ auto info = registerActivity(id);
+
+ showActivity(info, notifyClients);
+}
+
+void ActivitiesModelPrivate::onActivityRemoved(const QString &id)
+{
+ hideActivity(id);
+ unregisterActivity(id);
+}
+
+void ActivitiesModelPrivate::onCurrentActivityChanged(const QString &id)
+{
+ Q_UNUSED(id);
+
+ for (const auto &activity : shownActivities) {
+ Private::emitActivityUpdated(this, shownActivities, activity->id(), ActivitiesModel::ActivityIsCurrent);
+ }
+}
+
+ActivitiesModelPrivate::InfoPtr ActivitiesModelPrivate::registerActivity(const QString &id)
+{
+ auto position = Private::activityPosition(knownActivities, id);
+
+ if (position) {
+ return *(position.iterator);
+
+ } else {
+ auto activityInfo = std::make_shared<Info>(id);
+
+ auto ptr = activityInfo.get();
+
+ connect(ptr, &Info::nameChanged, this, &ActivitiesModelPrivate::onActivityNameChanged);
+ connect(ptr, &Info::descriptionChanged, this, &ActivitiesModelPrivate::onActivityDescriptionChanged);
+ connect(ptr, &Info::iconChanged, this, &ActivitiesModelPrivate::onActivityIconChanged);
+ connect(ptr, &Info::stateChanged, this, &ActivitiesModelPrivate::onActivityStateChanged);
+
+ knownActivities.insert(InfoPtr(activityInfo));
+
+ return activityInfo;
+ }
+}
+
+void ActivitiesModelPrivate::unregisterActivity(const QString &id)
+{
+ auto position = Private::activityPosition(knownActivities, id);
+
+ if (position) {
+ if (auto shown = Private::activityPosition(shownActivities, id)) {
+ q->beginRemoveRows(QModelIndex(), shown.index, shown.index);
+ shownActivities.removeAt(shown.index);
+ q->endRemoveRows();
+ }
+
+ knownActivities.removeAt(position.index);
+ }
+}
+
+void ActivitiesModelPrivate::showActivity(InfoPtr activityInfo, bool notifyClients)
+{
+ // Should it really be shown?
+ if (!Private::matchingState(activityInfo, shownStates)) {
+ return;
+ }
+
+ // Is it already shown?
+ if (std::binary_search(shownActivities.cbegin(), shownActivities.cend(), activityInfo, InfoPtrComparator())) {
+ return;
+ }
+
+ auto registeredPosition = Private::activityPosition(knownActivities, activityInfo->id());
+
+ if (!registeredPosition) {
+ qDebug() << "Got a request to show an unknown activity, ignoring";
+ return;
+ }
+
+ const auto activityInfoPtr = *(registeredPosition.iterator);
+
+ // In C++17, this would be:
+ // const auto [iterator, index, found] = shownActivities.insert(...);
+ const auto _result = shownActivities.insert(activityInfoPtr);
+ // const auto iterator = std::get<0>(_result);
+ const auto index = std::get<1>(_result);
+
+ if (notifyClients) {
+ q->beginInsertRows(QModelIndex(), index, index);
+ q->endInsertRows();
+ }
+}
+
+void ActivitiesModelPrivate::hideActivity(const QString &id)
+{
+ auto position = Private::activityPosition(shownActivities, id);
+
+ if (position) {
+ q->beginRemoveRows(QModelIndex(), position.index, position.index);
+ shownActivities.removeAt(position.index);
+ q->endRemoveRows();
+ }
+}
+
+// clang-format off
+#define CREATE_SIGNAL_EMITTER(What,Role) \
+ void ActivitiesModelPrivate::onActivity##What##Changed(const QString &) \
+ { \
+ Private::emitActivityUpdated(this, shownActivities, sender(), Role); \
+ }
+// clang-format on
+
+CREATE_SIGNAL_EMITTER(Name, Qt::DisplayRole)
+CREATE_SIGNAL_EMITTER(Description, ActivitiesModel::ActivityDescription)
+CREATE_SIGNAL_EMITTER(Icon, Qt::DecorationRole)
+
+#undef CREATE_SIGNAL_EMITTER
+
+void ActivitiesModelPrivate::onActivityStateChanged(Info::State state)
+{
+ if (shownStates.empty()) {
+ Private::emitActivityUpdated(this, shownActivities, sender(), ActivitiesModel::ActivityState);
+
+ } else {
+ auto info = findActivity(sender());
+
+ if (!info) {
+ return;
+ }
+
+ if (shownStates.contains(state)) {
+ showActivity(info, true);
+ } else {
+ hideActivity(info->id());
+ }
+ }
+}
+
+void ActivitiesModel::setShownStates(const QVector<Info::State> &states)
+{
+ d->shownStates = states;
+
+ d->replaceActivities(d->activities.activities());
+
+ Q_EMIT shownStatesChanged(states);
+}
+
+QVector<Info::State> ActivitiesModel::shownStates() const
+{
+ return d->shownStates;
+}
+
+int ActivitiesModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid()) {
+ return 0;
+ }
+
+ return d->shownActivities.size();
+}
+
+QVariant ActivitiesModel::data(const QModelIndex &index, int role) const
+{
+ const int row = index.row();
+ const auto &item = d->shownActivities.at(row);
+
+ switch (role) {
+ case Qt::DisplayRole:
+ case ActivityName:
+ return item->name();
+
+ case ActivityId:
+ return item->id();
+
+ case ActivityState:
+ return item->state();
+
+ case Qt::DecorationRole:
+ case ActivityIconSource: {
+ const QString &icon = item->icon();
+
+ // We need a default icon for activities
+ return icon.isEmpty() ? QStringLiteral("activities") : icon;
+ }
+
+ case ActivityDescription:
+ return item->description();
+
+ case ActivityIsCurrent:
+ return d->activities.currentActivity() == item->id();
+
+ default:
+ return QVariant();
+ }
+}
+
+QVariant ActivitiesModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ Q_UNUSED(section);
+ Q_UNUSED(orientation);
+ Q_UNUSED(role);
+
+ return QVariant();
+}
+
+ActivitiesModelPrivate::InfoPtr ActivitiesModelPrivate::findActivity(QObject *ptr) const
+{
+ auto info = std::find_if(knownActivities.cbegin(), knownActivities.cend(), [ptr](const InfoPtr &info) {
+ return ptr == info.get();
+ });
+
+ if (info == knownActivities.end()) {
+ return nullptr;
+ } else {
+ return *info;
+ }
+}
+
+} // namespace KActivities
+
+// #include "activitiesmodel.moc"
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2012, 2013, 2014 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef ACTIVITIES_ACTIVITIESMODEL_H
+#define ACTIVITIES_ACTIVITIESMODEL_H
+
+// Qt
+#include <QAbstractListModel>
+#include <QObject>
+
+// STL
+#include <memory>
+
+// Local
+#include "info.h"
+
+class QModelIndex;
+class QDBusPendingCallWatcher;
+
+namespace KActivities
+{
+class ActivitiesModelPrivate;
+
+/**
+ * Data model that shows existing activities
+ */
+class KACTIVITIES_EXPORT ActivitiesModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QVector<Info::State> shownStates READ shownStates WRITE setShownStates NOTIFY shownStatesChanged)
+
+public:
+ explicit ActivitiesModel(QObject *parent = nullptr);
+
+ /**
+ * Constructs the model and sets the shownStates
+ */
+ ActivitiesModel(QVector<Info::State> shownStates, QObject *parent = nullptr);
+ ~ActivitiesModel() override;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+
+ QHash<int, QByteArray> roleNames() const override;
+
+ enum Roles {
+ ActivityId = Qt::UserRole, ///< UUID of the activity
+ ActivityName = Qt::UserRole + 1, ///< Activity name
+ ActivityDescription = Qt::UserRole + 2, ///< Activity description
+ ActivityIconSource = Qt::UserRole + 3, ///< Activity icon source name
+ ActivityState = Qt::UserRole + 4, ///< The current state of the activity @see Info::State
+ ActivityBackground = Qt::UserRole + 5, ///< Activity wallpaper (currently unsupported)
+ ActivityIsCurrent = Qt::UserRole + 6, ///< Is this activity the current one current
+
+ UserRole = Qt::UserRole + 32, ///< To be used by models that inherit this one
+ };
+
+public Q_SLOTS:
+ /**
+ * The model can filter the list of activities based on their state.
+ * This method sets which states should be shown.
+ */
+ void setShownStates(const QVector<Info::State> &shownStates);
+
+ /**
+ * The model can filter the list of activities based on their state.
+ * This method returns which states are currently shown.
+ */
+ QVector<Info::State> shownStates() const;
+
+Q_SIGNALS:
+ void shownStatesChanged(const QVector<Info::State> &state);
+
+private:
+ friend class ActivitiesModelPrivate;
+ ActivitiesModelPrivate *const d;
+};
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_ACTIVITIESMODEL_H
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2016 Ivan Čukić <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_ACTIVITIESMODEL_P_H
+#define ACTIVITIES_ACTIVITIESMODEL_P_H
+
+#include "activitiesmodel.h"
+
+#include "consumer.h"
+
+#include "utils/qflatset.h"
+
+#include <QCollator>
+
+namespace KActivities
+{
+class ActivitiesModelPrivate : public QObject
+{
+ Q_OBJECT
+public:
+ ActivitiesModelPrivate(ActivitiesModel *parent);
+
+public Q_SLOTS:
+ void onActivityNameChanged(const QString &name);
+ void onActivityDescriptionChanged(const QString &description);
+ void onActivityIconChanged(const QString &icon);
+ void onActivityStateChanged(KActivities::Info::State state);
+
+ void replaceActivities(const QStringList &activities);
+ void onActivityAdded(const QString &id, bool notifyClients = true);
+ void onActivityRemoved(const QString &id);
+ void onCurrentActivityChanged(const QString &id);
+
+ void setServiceStatus(KActivities::Consumer::ServiceStatus status);
+
+public:
+ KActivities::Consumer activities;
+ QVector<Info::State> shownStates;
+
+ typedef std::shared_ptr<Info> InfoPtr;
+
+ struct InfoPtrComparator {
+ bool operator()(const InfoPtr &left, const InfoPtr &right) const
+ {
+ QCollator c;
+ c.setCaseSensitivity(Qt::CaseInsensitive);
+ c.setNumericMode(true);
+ int rc = c.compare(left->name(), right->name());
+ if (rc == 0) {
+ return left->id() < right->id();
+ }
+ return rc < 0;
+ }
+ };
+
+ QFlatSet<InfoPtr, InfoPtrComparator> knownActivities;
+ QFlatSet<InfoPtr, InfoPtrComparator> shownActivities;
+
+ InfoPtr registerActivity(const QString &id);
+ void unregisterActivity(const QString &id);
+ void showActivity(InfoPtr activityInfo, bool notifyClients);
+ void hideActivity(const QString &id);
+ void backgroundsUpdated(const QStringList &activities);
+
+ InfoPtr findActivity(QObject *ptr) const;
+
+ ActivitiesModel *const q;
+};
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_ACTIVITIESMODEL_P_H
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "consumer.h"
+#include "consumer_p.h"
+#include "manager_p.h"
+
+namespace KActivities
+{
+ConsumerPrivate::ConsumerPrivate()
+ : cache(ActivitiesCache::self())
+{
+}
+
+void ConsumerPrivate::setServiceStatus(Consumer::ServiceStatus status)
+{
+ Q_EMIT serviceStatusChanged(status);
+}
+
+Consumer::Consumer(QObject *parent)
+ : QObject(parent)
+ , d(new ConsumerPrivate())
+{
+ connect(d->cache.get(), &KActivities::ActivitiesCache::currentActivityChanged, this, &Consumer::currentActivityChanged);
+ connect(d->cache.get(), &KActivities::ActivitiesCache::activityAdded, this, &Consumer::activityAdded);
+ connect(d->cache.get(), &KActivities::ActivitiesCache::activityRemoved, this, &Consumer::activityRemoved);
+ connect(d->cache.get(), &KActivities::ActivitiesCache::serviceStatusChanged, this, &Consumer::serviceStatusChanged);
+
+ connect(d->cache.get(), &ActivitiesCache::activityListChanged, this, [=]() {
+ Q_EMIT activitiesChanged(activities());
+ });
+ connect(d->cache.get(), &ActivitiesCache::runningActivityListChanged, this, [=]() {
+ Q_EMIT runningActivitiesChanged(runningActivities());
+ });
+
+ // connect(d->cache.get(), SIGNAL(activityStateChanged(QString,int)),
+ // this, SIGNAL(activityStateChanged(QString,int)));
+}
+
+Consumer::~Consumer() = default;
+
+QString Consumer::currentActivity() const
+{
+ return d->cache->m_currentActivity;
+}
+
+QStringList Consumer::activities(Info::State state) const
+{
+ QStringList result;
+
+ result.reserve(d->cache->m_activities.size());
+
+ for (const auto &info : std::as_const(d->cache->m_activities)) {
+ if (info.state == state) {
+ result << info.id;
+ }
+ }
+
+ return result;
+}
+
+QStringList Consumer::activities() const
+{
+ QStringList result;
+
+ result.reserve(d->cache->m_activities.size());
+
+ for (const auto &info : std::as_const(d->cache->m_activities)) {
+ result << info.id;
+ }
+
+ return result;
+}
+
+QStringList Consumer::runningActivities() const
+{
+ QStringList result;
+
+ result.reserve(d->cache->m_activities.size());
+
+ for (const auto &info : std::as_const(d->cache->m_activities)) {
+ if (info.state == Info::Running || info.state == Info::Stopping) {
+ result << info.id;
+ }
+ }
+
+ return result;
+}
+
+Consumer::ServiceStatus Consumer::serviceStatus()
+{
+ return d->cache->m_status;
+}
+
+} // namespace KActivities
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_CONSUMER_H
+#define ACTIVITIES_CONSUMER_H
+
+#include <QObject>
+#include <QScopedPointer>
+#include <QString>
+#include <QStringList>
+
+#include "info.h"
+
+#include "kactivities_export.h"
+
+namespace KActivities
+{
+class ConsumerPrivate;
+
+/**
+ * Contextual information can be, from the user's point of view, divided
+ * into three aspects - "who am I?", "where am I?" (what are my surroundings?)
+ * and "what am I doing?".
+ *
+ * Activities deal with the last one - "what am I doing?". The current activity
+ * refers to what the user is doing at the moment, while the other activities
+ * represent things that he/she was doing before, and probably will be doing
+ * again.
+ *
+ * Activity is an abstract concept whose meaning can differ from one user to
+ * another. Typical examples of activities are "developing a KDE project",
+ * "studying the 19th century art", "composing music", "lazing on a Sunday
+ * afternoon" etc.
+ *
+ * Consumer provides read-only information about activities.
+ *
+ * Before relying on the values retrieved by the class, make sure that the
+ * serviceStatus is set to Running. Otherwise, you can get invalid data either
+ * because the service is not functioning properly (or at all) or because
+ * the class did not have enough time to synchronize the data with it.
+ *
+ * For example, if this is the only existing instance of the Consumer class,
+ * the listActivities method will return an empty list.
+ *
+ * @code
+ * void someMethod() {
+ * // Do not copy. This approach is not a good one!
+ * Consumer c;
+ * doSomethingWith(c.listActivities());
+ * }
+ * @endcode
+ *
+ * Instances of the Consumer class should be long-lived. For example, members
+ * of the classes that use them, and you should listen for the changes in the
+ * provided properties.
+ *
+ * @since 4.5
+ */
+class KACTIVITIES_EXPORT Consumer : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString currentActivity READ currentActivity NOTIFY currentActivityChanged)
+ Q_PROPERTY(QStringList activities READ activities NOTIFY activitiesChanged)
+ Q_PROPERTY(QStringList runningActivities READ runningActivities NOTIFY runningActivitiesChanged)
+ Q_PROPERTY(ServiceStatus serviceStatus READ serviceStatus NOTIFY serviceStatusChanged)
+
+public:
+ /**
+ * Different states of the activities service
+ */
+ enum ServiceStatus {
+ NotRunning, ///< Service is not running
+ Unknown, ///< Unable to determine the status of the service
+ Running, ///< Service is running properly
+ };
+
+ explicit Consumer(QObject *parent = nullptr);
+
+ ~Consumer() override;
+
+ /**
+ * @returns the id of the current activity
+ * @note Activity ID is a UUID-formatted string. If the serviceStatus
+ * is not Running, a null UUID is returned. The ID can also be an empty
+ * string in the case there is no current activity.
+ */
+ QString currentActivity() const;
+
+ /**
+ * @returns the list of activities filtered by state
+ * @param state state of the activity
+ * @note If the serviceStatus is not Running, only a null activity will be
+ * returned.
+ */
+ QStringList activities(Info::State state) const;
+
+ /**
+ * @returns a list of running activities
+ * This is a convenience method that returns Running and Stopping activities
+ */
+ QStringList runningActivities() const;
+
+ /**
+ * @returns the list of all existing activities
+ * @note If the serviceStatus is not Running, only a null activity will be
+ * returned.
+ */
+ QStringList activities() const;
+
+ /**
+ * @returns status of the activities service
+ */
+ ServiceStatus serviceStatus();
+
+Q_SIGNALS:
+ /**
+ * This signal is emitted when the current activity is changed
+ * @param id id of the new current activity
+ */
+ void currentActivityChanged(const QString &id);
+
+ /**
+ * This signal is emitted when the activity service goes online or offline,
+ * or when the class manages to synchronize the data with the service.
+ * @param status new status of the service
+ */
+ void serviceStatusChanged(Consumer::ServiceStatus status);
+
+ /**
+ * This signal is emitted when a new activity is added
+ * @param id id of the new activity
+ */
+ void activityAdded(const QString &id);
+
+ /**
+ * This signal is emitted when an activity has been removed
+ * @param id id of the removed activity
+ */
+ void activityRemoved(const QString &id);
+
+ /**
+ * This signal is emitted when the activity list changes
+ * @param activities list of activities
+ */
+ void activitiesChanged(const QStringList &activities);
+
+ /**
+ * This signal is emitted when the list of running activities changes
+ * @param runningActivities list of running activities
+ */
+ void runningActivitiesChanged(const QStringList &runningActivities);
+
+private:
+ const QScopedPointer<ConsumerPrivate> d;
+};
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_CONSUMER_H
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_CONSUMER_P_H
+#define ACTIVITIES_CONSUMER_P_H
+
+#include "consumer.h"
+
+#include <memory>
+
+#include "activitiescache_p.h"
+
+namespace KActivities
+{
+class ConsumerPrivate : public QObject
+{
+ Q_OBJECT
+
+public:
+ ConsumerPrivate();
+
+ std::shared_ptr<ActivitiesCache> cache;
+
+public Q_SLOTS:
+ void setServiceStatus(Consumer::ServiceStatus status);
+
+Q_SIGNALS:
+ void serviceStatusChanged(Consumer::ServiceStatus status);
+};
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_CONSUMER_P_H
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "controller.h"
+#include "consumer_p.h"
+#include "manager_p.h"
+
+#include "utils/dbusfuture_p.h"
+
+namespace KActivities
+{
+Controller::Controller(QObject *parent)
+ : Consumer(parent)
+{
+}
+
+Controller::~Controller()
+{
+}
+
+// clang-format off
+#define CREATE_SETTER(What) \
+ QFuture<void> Controller::setActivity##What(const QString &id, \
+ const QString &value) \
+ { \
+ return Manager::isServiceRunning() \
+ ? DBusFuture::asyncCall<void>( \
+ Manager::activities(), \
+ QString::fromLatin1("SetActivity" #What), id, value) \
+ : DBusFuture::fromVoid(); \
+ }
+// clang-format on
+
+CREATE_SETTER(Name)
+CREATE_SETTER(Description)
+CREATE_SETTER(Icon)
+
+#undef CREATE_SETTER
+
+QFuture<bool> Controller::setCurrentActivity(const QString &id)
+{
+ // Q_ASSERT_X(activities().contains(id), "Controller::setCurrentActivity",
+ // "You can not set an non-existent activity to be the current");
+
+ // return Manager::activities()->SetCurrentActivity(id);
+ return Manager::isServiceRunning() ? DBusFuture::asyncCall<bool>(Manager::activities(), QStringLiteral("SetCurrentActivity"), id)
+ : DBusFuture::fromValue(false);
+}
+
+QFuture<QString> Controller::addActivity(const QString &name)
+{
+ Q_ASSERT_X(!name.isEmpty(), "Controller::addActivity", "The activity name can not be an empty string");
+
+ // return Manager::activities()->AddActivity(name);
+ return Manager::isServiceRunning() ? DBusFuture::asyncCall<QString>(Manager::activities(), QStringLiteral("AddActivity"), name)
+ : DBusFuture::fromValue(QString());
+}
+
+QFuture<void> Controller::removeActivity(const QString &id)
+{
+ // Q_ASSERT_X(activities().contains(id), "Controller::removeActivity",
+ // "You can not remove an non-existent activity");
+
+ // Manager::activities()->RemoveActivity(id);
+ return Manager::isServiceRunning() ? DBusFuture::asyncCall<void>(Manager::activities(), QStringLiteral("RemoveActivity"), id) : DBusFuture::fromVoid();
+}
+
+QFuture<void> Controller::stopActivity(const QString &id)
+{
+ // Q_ASSERT_X(activities().contains(id), "Controller::stopActivity",
+ // "You can not stop an non-existent activity");
+
+ // Manager::activities()->StopActivity(id);
+ return Manager::isServiceRunning() ? DBusFuture::asyncCall<void>(Manager::activities(), QStringLiteral("StopActivity"), id) : DBusFuture::fromVoid();
+}
+
+QFuture<void> Controller::startActivity(const QString &id)
+{
+ // Q_ASSERT_X(activities().contains(id), "Controller::startActivity",
+ // "You can not start an non-existent activity");
+
+ // Manager::activities()->StartActivity(id);
+ return Manager::isServiceRunning() ? DBusFuture::asyncCall<void>(Manager::activities(), QStringLiteral("StartActivity"), id) : DBusFuture::fromVoid();
+}
+
+QFuture<void> Controller::previousActivity()
+{
+ return Manager::isServiceRunning() ? DBusFuture::asyncCall<void>(Manager::activities(), QStringLiteral("PreviousActivity")) : DBusFuture::fromVoid();
+}
+
+QFuture<void> Controller::nextActivity()
+{
+ return Manager::isServiceRunning() ? DBusFuture::asyncCall<void>(Manager::activities(), QStringLiteral("NextActivity")) : DBusFuture::fromVoid();
+}
+
+} // namespace KActivities
+
+// #include "controller.moc"
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_CONTROLLER_H
+#define ACTIVITIES_CONTROLLER_H
+
+#include <QFuture>
+#include <QObject>
+#include <QString>
+
+#include "consumer.h"
+
+#include "kactivities_export.h"
+
+namespace KActivities
+{
+class ControllerPrivate;
+
+/**
+ * This class provides methods for controlling and managing
+ * the activities.
+ *
+ * @note The QFuture objects returned by these methods are not thread-based,
+ * you can not call synchronous methods like waitForFinished, cancel, pause on
+ * them. You need either to register watchers to check when those have finished,
+ * or to check whether they are ready from time to time manually.
+ *
+ * @see Consumer for info about activities
+ *
+ * @since 5.0
+ */
+class KACTIVITIES_EXPORT Controller : public Consumer
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString currentActivity READ currentActivity WRITE setCurrentActivity)
+
+public:
+ explicit Controller(QObject *parent = nullptr);
+
+ ~Controller() override;
+
+ /**
+ * Sets the name of the specified activity
+ * @param id id of the activity
+ * @param name name to be set
+ */
+ QFuture<void> setActivityName(const QString &id, const QString &name);
+
+ /**
+ * Sets the description of the specified activity
+ * @param id id of the activity
+ * @param description description to be set
+ */
+ QFuture<void> setActivityDescription(const QString &id, const QString &description);
+
+ /**
+ * Sets the icon of the specified activity
+ * @param id id of the activity
+ * @param icon icon to be set - freedesktop.org name or file path
+ */
+ QFuture<void> setActivityIcon(const QString &id, const QString &icon);
+
+ /**
+ * Sets the current activity
+ * @param id id of the activity to make current
+ * @returns true if successful
+ */
+ QFuture<bool> setCurrentActivity(const QString &id);
+
+ /**
+ * Adds a new activity
+ * @param name name of the activity
+ * @returns id of the newly created activity
+ */
+ QFuture<QString> addActivity(const QString &name);
+
+ /**
+ * Removes the specified activity
+ * @param id id of the activity to delete
+ */
+ QFuture<void> removeActivity(const QString &id);
+
+ /**
+ * Stops the activity
+ * @param id id of the activity to stop
+ */
+ QFuture<void> stopActivity(const QString &id);
+
+ /**
+ * Starts the activity
+ * @param id id of the activity to start
+ */
+ QFuture<void> startActivity(const QString &id);
+
+ /**
+ * Switches to the previous activity
+ */
+ QFuture<void> previousActivity();
+
+ /**
+ * Switches to the next activity
+ */
+ QFuture<void> nextActivity();
+
+private:
+ // const QScopedPointer<ControllerPrivate> d;
+};
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_CONTROLLER_H
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "info.h"
+#include "info_p.h"
+#include "manager_p.h"
+
+#include "utils/dbusfuture_p.h"
+
+#include <QFileSystemWatcher>
+
+namespace KActivities
+{
+// InfoPrivate
+
+InfoPrivate::InfoPrivate(Info *info, const QString &activity)
+ : q(info)
+ , cache(ActivitiesCache::self())
+ , id(activity)
+{
+}
+
+// clang-format off
+// Filters out signals for only this activity
+#define IMPLEMENT_SIGNAL_HANDLER(INTERNAL) \
+ void InfoPrivate::INTERNAL(const QString &_id) const \
+ { if (id == _id) Q_EMIT q->INTERNAL(); }
+
+IMPLEMENT_SIGNAL_HANDLER(added)
+IMPLEMENT_SIGNAL_HANDLER(removed)
+IMPLEMENT_SIGNAL_HANDLER(started)
+IMPLEMENT_SIGNAL_HANDLER(stopped)
+IMPLEMENT_SIGNAL_HANDLER(infoChanged)
+
+#undef IMPLEMENT_SIGNAL_HANDLER
+
+#define IMPLEMENT_SIGNAL_HANDLER(INTERNAL) \
+ void InfoPrivate::INTERNAL##Changed(const QString &_id, \
+ const QString &val) const \
+ { \
+ if (id == _id) { \
+ Q_EMIT q->INTERNAL##Changed(val); \
+ } \
+ }
+
+IMPLEMENT_SIGNAL_HANDLER(name)
+IMPLEMENT_SIGNAL_HANDLER(description)
+IMPLEMENT_SIGNAL_HANDLER(icon)
+
+#undef IMPLEMENT_SIGNAL_HANDLER
+
+void InfoPrivate::activityStateChanged(const QString &idChanged,
+ int newState) const
+{
+ if (idChanged == id) {
+ auto state = static_cast<Info::State>(newState);
+ Q_EMIT q->stateChanged(state);
+
+ if (state == KActivities::Info::Stopped) {
+ Q_EMIT q->stopped();
+ } else if (state == KActivities::Info::Running) {
+ Q_EMIT q->started();
+ }
+ }
+}
+
+void InfoPrivate::setCurrentActivity(const QString ¤tActivity)
+{
+ if (isCurrent) {
+ if (currentActivity != id) {
+ // We are no longer the current activity
+ isCurrent = false;
+ Q_EMIT q->isCurrentChanged(false);
+ }
+ } else {
+ if (currentActivity == id) {
+ // We are the current activity
+ isCurrent = true;
+ Q_EMIT q->isCurrentChanged(true);
+ }
+ }
+}
+
+// Info
+Info::Info(const QString &activity, QObject *parent)
+ : QObject(parent)
+ , d(new InfoPrivate(this, activity))
+{
+ // qDebug() << "Created an instance of Info: " << (void*)this;
+#define PASS_SIGNAL_HANDLER(SIGNAL_NAME,SLOT_NAME) \
+ connect(d->cache.get(), SIGNAL(SIGNAL_NAME(QString)), \
+ this, SLOT(SLOT_NAME(QString)));
+
+ PASS_SIGNAL_HANDLER(activityAdded,added)
+ PASS_SIGNAL_HANDLER(activityRemoved,removed)
+ // PASS_SIGNAL_HANDLER(started)
+ // PASS_SIGNAL_HANDLER(stopped)
+ PASS_SIGNAL_HANDLER(activityChanged,infoChanged)
+#undef PASS_SIGNAL_HANDLER
+
+#define PASS_SIGNAL_HANDLER(SIGNAL_NAME,SLOT_NAME,TYPE) \
+ connect(d->cache.get(), SIGNAL(SIGNAL_NAME(QString,TYPE)), \
+ this, SLOT(SLOT_NAME(QString,TYPE))); \
+
+ PASS_SIGNAL_HANDLER(activityStateChanged,activityStateChanged,int);
+ PASS_SIGNAL_HANDLER(activityNameChanged,nameChanged,QString);
+ PASS_SIGNAL_HANDLER(activityDescriptionChanged,descriptionChanged,QString);
+ PASS_SIGNAL_HANDLER(activityIconChanged,iconChanged,QString);
+// clang-format on
+#undef PASS_SIGNAL_HANDLER
+ connect(d->cache.get(), SIGNAL(currentActivityChanged(QString)), this, SLOT(setCurrentActivity(QString)));
+
+ d->isCurrent = (d->cache.get()->m_currentActivity == activity);
+}
+
+Info::~Info()
+{
+ // qDebug() << "Deleted an instance of Info: " << (void*)this;
+}
+
+bool Info::isValid() const
+{
+ auto currentState = state();
+ return (currentState != Invalid && currentState != Unknown);
+}
+
+QString Info::uri() const
+{
+ return QStringLiteral("activities://") + d->id;
+}
+
+QString Info::id() const
+{
+ return d->id;
+}
+
+bool Info::isCurrent() const
+{
+ return d->isCurrent;
+}
+
+Info::State Info::state() const
+{
+ if (d->cache->m_status == Consumer::Unknown) {
+ return Info::Unknown;
+ }
+
+ auto info = d->cache->getInfo(d->id);
+
+ if (!info) {
+ return Info::Invalid;
+ }
+
+ return static_cast<Info::State>(info->state);
+}
+
+void InfoPrivate::setServiceStatus(Consumer::ServiceStatus status) const
+{
+ switch (status) {
+ case Consumer::NotRunning:
+ case Consumer::Unknown:
+ activityStateChanged(id, Info::Unknown);
+ break;
+
+ default:
+ activityStateChanged(id, q->state());
+ break;
+ }
+}
+
+Info::Availability Info::availability() const
+{
+ Availability result = Nothing;
+
+ if (!Manager::isServiceRunning()) {
+ return result;
+ }
+
+ if (Manager::activities()->ListActivities().value().contains(d->id)) {
+ result = BasicInfo;
+
+ if (Manager::features()->IsFeatureOperational(QStringLiteral("resources/linking"))) {
+ result = Everything;
+ }
+ }
+
+ return result;
+}
+
+// clang-format off
+#define CREATE_GETTER(What) \
+ QString Info::What() const \
+ { \
+ auto info = d->cache->getInfo(d->id); \
+ return info ? info->What : QString(); \
+ }
+// clang-format on
+
+CREATE_GETTER(name)
+CREATE_GETTER(description)
+CREATE_GETTER(icon)
+
+#undef CREATE_GETTER
+
+} // namespace KActivities
+
+#include "moc_info.cpp"
+// #include "moc_info_p.cpp"
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_INFO_H
+#define ACTIVITIES_INFO_H
+
+#include <QFuture>
+#include <QObject>
+#include <QString>
+
+#include "kactivities_export.h"
+
+namespace KActivities
+{
+class InfoPrivate;
+
+/**
+ * This class provides info about an activity. Most methods in it require a
+ * semantic backend running to function properly.
+ *
+ * This class is not thread-safe.
+ *
+ * @see Consumer for info about activities
+ *
+ * The API of the class is synchronous, but the most used properties
+ * are pre-fetched and cached. This means that, in order to get the least
+ * amount of d-bus related locks, you should declare long-lived instances
+ * of this class.
+ *
+ * Before relying on the values retrieved by the class, make sure that the
+ * state is not Info::Unknown. You can get invalid data either because the
+ * service is not functioning properly (or at all) or because the class did
+ * not have enough time to synchronize the data with it.
+ *
+ * For example, if this is the only existing instance of the Info class, the
+ * name method will return an empty string.
+ *
+ * For example, this is wrong (works, but blocks):
+ * @code
+ * void someMethod(const QString & activity) {
+ * // Do not copy. This approach is not a good one!
+ * Info info(activity);
+ * doSomethingWith(info.name());
+ * }
+ * @endcode
+ *
+ * Instances of the Info class should be long-lived. For example, members
+ * of the classes that use them, and you should listen for the changes in the
+ * provided properties.
+ *
+ * @since 4.5
+ */
+class KACTIVITIES_EXPORT Info : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString id READ id)
+ Q_PROPERTY(QString name READ name NOTIFY nameChanged)
+ Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
+ Q_PROPERTY(QString icon READ icon NOTIFY iconChanged)
+ Q_PROPERTY(bool isCurrent READ isCurrent NOTIFY isCurrentChanged)
+ Q_PROPERTY(Info::State state READ state NOTIFY stateChanged)
+
+public:
+ explicit Info(const QString &activity, QObject *parent = nullptr);
+ ~Info() override;
+
+ /**
+ * @return true if the activity represented by this object exists and is valid
+ */
+ bool isValid() const;
+
+ /**
+ * Specifies which parts of this class are functional
+ */
+ enum Availability {
+ Nothing = 0, ///< No activity info provided (isValid is false)
+ BasicInfo = 1, ///< Basic info is provided
+ Everything = 2, ///< Everything is available
+ };
+
+ /**
+ * State of the activity
+ */
+ enum State {
+ Invalid = 0, ///< This activity does not exist
+ Unknown = 1, ///< Information is not yet retrieved from the service
+ Running = 2, ///< Activity is running
+ Starting = 3, ///< Activity is begin started
+ Stopped = 4, ///< Activity is stopped
+ Stopping = 5, ///< Activity is begin started
+ };
+
+ /**
+ * @returns what info is provided by this instance of Info
+ */
+ Availability availability() const;
+
+ /**
+ * @returns the URI of this activity. The same URI is used by activities
+ * KIO worker.
+ */
+ QString uri() const;
+
+ /**
+ * @returns the id of the activity
+ */
+ QString id() const;
+
+ /**
+ * @returns whether this activity is the current one
+ */
+ bool isCurrent() const;
+
+ /**
+ * @returns the name of the activity
+ */
+ QString name() const;
+
+ /**
+ * @returns the description of the activity
+ */
+ QString description() const;
+
+ /**
+ * @returns the icon of the activity. Icon can be a freedesktop.org name or
+ * a file path. Or empty if no icon is set.
+ */
+ QString icon() const;
+
+ /**
+ * @returns the state of the activity
+ */
+ State state() const;
+
+ /**
+ * Links the specified resource to the activity
+ * @param resourceUri resource URI
+ * @note This method is <b>asynchronous</b>. It will return before the
+ * resource is actually linked to the activity.
+ */
+ // QFuture<void> linkResource(const QString &resourceUri);
+
+ /**
+ * Unlinks the specified resource from the activity
+ * @param resourceUri resource URI
+ * @note This method is <b>asynchronous</b>. It will return before the
+ * resource is actually unlinked from the activity.
+ */
+ // QFuture<void> unlinkResource(const QString &resourceUri);
+
+ /**
+ * @returns whether a resource is linked to this activity
+ * @note This QFuture is not thread-based, you can not call synchronous
+ * methods like waitForFinished, cancel, pause on it.
+ * @since 5.0
+ */
+ // QFuture<bool> isResourceLinked(const QString &resourceUri);
+
+Q_SIGNALS:
+ /**
+ * Emitted when the activity's name, icon or some custom property is changed
+ */
+ void infoChanged();
+
+ /**
+ * Emitted when the name is changed
+ */
+ void nameChanged(const QString &name);
+
+ /**
+ * Emitted when the activity becomes the current one, or when it stops
+ * being the current one
+ */
+ void isCurrentChanged(bool current);
+
+ /**
+ * Emitted when the description is changed
+ */
+ void descriptionChanged(const QString &description);
+
+ /**
+ * Emitted when the icon was changed
+ */
+ void iconChanged(const QString &icon);
+
+ /**
+ * Emitted when the activity is added
+ */
+ void added();
+
+ /**
+ * Emitted when the activity is removed
+ */
+ void removed();
+
+ /**
+ * Emitted when the activity is started
+ */
+ void started();
+
+ /**
+ * Emitted when the activity is stopped
+ */
+ void stopped();
+
+ /**
+ * Emitted when the activity changes state
+ * @param state new state of the activity
+ */
+ void stateChanged(KActivities::Info::State state);
+
+private:
+ const QScopedPointer<InfoPrivate> d;
+
+ Q_PRIVATE_SLOT(d, void activityStateChanged(const QString &, int))
+ Q_PRIVATE_SLOT(d, void added(const QString &))
+ Q_PRIVATE_SLOT(d, void removed(const QString &))
+ Q_PRIVATE_SLOT(d, void started(const QString &))
+ Q_PRIVATE_SLOT(d, void stopped(const QString &))
+ Q_PRIVATE_SLOT(d, void infoChanged(const QString &))
+ Q_PRIVATE_SLOT(d, void nameChanged(const QString &, const QString &))
+ Q_PRIVATE_SLOT(d, void descriptionChanged(const QString &, const QString &))
+ Q_PRIVATE_SLOT(d, void iconChanged(const QString &, const QString &))
+ Q_PRIVATE_SLOT(d, void setServiceStatus(Consumer::ServiceStatus))
+ Q_PRIVATE_SLOT(d, void setCurrentActivity(const QString &))
+
+ friend class InfoPrivate;
+};
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_INFO_H
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef KACTIVITIESINFO_P_H
+#define KACTIVITIESINFO_P_H
+
+#include "info.h"
+#include <memory>
+
+#include "activitiescache_p.h"
+
+namespace KActivities
+{
+class InfoPrivate
+{
+public:
+ InfoPrivate(Info *info, const QString &activity);
+
+ void activityStateChanged(const QString &, int) const;
+
+ void added(const QString &) const;
+ void removed(const QString &) const;
+ void started(const QString &) const;
+ void stopped(const QString &) const;
+ void infoChanged(const QString &) const;
+ void nameChanged(const QString &, const QString &) const;
+ void descriptionChanged(const QString &, const QString &) const;
+ void iconChanged(const QString &, const QString &) const;
+ void setServiceStatus(Consumer::ServiceStatus status) const;
+ void setCurrentActivity(const QString ¤tActivity);
+
+ Info *const q;
+ std::shared_ptr<ActivitiesCache> cache;
+ bool isCurrent;
+
+ const QString id;
+};
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_INFO_P_H
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2014-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "mainthreadexecutor_p.h"
+
+#include <mutex>
+
+#include <QCoreApplication>
+#include <QMetaObject>
+#include <QThread>
+
+namespace KActivities
+{
+namespace detail
+{
+MainThreadExecutor::MainThreadExecutor(std::function<void()> &&f)
+ : m_function(std::forward<std::function<void()>>(f))
+{
+}
+
+void MainThreadExecutor::start()
+{
+ m_function();
+ deleteLater();
+}
+
+} // namespace detail
+
+void runInMainThread(std::function<void()> &&f)
+{
+ static auto mainThread = QCoreApplication::instance()->thread();
+
+ if (QThread::currentThread() == mainThread) {
+ f();
+
+ } else {
+ auto executor = new detail::MainThreadExecutor(std::forward<std::function<void()>>(f));
+
+ executor->moveToThread(mainThread);
+
+ QMetaObject::invokeMethod(executor, "start", Qt::BlockingQueuedConnection);
+ }
+}
+
+} // namespace KActivities
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2014-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_MAINTHREADEXECUTOR_P
+#define ACTIVITIES_MAINTHREADEXECUTOR_P
+
+#include <functional>
+
+#include <QObject>
+
+namespace KActivities
+{
+namespace detail
+{
+class MainThreadExecutor : public QObject
+{
+ Q_OBJECT
+
+public:
+ MainThreadExecutor(std::function<void()> &&f);
+
+ Q_INVOKABLE void start();
+
+private:
+ std::function<void()> m_function;
+};
+} // namespace detail
+
+void runInMainThread(std::function<void()> &&f);
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_MAINTHREADEXECUTOR_P
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "manager_p.h"
+
+#include <mutex>
+#include <optional>
+
+#include <QCoreApplication>
+#include <QDBusConnection>
+#include <QFutureWatcher>
+#include <QFutureWatcherBase>
+
+#include "debug_p.h"
+#include "mainthreadexecutor_p.h"
+
+#include "common/dbus/common.h"
+#include "utils/continue_with.h"
+#include "utils/dbusfuture_p.h"
+#include "version.h"
+
+namespace KActivities
+{
+Manager *Manager::s_instance = nullptr;
+
+Manager::Manager()
+ : QObject()
+ , m_watcher(KAMD_DBUS_SERVICE, QDBusConnection::sessionBus())
+ , m_service(new KAMD_DBUS_CLASS_INTERFACE("/", Application, this))
+ , m_activities(new KAMD_DBUS_CLASS_INTERFACE("Activities", Activities, this))
+ , m_resources(new KAMD_DBUS_CLASS_INTERFACE("Resources", Resources, this))
+ , m_resourcesLinking(new KAMD_DBUS_CLASS_INTERFACE("Resources/Linking", ResourcesLinking, this))
+ , m_features(new KAMD_DBUS_CLASS_INTERFACE("Features", Features, this))
+ , m_serviceRunning(false)
+{
+ connect(&m_watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &Manager::serviceOwnerChanged);
+
+ if (isServiceRunning()) {
+ serviceOwnerChanged(KAMD_DBUS_SERVICE, QString(), KAMD_DBUS_SERVICE);
+ }
+}
+
+Manager *Manager::self()
+{
+ static std::mutex singleton;
+ std::lock_guard<std::mutex> singleton_lock(singleton);
+
+ if (!s_instance) {
+ runInMainThread([]() {
+ // check if the activity manager is already running
+ if (!Manager::isServiceRunning()) {
+ bool disableAutolaunch = QCoreApplication::instance()->property("org.kde.KActivities.core.disableAutostart").toBool();
+
+ qCDebug(KAMD_CORELIB) << "Should we start the daemon?";
+ // start only if not disabled and we have a dbus connection at all
+ if (!disableAutolaunch && QDBusConnection::sessionBus().interface()) {
+ qCDebug(KAMD_CORELIB) << "Starting the activity manager daemon";
+ auto busInterface = QDBusConnection::sessionBus().interface();
+ busInterface->asyncCall(QStringLiteral("StartServiceByName"), KAMD_DBUS_SERVICE, uint(0));
+ }
+ }
+
+ // creating a new instance of the class
+ Manager::s_instance = new Manager();
+ });
+ }
+
+ return s_instance;
+}
+
+bool Manager::isServiceRunning()
+{
+ return (s_instance ? s_instance->m_serviceRunning : true) && QDBusConnection::sessionBus().interface()
+ && QDBusConnection::sessionBus().interface()->isServiceRegistered(KAMD_DBUS_SERVICE);
+}
+
+void Manager::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner)
+{
+ Q_UNUSED(oldOwner);
+
+ if (serviceName == KAMD_DBUS_SERVICE) {
+ m_serviceRunning = !newOwner.isEmpty();
+ Q_EMIT serviceStatusChanged(m_serviceRunning);
+
+ if (m_serviceRunning) {
+ using namespace kamd::utils;
+
+ continue_with(DBusFuture::fromReply(m_service->serviceVersion()), [this](const std::optional<QString> &serviceVersion) {
+ // Test whether the service is older than the library.
+ // If it is, we need to end this
+
+ if (!serviceVersion.has_value()) {
+ qWarning() << "KActivities: FATAL ERROR: Failed to contact the activity manager daemon";
+ m_serviceRunning = false;
+ return;
+ }
+
+ auto split = serviceVersion->split(QLatin1Char('.'));
+ QList<int> version;
+
+ // We require kactivitymanagerd version to be at least the
+ // one before the repository split
+ const int requiredVersion[] = {6, 2, 0};
+
+ std::transform(split.cbegin(), split.cend(), std::back_inserter(version), [](const QString &component) {
+ return component.toInt();
+ });
+
+ // if required version is greater than the current version
+ if (std::lexicographical_compare(version.cbegin(), version.cend(), std::begin(requiredVersion), std::end(requiredVersion))) {
+ QString libraryVersion = QString::number(requiredVersion[0]) + QLatin1Char('.') + QString::number(requiredVersion[1]) + QLatin1Char('.')
+ + QString::number(requiredVersion[2]);
+
+ qDebug() << "KActivities service version: " << serviceVersion.value();
+ qDebug() << "KActivities library version: " << libraryVersion;
+ qFatal("KActivities: FATAL ERROR: The service is older than the library");
+ }
+ });
+ }
+ }
+}
+
+Service::Activities *Manager::activities()
+{
+ return self()->m_activities;
+}
+
+Service::Resources *Manager::resources()
+{
+ return self()->m_resources;
+}
+
+Service::ResourcesLinking *Manager::resourcesLinking()
+{
+ return self()->m_resourcesLinking;
+}
+
+Service::Features *Manager::features()
+{
+ return self()->m_features;
+}
+
+} // namespace KActivities
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2010-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_MANAGER_P
+#define ACTIVITIES_MANAGER_P
+
+#include <common/dbus/org.kde.ActivityManager.Activities.h>
+
+#include "activities_interface.h"
+#include "application_interface.h"
+#include "features_interface.h"
+#include "resources_interface.h"
+#include "resources_linking_interface.h"
+
+#include <QDBusServiceWatcher>
+
+namespace Service = org::kde::ActivityManager;
+
+namespace KActivities
+{
+class Manager : public QObject
+{
+ Q_OBJECT
+
+public:
+ static Manager *self();
+
+ static bool isServiceRunning();
+
+ static Service::Activities *activities();
+ static Service::Resources *resources();
+ static Service::ResourcesLinking *resourcesLinking();
+ static Service::Features *features();
+
+public Q_SLOTS:
+ void serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner);
+
+Q_SIGNALS:
+ void serviceStatusChanged(bool status);
+
+private:
+ Manager();
+
+ QDBusServiceWatcher m_watcher;
+
+ static Manager *s_instance;
+
+ Service::Application *const m_service;
+ Service::Activities *const m_activities;
+ Service::Resources *const m_resources;
+ Service::ResourcesLinking *const m_resourcesLinking;
+ Service::Features *const m_features;
+ bool m_serviceRunning;
+
+ friend class ManagerInstantiator;
+};
+
+} // namespace KActivities
+
+#endif // ACTIVITIES_MANAGER_P
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2011-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "resourceinstance.h"
+#include "manager_p.h"
+
+#include "debug_p.h"
+#include <QCoreApplication>
+
+namespace KActivities
+{
+class ResourceInstancePrivate
+{
+public:
+ quintptr wid;
+ QUrl uri;
+ QString mimetype;
+ QString title;
+ QString application;
+
+ void closeResource();
+ void openResource();
+
+ enum Type {
+ Accessed = 0,
+ Opened = 1,
+ Modified = 2,
+ Closed = 3,
+ FocusedIn = 4,
+ FocusedOut = 5,
+ };
+
+ static void registerResourceEvent(const QString &application, quintptr wid, const QUrl &uri, Type event)
+ {
+ Q_ASSERT_X(!application.isEmpty(), "ResourceInstance::event", "The application id must not be empty");
+
+ if (uri.isEmpty()) {
+ return;
+ }
+
+ Manager::resources()->RegisterResourceEvent(application, wid, uri.toString(), uint(event));
+ }
+};
+
+void ResourceInstancePrivate::closeResource()
+{
+ registerResourceEvent(application, wid, uri, Closed);
+}
+
+void ResourceInstancePrivate::openResource()
+{
+ registerResourceEvent(application, wid, uri, Opened);
+}
+
+ResourceInstance::ResourceInstance(quintptr wid, QObject *parent)
+ : QObject(parent)
+ , d(new ResourceInstancePrivate())
+{
+ qCDebug(KAMD_CORELIB) << "Creating ResourceInstance: empty for now";
+ d->wid = wid;
+ d->application = QCoreApplication::instance()->applicationName();
+}
+
+ResourceInstance::ResourceInstance(quintptr wid, const QString &application, QObject *parent)
+ : QObject(parent)
+ , d(new ResourceInstancePrivate())
+{
+ qCDebug(KAMD_CORELIB) << "Creating ResourceInstance: empty for now";
+ d->wid = wid;
+ d->application = application.isEmpty() ? QCoreApplication::instance()->applicationName() : application;
+}
+
+ResourceInstance::ResourceInstance(quintptr wid, QUrl resourceUri, const QString &mimetype, const QString &title, const QString &application, QObject *parent)
+ : QObject(parent)
+ , d(new ResourceInstancePrivate())
+{
+ qCDebug(KAMD_CORELIB) << "Creating ResourceInstance:" << resourceUri;
+ d->wid = wid;
+ d->uri = resourceUri.adjusted(QUrl::StripTrailingSlash);
+ d->application = application.isEmpty() ? QCoreApplication::instance()->applicationName() : application;
+
+ d->openResource();
+
+ setTitle(title);
+ setMimetype(mimetype);
+}
+
+ResourceInstance::~ResourceInstance()
+{
+ d->closeResource();
+}
+
+void ResourceInstance::notifyModified()
+{
+ d->registerResourceEvent(d->application, d->wid, d->uri, ResourceInstancePrivate::Modified);
+}
+
+void ResourceInstance::notifyFocusedIn()
+{
+ d->registerResourceEvent(d->application, d->wid, d->uri, ResourceInstancePrivate::FocusedIn);
+}
+
+void ResourceInstance::notifyFocusedOut()
+{
+ d->registerResourceEvent(d->application, d->wid, d->uri, ResourceInstancePrivate::FocusedOut);
+}
+
+void ResourceInstance::setUri(const QUrl &newUri)
+{
+ if (d->uri == newUri) {
+ return;
+ }
+
+ if (!d->uri.isEmpty()) {
+ d->closeResource();
+ }
+
+ d->uri = newUri.adjusted(QUrl::StripTrailingSlash);
+
+ d->openResource();
+}
+
+void ResourceInstance::setMimetype(const QString &mimetype)
+{
+ if (mimetype.isEmpty()) {
+ return;
+ }
+
+ d->mimetype = mimetype;
+ // TODO: update the service info
+ Manager::resources()->RegisterResourceMimetype(d->uri.toString(), mimetype);
+}
+
+void ResourceInstance::setTitle(const QString &title)
+{
+ qCDebug(KAMD_CORELIB) << "Setting the title:" << title;
+ if (title.isEmpty()) {
+ return;
+ }
+
+ d->title = title;
+ // TODO: update the service info
+ Manager::resources()->RegisterResourceTitle(d->uri.toString(), title);
+}
+
+QUrl ResourceInstance::uri() const
+{
+ return d->uri;
+}
+
+QString ResourceInstance::mimetype() const
+{
+ return d->mimetype;
+}
+
+QString ResourceInstance::title() const
+{
+ return d->title;
+}
+
+quintptr ResourceInstance::winId() const
+{
+ return d->wid;
+}
+
+void ResourceInstance::notifyAccessed(const QUrl &uri, const QString &application)
+{
+ ResourceInstancePrivate::registerResourceEvent(application.isEmpty() ? QCoreApplication::instance()->applicationName() : application,
+ 0,
+ uri,
+ ResourceInstancePrivate::Accessed);
+}
+
+} // namespace KActivities
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2011-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_RESOURCEINSTANCE_H
+#define ACTIVITIES_RESOURCEINSTANCE_H
+
+#include <QObject>
+#include <QUrl>
+
+#include "kactivities_export.h"
+
+namespace KActivities
+{
+class ResourceInstancePrivate;
+
+/**
+ * This class is used to notify the system that a file, web page
+ * or some other resource has been accessed.
+ *
+ * It provides methods to notify the system when the resource was
+ * opened, modified and closed, along with in what window the
+ * resource is shown.
+ *
+ * You should create an instance of this class for every resource
+ * you open.
+ *
+ * "The system" in this case can be the backend for tracking
+ * and automatically scoring files that are being accessed, the
+ * system to show the open files per window in the taskbar,
+ * the share-like-connect, etc.
+ *
+ * The user of this class shouldn't care about the backend
+ * systems - everything is done under-the-hood automatically.
+ *
+ */
+class KACTIVITIES_EXPORT ResourceInstance : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QUrl uri READ uri WRITE setUri)
+ Q_PROPERTY(QString mimetype READ mimetype WRITE setMimetype)
+ Q_PROPERTY(QString title READ title WRITE setTitle)
+ Q_PROPERTY(quintptr winId READ winId)
+
+public:
+ /**
+ * Creates a new resource instance
+ * @param wid id of the window that will show the resource
+ * @param parent pointer to the parent object
+ * @since 4.10
+ */
+ explicit ResourceInstance(quintptr wid, QObject *parent = nullptr);
+
+ /**
+ * Creates a new resource instance
+ * @param wid id of the window that will show the resource
+ * @param application application's name (the name used for the .desktop file).
+ * If not specified, QCoreApplication::applicationName is used
+ * @param parent pointer to the parent object
+ */
+ explicit ResourceInstance(quintptr wid, const QString &application, QObject *parent = nullptr);
+
+ /**
+ * Creates a new resource instance and automatically
+ * notifies the system that it was opened.
+ *
+ * In some special cases, where the URI of the resource is
+ * being constantly changed (for example, in the world globe,
+ * street map applications) you have two options:
+ * - to pass an empty resourceUri while passing the mimetype
+ * - to update the uri from time to time (in the example of
+ * the world map - to send URIs for major objects - cities
+ * or main streets.
+ * and in both cases reimplementing the currentUri() method
+ * which will return the exact URI shown at that specific moment.
+ *
+ * @param wid window id in which the resource is shown
+ * @param resourceUri URI of the resource that is shown
+ * @param mimetype the mime type of the resource
+ * @param title the title of the resource
+ * @param application application's name (the name used for the .desktop file).
+ * If not specified, QCoreApplication::applicationName is used
+ * @param parent pointer to the parent object
+ */
+ ResourceInstance(quintptr wid,
+ QUrl resourceUri,
+ const QString &mimetype = QString(),
+ const QString &title = QString(),
+ const QString &application = QString(),
+ QObject *parent = nullptr);
+
+ /**
+ * Destroys the ResourceInstance and notifies the system
+ * that the resource has been closed
+ */
+ ~ResourceInstance() override;
+
+public Q_SLOTS:
+ /**
+ * Call this method to notify the system that you modified
+ * (the contents of) the resource
+ */
+ void notifyModified();
+
+ /**
+ * Call this method to notify the system that the resource
+ * has the focus in your application
+ * @note You only need to call this in MDI applications
+ */
+ void notifyFocusedIn();
+
+ /**
+ * Call this method to notify the system that the resource
+ * lost the focus in your application
+ * @note You only need to call this in MDI applications
+ */
+ void notifyFocusedOut();
+
+ /**
+ * This is a convenience method that sets the new URI.
+ * This is usually handled by sending the close event for
+ * the previous URI, and an open event for the new one.
+ */
+ void setUri(const QUrl &newUri);
+
+ /**
+ * Sets the mimetype for this resource
+ */
+ void setMimetype(const QString &mimetype);
+
+ /**
+ * Sets the title for this resource
+ */
+ void setTitle(const QString &title);
+
+Q_SIGNALS:
+ /**
+ * Emitted when the system wants to show the resource
+ * represented by this ResourceInstance.
+ *
+ * You should listen to this signal if you have multiple
+ * resources shown in one window (MDI). On catching it, show
+ * the resource and give it focus.
+ */
+ void requestsFocus();
+
+public:
+ /**
+ * @returns the current uri
+ * The default implementation returns the URI that was passed
+ * to the constructor.
+ * You need to reimplement it only for the applications with
+ * frequently updated URIs.
+ */
+ virtual QUrl uri() const;
+
+ /**
+ * @returns mimetype of the resource
+ */
+ QString mimetype() const;
+
+ /**
+ * @returns title of the resource
+ */
+ QString title() const;
+
+ /**
+ * @returns the window id
+ */
+ quintptr winId() const;
+
+ /**
+ * If there's no way to tell for how long an application is keeping
+ * the resource open, you can just call this static method - it
+ * will notify the system that the application has accessed the
+ * resource
+ * @param uri URI of the resource
+ * @param application application's name (the name used for the .desktop file).
+ * If not specified, QCoreApplication::applicationName is used
+ *
+ */
+ static void notifyAccessed(const QUrl &uri, const QString &application = QString());
+
+private:
+ const QScopedPointer<ResourceInstancePrivate> d;
+};
+}
+
+#endif // ACTIVITIES_RESOURCEINSTANCE_H
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2008 Aaron Seigo <aseigo@kde.org>
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#include "version.h"
+
+namespace KActivities
+{
+unsigned int version()
+{
+ return KACTIVITIES_VERSION;
+}
+
+unsigned int versionMajor()
+{
+ return KACTIVITIES_VERSION_MAJOR;
+}
+
+unsigned int versionMinor()
+{
+ return KACTIVITIES_VERSION_MINOR;
+}
+
+unsigned int versionRelease()
+{
+ return KACTIVITIES_VERSION_RELEASE;
+}
+
+const char *versionString()
+{
+ return KACTIVITIES_VERSION_STRING;
+}
+
+} // KActivities namespace
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2008-2016 Aaron Seigo <aseigo@kde.org>
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef KACTIVITIES_VERSION_BIN_H
+#define KACTIVITIES_VERSION_BIN_H
+
+/** @file version.h <KActivities/Version> */
+
+#include "kactivities_export.h"
+#include <kactivities_version.h>
+
+#define KACTIVITIES_VERSION_RELEASE KACTIVITIES_VERSION_PATCH
+
+/**
+ * Namespace for everything in libkactivities
+ */
+namespace KActivities
+{
+/**
+ * The runtime version of libkactivities
+ */
+KACTIVITIES_EXPORT unsigned int version();
+
+/**
+ * The runtime major version of libkactivities
+ */
+KACTIVITIES_EXPORT unsigned int versionMajor();
+
+/**
+ * The runtime major version of libkactivities
+ */
+KACTIVITIES_EXPORT unsigned int versionMinor();
+
+/**
+ * The runtime major version of libkactivities
+ */
+KACTIVITIES_EXPORT unsigned int versionRelease();
+
+/**
+ * The runtime version string of libkactivities
+ */
+KACTIVITIES_EXPORT const char *versionString();
+
+} // KActivities namespace
+
+#endif // multiple inclusion guard
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2014-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef UTILS_CONTINUE_WITH_H
+#define UTILS_CONTINUE_WITH_H
+
+#include <QDebug>
+#include <QFuture>
+#include <QFutureWatcher>
+
+#include <optional>
+
+#ifdef ENABLE_QJSVALUE_CONTINUATION
+#include <QJSValue>
+#endif
+
+namespace kamd
+{
+namespace utils
+{
+namespace detail
+{ //_
+#ifdef ENABLE_QJSVALUE_CONTINUATION
+inline void test_continuation(const QJSValue &continuation)
+{
+ if (!continuation.isCallable()) {
+ qWarning() << "Passed handler is not callable: " << continuation.toString();
+ }
+}
+
+template<typename _ReturnType>
+inline void pass_value(const QFuture<_ReturnType> &future, QJSValue continuation)
+{
+ auto result = continuation.call({future.result()});
+ if (result.isError()) {
+ qWarning() << "Handler returned this error: " << result.toString();
+ }
+}
+
+inline void pass_value(const QFuture<void> &future, QJSValue continuation)
+{
+ Q_UNUSED(future);
+ auto result = continuation.call({});
+ if (result.isError()) {
+ qWarning() << "Handler returned this error: " << result.toString();
+ }
+}
+#endif
+
+template<typename _Continuation>
+inline void test_continuation(_Continuation &&continuation)
+{
+ Q_UNUSED(continuation);
+}
+
+template<typename _ReturnType, typename _Continuation>
+inline void pass_value(const QFuture<_ReturnType> &future, _Continuation &&continuation)
+{
+ using namespace kamd::utils;
+ continuation(future.resultCount() > 0 ? std::optional(future.result()) : std::nullopt);
+}
+
+template<typename _Continuation>
+inline void pass_value(_Continuation &&continuation)
+{
+ continuation();
+}
+
+} //^ namespace detail
+
+template<typename _ReturnType, typename _Continuation>
+inline void continue_with(const QFuture<_ReturnType> &future, _Continuation &&continuation)
+{
+ detail::test_continuation(continuation);
+
+ auto watcher = new QFutureWatcher<_ReturnType>();
+ QObject::connect(watcher, &QFutureWatcherBase::finished, [=]() mutable {
+ detail::pass_value(future, continuation);
+ });
+
+ watcher->setFuture(future);
+}
+
+} // namespace utils
+} // namespace kamd
+
+#endif /* !UTILS_CONTINUE_WITH_H */
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2012-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "dbusfuture_p.h"
+
+namespace DBusFuture
+{
+namespace detail
+{ //_
+
+template<>
+void DBusCallFutureInterface<void>::callFinished()
+{
+ deleteLater();
+
+ // qDebug() << "This is call end";
+
+ this->reportFinished();
+}
+
+ValueFutureInterface<void>::ValueFutureInterface()
+{
+}
+
+QFuture<void> ValueFutureInterface<void>::start()
+{
+ auto future = this->future();
+
+ this->reportFinished();
+
+ deleteLater();
+
+ return future;
+}
+
+} //^ namespace detail
+
+QFuture<void> fromVoid()
+{
+ using namespace detail;
+
+ auto valueFutureInterface = new ValueFutureInterface<void>();
+
+ return valueFutureInterface->start();
+}
+
+} // namespace DBusFuture
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2013-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIVITIES_DBUSFUTURE_P_H
+#define ACTIVITIES_DBUSFUTURE_P_H
+
+#include <QDBusAbstractInterface>
+#include <QDBusPendingCallWatcher>
+#include <QDBusPendingReply>
+#include <QDBusServiceWatcher>
+#include <QFuture>
+#include <QFutureInterface>
+#include <QFutureWatcherBase>
+
+#include "debug_p.h"
+
+namespace DBusFuture
+{
+namespace detail
+{ //_
+
+template<typename _Result>
+class DBusCallFutureInterface : public QObject, public QFutureInterface<_Result>
+{
+public:
+ DBusCallFutureInterface(QDBusPendingReply<_Result> reply)
+ : reply(reply)
+ , replyWatcher(nullptr)
+ {
+ }
+
+ ~DBusCallFutureInterface() override
+ {
+ delete replyWatcher;
+ }
+
+ void callFinished();
+
+ QFuture<_Result> start()
+ {
+ replyWatcher = new QDBusPendingCallWatcher(reply);
+
+ QObject::connect(replyWatcher, &QDBusPendingCallWatcher::finished, [this]() {
+ callFinished();
+ });
+
+ this->reportStarted();
+
+ if (reply.isFinished()) {
+ this->callFinished();
+ }
+
+ return this->future();
+ }
+
+private:
+ QDBusPendingReply<_Result> reply;
+ QDBusPendingCallWatcher *replyWatcher;
+};
+
+template<typename _Result>
+void DBusCallFutureInterface<_Result>::callFinished()
+{
+ deleteLater();
+
+ if (!reply.isError()) {
+ this->reportResult(reply.value());
+ }
+
+ this->reportFinished();
+}
+
+template<>
+void DBusCallFutureInterface<void>::callFinished();
+
+template<typename _Result>
+class ValueFutureInterface : public QObject, QFutureInterface<_Result>
+{
+public:
+ ValueFutureInterface(const _Result &value)
+ : value(value)
+ {
+ }
+
+ QFuture<_Result> start()
+ {
+ auto future = this->future();
+
+ this->reportResult(value);
+ this->reportFinished();
+
+ deleteLater();
+
+ return future;
+ }
+
+private:
+ _Result value;
+};
+
+template<>
+class ValueFutureInterface<void> : public QObject, QFutureInterface<void>
+{
+public:
+ ValueFutureInterface();
+
+ QFuture<void> start();
+ // {
+ // auto future = this->future();
+ // this->reportFinished();
+ // deleteLater();
+ // return future;
+ // }
+};
+
+} //^ namespace detail
+
+template<typename _Result>
+QFuture<_Result> asyncCall(QDBusAbstractInterface *interface,
+ const QString &method,
+ const QVariant &arg1 = QVariant(),
+ const QVariant &arg2 = QVariant(),
+ const QVariant &arg3 = QVariant(),
+ const QVariant &arg4 = QVariant(),
+ const QVariant &arg5 = QVariant(),
+ const QVariant &arg6 = QVariant(),
+ const QVariant &arg7 = QVariant(),
+ const QVariant &arg8 = QVariant())
+{
+ using namespace detail;
+
+ auto callFutureInterface = new DBusCallFutureInterface<_Result>(interface->asyncCall(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8));
+
+ return callFutureInterface->start();
+}
+
+template<typename _Result>
+QFuture<_Result> fromValue(const _Result &value)
+{
+ using namespace detail;
+
+ auto valueFutureInterface = new ValueFutureInterface<_Result>(value);
+
+ return valueFutureInterface->start();
+}
+
+template<typename _Result>
+QFuture<_Result> fromReply(const QDBusPendingReply<_Result> &reply)
+{
+ using namespace detail;
+
+ auto callFutureInterface = new DBusCallFutureInterface<_Result>(reply);
+
+ return callFutureInterface->start();
+}
+
+QFuture<void> fromVoid();
+
+} // namespace DBusFuture
+
+#endif /* DBUSFUTURE_P_H */
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2012 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef KACTIVITIES_MODEL_UPDATERS_H
+#define KACTIVITIES_MODEL_UPDATERS_H
+
+// -----------------------------------------
+// RAII classes for model updates ----------
+// -----------------------------------------
+
+// clang-format off
+#define DECLARE_RAII_MODEL_UPDATERS(Class) \
+ template <typename T> class _model_reset { \
+ T *model; \
+ \
+ public: \
+ _model_reset(T *m) : model(m) \
+ { \
+ model->beginResetModel(); \
+ } \
+ ~_model_reset() \
+ { \
+ model->endResetModel(); \
+ } \
+ }; \
+ template <typename T> class _model_insert { \
+ T *model; \
+ \
+ public: \
+ _model_insert(T *m, const QModelIndex &parent, int first, int last) \
+ : model(m) \
+ { \
+ model->beginInsertRows(parent, first, last); \
+ } \
+ ~_model_insert() \
+ { \
+ model->endInsertRows(); \
+ } \
+ }; \
+ template <typename T> class _model_remove { \
+ T *model; \
+ \
+ public: \
+ _model_remove(T *m, const QModelIndex &parent, int first, int last) \
+ : model(m) \
+ { \
+ model->beginRemoveRows(parent, first, last); \
+ } \
+ ~_model_remove() \
+ { \
+ model->endRemoveRows(); \
+ } \
+ }; \
+ typedef _model_reset<Class> model_reset; \
+ typedef _model_remove<Class> model_remove; \
+ typedef _model_insert<Class> model_insert;
+
+// -----------------------------------------
+
+#endif // KACTIVITIES_MODEL_UPDATERS_H
+
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2015-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef PTR_TO_H
+#define PTR_TO_H
+
+namespace kamd
+{
+namespace utils
+{
+enum {
+ Const = 0,
+ Mutable = 1,
+};
+
+template<typename T, int Policy = Const>
+struct ptr_to {
+ typedef const T *const type;
+};
+
+template<typename T>
+struct ptr_to<T, Mutable> {
+ typedef T *const type;
+};
+
+} // namespace utils
+} // namespace kamd
+
+#endif // PTR_TO_H
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2016 Ivan Čukić <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef KACTIVITIES_STATS_QFLATSET_H
+#define KACTIVITIES_STATS_QFLATSET_H
+
+#include <QPair>
+#include <QVector>
+
+namespace KActivities
+{
+template<typename T, typename LessThan>
+class QFlatSet : public QVector<T>
+{
+public:
+ QFlatSet()
+ {
+ }
+
+ inline
+ // QPair<typename QVector<T>::iterator, bool> insert(const T &value)
+ std::tuple<typename QVector<T>::iterator, int, bool>
+ insert(const T &value)
+ {
+ auto lessThan = LessThan();
+ auto begin = this->begin();
+ auto end = this->end();
+
+ if (begin == end) {
+ QVector<T>::insert(0, value);
+
+ return std::make_tuple(QVector<T>::begin(), 0, true);
+
+ } else {
+ auto iterator = std::lower_bound(begin, end, value, lessThan);
+
+ if (iterator != end) {
+ if (!lessThan(value, *iterator)) {
+ // Already present
+ return std::make_tuple(iterator, iterator - begin, false);
+ }
+ }
+
+ QVector<T>::insert(iterator, value);
+
+ return std::make_tuple(iterator, iterator - begin, true);
+ }
+ }
+
+private:
+ QFlatSet(const QFlatSet &original); // = delete
+};
+
+} // namespace KActivities
+
+#endif // KACTIVITIES_STATS_QFLATSET_H
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2012 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef UTILS_RANGE_H
+#define UTILS_RANGE_H
+
+#include <boost/range/adaptor/filtered.hpp>
+#include <boost/range/adaptor/transformed.hpp>
+#include <boost/range/algorithm/copy.hpp>
+
+/********************************************************************
+ * Syntactic sugar for converting ranges to collections *
+ ********************************************************************/
+
+namespace kamd
+{
+namespace utils
+{
+template<typename Collection, typename Range>
+__inline Collection as_collection(Range range)
+{
+ Collection result;
+
+ boost::copy(range, std::back_inserter(result));
+
+ return result;
+}
+
+template<typename Member, typename... Args>
+__inline auto transformed(Member member, Args... args) -> decltype(boost::adaptors::transformed(std::bind(member, args..., std::placeholders::_1)))
+{
+ return boost::adaptors::transformed(std::bind(member, args..., std::placeholders::_1));
+}
+
+template<typename Member, typename... Args>
+__inline auto filtered(Member member, Args... args) -> decltype(boost::adaptors::filtered(std::bind(member, args..., std::placeholders::_1)))
+{
+ return boost::adaptors::filtered(std::bind(member, args..., std::placeholders::_1));
+}
+
+template<typename Class, typename Member>
+__inline auto filtered(Class *const self, Member member) -> decltype(boost::adaptors::filtered(std::bind(member, self, std::placeholders::_1)))
+{
+ return boost::adaptors::filtered(std::bind(member, self, std::placeholders::_1));
+}
+
+} // namespace utils
+} // namespace kamd
+
+#endif // UTILS_RANGE_H
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2012 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef UTILS_REMOVE_IF_H
+#define UTILS_REMOVE_IF_H
+
+#include <algorithm>
+
+/********************************************************************
+ * Syntactic sugar for the erase-remove idiom *
+ ********************************************************************/
+
+namespace kamd
+{
+namespace utils
+{
+template<typename Collection, typename Filter>
+__inline void remove_if(Collection &collection, Filter filter)
+{
+ collection.erase(std::remove_if(collection.begin(), collection.end(), filter), collection.end());
+}
+
+} // namespace utils
+} // namespace kamd
+
+#endif // UTILS_REMOVE_IF_H
--- /dev/null
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+
+# add_subdirectory(slc-interface)
+
+if (UNIX AND NOT APPLE)
+ # Requires X11
+ add_subdirectory(activities-model)
+endif ()
+
+
--- /dev/null
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+project (KActivitiesModelTestApp)
+
+find_package (Qt${QT_MAJOR_VERSION} REQUIRED NO_MODULE COMPONENTS Core Gui Widgets)
+find_package (KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS WindowSystem)
+
+include_directories (
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/autotests/
+ )
+
+set (
+ KActivitiesModelTestApp_SRCS
+ window.cpp
+ main.cpp
+ )
+
+qt_wrap_ui(
+ KActivitiesModelTestApp_SRCS
+ window.ui
+ )
+
+if (NOT WIN32)
+
+ add_executable (
+ KActivitiesModelTestApp
+ ${KActivitiesModelTestApp_SRCS}
+ )
+
+ target_link_libraries (
+ KActivitiesModelTestApp
+ Qt${QT_MAJOR_VERSION}::Core
+ Qt${QT_MAJOR_VERSION}::Gui
+ Qt${QT_MAJOR_VERSION}::Widgets
+ Qt${QT_MAJOR_VERSION}::DBus
+ KF5::Activities
+ KF5::WindowSystem
+ )
+
+endif ()
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "window.h"
+#include <QApplication>
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+
+ Window w;
+ w.show();
+
+ return app.exec();
+}
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "window.h"
+
+#include "ui_window.h"
+
+#include <QItemDelegate>
+#include <QPainter>
+
+#include <KX11Extras>
+
+class Delegate : public QItemDelegate
+{
+public:
+ void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
+ {
+ painter->save();
+
+ const QString title = index.data().toString();
+
+ QRect titleRect = painter->fontMetrics().boundingRect(title);
+ // unused int lineHeight = titleRect.height();
+
+ // Header background
+ auto rect = option.rect;
+ rect.setHeight(64);
+ titleRect.moveTop(option.rect.top());
+ titleRect.setWidth(option.rect.width());
+
+ if (index.data(KActivities::ActivitiesModel::ActivityIsCurrent).toBool()) {
+ painter->fillRect(rect, QColor(64, 64, 64));
+ } else {
+ painter->fillRect(rect, QColor(32, 32, 32));
+ }
+
+ // Painting the title
+ painter->setPen(QColor(255, 255, 255));
+
+ titleRect.moveTop(titleRect.top() + 8);
+ titleRect.setLeft(64 + 8);
+ titleRect.setWidth(titleRect.width() - 64 - 8);
+ painter->drawText(titleRect, title);
+
+ titleRect.moveTop(titleRect.bottom() + 16);
+
+ const QString description = index.data(KActivities::ActivitiesModel::ActivityDescription).toString();
+
+ if (!description.isEmpty()) {
+ painter->drawText(titleRect, index.data(KActivities::ActivitiesModel::ActivityDescription).toString());
+ } else {
+ painter->setPen(QColor(128, 128, 128));
+ painter->drawText(titleRect, index.data(KActivities::ActivitiesModel::ActivityId).toString());
+ }
+
+ const QString iconName = index.data(KActivities::ActivitiesModel::ActivityIconSource).toString();
+
+ if (!iconName.isEmpty()) {
+ painter->drawPixmap(option.rect.x(), option.rect.y(), QIcon::fromTheme(iconName).pixmap(64, 64));
+ }
+
+ painter->restore();
+ }
+
+ QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
+ {
+ Q_UNUSED(option);
+ Q_UNUSED(index);
+ return QSize(0, 70);
+ }
+};
+
+Window::Window()
+ : ui(new Ui::MainWindow())
+ , activities(new KActivities::Consumer(this))
+ , modelRunningActivities(new KActivities::ActivitiesModel({KActivities::Info::Running, KActivities::Info::Stopping}, this))
+ , modelStoppedActivities(new KActivities::ActivitiesModel({KActivities::Info::Stopped, KActivities::Info::Starting}, this))
+{
+ ui->setupUi(this);
+
+ modelRunningActivities->setObjectName(QStringLiteral("RUNNING"));
+ ui->listRunningActivities->setModel(modelRunningActivities);
+ ui->listRunningActivities->setItemDelegate(new Delegate());
+
+ modelStoppedActivities->setObjectName(QStringLiteral("STOPPED"));
+ ui->listStoppedActivities->setModel(modelStoppedActivities);
+ ui->listStoppedActivities->setItemDelegate(new Delegate());
+
+ qDebug() << connect(activities, &KActivities::Consumer::runningActivitiesChanged, this, [](const QStringList &running) {
+ qDebug() << running;
+ });
+}
+
+void Window::showEvent(QShowEvent *event)
+{
+ Q_UNUSED(event);
+ KX11Extras::self()->setOnActivities(effectiveWinId(), QStringList());
+ KX11Extras::self()->setOnAllDesktops(effectiveWinId(), true);
+}
+
+Window::~Window()
+{
+ delete ui;
+}
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#pragma once
+
+#include <QMainWindow>
+
+#include <activitiesmodel.h>
+#include <consumer.h>
+
+namespace Ui
+{
+class MainWindow;
+}
+
+class Window : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ Window();
+ ~Window() override;
+
+protected:
+ void showEvent(QShowEvent *event) override;
+
+private:
+ Ui::MainWindow *ui;
+ KActivities::Consumer *activities;
+ KActivities::ActivitiesModel *modelRunningActivities;
+ KActivities::ActivitiesModel *modelStoppedActivities;
+};
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>406</width>
+ <height>869</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QVBoxLayout" name="verticalLayout_3" stretch="2,1,0">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Running</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListView" name="listRunningActivities"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Stopped</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListView" name="listStoppedActivities"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonClose">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Close</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonClose</sender>
+ <signal>clicked()</signal>
+ <receiver>MainWindow</receiver>
+ <slot>close()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>332</x>
+ <y>842</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>341</x>
+ <y>878</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2013 Heena Mahour <heena393@gmail.com>
+ SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+import QtQuick 2.0
+import org.kde.plasma.core 2.0 as PlasmaCore
+import org.kde.plasma.components 2.0 as PlasmaComponents
+import org.kde.plasma.extras 2.0 as PlasmaExtras
+import org.kde.activities 0.1 as Activities
+
+ListView {
+ id: main
+
+ property int minimumWidth: 32
+ property int minimumHeight: 32 // theme.mSize(theme.defaultFont).height * 14
+ property int implicitWidth: minimumWidth * 1.5
+ property int implicitHeight: minimumHeight * 1.5
+
+ property int formFactor: plasmoid.formFactor
+
+
+
+ model: modelMain
+
+ Activities.ActivityModel {
+ id: modelMain
+ }
+
+ add: Transition {
+ NumberAnimation { properties: "x"; from: 300; duration: 1000 }
+ }
+
+ addDisplaced: Transition {
+ NumberAnimation { properties: "x,y"; duration: 1000 }
+ }
+
+ remove: Transition {
+ NumberAnimation { properties: "x"; to: 300; duration: 1000 }
+ }
+
+ removeDisplaced: Transition {
+ NumberAnimation { properties: "x,y"; duration: 1000 }
+ }
+
+ ListModel {
+ id: modelDummy
+
+ ListElement {
+ name: "Bill Smith"
+ number: "555 3264"
+ }
+ ListElement {
+ name: "John Brown"
+ number: "555 8426"
+ }
+ ListElement {
+ name: "Sam Wise"
+ number: "555 0473"
+ }
+ }
+
+ delegate: Column {
+ height: 32
+ Text {
+ text: name
+ height: 16
+ font.bold: true
+ }
+ Text {
+ text: " id: " + id
+ height: 16
+ }
+
+ }
+}
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2013 Heena Mahour <heena393@gmail.com>
+ SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+import QtQuick 2.0
+import org.kde.plasma.core 2.0 as PlasmaCore
+import org.kde.plasma.components 2.0 as PlasmaComponents
+import org.kde.plasma.extras 2.0 as PlasmaExtras
+import org.kde.activities 0.1 as Activities
+
+ListView {
+ id: main
+
+ property int minimumWidth: 32
+ property int minimumHeight: 32 // theme.mSize(theme.defaultFont).height * 14
+ property int implicitWidth: minimumWidth * 1.5
+ property int implicitHeight: minimumHeight * 1.5
+
+ property int formFactor: plasmoid.formFactor
+
+
+
+ model: modelMain
+
+ Activities.ActivityModel {
+ id: modelMain
+ }
+
+ add: Transition {
+ NumberAnimation { properties: "x"; from: 300; duration: 1000 }
+ }
+
+ addDisplaced: Transition {
+ NumberAnimation { properties: "x,y"; duration: 1000 }
+ }
+
+ remove: Transition {
+ NumberAnimation { properties: "x"; to: 300; duration: 1000 }
+ }
+
+ removeDisplaced: Transition {
+ NumberAnimation { properties: "x,y"; duration: 1000 }
+ }
+
+ ListModel {
+ id: modelDummy
+
+ ListElement {
+ name: "Bill Smith"
+ number: "555 3264"
+ }
+ ListElement {
+ name: "John Brown"
+ number: "555 8426"
+ }
+ ListElement {
+ name: "Sam Wise"
+ number: "555 0473"
+ }
+ }
+
+ delegate: Column {
+ height: 32
+ Text {
+ text: name
+ height: 16
+ font.bold: true
+ }
+ Text {
+ text: " id: " + id
+ height: 16
+ }
+
+ }
+}
--- /dev/null
+[Desktop Entry]
+Encoding=UTF-8
+Name=Activities testing
+Name[ar]=اختبار الأنشطة
+Name[ast]=Prueba d'actividaes
+Name[ca]=Prova de les activitats
+Name[ca@valencia]=Prova de les activitats
+Name[cs]=Testování aktivit
+Name[da]=Test af aktiviteter
+Name[de]=Aktivitätentest
+Name[el]=Δοκιμές δραστηριοτήτων
+Name[en_GB]=Activities testing
+Name[es]=Prueba de actividades
+Name[fi]=Aktiviteettitesti
+Name[fr]=Test des activités
+Name[gd]=Deuchainn nan gnìomhachdan
+Name[gl]=Probas de actividades
+Name[he]=בדיקת פעילויות
+Name[hu]=Aktivitásteszt
+Name[ia]=Essayante Activitates
+Name[is]=Prófun á virknisviðum
+Name[it]=Prova delle attività
+Name[ko]=활동 테스트
+Name[lt]=Veiklų bandymas
+Name[mr]=कार्यपध्दती चाचणी
+Name[nb]=Aktiviteter
+Name[nds]=Aktiviteten-Tests
+Name[nl]=Testen van activiteiten
+Name[nn]=Aktivitetstesting
+Name[pa]=ਐਕਟਵਿਟੀ ਟੈਸਟਿੰਗ
+Name[pl]=Próba działań
+Name[pt]=Teste das actividades
+Name[pt_BR]=Teste de atividades
+Name[ro]=Testare activități
+Name[ru]=Тестирование комнат
+Name[sk]=Testovanie aktivít
+Name[sl]=Preizkušanje dejavnosti
+Name[sr]=Испробавање активности
+Name[sr@ijekavian]=Испробавање активности
+Name[sr@ijekavianlatin]=Isprobavanje aktivnosti
+Name[sr@latin]=Isprobavanje aktivnosti
+Name[sv]=Aktivitetstestning
+Name[tr]=Etkinlik sınaması
+Name[uk]=Тестування просторів дій
+Name[x-test]=xxActivities testingxx
+Name[zh_CN]=活动测试
+Name[zh_TW]=活動測試
+Icon=preferences-system-time
+Type=Service
+X-KDE-ParentApp=
+X-KDE-PluginInfo-Author=Ivan
+X-KDE-PluginInfo-Email=
+X-KDE-PluginInfo-License=GPL
+X-KDE-PluginInfo-Name=org.kde.listactivitiestest
+X-KDE-PluginInfo-Version=1.0
+X-KDE-PluginInfo-Website=plasma.kde.org
+X-KDE-ServiceTypes=Plasma/Applet
+X-Plasma-API=declarativeappletscript
+X-Plasma-DefaultSize=640,400
+X-Plasma-MainScript=ui/main.qml
+X-Plasma-RemoteLocation=
+X-KDE-PluginInfo-Category=
--- /dev/null
+[Desktop Entry]
+Name=List activities test
+Name[ar]=اختبار سرد الأنشطة
+Name[ca]=Prova de la llista d'activitats
+Name[ca@valencia]=Prova de la llista d'activitats
+Name[cs]=Test seznamu aktivit
+Name[da]=Oplist test af aktiviteter
+Name[de]=Aktivitätentest auflisten
+Name[el]=Λίστα δοκιμών δραστηριοτήτων
+Name[en_GB]=List activities test
+Name[es]=Prueba de listado de actividades
+Name[fi]=Aktiviteettien listaamistesti
+Name[fr]=Test de la liste d'activités
+Name[gd]=Seall deuchainn air na gnìomhachdan
+Name[gl]=Proba da lista de actividades
+Name[he]=בדיקת רשימת פעילויות
+Name[hu]=Aktivitáslista tesztelése
+Name[ia]=Lista essayos de activitates
+Name[is]=Gera lista með prófunum á virknisviðum
+Name[it]=Prova dell'elenco delle attività
+Name[ko]=활동 목록 테스트
+Name[lt]=Veiklų rikiavimo bandymas
+Name[mr]=कार्यपध्दती चाचणी यादी करा
+Name[nb]=List aktivitetsstester
+Name[nds]=Aktiviteten-Oplisttest
+Name[nl]=Lijst maken van activiteitentest
+Name[nn]=Vis oversikt over aktivitetstestar
+Name[pa]=ਐਕਟੀਵਿਟੀ ਟੈਸਟ ਸੂਚੀ
+Name[pl]=Próba wyszczególniania działań
+Name[pt]=Teste da listagem de actividades
+Name[pt_BR]=Teste da lista de atividades
+Name[ru]=Тестирование списка комнат
+Name[sk]=Test zoznamu aktivít
+Name[sl]=Seznam preizkusov dejavnosti
+Name[sr]=Проба набрајања активности
+Name[sr@ijekavian]=Проба набрајања активности
+Name[sr@ijekavianlatin]=Proba nabrajanja aktivnosti
+Name[sr@latin]=Proba nabrajanja aktivnosti
+Name[sv]=Lista aktivitetstester
+Name[tr]=Etkinilk sınamasını listele
+Name[uk]=Тестування списку просторів дій
+Name[x-test]=xxList activities testxx
+Name[zh_CN]=显示活动测试项
+Name[zh_TW]=列出活動測試
+Comment=Strange, but not a Clock
+Comment[ast]=Estrañu, pero nun ye un reló
+Comment[ca]=És estrany, però no és cap rellotge
+Comment[ca@valencia]=És estrany, però no és cap rellotge
+Comment[da]=Mærkelig, men ikke et ur
+Comment[de]=Seltsam, aber keine Uhr
+Comment[el]=Παράξενο, αλλά δεν είναι ρολόι
+Comment[en_GB]=Strange, but not a Clock
+Comment[es]=Es extraño, pero no es un reloj
+Comment[fi]=Outo, mutta ei kello
+Comment[fr]=Étranges, mais ceci n'est pas une horloge
+Comment[gd]=Neònach ach chan e uaireadair a th' ann
+Comment[gl]=Estraño, pero non é un reloxo
+Comment[he]=מוזר, אבל לא שעון
+Comment[hu]=Furcsa, de nem egy óra
+Comment[ia]=Stranie, ma non un horologio
+Comment[is]=Skrýtið, en ekki klukka
+Comment[it]=Stranamente, non un orologio
+Comment[ko]=수상하지만 시계는 아님
+Comment[lt]=Keistas, bet ne laikrodis
+Comment[nb]=Underlig, men ingen klokke
+Comment[nds]=Snaaksch, man keen Klock
+Comment[nl]=Vreemd, maar geen klok
+Comment[nn]=Merkeleg, men ikkje ei klokke
+Comment[pa]=ਅਜੀਬ, ਪਰ ਇਹ ਘੜੀ ਨਹੀਂ
+Comment[pl]=Dziwne, ale nie Zegar
+Comment[pt]=Estranho, Mas Não é um Relógio
+Comment[pt_BR]=Estranho, mas não é um relógio
+Comment[ro]=Straniu, dar nu e ceas
+Comment[ru]=Странное, но не часы
+Comment[sk]=Zvláštne, ale nie hodiny
+Comment[sl]=Nenavadno, ni pa ura
+Comment[sr]=Чудно, али није сат
+Comment[sr@ijekavian]=Чудно, али није сат
+Comment[sr@ijekavianlatin]=Čudno, ali nije sat
+Comment[sr@latin]=Čudno, ali nije sat
+Comment[sv]=Underlig, men inte en klocka
+Comment[tr]=Garip, ancak Saat değil
+Comment[uk]=Дивно, але не годинник
+Comment[x-test]=xxStrange, but not a Clockxx
+Comment[zh_CN]=奇怪,但不是一个时钟
+Comment[zh_TW]=Strange, but not a Clock(這是什麼奇怪的 comment?)
+
+Icon=preferences-system-time
+Type=Service
+X-KDE-ServiceTypes=Plasma/Applet
+
+X-Plasma-API=declarativeappletscript
+X-Plasma-MainScript=ui/main.qml
+X-Plasma-DefaultSize=250,250
+
+X-KDE-PluginInfo-Author=Ivan
+X-KDE-PluginInfo-Email=
+X-KDE-PluginInfo-Name=org.kde.listactivitiestest
+X-KDE-PluginInfo-Version=1.0
+X-KDE-PluginInfo-Website=https://userbase.kde.org/Plasma/Clocks
+X-KDE-PluginInfo-Category=Date and Time
+X-KDE-PluginInfo-License=GPL
+X-KDE-PluginInfo-EnabledByDefault=true
+
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2013 Heena Mahour <heena393@gmail.com>
+ SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+import QtQuick 2.0
+import org.kde.plasma.core 2.0 as PlasmaCore
+import org.kde.plasma.components 2.0 as PlasmaComponents
+import org.kde.plasma.extras 2.0 as PlasmaExtras
+import org.kde.activities 0.1 as Activities
+
+Item {
+ id: main
+
+ width: 320
+ height: 320
+
+ Row {
+ id: buttons
+
+ height: 32
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: parent.top
+ }
+
+ PlasmaComponents.Button {
+ id: buttonAdd
+ text: "Add"
+
+ onClicked: {
+ modelMain.linkResourceToActivity("/tmp", function () {});
+ }
+
+ width: 64
+ }
+
+ PlasmaComponents.Button {
+ id: buttonRemove
+ text: "Del"
+
+ onClicked: {
+ modelMain.unlinkResourceFromActivity("/tmp", function () {});
+ }
+
+ width: 64
+ }
+
+ PlasmaComponents.Button {
+ id: buttonSortAZ
+ text: "A-Z"
+
+ onClicked: {
+
+ var items = [];
+
+ for (var i = 0; i < modelMain.count(); i++) {
+ items.push([
+ modelMain.displayAt(i),
+ modelMain.resourceAt(i)
+ ]);
+ }
+
+ items = items.sort(function(left, right) {
+ return (left[0] < right[0]) ? -1 :
+ (left[0] > right[0]) ? 1 :
+ 0
+ });
+
+ items = items.map(function(item) { return item[1]; });
+
+ modelMain.setOrder(items);
+ }
+
+ width: 64
+ }
+
+ PlasmaComponents.Button {
+ id: buttonSortZA
+ text: "Z-A"
+
+ onClicked: {
+
+ var items = [];
+
+ for (var i = 0; i < modelMain.count(); i++) {
+ items.push([
+ modelMain.displayAt(i),
+ modelMain.resourceAt(i)
+ ]);
+ }
+
+ items = items.sort(function(left, right) {
+ return (left[0] < right[0]) ? 1 :
+ (left[0] > right[0]) ? -1 :
+ 0
+ });
+
+ items = items.map(function(item) { return item[1]; });
+
+ modelMain.setOrder(items);
+ }
+
+ width: 64
+ }
+ }
+
+ ListView {
+ id: list
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+
+ top: buttons.bottom
+
+ }
+
+ property int minimumWidth: 320
+ property int minimumHeight: 320 // theme.mSize(theme.defaultFont).height * 14
+ property int implicitWidth: minimumWidth * 1.5
+ property int implicitHeight: minimumHeight * 1.5
+
+ model: modelMain
+
+ Activities.ResourceModel {
+ id: modelMain
+ shownAgents: "org.kde.plasma.kickoff"
+ shownActivities: ":global,:current"
+ }
+
+ add: Transition {
+ NumberAnimation { properties: "x"; from: 300; duration: 1000 }
+ }
+
+ addDisplaced: Transition {
+ NumberAnimation { properties: "x,y"; duration: 1000 }
+ }
+
+ remove: Transition {
+ NumberAnimation { properties: "x"; to: 300; duration: 1000 }
+ }
+
+ removeDisplaced: Transition {
+ NumberAnimation { properties: "x,y"; duration: 1000 }
+ }
+
+ ListModel {
+ id: modelDummy
+
+ ListElement {
+ name: "Bill Smith"
+ number: "555 3264"
+ }
+ ListElement {
+ name: "John Brown"
+ number: "555 8426"
+ }
+ ListElement {
+ name: "Sam Wise"
+ number: "555 0473"
+ }
+ }
+
+ delegate: Column {
+ height: 48
+ Text {
+ text: display
+ height: 16
+ font.bold: true
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ if (display != "tmp") {
+ modelMain.linkResourceToActivity("/tmp", function () {});
+ } else {
+ modelMain.unlinkResourceFromActivity("/tmp", function () {});
+ }
+ }
+ }
+
+ }
+ Text {
+ text: " icon: " + decoration
+ height: 16
+ }
+ Text {
+ text: " application: " + agent
+ height: 16
+ }
+
+ }
+ }
+}
--- /dev/null
+# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab:
+project (KActivitiesSLCTestApp)
+
+find_package (Qt${QT_MAJOR_VERSION} REQUIRED NO_MODULE COMPONENTS Core Gui Widgets)
+
+include_directories (
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/autotests/
+ )
+
+set (
+ KActivitiesSLCTestApp_SRCS
+ window.cpp
+ main.cpp
+ )
+
+qt_add_dbus_interface (
+ KActivitiesSLCTestApp_SRCS
+
+ ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/service/plugins/slc/org.kde.ActivityManager.SLC.xml
+ slc_interface
+ )
+
+qt_wrap_ui(
+ KActivitiesSLCTestApp_SRCS
+ window.ui
+ )
+
+if (NOT WIN32)
+
+ add_executable (
+ KActivitiesSLCTestApp
+ ${KActivitiesSLCTestApp_SRCS}
+ )
+
+ target_link_libraries (
+ KActivitiesSLCTestApp
+ Qt${QT_MAJOR_VERSION}::Core
+ Qt${QT_MAJOR_VERSION}::Gui
+ Qt${QT_MAJOR_VERSION}::Widgets
+ Qt${QT_MAJOR_VERSION}::DBus
+ KF5::Activities
+ )
+
+endif ()
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "window.h"
+#include <QApplication>
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+
+ Window w;
+ w.show();
+
+ // ResultSet results(UsedResources | Agent{"gvim"});
+ //
+ // int count = 20;
+ // for (const auto& result: results) {
+ // qDebug() << "Result:" << result.title << result.resource;
+ // if (count -- == 0) break;
+ // }
+ //
+ // ResultModel model(UsedResources | Agent{"gvim"});
+ // model.setItemCountLimit(50);
+ //
+ // QListView view;
+ // view.setModel(&model);
+ //
+ // view.show();
+
+ return app.exec();
+}
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2015 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "window.h"
+
+#include "ui_window.h"
+
+#include <QDBusConnection>
+
+Window::Window()
+ : ui(new Ui::MainWindow())
+ , slc(new org::kde::ActivityManager::SLC("org.kde.ActivityManager", "/SLC", QDBusConnection::sessionBus(), this))
+{
+ ui->setupUi(this);
+
+ connect(slc, &org::kde::ActivityManager::SLC::focusChanged, this, &Window::focusChanged);
+}
+
+Window::~Window()
+{
+ delete ui;
+}
+
+void Window::focusChanged(const QString &uri, const QString &mimetype, const QString &title)
+{
+ Q_UNUSED(mimetype);
+ Q_UNUSED(title);
+ ui->textCurrentResource->setText(uri);
+}
--- /dev/null
+/*
+ SPDX-FileCopyrightText: 2013-2016 Ivan Cukic <ivan.cukic(at)kde.org>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#pragma once
+
+#include "slc_interface.h"
+#include <QMainWindow>
+
+namespace Ui
+{
+class MainWindow;
+}
+
+class Window : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ Window();
+ ~Window();
+
+private Q_SLOTS:
+ void focusChanged(const QString &uri, const QString &mimetype, const QString &title);
+
+private:
+ Ui::MainWindow *ui;
+ org::kde::ActivityManager::SLC *slc;
+};
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>474</width>
+ <height>74</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="icon">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>64</width>
+ <height>64</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelCurrentResource">
+ <property name="text">
+ <string>Resource</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="textCurrentResource">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelCurrentResourceTitle">
+ <property name="text">
+ <string>Title</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="textCurrentResourceTitle">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="labelCurrentResourceMimetype">
+ <property name="text">
+ <string>Mimetype</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="textCurrentResourceMimetype">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null
+
+set makeprg=OBJ_REPLACEMENT='s=src=build-clang='\ makeobj
+
+imap <f8> <esc>:SlimuxShellRun make && ./autotests/stats/KActivitiesStatsTest ResultWatcher<cr>
+map <f8> <esc>:SlimuxShellRun make && ./autotests/stats/KActivitiesStatsTest ResultWatcher<cr>
+
+let g:ctrlpswitcher_project_sources = expand('<sfile>:p:h')."/src"
+let g:ctrlpswitcher_mode = 1
+
+set foldmethod=marker
+set foldmarker=//_,//^
+