├── .gitignore ├── CMakeLists.txt ├── LICENSES ├── BSD-3-Clause.txt ├── CC-BY-SA-4.0.txt ├── CC0-1.0.txt ├── GPL-3.0-or-later.txt └── LGPL-3.0-or-later.txt ├── README.md ├── cmake └── Modules │ └── Findlibexif.cmake ├── data ├── CMakeLists.txt ├── Dynamic │ ├── contents │ │ └── images │ │ │ ├── dynamic.avif │ │ │ └── dynamic.avif.license │ ├── metadata.desktop │ └── metadata.desktop.license └── solar.png └── src ├── CMakeLists.txt ├── Messages.sh ├── declarative ├── CMakeLists.txt ├── config-dynamicwallpaper.h.cmake ├── dynamicwallpapercrawler.cpp ├── dynamicwallpapercrawler.h ├── dynamicwallpaperengine.cpp ├── dynamicwallpaperengine.h ├── dynamicwallpaperengine_daynight.cpp ├── dynamicwallpaperengine_daynight.h ├── dynamicwallpaperengine_solar.cpp ├── dynamicwallpaperengine_solar.h ├── dynamicwallpaperextensionplugin.cpp ├── dynamicwallpaperextensionplugin.h ├── dynamicwallpaperglobals.h ├── dynamicwallpaperhandler.cpp ├── dynamicwallpaperhandler.h ├── dynamicwallpaperimagehandle.cpp ├── dynamicwallpaperimagehandle.h ├── dynamicwallpaperimageprovider.cpp ├── dynamicwallpaperimageprovider.h ├── dynamicwallpapermodel.cpp ├── dynamicwallpapermodel.h ├── dynamicwallpaperpreviewcache.cpp ├── dynamicwallpaperpreviewcache.h ├── dynamicwallpaperpreviewjob.cpp ├── dynamicwallpaperpreviewjob.h ├── dynamicwallpaperpreviewprovider.cpp ├── dynamicwallpaperpreviewprovider.h ├── dynamicwallpaperprober.cpp ├── dynamicwallpaperprober.h └── qmldir ├── lib ├── CMakeLists.txt ├── KDynamicWallpaperConfig.cmake.in ├── kdaynightdynamicwallpapermetadata.cpp ├── kdaynightdynamicwallpapermetadata.h ├── kdynamicwallpapermetadata.cpp ├── kdynamicwallpapermetadata.h ├── kdynamicwallpaperreader.cpp ├── kdynamicwallpaperreader.h ├── kdynamicwallpaperwriter.cpp ├── kdynamicwallpaperwriter.h ├── ksolardynamicwallpapermetadata.cpp ├── ksolardynamicwallpapermetadata.h ├── ksunpath.cpp ├── ksunpath.h ├── ksunposition.cpp ├── ksunposition.h ├── ksystemclockmonitor.cpp ├── ksystemclockmonitor.h ├── ksystemclockmonitorengine.cpp ├── ksystemclockmonitorengine_linux.cpp ├── ksystemclockmonitorengine_linux_p.h ├── ksystemclockmonitorengine_p.h ├── metadata.xml └── resources.qrc ├── package ├── contents │ ├── config │ │ └── main.xml │ └── ui │ │ ├── DecimalSpinBox.qml │ │ ├── WallpaperImage.qml │ │ ├── WallpaperView.qml │ │ ├── config.qml │ │ └── main.qml ├── metadata.json └── metadata.json.license ├── plugins ├── CMakeLists.txt └── kpackage-integration │ ├── CMakeLists.txt │ ├── dynamicwallpaperpackagestructure.cpp │ ├── dynamicwallpaperpackagestructure.h │ ├── dynamicwallpaperpackagestructure.json │ └── dynamicwallpaperpackagestructure.json.license ├── tools ├── CMakeLists.txt └── builder │ ├── CMakeLists.txt │ ├── completions │ ├── CMakeLists.txt │ ├── bash │ │ ├── CMakeLists.txt │ │ └── kdynamicwallpaperbuilder │ ├── fish │ │ ├── CMakeLists.txt │ │ └── kdynamicwallpaperbuilder.fish │ └── zsh │ │ ├── CMakeLists.txt │ │ └── _kdynamicwallpaperbuilder │ ├── dynamicwallpaperexifmetadata.cpp │ ├── dynamicwallpaperexifmetadata.h │ ├── dynamicwallpapermanifest.cpp │ ├── dynamicwallpapermanifest.h │ └── main.cpp └── translations ├── CMakeLists.txt ├── extract-messages.sh └── po ├── de.po ├── it.po ├── pl.po ├── pt.po ├── ru.po ├── uk.po └── zh.po /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | /build 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | cmake_minimum_required(VERSION 3.18) 6 | project(plasma5-wallpapers-dynamic) 7 | 8 | set(KF_MIN_VERSION "6.0.0") 9 | set(QT_MIN_VERSION "6.6.0") 10 | 11 | set(PROJECT_VERSION "5.0.0") 12 | set(PROJECT_VERSION_MAJOR 5) 13 | 14 | find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE) 15 | set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/Modules) 16 | 17 | include(CMakePackageConfigHelpers) 18 | include(FeatureSummary) 19 | include(GenerateExportHeader) 20 | include(WriteBasicConfigVersionFile) 21 | include(ECMGenerateHeaders) 22 | include(KDEInstallDirs) 23 | include(KDECMakeSettings) 24 | include(KDECompilerSettings NO_POLICY_SCOPE) 25 | 26 | set(CMAKE_CXX_STANDARD 17) 27 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 28 | set(CMAKE_CXX_EXTENSIONS OFF) 29 | 30 | find_package(libavif REQUIRED) 31 | find_package(libexif REQUIRED) 32 | find_package(Plasma REQUIRED) 33 | 34 | find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS 35 | Config 36 | I18n 37 | Package 38 | ) 39 | 40 | find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS 41 | Concurrent 42 | Core 43 | DBus 44 | Gui 45 | Positioning 46 | Qml 47 | Quick 48 | Xml 49 | ) 50 | 51 | add_subdirectory(data) 52 | add_subdirectory(src) 53 | 54 | feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) 55 | -------------------------------------------------------------------------------- /LICENSES/BSD-3-Clause.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) . All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 26 | USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /LICENSES/CC0-1.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /LICENSES/LGPL-3.0-or-later.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | 3 | Version 3, 29 June 2007 4 | 5 | Copyright (C) 2007 Free Software Foundation, Inc. 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this license 8 | document, but changing it is not allowed. 9 | 10 | This version of the GNU Lesser General Public License incorporates the terms 11 | and conditions of version 3 of the GNU General Public License, supplemented 12 | by the additional permissions listed below. 13 | 14 | 0. Additional Definitions. 15 | 16 | 17 | 18 | As used herein, "this License" refers to version 3 of the GNU Lesser General 19 | Public License, and the "GNU GPL" refers to version 3 of the GNU General Public 20 | License. 21 | 22 | 23 | 24 | "The Library" refers to a covered work governed by this License, other than 25 | an Application or a Combined Work as defined below. 26 | 27 | 28 | 29 | An "Application" is any work that makes use of an interface provided by the 30 | Library, but which is not otherwise based on the Library. Defining a subclass 31 | of a class defined by the Library is deemed a mode of using an interface provided 32 | by the Library. 33 | 34 | 35 | 36 | A "Combined Work" is a work produced by combining or linking an Application 37 | with the Library. The particular version of the Library with which the Combined 38 | Work was made is also called the "Linked Version". 39 | 40 | 41 | 42 | The "Minimal Corresponding Source" for a Combined Work means the Corresponding 43 | Source for the Combined Work, excluding any source code for portions of the 44 | Combined Work that, considered in isolation, are based on the Application, 45 | and not on the Linked Version. 46 | 47 | 48 | 49 | The "Corresponding Application Code" for a Combined Work means the object 50 | code and/or source code for the Application, including any data and utility 51 | programs needed for reproducing the Combined Work from the Application, but 52 | excluding the System Libraries of the Combined Work. 53 | 54 | 1. Exception to Section 3 of the GNU GPL. 55 | 56 | You may convey a covered work under sections 3 and 4 of this License without 57 | being bound by section 3 of the GNU GPL. 58 | 59 | 2. Conveying Modified Versions. 60 | 61 | If you modify a copy of the Library, and, in your modifications, a facility 62 | refers to a function or data to be supplied by an Application that uses the 63 | facility (other than as an argument passed when the facility is invoked), 64 | then you may convey a copy of the modified version: 65 | 66 | a) under this License, provided that you make a good faith effort to ensure 67 | that, in the event an Application does not supply the function or data, the 68 | facility still operates, and performs whatever part of its purpose remains 69 | meaningful, or 70 | 71 | b) under the GNU GPL, with none of the additional permissions of this License 72 | applicable to that copy. 73 | 74 | 3. Object Code Incorporating Material from Library Header Files. 75 | 76 | The object code form of an Application may incorporate material from a header 77 | file that is part of the Library. You may convey such object code under terms 78 | of your choice, provided that, if the incorporated material is not limited 79 | to numerical parameters, data structure layouts and accessors, or small macros, 80 | inline functions and templates (ten or fewer lines in length), you do both 81 | of the following: 82 | 83 | a) Give prominent notice with each copy of the object code that the Library 84 | is used in it and that the Library and its use are covered by this License. 85 | 86 | b) Accompany the object code with a copy of the GNU GPL and this license document. 87 | 88 | 4. Combined Works. 89 | 90 | You may convey a Combined Work under terms of your choice that, taken together, 91 | effectively do not restrict modification of the portions of the Library contained 92 | in the Combined Work and reverse engineering for debugging such modifications, 93 | if you also do each of the following: 94 | 95 | a) Give prominent notice with each copy of the Combined Work that the Library 96 | is used in it and that the Library and its use are covered by this License. 97 | 98 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 99 | document. 100 | 101 | c) For a Combined Work that displays copyright notices during execution, include 102 | the copyright notice for the Library among these notices, as well as a reference 103 | directing the user to the copies of the GNU GPL and this license document. 104 | 105 | d) Do one of the following: 106 | 107 | 0) Convey the Minimal Corresponding Source under the terms of this License, 108 | and the Corresponding Application Code in a form suitable for, and under terms 109 | that permit, the user to recombine or relink the Application with a modified 110 | version of the Linked Version to produce a modified Combined Work, in the 111 | manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 112 | 113 | 1) Use a suitable shared library mechanism for linking with the Library. A 114 | suitable mechanism is one that (a) uses at run time a copy of the Library 115 | already present on the user's computer system, and (b) will operate properly 116 | with a modified version of the Library that is interface-compatible with the 117 | Linked Version. 118 | 119 | e) Provide Installation Information, but only if you would otherwise be required 120 | to provide such information under section 6 of the GNU GPL, and only to the 121 | extent that such information is necessary to install and execute a modified 122 | version of the Combined Work produced by recombining or relinking the Application 123 | with a modified version of the Linked Version. (If you use option 4d0, the 124 | Installation Information must accompany the Minimal Corresponding Source and 125 | Corresponding Application Code. If you use option 4d1, you must provide the 126 | Installation Information in the manner specified by section 6 of the GNU GPL 127 | for conveying Corresponding Source.) 128 | 129 | 5. Combined Libraries. 130 | 131 | You may place library facilities that are a work based on the Library side 132 | by side in a single library together with other library facilities that are 133 | not Applications and are not covered by this License, and convey such a combined 134 | library under terms of your choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based on the 137 | Library, uncombined with any other library facilities, conveyed under the 138 | terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it is a work 141 | based on the Library, and explaining where to find the accompanying uncombined 142 | form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions of the 147 | GNU Lesser General Public License from time to time. Such new versions will 148 | be similar in spirit to the present version, but may differ in detail to address 149 | new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the Library as you 152 | received it specifies that a certain numbered version of the GNU Lesser General 153 | Public License "or any later version" applies to it, you have the option of 154 | following the terms and conditions either of that published version or of 155 | any later version published by the Free Software Foundation. If the Library 156 | as you received it does not specify a version number of the GNU Lesser General 157 | Public License, you may choose any version of the GNU Lesser General Public 158 | License ever published by the Free Software Foundation. 159 | 160 | If the Library as you received it specifies that a proxy can decide whether 161 | future versions of the GNU Lesser General Public License shall apply, that 162 | proxy's public statement of acceptance of any version is permanent authorization 163 | for you to choose that version for the Library. 164 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Dynamic Wallpaper Engine 8 | 9 | A wallpaper plugin for KDE Plasma that continuously updates the desktop background 10 | based on the current time in your location. 11 | 12 | 13 | ## Additional Wallpapers 14 | 15 | More dynamic wallpapers can be found at https://github.com/karmanyaahm/awesome-plasma5-dynamic-wallpapers. 16 | 17 | 18 | ## Installation 19 | 20 | #### Arch Linux 21 | 22 | ``` 23 | yay -S plasma5-wallpapers-dynamic 24 | ``` 25 | 26 | #### Fedora 27 | 28 | ``` 29 | sudo dnf install plasma-wallpapers-dynamic 30 | ``` 31 | 32 | In order to use the dynamic wallpaper builder tool, install `plasma-wallpapers-dynamic-builder` package. 33 | 34 | #### Ubuntu 20.10 35 | 36 | ``` 37 | sudo apt install plasma-wallpaper-dynamic 38 | ``` 39 | 40 | 41 | ## Building From Git 42 | 43 | **Note**: master branch targets Plasma 6. If you use Plasma 5, check the [latest release](https://github.com/zzag/plasma5-wallpapers-dynamic/releases/latest). 44 | 45 | In order to build this wallpaper plugin from source code, you need to install a 46 | couple of prerequisites 47 | 48 | Arch Linux: 49 | 50 | ```sh 51 | sudo pacman -S cmake extra-cmake-modules git plasma-framework qt5-base qt5-declarative \ 52 | qt5-location libexif libavif 53 | ``` 54 | 55 | Fedora: 56 | 57 | ```sh 58 | sudo dnf install cmake extra-cmake-modules git kf5-kpackage-devel kf5-plasma-devel \ 59 | kf5-ki18n-devel qt5-qtbase-devel qt5-qtdeclarative-devel qt5-qtlocation-devel \ 60 | libexif-devel libavif-devel qt5-qtbase-private-devel 61 | ``` 62 | 63 | Ubuntu: 64 | 65 | ```sh 66 | sudo apt install cmake extra-cmake-modules git libkf5package-dev libkf5plasma-dev \ 67 | libkf5i18n-dev qtbase5-dev qtdeclarative5-dev qtpositioning5-dev gettext \ 68 | qml-module-qtpositioning libexif-dev libavif-dev build-essential qtdeclarative5-private-dev \ 69 | qtbase5-private-dev 70 | ``` 71 | 72 | Once all prerequisites are installed, you need to grab the source code 73 | 74 | ```sh 75 | git clone https://github.com/zzag/plasma5-wallpapers-dynamic.git 76 | cd plasma5-wallpapers-dynamic 77 | ``` 78 | 79 | Configure the build 80 | 81 | ```sh 82 | mkdir build && cd build 83 | cmake .. -DCMAKE_BUILD_TYPE=Release \ 84 | -DCMAKE_INSTALL_PREFIX=/usr \ 85 | -DCMAKE_INSTALL_LIBDIR=lib \ 86 | -DBUILD_TESTING=OFF 87 | ``` 88 | 89 | Now trigger the build by running the following command 90 | 91 | ```sh 92 | make 93 | ``` 94 | 95 | To install run 96 | 97 | ```sh 98 | sudo make install 99 | ``` 100 | 101 | 102 | ## How to Use It 103 | 104 | Right-click a blank area of the desktop and choose "Configure Desktop...", select 105 | "Dynamic" wallpaper type and click the Apply button. 106 | 107 | 108 | ## How to Create a Dynamic Wallpaper 109 | 110 | The `kdynamicwallpaperbuilder` command line tool is used to create dynamic wallpapers. As input, 111 | it takes a manifest json file describing the wallpaper and produces the wallpaper 112 | 113 | ```sh 114 | kdynamicwallpaperbuilder path/to/manifest.json --output wallpaper.avif 115 | ``` 116 | 117 | This engine supports several types of dynamic wallpapers - _solar_ and _day-night_. 118 | 119 | ### How to Create a Solar Dynamic Wallpaper 120 | 121 | With a solar dynamic wallpaper, the engine will try to keep the images in sync with the Sun 122 | position at your location. 123 | 124 |
125 | 126 |
127 | 128 | The manifest file looks as follows 129 | 130 | ```json 131 | { 132 | "Type": "solar", 133 | "Meta": [ 134 | { 135 | "SolarAzimuth": "*", 136 | "SolarElevation": "*", 137 | "CrossFade": true, 138 | "Time": "*", 139 | "FileName": "0.png" 140 | }, { 141 | "SolarAzimuth": 0, 142 | "SolarElevation": -90, 143 | "CrossFade": true, 144 | "Time": "00:00", 145 | "FileName": "1.png" 146 | }, { 147 | "SolarAzimuth": 90, 148 | "SolarElevation": 0, 149 | "CrossFade": true, 150 | "Time": "06:00", 151 | "FileName": "2.png" 152 | }, { 153 | "SolarAzimuth": 180, 154 | "SolarElevation": 90, 155 | "CrossFade": true, 156 | "Time": "12:00", 157 | "FileName": "3.png" 158 | } 159 | ] 160 | } 161 | ``` 162 | 163 | The `SolarAzimuth` field and the `SolarElevation` field specify the position of the Sun when the 164 | associated picture was taken. The `Time` field specifies the time, which is in 24-hour format, when 165 | the picture was taken. If the user is not located near the North or the South Pole, the dynamic 166 | wallpaper engine will try to show images based on the current position of the Sun; otherwise it will 167 | fallback to using time metadata. 168 | 169 | Only the `Time` field is required, the position of the Sun is optional. 170 | 171 | The `CrossFade` field indicates whether the current image can be blended with the next one. The 172 | cross-fading is used to make transitions between images smooth. By default, the `CrossFade` field is 173 | set to `true`. Last, but not least, the `FileName` field specifies the file path of the image 174 | relative to the manifest json file. 175 | 176 | Now that you have prepared all images and a manifest file, it's time pull out big guns. Run the 177 | following command 178 | 179 | ```sh 180 | kdynamicwallpaperbuilder path/to/manifest.json 181 | ``` 182 | 183 | If the command succeeds, you will see a new file in the current working directory `wallpaper.avif`, 184 | which can be used as a dynamic wallpaper. 185 | 186 | Note that encoding the dynamic wallpaper may take a lot of memory (AVIF encoders are very memory 187 | hungry) and time! 188 | 189 | 190 | #### Computing the position of the Sun based on GPS image metadata 191 | 192 | `SolarAzimuth`, `SolarElevation`, and `Time` fields can have a special value of `*`. In which case, 193 | `kdynamicwallpaperbuilder` will use the Exif metadata in the image to fill them. An image must 194 | contain the following GPS [EXIF tags](https://exiftool.org/TagNames/GPS.html) to compute the position 195 | of the Sun: 196 | 197 | - `GPSLatitude` and `GPSLatitudeRef` 198 | - `GPSLongitude` and `GPSLongitudeRef` 199 | - `GPSTimeStamp` and `GPSDateStamp` 200 | 201 | The calculated position of the Sun can be viewed by passing `--verbose` to the `kdynamicwallpaperbuilder` command. 202 | 203 | ### How to Create a Day/Night Dynamic Wallpaper 204 | 205 | A day/night dynamic wallpaper consists of only two images - one for the day, and one for the night. 206 | The engine will automagically figure out which one to use based on the current time or the Sun 207 | position at your location. 208 | 209 | The manifest file for a day/night wallpaper looks as follows 210 | 211 | ```sh 212 | { 213 | "Type": "day-night", 214 | "Meta": [ 215 | { 216 | "TimeOfDay": "day", 217 | "FileName": "day.png" 218 | }, { 219 | "TimeOfDay": "night", 220 | "FileName": "night.png" 221 | } 222 | ] 223 | } 224 | ``` 225 | 226 | 227 | ## How to Use Dynamic Wallpapers for macOS 228 | 229 | Since dynamic wallpapers for macOS and this plugin are incompatible, you need to use a script to 230 | convert dynamic wallpapers. 231 | 232 | ```sh 233 | curl -sLO https://raw.githubusercontent.com/zzag/plasma5-wallpapers-dynamic-extras/master/dynamicwallpaperconverter 234 | chmod +x dynamicwallpaperconverter 235 | ``` 236 | 237 | Once you've downloaded the dynamicwallpaperconverter script, you can start converting wallpapers 238 | 239 | ```sh 240 | ./dynamicwallpaperconverter --crossfade file.heic 241 | ``` 242 | 243 | After the command above has finished its execution, you should see a file in the current working 244 | directory named `wallpaper.avif`, which can be fed into this plugin. 245 | 246 | If it takes too long to encode the wallpaper as an avif file, you can change the encoding 247 | speed. Note that the encoding speed affects the final file size! 248 | 249 | ```sh 250 | ./dynamicwallpaperconverter --speed 5 --crossfade file.heic 251 | ``` 252 | 253 | For more details, check the output of `./dynamicwallpaperconverter --help`. 254 | -------------------------------------------------------------------------------- /cmake/Modules/Findlibexif.cmake: -------------------------------------------------------------------------------- 1 | #.rst: 2 | # Findlibexif 3 | # ------- 4 | # 5 | # Try to find libexif on a Unix system. 6 | # 7 | # This will define the following variables: 8 | # 9 | # ``libexif_FOUND`` 10 | # True if (the requested version of) libexif is available 11 | # ``libexif_VERSION`` 12 | # The version of libexif 13 | # ``libexif_LIBRARIES`` 14 | # This should be passed to target_compile_options() if the target is not 15 | # used for linking 16 | # ``libexif_INCLUDE_DIRS`` 17 | # This should be passed to target_include_directories() if the target is not 18 | # used for linking 19 | # ``libexif_DEFINITIONS`` 20 | # This should be passed to target_compile_options() if the target is not 21 | # used for linking 22 | # 23 | # If ``libexif_FOUND`` is TRUE, it will also define the following imported target: 24 | # 25 | # ``libexif::libexif`` 26 | # The libexif library 27 | # 28 | # In general we recommend using the imported target, as it is easier to use. 29 | # Bear in mind, however, that if the target is in the link interface of an 30 | # exported library, it must be made available by the package config file. 31 | 32 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 33 | # SPDX-License-Identifier: BSD-3-Clause 34 | 35 | find_package(PkgConfig) 36 | pkg_check_modules(PKG_libexif QUIET libexif) 37 | 38 | set(libexif_VERSION ${PKG_libexif_VERSION}) 39 | set(libexif_DEFINITIONS ${PKG_libexif_CFLAGS_OTHER}) 40 | 41 | find_path(libexif_INCLUDE_DIR 42 | NAMES libexif/exif-data.h 43 | HINTS ${PKG_libexif_INCLUDE_DIRS} 44 | ) 45 | 46 | find_library(libexif_LIBRARY 47 | NAMES exif 48 | HINTS ${PKG_libexif_LIBRARY_DIRS} 49 | ) 50 | 51 | include(FindPackageHandleStandardArgs) 52 | find_package_handle_standard_args(libexif 53 | FOUND_VAR libexif_FOUND 54 | REQUIRED_VARS libexif_LIBRARY 55 | libexif_INCLUDE_DIR 56 | VERSION_VAR libexif_VERSION 57 | ) 58 | 59 | if (libexif_FOUND AND NOT TARGET libexif::libexif) 60 | add_library(libexif::libexif UNKNOWN IMPORTED) 61 | set_target_properties(libexif::libexif PROPERTIES 62 | IMPORTED_LOCATION "${libexif_LIBRARY}" 63 | INTERFACE_COMPILE_OPTIONS "${libexif_DEFINITIONS}" 64 | INTERFACE_INCLUDE_DIRECTORIES "${libexif_INCLUDE_DIR}" 65 | ) 66 | endif() 67 | 68 | set(libexif_INCLUDE_DIRS ${libexif_INCLUDE_DIR}) 69 | set(libexif_LIBRARIES ${libexif_LIBRARY}) 70 | 71 | mark_as_advanced(libexif_INCLUDE_DIR) 72 | mark_as_advanced(libexif_LIBRARY) 73 | -------------------------------------------------------------------------------- /data/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | install(DIRECTORY "Dynamic" DESTINATION ${KDE_INSTALL_WALLPAPERDIR}) 6 | -------------------------------------------------------------------------------- /data/Dynamic/contents/images/dynamic.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzag/plasma5-wallpapers-dynamic/fa3d47c96a9fd713232cc5d28c16b067741de66e/data/Dynamic/contents/images/dynamic.avif -------------------------------------------------------------------------------- /data/Dynamic/contents/images/dynamic.avif.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | 3 | SPDX-License-Identifier: CC-BY-SA-4.0 4 | -------------------------------------------------------------------------------- /data/Dynamic/metadata.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Dynamic 3 | 4 | X-KDE-PluginInfo-Name=Dynamic 5 | X-KDE-PluginInfo-Author=Vlad Zahorodnii 6 | X-KDE-PluginInfo-Email=vlad.zahorodnii@kde.org 7 | X-KDE-PluginInfo-License=CC-BY-SA-4.0 8 | -------------------------------------------------------------------------------- /data/Dynamic/metadata.desktop.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | 3 | SPDX-License-Identifier: CC0-1.0 4 | -------------------------------------------------------------------------------- /data/solar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzag/plasma5-wallpapers-dynamic/fa3d47c96a9fd713232cc5d28c16b067741de66e/data/solar.png -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | plasma_install_package(package com.github.zzag.dynamic wallpapers wallpaper) 6 | 7 | add_subdirectory(declarative) 8 | add_subdirectory(lib) 9 | add_subdirectory(plugins) 10 | add_subdirectory(tools) 11 | add_subdirectory(translations) 12 | -------------------------------------------------------------------------------- /src/Messages.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 4 | # 5 | # SPDX-License-Identifier: CC0-1.0 6 | 7 | $XGETTEXT `find . -name \*.qml -o -name \*.cpp` -o $podir/plasma_wallpaper_com.github.zzag.dynamic.pot 8 | -------------------------------------------------------------------------------- /src/declarative/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | add_definitions(-DTRANSLATION_DOMAIN=\"plasma_wallpaper_com.github.zzag.dynamic\") 6 | 7 | set(FALLBACK_WALLPAPER "Dynamic") 8 | configure_file(config-dynamicwallpaper.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-dynamicwallpaper.h) 9 | 10 | set(dynamicwallpaperplugin_SOURCES 11 | dynamicwallpapercrawler.cpp 12 | dynamicwallpaperengine.cpp 13 | dynamicwallpaperengine_daynight.cpp 14 | dynamicwallpaperengine_solar.cpp 15 | dynamicwallpaperextensionplugin.cpp 16 | dynamicwallpaperhandler.cpp 17 | dynamicwallpaperimagehandle.cpp 18 | dynamicwallpaperimageprovider.cpp 19 | dynamicwallpapermodel.cpp 20 | dynamicwallpaperpreviewcache.cpp 21 | dynamicwallpaperpreviewjob.cpp 22 | dynamicwallpaperpreviewprovider.cpp 23 | dynamicwallpaperprober.cpp 24 | ) 25 | 26 | add_library(plasma_wallpaper_dynamicplugin ${dynamicwallpaperplugin_SOURCES}) 27 | 28 | target_link_libraries(plasma_wallpaper_dynamicplugin 29 | Qt6::Concurrent 30 | Qt6::Core 31 | Qt6::Gui 32 | Qt6::Positioning 33 | Qt6::Qml 34 | Qt6::Quick 35 | Qt6::QuickPrivate 36 | 37 | KF6::ConfigCore 38 | KF6::I18n 39 | KF6::Package 40 | 41 | KDynamicWallpaper::KDynamicWallpaper 42 | ) 43 | 44 | install(TARGETS plasma_wallpaper_dynamicplugin DESTINATION ${KDE_INSTALL_QMLDIR}/com/github/zzag/plasma/wallpapers/dynamic) 45 | install(FILES qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/com/github/zzag/plasma/wallpapers/dynamic) 46 | -------------------------------------------------------------------------------- /src/declarative/config-dynamicwallpaper.h.cmake: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #define FALLBACK_WALLPAPER "${FALLBACK_WALLPAPER}" 8 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpapercrawler.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #include "dynamicwallpapercrawler.h" 8 | 9 | #include 10 | 11 | #include 12 | 13 | /*! 14 | * \class DynamicWallpaperCrawler 15 | * \brief The DynamicWallpaperCrawler class discovers dynamic wallpapers. 16 | */ 17 | 18 | /*! 19 | * Constructs an DynamicWallpaperCrawler object with the given \p parent. 20 | */ 21 | DynamicWallpaperCrawler::DynamicWallpaperCrawler(QObject *parent) 22 | : QThread(parent) 23 | , m_token(QUuid::createUuid()) 24 | { 25 | } 26 | 27 | /*! 28 | * Destructs the DynamicWallpaperCrawler object. 29 | */ 30 | DynamicWallpaperCrawler::~DynamicWallpaperCrawler() 31 | { 32 | wait(); 33 | } 34 | 35 | /*! 36 | * Returns the UUID that uniquely identifies this crawler. 37 | */ 38 | QUuid DynamicWallpaperCrawler::token() const 39 | { 40 | return m_token; 41 | } 42 | 43 | /*! 44 | * Sets the list of directories where dynamic wallpapers should be searched to \p roots. 45 | * 46 | * Dynamic wallpapers will be searched recursively. 47 | */ 48 | void DynamicWallpaperCrawler::setSearchRoots(const QStringList &roots) 49 | { 50 | m_searchRoots = roots; 51 | } 52 | 53 | /*! 54 | * Returns the list of directories where dynamic wallpapers should be searched recursively. 55 | */ 56 | QStringList DynamicWallpaperCrawler::searchRoots() const 57 | { 58 | return m_searchRoots; 59 | } 60 | 61 | /*! 62 | * Sets the package structure for dynamic wallpaper packages to \p structure. 63 | */ 64 | void DynamicWallpaperCrawler::setPackageStructure(KPackage::PackageStructure *structure) 65 | { 66 | m_packageStructure = structure; 67 | } 68 | 69 | /*! 70 | * Returns the package structure for dynamic wallpaper packages. 71 | */ 72 | KPackage::PackageStructure *DynamicWallpaperCrawler::packageStructure() const 73 | { 74 | return m_packageStructure; 75 | } 76 | 77 | void DynamicWallpaperCrawler::run() 78 | { 79 | for (const QString &candidate : std::as_const(m_searchRoots)) 80 | visitFolder(candidate); 81 | 82 | deleteLater(); 83 | } 84 | 85 | void DynamicWallpaperCrawler::visitFolder(const QString &filePath) 86 | { 87 | QDir currentFolder(filePath); 88 | currentFolder.setFilter(QDir::NoDotAndDotDot | QDir::NoSymLinks | QDir::Readable | QDir::AllDirs | QDir::Files); 89 | currentFolder.setNameFilters({ QStringLiteral("*.avif") }); 90 | 91 | const QFileInfoList fileInfos = currentFolder.entryInfoList(); 92 | for (const QFileInfo &fileInfo : fileInfos) { 93 | if (fileInfo.isDir()) { 94 | if (checkPackage(fileInfo.filePath())) { 95 | Q_EMIT foundPackage(fileInfo.filePath(), token()); 96 | } else { 97 | visitFolder(fileInfo.filePath()); 98 | } 99 | } else { 100 | visitFile(fileInfo.filePath()); 101 | } 102 | } 103 | } 104 | 105 | void DynamicWallpaperCrawler::visitFile(const QString &filePath) 106 | { 107 | // Not every avif file is a dynamic wallpaper, we need to read the file contents to 108 | // determine whether filePath actually points to a dynamic wallpaper file. 109 | KDynamicWallpaperReader reader(filePath); 110 | if (reader.error() == KDynamicWallpaperReader::NoError) 111 | Q_EMIT foundFile(filePath, token()); 112 | } 113 | 114 | bool DynamicWallpaperCrawler::checkPackage(const QString &filePath) const 115 | { 116 | if (!QFile::exists(filePath + QLatin1String("/metadata.desktop")) && 117 | !QFile::exists(filePath + QLatin1String("/metadata.json"))) 118 | return false; 119 | 120 | KPackage::Package package(packageStructure()); 121 | package.setPath(filePath); 122 | 123 | const QUrl imageUrl = package.fileUrl(QByteArrayLiteral("dynamic")); 124 | return imageUrl.isValid(); 125 | } 126 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpapercrawler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | class DynamicWallpaperCrawler : public QThread 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | explicit DynamicWallpaperCrawler(QObject *parent = nullptr); 20 | ~DynamicWallpaperCrawler() override; 21 | 22 | QUuid token() const; 23 | 24 | void setSearchRoots(const QStringList &candidates); 25 | QStringList searchRoots() const; 26 | 27 | void setPackageStructure(KPackage::PackageStructure *structure); 28 | KPackage::PackageStructure *packageStructure() const; 29 | 30 | Q_SIGNALS: 31 | void foundPackage(const QString &packagePath, const QUuid &token); 32 | void foundFile(const QString &filePath, const QUuid &token); 33 | 34 | protected: 35 | void run() override; 36 | 37 | private: 38 | void visitFolder(const QString &filePath); 39 | void visitFile(const QString &filePath); 40 | 41 | bool checkPackage(const QString &filePath) const; 42 | 43 | KPackage::PackageStructure *m_packageStructure = nullptr; 44 | QStringList m_searchRoots; 45 | QUuid m_token; 46 | }; 47 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperengine.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #include "dynamicwallpaperengine.h" 8 | 9 | /*! 10 | * Destructs the DynamicWallpaperEngine object. 11 | */ 12 | DynamicWallpaperEngine::~DynamicWallpaperEngine() 13 | { 14 | } 15 | 16 | /*! 17 | * Returns \c true if the engine has been expired and must be rebuilt; otherwise returns \c false. 18 | */ 19 | bool DynamicWallpaperEngine::isExpired() const 20 | { 21 | return false; 22 | } 23 | 24 | /*! 25 | * Returns the QUrl of the image that is currently being displayed in the top layer. 26 | */ 27 | QUrl DynamicWallpaperEngine::topLayer() const 28 | { 29 | return m_topLayer; 30 | } 31 | 32 | /*! 33 | * Returns the QUrl of the image that is currently beind displayed in the bottom layer. 34 | */ 35 | QUrl DynamicWallpaperEngine::bottomLayer() const 36 | { 37 | return m_bottomLayer; 38 | } 39 | 40 | /*! 41 | * Returns the blend factor between the bottom layer and the top layer. 42 | */ 43 | qreal DynamicWallpaperEngine::blendFactor() const 44 | { 45 | return m_blendFactor; 46 | } 47 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperengine.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | class DynamicWallpaperEngine 14 | { 15 | public: 16 | virtual ~DynamicWallpaperEngine(); 17 | 18 | virtual void update() = 0; 19 | virtual bool isExpired() const; 20 | 21 | QUrl bottomLayer() const; 22 | QUrl topLayer() const; 23 | qreal blendFactor() const; 24 | 25 | protected: 26 | QUrl m_topLayer; 27 | QUrl m_bottomLayer; 28 | qreal m_blendFactor; 29 | }; 30 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperengine_daynight.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #include "dynamicwallpaperengine_daynight.h" 8 | #include "dynamicwallpaperimagehandle.h" 9 | 10 | #include 11 | 12 | DayNightDynamicWallpaperEngine *DayNightDynamicWallpaperEngine::create(const QList &metadata, 13 | const QUrl &source, 14 | const QGeoCoordinate &location) 15 | { 16 | return new DayNightDynamicWallpaperEngine(metadata, source, location); 17 | } 18 | 19 | DayNightDynamicWallpaperEngine::DayNightDynamicWallpaperEngine(const QList &metadata, 20 | const QUrl &source, 21 | const QGeoCoordinate &location) 22 | : m_location(location) 23 | { 24 | for (const KDynamicWallpaperMetaData &md : metadata) { 25 | const auto &dayNight = std::get(md); 26 | if (dayNight.timeOfDay() == KDayNightDynamicWallpaperMetaData::TimeOfDay::Day) { 27 | m_topLayer = DynamicWallpaperImageHandle(source.toLocalFile(), dayNight.index()).toUrl(); 28 | } else if (dayNight.timeOfDay() == KDayNightDynamicWallpaperMetaData::TimeOfDay::Night) { 29 | m_bottomLayer = DynamicWallpaperImageHandle(source.toLocalFile(), dayNight.index()).toUrl(); 30 | } 31 | } 32 | } 33 | 34 | void DayNightDynamicWallpaperEngine::update() 35 | { 36 | if (m_location.isValid()) { 37 | const KSunPosition sunPosition(QDateTime::currentDateTime(), m_location); 38 | if (sunPosition.isValid()) { 39 | m_blendFactor = (qBound(-6.0, sunPosition.elevation(), 6.0) + 6.0) / 12.0; 40 | return; 41 | } 42 | } 43 | 44 | const QTime currentTime = QTime::currentTime(); 45 | if (currentTime < QTime(6, 0)) { 46 | m_blendFactor = 0; 47 | } else if (currentTime < QTime(7, 0)) { 48 | m_blendFactor = QTime(6, 0).secsTo(currentTime) / 3600.0; 49 | } else if (currentTime < QTime(18, 0)) { 50 | m_blendFactor = 1; 51 | } else if (currentTime < QTime(19, 0)) { 52 | m_blendFactor = currentTime.secsTo(QTime(19, 0)) / 3600.0; 53 | } else { 54 | m_blendFactor = 0; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperengine_daynight.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "dynamicwallpaperengine.h" 10 | 11 | #include 12 | 13 | #include 14 | 15 | class DayNightDynamicWallpaperEngine : public DynamicWallpaperEngine 16 | { 17 | public: 18 | void update() override; 19 | 20 | static DayNightDynamicWallpaperEngine *create(const QList &metadata, 21 | const QUrl &source, 22 | const QGeoCoordinate &location); 23 | 24 | private: 25 | DayNightDynamicWallpaperEngine(const QList &metadata, 26 | const QUrl &source, 27 | const QGeoCoordinate &location); 28 | 29 | QGeoCoordinate m_location; 30 | }; 31 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperengine_solar.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #include "dynamicwallpaperengine_solar.h" 8 | #include "dynamicwallpaperimagehandle.h" 9 | 10 | #include 11 | 12 | SolarDynamicWallpaperEngine::SolarDynamicWallpaperEngine(const QList &metadata, 13 | const QUrl &source, 14 | const KSunPath &sunPath, 15 | const KSunPosition &midnight, 16 | const QGeoCoordinate &location, 17 | const QDateTime &dateTime) 18 | : m_mode(Mode::Normal) 19 | , m_source(source) 20 | , m_sunPath(sunPath) 21 | , m_midnight(midnight) 22 | , m_location(location) 23 | , m_dateTime(dateTime) 24 | { 25 | for (const KDynamicWallpaperMetaData &md : metadata) { 26 | const auto &solar = std::get(md); 27 | m_progressToMetaData.insert(progressForMetaData(solar), solar); 28 | } 29 | } 30 | 31 | SolarDynamicWallpaperEngine::SolarDynamicWallpaperEngine(const QList &metadata, 32 | const QUrl &source, 33 | const QDateTime &dateTime) 34 | : m_mode(Mode::Fallback) 35 | , m_source(source) 36 | , m_dateTime(dateTime) 37 | { 38 | for (const KDynamicWallpaperMetaData &md : metadata) { 39 | const auto &solar = std::get(md); 40 | m_progressToMetaData.insert(progressForMetaData(solar), solar); 41 | } 42 | } 43 | 44 | bool SolarDynamicWallpaperEngine::isExpired() const 45 | { 46 | return m_dateTime.date() != QDate::currentDate(); 47 | } 48 | 49 | static bool checkSolarMetadata(const QList &metadata) 50 | { 51 | return std::all_of(metadata.begin(), metadata.end(), [](auto md) { 52 | const auto &solar = std::get(md); 53 | return solar.fields() & (KSolarDynamicWallpaperMetaData::SolarAzimuthField | KSolarDynamicWallpaperMetaData::SolarElevationField); 54 | }); 55 | } 56 | 57 | SolarDynamicWallpaperEngine *SolarDynamicWallpaperEngine::create(const QList &metadata, 58 | const QUrl &source, 59 | const QGeoCoordinate &location) 60 | { 61 | const QDateTime dateTime = QDateTime::currentDateTime(); 62 | 63 | if (location.isValid() && checkSolarMetadata(metadata)) { 64 | const KSunPosition midnight = KSunPosition::midnight(dateTime, location); 65 | if (midnight.isValid()) { 66 | const KSunPath path = KSunPath::create(dateTime, location); 67 | if (path.isValid()) 68 | return new SolarDynamicWallpaperEngine(metadata, source, path, midnight, location, dateTime); 69 | } 70 | } 71 | 72 | return new SolarDynamicWallpaperEngine(metadata, source, dateTime); 73 | } 74 | 75 | qreal SolarDynamicWallpaperEngine::progressForMetaData(const KSolarDynamicWallpaperMetaData &metaData) const 76 | { 77 | if (m_mode == Mode::Fallback) { 78 | return metaData.time(); 79 | } else { 80 | const KSunPosition position(metaData.solarElevation(), metaData.solarAzimuth()); 81 | return progressForPosition(position); 82 | } 83 | } 84 | 85 | qreal SolarDynamicWallpaperEngine::progressForDateTime(const QDateTime &dateTime) const 86 | { 87 | if (m_mode == Mode::Fallback) { 88 | QDateTime midnight = dateTime; 89 | midnight.setTime(QTime()); 90 | return midnight.secsTo(dateTime) / 86400.0; 91 | } else { 92 | const KSunPosition position(dateTime, m_location); 93 | return progressForPosition(position); 94 | } 95 | } 96 | 97 | qreal SolarDynamicWallpaperEngine::progressForPosition(const KSunPosition &position) const 98 | { 99 | const QVector3D projectedMidnight = m_sunPath.project(m_midnight); 100 | const QVector3D projectedPosition = m_sunPath.project(position); 101 | 102 | const QVector3D v1 = (projectedMidnight - m_sunPath.center()).normalized(); 103 | const QVector3D v2 = (projectedPosition - m_sunPath.center()).normalized(); 104 | 105 | const QVector3D cross = QVector3D::crossProduct(v1, v2); 106 | const float dot = QVector3D::dotProduct(v1, v2); 107 | const float det = QVector3D::dotProduct(m_sunPath.normal(), cross); 108 | 109 | qreal angle = std::atan2(det, dot); 110 | if (angle < 0) 111 | angle += 2 * M_PI; 112 | 113 | return angle / (2 * M_PI); 114 | } 115 | 116 | static qreal computeTimeSpan(qreal from, qreal to) 117 | { 118 | if (to < from) 119 | return (1 - from) + to; 120 | 121 | return to - from; 122 | } 123 | 124 | static qreal computeBlendFactor(qreal from, qreal to, qreal now) 125 | { 126 | const qreal reflectedFrom = 1 - from; 127 | const qreal reflectedTo = 1 - to; 128 | 129 | const qreal totalDuration = computeTimeSpan(from, to); 130 | const qreal totalElapsed = computeTimeSpan(from, now); 131 | 132 | if ((reflectedFrom < from) ^ (reflectedTo < to)) { 133 | if (reflectedFrom < to) { 134 | const qreal threshold = computeTimeSpan(from, reflectedFrom); 135 | if (totalElapsed < threshold) 136 | return 0; 137 | return (totalElapsed - threshold) / (totalDuration - threshold); 138 | } 139 | if (from < reflectedTo) { 140 | const qreal threshold = computeTimeSpan(from, reflectedTo); 141 | if (threshold < totalElapsed) 142 | return 1; 143 | return totalElapsed / threshold; 144 | } 145 | } 146 | 147 | return totalElapsed / totalDuration; 148 | } 149 | 150 | void SolarDynamicWallpaperEngine::update() 151 | { 152 | const qreal progress = progressForDateTime(QDateTime::currentDateTime()); 153 | 154 | QMap::iterator nextImage; 155 | QMap::iterator currentImage; 156 | 157 | nextImage = m_progressToMetaData.upperBound(progress); 158 | if (nextImage == m_progressToMetaData.end()) 159 | nextImage = m_progressToMetaData.begin(); 160 | 161 | if (nextImage == m_progressToMetaData.begin()) 162 | currentImage = std::prev(m_progressToMetaData.end()); 163 | else 164 | currentImage = std::prev(nextImage); 165 | 166 | if (currentImage->crossFadeMode() == KSolarDynamicWallpaperMetaData::CrossFade) { 167 | m_blendFactor = computeBlendFactor(currentImage.key(), nextImage.key(), progress); 168 | } else { 169 | m_blendFactor = 0; 170 | } 171 | 172 | m_topLayer = DynamicWallpaperImageHandle(m_source.toLocalFile(), nextImage->index()).toUrl(); 173 | m_bottomLayer = DynamicWallpaperImageHandle(m_source.toLocalFile(), currentImage->index()).toUrl(); 174 | } 175 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperengine_solar.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "dynamicwallpaperengine.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | class SolarDynamicWallpaperEngine : public DynamicWallpaperEngine 16 | { 17 | public: 18 | bool isExpired() const override; 19 | void update() override; 20 | 21 | static SolarDynamicWallpaperEngine *create(const QList &metadata, 22 | const QUrl &source, 23 | const QGeoCoordinate &location); 24 | 25 | private: 26 | SolarDynamicWallpaperEngine(const QList &metadata, 27 | const QUrl &source, 28 | const KSunPath &sunPath, const KSunPosition &midnight, 29 | const QGeoCoordinate &location, const QDateTime &dateTime); 30 | SolarDynamicWallpaperEngine(const QList &metadata, 31 | const QUrl &source, const QDateTime &dateTime); 32 | 33 | qreal progressForPosition(const KSunPosition &position) const; 34 | qreal progressForMetaData(const KSolarDynamicWallpaperMetaData &metaData) const; 35 | qreal progressForDateTime(const QDateTime &dateTime) const; 36 | 37 | enum class Mode { 38 | Normal, 39 | Fallback, 40 | }; 41 | 42 | Mode m_mode; 43 | QUrl m_source; 44 | QMap m_progressToMetaData; 45 | KSunPath m_sunPath; 46 | KSunPosition m_midnight; 47 | QGeoCoordinate m_location; 48 | QDateTime m_dateTime; 49 | }; 50 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperextensionplugin.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #include "dynamicwallpaperextensionplugin.h" 8 | #include "dynamicwallpaperhandler.h" 9 | #include "dynamicwallpaperimageprovider.h" 10 | #include "dynamicwallpapermodel.h" 11 | #include "dynamicwallpaperpreviewprovider.h" 12 | 13 | #include 14 | 15 | #include 16 | 17 | void DynamicWallpaperExtensionPlugin::registerTypes(const char *uri) 18 | { 19 | qmlRegisterType(uri, 1, 0, "DynamicWallpaperHandler"); 20 | qmlRegisterType(uri, 1, 0, "DynamicWallpaperModel"); 21 | qmlRegisterType(uri, 1, 0, "SystemClockMonitor"); 22 | } 23 | 24 | void DynamicWallpaperExtensionPlugin::initializeEngine(QQmlEngine *engine, const char *uri) 25 | { 26 | Q_UNUSED(uri) 27 | engine->addImageProvider(QLatin1String("dynamic"), new DynamicWallpaperImageProvider); 28 | engine->addImageProvider(QLatin1String("dynamicpreview"), new DynamicWallpaperPreviewProvider); 29 | } 30 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperextensionplugin.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | class DynamicWallpaperExtensionPlugin : public QQmlExtensionPlugin 12 | { 13 | Q_OBJECT 14 | Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") 15 | 16 | public: 17 | void registerTypes(const char *uri) override; 18 | void initializeEngine(QQmlEngine *engine, const char *uri) override; 19 | }; 20 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperglobals.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | class DynamicWallpaperImageAsyncResult 12 | { 13 | public: 14 | DynamicWallpaperImageAsyncResult() {} 15 | explicit DynamicWallpaperImageAsyncResult(const QImage &image) : image(image) {} 16 | explicit DynamicWallpaperImageAsyncResult(const QString &text) : errorString(text) {} 17 | 18 | QImage image; 19 | QString errorString; 20 | }; 21 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperhandler.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #include "config-dynamicwallpaper.h" 8 | 9 | #include "dynamicwallpaperengine_daynight.h" 10 | #include "dynamicwallpaperengine_solar.h" 11 | #include "dynamicwallpaperhandler.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | DynamicWallpaperHandler::DynamicWallpaperHandler(QObject *parent) 20 | : QObject(parent) 21 | , m_updateTimer(new QTimer(this)) 22 | { 23 | m_updateTimer->setInterval(0); 24 | m_updateTimer->setSingleShot(true); 25 | connect(m_updateTimer, &QTimer::timeout, this, &DynamicWallpaperHandler::update); 26 | } 27 | 28 | DynamicWallpaperHandler::~DynamicWallpaperHandler() 29 | { 30 | delete m_engine; 31 | } 32 | 33 | void DynamicWallpaperHandler::setLocation(const QGeoCoordinate &coordinate) 34 | { 35 | if (m_location == coordinate) 36 | return; 37 | m_location = coordinate; 38 | reloadEngine(); 39 | scheduleUpdate(); 40 | Q_EMIT locationChanged(); 41 | } 42 | 43 | QGeoCoordinate DynamicWallpaperHandler::location() const 44 | { 45 | return m_location; 46 | } 47 | 48 | static QUrl locateWallpaper(const QString &name) 49 | { 50 | const QString packagePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, 51 | QStringLiteral("wallpapers/") + name, 52 | QStandardPaths::LocateDirectory); 53 | 54 | KPackage::PackageLoader *packageLoader = KPackage::PackageLoader::self(); 55 | KPackage::Package package = packageLoader->loadPackage(QStringLiteral("Wallpaper/Dynamic")); 56 | package.setPath(packagePath); 57 | if (package.isValid()) 58 | return package.fileUrl(QByteArrayLiteral("dynamic")); 59 | 60 | const QString filePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, 61 | QStringLiteral("wallpapers/") + name, 62 | QStandardPaths::LocateFile); 63 | 64 | return QUrl::fromLocalFile(filePath); 65 | } 66 | 67 | static QUrl defaultLookAndFeelWallpaper() 68 | { 69 | KConfigGroup kdeConfigGroup(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), QStringLiteral("KDE")); 70 | const QString lookAndFeelPackageName = kdeConfigGroup.readEntry("LookAndFeelPackage"); 71 | 72 | KPackage::PackageLoader *packageLoader = KPackage::PackageLoader::self(); 73 | KPackage::Package lookAndFeelPackage = 74 | packageLoader->loadPackage(QStringLiteral("Plasma/LookAndFeel")); 75 | if (!lookAndFeelPackageName.isEmpty()) 76 | lookAndFeelPackage.setPath(lookAndFeelPackageName); 77 | 78 | KSharedConfigPtr lookAndFeelConfig = 79 | KSharedConfig::openConfig(lookAndFeelPackage.filePath("defaults")); 80 | KConfigGroup wallpaperConfigGroup = KConfigGroup(lookAndFeelConfig, QStringLiteral("Dynamic Wallpaper")); 81 | 82 | const QString wallpaperName = wallpaperConfigGroup.readEntry("Image"); 83 | if (wallpaperName.isEmpty()) 84 | return QUrl(); 85 | 86 | return locateWallpaper(wallpaperName); 87 | } 88 | 89 | static QUrl defaultFallbackWallpaper() 90 | { 91 | return locateWallpaper(QStringLiteral(FALLBACK_WALLPAPER)); 92 | } 93 | 94 | static QUrl defaultWallpaper() 95 | { 96 | QUrl fileUrl = defaultLookAndFeelWallpaper(); 97 | if (fileUrl.isValid()) 98 | return fileUrl; 99 | return defaultFallbackWallpaper(); 100 | } 101 | 102 | void DynamicWallpaperHandler::setSource(const QUrl &url) 103 | { 104 | const QUrl source = url.isValid() ? url : defaultWallpaper(); 105 | if (m_source == source) 106 | return; 107 | m_source = source; 108 | reloadDescription(); 109 | reloadEngine(); 110 | scheduleUpdate(); 111 | Q_EMIT sourceChanged(); 112 | } 113 | 114 | QUrl DynamicWallpaperHandler::source() const 115 | { 116 | return m_source; 117 | } 118 | 119 | void DynamicWallpaperHandler::setTopLayer(const QUrl &url) 120 | { 121 | if (m_topLayer == url) 122 | return; 123 | m_topLayer = url; 124 | Q_EMIT topLayerChanged(); 125 | } 126 | 127 | QUrl DynamicWallpaperHandler::topLayer() const 128 | { 129 | return m_topLayer; 130 | } 131 | 132 | void DynamicWallpaperHandler::setBottomLayer(const QUrl &url) 133 | { 134 | if (m_bottomLayer == url) 135 | return; 136 | m_bottomLayer = url; 137 | Q_EMIT bottomLayerChanged(); 138 | } 139 | 140 | QUrl DynamicWallpaperHandler::bottomLayer() const 141 | { 142 | return m_bottomLayer; 143 | } 144 | 145 | void DynamicWallpaperHandler::setBlendFactor(qreal blendFactor) 146 | { 147 | if (m_blendFactor == blendFactor) 148 | return; 149 | m_blendFactor = blendFactor; 150 | Q_EMIT blendFactorChanged(); 151 | } 152 | 153 | qreal DynamicWallpaperHandler::blendFactor() const 154 | { 155 | return m_blendFactor; 156 | } 157 | 158 | void DynamicWallpaperHandler::setStatus(Status status) 159 | { 160 | if (m_status == status) 161 | return; 162 | m_status = status; 163 | Q_EMIT statusChanged(); 164 | } 165 | 166 | DynamicWallpaperHandler::Status DynamicWallpaperHandler::status() const 167 | { 168 | return m_status; 169 | } 170 | 171 | void DynamicWallpaperHandler::setErrorString(const QString &text) 172 | { 173 | if (m_errorString == text) 174 | return; 175 | m_errorString = text; 176 | Q_EMIT errorStringChanged(); 177 | } 178 | 179 | QString DynamicWallpaperHandler::errorString() const 180 | { 181 | return m_errorString; 182 | } 183 | 184 | void DynamicWallpaperHandler::scheduleUpdate() 185 | { 186 | m_updateTimer->start(); 187 | } 188 | 189 | void DynamicWallpaperHandler::update() 190 | { 191 | if (m_status != Ready) 192 | return; 193 | if (!m_engine || m_engine->isExpired()) 194 | reloadEngine(); 195 | 196 | m_engine->update(); 197 | 198 | QUrl topLayer = m_engine->topLayer(); 199 | if (m_engine->blendFactor() == 0) 200 | topLayer = QUrl(); 201 | 202 | QUrl bottomLayer = m_engine->bottomLayer(); 203 | if (m_engine->blendFactor() == 1) 204 | bottomLayer = QUrl(); 205 | 206 | setTopLayer(topLayer); 207 | setBottomLayer(bottomLayer); 208 | setBlendFactor(m_engine->blendFactor()); 209 | } 210 | 211 | void DynamicWallpaperHandler::reloadDescription() 212 | { 213 | const QString fileName = m_source.toLocalFile(); 214 | 215 | m_metadata = KDynamicWallpaperReader(fileName).metaData(); 216 | 217 | if (!m_metadata.isEmpty()) { 218 | setStatus(Ready); 219 | } else { 220 | setErrorString(i18n("%1 is not a dynamic wallpaper", fileName)); 221 | setStatus(Error); 222 | } 223 | } 224 | 225 | static bool isSolar(const QList &metadata) 226 | { 227 | return std::all_of(metadata.constBegin(), metadata.constEnd(), [](const auto md) { 228 | return std::holds_alternative(md); 229 | }); 230 | } 231 | 232 | static bool isDayNight(const QList &metadata) 233 | { 234 | return std::all_of(metadata.constBegin(), metadata.constEnd(), [](const auto md) { 235 | return std::holds_alternative(md); 236 | }); 237 | } 238 | 239 | void DynamicWallpaperHandler::reloadEngine() 240 | { 241 | delete m_engine; 242 | m_engine = nullptr; 243 | 244 | if (m_metadata.isEmpty()) 245 | return; 246 | 247 | if (isSolar(m_metadata)) { 248 | m_engine = SolarDynamicWallpaperEngine::create(m_metadata, m_source, m_location); 249 | } else if (isDayNight(m_metadata)) { 250 | m_engine = DayNightDynamicWallpaperEngine::create(m_metadata, m_source, m_location); 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperhandler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | class DynamicWallpaperEngine; 16 | 17 | class DynamicWallpaperHandler : public QObject 18 | { 19 | Q_OBJECT 20 | Q_PROPERTY(QGeoCoordinate location READ location WRITE setLocation NOTIFY locationChanged) 21 | Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) 22 | Q_PROPERTY(QUrl topLayer READ topLayer WRITE setTopLayer NOTIFY topLayerChanged) 23 | Q_PROPERTY(QUrl bottomLayer READ bottomLayer WRITE setBottomLayer NOTIFY bottomLayerChanged) 24 | Q_PROPERTY(qreal blendFactor READ blendFactor WRITE setBlendFactor NOTIFY blendFactorChanged) 25 | Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged) 26 | Q_PROPERTY(Status status READ status NOTIFY statusChanged) 27 | 28 | public: 29 | enum Status { Null, Ready, Error }; 30 | Q_ENUM(Status) 31 | 32 | explicit DynamicWallpaperHandler(QObject *parent = nullptr); 33 | ~DynamicWallpaperHandler() override; 34 | 35 | void setLocation(const QGeoCoordinate &coordinate); 36 | QGeoCoordinate location() const; 37 | 38 | void setSource(const QUrl &url); 39 | QUrl source() const; 40 | 41 | void setTopLayer(const QUrl &url); 42 | QUrl topLayer() const; 43 | 44 | void setBottomLayer(const QUrl &url); 45 | QUrl bottomLayer() const; 46 | 47 | void setBlendFactor(qreal blendFactor); 48 | qreal blendFactor() const; 49 | 50 | void setStatus(Status status); 51 | Status status() const; 52 | 53 | void setErrorString(const QString &text); 54 | QString errorString() const; 55 | 56 | public Q_SLOTS: 57 | void scheduleUpdate(); 58 | void update(); 59 | 60 | Q_SIGNALS: 61 | void locationChanged(); 62 | void sourceChanged(); 63 | void topLayerChanged(); 64 | void bottomLayerChanged(); 65 | void blendFactorChanged(); 66 | void statusChanged(); 67 | void errorStringChanged(); 68 | 69 | private: 70 | void reloadDescription(); 71 | void reloadEngine(); 72 | 73 | DynamicWallpaperEngine *m_engine = nullptr; 74 | QList m_metadata; 75 | QTimer *m_updateTimer; 76 | QGeoCoordinate m_location; 77 | QString m_errorString; 78 | QUrl m_source; 79 | QUrl m_topLayer; 80 | QUrl m_bottomLayer; 81 | qreal m_blendFactor = 0; 82 | Status m_status = Null; 83 | }; 84 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperimagehandle.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #include "dynamicwallpaperimagehandle.h" 8 | 9 | #include 10 | 11 | /*! 12 | * Constructs an invalid DynamicWallpaperImageHandle object. 13 | */ 14 | DynamicWallpaperImageHandle::DynamicWallpaperImageHandle() 15 | : m_imageIndex(-1) 16 | { 17 | } 18 | 19 | /*! 20 | * Constructs a DynamicWallpaperImageHandle object with the given \p fileName and image \p index. 21 | */ 22 | DynamicWallpaperImageHandle::DynamicWallpaperImageHandle(const QString &fileName, int index) 23 | : m_fileName(fileName) 24 | , m_imageIndex(index) 25 | { 26 | } 27 | 28 | /*! 29 | * Returns \c true if the image handle is valid; otherwise returns \c false. 30 | */ 31 | bool DynamicWallpaperImageHandle::isValid() const 32 | { 33 | return !m_fileName.isEmpty() && m_imageIndex != -1; 34 | } 35 | 36 | /*! 37 | * Sets the file name of the image handle to \p fileName. 38 | */ 39 | void DynamicWallpaperImageHandle::setFileName(const QString &fileName) 40 | { 41 | m_fileName = fileName; 42 | } 43 | 44 | /*! 45 | * Returns the file name of the image handle. 46 | */ 47 | QString DynamicWallpaperImageHandle::fileName() const 48 | { 49 | return m_fileName; 50 | } 51 | 52 | /*! 53 | * Sets the image index of the image handle to \p index. 54 | */ 55 | void DynamicWallpaperImageHandle::setImageIndex(int index) 56 | { 57 | m_imageIndex = index; 58 | } 59 | 60 | /*! 61 | * Returns the image index of the image handle. 62 | */ 63 | int DynamicWallpaperImageHandle::imageIndex() const 64 | { 65 | return m_imageIndex; 66 | } 67 | 68 | static QString fileNameFromBase64(QStringView base64) 69 | { 70 | return QString::fromUtf8(QByteArray::fromBase64(base64.toUtf8())); 71 | } 72 | 73 | static QString base64FromFileName(QStringView fileName) 74 | { 75 | return QString::fromLatin1(fileName.toUtf8().toBase64()); 76 | } 77 | 78 | static int imageIndexFromString(QStringView string) 79 | { 80 | bool ok; 81 | const int imageIndex = string.toInt(&ok); 82 | if (ok) 83 | return imageIndex; 84 | return -1; 85 | } 86 | 87 | static QString stringFromImageIndex(int imageIndex) 88 | { 89 | return QString::number(imageIndex); 90 | } 91 | 92 | /*! 93 | * Converts the value of the image handle to a QString and returns it. 94 | */ 95 | QString DynamicWallpaperImageHandle::toString() const 96 | { 97 | const QString fileName = base64FromFileName(m_fileName); 98 | const QString imageIndex = stringFromImageIndex(m_imageIndex); 99 | return fileName + QLatin1Char('#') + imageIndex; 100 | } 101 | 102 | /*! 103 | * Converts the value of the image handle to a QUrl which can be passed to the Image QML component. 104 | */ 105 | QUrl DynamicWallpaperImageHandle::toUrl() const 106 | { 107 | return QUrl(QLatin1String("image://dynamic/") + toString()); 108 | } 109 | 110 | /*! 111 | * Creates a DynamicWallpaperImageHandle from the specified string \p string. 112 | */ 113 | DynamicWallpaperImageHandle DynamicWallpaperImageHandle::fromString(const QString &string) 114 | { 115 | DynamicWallpaperImageHandle handle; 116 | 117 | const QStringList parts = string.split(QLatin1Char('#'), Qt::SkipEmptyParts); 118 | if (parts.count() != 2) 119 | return handle; 120 | 121 | // Encoding and decoding a file name to/from base64 is definitely an overkill, but I don't 122 | // want to deal with os-specific file path conventions. Sue me. :/ 123 | 124 | handle.setFileName(fileNameFromBase64(parts[0])); 125 | handle.setImageIndex(imageIndexFromString(parts[1])); 126 | 127 | return handle; 128 | } 129 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperimagehandle.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | class DynamicWallpaperImageHandle 13 | { 14 | public: 15 | DynamicWallpaperImageHandle(); 16 | DynamicWallpaperImageHandle(const QString &fileName, int index); 17 | 18 | bool isValid() const; 19 | 20 | void setFileName(const QString &fileName); 21 | QString fileName() const; 22 | 23 | void setImageIndex(int index); 24 | int imageIndex() const; 25 | 26 | QString toString() const; 27 | QUrl toUrl() const; 28 | 29 | static DynamicWallpaperImageHandle fromString(const QString &string); 30 | 31 | private: 32 | QString m_fileName; 33 | int m_imageIndex; 34 | }; 35 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperimageprovider.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #include "dynamicwallpaperimageprovider.h" 8 | #include "dynamicwallpaperglobals.h" 9 | #include "dynamicwallpaperimagehandle.h" 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | static DynamicWallpaperImageAsyncResult load(const QString &fileName, 17 | int index, 18 | const QSize &requestedSize, 19 | const QQuickImageProviderOptions &options) 20 | { 21 | const KDynamicWallpaperReader reader(fileName); 22 | if (reader.error() != KDynamicWallpaperReader::NoError) 23 | return DynamicWallpaperImageAsyncResult(reader.errorString()); 24 | 25 | const QImage image = reader.image(index); 26 | const QSize effectiveSize = QQuickImageProviderWithOptions::loadSize(image.size(), 27 | requestedSize, 28 | QByteArrayLiteral("avif"), 29 | options); 30 | 31 | return DynamicWallpaperImageAsyncResult(image.scaled(effectiveSize, 32 | Qt::IgnoreAspectRatio, 33 | Qt::SmoothTransformation)); 34 | } 35 | 36 | class DynamicWallpaperAsyncImageResponse : public QQuickImageResponse 37 | { 38 | public: 39 | DynamicWallpaperAsyncImageResponse(const QString &fileName, 40 | int index, 41 | const QSize &requestedSize, 42 | const QQuickImageProviderOptions &options); 43 | 44 | QQuickTextureFactory *textureFactory() const override; 45 | QString errorString() const override; 46 | 47 | private Q_SLOTS: 48 | void handleFinished(); 49 | 50 | private: 51 | QFutureWatcher *m_watcher; 52 | QImage m_image; 53 | QString m_errorString; 54 | }; 55 | 56 | DynamicWallpaperAsyncImageResponse::DynamicWallpaperAsyncImageResponse(const QString &fileName, 57 | int index, 58 | const QSize &requestedSize, 59 | const QQuickImageProviderOptions &options) 60 | { 61 | m_watcher = new QFutureWatcher(this); 62 | connect(m_watcher, &QFutureWatcher::finished, 63 | this, &DynamicWallpaperAsyncImageResponse::handleFinished); 64 | m_watcher->setFuture(QtConcurrent::run(load, fileName, index, requestedSize, options)); 65 | } 66 | 67 | void DynamicWallpaperAsyncImageResponse::handleFinished() 68 | { 69 | const DynamicWallpaperImageAsyncResult result = m_watcher->result(); 70 | 71 | if (result.errorString.isEmpty()) 72 | m_image = result.image; 73 | else 74 | m_errorString = result.errorString; 75 | 76 | Q_EMIT finished(); 77 | } 78 | 79 | QQuickTextureFactory *DynamicWallpaperAsyncImageResponse::textureFactory() const 80 | { 81 | return QQuickTextureFactory::textureFactoryForImage(m_image); 82 | } 83 | 84 | QString DynamicWallpaperAsyncImageResponse::errorString() const 85 | { 86 | return m_errorString; 87 | } 88 | 89 | DynamicWallpaperImageProvider::DynamicWallpaperImageProvider() 90 | : QQuickImageProviderWithOptions(ImageType::ImageResponse) 91 | { 92 | } 93 | 94 | QQuickImageResponse *DynamicWallpaperImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize, const QQuickImageProviderOptions &options) 95 | { 96 | const DynamicWallpaperImageHandle handle = DynamicWallpaperImageHandle::fromString(id); 97 | return new DynamicWallpaperAsyncImageResponse(handle.fileName(), handle.imageIndex(), requestedSize, options); 98 | } 99 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperimageprovider.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include // TODO: Use public API in Qt 6 10 | 11 | class DynamicWallpaperImageProvider : public QQuickImageProviderWithOptions 12 | { 13 | public: 14 | DynamicWallpaperImageProvider(); 15 | 16 | QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize, 17 | const QQuickImageProviderOptions &options) override; 18 | }; 19 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpapermodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | class DynamicWallpaperModelPrivate; 13 | 14 | class DynamicWallpaperModel : public QAbstractListModel 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | enum Roles { 20 | WallpaperNameRole = Qt::UserRole + 1, 21 | WallpaperFolderRole = Qt::UserRole + 2, 22 | WallpaperLicenseRole = Qt::UserRole + 3, 23 | WallpaperAuthorRole = Qt::UserRole + 4, 24 | WallpaperIsPackageRole = Qt::UserRole + 5, 25 | WallpaperIsCustomRole = Qt::UserRole + 6, 26 | WallpaperIsRemovableRole = Qt::UserRole + 7, 27 | WallpaperIsZombieRole = Qt::UserRole + 8, 28 | WallpaperImageRole = Qt::UserRole + 9, 29 | WallpaperPreviewRole = Qt::UserRole + 10, 30 | }; 31 | 32 | explicit DynamicWallpaperModel(QObject *parent = nullptr); 33 | ~DynamicWallpaperModel() override; 34 | 35 | QHash roleNames() const override; 36 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; 37 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; 38 | 39 | Q_INVOKABLE int find(const QUrl &url) const; 40 | Q_INVOKABLE QModelIndex modelIndex(int index) const; 41 | 42 | public Q_SLOTS: 43 | void reload(); 44 | void purge(); 45 | 46 | void add(const QUrl &fileUrl); 47 | void scheduleRemove(const QModelIndex &index); 48 | void unscheduleRemove(const QModelIndex &index); 49 | void remove(const QModelIndex &index); 50 | 51 | private Q_SLOTS: 52 | void handleProberFinished(const QUrl &fileUrl); 53 | void handleProberFailed(const QUrl &fileUrl); 54 | 55 | Q_SIGNALS: 56 | void errorOccurred(const QString &text); 57 | 58 | private: 59 | friend class DynamicWallpaperModelPrivate; 60 | QScopedPointer d; 61 | }; 62 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperpreviewcache.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #include "dynamicwallpaperpreviewcache.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | static QString cacheKey(const QString &fileName) 16 | { 17 | QCryptographicHash hash(QCryptographicHash::Sha1); 18 | hash.addData(QFile::encodeName(fileName)); 19 | return QString::fromLatin1(hash.result().toHex()) + QStringLiteral(".png"); 20 | } 21 | 22 | static QString cacheRoot() 23 | { 24 | QString cache = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); 25 | return cache + QLatin1String("/kdynamicwallpaper/"); 26 | } 27 | 28 | static QString cacheFileName(const QString &fileName) 29 | { 30 | return cacheRoot() + cacheKey(fileName); 31 | } 32 | 33 | /*! 34 | * Loads the preview for a wallpaper with the specified \a fileName from the cache. 35 | * 36 | * If the cache has no preview for a wallpaper with the given \a fileName or the cached preview 37 | * image is outdated, this method will return a null QImage object. 38 | * 39 | * This function can be called from multiple threads simultaneously. 40 | */ 41 | QImage DynamicWallpaperPreviewCache::load(const QString &fileName) 42 | { 43 | QImage image(cacheFileName(fileName)); 44 | if (image.isNull()) 45 | return QImage(); 46 | 47 | const qint64 lastCreated = image.text(QStringLiteral("Preview:Timestamp")).toLongLong(); 48 | const qint64 lastModified = QFileInfo(fileName).lastModified().toSecsSinceEpoch(); 49 | 50 | if (lastModified > lastCreated) 51 | return QImage(); 52 | 53 | return image; 54 | } 55 | 56 | /*! 57 | * Stores the preview \a image for a wallpaper with the specified \a fileName in the cache. 58 | * 59 | * This function can be called from multiple threads simultaneously. 60 | */ 61 | void DynamicWallpaperPreviewCache::store(const QImage &image, const QString &fileName) 62 | { 63 | const QDir cache(cacheRoot()); 64 | if (!cache.exists()) 65 | cache.mkpath(QStringLiteral(".")); 66 | 67 | const qint64 modifiedTimestamp = QFileInfo(fileName).lastModified().toSecsSinceEpoch(); 68 | 69 | QImage scaled = image.scaled(512, 512, Qt::KeepAspectRatio, Qt::SmoothTransformation); 70 | scaled.setText(QStringLiteral("Preview:Timestamp"), QString::number(modifiedTimestamp)); 71 | scaled.save(cacheFileName(fileName)); 72 | } 73 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperpreviewcache.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | class DynamicWallpaperPreviewCache 12 | { 13 | public: 14 | static QImage load(const QString &fileName); 15 | static void store(const QImage &image, const QString &fileName); 16 | }; 17 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperpreviewjob.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #include "dynamicwallpaperpreviewjob.h" 8 | #include "dynamicwallpaperglobals.h" 9 | #include "dynamicwallpaperpreviewcache.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | /*! 20 | * \class DynamicWallpaperPreviewJob 21 | * \brief The DynamicWallpaperPreviewJob provides a convenient way for getting dynamic 22 | * wallpaper thumbnail images. 23 | * 24 | * The DynamicWallpaperPreviewJob allows to load or generate preview images for wallpapers 25 | * asynchronously. After a preview image has been generated, the finished() signal will be 26 | * emitted. 27 | * 28 | * If for whatever reason, the preview job wasn't able to successfully get a preview image, 29 | * the failed() signal will be emitted. 30 | * 31 | * After the finished() or the failed() signal has been emitted, the preview job object will 32 | * be destroyed automatically. 33 | */ 34 | 35 | class DynamicWallpaperPreviewJobPrivate 36 | { 37 | public: 38 | QFutureWatcher *watcher; 39 | }; 40 | 41 | static QRgb blend(QRgb a, QRgb b, qreal blendFactor) 42 | { 43 | const int alpha = qAlpha(a) * (1 - blendFactor) + qAlpha(b) * blendFactor; 44 | const int red = qRed(a) * (1 - blendFactor) + qRed(b) * blendFactor; 45 | const int blue = qBlue(a) * (1 - blendFactor) + qBlue(b) * blendFactor; 46 | const int green = qGreen(a) * (1 - blendFactor) + qGreen(b) * blendFactor; 47 | 48 | return qRgba(red, green, blue, alpha); 49 | } 50 | 51 | static QImage ensureArgb32(const QImage &image) 52 | { 53 | return image.convertToFormat(QImage::Format_ARGB32_Premultiplied); 54 | } 55 | 56 | static QImage blend(const QImage &dark, const QImage &light, qreal delta) 57 | { 58 | // Note that the dark and the light images may have different dimensions. 59 | const int width = std::max(dark.width(), light.width()); 60 | const int height = std::max(dark.height(), light.height()); 61 | 62 | const QImage a = ensureArgb32(dark.scaled(width, height)); 63 | const QImage b = ensureArgb32(light.scaled(width, height)); 64 | 65 | const QEasingCurve blendCurve(QEasingCurve::InOutQuad); 66 | const int blendFrom = std::floor(width * (1 - delta) / 2); 67 | const int blendTo = std::ceil(width * (1 + delta) / 2); 68 | 69 | QVector blendFactorTable(width); 70 | for (int i = 0; i < width; ++i) { 71 | const qreal progress = qreal(i - blendFrom) / (blendTo - blendFrom); 72 | blendFactorTable[i] = blendCurve.valueForProgress(progress); 73 | } 74 | 75 | QImage result(width, height, QImage::Format_ARGB32_Premultiplied); 76 | 77 | for (int i = 0; i < height; ++i) { 78 | const uint32_t *in0 = reinterpret_cast(a.scanLine(i)); 79 | const uint32_t *in1 = reinterpret_cast(b.scanLine(i)); 80 | uint32_t *out = reinterpret_cast(result.scanLine(i)); 81 | 82 | for (int j = 0; j < width; ++j) 83 | *(out++) = blend(*(in0++), *(in1++), blendFactorTable[j]); 84 | } 85 | 86 | return result; 87 | } 88 | 89 | /*! 90 | * \internal 91 | * 92 | * Returns the approximate solar elevation for the specified wallpaper \a metadata. 93 | */ 94 | static qreal scoreForMetaData(const KDynamicWallpaperMetaData &metadata) 95 | { 96 | const auto &solar = std::get(metadata); 97 | if (solar.fields() & KSolarDynamicWallpaperMetaData::SolarElevationField) 98 | return solar.solarElevation() / 90; 99 | return std::cos(M_PI * (2 * solar.time() + 1)); 100 | } 101 | 102 | static bool score_compare(const KDynamicWallpaperMetaData &a, const KDynamicWallpaperMetaData &b) 103 | { 104 | return scoreForMetaData(a) < scoreForMetaData(b); 105 | } 106 | 107 | static bool isSolar(const QList &metadata) 108 | { 109 | return std::all_of(metadata.constBegin(), metadata.constEnd(), [](auto md) { 110 | return std::holds_alternative(md); 111 | }); 112 | } 113 | 114 | static bool isDayNight(const QList &metadata) 115 | { 116 | return std::all_of(metadata.constBegin(), metadata.constEnd(), [](auto md) { 117 | return std::holds_alternative(md); 118 | }); 119 | } 120 | 121 | /*! 122 | * \internal 123 | * 124 | * Creates a preview image for the given wallpaper \a fileName with the specified \a size. 125 | * 126 | * Note that this function runs off the main thread. 127 | */ 128 | static DynamicWallpaperImageAsyncResult makePreview(const QString &fileName, const QSize &size) 129 | { 130 | QImage preview = DynamicWallpaperPreviewCache::load(fileName); 131 | 132 | if (preview.isNull()) { 133 | // The cache has no preview for the specified wallpaper yet, so generate one... 134 | KDynamicWallpaperReader reader(fileName); 135 | if (reader.error() != KDynamicWallpaperReader::NoError) 136 | return DynamicWallpaperImageAsyncResult(reader.errorString()); 137 | 138 | const QList metadata = reader.metaData(); 139 | if (metadata.isEmpty()) 140 | return DynamicWallpaperImageAsyncResult(i18n("Not a dynamic wallpaper")); 141 | 142 | int darkIndex = 0; 143 | int lightIndex = 0; 144 | 145 | if (isSolar(metadata)) { 146 | auto dark = std::min_element(metadata.begin(), metadata.end(), score_compare); 147 | auto light = std::max_element(metadata.begin(), metadata.end(), score_compare); 148 | darkIndex = std::get(*dark).index(); 149 | lightIndex = std::get(*light).index(); 150 | } else if (isDayNight(metadata)) { 151 | for (const KDynamicWallpaperMetaData &md : metadata) { 152 | const auto &dayNight = std::get(md); 153 | switch (dayNight.timeOfDay()) { 154 | case KDayNightDynamicWallpaperMetaData::TimeOfDay::Day: 155 | lightIndex = dayNight.index(); 156 | break; 157 | case KDayNightDynamicWallpaperMetaData::TimeOfDay::Night: 158 | darkIndex = dayNight.index(); 159 | break; 160 | } 161 | } 162 | } 163 | 164 | const QImage darkImage = reader.image(darkIndex); 165 | if (darkImage.isNull()) 166 | return DynamicWallpaperImageAsyncResult(reader.errorString()); 167 | 168 | const QImage lightImage = reader.image(lightIndex); 169 | if (lightImage.isNull()) 170 | return DynamicWallpaperImageAsyncResult(reader.errorString()); 171 | 172 | preview = blend(darkImage, lightImage, 0.5); 173 | DynamicWallpaperPreviewCache::store(preview, fileName); 174 | } 175 | 176 | return DynamicWallpaperImageAsyncResult(preview.scaled(size, Qt::KeepAspectRatio)); 177 | } 178 | 179 | /*! 180 | * Constructs a DynamicWallpaperPreviewJob with the specified \a fileName and \a requestedSize. 181 | */ 182 | DynamicWallpaperPreviewJob::DynamicWallpaperPreviewJob(const QString &fileName, const QSize &requestedSize) 183 | : d(new DynamicWallpaperPreviewJobPrivate) 184 | { 185 | d->watcher = new QFutureWatcher(this); 186 | connect(d->watcher, &QFutureWatcher::finished, 187 | this, &DynamicWallpaperPreviewJob::handleFinished); 188 | d->watcher->setFuture(QtConcurrent::run(makePreview, fileName, requestedSize)); 189 | } 190 | 191 | /*! 192 | * Destructs the DynamicWallpaperPreviewJob object. 193 | */ 194 | DynamicWallpaperPreviewJob::~DynamicWallpaperPreviewJob() 195 | { 196 | } 197 | 198 | /*! 199 | * \fn void DynamicWallpaperPreviewJob::finished(const QImage &image) 200 | * 201 | * This signal is emitted when the preview job has successfully created a preview \a image. 202 | * 203 | * The DynamicWallpaperPreviewJob object will be destroyed after the finished() signal has 204 | * been emitted. 205 | */ 206 | 207 | /*! 208 | * \fn void DynamicWallpaperPreviewJob::failed(const QString &errorString) 209 | * 210 | * This signal is emitted to indicate that the preview job has failed to generate a preview 211 | * image. The specified \a errorString is the description of the error that occurred. 212 | * 213 | * The DynamicWallpaperPreviewJob object will be destroyed after the failed() signal has been 214 | * emitted. 215 | */ 216 | 217 | void DynamicWallpaperPreviewJob::handleFinished() 218 | { 219 | const DynamicWallpaperImageAsyncResult response = d->watcher->result(); 220 | if (response.errorString.isNull()) 221 | Q_EMIT finished(response.image); 222 | else 223 | Q_EMIT failed(response.errorString); 224 | 225 | deleteLater(); 226 | } 227 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperpreviewjob.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | class DynamicWallpaperPreviewJobPrivate; 15 | 16 | class DynamicWallpaperPreviewJob : public QObject 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | DynamicWallpaperPreviewJob(const QString &fileName, const QSize &size); 22 | ~DynamicWallpaperPreviewJob() override; 23 | 24 | Q_SIGNALS: 25 | void finished(const QImage &image); 26 | void failed(const QString &errorString); 27 | 28 | private Q_SLOTS: 29 | void handleFinished(); 30 | 31 | private: 32 | QScopedPointer d; 33 | }; 34 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperpreviewprovider.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #include "dynamicwallpaperpreviewprovider.h" 8 | #include "dynamicwallpaperpreviewjob.h" 9 | 10 | #include 11 | 12 | class AsyncImageResponse : public QQuickImageResponse 13 | { 14 | public: 15 | AsyncImageResponse(const QString &fileName, const QSize &requestedSize); 16 | 17 | QString errorString() const override; 18 | QQuickTextureFactory *textureFactory() const override; 19 | 20 | private Q_SLOTS: 21 | void handleFinished(const QImage &image); 22 | void handleFailed(const QString &errorString); 23 | 24 | private: 25 | QString m_errorString; 26 | QImage m_image; 27 | }; 28 | 29 | AsyncImageResponse::AsyncImageResponse(const QString &fileName, const QSize &requestedSize) 30 | { 31 | QSize desiredSize = requestedSize; 32 | if (desiredSize.isEmpty()) 33 | desiredSize = QSize(400, 250) * qApp->devicePixelRatio(); 34 | 35 | DynamicWallpaperPreviewJob *job = new DynamicWallpaperPreviewJob(fileName, desiredSize); 36 | 37 | connect(job, &DynamicWallpaperPreviewJob::finished, this, &AsyncImageResponse::handleFinished); 38 | connect(job, &DynamicWallpaperPreviewJob::failed, this, &AsyncImageResponse::handleFailed); 39 | } 40 | 41 | QString AsyncImageResponse::errorString() const 42 | { 43 | return m_errorString; 44 | } 45 | 46 | QQuickTextureFactory *AsyncImageResponse::textureFactory() const 47 | { 48 | return QQuickTextureFactory::textureFactoryForImage(m_image); 49 | } 50 | 51 | void AsyncImageResponse::handleFinished(const QImage &image) 52 | { 53 | m_image = image; 54 | Q_EMIT finished(); 55 | } 56 | 57 | void AsyncImageResponse::handleFailed(const QString &errorString) 58 | { 59 | m_errorString = errorString; 60 | Q_EMIT finished(); 61 | } 62 | 63 | static QString fileNameFromBase64(const QString &base64) 64 | { 65 | return QString::fromUtf8(QByteArray::fromBase64(base64.toUtf8())); 66 | } 67 | 68 | QQuickImageResponse *DynamicWallpaperPreviewProvider::requestImageResponse(const QString &id, const QSize &requestedSize) 69 | { 70 | return new AsyncImageResponse(fileNameFromBase64(id), requestedSize); 71 | } 72 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperpreviewprovider.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | class DynamicWallpaperPreviewProvider : public QQuickAsyncImageProvider 12 | { 13 | public: 14 | QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override; 15 | }; 16 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperprober.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #include "dynamicwallpaperprober.h" 8 | 9 | #include 10 | 11 | /*! 12 | * \class DynamicWallpaperProber 13 | * \brief The DynamicWallpaperProper class provides a convenient way to asynchronously 14 | * check whether the specified file url corresponds to a dynamic wallpaper. 15 | * 16 | * If the specified file url is not a dynamic wallpaper, the failed() signal is emitted; 17 | * otherwise the finished() signal is emitted. 18 | * 19 | * After either the failed() or the finished() signal has been emitted, the prober object 20 | * will be destroyed automatically. 21 | */ 22 | 23 | /*! 24 | * Constructs a dynamic wallpaper prober with the specified \a fileUrl and \a parent. 25 | */ 26 | DynamicWallpaperProber::DynamicWallpaperProber(const QUrl &fileUrl, QObject *parent) 27 | : QThread(parent) 28 | , m_fileUrl(fileUrl) 29 | { 30 | } 31 | 32 | /*! 33 | * Destructs the DynamicWallpaperProber object. 34 | */ 35 | DynamicWallpaperProber::~DynamicWallpaperProber() 36 | { 37 | wait(); 38 | } 39 | 40 | void DynamicWallpaperProber::run() 41 | { 42 | const KDynamicWallpaperReader reader(m_fileUrl.toLocalFile()); 43 | if (reader.error() == KDynamicWallpaperReader::NoError) 44 | Q_EMIT finished(m_fileUrl); 45 | else 46 | Q_EMIT failed(m_fileUrl); 47 | 48 | deleteLater(); 49 | } 50 | -------------------------------------------------------------------------------- /src/declarative/dynamicwallpaperprober.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | class DynamicWallpaperProber : public QThread 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | explicit DynamicWallpaperProber(const QUrl &fileUrl, QObject *parent = nullptr); 18 | ~DynamicWallpaperProber() override; 19 | 20 | protected: 21 | void run() override; 22 | 23 | Q_SIGNALS: 24 | void finished(const QUrl &fileUrl); 25 | void failed(const QUrl &fileUrl); 26 | 27 | private: 28 | QUrl m_fileUrl; 29 | }; 30 | -------------------------------------------------------------------------------- /src/declarative/qmldir: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | module com.github.zzag.plasma.wallpapers.dynamic 6 | plugin plasma_wallpaper_dynamicplugin 7 | -------------------------------------------------------------------------------- /src/lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | add_definitions(-DTRANSLATION_DOMAIN=\"plasma_wallpaper_com.github.zzag.dynamic\") 6 | 7 | set(dynamicwallpaperlib_SOURCES 8 | kdaynightdynamicwallpapermetadata.cpp 9 | kdynamicwallpapermetadata.cpp 10 | kdynamicwallpaperreader.cpp 11 | kdynamicwallpaperwriter.cpp 12 | ksolardynamicwallpapermetadata.cpp 13 | ksunpath.cpp 14 | ksunposition.cpp 15 | ksystemclockmonitor.cpp 16 | ksystemclockmonitorengine.cpp 17 | ) 18 | 19 | if (CMAKE_SYSTEM_NAME MATCHES "Linux") 20 | set(dynamicwallpaperlib_SOURCES 21 | ksystemclockmonitorengine_linux.cpp 22 | ${dynamicwallpaperlib_SOURCES} 23 | ) 24 | endif() 25 | 26 | ecm_generate_headers(dynamicwallpaperlib_HEADERS 27 | HEADER_NAMES 28 | KDayNightDynamicWallpaperMetaData 29 | KDynamicWallpaperMetaData 30 | KDynamicWallpaperReader 31 | KDynamicWallpaperWriter 32 | KSolarDynamicWallpaperMetaData 33 | KSunPath 34 | KSunPosition 35 | KSystemClockMonitor 36 | REQUIRED_HEADERS dynamicwallpaperlib_HEADERS 37 | ) 38 | 39 | qt_add_resources(dynamicwallpaperlib_SOURCES resources.qrc) 40 | 41 | add_library(kdynamicwallpaper ${dynamicwallpaperlib_SOURCES}) 42 | add_library(KDynamicWallpaper::KDynamicWallpaper ALIAS kdynamicwallpaper) 43 | 44 | generate_export_header(kdynamicwallpaper 45 | BASE_NAME KDynamicWallpaper 46 | EXPORT_MACRO_NAME KDYNAMICWALLPAPER_EXPORT 47 | ) 48 | 49 | set_target_properties(kdynamicwallpaper PROPERTIES 50 | VERSION ${PROJECT_VERSION} 51 | SOVERSION ${PROJECT_VERSION_MAJOR} 52 | EXPORT_NAME KDynamicWallpaper 53 | ) 54 | 55 | target_include_directories(kdynamicwallpaper PUBLIC 56 | "$" 57 | "$" 58 | ) 59 | 60 | target_link_libraries(kdynamicwallpaper 61 | PUBLIC 62 | Qt6::Core 63 | Qt6::Gui 64 | Qt6::Positioning 65 | 66 | PRIVATE 67 | Qt6::Xml 68 | KF6::I18n 69 | avif 70 | ) 71 | 72 | set(CMAKECONFIG_INSTALL_DIR ${KDE_INSTALL_LIBDIR}/cmake/KDynamicWallpaper) 73 | 74 | write_basic_config_version_file(${CMAKE_CURRENT_BINARY_DIR}/KDynamicWallpaperConfigVersion.cmake 75 | VERSION "${PROJECT_VERSION}" 76 | COMPATIBILITY AnyNewerVersion) 77 | 78 | configure_package_config_file(KDynamicWallpaperConfig.cmake.in 79 | "${CMAKE_CURRENT_BINARY_DIR}/KDynamicWallpaperConfig.cmake" 80 | INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}) 81 | 82 | install(TARGETS kdynamicwallpaper EXPORT KDynamicWallpaperTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) 83 | 84 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KDynamicWallpaperConfig.cmake 85 | DESTINATION ${CMAKECONFIG_INSTALL_DIR} 86 | COMPONENT Devel) 87 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KDynamicWallpaperConfigVersion.cmake 88 | DESTINATION ${CMAKECONFIG_INSTALL_DIR} 89 | COMPONENT Devel) 90 | 91 | install(EXPORT KDynamicWallpaperTargets 92 | NAMESPACE KDynamicWallpaper:: 93 | DESTINATION ${CMAKECONFIG_INSTALL_DIR} 94 | FILE KDynamicWallpaperTargets.cmake 95 | COMPONENT Devel) 96 | 97 | install(FILES ${dynamicwallpaperlib_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kdynamicwallpaper_export.h 98 | DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KDynamicWallpaper 99 | COMPONENT Devel) 100 | -------------------------------------------------------------------------------- /src/lib/KDynamicWallpaperConfig.cmake.in: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | @PACKAGE_INIT@ 6 | 7 | include(CMakeFindDependencyMacro) 8 | find_dependency(Qt5Core @QT_MIN_VERSION@) 9 | find_dependency(Qt5Gui @QT_MIN_VERSION@) 10 | find_dependency(Qt5Positioning @QT_MIN_VERSION@) 11 | 12 | include("${CMAKE_CURRENT_LIST_DIR}/KDynamicWallpaperTargets.cmake") 13 | -------------------------------------------------------------------------------- /src/lib/kdaynightdynamicwallpapermetadata.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #include "kdaynightdynamicwallpapermetadata.h" 8 | 9 | #include 10 | 11 | /*! 12 | * \class KDayNightDynamicWallpaperMetaData 13 | * \brief The KDayNightDynamicWallpaperMetaData class represents metadata associated with images 14 | * in the dynamic wallpaper. 15 | */ 16 | 17 | static QJsonValue timeOfDayToJson(KDayNightDynamicWallpaperMetaData::TimeOfDay tod) 18 | { 19 | switch (tod) { 20 | case KDayNightDynamicWallpaperMetaData::TimeOfDay::Day: 21 | return QJsonValue(QStringLiteral("day")); 22 | case KDayNightDynamicWallpaperMetaData::TimeOfDay::Night: 23 | return QJsonValue(QStringLiteral("night")); 24 | default: 25 | Q_UNREACHABLE(); 26 | } 27 | } 28 | 29 | static KDayNightDynamicWallpaperMetaData::TimeOfDay timeOfDayFromJson(const QJsonValue &value) 30 | { 31 | static QHash lookup = { 32 | { QStringLiteral("day"), KDayNightDynamicWallpaperMetaData::TimeOfDay::Day }, 33 | { QStringLiteral("night"), KDayNightDynamicWallpaperMetaData::TimeOfDay::Night }, 34 | }; 35 | return lookup.value(value.toString()); 36 | } 37 | 38 | class KDayNightDynamicWallpaperMetaDataPrivate : public QSharedData 39 | { 40 | public: 41 | KDayNightDynamicWallpaperMetaDataPrivate(); 42 | 43 | KDayNightDynamicWallpaperMetaData::MetaDataFields presentFields; 44 | KDayNightDynamicWallpaperMetaData::TimeOfDay timeOfDay; 45 | int index; 46 | }; 47 | 48 | KDayNightDynamicWallpaperMetaDataPrivate::KDayNightDynamicWallpaperMetaDataPrivate() 49 | : timeOfDay(KDayNightDynamicWallpaperMetaData::TimeOfDay::Day) 50 | , index(-1) 51 | { 52 | } 53 | 54 | /*! 55 | * Constructs an empty KDayNightDynamicWallpaperMetaData object. 56 | */ 57 | KDayNightDynamicWallpaperMetaData::KDayNightDynamicWallpaperMetaData() 58 | : d(new KDayNightDynamicWallpaperMetaDataPrivate) 59 | { 60 | } 61 | 62 | /*! 63 | * Constructs a KDayNightDynamicWallpaperMetaData object with the given \a timeOfDay and image \a index. 64 | */ 65 | KDayNightDynamicWallpaperMetaData::KDayNightDynamicWallpaperMetaData(TimeOfDay timeOfDay, int index) 66 | : KDayNightDynamicWallpaperMetaData() 67 | { 68 | setTimeOfDay(timeOfDay); 69 | setIndex(index); 70 | } 71 | 72 | /*! 73 | * Constructs a copy of the KDayNightDynamicWallpaperMetaData object. 74 | */ 75 | KDayNightDynamicWallpaperMetaData::KDayNightDynamicWallpaperMetaData(const KDayNightDynamicWallpaperMetaData &other) 76 | : d(other.d) 77 | { 78 | } 79 | 80 | /*! 81 | * Destructs the KDayNightDynamicWallpaperMetaData object. 82 | */ 83 | KDayNightDynamicWallpaperMetaData::~KDayNightDynamicWallpaperMetaData() 84 | { 85 | } 86 | 87 | /*! 88 | * Assigns the value of \p other to a dynamic wallpaper metadata object. 89 | */ 90 | KDayNightDynamicWallpaperMetaData &KDayNightDynamicWallpaperMetaData::operator=(const KDayNightDynamicWallpaperMetaData &other) 91 | { 92 | d = other.d; 93 | return *this; 94 | } 95 | 96 | /*! 97 | * Returns a bitmask that indicates which fields are present in the metadata. 98 | */ 99 | KDayNightDynamicWallpaperMetaData::MetaDataFields KDayNightDynamicWallpaperMetaData::fields() const 100 | { 101 | return d->presentFields; 102 | } 103 | 104 | /*! 105 | * Returns \c true if the KDayNightDynamicWallpaperMetaData contains valid metadata; otherwise \c false. 106 | */ 107 | bool KDayNightDynamicWallpaperMetaData::isValid() const 108 | { 109 | const MetaDataFields requiredFields = TimeOfDayField | IndexField; 110 | return (d->presentFields & requiredFields) == requiredFields; 111 | } 112 | 113 | /*! 114 | * Sets the time of day to \p tod. 115 | */ 116 | void KDayNightDynamicWallpaperMetaData::setTimeOfDay(TimeOfDay tod) 117 | { 118 | d->timeOfDay = tod; 119 | d->presentFields |= TimeOfDayField; 120 | } 121 | 122 | /*! 123 | * Returns the value of the time of day field in the dynamic wallpaper metadata. 124 | */ 125 | KDayNightDynamicWallpaperMetaData::TimeOfDay KDayNightDynamicWallpaperMetaData::timeOfDay() const 126 | { 127 | return d->timeOfDay; 128 | } 129 | 130 | /*! 131 | * Sets the index of the associated wallpaper image to \p index. 132 | */ 133 | void KDayNightDynamicWallpaperMetaData::setIndex(int index) 134 | { 135 | d->index = index; 136 | d->presentFields |= IndexField; 137 | } 138 | 139 | /*! 140 | * Returns the index of the associated wallpaper image. 141 | */ 142 | int KDayNightDynamicWallpaperMetaData::index() const 143 | { 144 | return d->index; 145 | } 146 | 147 | /*! 148 | * Converts the KDayNightDynamicWallpaperMetaData to a UTF-8 encoded JSON document. 149 | * 150 | * This method returns an empty QJsonObject if the metadata is invalid. 151 | */ 152 | QJsonObject KDayNightDynamicWallpaperMetaData::toJson() const 153 | { 154 | if (!isValid()) 155 | return QJsonObject(); 156 | 157 | QJsonObject object; 158 | 159 | object[QLatin1String("TimeOfDay")] = timeOfDayToJson(d->timeOfDay); 160 | object[QLatin1String("Index")] = d->index; 161 | 162 | return object; 163 | } 164 | 165 | /*! 166 | * Decodes a JSON-encoded KDayNightDynamicWallpaperMetaData object. 167 | */ 168 | KDayNightDynamicWallpaperMetaData KDayNightDynamicWallpaperMetaData::fromJson(const QJsonObject &object) 169 | { 170 | KDayNightDynamicWallpaperMetaData metaData; 171 | 172 | const QJsonValue index = object[QLatin1String("Index")]; 173 | if (index.isDouble()) 174 | metaData.setIndex(index.toInt()); 175 | 176 | const QJsonValue timeOfDay = object[QLatin1String("TimeOfDay")]; 177 | if (timeOfDay.isString()) 178 | metaData.setTimeOfDay(timeOfDayFromJson(timeOfDay)); 179 | 180 | return metaData; 181 | } 182 | -------------------------------------------------------------------------------- /src/lib/kdaynightdynamicwallpapermetadata.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "kdynamicwallpaper_export.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | class KDayNightDynamicWallpaperMetaDataPrivate; 16 | 17 | class KDYNAMICWALLPAPER_EXPORT KDayNightDynamicWallpaperMetaData 18 | { 19 | public: 20 | enum class TimeOfDay { 21 | Day = 1, 22 | Night = 2, 23 | }; 24 | 25 | enum MetaDataField { 26 | TimeOfDayField = 1 << 0, 27 | IndexField = 1 << 1, 28 | }; 29 | Q_DECLARE_FLAGS(MetaDataFields, MetaDataField) 30 | 31 | KDayNightDynamicWallpaperMetaData(); 32 | KDayNightDynamicWallpaperMetaData(TimeOfDay timeOfDay, int index); 33 | KDayNightDynamicWallpaperMetaData(const KDayNightDynamicWallpaperMetaData &other); 34 | ~KDayNightDynamicWallpaperMetaData(); 35 | 36 | KDayNightDynamicWallpaperMetaData &operator=(const KDayNightDynamicWallpaperMetaData &other); 37 | 38 | MetaDataFields fields() const; 39 | bool isValid() const; 40 | 41 | void setTimeOfDay(TimeOfDay tod); 42 | TimeOfDay timeOfDay() const; 43 | 44 | void setIndex(int index); 45 | int index() const; 46 | 47 | QJsonObject toJson() const; 48 | 49 | static KDayNightDynamicWallpaperMetaData fromJson(const QJsonObject &object); 50 | 51 | private: 52 | QSharedDataPointer d; 53 | }; 54 | 55 | Q_DECLARE_OPERATORS_FOR_FLAGS(KDayNightDynamicWallpaperMetaData::MetaDataFields) 56 | -------------------------------------------------------------------------------- /src/lib/kdynamicwallpapermetadata.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #include "kdynamicwallpapermetadata.h" 8 | 9 | /*! 10 | * \class KDynamicWallpaperMetaData 11 | * 12 | * The KDynamicWallpaperMetaData type provides a generic representation of dynamic wallpaper 13 | * representation. 14 | * 15 | * \sa KSolarDynamicWallpaperMetaData, KDayNightDynamicWallpaperMetaData 16 | */ 17 | -------------------------------------------------------------------------------- /src/lib/kdynamicwallpapermetadata.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "kdaynightdynamicwallpapermetadata.h" 10 | #include "ksolardynamicwallpapermetadata.h" 11 | 12 | #include 13 | 14 | using KDynamicWallpaperMetaData = std::variant; 16 | -------------------------------------------------------------------------------- /src/lib/kdynamicwallpaperreader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "kdynamicwallpaper_export.h" 10 | #include "kdynamicwallpapermetadata.h" 11 | 12 | #include 13 | 14 | class KDynamicWallpaperReaderPrivate; 15 | 16 | class KDYNAMICWALLPAPER_EXPORT KDynamicWallpaperReader 17 | { 18 | public: 19 | enum WallpaperReaderError { 20 | NoError, 21 | OpenError, 22 | ReadError, 23 | }; 24 | 25 | KDynamicWallpaperReader(); 26 | explicit KDynamicWallpaperReader(QIODevice *device); 27 | explicit KDynamicWallpaperReader(const QString &fileName); 28 | ~KDynamicWallpaperReader(); 29 | 30 | void setDevice(QIODevice *device); 31 | QIODevice *device() const; 32 | 33 | void setFileName(const QString &fileName); 34 | QString fileName() const; 35 | 36 | QList metaData() const; 37 | 38 | int imageCount() const; 39 | QImage image(int imageIndex) const; 40 | 41 | WallpaperReaderError error() const; 42 | QString errorString() const; 43 | 44 | static bool canRead(QIODevice *device); 45 | static bool canRead(const QString &fileName); 46 | 47 | private: 48 | QScopedPointer d; 49 | }; 50 | -------------------------------------------------------------------------------- /src/lib/kdynamicwallpaperwriter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "kdynamicwallpaper_export.h" 10 | #include "kdynamicwallpapermetadata.h" 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | class KDynamicWallpaperWriterPrivate; 18 | 19 | class KDYNAMICWALLPAPER_EXPORT KDynamicWallpaperWriter 20 | { 21 | public: 22 | enum WallpaperWriterError { 23 | NoError, 24 | DeviceError, 25 | EncoderError, 26 | UnknownError, 27 | }; 28 | 29 | class ImageView 30 | { 31 | public: 32 | explicit ImageView(const QString &fileName) 33 | : m_fileName(fileName) 34 | { 35 | } 36 | 37 | QImage data() const 38 | { 39 | return QImage(m_fileName); 40 | } 41 | 42 | QString key() const 43 | { 44 | return m_fileName; 45 | } 46 | 47 | private: 48 | QString m_fileName; 49 | }; 50 | 51 | KDynamicWallpaperWriter(); 52 | ~KDynamicWallpaperWriter(); 53 | 54 | void setSpeed(int speed); 55 | std::optional speed() const; 56 | 57 | void setMetaData(const QList &metaData); 58 | QList metaData() const; 59 | 60 | void setImages(const QList &views); 61 | QList images() const; 62 | 63 | bool setCodecName(const QString &codecName); 64 | QString codecName() const; 65 | 66 | bool flush(QIODevice *device); 67 | bool flush(const QString &fileName); 68 | 69 | void setMaxThreadCount(int max); 70 | std::optional maxThreadCount() const; 71 | 72 | WallpaperWriterError error() const; 73 | QString errorString() const; 74 | 75 | static bool canWrite(QIODevice *device); 76 | static bool canWrite(const QString &fileName); 77 | 78 | private: 79 | QScopedPointer d; 80 | }; 81 | -------------------------------------------------------------------------------- /src/lib/ksolardynamicwallpapermetadata.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #include "ksolardynamicwallpapermetadata.h" 8 | 9 | #include 10 | 11 | /*! 12 | * \class KSolarDynamicWallpaperMetaData 13 | * \brief The KSolarDynamicWallpaperMetaData class represents metadata associated with images 14 | * in the dynamic wallpaper. 15 | * 16 | * KSolarDynamicWallpaperMetaData provides information about images in the dynamic wallpaper, 17 | * for example, solar position, etc. Some fields may not be specified. In order to check 18 | * whether the given field is set, test the corresponding bit in fields(). 19 | */ 20 | 21 | static QJsonValue crossFadeModeToJson(KSolarDynamicWallpaperMetaData::CrossFadeMode crossFadeMode) 22 | { 23 | switch (crossFadeMode) { 24 | case KSolarDynamicWallpaperMetaData::NoCrossFade: 25 | return QJsonValue(false); 26 | case KSolarDynamicWallpaperMetaData::CrossFade: 27 | return QJsonValue(true); 28 | default: 29 | Q_UNREACHABLE(); 30 | } 31 | } 32 | 33 | static KSolarDynamicWallpaperMetaData::CrossFadeMode crossFadeModeFromJson(const QJsonValue &value) 34 | { 35 | return value.toBool() ? KSolarDynamicWallpaperMetaData::CrossFade : KSolarDynamicWallpaperMetaData::NoCrossFade; 36 | } 37 | 38 | class KSolarDynamicWallpaperMetaDataPrivate : public QSharedData 39 | { 40 | public: 41 | KSolarDynamicWallpaperMetaDataPrivate(); 42 | 43 | KSolarDynamicWallpaperMetaData::MetaDataFields presentFields; 44 | KSolarDynamicWallpaperMetaData::CrossFadeMode crossFadeMode; 45 | qreal solarAzimuth; 46 | qreal solarElevation; 47 | qreal time; 48 | int index; 49 | }; 50 | 51 | KSolarDynamicWallpaperMetaDataPrivate::KSolarDynamicWallpaperMetaDataPrivate() 52 | : crossFadeMode(KSolarDynamicWallpaperMetaData::NoCrossFade) 53 | , solarAzimuth(0.0) 54 | , solarElevation(0.0) 55 | , time(0.0) 56 | , index(-1) 57 | { 58 | } 59 | 60 | /*! 61 | * Constructs an empty KSolarDynamicWallpaperMetaData object. 62 | */ 63 | KSolarDynamicWallpaperMetaData::KSolarDynamicWallpaperMetaData() 64 | : d(new KSolarDynamicWallpaperMetaDataPrivate) 65 | { 66 | } 67 | 68 | /*! 69 | * Constructs a copy of the KSolarDynamicWallpaperMetaData object. 70 | */ 71 | KSolarDynamicWallpaperMetaData::KSolarDynamicWallpaperMetaData(const KSolarDynamicWallpaperMetaData &other) 72 | : d(other.d) 73 | { 74 | } 75 | 76 | /*! 77 | * Destructs the KSolarDynamicWallpaperMetaData object. 78 | */ 79 | KSolarDynamicWallpaperMetaData::~KSolarDynamicWallpaperMetaData() 80 | { 81 | } 82 | 83 | /*! 84 | * Assigns the value of \p other to a dynamic wallpaper metadata object. 85 | */ 86 | KSolarDynamicWallpaperMetaData &KSolarDynamicWallpaperMetaData::operator=(const KSolarDynamicWallpaperMetaData &other) 87 | { 88 | d = other.d; 89 | return *this; 90 | } 91 | 92 | /*! 93 | * Returns a bitmask that indicates which fields are present in the metadata. 94 | */ 95 | KSolarDynamicWallpaperMetaData::MetaDataFields KSolarDynamicWallpaperMetaData::fields() const 96 | { 97 | return d->presentFields; 98 | } 99 | 100 | /*! 101 | * Returns \c true if the KSolarDynamicWallpaperMetaData contains valid metadata; otherwise \c false. 102 | */ 103 | bool KSolarDynamicWallpaperMetaData::isValid() const 104 | { 105 | const MetaDataFields requiredFields = TimeField | IndexField; 106 | if ((d->presentFields & requiredFields) != requiredFields) 107 | return false; 108 | 109 | if (bool(d->presentFields & SolarAzimuthField) ^ bool(d->presentFields & SolarElevationField)) 110 | return false; 111 | 112 | if (d->time < 0 || d->time > 1) 113 | return false; 114 | 115 | return true; 116 | } 117 | 118 | /*! 119 | * Sets the value of the cross-fade mode field to \p mode. 120 | */ 121 | void KSolarDynamicWallpaperMetaData::setCrossFadeMode(CrossFadeMode mode) 122 | { 123 | d->crossFadeMode = mode; 124 | d->presentFields |= CrossFadeField; 125 | } 126 | 127 | /*! 128 | * Returns the value of the cross-fade mode field in the dynamic wallpaper metadata. 129 | */ 130 | KSolarDynamicWallpaperMetaData::CrossFadeMode KSolarDynamicWallpaperMetaData::crossFadeMode() const 131 | { 132 | return d->crossFadeMode; 133 | } 134 | 135 | /*! 136 | * Sets the value of the time field to \p time. 137 | */ 138 | void KSolarDynamicWallpaperMetaData::setTime(qreal time) 139 | { 140 | d->time = time; 141 | d->presentFields |= TimeField; 142 | } 143 | 144 | /*! 145 | * Returns the value of the time field in the dynamic wallpaper metadata. 146 | */ 147 | qreal KSolarDynamicWallpaperMetaData::time() const 148 | { 149 | return d->time; 150 | } 151 | 152 | /*! 153 | * Sets the value of the solar elevation field to \p elevation. 154 | */ 155 | void KSolarDynamicWallpaperMetaData::setSolarElevation(qreal elevation) 156 | { 157 | d->solarElevation = elevation; 158 | d->presentFields |= SolarElevationField; 159 | } 160 | 161 | /*! 162 | * Returns the value of solar elevation stored in the dynamic wallpaper metadata. 163 | * 164 | * Note that this method will return \c 0 if SolarElevationField is not set in fields(). 165 | */ 166 | qreal KSolarDynamicWallpaperMetaData::solarElevation() const 167 | { 168 | return d->solarElevation; 169 | } 170 | 171 | /*! 172 | * Sets the value of the solar azimuth field to \p azimuth. 173 | */ 174 | void KSolarDynamicWallpaperMetaData::setSolarAzimuth(qreal azimuth) 175 | { 176 | d->solarAzimuth = azimuth; 177 | d->presentFields |= SolarAzimuthField; 178 | } 179 | 180 | /*! 181 | * Returns the value of solar azimuth stored in the dynamic wallpaper metadata. 182 | * 183 | * Note that this method will return \c 0 if SolarAzimuthField is not set in fields(). 184 | */ 185 | qreal KSolarDynamicWallpaperMetaData::solarAzimuth() const 186 | { 187 | return d->solarAzimuth; 188 | } 189 | 190 | /*! 191 | * Sets the index of the associated wallpaper image to \p index. 192 | */ 193 | void KSolarDynamicWallpaperMetaData::setIndex(int index) 194 | { 195 | d->index = index; 196 | d->presentFields |= IndexField; 197 | } 198 | 199 | /*! 200 | * Returns the index of the associated wallpaper image. 201 | */ 202 | int KSolarDynamicWallpaperMetaData::index() const 203 | { 204 | return d->index; 205 | } 206 | 207 | /*! 208 | * Converts the KSolarDynamicWallpaperMetaData to a UTF-8 encoded JSON document. 209 | * 210 | * This method returns an empty QJsonObject if the metadata is invalid. 211 | */ 212 | QJsonObject KSolarDynamicWallpaperMetaData::toJson() const 213 | { 214 | if (!isValid()) 215 | return QJsonObject(); 216 | 217 | QJsonObject object; 218 | 219 | if (d->presentFields & CrossFadeField) 220 | object[QLatin1String("CrossFade")] = crossFadeModeToJson(d->crossFadeMode); 221 | if (d->presentFields & SolarElevationField) 222 | object[QLatin1String("Elevation")] = d->solarElevation; 223 | if (d->presentFields & SolarAzimuthField) 224 | object[QLatin1String("Azimuth")] = d->solarAzimuth; 225 | object[QLatin1String("Time")] = d->time; 226 | object[QLatin1String("Index")] = d->index; 227 | 228 | return object; 229 | } 230 | 231 | /*! 232 | * Decodes a JSON-encoded KSolarDynamicWallpaperMetaData object. 233 | */ 234 | KSolarDynamicWallpaperMetaData KSolarDynamicWallpaperMetaData::fromJson(const QJsonObject &object) 235 | { 236 | KSolarDynamicWallpaperMetaData metaData; 237 | 238 | const QJsonValue index = object[QLatin1String("Index")]; 239 | if (index.isDouble()) 240 | metaData.setIndex(index.toInt()); 241 | 242 | const QJsonValue crossFadeMode = object[QLatin1String("CrossFade")]; 243 | if (crossFadeMode.isBool()) 244 | metaData.setCrossFadeMode(crossFadeModeFromJson(crossFadeMode)); 245 | 246 | const QJsonValue time = object[QLatin1String("Time")]; 247 | if (time.isDouble()) 248 | metaData.setTime(time.toDouble()); 249 | 250 | const QJsonValue solarElevation = object[QLatin1String("Elevation")]; 251 | if (solarElevation.isDouble()) 252 | metaData.setSolarElevation(solarElevation.toDouble()); 253 | 254 | const QJsonValue solarAzimuth = object[QLatin1String("Azimuth")]; 255 | if (solarAzimuth.isDouble()) 256 | metaData.setSolarAzimuth(solarAzimuth.toDouble()); 257 | 258 | return metaData; 259 | } 260 | -------------------------------------------------------------------------------- /src/lib/ksolardynamicwallpapermetadata.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "kdynamicwallpaper_export.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | class KSolarDynamicWallpaperMetaDataPrivate; 16 | 17 | class KDYNAMICWALLPAPER_EXPORT KSolarDynamicWallpaperMetaData 18 | { 19 | public: 20 | enum CrossFadeMode { 21 | NoCrossFade, 22 | CrossFade, 23 | }; 24 | 25 | enum MetaDataField { 26 | CrossFadeField = 1 << 0, 27 | TimeField = 1 << 1, 28 | SolarAzimuthField = 1 << 2, 29 | SolarElevationField = 1 << 3, 30 | IndexField = 1 << 4, 31 | }; 32 | Q_DECLARE_FLAGS(MetaDataFields, MetaDataField) 33 | 34 | KSolarDynamicWallpaperMetaData(); 35 | KSolarDynamicWallpaperMetaData(const KSolarDynamicWallpaperMetaData &other); 36 | ~KSolarDynamicWallpaperMetaData(); 37 | 38 | KSolarDynamicWallpaperMetaData &operator=(const KSolarDynamicWallpaperMetaData &other); 39 | 40 | MetaDataFields fields() const; 41 | bool isValid() const; 42 | 43 | void setCrossFadeMode(CrossFadeMode mode); 44 | CrossFadeMode crossFadeMode() const; 45 | 46 | void setTime(qreal time); 47 | qreal time() const; 48 | 49 | void setSolarElevation(qreal elevation); 50 | qreal solarElevation() const; 51 | 52 | void setSolarAzimuth(qreal azimuth); 53 | qreal solarAzimuth() const; 54 | 55 | void setIndex(int index); 56 | int index() const; 57 | 58 | QJsonObject toJson() const; 59 | 60 | static KSolarDynamicWallpaperMetaData fromJson(const QJsonObject &object); 61 | 62 | private: 63 | QSharedDataPointer d; 64 | }; 65 | 66 | Q_DECLARE_OPERATORS_FOR_FLAGS(KSolarDynamicWallpaperMetaData::MetaDataFields) 67 | -------------------------------------------------------------------------------- /src/lib/ksunpath.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #include "ksunpath.h" 8 | #include "ksunposition.h" 9 | 10 | #include 11 | 12 | #include 13 | 14 | /*! 15 | * \class KSunPath 16 | * \brief The KSunPath class represents a path of the Sun at the given time and location. 17 | */ 18 | 19 | static QVector3D computeNormal(const QVector3D ¢er, const QVector3D &v1, const QVector3D &v2) 20 | { 21 | const QVector3D cross = QVector3D::crossProduct(v1 - center, v2 - center); 22 | return cross.normalized(); 23 | } 24 | 25 | static QVector3D positionToVector(const KSunPosition &position) 26 | { 27 | return position.toVector(); 28 | } 29 | 30 | /*! 31 | * Creates a path of the Sun at the specified date and location. 32 | */ 33 | KSunPath KSunPath::create(const QDateTime &dateTime, const QGeoCoordinate &location) 34 | { 35 | // I bet there is a nice formula to determine the normal and the center of 36 | // the sun path. Sampling the position of the Sun is not that bad, however 37 | // having the computed results as accurate as possible is still something we 38 | // have to strive for. 39 | const int sampleCount = 24; 40 | 41 | const QDateTime utcDateTime = dateTime.toUTC(); 42 | const QDate utcDate = utcDateTime.date(); 43 | 44 | QVector positions; 45 | positions.reserve(sampleCount); 46 | 47 | for (int i = 0; i < sampleCount; ++i) { 48 | const QTime utcTime(i, 0); 49 | const QDateTime sampleDataTime(utcDate, utcTime, Qt::UTC); 50 | const KSunPosition position(sampleDataTime, location); 51 | if (!position.isValid()) 52 | return KSunPath(); 53 | positions << position; 54 | } 55 | 56 | QVector samples; 57 | samples.reserve(sampleCount); 58 | std::transform(positions.constBegin(), positions.constEnd(), 59 | std::back_inserter(samples), positionToVector); 60 | 61 | QVector3D center = std::accumulate(samples.constBegin(), samples.constEnd(), QVector3D()); 62 | center /= sampleCount; 63 | 64 | float radius = 0; 65 | for (const QVector3D &sample : samples) 66 | radius += (sample - center).length(); 67 | radius /= sampleCount; 68 | 69 | QVector3D normal; 70 | for (int i = 1; i < samples.count(); ++i) { 71 | const QVector3D v1 = samples.at(i - 1); 72 | const QVector3D v2 = samples.at(i); 73 | normal += computeNormal(center, v1, v2); 74 | } 75 | normal.normalize(); 76 | 77 | return KSunPath(center, normal, radius); 78 | } 79 | 80 | /*! 81 | * Constructs a KSunPath with \p center, \p normal, and \p radius. 82 | */ 83 | KSunPath::KSunPath(const QVector3D ¢er, const QVector3D &normal, float radius) 84 | : m_center(center) 85 | , m_normal(normal) 86 | , m_radius(radius) 87 | { 88 | } 89 | 90 | /*! 91 | * Returns \c true if the path of the Sun is valid; otherwise returns \c false. 92 | */ 93 | bool KSunPath::isValid() const 94 | { 95 | return !qFuzzyIsNull(m_normal.x()); 96 | } 97 | 98 | /*! 99 | * Returns the coordinates of the center of this path of the Sun. 100 | */ 101 | QVector3D KSunPath::center() const 102 | { 103 | return m_center; 104 | } 105 | 106 | /*! 107 | * Returns the normal of the plane that contains this path of the Sun. 108 | */ 109 | QVector3D KSunPath::normal() const 110 | { 111 | return m_normal; 112 | } 113 | 114 | /*! 115 | * Projects the specified KSunPosition onto this KSunPath. 116 | */ 117 | QVector3D KSunPath::project(const KSunPosition &position) const 118 | { 119 | if (!isValid()) 120 | return QVector3D(); 121 | 122 | const QVector3D axis = QVector3D(1, 0, 0); 123 | const QVector3D origin = QVector3D(0, 0, 0); 124 | const QVector3D normal = computeNormal(origin, axis, position.toVector()); 125 | if (normal.isNull()) 126 | return QVector3D(); 127 | 128 | const QVector3D direction = QVector3D::crossProduct(m_normal, normal).normalized(); 129 | const QVector3D point = QVector3D::crossProduct(normal, direction) * QVector3D::dotProduct(m_normal, m_center) + 130 | QVector3D::crossProduct(direction, m_normal) * QVector3D::dotProduct(normal, origin); 131 | 132 | const QVector3D delta = point - m_center; 133 | const float dot = QVector3D::dotProduct(direction, delta); 134 | const float discriminator = dot * dot - delta.lengthSquared() + m_radius * m_radius; 135 | if (discriminator < 0) 136 | return QVector3D(); 137 | 138 | if (qFuzzyIsNull(discriminator)) 139 | return point - dot * direction; 140 | 141 | const QVector3D a = point + direction * (-dot - std::sqrt(discriminator)); 142 | const QVector3D b = point + direction * (-dot + std::sqrt(discriminator)); 143 | 144 | if (qFuzzyIsNull(position.elevation())) { 145 | if (position.azimuth() < 180) 146 | return (a.y() < b.y()) ? b : a; 147 | return (a.y() < b.y()) ? a : b; 148 | } 149 | 150 | if (position.elevation() < 0) 151 | return (a.z() < b.z()) ? a : b; 152 | 153 | return (a.z() < b.z()) ? b : a; 154 | } 155 | -------------------------------------------------------------------------------- /src/lib/ksunpath.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "kdynamicwallpaper_export.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | class KSunPosition; 16 | 17 | class KDYNAMICWALLPAPER_EXPORT KSunPath 18 | { 19 | public: 20 | KSunPath() = default; 21 | 22 | bool isValid() const; 23 | QVector3D center() const; 24 | QVector3D normal() const; 25 | 26 | QVector3D project(const KSunPosition &position) const; 27 | 28 | static KSunPath create(const QDateTime &dateTime, const QGeoCoordinate &location); 29 | 30 | private: 31 | KSunPath(const QVector3D ¢er, const QVector3D &normal, float radius); 32 | 33 | QVector3D m_center; 34 | QVector3D m_normal; 35 | float m_radius; 36 | }; 37 | -------------------------------------------------------------------------------- /src/lib/ksunposition.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #include "ksunposition.h" 8 | 9 | #include 10 | 11 | /*! 12 | * \class KSunPosition 13 | * \brief The KSunPosition class provides a convenient way for determining the position of the 14 | * Sun at the specified time and location. 15 | */ 16 | 17 | const static qreal s_midnightHourAngle = -180; 18 | 19 | static qreal julianCenturiesToJulianDay(qreal jcent) 20 | { 21 | return 36525.0 * jcent + 2451545.0; 22 | } 23 | 24 | static qreal julianDayToJulianCenturies(qreal jd) 25 | { 26 | return (jd - 2451545.0) / 36525.0; 27 | } 28 | 29 | static qreal dateTimeToJulianDay(const QDateTime &dateTime) 30 | { 31 | return dateTime.toSecsSinceEpoch() / 86400.0 + 2440587.5; 32 | } 33 | 34 | static qreal sind(qreal value) 35 | { 36 | return std::sin(qDegreesToRadians(value)); 37 | } 38 | 39 | static qreal cosd(qreal value) 40 | { 41 | return std::cos(qDegreesToRadians(value)); 42 | } 43 | 44 | static qreal eccentricityEarthOrbit(qreal jcent) 45 | { 46 | return 0.016708634 - jcent * (0.000042037 + 0.0000001267 * jcent); 47 | } 48 | 49 | static qreal solarGeometricMeanAnomaly(qreal jcent) 50 | { 51 | return qDegreesToRadians(357.52911 + jcent * (35999.05029 - 0.0001537 * jcent)); 52 | } 53 | 54 | static qreal solarMeanEclipticObliquity(const qreal jcent) 55 | { 56 | const qreal seconds = 21.448 - jcent * (46.815 + jcent * (0.00059 - jcent * 0.001813)); 57 | return 23.0 + (26.0 + seconds / 60.0) / 60.0; 58 | } 59 | 60 | static qreal obliquityCorrection(qreal jcent) 61 | { 62 | const qreal meanEclipticObliquity = solarMeanEclipticObliquity(jcent); 63 | const qreal omega = qDegreesToRadians(125.04 - jcent * 1934.136); 64 | return qDegreesToRadians(meanEclipticObliquity + 0.00256 * std::cos(omega)); 65 | } 66 | 67 | static qreal solarEquationOfCenter(qreal jcent) 68 | { 69 | const qreal anomaly = solarGeometricMeanAnomaly(jcent); 70 | const qreal equation = std::sin(2 * anomaly) * (0.019993 - jcent * 0.000101) + 71 | std::sin(anomaly) * (1.914602 - jcent * (0.004817 + jcent * 0.000014)) + 72 | std::sin(3 * anomaly) * 0.000289; 73 | return qDegreesToRadians(equation); 74 | } 75 | 76 | static qreal solarGeometricMeanLongitude(qreal jcent) 77 | { 78 | qreal l = std::fmod(280.46646 + jcent * (36000.76983 + jcent * 0.0003032), 360); 79 | if (l < 0) 80 | l += 360; 81 | return qDegreesToRadians(l); 82 | } 83 | 84 | static qreal solarTrueLongitude(qreal jcent) 85 | { 86 | return solarGeometricMeanLongitude(jcent) + solarEquationOfCenter(jcent); 87 | } 88 | 89 | static qreal solarApparentLongitude(qreal jcent) 90 | { 91 | const qreal omega = qDegreesToRadians(125.04 - jcent * 1934.136); 92 | const qreal correction = qDegreesToRadians(-0.00569 - 0.00478 * std::sin(omega)); 93 | return solarTrueLongitude(jcent) + correction; 94 | } 95 | 96 | static qreal solarDeclination(qreal jcent) 97 | { 98 | return std::asin(std::sin(obliquityCorrection(jcent)) * std::sin(solarApparentLongitude(jcent))); 99 | } 100 | 101 | static qreal equationOfTime(qreal jcent) 102 | { 103 | const qreal e = eccentricityEarthOrbit(jcent); 104 | const qreal m = solarGeometricMeanAnomaly(jcent); 105 | const qreal l = solarGeometricMeanLongitude(jcent); 106 | const qreal y = std::pow(std::tan(0.5 * obliquityCorrection(jcent)), 2); 107 | const qreal equation = y * std::sin(2 * l) - 2 * e * std::sin(m) + 108 | 4 * e * y * std::sin(m) * std::cos(2 * l) - 0.5 * y * y * std::sin(4 * l) - 109 | 1.25 * e * e * std::sin(2 * m); 110 | return 4 * qRadiansToDegrees(equation); 111 | } 112 | 113 | static qreal julianCenturiesToMinutesFromMidnight(qreal jcent) 114 | { 115 | const qreal jd = julianCenturiesToJulianDay(jcent); 116 | const qreal minutes = jd - std::round(jd) - 0.5; 117 | return minutes * 1440.0; 118 | } 119 | 120 | static qreal solarHourAngle(qreal jcent, const QGeoCoordinate &location) 121 | { 122 | const qreal minutes = julianCenturiesToMinutesFromMidnight(jcent); 123 | 124 | const qreal angle = std::fmod(location.longitude() + (equationOfTime(jcent) + minutes - 720) / 4, 360); 125 | if (angle < -180.0) 126 | return angle + 360.0; 127 | if (angle > 180.0) 128 | return angle - 360.0; 129 | 130 | return angle; 131 | } 132 | 133 | static qreal solarZenith(qreal jcent, const QGeoCoordinate &location, qreal hourAngle) 134 | { 135 | const qreal declination = solarDeclination(jcent); 136 | 137 | const qreal zenith = std::acos(sind(location.latitude()) * std::sin(declination) + 138 | cosd(location.latitude()) * std::cos(declination) * cosd(hourAngle)); 139 | 140 | return qRadiansToDegrees(zenith); 141 | } 142 | 143 | static qreal solarAzimuth(qreal jcent, const QGeoCoordinate &location, qreal hourAngle) 144 | { 145 | const qreal zenith = solarZenith(jcent, location, hourAngle); 146 | 147 | const qreal denominator = cosd(location.latitude()) * sind(zenith); 148 | if (qFuzzyIsNull(denominator)) 149 | return std::nan(""); 150 | 151 | const qreal declination = solarDeclination(jcent); 152 | const qreal numerator = sind(location.latitude()) * cosd(zenith) - std::sin(declination); 153 | 154 | qreal azimuth = std::acos(qBound(-1.0, numerator / denominator, 1.0)); 155 | 156 | if (hourAngle < 0) 157 | azimuth = M_PI - azimuth; 158 | else 159 | azimuth = azimuth + M_PI; 160 | 161 | return qRadiansToDegrees(azimuth); 162 | } 163 | 164 | static qreal atmosphericRefractionCorrection(qreal e) 165 | { 166 | if (e > 85) 167 | return 0; 168 | 169 | const qreal te = std::tan(qDegreesToRadians(e)); 170 | qreal correction = 0; 171 | 172 | if (e > 5) 173 | correction = 58.1 / te - 0.07 / (te * te * te) + 0.000086 / (te * te * te * te * te); 174 | else if (e > -0.575) 175 | correction = 1735 + e * (-518.2 + e * (103.4 + e * (-12.79 + e * 0.711))); 176 | else 177 | correction = -20.774 / te; 178 | 179 | return correction / 3600.0; 180 | } 181 | 182 | /*! 183 | * Constructs a null KSunPosition object. 184 | */ 185 | KSunPosition::KSunPosition() 186 | : KSunPosition(0, 0) 187 | { 188 | } 189 | 190 | /*! 191 | * Constructs a KSunPosition with the given elevation \p elevation and azimuth \p azimuth. 192 | */ 193 | KSunPosition::KSunPosition(qreal elevation, qreal azimuth) 194 | : m_elevation(elevation) 195 | , m_azimuth(azimuth) 196 | { 197 | } 198 | 199 | /*! 200 | * Constructs a KSunPosition with the position of the Sun at the specified date \p dateTime 201 | * and location \p location. 202 | */ 203 | KSunPosition::KSunPosition(const QDateTime &dateTime, const QGeoCoordinate &location) 204 | { 205 | const qreal jd = dateTimeToJulianDay(dateTime); 206 | const qreal jcent = julianDayToJulianCenturies(jd); 207 | const qreal hourAngle = solarHourAngle(jcent, location); 208 | 209 | init(jcent, location, hourAngle); 210 | } 211 | 212 | /*! 213 | * Returns \c true if the position of the Sun is valid; otherwise returns \c false. 214 | */ 215 | bool KSunPosition::isValid() const 216 | { 217 | return !(std::isnan(m_azimuth) || std::isnan(m_elevation)); 218 | } 219 | 220 | /*! 221 | * Returns the elevation angle of the Sun, in decimal degrees. 222 | */ 223 | qreal KSunPosition::elevation() const 224 | { 225 | return m_elevation; 226 | } 227 | 228 | /*! 229 | * Returns the azimuth angle of the Sun, in decimal degrees. 230 | * 231 | * The azimuth angle specifies the Sun's relative direction along the local horizon, where 0 232 | * degrees corresponds to north, 90 degrees corresponds to east, 180 degrees corresponds to 233 | * south, and 270 degrees corresponds to west. 234 | */ 235 | qreal KSunPosition::azimuth() const 236 | { 237 | return m_azimuth; 238 | } 239 | 240 | /*! 241 | * Converts the position of the Sun (elevation, azimuth) to the Cartesian coordinates. 242 | * 243 | * The returned value is a unit vector, i.e. it has a magnitude of 1. 244 | */ 245 | QVector3D KSunPosition::toVector() const 246 | { 247 | const float x = static_cast(cosd(m_elevation) * cosd(m_azimuth)); 248 | const float y = static_cast(cosd(m_elevation) * sind(m_azimuth)); 249 | const float z = static_cast(sind(m_elevation)); 250 | 251 | return QVector3D(x, y, z); 252 | } 253 | 254 | static qreal solarNoonCorrection(qreal jcent, const QGeoCoordinate &location) 255 | { 256 | return (720 - 4 * location.longitude() - equationOfTime(jcent)) / 1440; 257 | } 258 | 259 | /*! 260 | * Determines the position of the Sun (elevation, azimuth) at midnight. 261 | * 262 | * The returned value is the lowest position of the Sun in the sky at the specified time and location. 263 | */ 264 | KSunPosition KSunPosition::midnight(const QDateTime &dateTime, const QGeoCoordinate &location) 265 | { 266 | qreal jdNoon = std::round(dateTimeToJulianDay(dateTime)); 267 | qreal jcentNoon = julianDayToJulianCenturies(jdNoon); 268 | 269 | const qreal correction = solarNoonCorrection(jcentNoon, location); 270 | jdNoon += correction - 0.5; 271 | jcentNoon = julianDayToJulianCenturies(jdNoon); 272 | 273 | const qreal jdMidnight = jdNoon + 0.5; 274 | const qreal jcentMidnight = julianDayToJulianCenturies(jdMidnight); 275 | 276 | KSunPosition position; 277 | position.init(jcentMidnight, location, s_midnightHourAngle); 278 | 279 | return position; 280 | } 281 | 282 | void KSunPosition::init(qreal jcent, const QGeoCoordinate &location, qreal hourAngle) 283 | { 284 | const qreal zenith = solarZenith(jcent, location, hourAngle); 285 | 286 | m_elevation = 90 - zenith; 287 | m_elevation += atmosphericRefractionCorrection(m_elevation); 288 | 289 | m_azimuth = solarAzimuth(jcent, location, hourAngle); 290 | } 291 | -------------------------------------------------------------------------------- /src/lib/ksunposition.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "kdynamicwallpaper_export.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | class KDYNAMICWALLPAPER_EXPORT KSunPosition 16 | { 17 | public: 18 | KSunPosition(); 19 | KSunPosition(qreal elevation, qreal azimuth); 20 | KSunPosition(const QDateTime &dateTime, const QGeoCoordinate &location); 21 | 22 | bool isValid() const; 23 | qreal elevation() const; 24 | qreal azimuth() const; 25 | QVector3D toVector() const; 26 | 27 | static KSunPosition midnight(const QDateTime &dateTime, const QGeoCoordinate &location); 28 | 29 | private: 30 | void init(qreal jcent, const QGeoCoordinate &location, qreal hourAngle); 31 | 32 | qreal m_elevation; 33 | qreal m_azimuth; 34 | }; 35 | -------------------------------------------------------------------------------- /src/lib/ksystemclockmonitor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #include "ksystemclockmonitor.h" 8 | #include "ksystemclockmonitorengine_p.h" 9 | 10 | /*! 11 | * \class KSystemClockMonitor 12 | * \brief The KSystemClockMonitor class provides a way for monitoring system clock changes. 13 | * 14 | * The KSystemClockMonitor class makes it possible to detect discontinuous changes to the system 15 | * clock. Such changes are usually initiated by the user adjusting values in the Date and Time 16 | * KCM or calls made to functions like settimeofday(). 17 | */ 18 | 19 | class KSystemClockMonitor::Private 20 | { 21 | public: 22 | void loadMonitorEngine(); 23 | void unloadMonitorEngine(); 24 | 25 | KSystemClockMonitor *monitor = nullptr; 26 | KSystemClockMonitorEngine *engine = nullptr; 27 | bool isActive = false; 28 | }; 29 | 30 | void KSystemClockMonitor::Private::loadMonitorEngine() 31 | { 32 | engine = KSystemClockMonitorEngine::create(monitor); 33 | 34 | if (engine) { 35 | QObject::connect(engine, &KSystemClockMonitorEngine::systemClockChanged, 36 | monitor, &KSystemClockMonitor::systemClockChanged); 37 | } 38 | } 39 | 40 | void KSystemClockMonitor::Private::unloadMonitorEngine() 41 | { 42 | if (!engine) 43 | return; 44 | 45 | QObject::disconnect(engine, &KSystemClockMonitorEngine::systemClockChanged, 46 | monitor, &KSystemClockMonitor::systemClockChanged); 47 | engine->deleteLater(); 48 | 49 | engine = nullptr; 50 | } 51 | 52 | /*! 53 | * Constructs an inactive KSystemClockMonitor object with the given \p parent. 54 | */ 55 | KSystemClockMonitor::KSystemClockMonitor(QObject *parent) 56 | : QObject(parent) 57 | , d(new Private) 58 | { 59 | d->monitor = this; 60 | } 61 | 62 | /*! 63 | * Destructs the KSystemClockMonitor object. 64 | */ 65 | KSystemClockMonitor::~KSystemClockMonitor() 66 | { 67 | } 68 | 69 | /*! 70 | * Returns \c true if the KSystemClockMonitor is active; otherwise returns \c false. 71 | */ 72 | bool KSystemClockMonitor::isActive() const 73 | { 74 | return d->isActive; 75 | } 76 | 77 | /*! 78 | * Sets the active status of the KSystemClockMonitor to \p active. 79 | * 80 | * systemClockChanged() signal won't be emitted while the monitor is inactive. 81 | * 82 | * The monitor is inactive by default. 83 | * 84 | * @see activeChanged 85 | */ 86 | void KSystemClockMonitor::setActive(bool set) 87 | { 88 | if (d->isActive == set) 89 | return; 90 | 91 | d->isActive = set; 92 | 93 | if (d->isActive) 94 | d->loadMonitorEngine(); 95 | else 96 | d->unloadMonitorEngine(); 97 | 98 | Q_EMIT activeChanged(); 99 | } 100 | 101 | /*! 102 | * @fn void KSystemClockMonitor::activeChanged() 103 | * 104 | * This signal is emitted when the active property has been changed. 105 | */ 106 | 107 | /*! 108 | * @fn void KSystemClockMonitor::systemClockChanged() 109 | * 110 | * This signal is emitted when the system clock has been changed. 111 | */ 112 | -------------------------------------------------------------------------------- /src/lib/ksystemclockmonitor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "kdynamicwallpaper_export.h" 10 | 11 | #include 12 | 13 | class KDYNAMICWALLPAPER_EXPORT KSystemClockMonitor : public QObject 14 | { 15 | Q_OBJECT 16 | Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged) 17 | 18 | public: 19 | explicit KSystemClockMonitor(QObject *parent = nullptr); 20 | ~KSystemClockMonitor() override; 21 | 22 | bool isActive() const; 23 | void setActive(bool active); 24 | 25 | Q_SIGNALS: 26 | void activeChanged(); 27 | void systemClockChanged(); 28 | 29 | private: 30 | class Private; 31 | QScopedPointer d; 32 | }; 33 | -------------------------------------------------------------------------------- /src/lib/ksystemclockmonitorengine.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #include "ksystemclockmonitorengine_p.h" 8 | #if defined(Q_OS_LINUX) 9 | #include "ksystemclockmonitorengine_linux_p.h" 10 | #endif 11 | 12 | KSystemClockMonitorEngine *KSystemClockMonitorEngine::create(QObject *parent) 13 | { 14 | #if defined(Q_OS_LINUX) 15 | return KLinuxSystemClockMonitorEngine::create(parent); 16 | #else 17 | return nullptr; 18 | #endif 19 | } 20 | 21 | KSystemClockMonitorEngine::KSystemClockMonitorEngine(QObject *parent) 22 | : QObject(parent) 23 | { 24 | } 25 | -------------------------------------------------------------------------------- /src/lib/ksystemclockmonitorengine_linux.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #include "ksystemclockmonitorengine_linux_p.h" 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #ifndef TFD_TIMER_CANCEL_ON_SET 16 | #define TFD_TIMER_CANCEL_ON_SET (1 << 1) 17 | #endif 18 | 19 | KLinuxSystemClockMonitorEngine *KLinuxSystemClockMonitorEngine::create(QObject *parent) 20 | { 21 | const int fd = timerfd_create(CLOCK_REALTIME, O_CLOEXEC | O_NONBLOCK); 22 | if (fd == -1) { 23 | qWarning("Couldn't create clock skew notifier engine: %s", strerror(errno)); 24 | return nullptr; 25 | } 26 | 27 | const itimerspec spec = {}; 28 | const int ret = timerfd_settime(fd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &spec, nullptr); 29 | if (ret == -1) { 30 | qWarning("Couldn't create clock skew notifier engine: %s", strerror(errno)); 31 | close(fd); 32 | return nullptr; 33 | } 34 | 35 | return new KLinuxSystemClockMonitorEngine(fd, parent); 36 | } 37 | 38 | KLinuxSystemClockMonitorEngine::KLinuxSystemClockMonitorEngine(int fd, QObject *parent) 39 | : KSystemClockMonitorEngine(parent) 40 | , m_fd(fd) 41 | { 42 | const QSocketNotifier *notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); 43 | connect(notifier, &QSocketNotifier::activated, 44 | this, &KLinuxSystemClockMonitorEngine::handleTimerCancelled); 45 | } 46 | 47 | KLinuxSystemClockMonitorEngine::~KLinuxSystemClockMonitorEngine() 48 | { 49 | close(m_fd); 50 | } 51 | 52 | void KLinuxSystemClockMonitorEngine::handleTimerCancelled() 53 | { 54 | uint64_t expirationCount; 55 | const ssize_t readCount = read(m_fd, &expirationCount, sizeof(expirationCount)); 56 | if (readCount != -1 || errno != ECANCELED) { 57 | return; 58 | } 59 | 60 | Q_EMIT systemClockChanged(); 61 | } 62 | -------------------------------------------------------------------------------- /src/lib/ksystemclockmonitorengine_linux_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "ksystemclockmonitorengine_p.h" 10 | 11 | class KLinuxSystemClockMonitorEngine : public KSystemClockMonitorEngine 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | ~KLinuxSystemClockMonitorEngine() override; 17 | 18 | static KLinuxSystemClockMonitorEngine *create(QObject *parent); 19 | 20 | private Q_SLOTS: 21 | void handleTimerCancelled(); 22 | 23 | private: 24 | KLinuxSystemClockMonitorEngine(int fd, QObject *parent); 25 | 26 | int m_fd; 27 | }; 28 | -------------------------------------------------------------------------------- /src/lib/ksystemclockmonitorengine_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | class KSystemClockMonitorEngine : public QObject 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | static KSystemClockMonitorEngine *create(QObject *parent); 17 | 18 | protected: 19 | explicit KSystemClockMonitorEngine(QObject *parent); 20 | 21 | Q_SIGNALS: 22 | void systemClockChanged(); 23 | }; 24 | -------------------------------------------------------------------------------- /src/lib/metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/resources.qrc: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | metadata.xml 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/package/contents/config/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 2 19 | 20 | 21 | 22 | true 23 | 24 | 25 | 26 | 0 27 | -90 28 | 90 29 | 30 | 31 | 32 | 0 33 | -180 34 | 180 35 | 36 | 37 | 38 | 300000 39 | 40 | 41 | 42 | 330 43 | 100 44 | 1000 45 | 46 | 47 | 48 | true 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/package/contents/ui/DecimalSpinBox.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Controls as QtControls2 9 | 10 | Item { 11 | // QtControls 2 doen't provide a decimal spinbox out of the box. So we wrap the integer 12 | // based spinbox and perform integer <-> decimal conversions internally. 13 | 14 | id: container 15 | implicitWidth: control.implicitWidth 16 | implicitHeight: control.implicitHeight 17 | 18 | function __integerToDecimal(integer) { 19 | return integer / 100; 20 | } 21 | function __decimalToInteger(decimal) { 22 | return Math.round(decimal * 100); 23 | } 24 | 25 | property real from: 0 26 | property real to: 99 27 | 28 | property int decimals: 2 29 | property real value: 0 30 | property real stepSize: 1 31 | 32 | QtControls2.SpinBox { 33 | id: control 34 | anchors.fill: parent 35 | 36 | from: __decimalToInteger(container.from) 37 | to: __decimalToInteger(container.to) 38 | stepSize: __decimalToInteger(container.stepSize) 39 | value: __decimalToInteger(container.value) 40 | 41 | validator: DoubleValidator { 42 | bottom: Math.min(control.from, control.to) 43 | top: Math.max(control.from, control.to) 44 | } 45 | 46 | textFromValue: function(value, locale) { 47 | return __integerToDecimal(value).toLocaleString(locale, 'f', container.decimals); 48 | } 49 | valueFromText: function(text, locale) { 50 | return __decimalToInteger(Number.fromLocaleString(locale, text)); 51 | } 52 | 53 | onValueModified: { 54 | // Update the outer decimal value, this will be called when user has modified the 55 | // spinbox value, so it should not be possible to have a binding loop. 56 | container.value = __integerToDecimal(control.value); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/package/contents/ui/WallpaperImage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | 9 | Item { 10 | id: root 11 | 12 | /*! 13 | * The image being displayed in the bottom layer. 14 | */ 15 | property url bottomLayer 16 | 17 | /*! 18 | * The image being displayed in the top layer. 19 | */ 20 | property url topLayer 21 | 22 | /*! 23 | * The scaled width and height of the full-frame image. 24 | */ 25 | property size sourceSize 26 | 27 | /*! 28 | * The blend factor between the bottom layer and the top layer. 29 | * 30 | * The blend factor varies between 0 and 1. 0 means that only the bottom 31 | * layer is visible; 1 means that only the top layer is visible. 32 | * 33 | * The default value is 0. 34 | */ 35 | property real blendFactor: 0 36 | 37 | /*! 38 | * Set this property to define what happens when the source image has a 39 | * different size than the item. 40 | * 41 | * Defaults to \c Image.Stretch. 42 | */ 43 | property int fillMode: Image.Stretch 44 | 45 | /*! 46 | * This property holds the status of image loading. 47 | */ 48 | readonly property int status: { 49 | if (bottom.status == Image.Error || top.status == Image.Error) 50 | return Image.Error; 51 | if (bottom.status == Image.Loading || top.status == Image.Loading) 52 | return Image.Loading; 53 | if (bottom.status == Image.Ready || top.status == Image.Ready) 54 | return Image.Ready; 55 | return Image.Null; 56 | } 57 | 58 | Image { 59 | id: bottom 60 | anchors.fill: parent 61 | asynchronous: true 62 | autoTransform: true 63 | cache: wallpaper.configuration.Cache 64 | fillMode: root.fillMode 65 | source: root.bottomLayer 66 | sourceSize: root.sourceSize 67 | } 68 | 69 | Image { 70 | id: top 71 | anchors.fill: parent 72 | asynchronous: true 73 | autoTransform: true 74 | cache: wallpaper.configuration.Cache 75 | fillMode: root.fillMode 76 | opacity: root.blendFactor 77 | source: root.topLayer 78 | sourceSize: root.sourceSize 79 | } 80 | 81 | Behavior on blendFactor { 82 | NumberAnimation { 83 | duration: wallpaper.configuration.TransitionDuration 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/package/contents/ui/WallpaperView.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Controls 9 | 10 | StackView { 11 | id: root 12 | 13 | /*! 14 | * The image being displayed in the bottom layer. 15 | */ 16 | property url bottomLayer 17 | 18 | /*! 19 | * The image being displayed in the top layer. 20 | */ 21 | property url topLayer 22 | 23 | /*! 24 | * The blend factor between the bottom layer and the top layer. 25 | * 26 | * The blend factor varies between 0 and 1. 0 means that only the bottom 27 | * layer is visible; 1 means that only the top layer is visible. 28 | */ 29 | property real blendFactor 30 | 31 | /*! 32 | * Set this property to define what happens when the source image has a 33 | * different size than the item. 34 | */ 35 | property int fillMode 36 | 37 | /*! 38 | * The scaled width and height of the full-frame image. 39 | */ 40 | property size sourceSize 41 | 42 | /*! 43 | * This property holds the wallpaper image about to be presented. 44 | */ 45 | property WallpaperImage __nextItem: null 46 | 47 | /*! 48 | * This property holds the status of image loading. 49 | */ 50 | readonly property int status: __nextItem ? __nextItem.status : Image.Null 51 | 52 | onBottomLayerChanged: Qt.callLater(reload) 53 | onTopLayerChanged: Qt.callLater(reload) 54 | onBlendFactorChanged: Qt.callLater(reblend) 55 | onFillModeChanged: Qt.callLater(reload) 56 | 57 | Component { 58 | id: baseImage 59 | 60 | WallpaperImage { 61 | layer.enabled: root.replaceEnter.running 62 | StackView.onRemoved: destroy() 63 | } 64 | } 65 | 66 | replaceEnter: Transition { 67 | NumberAnimation { 68 | property: "opacity" 69 | from: 0 70 | to: 1 71 | duration: wallpaper.configuration.TransitionDuration 72 | } 73 | } 74 | 75 | replaceExit: Transition { 76 | PauseAnimation { 77 | duration: wallpaper.configuration.TransitionDuration 78 | } 79 | } 80 | 81 | function __swap() { 82 | if (root.__nextItem.status == Image.Loading) 83 | return; 84 | 85 | root.__nextItem.statusChanged.disconnect(root.__swap); 86 | 87 | if (root.__nextItem.status == Image.Error) 88 | return; 89 | 90 | var operation; 91 | if (!root.currentItem) 92 | operation = StackView.Immediate; 93 | else if (root.currentItem.bottomLayer !== bottomLayer) 94 | operation = StackView.Transition; 95 | else if (root.currentItem.topLayer !== topLayer) 96 | operation = StackView.Transition; 97 | else 98 | operation = StackView.Immediate; 99 | 100 | if (operation === StackView.Transition) 101 | root.__nextItem.opacity = 0; 102 | else 103 | root.__nextItem.opacity = 1; 104 | 105 | root.replace(root.__nextItem, {}, operation); 106 | } 107 | 108 | function reload() { 109 | if (root.status == Image.Loading) 110 | root.__nextItem.statusChanged.disconnect(root.__swap); 111 | 112 | root.__nextItem = baseImage.createObject(root, { 113 | bottomLayer: bottomLayer, 114 | topLayer: topLayer, 115 | blendFactor: blendFactor, 116 | fillMode: fillMode, 117 | sourceSize: sourceSize 118 | }); 119 | 120 | if (root.__nextItem.status == Image.Loading) 121 | root.__nextItem.statusChanged.connect(root.__swap); 122 | else 123 | root.__swap(); 124 | } 125 | 126 | function reblend() { 127 | if (!root.currentItem) 128 | return; 129 | if (root.currentItem.bottomLayer !== bottomLayer) 130 | return; 131 | if (root.currentItem.topLayer !== topLayer) 132 | return; 133 | root.currentItem.blendFactor = blendFactor; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/package/contents/ui/config.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import QtCore 8 | import QtQuick 9 | import QtQuick.Dialogs 10 | import QtQuick.Layouts 11 | import QtQuick.Controls as QtControls2 12 | import QtPositioning 13 | 14 | import org.kde.kcmutils as KCM 15 | import org.kde.kirigami as Kirigami 16 | import com.github.zzag.plasma.wallpapers.dynamic 17 | 18 | ColumnLayout { 19 | id: root 20 | 21 | property int cfg_FillMode 22 | property string cfg_Image 23 | property alias cfg_UpdateInterval: updateIntervalSpinBox.value 24 | property alias cfg_AutoDetectLocation: autoDetectLocationCheckBox.checked 25 | property alias cfg_ManualLatitude: latitudeSpinBox.value 26 | property alias cfg_ManualLongitude: longitudeSpinBox.value 27 | 28 | function saveConfig() { 29 | wallpapersModel.purge(); 30 | } 31 | 32 | Kirigami.FormLayout { 33 | twinFormLayouts: parentLayout 34 | 35 | QtControls2.ComboBox { 36 | id: positioningComboBox 37 | Kirigami.FormData.label: i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Fill Mode:") 38 | textRole: "text" 39 | model: [ 40 | { 41 | "text": i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Scaled and Cropped"), 42 | "value": Image.PreserveAspectCrop 43 | }, 44 | { 45 | "text": i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Scaled"), 46 | "value": Image.Stretch 47 | }, 48 | { 49 | "text": i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Scaled, Keep Proportions"), 50 | "value": Image.PreserveAspectFit 51 | }, 52 | { 53 | "text": i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Centered"), 54 | "value": Image.Pad 55 | }, 56 | { 57 | "text": i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Tiled"), 58 | "value": Image.Tile 59 | } 60 | ] 61 | 62 | onActivated: cfg_FillMode = model[currentIndex]["value"] 63 | 64 | Component.onCompleted: { 65 | for (var i = 0; i < model.length; i++) { 66 | if (model[i]["value"] === wallpaper.configuration.FillMode) { 67 | positioningComboBox.currentIndex = i; 68 | break; 69 | } 70 | } 71 | } 72 | } 73 | 74 | QtControls2.CheckBox { 75 | id: autoDetectLocationCheckBox 76 | text: i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Automatically detect location") 77 | } 78 | 79 | DecimalSpinBox { 80 | Kirigami.FormData.label: i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Latitude:") 81 | enabled: !autoDetectLocationCheckBox.checked 82 | visible: autoDetectLocationCheckBox.checked 83 | decimals: 2 84 | from: -90 85 | to: 90 86 | value: automaticLocationProvider.position.coordinate.latitude 87 | } 88 | 89 | DecimalSpinBox { 90 | Kirigami.FormData.label: i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Longitude:") 91 | enabled: !autoDetectLocationCheckBox.checked 92 | visible: autoDetectLocationCheckBox.checked 93 | decimals: 2 94 | from: -180 95 | to: 180 96 | value: automaticLocationProvider.position.coordinate.longitude 97 | } 98 | 99 | DecimalSpinBox { 100 | id: latitudeSpinBox 101 | Kirigami.FormData.label: i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Latitude:") 102 | decimals: 2 103 | from: -90 104 | to: 90 105 | visible: !autoDetectLocationCheckBox.checked 106 | } 107 | 108 | DecimalSpinBox { 109 | id: longitudeSpinBox 110 | Kirigami.FormData.label: i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Longitude:") 111 | decimals: 2 112 | from: -180 113 | to: 180 114 | visible: !autoDetectLocationCheckBox.checked 115 | } 116 | 117 | QtControls2.SpinBox { 118 | id: updateIntervalSpinBox 119 | Kirigami.FormData.label: i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Update Every:") 120 | to: minutesToMilliseconds(360) 121 | from: minutesToMilliseconds(1) 122 | stepSize: minutesToMilliseconds(1) 123 | 124 | function millisecondsToMinutes(milliseconds) { 125 | return milliseconds / 60000; 126 | } 127 | function minutesToMilliseconds(minutes) { 128 | return minutes * 60000; 129 | } 130 | 131 | valueFromText: function(text, locale) { 132 | return minutesToMilliseconds(Number.fromLocaleString(locale, text)); 133 | } 134 | textFromValue: function(value, locale) { 135 | const minutes = millisecondsToMinutes(value); 136 | return minutes.toLocaleString(locale, 'f', 0) + 137 | i18ndp("plasma_wallpaper_com.github.zzag.dynamic", " minute", " minutes", minutes); 138 | } 139 | } 140 | } 141 | 142 | Kirigami.InlineMessage { 143 | id: errorContainer 144 | Layout.fillWidth: true 145 | showCloseButton: true 146 | type: Kirigami.MessageType.Error 147 | visible: false 148 | } 149 | 150 | Item { 151 | Layout.fillWidth: true 152 | Layout.fillHeight: true 153 | 154 | KCM.GridView { 155 | id: wallpapersGrid 156 | anchors.fill: parent 157 | 158 | function resetCurrentIndex() { 159 | view.currentIndex = wallpapersModel.find(cfg_Image); 160 | } 161 | 162 | view.model: wallpapersModel 163 | view.delegate: KCM.GridDelegate { 164 | hoverEnabled: true 165 | opacity: model.zombie ? 0.5 : 1 166 | text: model.name 167 | toolTip: { 168 | if (model.author && model.license) 169 | return i18ndc("plasma_wallpaper_com.github.zzag.dynamic", " by ()", "By %1 (%2)", model.author, model.license); 170 | if (model.license) 171 | return i18ndc("plasma_wallpaper_com.github.zzag.dynamic", " ()", "%1 (%2)", model.name, model.license); 172 | return model.name; 173 | } 174 | actions: [ 175 | Kirigami.Action { 176 | icon.name: "document-open-folder" 177 | tooltip: i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Open Containing Folder") 178 | onTriggered: Qt.openUrlExternally(model.folder) 179 | }, 180 | Kirigami.Action { 181 | icon.name: "edit-undo" 182 | tooltip: i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Restore Wallpaper") 183 | visible: model.zombie 184 | onTriggered: wallpapersModel.unscheduleRemove(wallpapersModel.modelIndex(index)) 185 | }, 186 | Kirigami.Action { 187 | icon.name: "edit-delete" 188 | tooltip: i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Remove Wallpaper") 189 | visible: !model.zombie && model.removable 190 | onTriggered: wallpapersModel.scheduleRemove(wallpapersModel.modelIndex(index)) 191 | } 192 | ] 193 | thumbnail: Image { 194 | anchors.fill: parent 195 | fillMode: cfg_FillMode 196 | source: model.preview 197 | } 198 | onClicked: { 199 | cfg_Image = model.image; 200 | wallpapersGrid.forceActiveFocus(); 201 | } 202 | } 203 | 204 | Connections { 205 | target: wallpapersModel 206 | onRowsInserted: Qt.callLater(wallpapersGrid.resetCurrentIndex) 207 | onRowsRemoved: Qt.callLater(wallpapersGrid.resetCurrentIndex) 208 | } 209 | Connections { 210 | target: root 211 | onCfg_ImageChanged: Qt.callLater(wallpapersGrid.resetCurrentIndex) 212 | } 213 | } 214 | } 215 | 216 | Loader { 217 | id: wallpaperDialogLoader 218 | active: false 219 | sourceComponent: FileDialog { 220 | title: i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Open Wallpaper") 221 | currentFolder: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0] 222 | nameFilters: [i18nd("plasma_wallpaper_com.github.zzag.dynamic", "AVIF Image Files (*.avif)")] 223 | onAccepted: { 224 | wallpapersModel.add(selectedFile); 225 | wallpaperDialogLoader.active = false; 226 | } 227 | onRejected: { 228 | wallpaperDialogLoader.active = false; 229 | } 230 | Component.onCompleted: open() 231 | } 232 | } 233 | 234 | RowLayout { 235 | Layout.alignment: Qt.AlignRight 236 | 237 | QtControls2.Button { 238 | icon.name: "list-add" 239 | text: i18nd("plasma_wallpaper_com.github.zzag.dynamic", "Add Wallpaper...") 240 | onClicked: wallpaperDialogLoader.active = true 241 | } 242 | } 243 | 244 | PositionSource { 245 | id: automaticLocationProvider 246 | active: autoDetectLocationCheckBox.checked 247 | } 248 | 249 | DynamicWallpaperModel { 250 | id: wallpapersModel 251 | } 252 | 253 | function showErrorMessage(message) { 254 | errorContainer.text = message; 255 | errorContainer.visible = true; 256 | } 257 | 258 | Component.onCompleted: { 259 | wallpapersModel.errorOccurred.connect(showErrorMessage); 260 | wallpapersModel.reload() 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/package/contents/ui/main.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Window 9 | import QtPositioning 10 | 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.plasma.plasmoid 13 | import com.github.zzag.plasma.wallpapers.dynamic 14 | 15 | WallpaperItem { 16 | id: root 17 | 18 | PositionSource { 19 | id: automaticLocationProvider 20 | active: wallpaper.configuration.AutoDetectLocation 21 | } 22 | 23 | Location { 24 | id: manualLocationProvider 25 | coordinate { 26 | latitude: wallpaper.configuration.ManualLatitude 27 | longitude: wallpaper.configuration.ManualLongitude 28 | } 29 | } 30 | 31 | WallpaperView { 32 | id: view 33 | anchors.fill: parent 34 | blendFactor: handler.blendFactor 35 | bottomLayer: handler.bottomLayer 36 | fillMode: wallpaper.configuration.FillMode 37 | sourceSize: Qt.size(root.width, root.height) 38 | topLayer: handler.topLayer 39 | visible: handler.status == DynamicWallpaperHandler.Ready 40 | onStatusChanged: if (status != Image.Loading) { 41 | wallpaper.loading = false; 42 | } 43 | } 44 | 45 | Rectangle { 46 | anchors.fill: parent 47 | Kirigami.Theme.colorSet: Kirigami.Theme.View 48 | color: Kirigami.Theme.backgroundColor 49 | visible: handler.status == DynamicWallpaperHandler.Error 50 | 51 | Text { 52 | anchors.left: parent.left 53 | anchors.right: parent.right 54 | anchors.verticalCenter: parent.verticalCenter 55 | font.pointSize: 24 56 | horizontalAlignment: Text.AlignHCenter 57 | text: handler.errorString 58 | wrapMode: Text.Wrap 59 | } 60 | } 61 | 62 | DynamicWallpaperHandler { 63 | id: handler 64 | location: { 65 | if (wallpaper.configuration.AutoDetectLocation) 66 | return automaticLocationProvider.position.coordinate; 67 | return manualLocationProvider.coordinate; 68 | } 69 | source: wallpaper.configuration.Image 70 | onStatusChanged: if (status == DynamicWallpaperHandler.Error) { 71 | wallpaper.loading = false; 72 | } 73 | } 74 | 75 | SystemClockMonitor { 76 | active: handler.status == DynamicWallpaperHandler.Ready 77 | onSystemClockChanged: handler.scheduleUpdate() 78 | } 79 | 80 | Timer { 81 | interval: wallpaper.configuration.UpdateInterval 82 | repeat: true 83 | running: handler.status == DynamicWallpaperHandler.Ready 84 | onTriggered: handler.scheduleUpdate() 85 | } 86 | 87 | Component.onCompleted: { 88 | wallpaper.loading = handler.status == DynamicWallpaperHandler.Ready; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/package/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "KPackageStructure": "Plasma/Wallpaper", 3 | "KPlugin": { 4 | "Authors": [ 5 | { 6 | "Email": "vlad.zahorodnii@kde.org", 7 | "Name": "Vlad Zahorodnii" 8 | } 9 | ], 10 | "Category": "", 11 | "Description": "Dynamic wallpaper", 12 | "Description[de]": "Dynamischer Hintergrund", 13 | "Description[pt]": "Papel de parede dinâmico", 14 | "Description[ru]": "Динамические обои", 15 | "Description[uk]": "Динамічні шпалери", 16 | "Description[zh]": "动态壁纸", 17 | "Icon": "preferences-desktop-wallpaper", 18 | "Id": "com.github.zzag.dynamic", 19 | "License": "GPLv3+", 20 | "Name": "Dynamic", 21 | "Name[de]": "Dynamisch", 22 | "Name[fr]": "Dynamique", 23 | "Name[it]": "Dinamico", 24 | "Name[nl]": "Dynamisch", 25 | "Name[pt]": "Dinâmico", 26 | "Name[ru]": "Динамический", 27 | "Name[uk]": "Динамічний", 28 | "Name[zh]": "动态", 29 | "Version": "", 30 | "Website": "https://github.com/zzag/plasma5-wallpapers-dynamic" 31 | }, 32 | "X-KDE-ParentApp": "org.kde.plasmashell" 33 | } 34 | -------------------------------------------------------------------------------- /src/package/metadata.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | 3 | SPDX-License-Identifier: CC0-1.0 4 | -------------------------------------------------------------------------------- /src/plugins/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | add_subdirectory(kpackage-integration) 6 | -------------------------------------------------------------------------------- /src/plugins/kpackage-integration/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | add_definitions(-DTRANSLATION_DOMAIN=\"plasma_wallpaper_com.github.zzag.dynamic\") 6 | 7 | kcoreaddons_add_plugin(packagestructure_dynamicwallpaper INSTALL_NAMESPACE "kpackage/packagestructure") 8 | 9 | set_target_properties(packagestructure_dynamicwallpaper PROPERTIES 10 | OUTPUT_NAME kdynamicwallpaper 11 | ) 12 | 13 | target_sources(packagestructure_dynamicwallpaper PRIVATE 14 | dynamicwallpaperpackagestructure.cpp 15 | ) 16 | 17 | target_link_libraries(packagestructure_dynamicwallpaper 18 | Qt6::Core 19 | 20 | KF6::Package 21 | ) 22 | -------------------------------------------------------------------------------- /src/plugins/kpackage-integration/dynamicwallpaperpackagestructure.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #include "dynamicwallpaperpackagestructure.h" 8 | 9 | #include 10 | 11 | DynamicWallpaperPackageStructure::DynamicWallpaperPackageStructure(QObject *parent, const QVariantList &args) 12 | : KPackage::PackageStructure(parent, args) 13 | { 14 | } 15 | 16 | void DynamicWallpaperPackageStructure::initPackage(KPackage::Package *package) 17 | { 18 | package->addDirectoryDefinition(QByteArrayLiteral("images"), 19 | QStringLiteral("images/")); 20 | package->setRequired(QByteArrayLiteral("images"), true); 21 | } 22 | 23 | void DynamicWallpaperPackageStructure::pathChanged(KPackage::Package *package) 24 | { 25 | package->removeDefinition(QByteArrayLiteral("dynamic")); 26 | 27 | const QStringList fileFormats { QStringLiteral(".avif") }; 28 | 29 | for (const QString &fileFormat : fileFormats) { 30 | const QFileInfo fileInfo(package->path() + QLatin1String("contents/images/dynamic") + fileFormat); 31 | if (!fileInfo.exists()) 32 | continue; 33 | package->addFileDefinition(QByteArrayLiteral("dynamic"), 34 | QStringLiteral("images/dynamic") + fileFormat); 35 | package->setRequired(QByteArrayLiteral("dynamic"), true); 36 | break; 37 | } 38 | } 39 | 40 | K_PLUGIN_CLASS_WITH_JSON(DynamicWallpaperPackageStructure, "dynamicwallpaperpackagestructure.json") 41 | 42 | #include "dynamicwallpaperpackagestructure.moc" 43 | -------------------------------------------------------------------------------- /src/plugins/kpackage-integration/dynamicwallpaperpackagestructure.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | class DynamicWallpaperPackageStructure final : public KPackage::PackageStructure 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit DynamicWallpaperPackageStructure(QObject *parent = nullptr, const QVariantList &args = QVariantList()); 17 | 18 | void initPackage(KPackage::Package *package) override; 19 | void pathChanged(KPackage::Package *package) override; 20 | }; 21 | -------------------------------------------------------------------------------- /src/plugins/kpackage-integration/dynamicwallpaperpackagestructure.json: -------------------------------------------------------------------------------- 1 | { 2 | "KPackageStructure": "KPackage/PackageStructure" 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/kpackage-integration/dynamicwallpaperpackagestructure.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | 3 | SPDX-License-Identifier: CC0-1.0 4 | -------------------------------------------------------------------------------- /src/tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | add_subdirectory(builder) 6 | -------------------------------------------------------------------------------- /src/tools/builder/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | add_definitions(-DTRANSLATION_DOMAIN=\"plasma_wallpaper_com.github.zzag.dynamic\") 6 | 7 | add_subdirectory(completions) 8 | 9 | set(builder_SOURCES 10 | dynamicwallpaperexifmetadata.cpp 11 | dynamicwallpapermanifest.cpp 12 | main.cpp 13 | ) 14 | 15 | set(builder_LIBRARIES 16 | KF6::I18n 17 | libexif::libexif 18 | KDynamicWallpaper::KDynamicWallpaper 19 | ) 20 | 21 | add_executable(kdynamicwallpaperbuilder ${builder_SOURCES}) 22 | target_link_libraries(kdynamicwallpaperbuilder ${builder_LIBRARIES}) 23 | 24 | install(TARGETS kdynamicwallpaperbuilder ${INSTALL_TARGETS_DEFAULT_ARGS}) 25 | -------------------------------------------------------------------------------- /src/tools/builder/completions/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | add_subdirectory(bash) 6 | add_subdirectory(fish) 7 | add_subdirectory(zsh) 8 | -------------------------------------------------------------------------------- /src/tools/builder/completions/bash/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | install(FILES kdynamicwallpaperbuilder DESTINATION ${KDE_INSTALL_DATADIR}/bash-completion/completions/) 6 | -------------------------------------------------------------------------------- /src/tools/builder/completions/bash/kdynamicwallpaperbuilder: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: CC0-1.0 4 | 5 | _kdynamicwallpaperbuilder_module() 6 | { 7 | local cur prev OPTS 8 | _init_completion || return 9 | 10 | case $prev in 11 | '-h'|'--help'|'--help-all'|'-v'|'--version') 12 | return 13 | ;; 14 | esac 15 | 16 | case $cur in 17 | -*) 18 | OPTS=" 19 | --output 20 | --max-threads 21 | " 22 | COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) ) 23 | return 24 | ;; 25 | esac 26 | } 27 | complete -F _kdynamicwallpaperbuilder_module kdynamicwallpaperbuilder 28 | 29 | # ex: filetype=sh 30 | -------------------------------------------------------------------------------- /src/tools/builder/completions/fish/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | install(FILES kdynamicwallpaperbuilder.fish DESTINATION ${KDE_INSTALL_DATADIR}/fish/completions) 6 | -------------------------------------------------------------------------------- /src/tools/builder/completions/fish/kdynamicwallpaperbuilder.fish: -------------------------------------------------------------------------------- 1 | # kdynamicwallpaperbuilder(1) completion 2 | 3 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 4 | # 5 | # SPDX-License-Identifier: CC0-1.0 6 | 7 | complete -f -c kdynamicwallpaperbuilder 8 | complete -c kdynamicwallpaperbuilder -s v -l version -d "Print the version information and quit" 9 | complete -c kdynamicwallpaperbuilder -s h -l help -d "Show help message and quit" 10 | complete -c kdynamicwallpaperbuilder -l help-all -d "Show help message including Qt specific options and quit" 11 | 12 | complete -c kdynamicwallpaperbuilder -l output -d "Specify the file where the output will be written" -r 13 | complete -c kdynamicwallpaperbuilder -l max-threads -d "Maximum number of threads that can be used when encoding a wallpaper" -r 14 | -------------------------------------------------------------------------------- /src/tools/builder/completions/zsh/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | install(FILES _kdynamicwallpaperbuilder DESTINATION ${KDE_INSTALL_DATADIR}/zsh/site-functions/) 6 | -------------------------------------------------------------------------------- /src/tools/builder/completions/zsh/_kdynamicwallpaperbuilder: -------------------------------------------------------------------------------- 1 | #compdef kdynamicwallpaperbuilder 2 | 3 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 4 | # 5 | # SPDX-License-Identifier: CC0-1.0 6 | 7 | _arguments -s \ 8 | {-v,--version}'[Show the version information and quit]' \ 9 | {-h,--help}'[Show help message and quit]' \ 10 | '--help-all[Show help message including Qt specific options and quit]' \ 11 | '--output[Specify the file where the output will be written]:files:_files' \ 12 | '--max-threads[Maximum number of threads that can be used when encoding a wallpaper]' 13 | -------------------------------------------------------------------------------- /src/tools/builder/dynamicwallpaperexifmetadata.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | class DynamicWallpaperExifMetaDataPrivate; 14 | 15 | class DynamicWallpaperExifMetaData 16 | { 17 | public: 18 | enum MetaDataField { 19 | BirthDateTimeField = 0x1, 20 | SolarCoordinatesField = 0x2, 21 | }; 22 | Q_DECLARE_FLAGS(MetaDataFields, MetaDataField) 23 | 24 | explicit DynamicWallpaperExifMetaData(const QString &fileName); 25 | DynamicWallpaperExifMetaData(const DynamicWallpaperExifMetaData &other); 26 | ~DynamicWallpaperExifMetaData(); 27 | 28 | DynamicWallpaperExifMetaData &operator=(const DynamicWallpaperExifMetaData &other); 29 | 30 | QDateTime birthDateTime() const; 31 | qreal solarAzimuth() const; 32 | qreal solarElevation() const; 33 | 34 | MetaDataFields fields() const; 35 | bool isValid() const; 36 | 37 | private: 38 | QSharedDataPointer d; 39 | }; 40 | -------------------------------------------------------------------------------- /src/tools/builder/dynamicwallpapermanifest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | class DynamicWallpaperManifest 17 | { 18 | public: 19 | explicit DynamicWallpaperManifest(const QString &fileName); 20 | ~DynamicWallpaperManifest(); 21 | 22 | QList metaData() const; 23 | QList images() const; 24 | 25 | bool hasError() const; 26 | QString errorString() const; 27 | 28 | private: 29 | QString resolveFileName(const QString &fileName) const; 30 | 31 | void init(); 32 | void setError(const QString &text); 33 | void parseSolar(const QJsonArray &entries); 34 | void parseDayNight(const QJsonArray &entries); 35 | 36 | QString m_manifestFileName; 37 | QList m_metaDataList; 38 | QList m_imageList; 39 | QString m_errorString; 40 | bool m_hasError = false; 41 | }; 42 | -------------------------------------------------------------------------------- /src/tools/builder/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "dynamicwallpapermanifest.h" 19 | 20 | int main(int argc, char **argv) 21 | { 22 | QCoreApplication app(argc, argv); 23 | QCoreApplication::setApplicationName(QStringLiteral("kdynamicwallpaperbuilder")); 24 | QCoreApplication::setApplicationVersion(QStringLiteral("1.0")); 25 | 26 | QCommandLineOption outputOption(QStringLiteral("output")); 27 | outputOption.setDescription(i18n("Write output to ")); 28 | outputOption.setValueName(QStringLiteral("file")); 29 | 30 | QCommandLineOption speedOption(QStringLiteral("speed")); 31 | speedOption.setDescription(i18n("Encoding speed, 0 - slowest, 10 - fastest")); 32 | speedOption.setValueName(QStringLiteral("speed")); 33 | 34 | QCommandLineOption maxThreadsOption(QStringLiteral("max-threads")); 35 | maxThreadsOption.setDescription(i18n("Maximum number of threads that can be used when encoding a wallpaper")); 36 | maxThreadsOption.setValueName(QStringLiteral("max-threads")); 37 | 38 | QCommandLineOption codecOption(QStringLiteral("codec")); 39 | codecOption.setDescription(i18n("Codec to use (aom|rav1e|svt)")); 40 | codecOption.setValueName(QStringLiteral("codec")); 41 | 42 | QCommandLineOption verboseOption(QStringLiteral("verbose")); 43 | verboseOption.setDescription(i18n("Show debug information")); 44 | 45 | QCommandLineParser parser; 46 | parser.addHelpOption(); 47 | parser.addVersionOption(); 48 | parser.addPositionalArgument(QStringLiteral("json"), i18n("Manifest file to use")); 49 | parser.addOption(outputOption); 50 | parser.addOption(maxThreadsOption); 51 | parser.addOption(speedOption); 52 | parser.addOption(codecOption); 53 | parser.addOption(verboseOption); 54 | parser.process(app); 55 | 56 | if (parser.positionalArguments().count() != 1) 57 | parser.showHelp(-1); 58 | 59 | DynamicWallpaperManifest manifest(parser.positionalArguments().first()); 60 | if (manifest.hasError()) { 61 | if (manifest.hasError()) 62 | qWarning() << qPrintable(manifest.errorString()); 63 | return -1; 64 | } 65 | 66 | if (parser.isSet(verboseOption)) { 67 | qDebug() << "Images:"; 68 | const QList images = manifest.images(); 69 | for (int i = 0; i < images.size(); ++i) { 70 | qDebug(" [%d] -> %s", i, qUtf8Printable(images.at(i).key())); 71 | } 72 | 73 | const QList meta = manifest.metaData(); 74 | QJsonArray array; 75 | for (const KDynamicWallpaperMetaData &metaData : meta) { 76 | if (auto solar = std::get_if(&metaData)) { 77 | array.append(solar->toJson()); 78 | } else if (auto dayNight = std::get_if(&metaData)) { 79 | array.append(dayNight->toJson()); 80 | } 81 | } 82 | qDebug() << "Meta:"; 83 | const QList lines = QJsonDocument(array).toJson().split('\n'); 84 | for (const QByteArray &line : lines) { 85 | qDebug(" %s", line.constData()); 86 | } 87 | } 88 | 89 | KDynamicWallpaperWriter writer; 90 | writer.setImages(manifest.images()); 91 | writer.setMetaData(manifest.metaData()); 92 | 93 | if (parser.isSet(maxThreadsOption)) { 94 | bool ok; 95 | if (const int threadCount = parser.value(maxThreadsOption).toInt(&ok); ok) { 96 | writer.setMaxThreadCount(threadCount); 97 | } else { 98 | parser.showHelp(-1); 99 | } 100 | } 101 | 102 | if (parser.isSet(speedOption)) { 103 | bool ok; 104 | if (const int speed = parser.value(speedOption).toInt(&ok); ok) { 105 | writer.setSpeed(speed); 106 | } else { 107 | parser.showHelp(-1); 108 | } 109 | } 110 | 111 | if (parser.isSet(codecOption)) { 112 | if (!writer.setCodecName(parser.value(codecOption))) { 113 | qWarning() << qPrintable(writer.errorString()); 114 | return -1; 115 | } 116 | } 117 | 118 | QString targetFileName = parser.value(outputOption); 119 | if (targetFileName.isEmpty()) 120 | targetFileName = QStringLiteral("wallpaper.avif"); 121 | 122 | if (!writer.flush(targetFileName)) { 123 | qWarning() << writer.errorString(); 124 | QFile::remove(targetFileName); 125 | return -1; 126 | } 127 | 128 | return 0; 129 | } 130 | -------------------------------------------------------------------------------- /src/translations/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | find_program(GETTEXT_MSGFMT_EXECUTABLE msgfmt) 6 | 7 | if (NOT GETTEXT_MSGFMT_EXECUTABLE) 8 | message(WARNING "-- msgfmt: not found. Translations will not be installed") 9 | else () 10 | set(catalogname plasma_wallpaper_com.github.zzag.dynamic) 11 | add_custom_target(translations ALL) 12 | 13 | file(GLOB PO_FILES po/*.po) 14 | 15 | foreach (_po ${PO_FILES}) 16 | get_filename_component(_filename ${_po} NAME) 17 | string(REGEX REPLACE "^${catalogname}_?" "" _langCode ${_filename}) 18 | string(REGEX REPLACE "\\.po$" "" _langCode ${_langCode}) 19 | 20 | if (_langCode) 21 | get_filename_component(_lang ${_po} NAME_WE) 22 | set(_gmo ${CMAKE_CURRENT_BINARY_DIR}/${_lang}.gmo) 23 | 24 | add_custom_command(TARGET translations 25 | COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} --check -o ${_gmo} ${_po} 26 | DEPENDS ${_po}) 27 | 28 | install(FILES ${_gmo} 29 | DESTINATION ${KDE_INSTALL_DATADIR}/locale/${_langCode}/LC_MESSAGES/ 30 | RENAME ${catalogname}.mo) 31 | endif() 32 | endforeach() 33 | endif() 34 | -------------------------------------------------------------------------------- /src/translations/extract-messages.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | # SPDX-FileCopyrightText: 2020 Vlad Zahorodnii 4 | # 5 | # SPDX-License-Identifier: CC0-1.0 6 | 7 | cd "$(dirname $0)" 8 | BASEDIR="$(pwd)" 9 | SRCDIR="$(dirname $BASEDIR)" 10 | 11 | function xgettext_wrapper { 12 | xgettext --copyright-holder="This file is copyright:" \ 13 | --package-name=plasma5-wallpapers-dynamic \ 14 | --msgid-bugs-address=https://github.com/zzag/plasma5-wallpapers-dynamic/issues \ 15 | --from-code=UTF-8 \ 16 | -C --kde \ 17 | -ci18n \ 18 | -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 \ 19 | -ki18nd:2 -ki18ndc:2c,3 -ki18ndp:2,3 -ki18ndcp:2c,3,4 \ 20 | -kki18n:1 -kki18nc:1c,2 -kki18np:1,2 -kki18ncp:1c,2,3 \ 21 | -kki18nd:2 -kki18ndc:2c,3 -kki18ndp:2,3 -kki18ndcp:2c,3,4 \ 22 | -kxi18n:1 -kxi18nc:1c,2 -kxi18np:1,2 -kxi18ncp:1c,2,3 \ 23 | -kxi18nd:2 -kxi18ndc:2c,3 -kxi18ndp:2,3 -kxi18ndcp:2c,3,4 \ 24 | -kkxi18n:1 -kkxi18nc:1c,2 -kkxi18np:1,2 -kkxi18ncp:1c,2,3 \ 25 | -kkxi18nd:2 -kkxi18ndc:2c,3 -kkxi18ndp:2,3 -kkxi18ndcp:2c,3,4 \ 26 | -kI18N_NOOP:1 -kI18NC_NOOP:1c,2 \ 27 | -kI18N_NOOP2:1c,2 -kI18N_NOOP2_NOSTRIP:1c,2 \ 28 | -ktr2i18n:1 -ktr2xi18n:1 \ 29 | "$@" 30 | } 31 | export -f xgettext_wrapper 32 | 33 | podir=$BASEDIR/po 34 | files=`find $SRCDIR -name Messages.sh` 35 | dirs=`for i in $files; do echo \`dirname $i\`; done | sort -u` 36 | 37 | for subdir in $dirs; do 38 | cd $subdir 39 | 40 | if test -f Messages.sh; then 41 | podir=$podir srcdir=. XGETTEXT="xgettext_wrapper" bash Messages.sh 42 | fi 43 | done 44 | 45 | pofiles=`find $podir -name \*.po` 46 | for po in $pofiles; do 47 | msgmerge -o $po.new $po $podir/plasma_wallpaper_com.github.zzag.dynamic.pot 48 | mv $po.new $po 49 | done 50 | -------------------------------------------------------------------------------- /src/translations/po/de.po: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # 3 | # SPDX-FileCopyrightText: 2021 Lorenz Hoffmann 4 | # 5 | #, fuzzy 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: plasma5-wallpapers-dynamic\n" 9 | "Report-Msgid-Bugs-To: https://github.com/zzag/plasma5-wallpapers-dynamic/" 10 | "issues\n" 11 | "POT-Creation-Date: 2022-04-09 19:59+0300\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 20 | "X-Language: de_DE\n" 21 | "X-Source-Language: en_GB\n" 22 | 23 | #: tools/builder/main.cpp:26 24 | #, fuzzy, kde-format 25 | msgid "Write output to " 26 | msgstr "Speichere nach " 27 | 28 | #: tools/builder/main.cpp:30 29 | #, kde-format 30 | msgid "Maximum number of threads that can be used when encoding a wallpaper" 31 | msgstr "" 32 | 33 | #: tools/builder/main.cpp:36 34 | #, fuzzy, kde-format 35 | msgid "Manifest file to use" 36 | msgstr "zu verwendende Beschreibungsdatei" 37 | 38 | #: package/contents/ui/config.qml:36 39 | #, kde-format 40 | msgid "Fill Mode:" 41 | msgstr "Füllmodus:" 42 | 43 | #: package/contents/ui/config.qml:40 44 | #, kde-format 45 | msgid "Scaled and Cropped" 46 | msgstr "Skaliert und zugeschnitten" 47 | 48 | #: package/contents/ui/config.qml:44 49 | #, kde-format 50 | msgid "Scaled" 51 | msgstr "Skaliert" 52 | 53 | #: package/contents/ui/config.qml:48 54 | #, kde-format 55 | msgid "Scaled, Keep Proportions" 56 | msgstr "Skaliert, Seitenverhältnis beibehalten" 57 | 58 | #: package/contents/ui/config.qml:52 59 | #, kde-format 60 | msgid "Centered" 61 | msgstr "Zentriert" 62 | 63 | #: package/contents/ui/config.qml:56 64 | #, kde-format 65 | msgid "Tiled" 66 | msgstr "Gekachelt" 67 | 68 | #: package/contents/ui/config.qml:75 69 | #, kde-format 70 | msgid "Automatically detect location" 71 | msgstr "Position automatisch ermitteln" 72 | 73 | #: package/contents/ui/config.qml:79 package/contents/ui/config.qml:100 74 | #, kde-format 75 | msgid "Latitude:" 76 | msgstr "Breitengrad:" 77 | 78 | #: package/contents/ui/config.qml:89 package/contents/ui/config.qml:109 79 | #, kde-format 80 | msgid "Longitude:" 81 | msgstr "Längengrad:" 82 | 83 | #: package/contents/ui/config.qml:118 84 | #, kde-format 85 | msgid "Update Every:" 86 | msgstr "Aktualisiere jede:" 87 | 88 | #: package/contents/ui/config.qml:136 89 | #, kde-format 90 | msgid " minute" 91 | msgid_plural " minutes" 92 | msgstr[0] " Minute" 93 | msgstr[1] " Minuten" 94 | 95 | #: package/contents/ui/config.qml:165 96 | #, kde-format 97 | msgctxt " by ()" 98 | msgid "By %1 (%2)" 99 | msgstr "von %1 (%2)" 100 | 101 | #: package/contents/ui/config.qml:167 102 | #, kde-format 103 | msgctxt " ()" 104 | msgid "%1 (%2)" 105 | msgstr "%1 (%2)" 106 | 107 | # Assuming this is used to open the folder where the wallpaper is located 108 | #: package/contents/ui/config.qml:173 109 | #, fuzzy, kde-format 110 | msgid "Open Containing Folder" 111 | msgstr "Speicherort öffnen" 112 | 113 | #: package/contents/ui/config.qml:178 114 | #, kde-format 115 | msgid "Restore Wallpaper" 116 | msgstr "Hintergrundbild wiederherstellen" 117 | 118 | #: package/contents/ui/config.qml:184 119 | #, kde-format 120 | msgid "Remove Wallpaper" 121 | msgstr "Hintergrundbild löschen" 122 | 123 | #: package/contents/ui/config.qml:215 124 | #, kde-format 125 | msgid "Open Wallpaper" 126 | msgstr "Hintergrundbild öffnen" 127 | 128 | #: package/contents/ui/config.qml:217 129 | #, fuzzy, kde-format 130 | msgid "AVIF Image Files (*.avif)" 131 | msgstr "HEIF Bild Dateien (*.heic *.heif)" 132 | 133 | #: package/contents/ui/config.qml:234 134 | #, kde-format 135 | msgid "Add Wallpaper..." 136 | msgstr "Hintergrundbild hinzufügen..." 137 | 138 | #: plugins/kpackage-integration/dynamicwallpaperpackagestructure.cpp:22 139 | #, kde-format 140 | msgid "Dynamic wallpaper files" 141 | msgstr "Dynamische Hintergrundbilder" 142 | 143 | #: plugins/kpackage-integration/dynamicwallpaperpackagestructure.cpp:38 144 | #, kde-format 145 | msgid "Dynamic wallpaper file" 146 | msgstr "Dynamischer Hintergrund" 147 | 148 | #: declarative/dynamicwallpapermodel.cpp:509 149 | #: declarative/dynamicwallpaperhandler.cpp:210 150 | #, kde-format 151 | msgid "%1 is not a dynamic wallpaper" 152 | msgstr "%1 ist kein dynamischer Hintergrund" 153 | 154 | #: declarative/dynamicwallpaperpreviewjob.cpp:140 155 | #, fuzzy, kde-format 156 | msgid "Not a dynamic wallpaper" 157 | msgstr "%1 ist kein dynamischer Hintergrund" 158 | -------------------------------------------------------------------------------- /src/translations/po/it.po: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # 3 | # SPDX-FileCopyrightText: 2019 Federico Robin 4 | msgid "" 5 | msgstr "" 6 | "Project-Id-Version: plasma5-wallpapers-dynamic\n" 7 | "Report-Msgid-Bugs-To: https://github.com/zzag/plasma5-wallpapers-dynamic/" 8 | "issues\n" 9 | "POT-Creation-Date: 2022-04-09 19:59+0300\n" 10 | "PO-Revision-Date: 2019-10-26 15:00+0200\n" 11 | "Last-Translator: Federico Robin \n" 12 | "Language: it\n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 17 | 18 | #: tools/builder/main.cpp:26 19 | #, kde-format 20 | msgid "Write output to " 21 | msgstr "" 22 | 23 | #: tools/builder/main.cpp:30 24 | #, kde-format 25 | msgid "Maximum number of threads that can be used when encoding a wallpaper" 26 | msgstr "" 27 | 28 | #: tools/builder/main.cpp:36 29 | #, kde-format 30 | msgid "Manifest file to use" 31 | msgstr "" 32 | 33 | #: package/contents/ui/config.qml:36 34 | #, kde-format 35 | msgid "Fill Mode:" 36 | msgstr "Modalità di riempimento" 37 | 38 | #: package/contents/ui/config.qml:40 39 | #, kde-format 40 | msgid "Scaled and Cropped" 41 | msgstr "Scalato e Tagliato" 42 | 43 | #: package/contents/ui/config.qml:44 44 | #, kde-format 45 | msgid "Scaled" 46 | msgstr "Scalato" 47 | 48 | #: package/contents/ui/config.qml:48 49 | #, kde-format 50 | msgid "Scaled, Keep Proportions" 51 | msgstr "Scalato, Mantieni Proporzioni" 52 | 53 | #: package/contents/ui/config.qml:52 54 | #, kde-format 55 | msgid "Centered" 56 | msgstr "Centrato" 57 | 58 | #: package/contents/ui/config.qml:56 59 | #, kde-format 60 | msgid "Tiled" 61 | msgstr "Affiancato" 62 | 63 | #: package/contents/ui/config.qml:75 64 | #, kde-format 65 | msgid "Automatically detect location" 66 | msgstr "" 67 | 68 | #: package/contents/ui/config.qml:79 package/contents/ui/config.qml:100 69 | #, kde-format 70 | msgid "Latitude:" 71 | msgstr "Latitudine:" 72 | 73 | #: package/contents/ui/config.qml:89 package/contents/ui/config.qml:109 74 | #, kde-format 75 | msgid "Longitude:" 76 | msgstr "Longitudine:" 77 | 78 | #: package/contents/ui/config.qml:118 79 | #, kde-format 80 | msgid "Update Every:" 81 | msgstr "Aggiorna Ogni:" 82 | 83 | #: package/contents/ui/config.qml:136 84 | #, fuzzy, kde-format 85 | msgid " minute" 86 | msgid_plural " minutes" 87 | msgstr[0] " minuto" 88 | msgstr[1] " minuti" 89 | 90 | #: package/contents/ui/config.qml:165 91 | #, kde-format 92 | msgctxt " by ()" 93 | msgid "By %1 (%2)" 94 | msgstr "Da %1 (%2)" 95 | 96 | #: package/contents/ui/config.qml:167 97 | #, kde-format 98 | msgctxt " ()" 99 | msgid "%1 (%2)" 100 | msgstr "%1 (%2)" 101 | 102 | #: package/contents/ui/config.qml:173 103 | #, kde-format 104 | msgid "Open Containing Folder" 105 | msgstr "Mostra nel Gestore File" 106 | 107 | #: package/contents/ui/config.qml:178 108 | #, kde-format 109 | msgid "Restore Wallpaper" 110 | msgstr "Ripristina Wallpaper" 111 | 112 | #: package/contents/ui/config.qml:184 113 | #, kde-format 114 | msgid "Remove Wallpaper" 115 | msgstr "Rimuovi Wallpaper" 116 | 117 | #: package/contents/ui/config.qml:215 118 | #, kde-format 119 | msgid "Open Wallpaper" 120 | msgstr "Apri Wallpaper" 121 | 122 | #: package/contents/ui/config.qml:217 123 | #, kde-format 124 | msgid "AVIF Image Files (*.avif)" 125 | msgstr "" 126 | 127 | #: package/contents/ui/config.qml:234 128 | #, kde-format 129 | msgid "Add Wallpaper..." 130 | msgstr "Aggiungi Wallpaper..." 131 | 132 | #: plugins/kpackage-integration/dynamicwallpaperpackagestructure.cpp:22 133 | #, kde-format 134 | msgid "Dynamic wallpaper files" 135 | msgstr "" 136 | 137 | #: plugins/kpackage-integration/dynamicwallpaperpackagestructure.cpp:38 138 | #, kde-format 139 | msgid "Dynamic wallpaper file" 140 | msgstr "" 141 | 142 | #: declarative/dynamicwallpapermodel.cpp:509 143 | #: declarative/dynamicwallpaperhandler.cpp:210 144 | #, fuzzy, kde-format 145 | msgid "%1 is not a dynamic wallpaper" 146 | msgstr "Non è stato possibile inizializzare il wallpaper dinamico: %1" 147 | 148 | #: declarative/dynamicwallpaperpreviewjob.cpp:140 149 | #, fuzzy, kde-format 150 | msgid "Not a dynamic wallpaper" 151 | msgstr "Non è stato possibile inizializzare il wallpaper dinamico: %1" 152 | -------------------------------------------------------------------------------- /src/translations/po/pl.po: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # 3 | # SPDX-FileCopyrightText: 2022 Piotr Lange 4 | # 5 | #, fuzzy 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: plasma5-wallpapers-dynamic\n" 9 | "Report-Msgid-Bugs-To: https://github.com/zzag/plasma5-wallpapers-dynamic/" 10 | "issues\n" 11 | "POT-Creation-Date: 2022-04-15 07:20+0200\n" 12 | "PO-Revision-Date: 2022-04-15 07:27+0200\n" 13 | "Last-Translator: Piotr Lange \n" 14 | "Language: pl\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n" 19 | "%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n" 20 | "%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" 21 | "X-Language: pl_PL\n" 22 | "X-Source-Language: en_GB\n" 23 | 24 | #: declarative/dynamicwallpaperhandler.cpp:220 25 | #: declarative/dynamicwallpapermodel.cpp:509 26 | #, kde-format 27 | msgid "%1 is not a dynamic wallpaper" 28 | msgstr "%1 nie jest tapetą dynamiczną" 29 | 30 | #: declarative/dynamicwallpaperpreviewjob.cpp:140 31 | #, kde-format 32 | msgid "Not a dynamic wallpaper" 33 | msgstr "Nie jest tapetą dynamiczną" 34 | 35 | #: package/contents/ui/config.qml:36 36 | #, kde-format 37 | msgid "Fill Mode:" 38 | msgstr "Tryb wypełnienia:" 39 | 40 | #: package/contents/ui/config.qml:40 41 | #, kde-format 42 | msgid "Scaled and Cropped" 43 | msgstr "Przeskalowane i ucięte" 44 | 45 | #: package/contents/ui/config.qml:44 46 | #, kde-format 47 | msgid "Scaled" 48 | msgstr "Przeskalowane" 49 | 50 | #: package/contents/ui/config.qml:48 51 | #, kde-format 52 | msgid "Scaled, Keep Proportions" 53 | msgstr "Przeskalowane, z zachowanymi proporcjami" 54 | 55 | #: package/contents/ui/config.qml:52 56 | #, kde-format 57 | msgid "Centered" 58 | msgstr "Wyśrodkowane" 59 | 60 | #: package/contents/ui/config.qml:56 61 | #, kde-format 62 | msgid "Tiled" 63 | msgstr "Wykafelkowany" 64 | 65 | #: package/contents/ui/config.qml:75 66 | #, kde-format 67 | msgid "Automatically detect location" 68 | msgstr "Automatycznie wykryj lokalizację" 69 | 70 | #: package/contents/ui/config.qml:79 package/contents/ui/config.qml:100 71 | #, kde-format 72 | msgid "Latitude:" 73 | msgstr "Szerokość geograficzna:" 74 | 75 | #: package/contents/ui/config.qml:89 package/contents/ui/config.qml:109 76 | #, kde-format 77 | msgid "Longitude:" 78 | msgstr "Długość geograficzna:" 79 | 80 | #: package/contents/ui/config.qml:118 81 | #, kde-format 82 | msgid "Update Every:" 83 | msgstr "Aktualizuj co:" 84 | 85 | #: package/contents/ui/config.qml:136 86 | #, kde-format 87 | msgid " minute" 88 | msgid_plural " minutes" 89 | msgstr[0] " minuta" 90 | msgstr[1] " minuty" 91 | msgstr[2] " minut" 92 | msgstr[3] " minut" 93 | 94 | #: package/contents/ui/config.qml:165 95 | #, kde-format 96 | msgctxt " by ()" 97 | msgid "By %1 (%2)" 98 | msgstr "Autor %1 (%2)" 99 | 100 | #: package/contents/ui/config.qml:167 101 | #, kde-format 102 | msgctxt " ()" 103 | msgid "%1 (%2)" 104 | msgstr "%1 (%2)" 105 | 106 | #: package/contents/ui/config.qml:173 107 | #, kde-format 108 | msgid "Open Containing Folder" 109 | msgstr "Otwórz folder" 110 | 111 | #: package/contents/ui/config.qml:178 112 | #, kde-format 113 | msgid "Restore Wallpaper" 114 | msgstr "Przywróć tapetę" 115 | 116 | #: package/contents/ui/config.qml:184 117 | #, kde-format 118 | msgid "Remove Wallpaper" 119 | msgstr "Usuń tapetę" 120 | 121 | #: package/contents/ui/config.qml:215 122 | #, kde-format 123 | msgid "Open Wallpaper" 124 | msgstr "Otwórz tapetę" 125 | 126 | #: package/contents/ui/config.qml:217 127 | #, kde-format 128 | msgid "AVIF Image Files (*.avif)" 129 | msgstr "Pliki grafiki AVIF (*.avif)" 130 | 131 | #: package/contents/ui/config.qml:234 132 | #, kde-format 133 | msgid "Add Wallpaper..." 134 | msgstr "Dodaj tapetę..." 135 | 136 | #: tools/builder/main.cpp:26 137 | #, kde-format 138 | msgid "Write output to " 139 | msgstr "Zapisz do " 140 | 141 | #: tools/builder/main.cpp:30 142 | #, kde-format 143 | msgid "Maximum number of threads that can be used when encoding a wallpaper" 144 | msgstr "Maksymalna liczba wątków, która może zostać użyta do enkodowania tapety" 145 | 146 | #: tools/builder/main.cpp:36 147 | #, kde-format 148 | msgid "Manifest file to use" 149 | msgstr "Plik z opisem do użycia" 150 | 151 | #: plugins/kpackage-integration/dynamicwallpaperpackagestructure.cpp:22 152 | #, kde-format 153 | msgid "Dynamic wallpaper files" 154 | msgstr "Pliki tapety dynamicznej" 155 | 156 | #: plugins/kpackage-integration/dynamicwallpaperpackagestructure.cpp:38 157 | #, kde-format 158 | msgid "Dynamic wallpaper file" 159 | msgstr "Plik tapety dynamicznej" 160 | -------------------------------------------------------------------------------- /src/translations/po/pt.po: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # 3 | # SPDX-FileCopyrightText: 2019 DMMLeal 4 | msgid "" 5 | msgstr "" 6 | "Project-Id-Version: plasma5-wallpapers-dynamic\n" 7 | "Report-Msgid-Bugs-To: https://github.com/zzag/plasma5-wallpapers-dynamic/" 8 | "issues\n" 9 | "POT-Creation-Date: 2022-04-09 19:59+0300\n" 10 | "PO-Revision-Date: 2019-10-26 15:00+0000\n" 11 | "Last-Translator: DMMleal \n" 12 | "Language: pt\n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 17 | 18 | #: tools/builder/main.cpp:26 19 | #, kde-format 20 | msgid "Write output to " 21 | msgstr "" 22 | 23 | #: tools/builder/main.cpp:30 24 | #, kde-format 25 | msgid "Maximum number of threads that can be used when encoding a wallpaper" 26 | msgstr "" 27 | 28 | #: tools/builder/main.cpp:36 29 | #, kde-format 30 | msgid "Manifest file to use" 31 | msgstr "" 32 | 33 | #: package/contents/ui/config.qml:36 34 | #, kde-format 35 | msgid "Fill Mode:" 36 | msgstr "Modo de preenchimento:" 37 | 38 | #: package/contents/ui/config.qml:40 39 | #, kde-format 40 | msgid "Scaled and Cropped" 41 | msgstr "Escalado e Recortado" 42 | 43 | #: package/contents/ui/config.qml:44 44 | #, kde-format 45 | msgid "Scaled" 46 | msgstr "Escalado" 47 | 48 | #: package/contents/ui/config.qml:48 49 | #, kde-format 50 | msgid "Scaled, Keep Proportions" 51 | msgstr "Escalado, proporcionalmente" 52 | 53 | #: package/contents/ui/config.qml:52 54 | #, kde-format 55 | msgid "Centered" 56 | msgstr "Centrado" 57 | 58 | #: package/contents/ui/config.qml:56 59 | #, kde-format 60 | msgid "Tiled" 61 | msgstr "Mosaico" 62 | 63 | #: package/contents/ui/config.qml:75 64 | #, kde-format 65 | msgid "Automatically detect location" 66 | msgstr "Detetar localização automaticamente" 67 | 68 | #: package/contents/ui/config.qml:79 package/contents/ui/config.qml:100 69 | #, kde-format 70 | msgid "Latitude:" 71 | msgstr "Latitude:" 72 | 73 | #: package/contents/ui/config.qml:89 package/contents/ui/config.qml:109 74 | #, kde-format 75 | msgid "Longitude:" 76 | msgstr "Longitude:" 77 | 78 | #: package/contents/ui/config.qml:118 79 | #, kde-format 80 | msgid "Update Every:" 81 | msgstr "Atualizar a cada:" 82 | 83 | #: package/contents/ui/config.qml:136 84 | #, fuzzy, kde-format 85 | msgid " minute" 86 | msgid_plural " minutes" 87 | msgstr[0] " minuto" 88 | msgstr[1] " minutos" 89 | 90 | #: package/contents/ui/config.qml:165 91 | #, kde-format 92 | msgctxt " by ()" 93 | msgid "By %1 (%2)" 94 | msgstr "" 95 | 96 | #: package/contents/ui/config.qml:167 97 | #, kde-format 98 | msgctxt " ()" 99 | msgid "%1 (%2)" 100 | msgstr "%1 (%2)" 101 | 102 | #: package/contents/ui/config.qml:173 103 | #, kde-format 104 | msgid "Open Containing Folder" 105 | msgstr "" 106 | 107 | #: package/contents/ui/config.qml:178 108 | #, kde-format 109 | msgid "Restore Wallpaper" 110 | msgstr "Restaurar papel de parede" 111 | 112 | #: package/contents/ui/config.qml:184 113 | #, kde-format 114 | msgid "Remove Wallpaper" 115 | msgstr "Remover papel de parede" 116 | 117 | #: package/contents/ui/config.qml:215 118 | #, kde-format 119 | msgid "Open Wallpaper" 120 | msgstr "Abrir papel de parede" 121 | 122 | #: package/contents/ui/config.qml:217 123 | #, kde-format 124 | msgid "AVIF Image Files (*.avif)" 125 | msgstr "" 126 | 127 | #: package/contents/ui/config.qml:234 128 | #, kde-format 129 | msgid "Add Wallpaper..." 130 | msgstr "Adicionar papel de parede ..." 131 | 132 | #: plugins/kpackage-integration/dynamicwallpaperpackagestructure.cpp:22 133 | #, kde-format 134 | msgid "Dynamic wallpaper files" 135 | msgstr "Arquivos de papel de parede dinâmico" 136 | 137 | #: plugins/kpackage-integration/dynamicwallpaperpackagestructure.cpp:38 138 | #, kde-format 139 | msgid "Dynamic wallpaper file" 140 | msgstr "Arquivo de papel de parede dinâmico" 141 | 142 | #: declarative/dynamicwallpapermodel.cpp:509 143 | #: declarative/dynamicwallpaperhandler.cpp:210 144 | #, fuzzy, kde-format 145 | msgid "%1 is not a dynamic wallpaper" 146 | msgstr "%1 não é um papel de parede dinâmico" 147 | 148 | #: declarative/dynamicwallpaperpreviewjob.cpp:140 149 | #, fuzzy, kde-format 150 | msgid "Not a dynamic wallpaper" 151 | msgstr "%1 não é um papel de parede dinâmico" 152 | -------------------------------------------------------------------------------- /src/translations/po/ru.po: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # 3 | # SPDX-FileCopyrightText: 2019, 2020 Vlad Zahorodnii 4 | msgid "" 5 | msgstr "" 6 | "Project-Id-Version: plasma5-wallpapers-dynamic\n" 7 | "Report-Msgid-Bugs-To: https://github.com/zzag/plasma5-wallpapers-dynamic/" 8 | "issues\n" 9 | "POT-Creation-Date: 2022-04-09 19:59+0300\n" 10 | "PO-Revision-Date: 2020-02-16 19:45+0200\n" 11 | "Last-Translator: Vlad Zahorodnii \n" 12 | "Language-Team: Vlad Zahorodnii \n" 13 | "Language: ru\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n" 18 | "%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" 19 | "X-Generator: Lokalize 19.12.2\n" 20 | 21 | #: tools/builder/main.cpp:26 22 | #, kde-format 23 | msgid "Write output to " 24 | msgstr "" 25 | 26 | #: tools/builder/main.cpp:30 27 | #, kde-format 28 | msgid "Maximum number of threads that can be used when encoding a wallpaper" 29 | msgstr "" 30 | 31 | #: tools/builder/main.cpp:36 32 | #, kde-format 33 | msgid "Manifest file to use" 34 | msgstr "" 35 | 36 | #: package/contents/ui/config.qml:36 37 | #, kde-format 38 | msgid "Fill Mode:" 39 | msgstr "Расположение:" 40 | 41 | #: package/contents/ui/config.qml:40 42 | #, kde-format 43 | msgid "Scaled and Cropped" 44 | msgstr "Масштабирование с кадрированием" 45 | 46 | #: package/contents/ui/config.qml:44 47 | #, kde-format 48 | msgid "Scaled" 49 | msgstr "На весь рабочий стол" 50 | 51 | #: package/contents/ui/config.qml:48 52 | #, kde-format 53 | msgid "Scaled, Keep Proportions" 54 | msgstr "По центру пропорционально" 55 | 56 | #: package/contents/ui/config.qml:52 57 | #, kde-format 58 | msgid "Centered" 59 | msgstr "По центру" 60 | 61 | #: package/contents/ui/config.qml:56 62 | #, kde-format 63 | msgid "Tiled" 64 | msgstr "Черепицей" 65 | 66 | #: package/contents/ui/config.qml:75 67 | #, kde-format 68 | msgid "Automatically detect location" 69 | msgstr "Автоматически определить местоположение" 70 | 71 | #: package/contents/ui/config.qml:79 package/contents/ui/config.qml:100 72 | #, kde-format 73 | msgid "Latitude:" 74 | msgstr "Широта:" 75 | 76 | #: package/contents/ui/config.qml:89 package/contents/ui/config.qml:109 77 | #, kde-format 78 | msgid "Longitude:" 79 | msgstr "Долгота:" 80 | 81 | #: package/contents/ui/config.qml:118 82 | #, kde-format 83 | msgid "Update Every:" 84 | msgstr "Обновлять каждые:" 85 | 86 | #: package/contents/ui/config.qml:136 87 | #, kde-format 88 | msgid " minute" 89 | msgid_plural " minutes" 90 | msgstr[0] " минуту" 91 | msgstr[1] " минуты" 92 | msgstr[2] " минут" 93 | msgstr[3] " минуту" 94 | 95 | #: package/contents/ui/config.qml:165 96 | #, kde-format 97 | msgctxt " by ()" 98 | msgid "By %1 (%2)" 99 | msgstr "От %1 (%2)" 100 | 101 | #: package/contents/ui/config.qml:167 102 | #, kde-format 103 | msgctxt " ()" 104 | msgid "%1 (%2)" 105 | msgstr "%1 (%2)" 106 | 107 | #: package/contents/ui/config.qml:173 108 | #, kde-format 109 | msgid "Open Containing Folder" 110 | msgstr "Открыть содержащую объект папку" 111 | 112 | #: package/contents/ui/config.qml:178 113 | #, kde-format 114 | msgid "Restore Wallpaper" 115 | msgstr "Вернуть обои в этот список" 116 | 117 | #: package/contents/ui/config.qml:184 118 | #, kde-format 119 | msgid "Remove Wallpaper" 120 | msgstr "Удалить обои" 121 | 122 | #: package/contents/ui/config.qml:215 123 | #, kde-format 124 | msgid "Open Wallpaper" 125 | msgstr "Открытие обоев" 126 | 127 | #: package/contents/ui/config.qml:217 128 | #, kde-format 129 | msgid "AVIF Image Files (*.avif)" 130 | msgstr "" 131 | 132 | #: package/contents/ui/config.qml:234 133 | #, kde-format 134 | msgid "Add Wallpaper..." 135 | msgstr "Добавить обои..." 136 | 137 | #: plugins/kpackage-integration/dynamicwallpaperpackagestructure.cpp:22 138 | #, kde-format 139 | msgid "Dynamic wallpaper files" 140 | msgstr "" 141 | 142 | #: plugins/kpackage-integration/dynamicwallpaperpackagestructure.cpp:38 143 | #, kde-format 144 | msgid "Dynamic wallpaper file" 145 | msgstr "" 146 | 147 | #: declarative/dynamicwallpapermodel.cpp:509 148 | #: declarative/dynamicwallpaperhandler.cpp:210 149 | #, fuzzy, kde-format 150 | msgid "%1 is not a dynamic wallpaper" 151 | msgstr "Невозможно инициализировать обои" 152 | 153 | #: declarative/dynamicwallpaperpreviewjob.cpp:140 154 | #, fuzzy, kde-format 155 | msgid "Not a dynamic wallpaper" 156 | msgstr "Невозможно инициализировать обои" 157 | -------------------------------------------------------------------------------- /src/translations/po/uk.po: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # 3 | # SPDX-FileCopyrightText: 2019, 2020 Vlad Zahorodnii 4 | msgid "" 5 | msgstr "" 6 | "Project-Id-Version: plasma5-wallpapers-dynamic\n" 7 | "Report-Msgid-Bugs-To: https://github.com/zzag/plasma5-wallpapers-dynamic/issue" 8 | "s\n" 9 | "POT-Creation-Date: 2022-04-09 19:59+0300\n" 10 | "PO-Revision-Date: 2022-04-17 20:28+0300\n" 11 | "Last-Translator: Vlad Zahorodnii \n" 12 | "Language-Team: Vlad Zahorodnii \n" 13 | "Language: uk\n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | "Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n" 18 | "%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" 19 | "X-Generator: Lokalize 22.07.70\n" 20 | 21 | #: tools/builder/main.cpp:26 22 | #, kde-format 23 | msgid "Write output to " 24 | msgstr "Записати результат в <файл>" 25 | 26 | #: tools/builder/main.cpp:30 27 | #, kde-format 28 | msgid "Maximum number of threads that can be used when encoding a wallpaper" 29 | msgstr "Максимальна кількість потік процесора, які можуть бути використані під час кодування шпалер" 30 | 31 | #: tools/builder/main.cpp:36 32 | #, kde-format 33 | msgid "Manifest file to use" 34 | msgstr "Маніфест файл" 35 | 36 | #: package/contents/ui/config.qml:36 37 | #, kde-format 38 | msgid "Fill Mode:" 39 | msgstr "Розташування:" 40 | 41 | #: package/contents/ui/config.qml:40 42 | #, kde-format 43 | msgid "Scaled and Cropped" 44 | msgstr "Масштабоване та обрізане" 45 | 46 | #: package/contents/ui/config.qml:44 47 | #, kde-format 48 | msgid "Scaled" 49 | msgstr "Масштабоване" 50 | 51 | #: package/contents/ui/config.qml:48 52 | #, kde-format 53 | msgid "Scaled, Keep Proportions" 54 | msgstr "Масштабоване, зі збереженням пропорцій" 55 | 56 | #: package/contents/ui/config.qml:52 57 | #, kde-format 58 | msgid "Centered" 59 | msgstr "За центром" 60 | 61 | #: package/contents/ui/config.qml:56 62 | #, kde-format 63 | msgid "Tiled" 64 | msgstr "Плиткою" 65 | 66 | #: package/contents/ui/config.qml:75 67 | #, kde-format 68 | msgid "Automatically detect location" 69 | msgstr "Визначити місцезнаходження автоматично" 70 | 71 | #: package/contents/ui/config.qml:79 package/contents/ui/config.qml:100 72 | #, kde-format 73 | msgid "Latitude:" 74 | msgstr "Широта:" 75 | 76 | #: package/contents/ui/config.qml:89 package/contents/ui/config.qml:109 77 | #, kde-format 78 | msgid "Longitude:" 79 | msgstr "Довгота:" 80 | 81 | #: package/contents/ui/config.qml:118 82 | #, kde-format 83 | msgid "Update Every:" 84 | msgstr "Обновлювати кожні:" 85 | 86 | #: package/contents/ui/config.qml:136 87 | #, kde-format 88 | msgid " minute" 89 | msgid_plural " minutes" 90 | msgstr[0] " хвилину" 91 | msgstr[1] " хвилини" 92 | msgstr[2] " хвилин" 93 | msgstr[3] " хвилину" 94 | 95 | #: package/contents/ui/config.qml:165 96 | #, kde-format 97 | msgctxt " by ()" 98 | msgid "By %1 (%2)" 99 | msgstr "Автор - %1 (%2)" 100 | 101 | #: package/contents/ui/config.qml:167 102 | #, kde-format 103 | msgctxt " ()" 104 | msgid "%1 (%2)" 105 | msgstr "%1 (%2)" 106 | 107 | #: package/contents/ui/config.qml:173 108 | #, kde-format 109 | msgid "Open Containing Folder" 110 | msgstr "Відкрити теку об'єкта" 111 | 112 | #: package/contents/ui/config.qml:178 113 | #, kde-format 114 | msgid "Restore Wallpaper" 115 | msgstr "Відновити початкові шпалери" 116 | 117 | #: package/contents/ui/config.qml:184 118 | #, kde-format 119 | msgid "Remove Wallpaper" 120 | msgstr "Вилучити шпалери" 121 | 122 | #: package/contents/ui/config.qml:215 123 | #, kde-format 124 | msgid "Open Wallpaper" 125 | msgstr "Відкрити шпалери" 126 | 127 | #: package/contents/ui/config.qml:217 128 | #, kde-format 129 | msgid "AVIF Image Files (*.avif)" 130 | msgstr "AVIF файли зображень (*.avif)" 131 | 132 | #: package/contents/ui/config.qml:234 133 | #, kde-format 134 | msgid "Add Wallpaper..." 135 | msgstr "Додати шпалери..." 136 | 137 | #: plugins/kpackage-integration/dynamicwallpaperpackagestructure.cpp:22 138 | #, kde-format 139 | msgid "Dynamic wallpaper files" 140 | msgstr "Динамічні шпалери" 141 | 142 | #: plugins/kpackage-integration/dynamicwallpaperpackagestructure.cpp:38 143 | #, kde-format 144 | msgid "Dynamic wallpaper file" 145 | msgstr "Динамічні шпалери" 146 | 147 | #: declarative/dynamicwallpapermodel.cpp:509 148 | #: declarative/dynamicwallpaperhandler.cpp:210 149 | #, kde-format 150 | msgid "%1 is not a dynamic wallpaper" 151 | msgstr "%1 не є динамічними шпалерами" 152 | 153 | #: declarative/dynamicwallpaperpreviewjob.cpp:140 154 | #, kde-format 155 | msgid "Not a dynamic wallpaper" 156 | msgstr "Не динамічні шпалери" 157 | -------------------------------------------------------------------------------- /src/translations/po/zh.po: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-or-later 2 | # 3 | # SPDX-FileCopyrightText: 2021 wbs 4 | msgid "" 5 | msgstr "" 6 | "Project-Id-Version: plasma5-wallpapers-dynamic\n" 7 | "Report-Msgid-Bugs-To: https://github.com/zzag/plasma5-wallpapers-dynamic/" 8 | "issues\n" 9 | "POT-Creation-Date: 2022-04-09 19:59+0300\n" 10 | "PO-Revision-Date: 2021-01-28 18:34+0800\n" 11 | "Last-Translator: wbs \n" 12 | "Language: zh\n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | "Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n" 17 | "%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" 18 | "X-Generator: Lokalize 20.12.1\n" 19 | 20 | #: tools/builder/main.cpp:26 21 | #, kde-format 22 | msgid "Write output to " 23 | msgstr "输出文件为 " 24 | 25 | #: tools/builder/main.cpp:30 26 | #, kde-format 27 | msgid "Maximum number of threads that can be used when encoding a wallpaper" 28 | msgstr "" 29 | 30 | #: tools/builder/main.cpp:36 31 | #, fuzzy, kde-format 32 | msgid "Manifest file to use" 33 | msgstr "要构建的描述文件" 34 | 35 | #: package/contents/ui/config.qml:36 36 | #, kde-format 37 | msgid "Fill Mode:" 38 | msgstr "填充模式:" 39 | 40 | #: package/contents/ui/config.qml:40 41 | #, kde-format 42 | msgid "Scaled and Cropped" 43 | msgstr "缩放并裁剪" 44 | 45 | #: package/contents/ui/config.qml:44 46 | #, kde-format 47 | msgid "Scaled" 48 | msgstr "缩放" 49 | 50 | #: package/contents/ui/config.qml:48 51 | #, kde-format 52 | msgid "Scaled, Keep Proportions" 53 | msgstr "缩放, 保持宽高比" 54 | 55 | #: package/contents/ui/config.qml:52 56 | #, kde-format 57 | msgid "Centered" 58 | msgstr "中央" 59 | 60 | #: package/contents/ui/config.qml:56 61 | #, kde-format 62 | msgid "Tiled" 63 | msgstr "平铺" 64 | 65 | #: package/contents/ui/config.qml:75 66 | #, kde-format 67 | msgid "Automatically detect location" 68 | msgstr "自动设置位置" 69 | 70 | #: package/contents/ui/config.qml:79 package/contents/ui/config.qml:100 71 | #, kde-format 72 | msgid "Latitude:" 73 | msgstr "纬度:" 74 | 75 | #: package/contents/ui/config.qml:89 package/contents/ui/config.qml:109 76 | #, kde-format 77 | msgid "Longitude:" 78 | msgstr "经度:" 79 | 80 | #: package/contents/ui/config.qml:118 81 | #, kde-format 82 | msgid "Update Every:" 83 | msgstr "壁纸更换频率:" 84 | 85 | #: package/contents/ui/config.qml:136 86 | #, kde-format 87 | msgid " minute" 88 | msgid_plural " minutes" 89 | msgstr[0] "分钟" 90 | msgstr[1] "分钟" 91 | msgstr[2] "分钟" 92 | msgstr[3] "分钟" 93 | 94 | #: package/contents/ui/config.qml:165 95 | #, kde-format 96 | msgctxt " by ()" 97 | msgid "By %1 (%2)" 98 | msgstr "作者 - %1 (%2)" 99 | 100 | #: package/contents/ui/config.qml:167 101 | #, kde-format 102 | msgctxt " ()" 103 | msgid "%1 (%2)" 104 | msgstr "%1 (%2)" 105 | 106 | #: package/contents/ui/config.qml:173 107 | #, kde-format 108 | msgid "Open Containing Folder" 109 | msgstr "打开所在文件夹" 110 | 111 | #: package/contents/ui/config.qml:178 112 | #, kde-format 113 | msgid "Restore Wallpaper" 114 | msgstr "恢复壁纸" 115 | 116 | #: package/contents/ui/config.qml:184 117 | #, kde-format 118 | msgid "Remove Wallpaper" 119 | msgstr "移除壁纸" 120 | 121 | #: package/contents/ui/config.qml:215 122 | #, kde-format 123 | msgid "Open Wallpaper" 124 | msgstr "打开壁纸" 125 | 126 | #: package/contents/ui/config.qml:217 127 | #, fuzzy, kde-format 128 | msgid "AVIF Image Files (*.avif)" 129 | msgstr "HEIF 图片文件 (*.heic *.heif)" 130 | 131 | #: package/contents/ui/config.qml:234 132 | #, kde-format 133 | msgid "Add Wallpaper..." 134 | msgstr "添加壁纸..." 135 | 136 | #: plugins/kpackage-integration/dynamicwallpaperpackagestructure.cpp:22 137 | #, kde-format 138 | msgid "Dynamic wallpaper files" 139 | msgstr "动态壁纸文件" 140 | 141 | #: plugins/kpackage-integration/dynamicwallpaperpackagestructure.cpp:38 142 | #, kde-format 143 | msgid "Dynamic wallpaper file" 144 | msgstr "动态壁纸文件" 145 | 146 | #: declarative/dynamicwallpapermodel.cpp:509 147 | #: declarative/dynamicwallpaperhandler.cpp:210 148 | #, kde-format 149 | msgid "%1 is not a dynamic wallpaper" 150 | msgstr "%1 不是动态壁纸" 151 | 152 | #: declarative/dynamicwallpaperpreviewjob.cpp:140 153 | #, fuzzy, kde-format 154 | msgid "Not a dynamic wallpaper" 155 | msgstr "%1 不是动态壁纸" 156 | --------------------------------------------------------------------------------