├── .git-blame-ignore-revs ├── .gitignore ├── .gitlab-ci.yml ├── .kde-ci.yml ├── CMakeLists.txt ├── LICENSES ├── BSD-2-Clause.txt ├── CC0-1.0.txt ├── GPL-2.0-or-later.txt ├── LGPL-2.0-or-later.txt ├── LGPL-2.1-only.txt ├── LGPL-3.0-only.txt ├── LicenseRef-KDE-Accepted-LGPL.txt └── MIT.txt ├── Messages.sh ├── README.md ├── TODO ├── autotests ├── CMakeLists.txt ├── colorschemetest.py ├── inputcapturebarriertest.cpp └── xdgshortcuttest.cpp ├── cmake └── FindKIOFuse.cmake ├── data ├── CMakeLists.txt ├── kde.portal ├── org.freedesktop.Accounts.User.xml ├── org.freedesktop.Application.xml ├── org.freedesktop.impl.portal.PermissionStore.xml ├── org.freedesktop.impl.portal.desktop.kde.cmake.in ├── org.freedesktop.impl.portal.desktop.kde.desktop.in ├── org.freedesktop.portal.Documents.xml ├── org.kde.KIOFuse.VFS.xml └── plasma-xdg-desktop-portal-kde.service.in ├── po ├── ar │ └── xdg-desktop-portal-kde.po ├── ast │ └── xdg-desktop-portal-kde.po ├── az │ └── xdg-desktop-portal-kde.po ├── bg │ └── xdg-desktop-portal-kde.po ├── ca │ └── xdg-desktop-portal-kde.po ├── ca@valencia │ └── xdg-desktop-portal-kde.po ├── cs │ └── xdg-desktop-portal-kde.po ├── da │ └── xdg-desktop-portal-kde.po ├── de │ └── xdg-desktop-portal-kde.po ├── el │ └── xdg-desktop-portal-kde.po ├── en_GB │ └── xdg-desktop-portal-kde.po ├── eo │ └── xdg-desktop-portal-kde.po ├── es │ └── xdg-desktop-portal-kde.po ├── et │ └── xdg-desktop-portal-kde.po ├── eu │ └── xdg-desktop-portal-kde.po ├── fi │ └── xdg-desktop-portal-kde.po ├── fr │ └── xdg-desktop-portal-kde.po ├── gl │ └── xdg-desktop-portal-kde.po ├── he │ └── xdg-desktop-portal-kde.po ├── hi │ └── xdg-desktop-portal-kde.po ├── hu │ └── xdg-desktop-portal-kde.po ├── ia │ └── xdg-desktop-portal-kde.po ├── id │ └── xdg-desktop-portal-kde.po ├── is │ └── xdg-desktop-portal-kde.po ├── it │ └── xdg-desktop-portal-kde.po ├── ja │ └── xdg-desktop-portal-kde.po ├── ka │ └── xdg-desktop-portal-kde.po ├── ko │ └── xdg-desktop-portal-kde.po ├── lt │ └── xdg-desktop-portal-kde.po ├── lv │ └── xdg-desktop-portal-kde.po ├── ml │ └── xdg-desktop-portal-kde.po ├── nb │ └── xdg-desktop-portal-kde.po ├── nl │ └── xdg-desktop-portal-kde.po ├── nn │ └── xdg-desktop-portal-kde.po ├── pa │ └── xdg-desktop-portal-kde.po ├── pl │ └── xdg-desktop-portal-kde.po ├── pt │ └── xdg-desktop-portal-kde.po ├── pt_BR │ └── xdg-desktop-portal-kde.po ├── ro │ └── xdg-desktop-portal-kde.po ├── ru │ └── xdg-desktop-portal-kde.po ├── sa │ └── xdg-desktop-portal-kde.po ├── sk │ └── xdg-desktop-portal-kde.po ├── sl │ └── xdg-desktop-portal-kde.po ├── sr │ └── xdg-desktop-portal-kde.po ├── sr@ijekavian │ └── xdg-desktop-portal-kde.po ├── sr@ijekavianlatin │ └── xdg-desktop-portal-kde.po ├── sr@latin │ └── xdg-desktop-portal-kde.po ├── sv │ └── xdg-desktop-portal-kde.po ├── ta │ └── xdg-desktop-portal-kde.po ├── tr │ └── xdg-desktop-portal-kde.po ├── uk │ └── xdg-desktop-portal-kde.po ├── zh_CN │ └── xdg-desktop-portal-kde.po └── zh_TW │ └── xdg-desktop-portal-kde.po └── src ├── AccessDialog.qml ├── AppChooserDialog.qml ├── CMakeLists.txt ├── DynamicLauncherDialog.qml ├── GlobalShortcutsDialog.qml ├── InputCaptureDialog.qml ├── PipeWireDelegate.qml ├── RemoteDesktopDialog.qml ├── ScreenChooserDialog.qml ├── ScreenshotDialog.qml ├── UserInfoDialog.qml ├── access.cpp ├── access.h ├── accessdialog.cpp ├── accessdialog.h ├── account.cpp ├── account.h ├── appchooser.cpp ├── appchooser.h ├── appchooserdialog.cpp ├── appchooserdialog.h ├── background.cpp ├── background.h ├── clipboard.cpp ├── clipboard.h ├── dbushelpers.cpp ├── dbushelpers.h ├── desktopportal.cpp ├── desktopportal.h ├── dynamiclauncher.cpp ├── dynamiclauncher.h ├── dynamiclauncherdialog.cpp ├── dynamiclauncherdialog.h ├── email.cpp ├── email.h ├── filechooser.cpp ├── filechooser.h ├── globalshortcuts.cpp ├── globalshortcuts.h ├── inhibit.cpp ├── inhibit.h ├── inputcapture.cpp ├── inputcapture.h ├── inputcapturebarrier.cpp ├── inputcapturebarrier.h ├── inputcapturedialog.cpp ├── inputcapturedialog.h ├── kirigami-filepicker ├── CMakeLists.txt ├── api │ ├── mobilefiledialog.cpp │ └── mobilefiledialog.h └── declarative │ ├── CreateDirectorySheet.qml │ ├── FilePicker.qml │ ├── FilePickerWindow.qml │ ├── PlacesGlobalDrawer.qml │ ├── dirmodel.cpp │ ├── dirmodel.h │ ├── dirmodelutils.cpp │ ├── dirmodelutils.h │ ├── filechooserqmlcallback.cpp │ ├── filechooserqmlcallback.h │ ├── filepicker.qrc │ ├── fileplacesmodel.cpp │ └── fileplacesmodel.h ├── notification.cpp ├── notification.h ├── notificationinhibition.cpp ├── notificationinhibition.h ├── org.kde.KWin.TabletModeManager.xml ├── org.kde.KWin.VirtualKeyboard.xml ├── outputsmodel.cpp ├── outputsmodel.h ├── portalicon.cpp ├── portalicon.h ├── print.cpp ├── print.h ├── quickdialog.cpp ├── quickdialog.h ├── region-select ├── FloatingBackground.qml ├── FloatingTextBox.qml ├── RegionSelectOverlay.qml ├── SelectionEditor.cpp └── SelectionEditor.h ├── remotedesktop.cpp ├── remotedesktop.h ├── remotedesktopdialog.cpp ├── remotedesktopdialog.h ├── request.cpp ├── request.h ├── resources.qrc ├── restoredata.cpp ├── restoredata.h ├── screencast.cpp ├── screencast.h ├── screencasting.cpp ├── screencasting.h ├── screenchooserdialog.cpp ├── screenchooserdialog.h ├── screenshot.cpp ├── screenshot.h ├── screenshotdialog.cpp ├── screenshotdialog.h ├── session.cpp ├── session.h ├── settings.cpp ├── settings.h ├── userinfodialog.cpp ├── userinfodialog.h ├── utils.cpp ├── utils.h ├── waylandintegration.cpp ├── waylandintegration.h ├── waylandintegration_p.h ├── xdg-desktop-portal-kde.cpp ├── xdg-desktop-portal-kde.notifyrc ├── xdgshortcut.cpp └── xdgshortcut.h /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # clang-format 2 | 79184c06c27a417e269dbe4ab82add8edf24a5d8 3 | 9fec0c4b5f0b1b36ceab211a3b2d27fd4a2114f9 4 | 369328311ec82f9c4cca5a6ca67cee2ecc5cdca8 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore the following files 2 | *~ 3 | *.[oa] 4 | *.diff 5 | *.kate-swp 6 | *.kdev4 7 | .kdev_include_paths 8 | *.kdevelop.pcs 9 | *.moc 10 | *.moc.cpp 11 | *.orig 12 | *.user 13 | .*.swp 14 | .swp.* 15 | Doxyfile 16 | Makefile 17 | avail 18 | random_seed 19 | /build*/ 20 | CMakeLists.txt.user* 21 | *.unc-backup* 22 | .clang-format 23 | /build*/ 24 | /compile_commands.json 25 | .clangd 26 | .idea 27 | /cmake-build* 28 | .cache 29 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: None 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | include: 5 | - project: sysadmin/ci-utilities 6 | file: 7 | - /gitlab-templates/linux-qt6.yml 8 | - /gitlab-templates/freebsd-qt6.yml 9 | - /gitlab-templates/linux-qt6-next.yml 10 | -------------------------------------------------------------------------------- /.kde-ci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: None 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | Dependencies: 5 | - 'on': ['@all'] 6 | 'require': 7 | 'frameworks/extra-cmake-modules': '@latest-kf6' 8 | 'frameworks/kauth': '@latest-kf6' 9 | 'frameworks/kcodecs': '@latest-kf6' 10 | 'frameworks/kcompletion': '@latest-kf6' 11 | 'frameworks/kconfig': '@latest-kf6' 12 | 'frameworks/kconfigwidgets': '@latest-kf6' 13 | 'frameworks/kcoreaddons': '@latest-kf6' 14 | 'frameworks/ki18n': '@latest-kf6' 15 | 'frameworks/kio': '@latest-kf6' 16 | 'frameworks/kitemviews': '@latest-kf6' 17 | 'frameworks/kjobwidgets': '@latest-kf6' 18 | 'frameworks/knotifications': '@latest-kf6' 19 | 'frameworks/kservice': '@latest-kf6' 20 | 'frameworks/kwidgetsaddons': '@latest-kf6' 21 | 'frameworks/kwindowsystem': '@latest-kf6' 22 | 'frameworks/kxmlgui': '@latest-kf6' 23 | 'frameworks/solid': '@latest-kf6' 24 | 'frameworks/kirigami': '@latest-kf6' 25 | 'frameworks/kstatusnotifieritem': '@latest-kf6' 26 | 'frameworks/kcrash': '@latest-kf6' 27 | 'plasma/kwayland': '@same' 28 | 'third-party/wayland': '@latest' 29 | 'third-party/wayland-protocols': '@latest' 30 | 31 | RuntimeDependencies: 32 | - 'on': ['@all'] 33 | 'require': 34 | 'frameworks/kiconthemes': '@latest-kf6' # IconDialog 35 | 36 | Options: 37 | require-passing-tests-on: [ 'Linux', 'FreeBSD'] 38 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | set(CMAKE_C_STANDARD 99) 4 | 5 | set(PROJECT_VERSION "6.4.80") 6 | 7 | project(xdg-desktop-portal-kde VERSION ${PROJECT_VERSION}) 8 | 9 | set(PROJECT_DEP_VERSION "6.3.90") 10 | set(QT_MIN_VERSION "6.8.0") 11 | set(KF6_MIN_VERSION "6.14.0") 12 | set(KDE_COMPILERSETTINGS_LEVEL "6.10.0") 13 | set(CMAKE_CXX_STANDARD 20) 14 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 15 | 16 | ################# set KDE specific information ################# 17 | 18 | find_package(ECM ${KF6_MIN_VERSION} REQUIRED NO_MODULE) 19 | set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 20 | 21 | include(KDEInstallDirs) 22 | include(KDECMakeSettings) 23 | include(KDECompilerSettings NO_POLICY_SCOPE) 24 | include(KDEClangFormat) 25 | include(KDEGitCommitHooks) 26 | include(ECMSetupVersion) 27 | include(ECMConfiguredInstall) 28 | include(ECMQtDeclareLoggingCategory) 29 | include(ECMAddTests) 30 | include(ECMFindQmlModule) 31 | include(ECMDeprecationSettings) 32 | include(FeatureSummary) 33 | 34 | find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS 35 | Core 36 | Concurrent 37 | DBus 38 | PrintSupport 39 | QuickWidgets 40 | Widgets 41 | WaylandClient 42 | Quick 43 | QuickControls2 44 | Qml 45 | Test 46 | ) 47 | find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS WaylandClient) 48 | 49 | if (Qt6_VERSION VERSION_GREATER_EQUAL "6.10.0") 50 | find_package(Qt6GuiPrivate ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) 51 | find_package(Qt6PrintSupportPrivate ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) 52 | endif() 53 | 54 | find_package(KF6 ${KF6_MIN_VERSION} REQUIRED 55 | CoreAddons 56 | Config 57 | I18n 58 | GuiAddons 59 | GlobalAccel 60 | KIO 61 | Kirigami 62 | Notifications 63 | Service 64 | WidgetsAddons 65 | WindowSystem 66 | IconThemes 67 | StatusNotifierItem 68 | Crash 69 | ) 70 | 71 | find_package(KWayland ${PROJECT_DEP_VERSION} REQUIRED) 72 | 73 | ecm_find_qmlmodule(org.kde.iconthemes 1.0) 74 | ecm_find_qmlmodule(org.kde.plasma.workspace.dialogs 1.0) 75 | find_package(Wayland 1.15 REQUIRED COMPONENTS Client) 76 | find_package(PlasmaWaylandProtocols 1.7.0 REQUIRED) 77 | 78 | find_package(WaylandProtocols 1.25) 79 | set_package_properties(WaylandProtocols PROPERTIES 80 | TYPE REQUIRED 81 | PURPOSE "Collection of Wayland protocols that add functionality not available in the Wayland core protocol" 82 | URL "https://gitlab.freedesktop.org/wayland/wayland-protocols/" 83 | ) 84 | 85 | find_package(KIOFuse) 86 | set_package_properties(KIOFuse PROPERTIES 87 | URL https://commits.kde.org/system/kio-fuse 88 | TYPE RUNTIME 89 | PURPOSE "Automatic mounting of remote URLs") 90 | 91 | pkg_check_modules(XKB IMPORTED_TARGET xkbcommon REQUIRED) 92 | add_feature_info(XKB XKB_FOUND "Required for converting keysyms into keycodes") 93 | 94 | ecm_setup_version(${PROJECT_VERSION} 95 | VARIABLE_PREFIX XDPK 96 | VERSION_HEADER ${CMAKE_BINARY_DIR}/version.h) 97 | 98 | ecm_set_disabled_deprecation_versions(QT 5.15.0 99 | KF 6.4.0 100 | ) 101 | 102 | add_subdirectory(data) 103 | add_subdirectory(src) 104 | add_subdirectory(autotests) 105 | 106 | # add clang-format target for all our real source files 107 | file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h) 108 | kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) 109 | kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT) 110 | 111 | ki18n_install(po) 112 | 113 | feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) 114 | -------------------------------------------------------------------------------- /LICENSES/BSD-2-Clause.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /LICENSES/LicenseRef-KDE-Accepted-LGPL.txt: -------------------------------------------------------------------------------- 1 | This library is free software; you can redistribute it and/or 2 | modify it under the terms of the GNU Lesser General Public 3 | License as published by the Free Software Foundation; either 4 | version 3 of the license or (at your option) any later version 5 | that is accepted by the membership of KDE e.V. (or its successor 6 | approved by the membership of KDE e.V.), which shall act as a 7 | proxy as defined in Section 6 of version 3 of the license. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /Messages.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | $EXTRACTRC `find . -name "*.ui" -o -name "*.rc"` >> rc.cpp 3 | $XGETTEXT `find . -name "*.cpp" -o -name "*.qml"` -o $podir/xdg-desktop-portal-kde.pot 4 | rm -f rc.cpp 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xdg-desktop-portal-kde 2 | 3 | A backend implementation for [xdg-desktop-portal](http://github.com/flatpak/xdg-desktop-portal) 4 | that is using Qt/KF5. 5 | 6 | ## Building xdg-desktop-portal-kde 7 | 8 | ### Dependencies: 9 | - xdg-desktop-portal (runtime dependency) 10 | - Qt 5 (build dependency) 11 | - KDE Frameworks - KCoreAddons, KI18n, KNotifications(build dependency) 12 | 13 | ### Build instructions: 14 | ``` 15 | $ mkdir build && cd build 16 | $ cmake .. [your_options] 17 | $ make -j5 18 | $ make install 19 | ``` 20 | 21 | ## Testing xdg-desktop-portal-kde 22 | 23 | The repository [xdg-portal-test-kde](https://invent.kde.org/libraries/xdg-portal-test-kde) contains a simple test application. 24 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | ############################# 2 | # Missing portals # 3 | ############################# 4 | 5 | * Background portal 6 | - a portal for autostart of applications, or to notify users that an application 7 | was started in background 8 | 9 | ############################# 10 | # Missing functions # 11 | ############################# 12 | 13 | * Inhibit portal 14 | - CreateMonitor 15 | - creates a monitoring session. While this session is active, the caller will 16 | receive StateChanged signals with updates on the session state. 17 | - StateChanged 18 | - the StateChanged signal is sent to active monitoring sessions when the session 19 | state changes. 20 | - QueryEndResponse 21 | - acknowledges that the caller received the #org.freedesktop.impl.portal.Inhibit::StateChanged 22 | signal. This method should be called within one second or receiving a StateChanged 23 | signal with the 'Query End' state. 24 | 25 | * Remote desktop portal 26 | - NotifyKeyboardKeysym 27 | - NotifyKeyboardKeycode 28 | - NotifyTouchDown 29 | - NotifyTouchMotion 30 | - NotifyTouchUp 31 | 32 | * Screensharing portal 33 | - AvailableCursorModes 34 | - CreateSession - cursor_mode option 35 | - Window source type - ability to share windows only 36 | -------------------------------------------------------------------------------- /autotests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ecm_add_tests( 2 | xdgshortcuttest.cpp 3 | 4 | LINK_LIBRARIES Qt::Test Qt::Gui 5 | ) 6 | target_sources(xdgshortcuttest PRIVATE ${CMAKE_SOURCE_DIR}/src/xdgshortcut.cpp ${CMAKE_SOURCE_DIR}/src/xdgshortcut.h) 7 | target_link_libraries(xdgshortcuttest Qt::GuiPrivate) 8 | 9 | add_test( 10 | NAME colorschemetest 11 | COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/colorschemetest.py 12 | ) 13 | set_tests_properties(colorschemetest PROPERTIES ENVIRONMENT "CMAKE_RUNTIME_OUTPUT_DIRECTORY=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") 14 | 15 | ecm_add_test(inputcapturebarriertest.cpp ${CMAKE_SOURCE_DIR}/src/inputcapturebarrier.cpp TEST_NAME inputcapturebarriertest LINK_LIBRARIES Qt::Test Qt::Gui) 16 | -------------------------------------------------------------------------------- /autotests/colorschemetest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | SPDX-FileCopyrightText: 2023 Fushan Wen 4 | SPDX-License-Identifier: MIT 5 | """ 6 | 7 | # For FreeBSD CI which only has python 3.9 8 | from __future__ import annotations 9 | 10 | import logging 11 | import os 12 | import subprocess 13 | import sys 14 | import time 15 | import unittest 16 | from typing import Final 17 | 18 | from gi.repository import Gio, GLib 19 | 20 | CMAKE_RUNTIME_OUTPUT_DIRECTORY: Final = os.environ.get("CMAKE_RUNTIME_OUTPUT_DIRECTORY", "/usr/libexec") 21 | PORTAL_SERVICE_NAME: Final = "org.freedesktop.impl.portal.desktop.kde" 22 | GROUP: Final = "org.freedesktop.appearance" 23 | KEYS: Final = ("accent-color", "color-scheme") 24 | EXPECTED_SIGNATURES: Final = ("(ddd)", "u") 25 | 26 | 27 | def name_has_owner(session_bus: Gio.DBusConnection, name: str) -> bool: 28 | """ 29 | Whether the given name is available on session bus 30 | """ 31 | message: Gio.DBusMessage = Gio.DBusMessage.new_method_call("org.freedesktop.DBus", "/", "org.freedesktop.DBus", "NameHasOwner") 32 | message.set_body(GLib.Variant("(s)", [name])) 33 | reply, _ = session_bus.send_message_with_reply_sync(message, Gio.DBusSendMessageFlags.NONE, 1000) 34 | return reply and reply.get_signature() == 'b' and reply.get_body().get_child_value(0).get_boolean() 35 | 36 | 37 | class OrgFreeDesktopAppearanceTests(unittest.TestCase): 38 | """ 39 | Tests for org.freedesktop.appearance 40 | """ 41 | 42 | portal_process: subprocess.Popen | None = None 43 | 44 | @classmethod 45 | def setUpClass(cls) -> None: 46 | session_bus: Gio.DBusConnection = Gio.bus_get_sync(Gio.BusType.SESSION) 47 | portal_launched = name_has_owner(session_bus, PORTAL_SERVICE_NAME) 48 | if not portal_launched: 49 | debug_environ = os.environ.copy() 50 | debug_environ["QT_LOGGING_RULES"] = "*.debug=true" 51 | cls.portal_process = subprocess.Popen([os.path.join(CMAKE_RUNTIME_OUTPUT_DIRECTORY, "xdg-desktop-portal-kde")], env=debug_environ, stdout=sys.stderr, stderr=sys.stderr) 52 | for _ in range(10): 53 | if name_has_owner(session_bus, PORTAL_SERVICE_NAME): 54 | portal_launched = True 55 | break 56 | logging.info("waiting for portal to appear on the DBus session") 57 | time.sleep(1) 58 | assert portal_launched 59 | 60 | @classmethod 61 | def tearDownClass(cls) -> None: 62 | if cls.portal_process is not None: 63 | cls.portal_process.terminate() 64 | 65 | def test_bug476592_signature(self) -> None: 66 | """ 67 | Make sure the signature is correct 68 | """ 69 | session_bus: Gio.DBusConnection = Gio.bus_get_sync(Gio.BusType.SESSION) 70 | 71 | for key, expected_signature in zip(KEYS, EXPECTED_SIGNATURES): 72 | message: Gio.DBusMessage = Gio.DBusMessage.new_method_call(PORTAL_SERVICE_NAME, "/org/freedesktop/portal/desktop", "org.freedesktop.impl.portal.Settings", "Read") 73 | message.set_body(GLib.Variant("(ss)", [GROUP, key])) 74 | reply, _ = session_bus.send_message_with_reply_sync(message, Gio.DBusSendMessageFlags.NONE, 1000) 75 | self.assertIsNotNone(reply) 76 | self.assertEqual(reply.get_signature(), 'v', f"Wrong signature: {reply.get_signature()}") 77 | self.assertEqual(reply.get_body().get_child_value(0).get_variant().get_type_string(), expected_signature) 78 | 79 | 80 | if __name__ == '__main__': 81 | logging.getLogger().setLevel(logging.DEBUG) 82 | unittest.main() 83 | -------------------------------------------------------------------------------- /autotests/inputcapturebarriertest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 3 | SPDX-FileCopyrightText: 2024 David Redondo 4 | */ 5 | 6 | #include 7 | 8 | #include "../src/inputcapturebarrier.h" 9 | 10 | using Barrier = QPair; 11 | 12 | template 13 | bool operator==(const T &t, const std::variant &variant) 14 | { 15 | return std::holds_alternative(variant) && std::get(variant) == t; 16 | } 17 | 18 | template 19 | QDebug &operator<<(QDebug &debug, const std::variant &variant) 20 | { 21 | auto printer = [&debug](auto &&value) { 22 | return QTest::toString(value); 23 | }; 24 | return debug << "variant: " << std::visit(printer, variant) << " (index=" << variant.index() << ")"; 25 | } 26 | 27 | class InputCaptureBarrierTest : public QObject 28 | { 29 | Q_OBJECT 30 | private Q_SLOTS: 31 | void testValidBarriers(); 32 | void testValidBarriersSwapped(); 33 | void testDiagonalBarriers(); 34 | void testBarrierNotOnEdge(); 35 | void testBarrierTooShortOrLong(); 36 | void testMultipleScreens(); 37 | }; 38 | 39 | void InputCaptureBarrierTest::testValidBarriers() 40 | { 41 | const QList screens = {{0, 0, 1920, 1080}}; 42 | QCOMPARE(checkAndMakeBarrier(0, 0, 0, 1079, screens), Barrier({0, 0}, {0, 1079})); 43 | QCOMPARE(checkAndMakeBarrier(0, 0, 1919, 0, screens), Barrier({0, 0}, {1919, 0})); 44 | QCOMPARE(checkAndMakeBarrier(1920, 0, 1920, 1079, screens), Barrier({1919, 0}, {1919, 1079})); 45 | QCOMPARE(checkAndMakeBarrier(0, 1080, 1919, 1080, screens), Barrier({0, 1079}, {1919, 1079})); 46 | } 47 | 48 | void InputCaptureBarrierTest::testValidBarriersSwapped() 49 | { 50 | const QList screens = {{0, 0, 1920, 1080}}; 51 | QCOMPARE(checkAndMakeBarrier(0, 1079, 0, 0, screens), Barrier({0, 0}, {0, 1079})); 52 | QCOMPARE(checkAndMakeBarrier(1919, 0, 0, 0, screens), Barrier({0, 0}, {1919, 0})); 53 | QCOMPARE(checkAndMakeBarrier(1920, 1079, 1920, 0, screens), Barrier({1919, 0}, {1919, 1079})); 54 | QCOMPARE(checkAndMakeBarrier(1919, 1080, 0, 1080, screens), Barrier({0, 1079}, {1919, 1079})); 55 | } 56 | 57 | void InputCaptureBarrierTest::testDiagonalBarriers() 58 | { 59 | const QList screens = {{0, 0, 1920, 1080}}; 60 | QCOMPARE(checkAndMakeBarrier(0, 0, 1920, 1080, screens), BarrierFailureReason::Diagonal); 61 | QCOMPARE(checkAndMakeBarrier(1, 1, 2, 2, screens), BarrierFailureReason::Diagonal); 62 | } 63 | 64 | void InputCaptureBarrierTest::testBarrierNotOnEdge() 65 | { 66 | const QList screens = {{0, 0, 1920, 1080}}; 67 | QCOMPARE(checkAndMakeBarrier(1, 0, 1, 1079, screens), BarrierFailureReason::NotOnEdge); 68 | QCOMPARE(checkAndMakeBarrier(0, 1, 1919, 1, screens), BarrierFailureReason::NotOnEdge); 69 | 70 | QCOMPARE(checkAndMakeBarrier(-1, 0, -1, 1079, screens), BarrierFailureReason::NotOnEdge); 71 | QCOMPARE(checkAndMakeBarrier(0, -1, 1919, -1, screens), BarrierFailureReason::NotOnEdge); 72 | QCOMPARE(checkAndMakeBarrier(1921, 0, 1921, 1079, screens), BarrierFailureReason::NotOnEdge); 73 | QCOMPARE(checkAndMakeBarrier(0, 1081, 1919, 1081, screens), BarrierFailureReason::NotOnEdge); 74 | } 75 | 76 | void InputCaptureBarrierTest::testBarrierTooShortOrLong() 77 | { 78 | const QList screens = {{0, 0, 1920, 1080}}; 79 | QCOMPARE(checkAndMakeBarrier(0, 0, 0, 1078, screens), BarrierFailureReason::BetweenScreensOrDoesNotFill); 80 | QCOMPARE(checkAndMakeBarrier(0, 1, 0, 1079, screens), BarrierFailureReason::BetweenScreensOrDoesNotFill); 81 | QCOMPARE(checkAndMakeBarrier(0, 0, 1918, 0, screens), BarrierFailureReason::BetweenScreensOrDoesNotFill); 82 | QCOMPARE(checkAndMakeBarrier(1, 0, 1919, 0, screens), BarrierFailureReason::BetweenScreensOrDoesNotFill); 83 | } 84 | 85 | void InputCaptureBarrierTest::testMultipleScreens() 86 | { 87 | const QList screens = {{0, 0, 1920, 1080}, {1920, 0, 1280, 1024}}; 88 | 89 | QCOMPARE(checkAndMakeBarrier(0, 0, 0, 1079, screens), Barrier({0, 0}, {0, 1079})); 90 | QCOMPARE(checkAndMakeBarrier(0, 0, 1919, 0, screens), Barrier({0, 0}, {1919, 0})); 91 | QCOMPARE(checkAndMakeBarrier(0, 1080, 1919, 1080, screens), Barrier({0, 1079}, {1919, 1079})); 92 | 93 | QCOMPARE(checkAndMakeBarrier(3200, 0, 3200, 1023, screens), Barrier({3199, 0}, {3199, 1023})); 94 | QCOMPARE(checkAndMakeBarrier(1920, 0, 3199, 0, screens), Barrier({1920, 0}, {3199, 0})); 95 | QCOMPARE(checkAndMakeBarrier(1920, 1024, 3199, 1024, screens), Barrier({1920, 1023}, {3199, 1023})); 96 | 97 | // Barriers between screens are not allowed 98 | QCOMPARE(checkAndMakeBarrier(1920, 0, 1920, 1079, screens), BarrierFailureReason::BetweenScreensOrDoesNotFill); 99 | QCOMPARE(checkAndMakeBarrier(1920, 0, 1920, 1023, screens), BarrierFailureReason::BetweenScreensOrDoesNotFill); 100 | 101 | // Barriers cant span multiple screens 102 | QCOMPARE(checkAndMakeBarrier(0, 0, 3840, 0, screens), BarrierFailureReason::BetweenScreensOrDoesNotFill); 103 | QCOMPARE(checkAndMakeBarrier(0, 1080, 3199, 1080, screens), BarrierFailureReason::BetweenScreensOrDoesNotFill); 104 | QCOMPARE(checkAndMakeBarrier(0, 1024, 3199, 1024, screens), BarrierFailureReason::BetweenScreensOrDoesNotFill); 105 | } 106 | 107 | QTEST_GUILESS_MAIN(InputCaptureBarrierTest) 108 | 109 | #include "inputcapturebarriertest.moc" 110 | -------------------------------------------------------------------------------- /autotests/xdgshortcuttest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0-or-later 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include "../src/xdgshortcut.h" 11 | 12 | class XdgShortcutTest : public QObject 13 | { 14 | Q_OBJECT 15 | public: 16 | XdgShortcutTest(QObject *parent = nullptr) 17 | : QObject(parent) 18 | { 19 | QStandardPaths::setTestModeEnabled(true); 20 | } 21 | 22 | private Q_SLOTS: 23 | void initTestCase() 24 | { 25 | } 26 | 27 | void testCheckShortcut_data() 28 | { 29 | QTest::addColumn("expression"); 30 | QTest::addColumn("result"); 31 | 32 | QTest::newRow("a") << "a" << QKeySequence(Qt::Key_A); 33 | QTest::newRow("ctrla") << "CTRL+a" << QKeySequence(Qt::Key_A | Qt::ControlModifier); 34 | QTest::newRow("ctrlshifta") << "CTRL+SHIFT+a" << QKeySequence(Qt::Key_A | Qt::ControlModifier | Qt::ShiftModifier); 35 | QTest::newRow("ctrlaltreturn") << "CTRL+ALT+Return" << QKeySequence(Qt::Key_Return | Qt::ControlModifier | Qt::AltModifier); 36 | QTest::newRow("withweirdtoken") << "CTRL+a;Banana" << QKeySequence(Qt::Key_A | Qt::ControlModifier); 37 | QTest::newRow("justcontrol") << "Control_L" << QKeySequence(Qt::Key_Control); 38 | } 39 | 40 | void testCheckShortcut() 41 | { 42 | QFETCH(QString, expression); 43 | QFETCH(QKeySequence, result); 44 | 45 | const auto shortcut = XdgShortcut::parse(expression); 46 | QVERIFY(shortcut.has_value()); 47 | QCOMPARE(*shortcut, result); 48 | } 49 | }; 50 | 51 | QTEST_GUILESS_MAIN(XdgShortcutTest) 52 | 53 | #include "xdgshortcuttest.moc" 54 | -------------------------------------------------------------------------------- /cmake/FindKIOFuse.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-2-Clause 2 | # SPDX-FileCopyrightText: 2022 Harald Sitter 3 | 4 | execute_process( 5 | COMMAND dbus-send --session --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListActivatableNames 6 | OUTPUT_VARIABLE _kiofuseOut) 7 | 8 | set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE) 9 | if("${_kiofuseOut}" MATCHES "\"org.kde.KIOFuse\"") 10 | set(${CMAKE_FIND_PACKAGE_NAME}_FOUND TRUE) 11 | endif() 12 | 13 | include(FindPackageHandleStandardArgs) 14 | find_package_handle_standard_args(${CMAKE_FIND_PACKAGE_NAME} 15 | FOUND_VAR ${CMAKE_FIND_PACKAGE_NAME}_FOUND 16 | REQUIRED_VARS ${CMAKE_FIND_PACKAGE_NAME}_FOUND 17 | REASON_FAILURE_MESSAGE "Could not find DBus service org.kde.KIOFuse in org.freedesktop.DBus.ListActivatableNames" 18 | ) 19 | -------------------------------------------------------------------------------- /data/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | configure_file(org.freedesktop.impl.portal.desktop.kde.desktop.in org.freedesktop.impl.portal.desktop.kde.desktop @ONLY) 2 | configure_file(org.freedesktop.impl.portal.desktop.kde.cmake.in org.freedesktop.impl.portal.desktop.kde.service @ONLY) 3 | 4 | install(FILES kde.portal DESTINATION ${KDE_INSTALL_DATADIR}/xdg-desktop-portal/portals) 5 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.impl.portal.desktop.kde.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR}) 6 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.impl.portal.desktop.kde.desktop DESTINATION ${KDE_INSTALL_APPDIR}) 7 | 8 | ecm_install_configured_files(INPUT plasma-xdg-desktop-portal-kde.service.in @ONLY 9 | DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR}) 10 | -------------------------------------------------------------------------------- /data/kde.portal: -------------------------------------------------------------------------------- 1 | [portal] 2 | DBusName=org.freedesktop.impl.portal.desktop.kde 3 | Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.Account;org.freedesktop.impl.portal.AppChooser;org.freedesktop.impl.portal.Background;org.freedesktop.impl.portal.Email;org.freedesktop.impl.portal.FileChooser;org.freedesktop.impl.portal.Inhibit;org.freedesktop.impl.portal.Notification;org.freedesktop.impl.portal.Print;org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.RemoteDesktop;org.freedesktop.impl.portal.Settings;org.freedesktop.impl.portal.DynamicLauncher;org.freedesktop.impl.portal.GlobalShortcuts;org.freedesktop.impl.portal.InputCapture;org.freedesktop.impl.portal.Clipboard 4 | UseIn=KDE 5 | -------------------------------------------------------------------------------- /data/org.freedesktop.Accounts.User.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | The username of the user. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | The users real name. 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | The filename of a png file containing the users icon. 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | Emitted when the user is changed. 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /data/org.freedesktop.Application.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /data/org.freedesktop.impl.portal.desktop.kde.cmake.in: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=org.freedesktop.impl.portal.desktop.kde 3 | Exec=@CMAKE_INSTALL_FULL_LIBEXECDIR@/xdg-desktop-portal-kde 4 | SystemdService=plasma-xdg-desktop-portal-kde.service 5 | -------------------------------------------------------------------------------- /data/org.freedesktop.impl.portal.desktop.kde.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=Portal 4 | Name[ar]=البوابة 5 | Name[ast]=Portal 6 | Name[az]=Portal 7 | Name[bg]=Портал 8 | Name[ca]=Portal 9 | Name[ca@valencia]=Portal 10 | Name[cs]=Portál 11 | Name[da]=Portal 12 | Name[de]=Portal 13 | Name[el]=Πύλη 14 | Name[en_GB]=Portal 15 | Name[eo]=Portalo 16 | Name[es]=Portal 17 | Name[eu]=Ataria 18 | Name[fi]=Portaali 19 | Name[fr]=Portail 20 | Name[gl]=Portal 21 | Name[he]=שער גישה 22 | Name[hi]=पोर्टल 23 | Name[hu]=Portál 24 | Name[ia]=Portal 25 | Name[id]=Portal 26 | Name[it]=Portale 27 | Name[ka]=Portal 28 | Name[ko]=포털 29 | Name[lt]=Portalas 30 | Name[lv]=Portal 31 | Name[nb]=Portal 32 | Name[nl]=Portaal 33 | Name[nn]=Portal 34 | Name[pa]=ਪੋਰਟਲ 35 | Name[pl]=Portal 36 | Name[pt]=Portal 37 | Name[pt_BR]=Portal 38 | Name[ro]=Portal 39 | Name[ru]=Портал 40 | Name[sa]=पोर्टल 41 | Name[sk]=Portal 42 | Name[sl]=Portal 43 | Name[sv]=Portal 44 | Name[tr]=Portal 45 | Name[uk]=Портал 46 | Name[x-test]=xxPortalxx 47 | Name[zh_CN]=系统门户 48 | Name[zh_TW]=Portal 49 | Exec=@CMAKE_INSTALL_FULL_LIBEXECDIR@/xdg-desktop-portal-kde 50 | X-KDE-Wayland-Interfaces=org_kde_kwin_fake_input,org_kde_plasma_window_management,zkde_screencast_unstable_v1 51 | X-KDE-DBUS-Restricted-Interfaces=org.kde.KWin.ScreenShot2 52 | NoDisplay=true 53 | Icon=kde 54 | -------------------------------------------------------------------------------- /data/org.freedesktop.portal.Documents.xml: -------------------------------------------------------------------------------- 1 | 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /data/org.kde.KIOFuse.VFS.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /data/plasma-xdg-desktop-portal-kde.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Xdg Desktop Portal For KDE 3 | PartOf=graphical-session.target 4 | After=plasma-core.target 5 | 6 | [Service] 7 | ExecStart=@CMAKE_INSTALL_FULL_LIBEXECDIR@/xdg-desktop-portal-kde 8 | BusName=org.freedesktop.impl.portal.desktop.kde 9 | Slice=session.slice 10 | # Auto restart is off as we are DBus activated 11 | Restart=no 12 | -------------------------------------------------------------------------------- /src/AccessDialog.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez 3 | */ 4 | 5 | import QtQuick 6 | import QtQuick.Controls as QQC2 7 | import QtQuick.Layouts 8 | import org.kde.kirigami as Kirigami 9 | import org.kde.plasma.workspace.dialogs as PWD 10 | 11 | PWD.SystemDialog { 12 | id: root 13 | 14 | property alias body: bodyLabel.text 15 | property string acceptLabel 16 | property string rejectLabel 17 | property var choices 18 | property var selectedChoices: new Object() 19 | 20 | QQC2.Label { 21 | id: bodyLabel 22 | Layout.fillWidth: true 23 | wrapMode: Text.WordWrap 24 | } 25 | 26 | Kirigami.FormLayout { 27 | Layout.fillWidth: true 28 | Repeater { 29 | model: root.choices 30 | delegate: Loader { 31 | Kirigami.FormData.label: modelData.label 32 | sourceComponent: modelData.choices.length == 0 ? checkBox : comboBox 33 | Component { 34 | id: checkBox 35 | QQC2.CheckBox { 36 | Kirigami.FormData.label: modelData.label 37 | checked: modelData.initialChoiceId === "true" 38 | onToggled: { 39 | root.selectedChoices[modelData.id] = checked ? "true" : "false" 40 | } 41 | } 42 | } 43 | Component { 44 | id: comboBox 45 | QQC2.ComboBox { 46 | model: modelData.choices 47 | textRole: "value" 48 | valueRole: "id" 49 | onActivated: root.selectedChoices[modelData.id] = currentValue 50 | Component.onCompleted: currentIndex = indexOfValue(modelData.initialChoiceId) 51 | } 52 | } 53 | Component.onCompleted: root.selectedChoices[modelData.id] = modelData.initialChoiceId 54 | } 55 | } 56 | } 57 | 58 | 59 | 60 | standardButtons: QQC2.DialogButtonBox.Ok | QQC2.DialogButtonBox.Cancel 61 | 62 | Component.onCompleted: { 63 | if (root.acceptLabel.length > 0) { 64 | dialogButtonBox.button(QQC2.DialogButtonBox.Ok).text = Qt.binding(() => root.acceptLabel); 65 | } 66 | if (root.rejectLabel.length > 0) { 67 | dialogButtonBox.button(QQC2.DialogButtonBox.Cancel).text = Qt.binding(() => root.rejectLabel); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/DynamicLauncherDialog.qml: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 2 | // SPDX-FileCopyrightText: 2022 Harald Sitter 3 | 4 | import QtQuick 5 | import QtQuick.Layouts 6 | import QtQuick.Controls as QQC2 7 | import org.kde.kirigami as Kirigami 8 | import org.kde.plasma.workspace.dialogs as PWD 9 | import org.kde.iconthemes as KIconThemes 10 | 11 | PWD.SystemDialog { 12 | id: root 13 | 14 | property var dialog 15 | property string appID 16 | property url launcherURL: "" 17 | property bool edit: false 18 | 19 | readonly property Component displayComponent: Component { 20 | ColumnLayout { 21 | Kirigami.Icon { 22 | id: icon 23 | Layout.alignment: Qt.AlignHCenter 24 | implicitWidth: Kirigami.Units.iconSizes.enormous 25 | implicitHeight: implicitWidth 26 | source: dialog.icon 27 | } 28 | 29 | Kirigami.Heading { 30 | Layout.alignment: Qt.AlignHCenter 31 | Layout.fillWidth: true 32 | level: 3 33 | wrapMode: Text.Wrap 34 | text: dialog.name 35 | verticalAlignment: Qt.AlignTop 36 | } 37 | 38 | Kirigami.LinkButton { 39 | Layout.fillWidth: true 40 | visible: text.length > 0 41 | Layout.alignment: Qt.AlignHCenter 42 | elide: Text.ElideMiddle 43 | text: launcherURL 44 | onClicked: Qt.openUrlExternally(launcherURL) 45 | } 46 | } 47 | } 48 | 49 | readonly property Component editComponent: Component { 50 | ColumnLayout { 51 | QQC2.Button { 52 | Layout.alignment: Qt.AlignHCenter 53 | contentItem: Kirigami.Icon { 54 | id: icon 55 | implicitHeight: implicitWidth 56 | implicitWidth: Kirigami.Units.iconSizes.enormous 57 | source: dialog.icon 58 | 59 | KIconThemes.IconDialog { 60 | id: iconDialog 61 | onIconNameChanged: dialog.icon = iconName 62 | } 63 | 64 | TapHandler { 65 | onTapped: iconDialog.open() 66 | } 67 | } 68 | } 69 | 70 | QQC2.Label { 71 | text: i18nc("@label name of a launcher/application", "Name") 72 | Layout.fillWidth: true 73 | } 74 | QQC2.TextField { 75 | verticalAlignment: Qt.AlignTop 76 | Layout.alignment: Qt.AlignHCenter 77 | Layout.fillWidth: true 78 | onTextChanged: dialog.name = text 79 | Component.onCompleted: text = dialog.name 80 | } 81 | } 82 | } 83 | 84 | Loader { 85 | sourceComponent: root.edit ? editComponent : displayComponent 86 | } 87 | 88 | actions: [ 89 | Kirigami.Action { 90 | text: i18nc("@action edit launcher name/icon", "Edit Info…") 91 | icon.name: "document-edit" 92 | onCheckedChanged: root.edit = checked 93 | checkable: true 94 | }, 95 | Kirigami.Action { 96 | text: i18nc("@action accept dialog and create launcher", "Accept") 97 | icon.name: "dialog-ok" 98 | onTriggered: accept() 99 | }, 100 | Kirigami.Action { 101 | text: i18nc("@action", "Cancel") 102 | icon.name: "dialog-cancel" 103 | onTriggered: reject() 104 | } 105 | ] 106 | } 107 | -------------------------------------------------------------------------------- /src/GlobalShortcutsDialog.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 3 | SPDX-FileCopyrightText: 2025 David Redondo 4 | */ 5 | 6 | 7 | import QtQuick 8 | import QtQuick.Layouts 9 | import QtQuick.Controls as QQC2 10 | import org.kde.kquickcontrols as KQC 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.plasma.workspace.dialogs as PWD 13 | import org.kde.kcmutils as KCMUtils 14 | 15 | PWD.SystemDialog { 16 | 17 | id: root 18 | 19 | required property string app 20 | required property string component 21 | required property var newShortcuts 22 | required property var returningShortcuts 23 | 24 | iconName: "preferences-desktop-keyboard-shortcut" 25 | title: i18nc("@title:window", "Global Shortcuts Requested") 26 | subtitle: { 27 | const count = newShortcuts.rowCount() 28 | if (app === "") { 29 | return i18ncp("The application is unknown", "An application wants to register the following shortcut:", "An application wants to register the following %1 shortcuts:", count) 30 | } 31 | return i18ncp("%2 is the name of the application", "%2 wants to register the following shortcut:", "%2 wants to register the following %1 shortcuts:", count, app) 32 | } 33 | 34 | standardButtons: QQC2.DialogButtonBox.Ok | QQC2.DialogButtonBox.Cancel 35 | ColumnLayout { 36 | id: content 37 | Binding { 38 | when: content.parent 39 | target: content.parent 40 | property: "leftPadding" 41 | value: 0 42 | } 43 | Binding { 44 | when: content.parent 45 | target: content.parent 46 | property: "rightPadding" 47 | value: 0 48 | } 49 | Binding { 50 | when: content.parent 51 | target: content.parent 52 | property: "bottomPadding" 53 | value: 0 54 | } 55 | 56 | spacing: 0 57 | 58 | Kirigami.Separator { 59 | Layout.fillWidth: true 60 | } 61 | 62 | QQC2.ScrollView { 63 | id: view 64 | Kirigami.Theme.colorSet: Kirigami.Theme.View 65 | clip: true 66 | Layout.fillWidth: true 67 | Layout.fillHeight: true 68 | // Let the implicitHeight be "5 items shown by default" 69 | implicitHeight: Math.min(list.currentItem.implicitHeight * 5, implicitContentHeight) 70 | ListView { 71 | id: list 72 | model: newShortcuts 73 | delegate: QQC2.ItemDelegate { 74 | id: delegate 75 | required property var model 76 | width: ListView.view.width 77 | hoverEnabled: false 78 | down: false 79 | contentItem: RowLayout { 80 | spacing: Kirigami.Units.smallSpacing 81 | Kirigami.TitleSubtitle { 82 | Layout.fillWidth: true 83 | title: model.display 84 | font: delegate.font 85 | } 86 | Kirigami.Icon { 87 | id: conflictIcon 88 | visible: model.globalConflict || model.standardConflict || model.internalConflict 89 | source: "data-warning" 90 | QQC2.ToolTip.text: model.conflictText ?? "" 91 | QQC2.ToolTip.visible: hoverHandler.hovered 92 | HoverHandler { 93 | id: hoverHandler 94 | } 95 | } 96 | KQC.KeySequenceItem { 97 | id: keySequenceItem 98 | Layout.alignment: Qt.AlignRight 99 | showCancelButton: true 100 | keySequence: model.keySequence 101 | onKeySequenceModified: { 102 | model.keySequence = keySequence 103 | } 104 | } 105 | } 106 | } 107 | } 108 | background: Rectangle { 109 | color: Kirigami.Theme.backgroundColor 110 | } 111 | } 112 | Kirigami.Separator { 113 | Layout.fillWidth: true 114 | } 115 | QQC2.Button { 116 | parent: root.dialogButtonBox 117 | QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.HelpRole 118 | visible: returningShortcuts.length != 0 119 | icon.name: "systemsettings" 120 | text: i18nc("@action:button", "See Other Shortcuts…") 121 | QQC2.ToolTip.text: i18nc("@info:tooltip", "View other shortcuts registered by this application") 122 | QQC2.ToolTip.visible: hovered 123 | onClicked: KCMUtils.KCMLauncher.openSystemSettings("kcm_keys", component) 124 | } 125 | Binding { 126 | delayed: true 127 | target: root.dialogButtonBox.standardButton(QQC2.DialogButtonBox.Ok) 128 | value: !(newShortcuts.hasGlobalConflict || newShortcuts.hasInternalConflict) 129 | property: "enabled" 130 | } 131 | } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /src/InputCaptureDialog.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 3 | SPDX-FileCopyrightText: 2024 David Redondo 4 | */ 5 | 6 | 7 | import QtQuick 8 | import QtQuick.Controls as QQC2 9 | import org.kde.plasma.workspace.dialogs as PWD 10 | 11 | PWD.SystemDialog { 12 | 13 | readonly property string app: "" 14 | 15 | iconName: "dialog-input-devices" 16 | title: i18nc("@title:window", "Input Capture Requested") 17 | subtitle: app === "" ? i18nc("The application is unknown", "An application requested to capture input events") : i18nc("%1 is the name of the application", "%1 requested to capture input events", app) 18 | 19 | standardButtons: QQC2.DialogButtonBox.Ok | QQC2.DialogButtonBox.Cancel 20 | 21 | Component.onCompleted: dialogButtonBox.standardButton(QQC2.DialogButtonBox.Ok).text = i18nc("@action:button", "Allow") 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/PipeWireDelegate.qml: -------------------------------------------------------------------------------- 1 | /* This file is part of the KDE project 2 | SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez 3 | 4 | SPDX-License-Identifier: LGPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Layouts 9 | import org.kde.kirigami as Kirigami 10 | import org.kde.pipewire as PipeWire 11 | 12 | Kirigami.Card { 13 | id: card 14 | 15 | property alias nodeId: pipeWireSourceItem.nodeId 16 | 17 | showClickFeedback: true 18 | 19 | contentItem: PipeWire.PipeWireSourceItem { 20 | id: pipeWireSourceItem 21 | Layout.preferredHeight: Kirigami.Units.gridUnit * 7 22 | Layout.preferredWidth: Kirigami.Units.gridUnit * 7 23 | Layout.fillWidth: true 24 | Layout.fillHeight: true 25 | 26 | Kirigami.Icon { 27 | anchors.fill: parent 28 | visible: pipeWireSourceItem.nodeId === 0 29 | source: card.banner.titleIcon 30 | } 31 | } 32 | 33 | Layout.preferredHeight: contentItem.Layout.preferredHeight 34 | } 35 | -------------------------------------------------------------------------------- /src/RemoteDesktopDialog.qml: -------------------------------------------------------------------------------- 1 | /* This file is part of the KDE project 2 | SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez 3 | 4 | SPDX-License-Identifier: LGPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Layouts 9 | import QtQuick.Controls as QQC2 10 | import org.kde.plasma.workspace.dialogs as PWD 11 | 12 | PWD.SystemDialog { 13 | id: root 14 | 15 | property alias description: desc.text 16 | property alias allowRestore: allowRestoreItem.checked 17 | property alias persistenceRequested: allowRestoreItem.visible 18 | 19 | iconName: "krfb" 20 | 21 | ColumnLayout { 22 | QQC2.Label { 23 | id: desc 24 | textFormat: Text.MarkdownText 25 | Layout.fillHeight: true 26 | } 27 | QQC2.CheckBox { 28 | id: allowRestoreItem 29 | checked: true 30 | text: i18n("Allow restoring on future sessions") 31 | } 32 | } 33 | 34 | standardButtons: QQC2.DialogButtonBox.Ok | QQC2.DialogButtonBox.Cancel 35 | 36 | Component.onCompleted: { 37 | dialogButtonBox.standardButton(QQC2.DialogButtonBox.Ok).text = i18n("Share") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/ScreenshotDialog.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Layouts 9 | import QtQuick.Controls as QQC2 10 | import org.kde.kirigami as Kirigami 11 | import org.kde.plasma.workspace.dialogs as PWD 12 | 13 | PWD.SystemDialog { 14 | id: root 15 | 16 | property alias screenshotType: areaCombo.currentIndex 17 | property alias screenshotTypesModel: areaCombo.model 18 | property alias screenshotImage: screenshot.source 19 | property alias withCursor: hasCursor.checked 20 | property alias withBorders: hasWindowBorders.checked 21 | property QtObject app 22 | 23 | title: i18n("Request Screenshot") 24 | iconName: "preferences-system-windows-effect-screenshot" 25 | acceptable: screenshot.valid 26 | 27 | Kirigami.FormLayout { 28 | Kirigami.Heading { 29 | text: i18n("Capture Mode") 30 | } 31 | QQC2.ComboBox { 32 | id: areaCombo 33 | Kirigami.FormData.label: i18n("Area:") 34 | textRole: "display" 35 | } 36 | QQC2.SpinBox { 37 | id: delayTime 38 | Kirigami.FormData.label: i18n("Delay:") 39 | from: 0 40 | to: 60 41 | stepSize: 1 42 | textFromValue: (value, locale) => i18np("%1 second", "%1 seconds", value) 43 | valueFromText: (text, locale) => parseInt(text); 44 | } 45 | 46 | Kirigami.Heading { 47 | text: i18n("Content Options") 48 | } 49 | QQC2.CheckBox { 50 | id: hasCursor 51 | text: i18n("Include cursor pointer") 52 | checked: true 53 | } 54 | QQC2.CheckBox { 55 | id: hasWindowBorders 56 | text: i18n("Include window borders") 57 | enabled: areaCombo.currentIndex === 2 58 | checked: true 59 | } 60 | 61 | Kirigami.Icon { 62 | id: screenshot 63 | Layout.fillWidth: true 64 | Layout.fillHeight: true 65 | } 66 | } 67 | 68 | standardButtons: QQC2.DialogButtonBox.Ok | QQC2.DialogButtonBox.Cancel 69 | 70 | Component.onCompleted: { 71 | dialogButtonBox.standardButton(QQC2.DialogButtonBox.Ok).text = i18n("Save") 72 | } 73 | 74 | actions: [ 75 | QQC2.Action { 76 | readonly property Timer takeTimer: Timer { 77 | repeat: false 78 | interval: delayTime.value 79 | onTriggered: root.app.takeScreenshotInteractive() 80 | } 81 | text: i18n("Take") 82 | enabled: !takeTimer.running 83 | onTriggered: takeTimer.restart() 84 | } 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /src/UserInfoDialog.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Red Hat Inc 3 | * SPDX-License-Identifier: LGPL-2.0-or-later 4 | * 5 | * SPDX-FileCopyrightText: 2020 Jan Grulich 6 | * SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez 7 | */ 8 | 9 | import QtQuick 10 | import QtQuick.Layouts 11 | import QtQuick.Controls as QQC2 12 | import org.kde.kirigami as Kirigami 13 | import org.kde.kirigamiaddons.components as KirigamiComponents 14 | import org.kde.plasma.workspace.dialogs as PWD 15 | 16 | PWD.SystemDialog { 17 | id: root 18 | 19 | property alias name: nameText.text 20 | property alias details: detailsText.text 21 | property alias avatar: avatar.source 22 | 23 | ColumnLayout { 24 | spacing: Kirigami.Units.largeSpacing 25 | 26 | KirigamiComponents.Avatar { 27 | id: avatar 28 | 29 | readonly property int size: 4 * Kirigami.Units.gridUnit 30 | 31 | Layout.preferredWidth: size 32 | Layout.preferredHeight: size 33 | Layout.alignment: Qt.AlignBottom | Qt.AlignHCenter 34 | } 35 | 36 | Kirigami.Heading { 37 | id: nameText 38 | 39 | horizontalAlignment: Text.AlignHCenter 40 | verticalAlignment: Text.AlignTop 41 | 42 | Layout.alignment: Qt.AlignTop | Qt.AlignHCenter 43 | Layout.fillWidth: true 44 | } 45 | 46 | QQC2.Label { 47 | id: detailsText 48 | 49 | wrapMode: Text.WordWrap 50 | 51 | Layout.alignment: Qt.AlignBottom 52 | Layout.fillWidth: true 53 | } 54 | } 55 | 56 | standardButtons: QQC2.DialogButtonBox.Ok | QQC2.DialogButtonBox.Cancel 57 | 58 | Component.onCompleted: { 59 | dialogButtonBox.standardButton(QQC2.DialogButtonBox.Ok).text = i18nc("@action:button", "Share") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/access.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2017 Red Hat Inc 3 | * SPDX-License-Identifier: LGPL-2.0-or-later 4 | * 5 | * SPDX-FileCopyrightText: 2017 Jan Grulich 6 | * SPDX-FileCopyrightText: 2022 Harald Sitter 7 | */ 8 | 9 | #include "access.h" 10 | #include "access_debug.h" 11 | #include "accessdialog.h" 12 | #include "dbushelpers.h" 13 | #include "request.h" 14 | #include "utils.h" 15 | 16 | #include 17 | #include 18 | 19 | using namespace Qt::StringLiterals; 20 | 21 | AccessPortal::AccessPortal(QObject *parent) 22 | : QDBusAbstractAdaptor(parent) 23 | { 24 | } 25 | 26 | void AccessPortal::AccessDialog(const QDBusObjectPath &handle, 27 | const QString &app_id, 28 | const QString &parent_window, 29 | const QString &title, 30 | const QString &subtitle, 31 | const QString &body, 32 | const QVariantMap &options, 33 | const QDBusMessage &message, 34 | [[maybe_unused]] uint &replyResponse, 35 | [[maybe_unused]] QVariantMap &replyResults) 36 | { 37 | qCDebug(XdgDesktopPortalKdeAccess) << "AccessDialog called with parameters:"; 38 | qCDebug(XdgDesktopPortalKdeAccess) << " handle: " << handle.path(); 39 | qCDebug(XdgDesktopPortalKdeAccess) << " app_id: " << app_id; 40 | qCDebug(XdgDesktopPortalKdeAccess) << " parent_window: " << parent_window; 41 | qCDebug(XdgDesktopPortalKdeAccess) << " title: " << title; 42 | qCDebug(XdgDesktopPortalKdeAccess) << " subtitle: " << subtitle; 43 | qCDebug(XdgDesktopPortalKdeAccess) << " body: " << body; 44 | qCDebug(XdgDesktopPortalKdeAccess) << " options: " << options; 45 | 46 | auto accessDialog = new ::AccessDialog(); 47 | accessDialog->setBody(body); 48 | accessDialog->setTitle(title); 49 | accessDialog->setSubtitle(subtitle); 50 | 51 | if (options.contains(QStringLiteral("deny_label"))) { 52 | accessDialog->setRejectLabel(options.value(QStringLiteral("deny_label")).toString()); 53 | } 54 | 55 | if (options.contains(QStringLiteral("grant_label"))) { 56 | accessDialog->setAcceptLabel(options.value(QStringLiteral("grant_label")).toString()); 57 | } 58 | 59 | if (options.contains(QStringLiteral("icon"))) { 60 | accessDialog->setIcon(options.value(QStringLiteral("icon")).toString()); 61 | } 62 | 63 | if (options.contains(u"choices"_s)) { 64 | accessDialog->setChoices(qdbus_cast(options.value(u"choices"_s))); 65 | } 66 | 67 | accessDialog->createDialog(); 68 | Utils::setParentWindow(accessDialog->windowHandle(), parent_window); 69 | Request::makeClosableDialogRequest(handle, accessDialog); 70 | accessDialog->windowHandle()->setModality(options.value(QStringLiteral("modal"), true).toBool() ? Qt::WindowModal : Qt::NonModal); 71 | 72 | delayReply(message, accessDialog, this, [accessDialog](DialogResult result) { 73 | auto choices = accessDialog->selectedChoices(); 74 | QVariantMap results{{u"choices"_s, QVariant::fromValue(choices)}}; 75 | return QVariantList{PortalResponse::fromDialogResult(result), results}; 76 | }); 77 | } 78 | -------------------------------------------------------------------------------- /src/access.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2017 Red Hat Inc 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0-or-later 5 | * 6 | * SPDX-FileCopyrightText: 2017 Jan Grulich 7 | */ 8 | 9 | #ifndef XDG_DESKTOP_PORTAL_KDE_ACCESS_H 10 | #define XDG_DESKTOP_PORTAL_KDE_ACCESS_H 11 | 12 | #include 13 | #include 14 | 15 | class QDBusMessage; 16 | 17 | class AccessPortal : public QDBusAbstractAdaptor 18 | { 19 | Q_OBJECT 20 | Q_CLASSINFO("D-Bus Interface", "org.freedesktop.impl.portal.Access") 21 | public: 22 | explicit AccessPortal(QObject *parent); 23 | 24 | public Q_SLOTS: 25 | void AccessDialog(const QDBusObjectPath &handle, 26 | const QString &app_id, 27 | const QString &parent_window, 28 | const QString &title, 29 | const QString &subtitle, 30 | const QString &body, 31 | const QVariantMap &options, 32 | const QDBusMessage &message, 33 | uint &replyResponse, 34 | QVariantMap &replyResults); 35 | }; 36 | 37 | #endif // XDG_DESKTOP_PORTAL_KDE_ACCESS_H 38 | -------------------------------------------------------------------------------- /src/accessdialog.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2017 Red Hat Inc 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0-or-later 5 | * 6 | * SPDX-FileCopyrightText: 2017 Jan Grulich 7 | */ 8 | 9 | #include "accessdialog.h" 10 | #include "accessdialog_debug.h" 11 | #include "utils.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace Qt::StringLiterals; 19 | 20 | using namespace Qt::StringLiterals; 21 | 22 | AccessDialog::AccessDialog(QObject *parent) 23 | : QuickDialog(parent) 24 | { 25 | m_props = { 26 | {u"iconName"_s, u"dialog-question"_s}, 27 | {u"title"_s, i18n("Request device access")}, 28 | }; 29 | } 30 | 31 | void AccessDialog::setAcceptLabel(const QString &label) 32 | { 33 | m_props.insert(QStringLiteral("acceptLabel"), label); 34 | } 35 | 36 | void AccessDialog::setBody(const QString &body) 37 | { 38 | m_props.insert(QStringLiteral("body"), body); 39 | } 40 | 41 | void AccessDialog::setIcon(const QString &icon) 42 | { 43 | m_props.insert(QStringLiteral("iconName"), icon); 44 | } 45 | 46 | void AccessDialog::setRejectLabel(const QString &label) 47 | { 48 | m_props.insert(QStringLiteral("rejectLabel"), label); 49 | } 50 | 51 | void AccessDialog::setSubtitle(const QString &subtitle) 52 | { 53 | m_props.insert(QStringLiteral("subtitle"), subtitle); 54 | } 55 | 56 | void AccessDialog::setTitle(const QString &title) 57 | { 58 | m_props.insert(QStringLiteral("title"), title); 59 | } 60 | 61 | void AccessDialog::setChoices(const OptionList &choices) 62 | { 63 | m_props.insert(u"choices"_s, QVariant::fromValue(choices)); 64 | } 65 | 66 | Choices AccessDialog::selectedChoices() const 67 | { 68 | auto props = m_theDialog->property("selectedChoices").value(); 69 | Choices choices; 70 | choices.reserve(props.size()); 71 | for (const auto &prop : props.asKeyValueRange()) { 72 | choices.emplaceBack(prop.first, prop.second.toString()); 73 | } 74 | return choices; 75 | } 76 | 77 | void AccessDialog::createDialog() 78 | { 79 | create(QStringLiteral("qrc:/AccessDialog.qml"), m_props); 80 | } 81 | -------------------------------------------------------------------------------- /src/accessdialog.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2017 Red Hat Inc 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0-or-later 5 | * 6 | * SPDX-FileCopyrightText: 2017 Jan Grulich 7 | */ 8 | 9 | #ifndef XDG_DESKTOP_PORTAL_KDE_ACCESS_DIALOG_H 10 | #define XDG_DESKTOP_PORTAL_KDE_ACCESS_DIALOG_H 11 | 12 | #include "dbushelpers.h" 13 | #include "quickdialog.h" 14 | #include 15 | 16 | class AccessDialog : public QuickDialog 17 | { 18 | Q_OBJECT 19 | public: 20 | explicit AccessDialog(QObject *parent = nullptr); 21 | 22 | void setAcceptLabel(const QString &label); 23 | void setBody(const QString &body); 24 | void setIcon(const QString &icon); 25 | void setRejectLabel(const QString &label); 26 | void setTitle(const QString &title); 27 | void setSubtitle(const QString &subtitle); 28 | void setChoices(const OptionList &choices); 29 | 30 | Choices selectedChoices() const; 31 | 32 | void createDialog(); 33 | 34 | private: 35 | QVariantMap m_props; 36 | }; 37 | 38 | #endif // XDG_DESKTOP_PORTAL_KDE_ACCESS_DIALOG_H 39 | -------------------------------------------------------------------------------- /src/account.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Red Hat Inc 3 | * SPDX-License-Identifier: LGPL-2.0-or-later 4 | * 5 | * SPDX-FileCopyrightText: 2020 Jan Grulich 6 | */ 7 | 8 | #include "account.h" 9 | #include "account_debug.h" 10 | #include "dbushelpers.h" 11 | #include "userinfodialog.h" 12 | #include "utils.h" 13 | 14 | #include 15 | 16 | AccountPortal::AccountPortal(QObject *parent) 17 | : QDBusAbstractAdaptor(parent) 18 | { 19 | } 20 | 21 | void AccountPortal::GetUserInformation(const QDBusObjectPath &handle, 22 | const QString &app_id, 23 | const QString &parent_window, 24 | const QVariantMap &options, 25 | const QDBusMessage &message, 26 | [[maybe_unused]] uint &replyResponse, 27 | [[maybe_unused]] QVariantMap &replyResults) 28 | { 29 | qCDebug(XdgDesktopPortalKdeAccount) << "GetUserInformation called with parameters:"; 30 | qCDebug(XdgDesktopPortalKdeAccount) << " handle: " << handle.path(); 31 | qCDebug(XdgDesktopPortalKdeAccount) << " parent_window: " << parent_window; 32 | qCDebug(XdgDesktopPortalKdeAccount) << " app_id: " << app_id; 33 | qCDebug(XdgDesktopPortalKdeAccount) << " options: " << options; 34 | 35 | QString reason; 36 | 37 | if (options.contains(QStringLiteral("reason"))) { 38 | reason = options.value(QStringLiteral("reason")).toString(); 39 | } 40 | 41 | UserInfoDialog *userInfoDialog = new UserInfoDialog(reason, app_id); 42 | Utils::setParentWindow(userInfoDialog->windowHandle(), parent_window); 43 | 44 | delayReply(message, userInfoDialog, this, [message, userInfoDialog](DialogResult result) { 45 | QVariantMap results; 46 | if (result == DialogResult::Accepted) { 47 | results.insert(QStringLiteral("id"), userInfoDialog->id()); 48 | results.insert(QStringLiteral("name"), userInfoDialog->name()); 49 | const QString image = userInfoDialog->image(); 50 | results.insert(QStringLiteral("image"), image.isEmpty() ? QStringLiteral("file://") : image); 51 | } 52 | return QVariantList{PortalResponse::fromDialogResult(result), results}; 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /src/account.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Red Hat Inc 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0-or-later 5 | * 6 | * SPDX-FileCopyrightText: 2020 Jan Grulich 7 | */ 8 | 9 | #ifndef XDG_DESKTOP_PORTAL_KDE_ACCOUNT_H 10 | #define XDG_DESKTOP_PORTAL_KDE_ACCOUNT_H 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | class AccountPortal : public QDBusAbstractAdaptor 17 | { 18 | Q_OBJECT 19 | Q_CLASSINFO("D-Bus Interface", "org.freedesktop.impl.portal.Account") 20 | public: 21 | explicit AccountPortal(QObject *parent); 22 | 23 | public Q_SLOTS: 24 | void GetUserInformation(const QDBusObjectPath &handle, // 25 | const QString &app_id, 26 | const QString &parent_window, 27 | const QVariantMap &options, 28 | const QDBusMessage &message, 29 | uint &replyResponse, 30 | QVariantMap &replyResults); 31 | }; 32 | 33 | #endif // XDG_DESKTOP_PORTAL_KDE_ACCOUNT_H 34 | -------------------------------------------------------------------------------- /src/appchooser.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2016-2018 Red Hat Inc 3 | * SPDX-License-Identifier: LGPL-2.0-or-later 4 | * 5 | * SPDX-FileCopyrightText: 2016-2018 Jan Grulich 6 | * SPDX-FileCopyrightText: 2022 Harald Sitter 7 | */ 8 | 9 | #include "appchooser.h" 10 | #include "appchooser_debug.h" 11 | #include "appchooserdialog.h" 12 | #include "request.h" 13 | #include "utils.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | using namespace Qt::StringLiterals; 22 | 23 | AppChooserPortal::AppChooserPortal(QObject *parent) 24 | : QDBusAbstractAdaptor(parent) 25 | { 26 | } 27 | 28 | void AppChooserPortal::ChooseApplication(const QDBusObjectPath &handle, 29 | const QString &app_id, 30 | const QString &parent_window, 31 | const QStringList &choices, 32 | const QVariantMap &options, 33 | const QDBusMessage &message, 34 | [[maybe_unused]] uint &replyResponse, 35 | [[maybe_unused]] QVariantMap &replyResults) 36 | { 37 | qCDebug(XdgDesktopPortalKdeAppChooser) << "ChooseApplication called with parameters:"; 38 | qCDebug(XdgDesktopPortalKdeAppChooser) << " handle: " << handle.path(); 39 | qCDebug(XdgDesktopPortalKdeAppChooser) << " app_id: " << app_id; 40 | qCDebug(XdgDesktopPortalKdeAppChooser) << " parent_window: " << parent_window; 41 | qCDebug(XdgDesktopPortalKdeAppChooser) << " choices: " << choices; 42 | qCDebug(XdgDesktopPortalKdeAppChooser) << " options: " << options; 43 | 44 | QString latestChoice; 45 | 46 | if (options.contains(QStringLiteral("last_choice"))) { 47 | latestChoice = options.value(QStringLiteral("last_choice")).toString(); 48 | } 49 | 50 | QVariant itemName = options.value(QStringLiteral("filename")); 51 | if (!itemName.isValid()) { 52 | itemName = options.value(QStringLiteral("content_type")); 53 | } 54 | auto appDialog = new AppChooserDialog(choices, latestChoice, itemName.toString(), options.value(QStringLiteral("content_type")).toString(), true); 55 | m_appChooserDialogs.insert(handle.path(), appDialog); 56 | Utils::setParentWindow(appDialog->windowHandle(), parent_window); 57 | Request::makeClosableDialogRequest(handle, appDialog); 58 | 59 | delayReply(message, appDialog, this, [this, appDialog, handle](DialogResult result) { 60 | QVariantMap results; 61 | if (result == DialogResult::Accepted) { 62 | results = {{QStringLiteral("choice"), appDialog->selectedApplication()}, {QStringLiteral("activation_token"), appDialog->activationToken()}}; 63 | } 64 | m_appChooserDialogs.remove(handle.path()); 65 | return QVariantList{PortalResponse::fromDialogResult(result), results}; 66 | }); 67 | } 68 | 69 | void AppChooserPortal::UpdateChoices(const QDBusObjectPath &handle, const QStringList &choices) 70 | { 71 | qCDebug(XdgDesktopPortalKdeAppChooser) << "UpdateChoices called with parameters:"; 72 | qCDebug(XdgDesktopPortalKdeAppChooser) << " handle: " << handle.path(); 73 | qCDebug(XdgDesktopPortalKdeAppChooser) << " choices: " << choices; 74 | 75 | if (m_appChooserDialogs.contains(handle.path())) { 76 | m_appChooserDialogs.value(handle.path())->updateChoices(choices); 77 | } 78 | } 79 | 80 | uint AppChooserPortal::ChooseApplicationPrivate(const QString &parent_window, 81 | const QStringList &urls, 82 | const QVariantMap &options, 83 | const QDBusMessage &msg, 84 | [[maybe_unused]] QVariantMap &replyResults) 85 | { 86 | qCDebug(XdgDesktopPortalKdeAppChooser) << "ChooseApplicationPrivate called with parameters:"; 87 | qCDebug(XdgDesktopPortalKdeAppChooser) << " parent_window: " << parent_window; 88 | qCDebug(XdgDesktopPortalKdeAppChooser) << " urls: " << urls; 89 | qCDebug(XdgDesktopPortalKdeAppChooser) << " options: " << options; 90 | 91 | if (urls.isEmpty()) { 92 | return 1; 93 | } 94 | 95 | const QString itemName = urls.size() == 1 ? urls.at(0) : i18nc("count of files to open", "%1 files", urls.size()); 96 | 97 | auto appDialog = new AppChooserDialog({}, 98 | options.value(QStringLiteral("last_choice")).toString(), 99 | itemName, 100 | options.value(QStringLiteral("content_type")).toString(), 101 | false); 102 | Utils::setParentWindow(appDialog->windowHandle(), parent_window); 103 | msg.setDelayedReply(true); 104 | 105 | appDialog->m_appChooserData->setHistory(options.value("history"_L1).toStringList()); 106 | appDialog->m_appChooserData->setShellAccess(KAuthorized::authorize(KAuthorized::SHELL_ACCESS)); 107 | 108 | QDBusServiceWatcher watcher(msg.service(), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForUnregistration); 109 | connect(&watcher, &QDBusServiceWatcher::serviceUnregistered, appDialog, [appDialog] { 110 | appDialog->reject(); 111 | }); 112 | 113 | delayReply(msg, appDialog, this, [appDialog](DialogResult result) { 114 | QVariantMap results; 115 | if (result == DialogResult::Accepted) { 116 | results.insert(QStringLiteral("choice"), appDialog->selectedApplication()); 117 | results.insert(QStringLiteral("remember"), appDialog->m_appChooserData->m_remember); 118 | results.insert(QStringLiteral("openInTerminal"), appDialog->m_appChooserData->m_openInTerminal); 119 | results.insert(QStringLiteral("lingerTerminal"), appDialog->m_appChooserData->m_lingerTerminal); 120 | } 121 | return QVariantList{PortalResponse::fromDialogResult(result), results}; 122 | }); 123 | return PortalResponse::Success; 124 | } 125 | -------------------------------------------------------------------------------- /src/appchooser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2016-2018 Red Hat Inc 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0-or-later 5 | * 6 | * SPDX-FileCopyrightText: 2016-2018 Jan Grulich 7 | * SPDX-FileCopyrightText: 2022 Harald Sitter 8 | */ 9 | 10 | #ifndef XDG_DESKTOP_PORTAL_KDE_APPCHOOSER_H 11 | #define XDG_DESKTOP_PORTAL_KDE_APPCHOOSER_H 12 | 13 | #include 14 | #include 15 | 16 | class QDBusMessage; 17 | class AppChooserDialog; 18 | 19 | class AppChooserPortal : public QDBusAbstractAdaptor 20 | { 21 | Q_OBJECT 22 | Q_CLASSINFO("D-Bus Interface", "org.freedesktop.impl.portal.AppChooser") 23 | public: 24 | explicit AppChooserPortal(QObject *parent); 25 | 26 | public Q_SLOTS: 27 | void ChooseApplication(const QDBusObjectPath &handle, 28 | const QString &app_id, 29 | const QString &parent_window, 30 | const QStringList &choices, 31 | const QVariantMap &options, 32 | const QDBusMessage &message, 33 | uint &replyReponse, 34 | QVariantMap &replyResults); 35 | 36 | uint ChooseApplicationPrivate(const QString &parent_window, 37 | const QStringList &urls, 38 | const QVariantMap &options, 39 | const QDBusMessage &msg, 40 | QVariantMap &replyResults); 41 | void UpdateChoices(const QDBusObjectPath &handle, const QStringList &choices); 42 | 43 | private: 44 | QMap m_appChooserDialogs; 45 | }; 46 | 47 | #endif // XDG_DESKTOP_PORTAL_KDE_APPCHOOSER_H 48 | -------------------------------------------------------------------------------- /src/background.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Red Hat Inc 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0-or-later 5 | * 6 | * SPDX-FileCopyrightText: 2020 Jan Grulich 7 | */ 8 | 9 | #ifndef XDG_DESKTOP_PORTAL_KDE_BACKGROUND_H 10 | #define XDG_DESKTOP_PORTAL_KDE_BACKGROUND_H 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace KWayland 18 | { 19 | namespace Client 20 | { 21 | class PlasmaWindow; 22 | } 23 | } 24 | 25 | class KNotification; 26 | 27 | class BackgroundPortal : public QDBusAbstractAdaptor 28 | { 29 | Q_OBJECT 30 | Q_CLASSINFO("D-Bus Interface", "org.freedesktop.impl.portal.Background") 31 | public: 32 | explicit BackgroundPortal(QObject *parent, QDBusContext *context); 33 | ~BackgroundPortal() override; 34 | 35 | enum ApplicationState { 36 | Background = 0, 37 | Running = 1, 38 | Active = 2, 39 | }; 40 | 41 | enum AutostartFlag { 42 | None = 0x0, 43 | Activatable = 0x1, 44 | }; 45 | Q_DECLARE_FLAGS(AutostartFlags, AutostartFlag) 46 | 47 | enum NotifyResult { 48 | Forbid = 0, 49 | Allow = 1, 50 | AllowOnce = 2, 51 | }; 52 | 53 | public Q_SLOTS: 54 | QVariantMap GetAppState(); 55 | 56 | uint NotifyBackground(const QDBusObjectPath &handle, const QString &app_id, const QString &name, QVariantMap &results); 57 | 58 | bool EnableAutostart(const QString &app_id, bool enable, const QStringList &commandline, uint flags); 59 | Q_SIGNALS: 60 | void RunningApplicationsChanged(); 61 | 62 | private: 63 | void addWindow(KWayland::Client::PlasmaWindow *window); 64 | void setActiveWindow(const QString &appId, bool active); 65 | 66 | uint m_notificationCounter = 0; 67 | QList m_windows; 68 | QVariantMap m_appStates; 69 | QSet m_backgroundAppWarned; 70 | QDBusContext *const m_context; 71 | }; 72 | Q_DECLARE_OPERATORS_FOR_FLAGS(BackgroundPortal::AutostartFlags) 73 | 74 | #endif // XDG_DESKTOP_PORTAL_KDE_BACKGROUND_H 75 | -------------------------------------------------------------------------------- /src/clipboard.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 3 | SPDX-FileCopyrightText: 2024 David Redondo 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | class Session; 15 | 16 | class ClipboardPortal : public QDBusAbstractAdaptor 17 | { 18 | Q_OBJECT 19 | Q_CLASSINFO("D-Bus Interface", "org.freedesktop.impl.portal.Clipboard") 20 | public: 21 | explicit ClipboardPortal(QObject *parent); 22 | 23 | QVariant fetchData(Session *session, const QString &mimetype); 24 | 25 | public Q_SLOTS: 26 | void RequestClipboard(const QDBusObjectPath &session_handle, const QVariantMap &options); 27 | void SetSelection(const QDBusObjectPath &session_handle, const QVariantMap &options); 28 | QDBusUnixFileDescriptor SelectionWrite(const QDBusObjectPath &session_handle, uint serial, const QDBusMessage &message); 29 | void SelectionWriteDone(const QDBusObjectPath &session_handle, uint serial, bool success, const QDBusMessage &message); 30 | QDBusUnixFileDescriptor SelectionRead(const QDBusObjectPath &session_handle, const QString &mime_type, const QDBusMessage &message); 31 | 32 | Q_SIGNALS: 33 | void SelectionOwnerChanged(const QDBusObjectPath &session_handle, const QVariantMap &options); 34 | void SelectionTransfer(const QDBusObjectPath &session_handle, const QString &mimeType, uint serial); 35 | 36 | private: 37 | struct Transfer { 38 | QEventLoop &loop; 39 | int fd = -1; 40 | QByteArray data; 41 | }; 42 | std::map m_pendingTransfers; 43 | }; 44 | -------------------------------------------------------------------------------- /src/dbushelpers.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2018-2019 Red Hat Inc 3 | * SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez 4 | * 5 | * SPDX-License-Identifier: LGPL-2.0-or-later 6 | * 7 | * SPDX-FileCopyrightText: 2018-2019 Jan Grulich 8 | */ 9 | 10 | #include "dbushelpers.h" 11 | 12 | QDBusArgument &operator<<(QDBusArgument &arg, const Choice &choice) 13 | { 14 | arg.beginStructure(); 15 | arg << choice.id << choice.value; 16 | arg.endStructure(); 17 | return arg; 18 | } 19 | 20 | const QDBusArgument &operator>>(const QDBusArgument &arg, Choice &choice) 21 | { 22 | QString id; 23 | QString value; 24 | arg.beginStructure(); 25 | arg >> id >> value; 26 | choice.id = id; 27 | choice.value = value; 28 | arg.endStructure(); 29 | return arg; 30 | } 31 | 32 | QDBusArgument &operator<<(QDBusArgument &arg, const Option &option) 33 | { 34 | arg.beginStructure(); 35 | arg << option.id << option.label << option.choices << option.initialChoiceId; 36 | arg.endStructure(); 37 | return arg; 38 | } 39 | 40 | const QDBusArgument &operator>>(const QDBusArgument &arg, Option &option) 41 | { 42 | QString id; 43 | QString label; 44 | Choices choices; 45 | QString initialChoiceId; 46 | arg.beginStructure(); 47 | arg >> id >> label >> choices >> initialChoiceId; 48 | option.id = id; 49 | option.label = label; 50 | option.choices = choices; 51 | option.initialChoiceId = initialChoiceId; 52 | arg.endStructure(); 53 | return arg; 54 | } 55 | -------------------------------------------------------------------------------- /src/dbushelpers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2018-2019 Red Hat Inc 3 | * SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez 4 | * 5 | * SPDX-License-Identifier: LGPL-2.0-or-later 6 | * 7 | * SPDX-FileCopyrightText: 2018-2019 Jan Grulich 8 | */ 9 | 10 | #pragma once 11 | 12 | #include "quickdialog.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "utils.h" 22 | 23 | // not an enum class so it can convert to uint implicitelly when returning from a dbus handling slot 24 | namespace PortalResponse 25 | { 26 | enum Response : unsigned { 27 | Success = 0, 28 | Cancelled = 1, 29 | OtherError = 2 30 | }; 31 | constexpr inline Response fromDialogResult(DialogResult r) 32 | { 33 | return r == DialogResult::Accepted ? Success : Cancelled; 34 | } 35 | constexpr inline Response fromDialogCode(QDialog::DialogCode c) 36 | { 37 | return c == QDialog::Accepted ? Success : Cancelled; 38 | } 39 | } 40 | 41 | /// a{sa{sv}} 42 | using VariantMapMap = QMap>; 43 | 44 | /// sa{sv} 45 | using Shortcut = QPair; 46 | 47 | /// a(sa{sv}) 48 | using Shortcuts = QList; 49 | 50 | // as 51 | using Permissions = QStringList; 52 | 53 | // a{sas} 54 | using AppIdPermissionsMap = QMap; 55 | 56 | Q_DECLARE_METATYPE(VariantMapMap) 57 | Q_DECLARE_METATYPE(Shortcuts) 58 | 59 | struct Choice { 60 | Q_GADGET 61 | Q_PROPERTY(QString id MEMBER id CONSTANT) 62 | Q_PROPERTY(QString value MEMBER value CONSTANT) 63 | public: 64 | QString id; 65 | QString value; 66 | }; 67 | using Choices = QList; 68 | QDBusArgument &operator<<(QDBusArgument &arg, const Choice &choice); 69 | const QDBusArgument &operator>>(const QDBusArgument &arg, Choice &choice); 70 | 71 | struct Option { 72 | Q_GADGET 73 | Q_PROPERTY(QString id MEMBER id CONSTANT) 74 | Q_PROPERTY(QString label MEMBER label CONSTANT) 75 | Q_PROPERTY(Choices choices MEMBER choices CONSTANT) 76 | Q_PROPERTY(QString initialChoiceId MEMBER initialChoiceId CONSTANT) 77 | public: 78 | QString id; 79 | QString label; 80 | Choices choices; 81 | QString initialChoiceId; 82 | }; 83 | using OptionList = QList