From 8a6ff6673acb452c5cb4f85bd0e170c6ce80e47a Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Fri, 18 Dec 2020 01:03:51 +0000 Subject: [PATCH] Import kactivities-kf5_5.77.0.orig.tar.xz [dgit import orig kactivities-kf5_5.77.0.orig.tar.xz] --- .clang-format | 49 ++ .gitignore | 19 + .videproject/project.conf | 30 + .videproject/vimrc | 2 + CMakeLists.txt | 124 ++++ KF5ActivitiesConfig.cmake.in | 7 + LICENSES/GPL-2.0-or-later.txt | 319 ++++++++ LICENSES/LGPL-2.0-or-later.txt | 446 ++++++++++++ LICENSES/LGPL-2.1-only.txt | 467 ++++++++++++ LICENSES/LGPL-3.0-only.txt | 163 +++++ LICENSES/LicenseRef-KDE-Accepted-LGPL.txt | 12 + LICENSES/MIT.txt | 19 + MAINTAINER | 3 + README | 6 + README.developers | 87 +++ README.md | 33 + README.packagers | 23 + TODO | 28 + autotests/CMakeLists.txt | 7 + autotests/common/test.cpp | 43 ++ autotests/common/test.h | 145 ++++ autotests/core/CMakeLists.txt | 37 + autotests/core/CleanOnlineTest.cpp | 113 +++ autotests/core/CleanOnlineTest.h | 69 ++ autotests/core/OfflineTest.cpp | 76 ++ autotests/core/OfflineTest.h | 37 + autotests/core/Process.cpp | 143 ++++ autotests/core/Process.h | 48 ++ autotests/core/main.cpp | 109 +++ contrib/bash/next-activity.sh | 30 + contrib/bash/prev-activity.sh | 30 + contrib/commit.sh | 35 + contrib/run-krazy.sh | 25 + contrib/update-todo.hs | 115 +++ contrib/zsh/kamd-functions | 141 ++++ docs/Doxyfile.local | 1 + logo.png | Bin 0 -> 10353 bytes metainfo.yaml | 21 + src/CMakeLists.txt | 46 ++ src/cli/CMakeLists.txt | 40 + src/cli/main.cpp | 253 +++++++ src/cli/utils.h | 157 ++++ src/common/dbus/common.h | 45 ++ .../org.kde.ActivityManager.Activities.cpp | 63 ++ .../dbus/org.kde.ActivityManager.Activities.h | 46 ++ .../org.kde.ActivityManager.Activities.xml | 115 +++ .../org.kde.ActivityManager.Application.xml | 14 + .../dbus/org.kde.ActivityManager.Features.xml | 21 + .../org.kde.ActivityManager.Resources.xml | 23 + ...g.kde.ActivityManager.ResourcesLinking.xml | 49 ++ ...g.kde.ActivityManager.ResourcesScoring.xml | 45 ++ src/imports/CMakeLists.txt | 41 ++ src/imports/README | 7 + src/imports/activitiesextensionplugin.cpp | 42 ++ src/imports/activitiesextensionplugin.h | 22 + src/imports/activityinfo.cpp | 96 +++ src/imports/activityinfo.h | 96 +++ src/imports/activitymodel.cpp | 641 ++++++++++++++++ src/imports/activitymodel.h | 151 ++++ src/imports/qmldir | 3 + src/imports/resourceinstance.cpp | 115 +++ src/imports/resourceinstance.h | 88 +++ src/imports/resourcemodel.cpp | 682 ++++++++++++++++++ src/imports/resourcemodel.h | 197 +++++ src/kactivities-features.h.cmake | 17 + src/lib/CMakeLists.txt | 191 +++++ src/lib/activitiescache_p.cpp | 302 ++++++++ src/lib/activitiescache_p.h | 128 ++++ src/lib/activitiesmodel.cpp | 430 +++++++++++ src/lib/activitiesmodel.h | 91 +++ src/lib/activitiesmodel_p.h | 76 ++ src/lib/consumer.cpp | 106 +++ src/lib/consumer.h | 165 +++++ src/lib/consumer_p.h | 37 + src/lib/controller.cpp | 127 ++++ src/lib/controller.h | 115 +++ src/lib/info.cpp | 206 ++++++ src/lib/info.h | 235 ++++++ src/lib/info_p.h | 43 ++ src/lib/libKActivities.pc.cmake | 12 + src/lib/mainthreadexecutor_p.cpp | 48 ++ src/lib/mainthreadexecutor_p.h | 34 + src/lib/manager_p.cpp | 165 +++++ src/lib/manager_p.h | 63 ++ src/lib/resourceinstance.cpp | 177 +++++ src/lib/resourceinstance.h | 188 +++++ src/lib/version.cpp | 36 + src/lib/version.h | 49 ++ src/utils/continue_with.h | 96 +++ src/utils/dbusfuture_p.cpp | 50 ++ src/utils/dbusfuture_p.h | 163 +++++ src/utils/model_updaters.h | 63 ++ src/utils/optional_view.h | 65 ++ src/utils/ptr_to.h | 33 + src/utils/qflatset.h | 60 ++ src/utils/range.h | 68 ++ src/utils/remove_if.h | 31 + tests/CMakeLists.txt | 6 + tests/activities-model/CMakeLists.txt | 41 ++ tests/activities-model/main.cpp | 19 + tests/activities-model/window.cpp | 114 +++ tests/activities-model/window.h | 34 + tests/activities-model/window.ui | 81 +++ tests/imports/activities.qml | 77 ++ .../contents/ui/main.qml | 77 ++ .../metadata.desktop | 62 ++ ...-applet-org.kde.listactivitiestest.desktop | 104 +++ tests/imports/resources.qml | 198 +++++ tests/slc-interface/CMakeLists.txt | 45 ++ tests/slc-interface/main.cpp | 35 + tests/slc-interface/window.cpp | 40 + tests/slc-interface/window.h | 30 + tests/slc-interface/window.ui | 88 +++ vim-extrarc | 12 + 114 files changed, 10893 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 .videproject/project.conf create mode 100644 .videproject/vimrc create mode 100644 CMakeLists.txt create mode 100644 KF5ActivitiesConfig.cmake.in create mode 100644 LICENSES/GPL-2.0-or-later.txt create mode 100644 LICENSES/LGPL-2.0-or-later.txt create mode 100644 LICENSES/LGPL-2.1-only.txt create mode 100644 LICENSES/LGPL-3.0-only.txt create mode 100644 LICENSES/LicenseRef-KDE-Accepted-LGPL.txt create mode 100644 LICENSES/MIT.txt create mode 100644 MAINTAINER create mode 100644 README create mode 100644 README.developers create mode 100644 README.md create mode 100644 README.packagers create mode 100644 TODO create mode 100644 autotests/CMakeLists.txt create mode 100644 autotests/common/test.cpp create mode 100644 autotests/common/test.h create mode 100644 autotests/core/CMakeLists.txt create mode 100644 autotests/core/CleanOnlineTest.cpp create mode 100644 autotests/core/CleanOnlineTest.h create mode 100644 autotests/core/OfflineTest.cpp create mode 100644 autotests/core/OfflineTest.h create mode 100644 autotests/core/Process.cpp create mode 100644 autotests/core/Process.h create mode 100644 autotests/core/main.cpp create mode 100755 contrib/bash/next-activity.sh create mode 100755 contrib/bash/prev-activity.sh create mode 100755 contrib/commit.sh create mode 100755 contrib/run-krazy.sh create mode 100755 contrib/update-todo.hs create mode 100644 contrib/zsh/kamd-functions create mode 100644 docs/Doxyfile.local create mode 100644 logo.png create mode 100644 metainfo.yaml create mode 100644 src/CMakeLists.txt create mode 100644 src/cli/CMakeLists.txt create mode 100644 src/cli/main.cpp create mode 100644 src/cli/utils.h create mode 100644 src/common/dbus/common.h create mode 100644 src/common/dbus/org.kde.ActivityManager.Activities.cpp create mode 100644 src/common/dbus/org.kde.ActivityManager.Activities.h create mode 100644 src/common/dbus/org.kde.ActivityManager.Activities.xml create mode 100644 src/common/dbus/org.kde.ActivityManager.Application.xml create mode 100644 src/common/dbus/org.kde.ActivityManager.Features.xml create mode 100644 src/common/dbus/org.kde.ActivityManager.Resources.xml create mode 100644 src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml create mode 100644 src/common/dbus/org.kde.ActivityManager.ResourcesScoring.xml create mode 100644 src/imports/CMakeLists.txt create mode 100644 src/imports/README create mode 100644 src/imports/activitiesextensionplugin.cpp create mode 100644 src/imports/activitiesextensionplugin.h create mode 100644 src/imports/activityinfo.cpp create mode 100644 src/imports/activityinfo.h create mode 100644 src/imports/activitymodel.cpp create mode 100644 src/imports/activitymodel.h create mode 100644 src/imports/qmldir create mode 100644 src/imports/resourceinstance.cpp create mode 100644 src/imports/resourceinstance.h create mode 100644 src/imports/resourcemodel.cpp create mode 100644 src/imports/resourcemodel.h create mode 100644 src/kactivities-features.h.cmake create mode 100644 src/lib/CMakeLists.txt create mode 100644 src/lib/activitiescache_p.cpp create mode 100644 src/lib/activitiescache_p.h create mode 100644 src/lib/activitiesmodel.cpp create mode 100644 src/lib/activitiesmodel.h create mode 100644 src/lib/activitiesmodel_p.h create mode 100644 src/lib/consumer.cpp create mode 100644 src/lib/consumer.h create mode 100644 src/lib/consumer_p.h create mode 100644 src/lib/controller.cpp create mode 100644 src/lib/controller.h create mode 100644 src/lib/info.cpp create mode 100644 src/lib/info.h create mode 100644 src/lib/info_p.h create mode 100644 src/lib/libKActivities.pc.cmake create mode 100644 src/lib/mainthreadexecutor_p.cpp create mode 100644 src/lib/mainthreadexecutor_p.h create mode 100644 src/lib/manager_p.cpp create mode 100644 src/lib/manager_p.h create mode 100644 src/lib/resourceinstance.cpp create mode 100644 src/lib/resourceinstance.h create mode 100644 src/lib/version.cpp create mode 100644 src/lib/version.h create mode 100644 src/utils/continue_with.h create mode 100644 src/utils/dbusfuture_p.cpp create mode 100644 src/utils/dbusfuture_p.h create mode 100644 src/utils/model_updaters.h create mode 100644 src/utils/optional_view.h create mode 100644 src/utils/ptr_to.h create mode 100644 src/utils/qflatset.h create mode 100644 src/utils/range.h create mode 100644 src/utils/remove_if.h create mode 100644 tests/CMakeLists.txt create mode 100644 tests/activities-model/CMakeLists.txt create mode 100644 tests/activities-model/main.cpp create mode 100644 tests/activities-model/window.cpp create mode 100644 tests/activities-model/window.h create mode 100644 tests/activities-model/window.ui create mode 100644 tests/imports/activities.qml create mode 100644 tests/imports/org.kde.listactivitiestest/contents/ui/main.qml create mode 100644 tests/imports/org.kde.listactivitiestest/metadata.desktop create mode 100644 tests/imports/plasma-applet-org.kde.listactivitiestest.desktop create mode 100644 tests/imports/resources.qml create mode 100644 tests/slc-interface/CMakeLists.txt create mode 100644 tests/slc-interface/main.cpp create mode 100644 tests/slc-interface/window.cpp create mode 100644 tests/slc-interface/window.h create mode 100644 tests/slc-interface/window.ui create mode 100644 vim-extrarc diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..54db9fa --- /dev/null +++ b/.clang-format @@ -0,0 +1,49 @@ +--- +# BasedOnStyle: WebKit +Language: Cpp +AccessModifierOffset: -4 +ConstructorInitializerIndentWidth: 4 +AlignEscapedNewlinesLeft: false +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +AlwaysBreakTemplateDeclarations: false +AlwaysBreakBeforeMultilineStrings: false +BreakBeforeBinaryOperators: true +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: true +BinPackParameters: true +ColumnLimit: 80 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +DerivePointerBinding: false +ExperimentalAutoDetectBinPacking: false +IndentCaseLabels: true +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 60 +PenaltyBreakString: 1000 +PenaltyBreakFirstLessLess: 120 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerBindsToType: false +SpacesBeforeTrailingComments: 1 +Cpp11BracedListStyle: false +Standard: Cpp11 +IndentWidth: 4 +TabWidth: 8 +UseTab: Never +BreakBeforeBraces: Stroustrup +IndentFunctionDeclarationAfterType: false +SpacesInParentheses: false +SpacesInAngles: false +SpaceInEmptyParentheses: false +SpacesInCStyleCastParentheses: false +SpaceAfterControlStatementKeyword: true +SpaceBeforeAssignmentOperators: true +ContinuationIndentWidth: 4 +... + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac1e9f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +.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/ diff --git a/.videproject/project.conf b/.videproject/project.conf new file mode 100644 index 0000000..fb52c7b --- /dev/null +++ b/.videproject/project.conf @@ -0,0 +1,30 @@ +[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='' +MakeProject='' +RunProject='' + +[Grep] +Root=. + +[Find] +Root=. + +[QuickBrowser] +iNotifyEnabled=0 +Root=. +TagsSystem=ctags diff --git a/.videproject/vimrc b/.videproject/vimrc new file mode 100644 index 0000000..a8dbe5c --- /dev/null +++ b/.videproject/vimrc @@ -0,0 +1,2 @@ + +set tags+=/opt/kde4trunk/ctags/libQt.tags diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..47d26f9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,124 @@ +# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: + +cmake_minimum_required(VERSION 3.5) + +# KDE Frameworks +set(KF5_VERSION "5.77.0") # handled by release scripts +set(KF5_DEP_VERSION "5.77.0") # handled by release scripts +project (KActivities VERSION ${KF5_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.13.0) + +# 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.77.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 (KDEFrameworkCompilerSettings NO_POLICY_SCOPE) +include (GenerateExportHeader) +include (ECMGenerateHeaders) +include (ECMQtDeclareLoggingCategory) +include (ECMAddQch) +include (ECMMarkNonGuiExecutable) + +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 (Qt5 ${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 + ) + +add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050d00) +add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054B00) +add_definitions(-DQT_NO_FOREACH) + +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} + PATH_VARS KF5_INCLUDE_INSTALL_DIR CMAKE_INSTALL_PREFIX + ) + +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_KF5} COMPONENT Devel + ) + +# Write out the features +feature_summary (WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) + diff --git a/KF5ActivitiesConfig.cmake.in b/KF5ActivitiesConfig.cmake.in new file mode 100644 index 0000000..25291c9 --- /dev/null +++ b/KF5ActivitiesConfig.cmake.in @@ -0,0 +1,7 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(Qt5Core @REQUIRED_QT_VERSION@) + +include("${CMAKE_CURRENT_LIST_DIR}/KF5ActivitiesLibraryTargets.cmake") +@PACKAGE_INCLUDE_QCHTARGETS@ diff --git a/LICENSES/GPL-2.0-or-later.txt b/LICENSES/GPL-2.0-or-later.txt new file mode 100644 index 0000000..1d80ac3 --- /dev/null +++ b/LICENSES/GPL-2.0-or-later.txt @@ -0,0 +1,319 @@ +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. + + + +Copyright (C) + +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. + +, 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. diff --git a/LICENSES/LGPL-2.0-or-later.txt b/LICENSES/LGPL-2.0-or-later.txt new file mode 100644 index 0000000..5c96471 --- /dev/null +++ b/LICENSES/LGPL-2.0-or-later.txt @@ -0,0 +1,446 @@ +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! diff --git a/LICENSES/LGPL-2.1-only.txt b/LICENSES/LGPL-2.1-only.txt new file mode 100644 index 0000000..130dffb --- /dev/null +++ b/LICENSES/LGPL-2.1-only.txt @@ -0,0 +1,467 @@ +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! diff --git a/LICENSES/LGPL-3.0-only.txt b/LICENSES/LGPL-3.0-only.txt new file mode 100644 index 0000000..bd405af --- /dev/null +++ b/LICENSES/LGPL-3.0-only.txt @@ -0,0 +1,163 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + +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. diff --git a/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt b/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt new file mode 100644 index 0000000..232b3c5 --- /dev/null +++ b/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt @@ -0,0 +1,12 @@ +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. diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 0000000..204b93d --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,19 @@ +MIT License Copyright (c) + +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. diff --git a/MAINTAINER b/MAINTAINER new file mode 100644 index 0000000..51d257d --- /dev/null +++ b/MAINTAINER @@ -0,0 +1,3 @@ + +Current kactivities maintainer is: Ivan Čukić + diff --git a/README b/README new file mode 100644 index 0000000..43a57c6 --- /dev/null +++ b/README @@ -0,0 +1,6 @@ +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 diff --git a/README.developers b/README.developers new file mode 100644 index 0000000..575d704 --- /dev/null +++ b/README.developers @@ -0,0 +1,87 @@ + + +.\" " " " " " " " " " " " " " " " " " " " " " " " " " " " +.\" " +.\" 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. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..c5901ce --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# 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 activites 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 activites, +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. diff --git a/README.packagers b/README.packagers new file mode 100644 index 0000000..6b7155b --- /dev/null +++ b/README.packagers @@ -0,0 +1,23 @@ + + +.\" " " " " " " " " " " " " " " " " " " " " " " " " " " " +.\" " +.\" 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 + diff --git a/TODO b/TODO new file mode 100644 index 0000000..10e0973 --- /dev/null +++ b/TODO @@ -0,0 +1,28 @@ +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) + diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt new file mode 100644 index 0000000..c2d5775 --- /dev/null +++ b/autotests/CMakeLists.txt @@ -0,0 +1,7 @@ +# 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) + diff --git a/autotests/common/test.cpp b/autotests/common/test.cpp new file mode 100644 index 0000000..26f3b8c --- /dev/null +++ b/autotests/common/test.cpp @@ -0,0 +1,43 @@ +/* + SPDX-FileCopyrightText: 2013, 2014, 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "test.h" + +#include +#include + +#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); +} + diff --git a/autotests/common/test.h b/autotests/common/test.h new file mode 100644 index 0000000..aca1099 --- /dev/null +++ b/autotests/common/test.h @@ -0,0 +1,145 @@ +/* + SPDX-FileCopyrightText: 2013, 2014, 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef COMMON_TEST_H +#define COMMON_TEST_H + +#include +#include +#include +#include +#include + +class Test: public QObject { + Q_OBJECT +public: + Test(QObject *parent = nullptr); + +protected: + enum WhenToFail { + DontFail = 0, + FailIfTrue = 1, + FailIfFalse = 2 + }; + + template + void continue_future(const QFuture<_ReturnType> &future, + _Continuation &&continuation) + { + if (!future.isFinished()) { + auto watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcherBase::finished, + watcher, + [=] { + continuation(watcher->result()); + watcher->deleteLater(); + }, + Qt::QueuedConnection + ); + + watcher->setFuture(future); + + } else { + continuation(future.result()); + + } + } + + template + void continue_future(const QFuture &future, + _Continuation &&continuation) + { + if (!future.isFinished()) { + auto watcher = new QFutureWatcher(); + QObject::connect(watcher, &QFutureWatcherBase::finished, + watcher, + [=] { + continuation(); + watcher->deleteLater(); + }, + Qt::QueuedConnection + ); + + watcher->setFuture(future); + + } else { + continuation(); + + } + } + + template + 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; + } + } + +#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); + + + template + 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 + +#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 */ + diff --git a/autotests/core/CMakeLists.txt b/autotests/core/CMakeLists.txt new file mode 100644 index 0000000..41b885a --- /dev/null +++ b/autotests/core/CMakeLists.txt @@ -0,0 +1,37 @@ +# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: +project (KActivitiesTest) + +find_package (Qt5 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 + Qt5::Core + Qt5::Test + Qt5::DBus + KF5::Activities + ) + +endif () diff --git a/autotests/core/CleanOnlineTest.cpp b/autotests/core/CleanOnlineTest.cpp new file mode 100644 index 0000000..52e0da6 --- /dev/null +++ b/autotests/core/CleanOnlineTest.cpp @@ -0,0 +1,113 @@ +/* + SPDX-FileCopyrightText: 2013 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "CleanOnlineTest.h" + +#include +#include +#include +#include +#include + +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() +{ + emit testFinished(); +} + +void CleanOnlineSetup::cleanupTestCase() +{ + emit testFinished(); +} + +void OnlineTest::cleanupTestCase() +{ + emit testFinished(); +} diff --git a/autotests/core/CleanOnlineTest.h b/autotests/core/CleanOnlineTest.h new file mode 100644 index 0000000..3b9787d --- /dev/null +++ b/autotests/core/CleanOnlineTest.h @@ -0,0 +1,69 @@ +/* + SPDX-FileCopyrightText: 2013 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef CLEANONLINETEST_H +#define CLEANONLINETEST_H + +#include + +#include + +#include + +class CleanOnlineTest : public Test { + Q_OBJECT +public: + CleanOnlineTest(QObject *parent = nullptr); + +private Q_SLOTS: + void testCleanOnlineActivityListing(); + + void cleanupTestCase(); + +private: + QScopedPointer 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 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 activities; + +}; + + +#endif /* CLEANONLINETEST_H */ + diff --git a/autotests/core/OfflineTest.cpp b/autotests/core/OfflineTest.cpp new file mode 100644 index 0000000..00f0757 --- /dev/null +++ b/autotests/core/OfflineTest.cpp @@ -0,0 +1,76 @@ +/* + SPDX-FileCopyrightText: 2013 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "OfflineTest.h" + +#include +#include +#include +#include + +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() +{ + emit testFinished(); +} diff --git a/autotests/core/OfflineTest.h b/autotests/core/OfflineTest.h new file mode 100644 index 0000000..4240134 --- /dev/null +++ b/autotests/core/OfflineTest.h @@ -0,0 +1,37 @@ +/* + SPDX-FileCopyrightText: 2013 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef OFFLINETEST_H +#define OFFLINETEST_H + +#include + +#include + +#include + +class OfflineTest : public Test { + Q_OBJECT +public: + OfflineTest(QObject *parent = nullptr); + +private Q_SLOTS: + void initTestCase(); + + void testOfflineActivityListing(); + void testOfflineActivityControl(); + + void cleanupTestCase(); + +private: + + QScopedPointer activities; + +}; + + +#endif /* OFFLINETEST_H */ + diff --git a/autotests/core/Process.cpp b/autotests/core/Process.cpp new file mode 100644 index 0000000..d132aa2 --- /dev/null +++ b/autotests/core/Process.cpp @@ -0,0 +1,143 @@ +/* + SPDX-FileCopyrightText: 2013 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "Process.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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); + s_process->start(QStringLiteral("kactivitymanagerd"), 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); + + ::kill(pid, + m_action == Stop ? SIGQUIT : + m_action == Kill ? SIGKILL : + /* else */ SIGSEGV + ); + + 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() +{ + emit testFinished(); +} + +Modifier *exec(Action action) { + return new Modifier(action); +} + +} // namespace Process diff --git a/autotests/core/Process.h b/autotests/core/Process.h new file mode 100644 index 0000000..3f6e8d3 --- /dev/null +++ b/autotests/core/Process.h @@ -0,0 +1,48 @@ +/* + SPDX-FileCopyrightText: 2013 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef PROCESS_H +#define PROCESS_H + +#include + +#include + +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 */ + diff --git a/autotests/core/main.cpp b/autotests/core/main.cpp new file mode 100644 index 0000000..1bd697f --- /dev/null +++ b/autotests/core/main.cpp @@ -0,0 +1,109 @@ +/* + SPDX-FileCopyrightText: 2013 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include +#include + +#include + +#include "Process.h" +#include "OfflineTest.h" +#include "CleanOnlineTest.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 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); +} + diff --git a/contrib/bash/next-activity.sh b/contrib/bash/next-activity.sh new file mode 100755 index 0000000..5b0964d --- /dev/null +++ b/contrib/bash/next-activity.sh @@ -0,0 +1,30 @@ +#! /bin/bash +# +# next-activity.sh +# SPDX-FileCopyrightText: 2016 Ivan Čukić +# +# 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 + + diff --git a/contrib/bash/prev-activity.sh b/contrib/bash/prev-activity.sh new file mode 100755 index 0000000..05415df --- /dev/null +++ b/contrib/bash/prev-activity.sh @@ -0,0 +1,30 @@ +#! /bin/bash +# +# next-activity.sh +# SPDX-FileCopyrightText: 2016 Ivan Čukić +# +# 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 + diff --git a/contrib/commit.sh b/contrib/commit.sh new file mode 100755 index 0000000..3377ff3 --- /dev/null +++ b/contrib/commit.sh @@ -0,0 +1,35 @@ +#!/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 + diff --git a/contrib/run-krazy.sh b/contrib/run-krazy.sh new file mode 100755 index 0000000..1c5635d --- /dev/null +++ b/contrib/run-krazy.sh @@ -0,0 +1,25 @@ +#!/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 diff --git a/contrib/update-todo.hs b/contrib/update-todo.hs new file mode 100755 index 0000000..e012cd4 --- /dev/null +++ b/contrib/update-todo.hs @@ -0,0 +1,115 @@ +#! /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 () + + diff --git a/contrib/zsh/kamd-functions b/contrib/zsh/kamd-functions new file mode 100644 index 0000000..6afd3b8 --- /dev/null +++ b/contrib/zsh/kamd-functions @@ -0,0 +1,141 @@ +# 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 +} + diff --git a/docs/Doxyfile.local b/docs/Doxyfile.local new file mode 100644 index 0000000..1d9d027 --- /dev/null +++ b/docs/Doxyfile.local @@ -0,0 +1 @@ +EXCLUDE_PATTERNS += */service/* */scripts/* */workspace/* diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7e401e0c944c9b322832077dff12b5daa7ca67f7 GIT binary patch literal 10353 zcmZvCc|6qL7x#=MyRjzg*s_MozK*hmED?%qNl1*WBh1K>eG5_cDElsCn^4wl5oH@> zklk2^G4p(Uf6wdr>v_FqUc={p?mg$;bME_|_c=Gl)L4&!mWvhy0x{^{(KZ8tz`&ni z5cMVC=aF~GQ{acj>&`Kowb?FlPaWGd)cp=NBLT7f&mNAD9kcv}3H2%lg2KlEw+dFr2F_wjMh zK}X?eh}I@4@L++%;?BwWWb94kjNE)VK|Fw>(6?NJ2Rp?Gi|xy+g9Oz{fiM%DKPB;L zlcyWl2lfmU8vdsB_W6DApC9kl^yS^u6K%&9Kosl5LDuCT(O6lob4N>uay69yw-173XAtaZ|%P44bq^C z6m^J;wtsr#2-15BMh`gtn_a^sN*rg?CXQ46cJW+A+~JBprKY=11v>JRYr4zz%PDB@ z#J;@IiavYi)Q91gln(!_H4J~SD=3RNG2=c+cZ#k^_Ju5!QESw=(!AF- zK@>K-tq&feTCD{YM71ES@SPt(*S@^T8ogHc%ywB(Z&?wp;ErcK(vraioz6|BVe%pS zLZC|YQy%xxFMamP8mxF;|HdECAjU5S?X&KgkmIk!69fxNC1mv6Q;GD-FJ zuTLyzOn9mQCrr%4I-p-~6u|+(a(!YprvGoZHqyu14w;jA4QDmHcNKQ!a;WCN8J2M1 zu{Lu5lMaz9>t{dY5~ahUQ;ThP+5209Xl1|!#syIcM*px>;bf+p4wMJ5uk~a=!^<@o z(7`Bm{b$>>{@{jCH>SkyjqXdmq6yd^T!t7%cx$6%u0iG7uv zegWqbM;P7b`X_B;Cm!R7YMdT(C)Li;aEgqdP10Z#LHqC> zNRFqR=GKAmYXhxg#d#K=Y3y<2CF(Xm$kd(W z1gP8ks$UwLBDQF{7|_tur=?k4-wheM{%NA4m^FckTV2PV`%SwC4qZl>8=g@6|mo5a;f4!;Jo4SLyf&uKyf znVInIt4en%`9g4zVLF8hEe}-ITHVZ;&xq{Cu#&5(S57sEUUzHGtQ}MYtOM-s-uEb) z#`Y97#wmOE0@XH0sWjl;0 zEKy48b)@J5i;W3+wKRwh%}wobx*-98fnTCqnGV>M0UxytM`d-9ql|*b7_KJO5EmArfZZSZb%o;Z!rsLHx*C-^f?d_|RMY>IZ#8ePmQ-%UNTcc0 z-C7ni%i`}@G1ny0-=&slfW*7~hDqGKO)Sw~9Qw>QNfJ3^Pl(mgTy7e!HImPy8V$zz z5UwxEptG!rofNGK=D}+y)(#wEXLwr-Hi04=^|~ZN@v5HCnJd)*J8fdK3+kG|{vZl6 zc_MX|1NvU*8y%{Jwhh=L4xpw~0a;0dX(iexxW5!Olja?5U01tf>CZN_KblWcA;Lg7oo2aEIx95kSa?4 zC|tQy6gBjmaedS-?$yUztj{0m%_JtIP0qf`t$*_EOz3r~d}ktqAD6vHSo=)=>XimR z%Y=W>4waYDOZyNO-*(%)&M_N`*bOAvE5Y|culnq-YHZzj(B*qw>9}8P@kA1AgJ(T+ zofSn%9b{iklR?wXDW@y~LaAuxQIQVg`z76H+zLI+6asq{-?iy1S@J%!O_Y|7a%sAE z1#HS};kC;{e>&$WG^->}Bpa^kXW^vLPxoC^o~d}+fwYO*zBW{Qk%~-G?L4LwtxZgA zhxE@jxZ9IfhGltKqc2x~3f{)99T|ru{j{Cl?~exDk-Hy)kN^dy!(zoW2u;PFoBcF< z@pz_mnj=s6f$63jx0Y|upNJU@+1!Z_J2?e-PBHi$Vx6b8?ebeFLl(@Y6_i;)=U}3~ zJjL`JldOsdhd~q-iwPigQJSX8kqiRp#ofh9%pYt+GeM-tjw9ci^g1gCOO=0?IhGtT z_Nopa6i3iKkpdm2K}TgdZf7=orCc|NewyoaS!rN-O1Ja~7d}E}>VgL!oRn%yR+~J# zbA@uPUshb^OBes>$Gsw>gmjG0E)#(*^im(jBzRta&8_|o7u$O-osp|ekS}*1GN)kP zA5iAM!jUskw431W5lvi{6Zu1rYS;G6M|uAiw9DfAH3~y<${DPA{+3((J2)$)T|Q@= z)}>>z37$AO{!HsrPK=#Dh07yN9;f1;orpNC2ge%( zR>mM>7P;PtHHapZ?)uAU3IQuRsvC^Yi6EV-4}R;(FCG%Hu6g6SsM8*WBZoK3SzfIj zHTGR$QSza2*4)|=4Bl}Lld8@)ULmnOlMA36tKf1Wsz*QEKkF#gxchfQ3f9aPsy2Y( zfR1QTeO|a7c5Gk!Jl~i?VC;io^jq7=T*P$WamawZz+ICZBQmDt(Pb%1*}@FjZ};MDLSpq-0ct z&Ig_H=*zaZn-3XitIuiuT$8#%b%m=RObUAA%Lz}*2X^iekY-Z!Zxr!h-(JK(#;o?@ zzq|(uZyEL;uHLf?arfIR=)gg4F%PNdxenj#GTIR9AZklLwbGTn%raEHJ8E|4bJG3! zHO-S`G9*A+$UZVQ$Uwc#Ek}>4j^u;WM$nAv9bNB%DA^Ax@?a!)PZ|{qo-mkV z{lsj4f7PwaF$MR&Ros^7=36ize}?oHAuXIBhRt4j5@nY->7=;t=&AjqA26({P?2`h zMmkl{YSsg;->}JbF!7F&_fMhd-H)p)D7^+m`mGv|zbi3rSSaF;Mq=*9_Eqb)o;m@= z23j%Oms5TJIxaL&xWWt{D=+_kjs} zq{(z#-52j2Tay1~J|f3$C9jQikN@FnfIlNCZ{1z`cqt%7nI%snDwmtYrXunnVK7>E z02Yn=Vr6Q*|7Sjk`KD%oF_-e3)pFBzcjPy;tLz}g#CI$K*oQnS)kbPgU~CeKX1Z4; zVencBd;G*(&cfQScl3YSRyC94v$T<*SPyOYZQm=J2~{E^`Mz7LsU}Ca z#xKU3^d2bptT(CnwX*xa9&TQepdn!tbZ^rqn&V29R^G&~=KeI#X80NpviF5WQgu$r zke2&_V;-wiOBxQJTUAA$w=IdYIZnBJMg|-($E=kd-#5M5FMn4sn8M5)6Hu_fT%38= zP^TKRJ_D<$_>&*bj0_CUeZKZ7m=*~1H+!haTc~D0d|}(V$P6<>LEGt3O4FR>=8w8X zkrb$$n9c|`@5Y(>eSusJ(!~Ed*=KTueye|le~X4G*)_9-yq=-Ra9uR`pu-Ww$1!vj zMMBeV9XvupA{Z6Z%0hF01svO1v*9bxt}`k|D@uZ5a5LXT6;sm*uA1V$6HckKSt^ba z*g?-@HR<=}#12w#>PUXWruD|B^u&9;5P-UcT#4fDDSmXNsDHT_K9;C{6z892HD`U+ zFE{!3Ln(HrxNS2jx_G#8`@@OnxRZJ*($O+$0U>cglTSNnGOjks>erehSo3-lI%pQ{01E^O5yYJaGg`78kn2dvEOX>ZaT60-BUMic10*PyH& zqT|@d_B;h9Z#PMKJweg9lBFaOdV6c@_SU>XO_0w{anSubo@CCwFh`S*?D&M}r&792 zNji8$i`RXT-%SY)7zJ9jan-cMy>I4ES@RS!5YuWOO6&Qr2Okyo!NMjs$TE9u2BEzr zDd7*4<3}&Q@0^;SkWVnuX6Lk7QFLG)2)R&t!I|UJKKEGE`VNs2OUg+ z|BK0-3jvUr-J8Cmit@^*3&Dx)Ex#+4Hf$^-T-qDHP)YPtuF~DTe&`2)o^A@OF1-s; zq>CXKhpg4I#`JLArN>=EyFo?vD*0iVY@zWmJl%+Twj}=5e6M5N1sNPERhCG>6eKNc zqag^Nli})?`8}10hP4}iM^ou@w^apSEWr95pBBi!83)oApO&lBB z)EcAXtJ5$tn`M)9A)FP>$OGiA1&9YbBJ^_U-&-1Tl0Ks>U1dtPh~08EJ0=zvKByL2 z#;b@XfL?~_0Mt6i$G1{Z{m_vMQ-;%;Cc8ecOcf^-n%xiUptdrBJdii4MT9-JEPi~O znf8x#tgx_Y;-Z)SxZnNyz5Ert`VDtjw34+W(YhTQB}eJNHUP<#;(sHA=ZLo=%tV3} zhiR{{iuBSQeq{QZ=Kme|;LR7eDFs9e1<|vY_u3Pj6`uyS!PHtDNx~kP1W*xAzDk^!kkR8O2CE&0->6plO&6Kc?zTTsgcc>ap4eP++qv zI){nR3JgEM5*0+#IG%pg>6PzElWZrmWGU!(H0;5SFJ9%@P4)b!y^QL6B{X{QShPxyPd6}u9=E)!<`;HL7ztBvUg3g0#LidiXty|CtRxIX|%pTZBO z5M))@NX=zm-JGt?aCna#Fn&L-mc9uOPU!NKMBgeoYylNoAVKWCT8hheh&KdgQ7^=B zNsm7@NOt(>aPy{jL_Xz*J*c8vuqOsv(Ax6(dWh)Z$O%zCTEiLL#CszjVq#^aI4!|9 z0q)&F7VnP--~R&to}b;1c4%A5#e3gETby()k&UaK#)iDcg*$@#?h@O6fp~A31d!sE zcbQ4ow|b=s^O4?-==3$QuT~lCH#2}Vj^|D_cJ#Kk*lG^LKK2HRmA2Yl!~c;#90J+rs+0*QgUVjgOL`!n?@)6(1@f9d}8| z`0$auQR2Bxx?yqFFr8O#J1G7(kV|<5k?@2^5J0PUlgUQQvSr0@nLsqYu!E{`tL{{g zqpd5omG^MuKkD8IO2kVH^JK`0mnev|+LZ-?a`(qo3Tqn|OCt&`%Hsh|)_-dT48e0h(r zvG$0Ku^L=?ZC?588OeBpC>=IuV+!@u%{d54W9lGLwUY7U=pH5)5Ed#yPee((p7}ds zj;Csw8cDAG?3$Q+UAedPXwDjot!=LVCU8B&8l~qkn>s)0i6(@Jn&p&t8G=iy2^N^` z?C9LqY{WKhp5Y^3*tzPxvjC>eKVhd{alh_`N#yYV#z~BQuqf?uzAS^IJ1Ru3_w&`6_H21>0CMD^+w;Z;$9E@o=O!Tpc+G06d`$?n!gvfK^wD`^Udci z)APW&W=_8+u+B!sm=((h{~Gyls}+L5qyl>%QWDhT;QCFh`|<3zyjfzcE~HSTW0`7I zC{!dBodiT68!H#l_(6>ijg%NV84^4IF{aE{#!>-Pt-HD3(x^-;h+0u&M?*Trem4Ss zapcNX6F}CKX=hbjzfFVBY1Wjb0i}fK@SSBKxhBFnGghbkSvAo-X4{F)9`Vib?k~ky zLYVl&en^ifSTBkIVw;Wu{G)k`q1>};NQQMtIS&YZyKh)v#Bg~_RHXBb3Z;M!^X+nO zh5bnM$=g+CY}_?0RiRMmJz?}~2a<6YFI?I8()U%=Yn8y?HT47BI>=5M7uYkW&(fhU z(8}rd|1!H;s2onn_+OW#>WNjIJ05br7DOjga`r#DtGL&99xWZetHs z8-$n!6Fbc%Nr?R2pM?=S9e`=@Utj3$n}Kd-pz`a!^g}(7?-s(1-sXt-6#@Ar{>90u zvIK`^S6+z*e&IJu<{|)E`K4g4*FJVVzCnvRiF7W0D#mM0I8UO%5a*&3B?sVfY;*!K zX;GP_law{{Fo3g}2JhLo!LB`V?R~#Am~=EJn=i?5Ynh|2v-{X^)erps7SUmYJING}QVkEQVrbY2j&dbNrj4|}LHx`=WCC7{0d{dX zSuzJ6K)_;Cy>p}E!^A{t42gVkB_sOjH9?iLn;+h-YMQ!%n{$zu2o>u-rB}(Xa8PHk&kc^ZAlG} zxOI{W4W6f;aKOvH+s={)B1ep^o|l75p`M@|B@#C<7Z6Ijw zeb=kY*`5(wrYHb455Virid3nR$?-wOXC2R!lSP2k6UJxUVm9#HIe}02Q$y2CC11L_ zygx-#)9VF0kT*a2=Z)_id}SNHn3m*mXmR_4qV`P1Th?q4oK|estJ$S<;GFPpk5Xxv z^1iMtT8yJp?n)&%1W_cfuUyu`OP!kPZNR2r=Kq-y7mjTQO#)L7&Gi?_-+Z$k)}1Lq z*ffJg2q4}z>_DebLuI5?6d7(yAZZ#M* zor_-`*!qRIN*%r@06Vz`Vn{>h;V2bBPwXkufc%_*x+FZTvDUJ5ui*=OQyhRq0Q}ig z@>AiCQ~j7n{}!8B`-zUlM;gPTwlhNk)K)>RO*9rTyG1=nl_Z_OT&9ub`v(w`-lwgz zG-xneC{I7eBrx!jj%H+9#v^?Sewx6(btxvCXUIL&56jDaiO1T zh*OL?pIn7CUk(*XMx*|g3V~Y%nMO1+at%H8 z0*t+(=IPn4xQh8|<`?~9cCNN{Y0yWsnyi4r{U505A5CY$Hh<xzSP;V z_3wiN`?_ElwAC%Wg-=88Sg3hyk6s*y`cG<-00JxW7M=GK$E|r> z^;c2YY4>A$gHu_u`M;a?zSBIk+~x-Y4VO9bPb@*YuL$Sd27_?Tq&(f`y@&TFi~vg+ z3|2+fbX9kCXO`LiO)AcH{?j~K_hc<~aY&PMU}r1J5EvXsh|@*(ny5}s7x*%aJ)2r?yM%R8lcLb)>*^qgf`wiyB%IT8(=;{@gt5v<0k+VfA&OhCcz2%u102? zqQ;b(%O=xl*I?JL3-k?UMobE0k&iA!i6t{8`CKFet_Ty1)yV{(MSB zB9M?RGV%w6nn%rD5ysJpAd>&9+T=C#+acH5fBhD*V|unD3Y0vlW9-4cK=c+(=r=l6 z7r3*`5q~EQlel^L5;Hy_M)bZivlw-FZ92vQ*3iuHl`I8QmBfE8v30ocsy7cCl_ZBa zkBrCvwW-2ZaZk#`PoaZL`jN**{0jlje26&mmx#U@)J{2RIFRDr|xObWvp??edcV4y1)mo47XCXUn3oW^ckHzHsCr{sRWTL+@uK6}ZIU*C3I){FMGzp&2vWF^us zQ>6+44rXz|IVE6U7Ynz2b|6sI?H9tzB0~5A>0`3!3mW+l85+=tTF7p7%L?)w2Mm;q zZwCct*eB65;Wq|YM>-5Pz0_fo??v;1Bs3uUun(dElnvp$GB^mf8cpKQYTTAf#56mLlJ=<}&PpGGQ_=~^ zjTfmGEyzLE9pdFuaIdki&khQ&Fw%kZf4nIxgZ>_ZAt>~k_)j@LNf0#z;_r44fCo>* z1yRyU!3RqA#PS_)?7+uazjH2W<(=o#GcT=-*0;s?x=sTS3o$N%E7&G3wVynTj?fp9 zARq+^ROx%hW!k?3-$=7(3o0a{mY(%y50qT(TxF9k6ex$KRh)`IaK$Ie@z{)zU{K}f z*)w|v0GOhJz}iG>z=s#uN6z#I{@;J;^ha}S$7E|oO`A-3$^sS6t=7bii-q;pEhhxb z)x6%As&EAYU+1OfVO@x$Fz0qFqt$;y0R%C0&q?(Ptg&#c+g0l3e1McFS$_sn&Uq@Q0o`X>c#(8qrGU2 zjS3O21@>4vQ1~w>Kuqh^*>ilos<#ZyXH#tuh^Kw|K?05-g!cK{f--ngP}F*c-b$lo zJpc4tt3|0?nxsPwjmHP8_dPu-?mwCwhj$-q@ooQ)Z7qFVR-vAj$Bma z?s~9#DSOlZecSHpYWLswG8(o^g`VPMg$d`P@_8tNuni=O2YYoO>9rqxu95|El?$k+ z*exrzdjCP-&jR-~f6SR2?Izx3;*x)^#^keFC>rRK2gRSw2mdbGc|A*)EQ(jBO1e`| ztN7!Ox3*7j>Rj;ZP~GCHgAowTILZhL9J^gD0It<%ZfrHAxFIb6%QUn zsvg~FC-11M_*%J7>)Tv?AU2W0ik;se9HXb5LK-o`*4^;dZEjShe)&_%udRfb^v*Ak z)RQqm(!#e|o#vNM`#z4755Hr(?$Y)+_};w()hY9uBDU^D&!MBj_Y7xFxijp0_pO%Y z`g`TH3gFX7OoV;rcafT17e<$C;0B5p1edjXV@ISr<*NLH%a@qmK7l!_#q4ne#kVq`#!R7>1n>Bj1Ey^Q4o!{5kxZ^ z)O((_|K+RUe1jg>l}Ei2Bqi^T@uR*2$AwE>(uBfX$>7~%=3Wz|QBrKUOktYE`#iGi zT+7wnnZ9Kj^r^O8fKt-?;xMjrj+qX*Ba1A#J2oJexg&m5EH5CO8pzJJ{wO85^6i{@ z5iq}t4*vpWPhcLa8U#LE(*K&v3A?(#?TGP1G3ZW2D}d3}EYi3j_uCef58nL^CN0 z{@;oQzCA|kMaodXDjC1+2A&kw9w2DBn0%JY=SG4vgWj5L)kcl32ji&CV+02;S}BL7x{aWd-ke$d j|GQfUT)4X}PlWlrp4(~m2@wUZ3xf1@jJ2z7JB0loKn;X~ literal 0 HcmV?d00001 diff --git a/metainfo.yaml b/metainfo.yaml new file mode 100644 index 0000000..ea663b4 --- /dev/null +++ b/metainfo.yaml @@ -0,0 +1,21 @@ +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: MacOSX + 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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..45aec93 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,46 @@ +# 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} + ${Boost_INCLUDE_DIRS} + ) + add_subdirectory (imports) +endif () + +add_subdirectory (cli) + +ecm_qt_install_logging_categories( + EXPORT KACTIVITIES + FILE kactivities.categories + DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} +) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt new file mode 100644 index 0000000..479031b --- /dev/null +++ b/src/cli/CMakeLists.txt @@ -0,0 +1,40 @@ +# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: +project (KActivitiesCLI) + +find_package (Qt5 REQUIRED NO_MODULE COMPONENTS Core Gui Widgets) +find_package (Qt5 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 + ) + +qt5_wrap_ui( + KActivitiesCLI_SRCS + ) + +add_executable ( + kactivities-cli + ${KActivitiesCLI_SRCS} + ) + +target_link_libraries ( + kactivities-cli + Qt5::Core + KF5::Activities + ) + +ecm_mark_nongui_executable( + kactivities-cli + ) + +install (TARGETS + kactivities-cli + ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} + ) diff --git a/src/cli/main.cpp b/src/cli/main.cpp new file mode 100644 index 0000000..23a69e8 --- /dev/null +++ b/src/cli/main.cpp @@ -0,0 +1,253 @@ +/* + SPDX-FileCopyrightText: 2016 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include +#include +#include + +#include + +#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); + + awaitFuture( + what == QLatin1String("name") ? controller->setActivityName(id, value) : + what == QLatin1String("description") ? controller->setActivityDescription(id, value) : + what == QLatin1String("icon") ? controller->setActivityIcon(id, value) : + QFuture() + ); + + return 3; +} + +DEFINE_COMMAND(activityProperty, 2) +{ + const auto what = args(1); + const auto id = args(2); + + KActivities::Info info(id); + + out << ( + what == QLatin1String("name") ? info.name() : + what == QLatin1String("description") ? info.description() : + what == QLatin1String("icon") ? info.icon() : + QString() + ) << "\n"; + + 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(); + } + + #define MATCH_COMMAND(Command) \ + else if (args[argId] == QLatin1String("--") + toDashes(QStringLiteral(#Command))) \ + { \ + argId += 1 + Command##_command({ args, argId })(); \ + } + + 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(); +} + diff --git a/src/cli/utils.h b/src/cli/utils.h new file mode 100644 index 0000000..a23156e --- /dev/null +++ b/src/cli/utils.h @@ -0,0 +1,157 @@ +/* + SPDX-FileCopyrightText: 2016 Ivan Čukić + + 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) +{ + 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 " + ); + } + } +} + +template +T awaitFuture(const QFuture &future) +{ + while (!future.isFinished()) { + QCoreApplication::processEvents(); + } + + return future.result(); +} + +void awaitFuture(const QFuture &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"; + } + } +} + + +#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 diff --git a/src/common/dbus/common.h b/src/common/dbus/common.h new file mode 100644 index 0000000..cc6b9a7 --- /dev/null +++ b/src/common/dbus/common.h @@ -0,0 +1,45 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Ivan Cukic + + 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 +#include + +#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 + diff --git a/src/common/dbus/org.kde.ActivityManager.Activities.cpp b/src/common/dbus/org.kde.ActivityManager.Activities.cpp new file mode 100644 index 0000000..700bbc1 --- /dev/null +++ b/src/common/dbus/org.kde.ActivityManager.Activities.cpp @@ -0,0 +1,63 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "org.kde.ActivityManager.Activities.h" + +#include +#include + +namespace details { + +class ActivityInfoStaticInit { +public: + ActivityInfoStaticInit() + { + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + } + + 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(); +} diff --git a/src/common/dbus/org.kde.ActivityManager.Activities.h b/src/common/dbus/org.kde.ActivityManager.Activities.h new file mode 100644 index 0000000..10833f4 --- /dev/null +++ b/src/common/dbus/org.kde.ActivityManager.Activities.h @@ -0,0 +1,46 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Ivan Cukic + + 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 +#include +#include +#include + +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 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 diff --git a/src/common/dbus/org.kde.ActivityManager.Activities.xml b/src/common/dbus/org.kde.ActivityManager.Activities.xml new file mode 100644 index 0000000..8d3e074 --- /dev/null +++ b/src/common/dbus/org.kde.ActivityManager.Activities.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/common/dbus/org.kde.ActivityManager.Application.xml b/src/common/dbus/org.kde.ActivityManager.Application.xml new file mode 100644 index 0000000..fc4a36b --- /dev/null +++ b/src/common/dbus/org.kde.ActivityManager.Application.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/common/dbus/org.kde.ActivityManager.Features.xml b/src/common/dbus/org.kde.ActivityManager.Features.xml new file mode 100644 index 0000000..e45f046 --- /dev/null +++ b/src/common/dbus/org.kde.ActivityManager.Features.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/common/dbus/org.kde.ActivityManager.Resources.xml b/src/common/dbus/org.kde.ActivityManager.Resources.xml new file mode 100644 index 0000000..5f8e725 --- /dev/null +++ b/src/common/dbus/org.kde.ActivityManager.Resources.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml b/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml new file mode 100644 index 0000000..9d04cdd --- /dev/null +++ b/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/common/dbus/org.kde.ActivityManager.ResourcesScoring.xml b/src/common/dbus/org.kde.ActivityManager.ResourcesScoring.xml new file mode 100644 index 0000000..0190ff7 --- /dev/null +++ b/src/common/dbus/org.kde.ActivityManager.ResourcesScoring.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/imports/CMakeLists.txt b/src/imports/CMakeLists.txt new file mode 100644 index 0000000..2efda2b --- /dev/null +++ b/src/imports/CMakeLists.txt @@ -0,0 +1,41 @@ +# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: + +project (kactivities-imports) +find_package (ECM 0.0.8 REQUIRED NO_MODULE) +set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH}) + +find_package (Qt5 REQUIRED NO_MODULE COMPONENTS Sql Gui Qml Quick Sql) +find_package (KF5Config ${KF5_DEP_VERSION} CONFIG REQUIRED) +find_package (KF5CoreAddons ${KF5_DEP_VERSION} CONFIG REQUIRED) + +set ( + kactivities_imports_LIB_SRCS + activitiesextensionplugin.cpp + activitymodel.cpp + activityinfo.cpp +# resourcemodel.cpp + resourceinstance.cpp + + ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/utils/dbusfuture_p.cpp + ) + +add_library (kactivitiesextensionplugin SHARED ${kactivities_imports_LIB_SRCS}) + +target_link_libraries ( + kactivitiesextensionplugin + Qt5::Core + Qt5::DBus + Qt5::Gui + Qt5::Qml + Qt5::Quick + Qt5::Sql + KF5::Activities + KF5::ConfigCore + KF5::CoreAddons + ) + +## install + +install (TARGETS kactivitiesextensionplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/activities) +install (FILES qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/activities) + diff --git a/src/imports/README b/src/imports/README new file mode 100644 index 0000000..bb33c28 --- /dev/null +++ b/src/imports/README @@ -0,0 +1,7 @@ +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. + + diff --git a/src/imports/activitiesextensionplugin.cpp b/src/imports/activitiesextensionplugin.cpp new file mode 100644 index 0000000..1cc3a49 --- /dev/null +++ b/src/imports/activitiesextensionplugin.cpp @@ -0,0 +1,42 @@ +/* + SPDX-FileCopyrightText: 2012, 2013, 2014, 2015 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "activitiesextensionplugin.h" + + +#include "activitymodel.h" +#include "activityinfo.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(uri, 0, 1, "ActivityModel"); + + qmlRegisterType(uri, 0, 1, "ActivityInfo"); + qmlRegisterType(uri, 0, 1, "ResourceInstance"); + + // This one is removed in favor of KActivities::Stats::ResultModel. + // Subclass it, and make it do what you want. + // qmlRegisterType(uri, 0, 1, "ResourceModel"); +} + diff --git a/src/imports/activitiesextensionplugin.h b/src/imports/activitiesextensionplugin.h new file mode 100644 index 0000000..265ca5a --- /dev/null +++ b/src/imports/activitiesextensionplugin.h @@ -0,0 +1,22 @@ +/* + SPDX-FileCopyrightText: 2011, 2012, 2013, 2014, 2015 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#ifndef KACTIVITIES_ACTIVITIES_EXTENSION_PLUGIN_H +#define KACTIVITIES_ACTIVITIES_EXTENSION_PLUGIN_H + +#include + +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 + diff --git a/src/imports/activityinfo.cpp b/src/imports/activityinfo.cpp new file mode 100644 index 0000000..2b9fe2a --- /dev/null +++ b/src/imports/activityinfo.cpp @@ -0,0 +1,96 @@ +/* + SPDX-FileCopyrightText: 2012, 2013, 2014, 2015 Ivan Cukic + + 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); + + emit nameChanged(m_info->name()); + emit descriptionChanged(m_info->description()); + 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); +} + +#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); \ + } + +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" + diff --git a/src/imports/activityinfo.h b/src/imports/activityinfo.h new file mode 100644 index 0000000..0986ae9 --- /dev/null +++ b/src/imports/activityinfo.h @@ -0,0 +1,96 @@ +/* + SPDX-FileCopyrightText: 2012, 2013, 2014, 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef KACTIVITIES_IMPORTS_ACTIVITY_INFO_H +#define KACTIVITIES_IMPORTS_ACTIVITY_INFO_H + +// Qt +#include + +// STL +#include + +// Local +#include +#include + +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); + virtual ~ActivityInfo(); + +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 m_info; + bool m_showCurrentActivity; +}; + +} // namespace Imports +} // namespace KActivities + +#endif // KACTIVITIES_IMPORTS_ACTIVITY_INFO_H + diff --git a/src/imports/activitymodel.cpp b/src/imports/activitymodel.cpp new file mode 100644 index 0000000..6f316d2 --- /dev/null +++ b/src/imports/activitymodel.cpp @@ -0,0 +1,641 @@ +/* + SPDX-FileCopyrightText: 2012, 2013, 2014, 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +// Self +#include "activitymodel.h" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include + +// Boost +#include +#include +#include +#include + +// 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 + 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 + static inline + boost::optional< + std::pair + > + 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 + static inline + void emitActivityUpdated(_Model *model, + const _Container &container, + QObject *activityInfo, int role) + { + const auto activity = static_cast (activityInfo); + emitActivityUpdated(model, container, activity->id(), role); + } + + /** + * Notifies the model that an activity was updated + */ + template + static inline + void emitActivityUpdated(_Model *model, + const _Container &container, + const QString &activity, int role) + { + auto position = Private::activityPosition(container, activity); + + if (position) { + emit model->dataChanged( + model->index(position->first), + model->index(position->first), + role == Qt::DecorationRole ? + QVector {role, ActivityModel::ActivityIcon} : + QVector {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 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 forActivity; + QList 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, SIGNAL(activityAdded(QString)), + this, SLOT(onActivityAdded(QString))); + connect(&m_service, SIGNAL(activityRemoved(QString)), + this, SLOT(onActivityRemoved(QString))); + connect(&m_service, SIGNAL(currentActivityChanged(QString)), + this, SLOT(onCurrentActivityChanged(QString))); + + setServiceStatus(m_service.serviceStatus()); + + Private::backgrounds().subscribe(this); +} + +ActivityModel::~ActivityModel() +{ + Private::backgrounds().unsubscribe(this); +} + +QHash 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(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); + } +} + +#define CREATE_SIGNAL_EMITTER(What,Role) \ + void ActivityModel::onActivity##What##Changed(const QString &) \ + { \ + Private::emitActivityUpdated(this, m_shownActivities, sender(), Role); \ + } + +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()); + + 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; + } +} + +// QFuture 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); \ + } + +CREATE_SETTER(Name) +CREATE_SETTER(Description) +CREATE_SETTER(Icon) + +#undef CREATE_SETTER + +// QFuture Controller::setCurrentActivity(id) +void ActivityModel::setCurrentActivity(const QString &id, + const QJSValue &callback) +{ + continue_with(m_service.setCurrentActivity(id), callback); +} + +// QFuture Controller::addActivity(name) +void ActivityModel::addActivity(const QString &name, const QJSValue &callback) +{ + continue_with(m_service.addActivity(name), callback); +} + +// QFuture Controller::removeActivity(id) +void ActivityModel::removeActivity(const QString &id, const QJSValue &callback) +{ + continue_with(m_service.removeActivity(id), callback); +} + +// QFuture Controller::stopActivity(id) +void ActivityModel::stopActivity(const QString &id, const QJSValue &callback) +{ + continue_with(m_service.stopActivity(id), callback); +} + +// QFuture 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" + diff --git a/src/imports/activitymodel.h b/src/imports/activitymodel.h new file mode 100644 index 0000000..1ac2f2d --- /dev/null +++ b/src/imports/activitymodel.h @@ -0,0 +1,151 @@ +/* + SPDX-FileCopyrightText: 2012, 2013, 2014 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef KACTIVITIES_IMPORTS_ACTIVITY_MODEL_H +#define KACTIVITIES_IMPORTS_ACTIVITY_MODEL_H + +// Qt +#include +#include +#include +#include + +// STL and Boost +#include +#include + +// Local +#include +#include +#include + +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 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 m_shownStates; + QString m_shownStatesString; + + typedef std::shared_ptr 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 m_knownActivities; + boost::container::flat_set 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 + diff --git a/src/imports/qmldir b/src/imports/qmldir new file mode 100644 index 0000000..9138785 --- /dev/null +++ b/src/imports/qmldir @@ -0,0 +1,3 @@ +module org.kde.activities +plugin kactivitiesextensionplugin + diff --git a/src/imports/resourceinstance.cpp b/src/imports/resourceinstance.cpp new file mode 100644 index 0000000..52c07b1 --- /dev/null +++ b/src/imports/resourceinstance.cpp @@ -0,0 +1,115 @@ +/* + SPDX-FileCopyrightText: 2011-2015 Marco Martin + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "resourceinstance.h" + +#include +#include + +#include +#include + +namespace KActivities { +namespace Imports { + +ResourceInstance::ResourceInstance(QQuickItem *parent) + : QQuickItem(parent) +{ + m_syncTimer = new QTimer(this); + m_syncTimer->setSingleShot(true); + connect(m_syncTimer, SIGNAL(timeout()), this, SLOT(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(); +} + +} +} + + diff --git a/src/imports/resourceinstance.h b/src/imports/resourceinstance.h new file mode 100644 index 0000000..02e8abf --- /dev/null +++ b/src/imports/resourceinstance.h @@ -0,0 +1,88 @@ +/* + SPDX-FileCopyrightText: 2011-2015 Marco Martin + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#ifndef RESOURCEINSTANCE_H +#define RESOURCEINSTANCE_H + +//Qt +#include +#include + +// STL +#include + +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(); + + 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 m_resourceInstance; + QUrl m_uri; + QString m_mimetype; + QString m_title; + QTimer *m_syncTimer; +}; + +} +} + +#endif diff --git a/src/imports/resourcemodel.cpp b/src/imports/resourcemodel.cpp new file mode 100644 index 0000000..6e64824 --- /dev/null +++ b/src/imports/resourcemodel.cpp @@ -0,0 +1,682 @@ +/* + SPDX-FileCopyrightText: 2012, 2013, 2014 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +// Self +#include "resourcemodel.h" + +// Qt +#include +#include +#include +#include +#include +#include + +// KDE +#include +#include +#include +#include + +// STL and Boost +#include +#include +#include +#include +#include +#include + +// Local +#include "utils/range.h" +#include "utils/dbusfuture_p.h" +#include "common/dbus/common.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 self() + { + static std::weak_ptr s_instance; + static std::mutex singleton; + + std::lock_guard 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 ResourceModel::roleNames() const +{ + return { + { Qt::DisplayRole, "display" }, + { Qt::DecorationRole, "decoration" }, + { ResourceRole, "uri" }, + { AgentRole, "agent" }, + { ActivityRole, "activity" }, + { DescriptionRole, "subtitle" } + }; +} + +template +inline QStringList validateList( + const QString &values, Validator validator) +{ + using boost::adaptors::filtered; + using kamd::utils::as_collection; + + auto result + = as_collection(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(); + 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(); + 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); + + 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 + ); +} + +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(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(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 Resource; + QList 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 : qAsConst(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" + diff --git a/src/imports/resourcemodel.h b/src/imports/resourcemodel.h new file mode 100644 index 0000000..07a6e13 --- /dev/null +++ b/src/imports/resourcemodel.h @@ -0,0 +1,197 @@ +/* + SPDX-FileCopyrightText: 2012, 2013, 2014 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef KACTIVITIES_IMPORTS_RESOURCE_MODEL_H +#define KACTIVITIES_IMPORTS_RESOURCE_MODEL_H + +// Qt +#include +#include +#include +#include +#include + +// KDE +#include + +// STL and Boost +#include +#include + +// Local +#include +#include +#include + +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 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 m_linker; + + mutable KConfigGroup m_config; +}; + +} // namespace Imports +} // namespace KActivities + +#endif // KACTIVITIES_IMPORTS_RESOURCE_MODEL_H + diff --git a/src/kactivities-features.h.cmake b/src/kactivities-features.h.cmake new file mode 100644 index 0000000..30aa01b --- /dev/null +++ b/src/kactivities-features.h.cmake @@ -0,0 +1,17 @@ +#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 diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt new file mode 100644 index 0000000..11f63df --- /dev/null +++ b/src/lib/CMakeLists.txt @@ -0,0 +1,191 @@ +# 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 + ) + +qt5_add_dbus_interface ( + KActivities_LIB_SRCS + + ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.xml + activities_interface + ) + +qt5_add_dbus_interface ( + KActivities_LIB_SRCS + + ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Resources.xml + resources_interface + ) + +qt5_add_dbus_interface ( + KActivities_LIB_SRCS + + ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Features.xml + features_interface + ) + +qt5_add_dbus_interface ( + KActivities_LIB_SRCS + + ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml + resources_linking_interface + ) + +qt5_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_STRING} + SOVERSION ${KACTIVITIES_SOVERSION} + EXPORT_NAME Activities + ) + +target_link_libraries ( + KF5Activities + PUBLIC + Qt5::Core + PRIVATE + Qt5::DBus + ) + +target_include_directories ( + KF5Activities + INTERFACE "$" + ) + +# 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_KF5}/KActivities/KActivities + COMPONENT Devel + ) + +install ( + FILES ${KActivities_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kactivities_export.h + DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KActivities/kactivities + COMPONENT Devel + ) + +install ( + TARGETS KF5Activities + EXPORT KF5ActivitiesLibraryTargets + ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} + ) + +if(BUILD_QCH) + ecm_add_qch( + KF5Activities_QCH + NAME KActivities + BASE_NAME KF5Activities + VERSION ${KF5_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) + configure_file ( + ${CMAKE_CURRENT_SOURCE_DIR}/libKActivities.pc.cmake + ${CMAKE_CURRENT_BINARY_DIR}/libKActivities.pc + ) + install ( + FILES ${CMAKE_CURRENT_BINARY_DIR}/libKActivities.pc + DESTINATION ${KDE_INSTALL_LIBDIR}/pkgconfig + ) +endif () + +include (ECMGeneratePriFile) +ecm_generate_pri_file ( + BASE_NAME KActivities + LIB_NAME KF5Activities + FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KActivities + ) +install ( + FILES ${PRI_FILENAME} + DESTINATION ${ECM_MKSPECS_INSTALL_DIR} + ) + diff --git a/src/lib/activitiescache_p.cpp b/src/lib/activitiescache_p.cpp new file mode 100644 index 0000000..58ee41f --- /dev/null +++ b/src/lib/activitiescache_p.cpp @@ -0,0 +1,302 @@ +/* + SPDX-FileCopyrightText: 2013-2016 Ivan Cukic + + 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 + +#include + +#include "mainthreadexecutor_p.h" + +namespace KActivities { + +static QString nulluuid = QStringLiteral("00000000-0000-0000-0000-000000000000"); + +using kamd::utils::Mutable; + +std::shared_ptr ActivitiesCache::self() +{ + static std::weak_ptr s_instance; + static std::mutex singleton; + std::lock_guard 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; + + emit serviceStatusChanged(m_status); + 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); + emit activityRemoved(id); + emit activityListChanged(); + + } else { + // qFatal("Requested to delete an non-existent activity"); + } +} + +void ActivitiesCache::updateAllActivities() +{ + // qDebug() << "Updating all"; + m_status = Consumer::Unknown; + 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(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) { + emit runningActivityListChanged(); + } + + emit activityStateChanged(id, state); + + } else { + // qFatal("Requested to update the state of an non-existent activity"); + } +} + +template +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(watcher, &ActivitiesCache::setActivityInfo); +} + +void ActivitiesCache::setAllActivitiesFromReply(QDBusPendingCallWatcher *watcher) +{ + // qDebug() << "reply..."; + passInfoFromReply(watcher, &ActivitiesCache::setAllActivities); +} + +void ActivitiesCache::setCurrentActivityFromReply(QDBusPendingCallWatcher *watcher) +{ + // qDebug() << "reply..."; + passInfoFromReply(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) { + emit activityChanged(info.id); + } else { + emit activityAdded(info.id); + emit activityListChanged(); + if (runningChanged) { + emit runningActivityListChanged(); + } + } +} + +#define CREATE_SETTER(WHAT, What) \ + void ActivitiesCache::setActivity##WHAT(const QString &id, \ + const QString &value) \ + { \ + auto where = getInfo(id); \ + \ + if (where) { \ + where->What = value; \ + emit activity##WHAT##Changed(id, value); \ + } \ + } + +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; + emit serviceStatusChanged(m_status); + emit activityListChanged(); +} + +void ActivitiesCache::setCurrentActivity(const QString &activity) +{ + // qDebug() << "Setting current activity to" << activity; + + if (m_currentActivity == activity) { + return; + } + + m_currentActivity = activity; + + emit currentActivityChanged(activity); +} + +} // namespace KActivities + diff --git a/src/lib/activitiescache_p.h b/src/lib/activitiescache_p.h new file mode 100644 index 0000000..b620018 --- /dev/null +++ b/src/lib/activitiescache_p.h @@ -0,0 +1,128 @@ +/* + SPDX-FileCopyrightText: 2013-2016 Ivan Cukic + + 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 + +#include + +#include +#include + +#include "activities_interface.h" +#include "consumer.h" + +namespace KActivities { + +class ActivitiesCache : public QObject { + Q_OBJECT + +public: + static std::shared_ptr self(); + + ~ActivitiesCache(); + +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 + 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 + inline typename kamd::utils::ptr_to::type + getInfo(const QString &id) + { + const auto where = find(id); + + if (where != m_activities.end()) { + return &(*where); + } + + return nullptr; + } + + template + void onCallFinished(QDBusPendingCall &call, TargetSlot slot) { + auto watcher = new QDBusPendingCallWatcher(call, this); + + connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher *)), + this, slot); + } + + + ActivitiesCache(); + + QList m_activities; + QString m_currentActivity; + Consumer::ServiceStatus m_status; +}; + +} // namespace KActivities + + +#endif /* ACTIVITIES_CACHE_P_H */ + diff --git a/src/lib/activitiesmodel.cpp b/src/lib/activitiesmodel.cpp new file mode 100644 index 0000000..711f0d7 --- /dev/null +++ b/src/lib/activitiesmodel.cpp @@ -0,0 +1,430 @@ +/* + SPDX-FileCopyrightText: 2012-2016 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +// Self +#include "activitiesmodel.h" +#include "activitiesmodel_p.h" + +// Qt +#include +#include +#include +#include +#include +#include +#include + +// Local +#include "utils/remove_if.h" + +namespace KActivities { + +namespace Private { + template + 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 + 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 + 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 + inline + void emitActivityUpdated(_Model *model, + const _Container &container, + const QString &activity, int role) + { + auto position = Private::activityPosition(container, activity); + + if (position) { + emit model->q->dataChanged( + model->q->index(position.index), + model->q->index(position.index), + role == Qt::DecorationRole ? + QVector {role, ActivitiesModel::ActivityIconSource} : + QVector {role} + ); + } + } + + /** + * Notifies the model that an activity was updated + */ + template + inline + void emitActivityUpdated(_Model *model, + const _Container &container, + QObject *activityInfo, int role) + { + const auto activity = static_cast (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 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 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(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(); + } +} + +#define CREATE_SIGNAL_EMITTER(What,Role) \ + void ActivitiesModelPrivate::onActivity##What##Changed(const QString &) \ + { \ + Private::emitActivityUpdated(this, shownActivities, sender(), Role); \ + } + +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 &states) +{ + d->shownStates = states; + + d->replaceActivities(d->activities.activities()); + + emit shownStatesChanged(states); +} + +QVector 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" + diff --git a/src/lib/activitiesmodel.h b/src/lib/activitiesmodel.h new file mode 100644 index 0000000..9221a62 --- /dev/null +++ b/src/lib/activitiesmodel.h @@ -0,0 +1,91 @@ +/* + SPDX-FileCopyrightText: 2012, 2013, 2014 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef ACTIVITIES_ACTIVITIESMODEL_H +#define ACTIVITIES_ACTIVITIESMODEL_H + +// Qt +#include +#include + +// STL +#include + +// 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 shownStates READ shownStates WRITE setShownStates NOTIFY shownStatesChanged) + +public: + explicit ActivitiesModel(QObject *parent = nullptr); + + /** + * Constructs the model and sets the shownStates + */ + ActivitiesModel(QVector 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 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 &shownStates); + + /** + * The model can filter the list of activities based on their state. + * This method returns which states are currently shown. + */ + QVector shownStates() const; + +Q_SIGNALS: + void shownStatesChanged(const QVector &state); + +private: + friend class ActivitiesModelPrivate; + ActivitiesModelPrivate * const d; +}; + +} // namespace KActivities + +#endif // ACTIVITIES_ACTIVITIESMODEL_H + diff --git a/src/lib/activitiesmodel_p.h b/src/lib/activitiesmodel_p.h new file mode 100644 index 0000000..1804e68 --- /dev/null +++ b/src/lib/activitiesmodel_p.h @@ -0,0 +1,76 @@ +/* + SPDX-FileCopyrightText: 2016 Ivan Čukić + + 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 + +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 shownStates; + + typedef std::shared_ptr 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 knownActivities; + QFlatSet 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 + diff --git a/src/lib/consumer.cpp b/src/lib/consumer.cpp new file mode 100644 index 0000000..f331262 --- /dev/null +++ b/src/lib/consumer.cpp @@ -0,0 +1,106 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Ivan Cukic + + 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" + +#include "debug_p.h" + +namespace KActivities { + +ConsumerPrivate::ConsumerPrivate() + : cache(ActivitiesCache::self()) +{ +} + +void ConsumerPrivate::setServiceStatus(Consumer::ServiceStatus status) +{ + emit serviceStatusChanged(status); +} + +Consumer::Consumer(QObject *parent) + : QObject(parent) + , d(new ConsumerPrivate()) +{ + connect(d->cache.get(), SIGNAL(currentActivityChanged(QString)), + this, SIGNAL(currentActivityChanged(QString))); + connect(d->cache.get(), SIGNAL(activityAdded(QString)), + this, SIGNAL(activityAdded(QString))); + connect(d->cache.get(), SIGNAL(activityRemoved(QString)), + this, SIGNAL(activityRemoved(QString))); + connect(d->cache.get(), SIGNAL(serviceStatusChanged(Consumer::ServiceStatus)), + this, SIGNAL(serviceStatusChanged(Consumer::ServiceStatus))); + + connect(d->cache.get(), &ActivitiesCache::activityListChanged, + this, [=]() { emit activitiesChanged(activities()); }); + connect(d->cache.get(), &ActivitiesCache::runningActivityListChanged, + this, [=]() { emit runningActivitiesChanged(runningActivities()); }); + + // connect(d->cache.get(), SIGNAL(activityStateChanged(QString,int)), + // this, SIGNAL(activityStateChanged(QString,int))); +} + +Consumer::~Consumer() +{ + qCDebug(KAMD_CORELIB) << "Killing the consumer"; +} + +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 : qAsConst(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 : qAsConst(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 : qAsConst(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 diff --git a/src/lib/consumer.h b/src/lib/consumer.h new file mode 100644 index 0000000..29e74ca --- /dev/null +++ b/src/lib/consumer.h @@ -0,0 +1,165 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Ivan Cukic + + 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 +#include +#include +#include + +#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(); + + /** + * @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 d; +}; + +} // namespace KActivities + +#endif // ACTIVITIES_CONSUMER_H diff --git a/src/lib/consumer_p.h b/src/lib/consumer_p.h new file mode 100644 index 0000000..86d203a --- /dev/null +++ b/src/lib/consumer_p.h @@ -0,0 +1,37 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Ivan Cukic + + 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 + + +#include "activitiescache_p.h" + +namespace KActivities { + +class ConsumerPrivate : public QObject { + Q_OBJECT + +public: + ConsumerPrivate(); + + std::shared_ptr cache; + +public Q_SLOTS: + void setServiceStatus(Consumer::ServiceStatus status); + +Q_SIGNALS: + void serviceStatusChanged(Consumer::ServiceStatus status); + +}; + +} // namespace KActivities + +#endif // ACTIVITIES_CONSUMER_P_H diff --git a/src/lib/controller.cpp b/src/lib/controller.cpp new file mode 100644 index 0000000..73574ad --- /dev/null +++ b/src/lib/controller.cpp @@ -0,0 +1,127 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Ivan Cukic + + 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() +{ +} + +#define CREATE_SETTER(What) \ + QFuture Controller::setActivity##What(const QString &id, \ + const QString &value) \ + { \ + return Manager::isServiceRunning() \ + ? DBusFuture::asyncCall( \ + Manager::activities(), \ + QString::fromLatin1("SetActivity" #What), id, value) \ + : DBusFuture::fromVoid(); \ + } + +CREATE_SETTER(Name) +CREATE_SETTER(Description) +CREATE_SETTER(Icon) + +#undef CREATE_SETTER + +QFuture 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( + Manager::activities(), QStringLiteral("SetCurrentActivity"), id) + : + DBusFuture::fromValue(false); +} + +QFuture 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( + Manager::activities(), QStringLiteral("AddActivity"), name) + : + DBusFuture::fromValue(QString()); +} + +QFuture 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( + Manager::activities(), QStringLiteral("RemoveActivity"), id) + : + DBusFuture::fromVoid(); +} + +QFuture 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( + Manager::activities(), QStringLiteral("StopActivity"), id) + : + DBusFuture::fromVoid(); +} + +QFuture 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( + Manager::activities(), QStringLiteral("StartActivity"), id) + : + DBusFuture::fromVoid(); +} + +QFuture Controller::previousActivity() +{ + return Manager::isServiceRunning() ? + DBusFuture::asyncCall( + Manager::activities(), QStringLiteral("PreviousActivity")) + : + DBusFuture::fromVoid(); +} + +QFuture Controller::nextActivity() +{ + return Manager::isServiceRunning() ? + DBusFuture::asyncCall( + Manager::activities(), QStringLiteral("NextActivity")) + : + DBusFuture::fromVoid(); +} + +} // namespace KActivities + +// #include "controller.moc" diff --git a/src/lib/controller.h b/src/lib/controller.h new file mode 100644 index 0000000..cd7b7e8 --- /dev/null +++ b/src/lib/controller.h @@ -0,0 +1,115 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Ivan Cukic + + 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 +#include +#include + +#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(); + + /** + * Sets the name of the specified activity + * @param id id of the activity + * @param name name to be set + */ + QFuture 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 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 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 setCurrentActivity(const QString &id); + + /** + * Adds a new activity + * @param name name of the activity + * @returns id of the newly created activity + */ + QFuture addActivity(const QString &name); + + /** + * Removes the specified activity + * @param id id of the activity to delete + */ + QFuture removeActivity(const QString &id); + + /** + * Stops the activity + * @param id id of the activity to stop + */ + QFuture stopActivity(const QString &id); + + /** + * Starts the activity + * @param id id of the activity to start + */ + QFuture startActivity(const QString &id); + + /** + * Switches to the previous activity + */ + QFuture previousActivity(); + + /** + * Switches to the next activity + */ + QFuture nextActivity(); + +private: + // const QScopedPointer d; +}; + +} // namespace KActivities + +#endif // ACTIVITIES_CONTROLLER_H diff --git a/src/lib/info.cpp b/src/lib/info.cpp new file mode 100644 index 0000000..db155ae --- /dev/null +++ b/src/lib/info.cpp @@ -0,0 +1,206 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Ivan Cukic + + 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 + +namespace KActivities { + +// InfoPrivate + +InfoPrivate::InfoPrivate(Info *info, const QString &activity) + : q(info) + , cache(ActivitiesCache::self()) + , id(activity) +{ +} + +// Filters out signals for only this activity +#define IMPLEMENT_SIGNAL_HANDLER(INTERNAL) \ + void InfoPrivate::INTERNAL(const QString &_id) const \ + { if (id == _id) 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) { \ + 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(newState); + emit q->stateChanged(state); + + if (state == KActivities::Info::Stopped) { + emit q->stopped(); + } else if (state == KActivities::Info::Running) { + emit q->started(); + } + } +} + +void InfoPrivate::setCurrentActivity(const QString ¤tActivity) +{ + if (isCurrent) { + if (currentActivity != id) { + // We are no longer the current activity + isCurrent = false; + emit q->isCurrentChanged(false); + } + } else { + if (currentActivity == id) { + // We are the current activity + isCurrent = true; + 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); +#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); +} + +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; +} + +#define CREATE_GETTER(What) \ + QString Info::What() const \ + { \ + auto info = d->cache->getInfo(d->id); \ + return info ? info->What : QString(); \ + } + +CREATE_GETTER(name) +CREATE_GETTER(description) +CREATE_GETTER(icon) + +#undef CREATE_GETTER + +} // namespace KActivities + +#include "moc_info.cpp" +// #include "moc_info_p.cpp" diff --git a/src/lib/info.h b/src/lib/info.h new file mode 100644 index 0000000..9afa39d --- /dev/null +++ b/src/lib/info.h @@ -0,0 +1,235 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Ivan Cukic + + 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 +#include +#include + +#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(); + + /** + * @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 slave. + */ + 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 asynchronous. It will return before the + * resource is actually linked to the activity. + */ + // QFuture linkResource(const QString &resourceUri); + + /** + * Unlinks the specified resource from the activity + * @param resourceUri resource URI + * @note This method is asynchronous. It will return before the + * resource is actually unlinked from the activity. + */ + // QFuture 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 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 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 diff --git a/src/lib/info_p.h b/src/lib/info_p.h new file mode 100644 index 0000000..46798a7 --- /dev/null +++ b/src/lib/info_p.h @@ -0,0 +1,43 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Ivan Cukic + + 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 + +#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 cache; + bool isCurrent; + + const QString id; +}; + +} // namespace KActivities + +#endif // ACTIVITIES_INFO_P_H diff --git a/src/lib/libKActivities.pc.cmake b/src/lib/libKActivities.pc.cmake new file mode 100644 index 0000000..f24960f --- /dev/null +++ b/src/lib/libKActivities.pc.cmake @@ -0,0 +1,12 @@ +prefix=${CMAKE_INSTALL_PREFIX} +exec_prefix=${BIN_INSTALL_DIR} +libdir=${LIB_INSTALL_DIR} +includedir=${INCLUDE_INSTALL_DIR} + +Name: libKActivities +Description: libKActivities is a C++ library for using KDE activities +URL: https://www.kde.org +Requires: Qt5Core +Version: ${KACTIVITIES_VERSION_STRING} +Libs: -L${LIB_INSTALL_DIR} -lKF5Activities +Cflags: -I${INCLUDE_INSTALL_DIR} diff --git a/src/lib/mainthreadexecutor_p.cpp b/src/lib/mainthreadexecutor_p.cpp new file mode 100644 index 0000000..2a259e6 --- /dev/null +++ b/src/lib/mainthreadexecutor_p.cpp @@ -0,0 +1,48 @@ +/* + SPDX-FileCopyrightText: 2014-2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "mainthreadexecutor_p.h" + +#include + +#include +#include +#include + +namespace KActivities { + +namespace detail { + +MainThreadExecutor::MainThreadExecutor(std::function &&f) + : m_function(std::forward>(f)) +{ +} + +void MainThreadExecutor::start() +{ + m_function(); + deleteLater(); +} + +} // namespace detail + +void runInMainThread(std::function &&f) +{ + static auto mainThread = QCoreApplication::instance()->thread(); + + if (QThread::currentThread() == mainThread) { + f(); + + } else { + auto executor = new detail::MainThreadExecutor(std::forward>(f)); + + executor->moveToThread(mainThread); + + QMetaObject::invokeMethod(executor, "start", Qt::BlockingQueuedConnection); + } +} + +} // namespace KActivities diff --git a/src/lib/mainthreadexecutor_p.h b/src/lib/mainthreadexecutor_p.h new file mode 100644 index 0000000..b701732 --- /dev/null +++ b/src/lib/mainthreadexecutor_p.h @@ -0,0 +1,34 @@ +/* + SPDX-FileCopyrightText: 2014-2016 Ivan Cukic + + 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 + +#include + +namespace KActivities { + +namespace detail { + class MainThreadExecutor: public QObject { + Q_OBJECT + + public: + MainThreadExecutor(std::function &&f); + + Q_INVOKABLE void start(); + + private: + std::function m_function; + }; +} // namespace detail + +void runInMainThread(std::function &&f); + +} // namespace KActivities + +#endif // ACTIVITIES_MAINTHREADEXECUTOR_P diff --git a/src/lib/manager_p.cpp b/src/lib/manager_p.cpp new file mode 100644 index 0000000..c712c4f --- /dev/null +++ b/src/lib/manager_p.cpp @@ -0,0 +1,165 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "manager_p.h" + +#include + +#include +#include +#include +#include + +#include "debug_p.h" +#include "mainthreadexecutor_p.h" + +#include "common/dbus/common.h" +#include "utils/dbusfuture_p.h" +#include "utils/continue_with.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 singleton_lock(singleton); + #if defined(QT_DEBUG) + QLoggingCategory::setFilterRules(QStringLiteral("kf.activities.debug=true")); + #endif + + 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 reply = QDBusConnection::sessionBus().interface()->startService(KAMD_DBUS_SERVICE); + if (!reply.isValid()) { + //pre Plasma 5.12 the daemon did not support DBus activation. Fall back to manually forking + QProcess::startDetached(QStringLiteral("kactivitymanagerd"), QStringList()); + } + } + } + + // 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(); + emit serviceStatusChanged(m_serviceRunning); + + if (m_serviceRunning) { + using namespace kamd::utils; + + continue_with( + DBusFuture::fromReply(m_service->serviceVersion()), + [this] (const optional_view &serviceVersion) { + // Test whether the service is older than the library. + // If it is, we need to end this + + if (!serviceVersion.is_initialized()) { + qWarning() << "KActivities: FATAL ERROR: Failed to contact the activity manager daemon"; + m_serviceRunning = false; + return; + } + + auto split = serviceVersion->split(QLatin1Char('.')); + QList 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.get(); + 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 diff --git a/src/lib/manager_p.h b/src/lib/manager_p.h new file mode 100644 index 0000000..394ef83 --- /dev/null +++ b/src/lib/manager_p.h @@ -0,0 +1,63 @@ +/* + SPDX-FileCopyrightText: 2010-2016 Ivan Cukic + + 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 + +#include "application_interface.h" +#include "activities_interface.h" +#include "resources_interface.h" +#include "resources_linking_interface.h" +#include "features_interface.h" + +#include + +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 diff --git a/src/lib/resourceinstance.cpp b/src/lib/resourceinstance.cpp new file mode 100644 index 0000000..58d6856 --- /dev/null +++ b/src/lib/resourceinstance.cpp @@ -0,0 +1,177 @@ +/* + SPDX-FileCopyrightText: 2011-2016 Ivan Cukic + + 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 +#include "debug_p.h" + +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 diff --git a/src/lib/resourceinstance.h b/src/lib/resourceinstance.h new file mode 100644 index 0000000..00fa97e --- /dev/null +++ b/src/lib/resourceinstance.h @@ -0,0 +1,188 @@ +/* + SPDX-FileCopyrightText: 2011-2016 Ivan Cukic + + 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 +#include + +#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(); + +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 d; +}; +} + +#endif // ACTIVITIES_RESOURCEINSTANCE_H diff --git a/src/lib/version.cpp b/src/lib/version.cpp new file mode 100644 index 0000000..3d3cdcd --- /dev/null +++ b/src/lib/version.cpp @@ -0,0 +1,36 @@ +/* + SPDX-FileCopyrightText: 2008 Aaron Seigo + + 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 diff --git a/src/lib/version.h b/src/lib/version.h new file mode 100644 index 0000000..fb8c111 --- /dev/null +++ b/src/lib/version.h @@ -0,0 +1,49 @@ +/* + SPDX-FileCopyrightText: 2008-2016 Aaron Seigo + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#ifndef KACTIVITIES_VERSION_BIN_H +#define KACTIVITIES_VERSION_BIN_H + +/** @file version.h */ + +#include "kactivities_export.h" +#include + +#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 diff --git a/src/utils/continue_with.h b/src/utils/continue_with.h new file mode 100644 index 0000000..8c07c37 --- /dev/null +++ b/src/utils/continue_with.h @@ -0,0 +1,96 @@ +/* + SPDX-FileCopyrightText: 2014-2016 Ivan Cukic + + 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 +#include +#include + +#include "utils/optional_view.h" +// #include + +#ifdef ENABLE_QJSVALUE_CONTINUATION +#include +#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 + 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 &future, QJSValue continuation) + { + Q_UNUSED(future); + auto result = continuation.call({}); + if (result.isError()) { + qWarning() << "Handler returned this error: " << result.toString(); + } + } +#endif + + template + inline void test_continuation(_Continuation &&continuation) + { + Q_UNUSED(continuation); + } + + template + inline void pass_value(const QFuture<_ReturnType> &future, + _Continuation &&continuation) + { + using namespace kamd::utils; + continuation(future.resultCount() > 0 + ? make_optional_view(future.result()) + : none()); + } + + template + inline void pass_value(_Continuation &&continuation) + { + continuation(); + } + +} //^ namespace detail + +template +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 */ + + diff --git a/src/utils/dbusfuture_p.cpp b/src/utils/dbusfuture_p.cpp new file mode 100644 index 0000000..980c23a --- /dev/null +++ b/src/utils/dbusfuture_p.cpp @@ -0,0 +1,50 @@ +/* + SPDX-FileCopyrightText: 2012-2016 Ivan Cukic + + 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::callFinished() +{ + deleteLater(); + + // qDebug() << "This is call end"; + + this->reportFinished(); +} + +ValueFutureInterface::ValueFutureInterface() +{ +} + +QFuture ValueFutureInterface::start() +{ + auto future = this->future(); + + this->reportFinished(); + + deleteLater(); + + return future; +} + +} //^ namespace detail + +QFuture fromVoid() +{ + using namespace detail; + + auto valueFutureInterface = new ValueFutureInterface(); + + return valueFutureInterface->start(); +} + +} // namespace DBusFuture + diff --git a/src/utils/dbusfuture_p.h b/src/utils/dbusfuture_p.h new file mode 100644 index 0000000..1e689bb --- /dev/null +++ b/src/utils/dbusfuture_p.h @@ -0,0 +1,163 @@ +/* + SPDX-FileCopyrightText: 2013-2016 Ivan Cukic + + 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 +#include +#include +#include +#include +#include +#include + +#include "debug_p.h" + +namespace DBusFuture { + +namespace detail { //_ + +template +class DBusCallFutureInterface : public QObject, + public QFutureInterface<_Result> { +public: + DBusCallFutureInterface(QDBusPendingReply<_Result> reply) + : reply(reply), + replyWatcher(nullptr) + { + } + + ~DBusCallFutureInterface() + { + 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 +void DBusCallFutureInterface<_Result>::callFinished() +{ + deleteLater(); + + if (!reply.isError()) { + this->reportResult(reply.value()); + } + + this->reportFinished(); +} + +template <> +void DBusCallFutureInterface::callFinished(); + +template +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 : public QObject, QFutureInterface { +public: + ValueFutureInterface(); + + QFuture start(); + // { + // auto future = this->future(); + // this->reportFinished(); + // deleteLater(); + // return future; + // } +}; + +} //^ namespace detail + +template +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 +QFuture<_Result> +fromValue(const _Result & value) +{ + using namespace detail; + + auto valueFutureInterface = new ValueFutureInterface<_Result>(value); + + return valueFutureInterface->start(); +} + +template +QFuture<_Result> +fromReply(const QDBusPendingReply<_Result> &reply) +{ + using namespace detail; + + auto callFutureInterface = new DBusCallFutureInterface<_Result>(reply); + + return callFutureInterface->start(); +} + +QFuture fromVoid(); + +} // namespace DBusFuture + +#endif /* DBUSFUTURE_P_H */ + diff --git a/src/utils/model_updaters.h b/src/utils/model_updaters.h new file mode 100644 index 0000000..f5e7467 --- /dev/null +++ b/src/utils/model_updaters.h @@ -0,0 +1,63 @@ +/* + SPDX-FileCopyrightText: 2012 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#ifndef KACTIVITIES_MODEL_UPDATERS_H +#define KACTIVITIES_MODEL_UPDATERS_H + +// ----------------------------------------- +// RAII classes for model updates ---------- +// ----------------------------------------- + +#define DECLARE_RAII_MODEL_UPDATERS(Class) \ + template class _model_reset { \ + T *model; \ + \ + public: \ + _model_reset(T *m) : model(m) \ + { \ + model->beginResetModel(); \ + } \ + ~_model_reset() \ + { \ + model->endResetModel(); \ + } \ + }; \ + template 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 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 model_reset; \ + typedef _model_remove model_remove; \ + typedef _model_insert model_insert; + +// ----------------------------------------- + +#endif // KACTIVITIES_MODEL_UPDATERS_H + diff --git a/src/utils/optional_view.h b/src/utils/optional_view.h new file mode 100644 index 0000000..bafd164 --- /dev/null +++ b/src/utils/optional_view.h @@ -0,0 +1,65 @@ +/* + SPDX-FileCopyrightText: 2012-2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef UTILS_OPTIONAL_VIEW_H +#define UTILS_OPTIONAL_VIEW_H + +namespace kamd { +namespace utils { + +struct none_t {}; +inline const none_t none() { return none_t(); } + +// A simple implementation of the optional class +// until we can rely on std::optional. +// It is not going to come close in the supported +// features to the std one. +// (we need it in the core library, so we don't +// want to use boost.optional) +template +class optional_view { +public: + explicit optional_view(const T &value) + : m_value(&value) + { + } + + optional_view(const none_t &) + : m_value(nullptr) + { + } + + bool is_initialized() const + { + return m_value != nullptr; + } + + const T &get() const + { + return *m_value; + } + + const T *operator->() const + { + return m_value; + } + +private: + const T *const m_value; +}; + +template +optional_view make_optional_view(const T &value) +{ + return optional_view(value); +} + +} // namespace utils +} // namespace kamd + + +#endif // UTILS_OPTIONAL_VIEW_H + diff --git a/src/utils/ptr_to.h b/src/utils/ptr_to.h new file mode 100644 index 0000000..cb3fe2e --- /dev/null +++ b/src/utils/ptr_to.h @@ -0,0 +1,33 @@ +/* + SPDX-FileCopyrightText: 2015-2016 Ivan Cukic + + 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 +struct ptr_to { + typedef const T * const type; +}; + +template +struct ptr_to { + typedef T * const type; +}; + + + +} // namespace utils +} // namespace kamd + +#endif // PTR_TO_H diff --git a/src/utils/qflatset.h b/src/utils/qflatset.h new file mode 100644 index 0000000..7bb371e --- /dev/null +++ b/src/utils/qflatset.h @@ -0,0 +1,60 @@ +/* + SPDX-FileCopyrightText: 2016 Ivan Čukić + + 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 +#include + +namespace KActivities { + +template +class QFlatSet: public QVector { +public: + QFlatSet() + { + } + + inline + // QPair::iterator, bool> insert(const T &value) + std::tuple::iterator, int, bool> insert(const T &value) + { + auto lessThan = LessThan(); + auto begin = this->begin(); + auto end = this->end(); + + if (begin == end) { + QVector::insert(0, value); + + return std::make_tuple(QVector::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::insert(iterator, value); + + return std::make_tuple(iterator, iterator - begin, true); + } + } + +private: + QFlatSet(const QFlatSet &original); // = delete + +}; + + +} // namespace KActivities + +#endif // KACTIVITIES_STATS_QFLATSET_H + diff --git a/src/utils/range.h b/src/utils/range.h new file mode 100644 index 0000000..0d58a46 --- /dev/null +++ b/src/utils/range.h @@ -0,0 +1,68 @@ +/* + SPDX-FileCopyrightText: 2012 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#ifndef UTILS_RANGE_H +#define UTILS_RANGE_H + +#include +#include +#include + +/******************************************************************** + * Syntactic sugar for converting ranges to collections * + ********************************************************************/ + +namespace kamd { +namespace utils { + +template +__inline Collection as_collection(Range range) +{ + Collection result; + + boost::copy(range, std::back_inserter(result)); + + return result; +} + +template +__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 +__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 +__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 diff --git a/src/utils/remove_if.h b/src/utils/remove_if.h new file mode 100644 index 0000000..244b078 --- /dev/null +++ b/src/utils/remove_if.h @@ -0,0 +1,31 @@ +/* + SPDX-FileCopyrightText: 2012 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#ifndef UTILS_REMOVE_IF_H +#define UTILS_REMOVE_IF_H + +#include + +/******************************************************************** + * Syntactic sugar for the erase-remove idiom * + ********************************************************************/ + +namespace kamd { +namespace utils { + +template +__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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..9d268e8 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,6 @@ +# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: + +# add_subdirectory(slc-interface) +add_subdirectory(activities-model) + + diff --git a/tests/activities-model/CMakeLists.txt b/tests/activities-model/CMakeLists.txt new file mode 100644 index 0000000..be2d66d --- /dev/null +++ b/tests/activities-model/CMakeLists.txt @@ -0,0 +1,41 @@ +# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: +project (KActivitiesModelTestApp) + +find_package (Qt5 REQUIRED NO_MODULE COMPONENTS Core Gui Widgets) +find_package (Qt5 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 + ) + +qt5_wrap_ui( + KActivitiesModelTestApp_SRCS + window.ui + ) + +if (NOT WIN32) + + add_executable ( + KActivitiesModelTestApp + ${KActivitiesModelTestApp_SRCS} + ) + + target_link_libraries ( + KActivitiesModelTestApp + Qt5::Core + Qt5::Gui + Qt5::Widgets + Qt5::DBus + KF5::Activities + KF5::WindowSystem + ) + +endif () diff --git a/tests/activities-model/main.cpp b/tests/activities-model/main.cpp new file mode 100644 index 0000000..1dcf453 --- /dev/null +++ b/tests/activities-model/main.cpp @@ -0,0 +1,19 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include +#include "window.h" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + Window w; + w.show(); + + return app.exec(); +} + diff --git a/tests/activities-model/window.cpp b/tests/activities-model/window.cpp new file mode 100644 index 0000000..36d92ea --- /dev/null +++ b/tests/activities-model/window.cpp @@ -0,0 +1,114 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "window.h" + +#include "ui_window.h" + +#include +#include + +#include + +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); + KWindowSystem::self()->setOnActivities(effectiveWinId(), QStringList()); + KWindowSystem::self()->setOnAllDesktops(effectiveWinId(), true); +} + +Window::~Window() +{ + delete ui; +} diff --git a/tests/activities-model/window.h b/tests/activities-model/window.h new file mode 100644 index 0000000..6fdbc1d --- /dev/null +++ b/tests/activities-model/window.h @@ -0,0 +1,34 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include + +#include +#include + +namespace Ui { + class MainWindow; +} + +class Window: public QMainWindow { + Q_OBJECT + +public: + Window(); + ~Window(); + +protected: + void showEvent(QShowEvent * event) override; + +private: + Ui::MainWindow *ui; + KActivities::Consumer *activities; + KActivities::ActivitiesModel *modelRunningActivities; + KActivities::ActivitiesModel *modelStoppedActivities; +}; + diff --git a/tests/activities-model/window.ui b/tests/activities-model/window.ui new file mode 100644 index 0000000..6e566b1 --- /dev/null +++ b/tests/activities-model/window.ui @@ -0,0 +1,81 @@ + + + MainWindow + + + + 0 + 0 + 406 + 869 + + + + MainWindow + + + + + + + + + Running + + + + + + + + + + + + + + Stopped + + + + + + + + + + + + + 0 + 48 + + + + Close + + + + + + + + + + buttonClose + clicked() + MainWindow + close() + + + 332 + 842 + + + 341 + 878 + + + + + diff --git a/tests/imports/activities.qml b/tests/imports/activities.qml new file mode 100644 index 0000000..eca5b18 --- /dev/null +++ b/tests/imports/activities.qml @@ -0,0 +1,77 @@ +/* + SPDX-FileCopyrightText: 2013 Heena Mahour + SPDX-FileCopyrightText: 2013 Sebastian Kügler + + 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 + } + + } +} diff --git a/tests/imports/org.kde.listactivitiestest/contents/ui/main.qml b/tests/imports/org.kde.listactivitiestest/contents/ui/main.qml new file mode 100644 index 0000000..eca5b18 --- /dev/null +++ b/tests/imports/org.kde.listactivitiestest/contents/ui/main.qml @@ -0,0 +1,77 @@ +/* + SPDX-FileCopyrightText: 2013 Heena Mahour + SPDX-FileCopyrightText: 2013 Sebastian Kügler + + 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 + } + + } +} diff --git a/tests/imports/org.kde.listactivitiestest/metadata.desktop b/tests/imports/org.kde.listactivitiestest/metadata.desktop new file mode 100644 index 0000000..dae15d6 --- /dev/null +++ b/tests/imports/org.kde.listactivitiestest/metadata.desktop @@ -0,0 +1,62 @@ +[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= diff --git a/tests/imports/plasma-applet-org.kde.listactivitiestest.desktop b/tests/imports/plasma-applet-org.kde.listactivitiestest.desktop new file mode 100644 index 0000000..b089b47 --- /dev/null +++ b/tests/imports/plasma-applet-org.kde.listactivitiestest.desktop @@ -0,0 +1,104 @@ +[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 + diff --git a/tests/imports/resources.qml b/tests/imports/resources.qml new file mode 100644 index 0000000..924bd2d --- /dev/null +++ b/tests/imports/resources.qml @@ -0,0 +1,198 @@ +/* + SPDX-FileCopyrightText: 2013 Heena Mahour + SPDX-FileCopyrightText: 2013 Sebastian Kügler + + 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 + } + + } + } +} diff --git a/tests/slc-interface/CMakeLists.txt b/tests/slc-interface/CMakeLists.txt new file mode 100644 index 0000000..fcb5c63 --- /dev/null +++ b/tests/slc-interface/CMakeLists.txt @@ -0,0 +1,45 @@ +# vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: +project (KActivitiesSLCTestApp) + +find_package (Qt5 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 + ) + +qt5_add_dbus_interface ( + KActivitiesSLCTestApp_SRCS + + ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/service/plugins/slc/org.kde.ActivityManager.SLC.xml + slc_interface + ) + +qt5_wrap_ui( + KActivitiesSLCTestApp_SRCS + window.ui + ) + +if (NOT WIN32) + + add_executable ( + KActivitiesSLCTestApp + ${KActivitiesSLCTestApp_SRCS} + ) + + target_link_libraries ( + KActivitiesSLCTestApp + Qt5::Core + Qt5::Gui + Qt5::Widgets + Qt5::DBus + KF5::Activities + ) + +endif () diff --git a/tests/slc-interface/main.cpp b/tests/slc-interface/main.cpp new file mode 100644 index 0000000..f73c574 --- /dev/null +++ b/tests/slc-interface/main.cpp @@ -0,0 +1,35 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include +#include "window.h" + +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(); +} + diff --git a/tests/slc-interface/window.cpp b/tests/slc-interface/window.cpp new file mode 100644 index 0000000..ba4d1b7 --- /dev/null +++ b/tests/slc-interface/window.cpp @@ -0,0 +1,40 @@ +/* + SPDX-FileCopyrightText: 2015 Ivan Cukic + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "window.h" + +#include "ui_window.h" + +#include + +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); + +} + diff --git a/tests/slc-interface/window.h b/tests/slc-interface/window.h new file mode 100644 index 0000000..8add077 --- /dev/null +++ b/tests/slc-interface/window.h @@ -0,0 +1,30 @@ +/* + SPDX-FileCopyrightText: 2013-2016 Ivan Cukic + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#pragma once + +#include +#include "slc_interface.h" + +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; +}; + diff --git a/tests/slc-interface/window.ui b/tests/slc-interface/window.ui new file mode 100644 index 0000000..35ddc9d --- /dev/null +++ b/tests/slc-interface/window.ui @@ -0,0 +1,88 @@ + + + MainWindow + + + + 0 + 0 + 474 + 74 + + + + MainWindow + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + + + + + + + + + Resource + + + + + + + ... + + + + + + + Title + + + + + + + ... + + + + + + + Mimetype + + + + + + + ... + + + + + + + + + + + diff --git a/vim-extrarc b/vim-extrarc new file mode 100644 index 0000000..ea92437 --- /dev/null +++ b/vim-extrarc @@ -0,0 +1,12 @@ + +set makeprg=OBJ_REPLACEMENT='s=src=build-clang='\ makeobj + +imap :SlimuxShellRun make && ./autotests/stats/KActivitiesStatsTest ResultWatcher +map :SlimuxShellRun make && ./autotests/stats/KActivitiesStatsTest ResultWatcher + +let g:ctrlpswitcher_project_sources = expand(':p:h')."/src" +let g:ctrlpswitcher_mode = 1 + +set foldmethod=marker +set foldmarker=//_,//^ + -- 2.30.2