commit 1516be627a8fd45f78f013201eb57fd0067633d4 Author: cutefishd Date: Tue Mar 16 16:21:03 2021 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e9c222c --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# C++ objects and libs +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.so.* +*.dll +*.dylib + +# Qt-es +object_script.*.Release +object_script.*.Debug +*_plugin_import.cpp +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +moc_*.h +qrc_*.cpp +ui_*.h +*.qmlc +*.jsc +Makefile* +*build-* +*.qm +*.prl + +# Qt unit tests +target_wrapper.* + +# QtCreator +*.autosave + +# QtCreator Qml +*.qmlproject.user +*.qmlproject.user.* + +# QtCreator CMake +CMakeLists.txt.user* + +# QtCreator 4.8< compilation database +compile_commands.json + +# QtCreator local machine specific files for imported projects +*creator.user* + +build/* \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a1c27fe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,58 @@ +{ + "files.associations": { + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "list": "cpp", + "map": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "ostream": "cpp", + "ranges": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "typeinfo": "cpp", + "variant": "cpp" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a3e52a1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.5) + +project(libcutefish) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include(GenerateExportHeader) +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + +set(QT Core Gui Quick QuickControls2 DBus Xml Concurrent) +find_package(Qt5 REQUIRED ${QT}) + +# Get the installation directory from qmake +get_target_property(QT_QMAKE_EXECUTABLE ${Qt5Core_QMAKE_EXECUTABLE} IMPORTED_LOCATION) +if(NOT QT_QMAKE_EXECUTABLE) + message(FATAL_ERROR "qmake is not found.") +endif() + +execute_process(COMMAND ${QT_QMAKE_EXECUTABLE} -query QT_INSTALL_QML + OUTPUT_VARIABLE INSTALL_QMLDIR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +if(INSTALL_QMLDIR) + message(STATUS "qml directory:" "${INSTALL_QMLDIR}") +else() + message(FATAL_ERROR "qml directory cannot be detected.") +endif() +# ------------------------------------------- + +add_subdirectory(accounts) +add_subdirectory(mpris) +add_subdirectory(networkmanagement) +add_subdirectory(screen) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6f0ed51 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# System Library + +## Dependencies + +```shell +sudo pacman -S gcc cmake qt5-base qt5-quickcontrols2 networkmanager-qt modemmanager-qt +``` + +## Build and Install + +``` +mkdir build +cd build +cmake .. +make +sudo make install +``` + +## License + +This project has been licensed by GPLv3. \ No newline at end of file diff --git a/accounts/CMakeLists.txt b/accounts/CMakeLists.txt new file mode 100644 index 0000000..ffa504b --- /dev/null +++ b/accounts/CMakeLists.txt @@ -0,0 +1,33 @@ +set(ACCOUNTS_SRCS + accountsmanager.cpp + accountsmanager.h + accountsmanager_p.h + useraccount.cpp + useraccount.h + useraccount_p.h + usersmodel.cpp + usersmodel.h + usersmodel_p.h + qmlplugins.cpp +) + +qt5_add_dbus_interface(ACCOUNTS_SRCS + org.freedesktop.Accounts.User.xml + user_interface) +qt5_add_dbus_interface(ACCOUNTS_SRCS + org.freedesktop.Accounts.xml + accounts_interface) + +# set_source_files_properties(${ACCOUNTS_SRCS} PROPERTIES SKIP_AUTOGEN ON) + +add_library(cutefishaccounts_qmlplugins SHARED ${ACCOUNTS_SRCS}) + +target_link_libraries (cutefishaccounts_qmlplugins + Qt5::Core + Qt5::DBus + Qt5::Quick + Qt5::Gui +) + +install(TARGETS cutefishaccounts_qmlplugins DESTINATION ${INSTALL_QMLDIR}/Cutefish/Accounts) +install(FILES qmldir DESTINATION ${INSTALL_QMLDIR}/Cutefish/Accounts) \ No newline at end of file diff --git a/accounts/accountsmanager.cpp b/accounts/accountsmanager.cpp new file mode 100644 index 0000000..e073011 --- /dev/null +++ b/accounts/accountsmanager.cpp @@ -0,0 +1,419 @@ +/**************************************************************************** + * This file is part of Qt AccountsService. + * + * Copyright (C) 2018 Pier Luigi Fiorini + * + * $BEGIN_LICENSE:LGPLv3+$ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * $END_LICENSE$ + ***************************************************************************/ + +#include +#include + +#include "accountsmanager.h" +#include "accountsmanager_p.h" + +namespace QtAccountsService { + +/* + * AccountsManagerPrivate + */ + +AccountsManagerPrivate::AccountsManagerPrivate(const QDBusConnection &bus) +{ + interface = + new OrgFreedesktopAccountsInterface(QStringLiteral("org.freedesktop.Accounts"), + QStringLiteral("/org/freedesktop/Accounts"), bus); +} + +AccountsManagerPrivate::~AccountsManagerPrivate() +{ + qDeleteAll(usersCache); + delete interface; +} + +void AccountsManagerPrivate::_q_userAdded(const QDBusObjectPath &path) +{ + Q_Q(AccountsManager); + + if (usersCache.contains(path.path())) { + Q_EMIT q->userAdded(usersCache[path.path()]); + return; + } + + UserAccount *account = new UserAccount(path.path(), interface->connection()); + usersCache[path.path()] = account; + Q_EMIT q->userAdded(account); +} + +void AccountsManagerPrivate::_q_userDeleted(const QDBusObjectPath &path) +{ + Q_Q(AccountsManager); + + UserAccount *account = usersCache.value(path.path(), nullptr); + if (!account) + account = new UserAccount(path.path()); + usersCache.remove(path.path()); + Q_EMIT q->userDeleted(account->userId()); + account->deleteLater(); +} + +/*! + \class AccountsManager + \brief AccountsManager provides the backend for user management. + + The AccountsManager class allows for querying and manipiulating user + account information. + + \author Pier Luigi Fiorini +*/ + +/*! + Constructs a AccountsManager object. +*/ +AccountsManager::AccountsManager(const QDBusConnection &bus, QObject *parent) + : QObject(parent) + , d_ptr(new AccountsManagerPrivate(bus)) +{ + qRegisterMetaType("UserAccount::AccountType"); + qRegisterMetaType("UserAccount*"); + + d_ptr->q_ptr = this; + + connect(d_ptr->interface, SIGNAL(UserAdded(QDBusObjectPath)), // clazy:exclude=old-style-connect + this, SLOT(_q_userAdded(QDBusObjectPath))); + connect(d_ptr->interface, + SIGNAL(UserDeleted(QDBusObjectPath)), // clazy:exclude=old-style-connect + this, SLOT(_q_userDeleted(QDBusObjectPath))); +} + +/*! + Destroy a AccountsManager object. +*/ +AccountsManager::~AccountsManager() +{ + delete d_ptr; +} + +/*! + Caches a user account, so that it shows up in listCachedUsers() output. + The user name may be a remote user, but the system must be able to lookup + the user name and resolve the user information. + + A userCached() signal with a UserAccount pointer will be emitted as soon + as the user account has been cached by AccountsService. + + \param userName The user name for the user. +*/ +void AccountsManager::cacheUser(const QString &userName) +{ + Q_D(AccountsManager); + + QDBusPendingCall call = d->interface->CacheUser(userName); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [=](QDBusPendingCallWatcher *w) { + QDBusPendingReply reply = *w; + w->deleteLater(); + if (reply.isError()) { + QDBusError error = reply.error(); + qWarning("Couldn't cache user %s: %s", userName.toUtf8().constData(), + error.errorString(error.type()).toUtf8().constData()); + } else { + QDBusObjectPath path = reply.argumentAt<0>(); + if (path.path().isEmpty()) + return; + + UserAccount *account = d->usersCache.value(path.path(), nullptr); + if (!account) { + account = new UserAccount(path.path(), d->interface->connection()); + d->usersCache[path.path()] = account; + } + Q_EMIT userCached(userName); + } + }); +} + +/*! + Releases all metadata about a user account, including icon, language and session. + If the user account is from a remote server and the user has never logged in + before, then that account will no longer show up in listCachedUsers() output. + + Emits userUncached() when done. + + \param userName The user name for the user. +*/ +void AccountsManager::uncacheUser(const QString &userName) +{ + Q_D(AccountsManager); + + QDBusPendingCall call = d->interface->UncacheUser(userName); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [=](QDBusPendingCallWatcher *w) { + QDBusPendingReply reply = *w; + w->deleteLater(); + if (reply.isError()) { + QDBusError error = reply.error(); + qWarning("Couldn't uncache user %s: %s", userName.toUtf8().constData(), + error.errorString(error.type()).toUtf8().constData()); + } else { + QMap::iterator it = d->usersCache.begin(); + while (it != d->usersCache.end()) { + auto account = it.value(); + if (account->userName() == userName) { + d->usersCache.erase(++it); + account->deleteLater(); + } else { + ++it; + } + } + Q_EMIT userUncached(userName); + } + }); +} + +/*! + List cache users asynchronously. + When the list is ready, \c listCachedUsersFinished will be emitted. +*/ +void AccountsManager::listCachedUsers() +{ + Q_D(AccountsManager); + + QDBusPendingCall call = d->interface->ListCachedUsers(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [=](QDBusPendingCallWatcher *w) { + QDBusPendingReply> reply = *w; + w->deleteLater(); + if (reply.isError()) { + QDBusError error = reply.error(); + qWarning("Couldn't list cached users: %s", + error.errorString(error.type()).toUtf8().constData()); + } else { + UserAccountList userList; + + QList value = reply.argumentAt<0>(); + userList.reserve(value.size()); + for (int i = 0; i < value.size(); i++) { + const QString path = value.at(i).path(); + UserAccount *account = d->usersCache.value(path, nullptr); + if (!account) { + account = new UserAccount(path, d->interface->connection()); + d->usersCache[path] = account; + } + userList.append(account); + } + Q_EMIT listCachedUsersFinished(userList); + } + }); +} + +/*! + Returns a list of user accounts. + + This method is synchronous and may take some time. + + \param systemUsers If true, returns also system users. +*/ +UserAccountList AccountsManager::listCachedUsersSync() +{ + Q_D(AccountsManager); + + UserAccountList list; + + QDBusPendingReply> reply = d->interface->ListCachedUsers(); + reply.waitForFinished(); + + if (reply.isError()) { + QDBusError error = reply.error(); + qWarning("Couldn't list cached users: %s", + error.errorString(error.type()).toUtf8().constData()); + return list; + } + + QList value = reply.argumentAt<0>(); + list.reserve(value.size()); + + for (int i = 0; i < value.size(); i++) { + const QString path = value.at(i).path(); + UserAccount *account = d->usersCache.value(path, nullptr); + if (!account) { + account = new UserAccount(path, d->interface->connection()); + d->usersCache[path] = account; + } + list.append(account); + } + + return list; +} + +/*! + Returns user with name \a userName from the list of cached users + created by \l listCachedUsers(). + + \param userName The user name to look up. + \return the corresponding UserAccount object. +*/ +UserAccount *AccountsManager::cachedUser(const QString &userName) const +{ + Q_D(const AccountsManager); + + QMap::const_iterator it = d->usersCache.constBegin(); + while (it != d->usersCache.constEnd()) { + auto account = it.value(); + if (account->userName() == userName) + return account; + } + + return nullptr; +} + +/*! + Finds a user by \a uid. + + This method is synchronous and may take some time. + + \param uid The uid to look up. + \return the corresponding UserAccount object. +*/ +UserAccount *AccountsManager::findUserById(qlonglong uid) +{ + Q_D(AccountsManager); + + QDBusPendingReply reply = d->interface->FindUserById(uid); + reply.waitForFinished(); + + if (reply.isError()) { + QDBusError error = reply.error(); + qWarning("Couldn't find user by uid %lld: %s", uid, + error.errorString(error.type()).toUtf8().constData()); + return nullptr; + } + + QDBusObjectPath path = reply.argumentAt<0>(); + if (path.path().isEmpty()) + return nullptr; + + UserAccount *account = d->usersCache.value(path.path(), nullptr); + if (!account) { + account = new UserAccount(path.path(), d->interface->connection()); + d->usersCache[path.path()] = account; + } + return account; +} + +/*! + Finds a user by user \a userName. + + This method is synchronous and may take some time. + + \param uid The user name to look up. + \return the corresponding UserAccount object. +*/ +UserAccount *AccountsManager::findUserByName(const QString &userName) +{ + Q_D(AccountsManager); + + QDBusPendingReply reply = d->interface->FindUserByName(userName); + reply.waitForFinished(); + + if (reply.isError()) { + QDBusError error = reply.error(); + qWarning("Couldn't find user by user name %s: %s", userName.toUtf8().constData(), + error.errorString(error.type()).toUtf8().constData()); + return nullptr; + } + + QDBusObjectPath path = reply.argumentAt<0>(); + if (path.path().isEmpty()) + return nullptr; + + UserAccount *account = d->usersCache.value(path.path(), nullptr); + if (!account) { + account = new UserAccount(path.path(), d->interface->connection()); + d->usersCache[path.path()] = account; + } + return account; +} + +/*! + Creates a new \a accountType type user account whose name is \a userName, + real name is \a fullName. + + This method is synchronous and may take some time. + + \param userName The name of the new user to be created. + \param fullName First name and last name. + \param accountType The account type. + \return whether the user was created successfully. +*/ +bool AccountsManager::createUser(const QString &userName, const QString &fullName, + UserAccount::AccountType accountType) +{ + Q_D(AccountsManager); + + QDBusPendingReply reply = + d->interface->CreateUser(userName, fullName, accountType); + if (reply.isError()) { + QDBusError error = reply.error(); + qWarning("Couldn't create user %s: %s", userName.toUtf8().constData(), + error.errorString(error.type()).toUtf8().constData()); + return false; + } + + return true; +} + +/*! + Deletes the user designated by \a uid. + + This method is synchronous and may take some time. + + \param uid The user identifier. + \param removeFiles If true all files owned by the user will be removed. + \return whether the user was deleted successfully. +*/ +bool AccountsManager::deleteUser(qlonglong uid, bool removeFiles) +{ + Q_D(AccountsManager); + + QDBusPendingReply<> reply = d->interface->DeleteUser(uid, removeFiles); + if (reply.isError()) { + QDBusError error = reply.error(); + qWarning("Couldn't delete user %lld: %s", uid, + error.errorString(error.type()).toUtf8().constData()); + return false; + } + + return true; +} + +/*! + Deletes the specified \a account. + + This method is synchronous and may take some time. + + \param account The account to remove. + \param removeFiles If true all files owned by the user will be removed. + \return whether the user was deleted successfully. +*/ +bool AccountsManager::deleteUser(UserAccount *account, bool removeFiles) +{ + return deleteUser(account->userId(), removeFiles); +} +} + +#include "moc_accountsmanager.cpp" diff --git a/accounts/accountsmanager.h b/accounts/accountsmanager.h new file mode 100644 index 0000000..5351c91 --- /dev/null +++ b/accounts/accountsmanager.h @@ -0,0 +1,80 @@ +/**************************************************************************** + * This file is part of Qt AccountsService. + * + * Copyright (C) 2018 Pier Luigi Fiorini + * + * $BEGIN_LICENSE:LGPLv3+$ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * $END_LICENSE$ + ***************************************************************************/ + +#ifndef QTACCOUNTSSERVICE_ACCOUNTSMANAGER_H +#define QTACCOUNTSSERVICE_ACCOUNTSMANAGER_H + +#include +#include + +#include "useraccount.h" + +QT_FORWARD_DECLARE_CLASS(QDBusObjectPath) + +namespace QtAccountsService { + +class AccountsManagerPrivate; + +class AccountsManager : public QObject +{ + Q_OBJECT +public: + explicit AccountsManager(const QDBusConnection &bus = QDBusConnection::systemBus(), + QObject *parent = nullptr); + ~AccountsManager(); + + Q_INVOKABLE void cacheUser(const QString &userName); + Q_INVOKABLE void uncacheUser(const QString &userName); + + Q_INVOKABLE void listCachedUsers(); + UserAccountList listCachedUsersSync(); + + Q_INVOKABLE QtAccountsService::UserAccount *cachedUser(const QString &userName) const; + + Q_INVOKABLE QtAccountsService::UserAccount *findUserById(qlonglong uid); + Q_INVOKABLE QtAccountsService::UserAccount *findUserByName(const QString &userName); + + Q_INVOKABLE bool createUser(const QString &userName, const QString &fullName, + UserAccount::AccountType accountType); + + Q_INVOKABLE bool deleteUser(qlonglong uid, bool removeFiles); + bool deleteUser(UserAccount *account, bool removeFiles); + +Q_SIGNALS: + void userAdded(QtAccountsService::UserAccount *account); + void userDeleted(qlonglong uid); + void userCached(const QString &userName); + void userUncached(const QString &userName); + void listCachedUsersFinished(const QtAccountsService::UserAccountList &userList); + +protected: + AccountsManagerPrivate *d_ptr; + +private: + Q_DECLARE_PRIVATE(AccountsManager) + Q_PRIVATE_SLOT(d_func(), void _q_userAdded(const QDBusObjectPath &path)) + Q_PRIVATE_SLOT(d_func(), void _q_userDeleted(const QDBusObjectPath &path)) +}; +} + +#endif // QTACCOUNTSSERVICE_ACCOUNTSMANAGER_H diff --git a/accounts/accountsmanager_p.h b/accounts/accountsmanager_p.h new file mode 100644 index 0000000..c539cc6 --- /dev/null +++ b/accounts/accountsmanager_p.h @@ -0,0 +1,62 @@ +/**************************************************************************** + * This file is part of Qt AccountsService. + * + * Copyright (C) 2018 Pier Luigi Fiorini + * + * $BEGIN_LICENSE:LGPLv3+$ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * $END_LICENSE$ + ***************************************************************************/ + +#ifndef QTACCOUNTSSERVICE_ACCOUNTSMANAGER_P_H +#define QTACCOUNTSSERVICE_ACCOUNTSMANAGER_P_H + +#include "accounts_interface.h" + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt AccountsService API. It exists +// purely as an implementation detail. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +class OrgFreedesktopAccountsInterface; + +namespace QtAccountsService { + +class AccountsManager; + +class AccountsManagerPrivate +{ + Q_DECLARE_PUBLIC(AccountsManager) +public: + AccountsManagerPrivate(const QDBusConnection &bus); + ~AccountsManagerPrivate(); + + AccountsManager *q_ptr; + OrgFreedesktopAccountsInterface *interface; + QMap usersCache; + + void _q_userAdded(const QDBusObjectPath &path); + void _q_userDeleted(const QDBusObjectPath &path); +}; +} + +#endif // QTACCOUNTSSERVICE_ACCOUNTSMANAGER_P_H diff --git a/accounts/org.freedesktop.Accounts.User.xml b/accounts/org.freedesktop.Accounts.User.xml new file mode 100644 index 0000000..7dfcef8 --- /dev/null +++ b/accounts/org.freedesktop.Accounts.User.xml @@ -0,0 +1,817 @@ + + + + + + + + + + The new username. + + + + + + + Sets the users username. Note that it is usually not allowed + to have multiple users with the same username. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.user-administration + To change the username of any user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The new name, typically in the form "Firstname Lastname". + + + + + + + Sets the users real name. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change his own name + + + org.freedesktop.accounts.user-administration + To change the name of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The new email address. + + + + + + + Sets the users email address. + + + Note that setting an email address in the AccountsService is + not the same as configuring a mail client. Mail clients might + default to email address that is configured here, though. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change his own email address + + + org.freedesktop.accounts.user-administration + To change the email address of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The new language, as a locale specification like "de_DE.UTF-8". + + + + + + + Sets the users language. + + + The expectation is that display managers will start the + users session with this locale. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change his own language + + + org.freedesktop.accounts.user-administration + To change the language of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + + The new xsession to start (e.g. "gnome") + + + + + + + Sets the users x session. + + + The expectation is that display managers will log the user in to this + specified session, if available. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change his own language + + + org.freedesktop.accounts.user-administration + To change the language of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The new location as a freeform string. + + + + + + + Sets the users location. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change his own location + + + org.freedesktop.accounts.user-administration + To change the location of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The new homedir as an absolute path. + + + + + + + Sets the users home directory. + + + Note that changing the users home directory moves all the content + from the old location to the new one, and is potentially an + expensive operation. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.user-administration + To change the home directory of a user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The new user shell. + + + + + + + Sets the users shell. + + + Note that setting the shell to a non-allowed program may + prevent the user from logging in. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.user-administration + To change the shell of a user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The absolute filename of a png file to use as the users icon. + + + + + + + Sets the users icon. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change his own icon + + + org.freedesktop.accounts.user-administration + To change the icon of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + Whether to lock or unlock the users account. + + + + + + + Locks or unlocks a users account. + + + Locking an account prevents the user from logging in. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.user-administration + To lock or unlock user accounts + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The new account type, encoded as an integer: + + + 0 + Standard user + + + 1 + Administrator + + + + + + + + + Changes the users account type. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.user-administration + To change an account type + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The new password mode, encoded as an integer: + + + 0 + Regular password + + + 1 + Password must be set at next login + + + 2 + No password + + + + + + + + + Changes the users password mode. + + + Note that changing the password mode has the side-effect of + unlocking the account. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.user-administration + To change a users password mode + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The crypted password. + + + + + + + The password hint. + + + + + + + Sets a new password for this user. + + + Note that setting a password has the side-effect of + unlocking the account. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.user-administration + To change the password of a user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + The password hint. + + + + + + + Sets the users password hint. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.change-own-user-data + To change his own language + + + org.freedesktop.accounts.user-administration + To change the language of another user + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + + Whether to enable automatic login for this user. + + + + + + + Enables or disables automatic login for a user. + + + Note that usually only one user can have automatic login + enabled, so turning it on for a user will disable it for + the previously configured autologin user. + + + + The caller needs one of the following PolicyKit authorizations: + + + org.freedesktop.accounts.set-login-option + To change the login screen configuration + + + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + + The uid of the user. + + + + + + + + + + The username of the user. + + + + + + + + + + The users real name. + + + + + + + + + + The users account type, encoded as an integer: + + + 0 + Standard user + + + 1 + Administrator + + + + + + + + + + + + The users home directory. + + + + + + + + + + The users shell. + + + + + + + + + + The email address. + + + + + + + + + + The users language, as a locale specification like "de_DE.UTF-8". + + + + + + + + + + The users x session. + + + + + + + + + + The users location. + + + + + + + + + + How often the user has logged in. + + + + + + + + + + The last login time. + + + + + + + + + + + + The filename of a png file containing the users icon. + + + + + + + + + + Whether the users account is locked. + + + + + + + + + + The password mode for the user account, encoded as an integer: + + + 0 + Regular password + + + 1 + Password must be set at next login + + + 2 + No password + + + + + + + + + + + + The password hint for the user. + + + + + + + + + + Whether automatic login is enabled for the user. + + + + + + + + + + Whether this is a 'system' account, like 'root' or 'nobody'. + System accounts should normally not appear in lists of + users, and ListCachedUsers will not include such accounts. + + + + + + + + + + Whether the user is a local account or not. + + + + + + + + + + Emitted when the user is changed. + + + + + + + diff --git a/accounts/org.freedesktop.Accounts.xml b/accounts/org.freedesktop.Accounts.xml new file mode 100644 index 0000000..692540a --- /dev/null +++ b/accounts/org.freedesktop.Accounts.xml @@ -0,0 +1,228 @@ + + + + + + + + + + Object paths of cached users + + + + + + Lists users which have logged into the system locally before. + This is not meant to return an exhaustive list of all users. + It is possible for FindUserByName() + to return a user that's not on the list. + + + + + + + + + The uid to look up + + + Object path of user + + + + + + Finds a user by uid. + + + + if no user with the given uid exists + + + + + + + + The username to look up + + + Object path of user + + + + + + Finds a user by its username. + + + + if no user with the given username exists + + + + + + + + The username for the new user + + + + The real name for the new user + + + Object path of the new user + + + + The account type, encoded as an integer + + + + + + Creates a new user account. + + + The accountType argument can take the following values: + + + + 0 + Standard user + + + 1 + Administrator + + + + + The caller needs the org.freedesktop.accounts.user-administration PolicyKit authorization. + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + + The username for the user + + + Object path of user + + + + + + Caches a user account, so that it shows up in ListCachedUsers() output. + The user name may be a remote user, but the system must be able to lookup + the user name and resolve the user information. + + + + The caller needs the org.freedesktop.accounts.user-administration PolicyKit authorization. + + + if the caller lacks the appropriate PolicyKit authorization + if the user name cannot be resolved + + + + + + + + The username for the user + + + + + + Releases all metadata about a user account, including icon, language and session. If the user account is + from a remote server and the user has never logged in before, then that account will no longer show up + in ListCachedUsers() output. + + + + The caller needs the org.freedesktop.accounts.user-administration PolicyKit authorization. + + + if the caller lacks the appropriate PolicyKit authorization + if the user name cannot be resolved + + + + + + + + The uid to delete + + + Whether to remove the users files + + + + + + Deletes a user account. + + + + The caller needs the org.freedesktop.accounts.user-administration PolicyKit authorization. + + + if the caller lacks the appropriate PolicyKit authorization + if the operation failed + + + + + + + Object path of the user that was added. + + + + + Emitted when a user is added. + + + + + + + + Object path of the user that was deleted. + + + + + Emitted when a user is deleted. + + + + + + + + + + The version of the running daemon. + + + + + + + diff --git a/accounts/qmldir b/accounts/qmldir new file mode 100644 index 0000000..8507f55 --- /dev/null +++ b/accounts/qmldir @@ -0,0 +1,2 @@ +module Cutefish.Accounts +plugin cutefishaccounts_qmlplugins \ No newline at end of file diff --git a/accounts/qmlplugins.cpp b/accounts/qmlplugins.cpp new file mode 100644 index 0000000..84ca87e --- /dev/null +++ b/accounts/qmlplugins.cpp @@ -0,0 +1,21 @@ +#include +#include + +#include "accountsmanager.h" +#include "useraccount.h" +#include "usersmodel.h" + +class QmlPlugins : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char * uri) override { + qmlRegisterType(uri, 1, 0, "AccountsManager"); + qmlRegisterType(uri, 1, 0, "UserAccount"); + qmlRegisterType(uri, 1, 0, "UsersModel"); + } +}; + +#include "qmlplugins.moc" \ No newline at end of file diff --git a/accounts/useraccount.cpp b/accounts/useraccount.cpp new file mode 100644 index 0000000..6f5d0c0 --- /dev/null +++ b/accounts/useraccount.cpp @@ -0,0 +1,680 @@ +/**************************************************************************** + * + * Copyright (C) 2018 Pier Luigi Fiorini + * + * $BEGIN_LICENSE:LGPLv3+$ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * $END_LICENSE$ + ***************************************************************************/ + +#include "useraccount.h" +#include "useraccount_p.h" + +#include +#include +#include + +namespace QtAccountsService { + +/* + * UserAccountPrivate + */ + +UserAccountPrivate::UserAccountPrivate(UserAccount *q) + : bus(QDBusConnection::systemBus()) + , user(nullptr) + , accountType(UserAccount::StandardAccountType) + , locked(false) + , automaticLogin(false) + , passwordMode(UserAccount::NonePasswordMode) + , q_ptr(q) +{ +} + +void UserAccountPrivate::initialize(const QDBusConnection &connection, const QString &objectPath) +{ + Q_Q(UserAccount); + + bus = connection; + + if (user) { + q->disconnect(user, &OrgFreedesktopAccountsUserInterface::Changed, q, + &UserAccount::handleAccountChanged); + user = nullptr; + } + + user = new OrgFreedesktopAccountsUserInterface(QStringLiteral("org.freedesktop.Accounts"), + objectPath, bus, q); + q->connect(user, &OrgFreedesktopAccountsUserInterface::Changed, q, + &UserAccount::handleAccountChanged); + + emitSignals(); +} + +void UserAccountPrivate::emitSignals() +{ + Q_Q(UserAccount); + + Q_EMIT q->userIdChanged(); + Q_EMIT q->groupIdChanged(); + Q_EMIT q->accountTypeChanged(); + Q_EMIT q->lockedChanged(); + Q_EMIT q->automaticLoginChanged(); + Q_EMIT q->passwordModeChanged(); + Q_EMIT q->userNameChanged(); + Q_EMIT q->realNameChanged(); + Q_EMIT q->displayNameChanged(); + Q_EMIT q->homeDirectoryChanged(); + Q_EMIT q->shellChanged(); + Q_EMIT q->iconFileNameChanged(); + Q_EMIT q->emailChanged(); + Q_EMIT q->languageChanged(); + Q_EMIT q->locationChanged(); + Q_EMIT q->xsessionChanged(); +} + +/*! + \class UserAcccount + \brief The UserAccount class describes user accounts. + + This class describes user accounts retrieved with AccountsManager. + + \author Pier Luigi Fiorini + + \sa AccountsManager +*/ + +/*! + Constructs a UserAccount object for the currently logged in user. +*/ +UserAccount::UserAccount(const QDBusConnection &bus, QObject *parent) + : QObject(parent) + , d_ptr(new UserAccountPrivate(this)) +{ + Q_D(UserAccount); + + QString objectPath = + QStringLiteral("/org/freedesktop/Accounts/User") + QString::number(getuid()); + d->initialize(bus, objectPath); +} + +/*! + \internal + + Constructs a UserAccount object from a specific objectPath in the form of + /org/freedesktop/Accounts/UserUID where UID is user's uid. + + \param objectPath Accounts Service object path for the user account. +*/ +UserAccount::UserAccount(const QString &objectPath, const QDBusConnection &bus, QObject *parent) + : QObject(parent) + , d_ptr(new UserAccountPrivate(this)) +{ + Q_D(UserAccount); + d->initialize(bus, objectPath); +} + +/*! + Returns the user identifier. +*/ +qlonglong UserAccount::userId() const +{ + Q_D(const UserAccount); + return d->user->uid(); +} + +/*! + Change the user identifier to \c uid. + The object will hold information for the requested user identifier. +*/ +void UserAccount::setUserId(qlonglong uid) +{ + Q_D(UserAccount); + + QString objectPath = QStringLiteral("/org/freedesktop/Accounts/User") + QString::number(uid); + d->initialize(d->bus, objectPath); +} + +/*! + Returns group identifier. +*/ +qlonglong UserAccount::groupId() const +{ + Q_D(const UserAccount); + + size_t bufSize = 0; + long sizeMax = ::sysconf(_SC_GETPW_R_SIZE_MAX); + if (sizeMax <= 0) + bufSize = 16384; + else + bufSize = static_cast(sizeMax); + char *buf = static_cast(::malloc(bufSize)); + if (!buf) + qFatal("Cannot allocate %lu bytes: %s", static_cast(bufSize), strerror(errno)); + + struct passwd pwd; + struct passwd *result; + int s = ::getpwuid_r(d->user->uid(), &pwd, buf, bufSize, &result); + if (!result) { + if (s == 0) + qCritical("User with uid %lld not found", d->user->uid()); + else + qCritical("Failed to get group information: %s", strerror(s)); + return 0; + } + + return pwd.pw_gid; +} + +/*! + Returns the account type. +*/ +UserAccount::AccountType UserAccount::accountType() const +{ + Q_D(const UserAccount); + return static_cast(d->user->accountType()); +} + +/*! + Sets the account type to \a type. + + \param type Account type. +*/ +void UserAccount::setAccountType(AccountType type) +{ + Q_D(UserAccount); + + if (type == accountType()) + return; + + d->accountType = type; + d->user->SetAccountType(static_cast(type)); + Q_EMIT accountTypeChanged(); +} + +/*! + Returns whether this account is locked or not. +*/ +bool UserAccount::isLocked() const +{ + Q_D(const UserAccount); + return d->user->locked(); +} + +/*! + Locks or unlocks the user account. + Locking an account prevents the user from logging in. + + \param locked Whether to lock or unlock the user account. +*/ +void UserAccount::setLocked(bool locked) +{ + Q_D(UserAccount); + + if (isLocked() == locked) + return; + + d->locked = locked; + d->user->SetLocked(locked); + Q_EMIT lockedChanged(); +} + +/*! + Returns whether the user account will automatically log in when the + system starts up. +*/ +bool UserAccount::automaticLogin() const +{ + Q_D(const UserAccount); + return d->user->automaticLogin(); +} + +/*! + Sets whether the user account will automatically log in when + the system starts up. + + \param automaticLogin Whether automatic login is enabled for the user. +*/ +void UserAccount::setAutomaticLogin(bool automaticLogin) +{ + Q_D(UserAccount); + + if (this->automaticLogin() == automaticLogin) + return; + + d->automaticLogin = automaticLogin; + d->user->SetAutomaticLogin(automaticLogin); + Q_EMIT automaticLoginChanged(); +} + +/*! + Returns how often the user has logged in. +*/ +qlonglong UserAccount::loginFrequency() const +{ + Q_D(const UserAccount); + return d->user->loginFrequency(); +} + +/*! + Returns the last login time. +*/ +qlonglong UserAccount::loginTime() const +{ + Q_D(const UserAccount); + return d->user->loginTime(); +} + +/*! + Returns the password mode for the user account. +*/ +UserAccount::PasswordMode UserAccount::passwordMode() const +{ + Q_D(const UserAccount); + return static_cast(d->user->passwordMode()); +} + +/*! + Sets the password mode for the user account. + + \param mode Password mode. +*/ +void UserAccount::setPasswordMode(UserAccount::PasswordMode mode) +{ + Q_D(UserAccount); + + if (passwordMode() == mode) + return; + + d->passwordMode = mode; + d->user->SetPasswordMode(static_cast(mode)); + Q_EMIT passwordModeChanged(); +} + +/*! + Returns the password hint for the user. +*/ +QString UserAccount::passwordHint() const +{ + Q_D(const UserAccount); + return d->user->passwordHint(); +} + +/*! + Returns whether the user is a local account or not. +*/ +bool UserAccount::isLocalAccount() const +{ + Q_D(const UserAccount); + return d->user->localAccount(); +} + +/*! + Returns whether the user is a system account or not. +*/ +bool UserAccount::isSystemAccount() const +{ + Q_D(const UserAccount); + return d->user->systemAccount(); +} + +/*! + Returns the user name. +*/ +QString UserAccount::userName() const +{ + Q_D(const UserAccount); + return d->user->userName(); +} + +/*! + Sets the user name to \a userName. + + \param userName The new user name. +*/ +void UserAccount::setUserName(const QString &userName) +{ + Q_D(UserAccount); + + if (this->userName() == userName) + return; + + d->userName = userName; + d->user->SetUserName(userName); + Q_EMIT userNameChanged(); + Q_EMIT displayNameChanged(); +} + +/*! + Returns user's real name. +*/ +QString UserAccount::realName() const +{ + Q_D(const UserAccount); + return d->user->realName(); +} + +/*! + Sets the user's real name to \a realName. + + \param realName Real name. +*/ +void UserAccount::setRealName(const QString &realName) +{ + Q_D(UserAccount); + + if (this->realName() == realName) + return; + + d->realName = realName; + d->user->SetRealName(realName); + Q_EMIT realNameChanged(); + Q_EMIT displayNameChanged(); +} + +/*! + Returns user's real name if not empty, otherwise the user name. +*/ +QString UserAccount::displayName() const +{ + if (realName().isEmpty()) + return userName(); + return realName(); +} + +/*! + Returns the home directory absolute path. +*/ +QString UserAccount::homeDirectory() const +{ + Q_D(const UserAccount); + return d->user->homeDirectory(); +} + +/*! + Sets the home directory to \a homeDirectory. + + \param homeDirectory Home directory. +*/ +void UserAccount::setHomeDirectory(const QString &homeDirectory) +{ + Q_D(UserAccount); + + if (this->homeDirectory() == homeDirectory) + return; + + d->user->SetHomeDirectory(homeDirectory); + Q_EMIT homeDirectoryChanged(); +} + +/*! + Returns the login shell absolute path. +*/ +QString UserAccount::shell() const +{ + Q_D(const UserAccount); + return d->user->shell(); +} + +/*! + Sets the login shell to \a shell. + + \param shell Login shell absolute path. +*/ +void UserAccount::setShell(const QString &shell) +{ + Q_D(UserAccount); + + if (this->shell() == shell) + return; + + d->shell = shell; + d->user->SetShell(shell); + Q_EMIT shellChanged(); +} + +/*! + Returns user's picture absoulte path. +*/ +QString UserAccount::iconFileName() const +{ + Q_D(const UserAccount); + return QFile::exists(d->user->iconFile()) ? d->user->iconFile() : QString(); +} + +/*! + Sets users' picture absolute path to \a fileName. + + \param fileName Picture absolute path. +*/ +void UserAccount::setIconFileName(const QString &fileName) +{ + Q_D(UserAccount); + + if (iconFileName() == fileName) + return; + + d->iconFileName = fileName; + d->user->SetIconFile(fileName); + Q_EMIT iconFileNameChanged(); +} + +/*! + Returns user's email address. +*/ +QString UserAccount::email() const +{ + Q_D(const UserAccount); + return d->user->email(); +} + +/*! + Sets user's email address to \a email. + + \param email Email address. +*/ +void UserAccount::setEmail(const QString &email) +{ + Q_D(UserAccount); + + if (this->email() == email) + return; + + d->email = email; + d->user->SetEmail(email); + Q_EMIT emailChanged(); +} + +/*! + Returns user language. +*/ +QString UserAccount::language() const +{ + Q_D(const UserAccount); + return d->user->language(); +} + +/*! + Sets user language to \a language. + + \param language Language. +*/ +void UserAccount::setLanguage(const QString &language) +{ + Q_D(UserAccount); + + if (this->language() == language) + return; + + d->language = language; + d->user->SetLanguage(language); + Q_EMIT languageChanged(); +} + +/*! + Returns user location. +*/ +QString UserAccount::location() const +{ + Q_D(const UserAccount); + return d->user->location(); +} + +/*! + Sets user \a location. + + \param location Location. +*/ +void UserAccount::setLocation(const QString &location) +{ + Q_D(UserAccount); + + if (this->location() == location) + return; + + d->location = location; + d->user->SetLocation(location); + Q_EMIT locationChanged(); +} + +/*! + Returns the X11 session for the user account. +*/ +QString UserAccount::xsession() const +{ + Q_D(const UserAccount); + return d->user->xSession(); +} + +/*! + Sets the X11 session for the user account. + + \param session X11 session name. +*/ +void UserAccount::setXSession(const QString &session) +{ + Q_D(UserAccount); + + if (xsession() == session) + return; + + d->xsession = session; + d->user->SetXSession(session); + Q_EMIT xsessionChanged(); +} + +/*! + Sets the password for the user account. + + \param password. + \param hint passwordHint. + */ +void UserAccount::setPassword(const QString &password, const QString &hint) +{ + Q_D(UserAccount); + d->user->SetPassword(password, hint); +} + +/*! + Sets the password hint for the user account. + + \param hint Password hint. + */ +void UserAccount::setPasswordHint(const QString &hint) +{ + Q_D(UserAccount); + d->user->SetPasswordHint(hint); +} + +/*! + \internal + */ +void UserAccount::handleAccountChanged() +{ + // Catch changes outside of this API + + Q_D(UserAccount); + + if (d->accountType != accountType()) { + d->accountType = accountType(); + Q_EMIT accountTypeChanged(); + } + + if (d->locked != isLocked()) { + d->locked = isLocked(); + Q_EMIT lockedChanged(); + } + + if (d->automaticLogin != automaticLogin()) { + d->automaticLogin = automaticLogin(); + Q_EMIT automaticLoginChanged(); + } + + if (d->passwordMode != passwordMode()) { + d->passwordMode = passwordMode(); + Q_EMIT passwordModeChanged(); + } + + if (d->userName != userName()) { + d->userName = userName(); + Q_EMIT userNameChanged(); + Q_EMIT displayNameChanged(); + } + + if (d->realName != realName()) { + d->realName = realName(); + Q_EMIT realNameChanged(); + Q_EMIT displayNameChanged(); + } + + if (d->homeDirectory != homeDirectory()) { + d->homeDirectory = homeDirectory(); + Q_EMIT homeDirectoryChanged(); + } + + if (d->shell != shell()) { + d->shell = shell(); + Q_EMIT shellChanged(); + } + + if (d->iconFileName != iconFileName()) { + d->iconFileName = iconFileName(); + Q_EMIT iconFileNameChanged(); + } + + if (d->email != email()) { + d->email = email(); + Q_EMIT emailChanged(); + } + + if (d->language != language()) { + d->language = language(); + Q_EMIT languageChanged(); + } + + if (d->location != location()) { + d->location = location(); + Q_EMIT locationChanged(); + } + + if (d->xsession != xsession()) { + d->xsession = xsession(); + Q_EMIT xsessionChanged(); + } +} +} + +#include "moc_useraccount.cpp" diff --git a/accounts/useraccount.h b/accounts/useraccount.h new file mode 100644 index 0000000..ab5175f --- /dev/null +++ b/accounts/useraccount.h @@ -0,0 +1,171 @@ +/**************************************************************************** + * + * Copyright (C) 2018 Pier Luigi Fiorini + * + * $BEGIN_LICENSE:LGPLv3+$ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * $END_LICENSE$ + ***************************************************************************/ + +#ifndef QTACCOUNTSSERVICE_USERACCOUNT_H +#define QTACCOUNTSSERVICE_USERACCOUNT_H + +#include +#include +#include + +namespace QtAccountsService { + +class AccountsManager; +class AccountsManagerPrivate; +class UserAccountPrivate; + +class UserAccount : public QObject +{ + Q_OBJECT + Q_PROPERTY(qlonglong userId READ userId WRITE setUserId NOTIFY userIdChanged) + Q_PROPERTY(qlonglong groupId READ groupId NOTIFY groupIdChanged) + Q_PROPERTY( + AccountType accountType READ accountType WRITE setAccountType NOTIFY accountTypeChanged) + Q_PROPERTY(bool locked READ isLocked WRITE setLocked NOTIFY lockedChanged) + Q_PROPERTY(bool automaticLogin READ automaticLogin WRITE setAutomaticLogin NOTIFY + automaticLoginChanged) + Q_PROPERTY(qlonglong loginFrequency READ loginFrequency CONSTANT) + Q_PROPERTY(qlonglong loginTime READ loginTime CONSTANT) + Q_PROPERTY(PasswordMode passwordMode READ passwordMode WRITE setPasswordMode NOTIFY + passwordModeChanged) + Q_PROPERTY(QString passwordHint READ passwordHint CONSTANT) + Q_PROPERTY(bool localAccount READ isLocalAccount CONSTANT) + Q_PROPERTY(bool systemAccount READ isSystemAccount CONSTANT) + Q_PROPERTY(QString userName READ userName WRITE setUserName NOTIFY userNameChanged) + Q_PROPERTY(QString realName READ realName WRITE setRealName NOTIFY realNameChanged) + Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged) + Q_PROPERTY( + QString homeDirectory READ homeDirectory WRITE setHomeDirectory NOTIFY homeDirectoryChanged) + Q_PROPERTY(QString shell READ shell WRITE setShell NOTIFY shellChanged) + Q_PROPERTY( + QString iconFileName READ iconFileName WRITE setIconFileName NOTIFY iconFileNameChanged) + Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged) + Q_PROPERTY(QString language READ language WRITE setLanguage NOTIFY languageChanged) + Q_PROPERTY(QString location READ location WRITE setLocation NOTIFY locationChanged) + Q_PROPERTY(QString xsession READ xsession WRITE setXSession NOTIFY xsessionChanged) + Q_DECLARE_PRIVATE(UserAccount) +public: + enum AccountType { StandardAccountType = 0, AdministratorAccountType }; + Q_ENUM(AccountType) + + enum PasswordMode { RegularPasswordMode = 0, SetAtLoginPasswordMode, NonePasswordMode }; + Q_ENUM(PasswordMode) + + UserAccount(const QDBusConnection &bus = QDBusConnection::systemBus(), + QObject *parent = nullptr); + + qlonglong userId() const; + void setUserId(qlonglong uid); + + qlonglong groupId() const; + + AccountType accountType() const; + void setAccountType(AccountType type); + + bool isLocked() const; + void setLocked(bool locked); + + bool automaticLogin() const; + void setAutomaticLogin(bool automaticLogin); + + qlonglong loginFrequency() const; + + qlonglong loginTime() const; + + PasswordMode passwordMode() const; + void setPasswordMode(PasswordMode mode); + + QString passwordHint() const; + + bool isLocalAccount() const; + + bool isSystemAccount() const; + + QString userName() const; + void setUserName(const QString &userName); + + QString realName() const; + void setRealName(const QString &realName); + + QString displayName() const; + + QString homeDirectory() const; + void setHomeDirectory(const QString &homeDirectory); + + QString shell() const; + void setShell(const QString &shell); + + QString iconFileName() const; + void setIconFileName(const QString &fileName); + + QString email() const; + void setEmail(const QString &email); + + QString language() const; + void setLanguage(const QString &language); + + QString location() const; + void setLocation(const QString &location); + + QString xsession() const; + void setXSession(const QString &session); + + Q_INVOKABLE void setPassword(const QString &password, const QString &hint = QString()); + Q_INVOKABLE void setPasswordHint(const QString &hint); + +Q_SIGNALS: + void accountChanged(); + void userIdChanged(); + void groupIdChanged(); + void accountTypeChanged(); + void lockedChanged(); + void automaticLoginChanged(); + void passwordModeChanged(); + void userNameChanged(); + void realNameChanged(); + void displayNameChanged(); + void homeDirectoryChanged(); + void shellChanged(); + void iconFileNameChanged(); + void emailChanged(); + void languageChanged(); + void locationChanged(); + void xsessionChanged(); + +private: + friend class AccountsManager; + friend class AccountsManagerPrivate; + + UserAccountPrivate *const d_ptr; + + UserAccount(const QString &objectPath, + const QDBusConnection &bus = QDBusConnection::systemBus(), + QObject *parent = nullptr); + +private Q_SLOTS: + void handleAccountChanged(); +}; + +typedef QList UserAccountList; +} + +#endif // QTACCOUNTSSERVICE_USERACCOUNT_H diff --git a/accounts/useraccount_p.h b/accounts/useraccount_p.h new file mode 100644 index 0000000..e072262 --- /dev/null +++ b/accounts/useraccount_p.h @@ -0,0 +1,74 @@ +/**************************************************************************** + * + * Copyright (C) 2018 Pier Luigi Fiorini + * + * $BEGIN_LICENSE:LGPLv3+$ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * $END_LICENSE$ + ***************************************************************************/ + +#ifndef QTACCOUNTSSERVICE_USERACCOUNT_P_H +#define QTACCOUNTSSERVICE_USERACCOUNT_P_H + +#include "useraccount.h" +#include "user_interface.h" + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt AccountsService API. It exists +// purely as an implementation detail. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +namespace QtAccountsService { + +class UserAccountPrivate +{ + Q_DECLARE_PUBLIC(UserAccount) + Q_DISABLE_COPY(UserAccountPrivate) +public: + explicit UserAccountPrivate(UserAccount *q); + + void initialize(const QDBusConnection &connection, const QString &objectPath); + void emitSignals(); + + QDBusConnection bus; + OrgFreedesktopAccountsUserInterface *user; + + UserAccount::AccountType accountType; + bool locked; + bool automaticLogin; + UserAccount::PasswordMode passwordMode; + QString userName; + QString realName; + QString homeDirectory; + QString shell; + QString iconFileName; + QString email; + QString language; + QString location; + QString xsession; + +protected: + UserAccount *q_ptr; +}; +} + +#endif // QTACCOUNTSSERVICE_USERACCOUNT_P_H diff --git a/accounts/usersmodel.cpp b/accounts/usersmodel.cpp new file mode 100644 index 0000000..6713bda --- /dev/null +++ b/accounts/usersmodel.cpp @@ -0,0 +1,237 @@ +/**************************************************************************** + * + * Copyright (C) 2018 Pier Luigi Fiorini + * + * $BEGIN_LICENSE:LGPLv3+$ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * $END_LICENSE$ + ***************************************************************************/ + +#include + +#include "usersmodel.h" +#include "usersmodel_p.h" + +namespace QtAccountsService { + +/* + * UsersModelPrivate + */ + +UsersModelPrivate::UsersModelPrivate(UsersModel *q) + : manager(new AccountsManager()) + , q_ptr(q) +{ +} + +UsersModelPrivate::~UsersModelPrivate() +{ + delete manager; +} + +void UsersModelPrivate::_q_userAdded(UserAccount *account) +{ + Q_Q(UsersModel); + + q->connect(account, &UserAccount::accountChanged, q, [account, q, this]() { + auto index = q->index(list.indexOf(account)); + if (index.isValid()) + Q_EMIT q->dataChanged(index, index); + }); + + q->beginInsertRows(QModelIndex(), list.size(), list.size()); + list.append(account); + q->endInsertRows(); +} + +void UsersModelPrivate::_q_userDeleted(qlonglong uid) +{ + Q_Q(UsersModel); + + for (int i = 0; i < list.size(); i++) { + UserAccount *curAccount = list.at(i); + + if (curAccount->userId() == uid) { + q->beginRemoveRows(QModelIndex(), i, i); + list.removeOne(curAccount); + q->endRemoveRows(); + break; + } + } +} + +/* + * UsersModel + */ + +UsersModel::UsersModel(QObject *parent) + : QAbstractListModel(parent) + , d_ptr(new UsersModelPrivate(this)) +{ + Q_D(UsersModel); + + connect(d->manager, SIGNAL(userAdded(UserAccount *)), // clazy:exclude=old-style-connect + this, SLOT(_q_userAdded(UserAccount *))); + connect(d->manager, SIGNAL(userDeleted(qlonglong)), // clazy:exclude=old-style-connect + this, SLOT(_q_userDeleted(qlonglong))); + + connect(d->manager, &AccountsManager::listCachedUsersFinished, + this, [d](const UserAccountList &list) { + for (auto account : list) + d->_q_userAdded(account); + }); + d->manager->listCachedUsers(); +} + +QHash UsersModel::roleNames() const +{ + QHash roles = QAbstractItemModel::roleNames(); + roles[UserAccountRole] = "userAccount"; + roles[UserIdRole] = "userId"; + roles[AccountTypeRole] = "accountType"; + roles[LockedRole] = "locked"; + roles[AutomaticLoginRole] = "automaticLogin"; + roles[LoginFrequencyRole] = "loginFrequency"; + roles[LoginTimeRole] = "loginTime"; + roles[PasswordModeRole] = "passwordMode"; + roles[PasswordHintRole] = "passwordHint"; + roles[LocalAccount] = "localAccount"; + roles[SystemAccount] = "systemAccount"; + roles[UserNameRole] = "userName"; + roles[RealNameRole] = "realName"; + roles[DisplayNameRole] = "displayNameRole"; + roles[HomeDirectoryRole] = "homeDirectory"; + roles[ShellRole] = "shell"; + roles[IconFileNameRole] = "iconFileName"; + roles[LanguageRole] = "language"; + roles[EmailRole] = "email"; + roles[LocationRole] = "location"; + roles[XSessionRole] = "xsession"; + return roles; +} + +int UsersModel::rowCount(const QModelIndex &parent) const +{ + Q_D(const UsersModel); + + if (parent == QModelIndex()) + return d->list.size(); + + return 0; +} + +QVariant UsersModel::data(const QModelIndex &index, int role) const +{ + UserAccount *user = userAccount(index); + if (!user) + return QVariant(); + + switch (role) { + case Qt::DisplayRole: + case DisplayNameRole: + return user->displayName(); + case Qt::DecorationRole: + return QPixmap(user->iconFileName()); + case UserAccountRole: + return QVariant::fromValue(user); + case UserIdRole: + return user->userId(); + case AccountTypeRole: + return user->accountType(); + case LockedRole: + return user->isLocked(); + case AutomaticLoginRole: + return user->automaticLogin(); + case LoginFrequencyRole: + return user->loginFrequency(); + case LoginTimeRole: + return user->loginTime(); + case PasswordModeRole: + return user->passwordMode(); + case PasswordHintRole: + return user->passwordHint(); + case LocalAccount: + return user->isLocalAccount(); + case SystemAccount: + return user->isSystemAccount(); + case UserNameRole: + return user->userName(); + case RealNameRole: + return user->realName(); + case HomeDirectoryRole: + return user->homeDirectory(); + case ShellRole: + return user->shell(); + case IconFileNameRole: + return user->iconFileName(); + case EmailRole: + return user->email(); + case LanguageRole: + return user->language(); + case LocationRole: + return user->location(); + case XSessionRole: + return user->xsession(); + default: + break; + } + + return QVariant(); +} + +bool UsersModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + UserAccount *user = userAccount(index); + if (!user) + return false; + + switch (role) { + case UsersModel::UserNameRole: + user->setUserName(value.toString()); + break; + case UsersModel::RealNameRole: + user->setRealName(value.toString()); + break; + case UsersModel::IconFileNameRole: + user->setIconFileName(value.toString()); + break; + case UsersModel::AccountTypeRole: + user->setAccountType((UserAccount::AccountType)value.toInt()); + break; + case UsersModel::LanguageRole: + user->setLanguage(value.toString()); + break; + default: + return false; + } + + return true; +} + +UserAccount *UsersModel::userAccount(const QModelIndex &index) const +{ + Q_D(const UsersModel); + + if (!index.isValid()) + return nullptr; + if (index.row() >= d->list.size()) + return nullptr; + + return d->list[index.row()]; +} +} + +#include "moc_usersmodel.cpp" diff --git a/accounts/usersmodel.h b/accounts/usersmodel.h new file mode 100644 index 0000000..cc51f42 --- /dev/null +++ b/accounts/usersmodel.h @@ -0,0 +1,82 @@ +/**************************************************************************** + * + * Copyright (C) 2018 Pier Luigi Fiorini + * + * $BEGIN_LICENSE:LGPLv3+$ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * $END_LICENSE$ + ***************************************************************************/ + +#ifndef QTACCOUNTSSERVICE_USERSMODEL_H +#define QTACCOUNTSSERVICE_USERSMODEL_H + +#include + +namespace QtAccountsService { + +class UserAccount; +class UsersModelPrivate; + +class UsersModel : public QAbstractListModel +{ + Q_OBJECT + Q_DECLARE_PRIVATE(UsersModel) +public: + enum Roles { + UserAccountRole = Qt::UserRole + 1, + UserIdRole, + AccountTypeRole, + LockedRole, + AutomaticLoginRole, + LoginFrequencyRole, + LoginTimeRole, + PasswordModeRole, + PasswordHintRole, + LocalAccount, + SystemAccount, + UserNameRole, + RealNameRole, + DisplayNameRole, + HomeDirectoryRole, + ShellRole, + IconFileNameRole, + EmailRole, + LanguageRole, + LocationRole, + XSessionRole + }; + Q_ENUM(Roles) + + explicit UsersModel(QObject *parent = nullptr); + + QHash roleNames() const; + + int rowCount(const QModelIndex &parent) const; + + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, int role); + + UserAccount *userAccount(const QModelIndex &index) const; + +private: + UsersModelPrivate *const d_ptr; + + Q_PRIVATE_SLOT(d_func(), void _q_userAdded(UserAccount *account)) + Q_PRIVATE_SLOT(d_func(), void _q_userDeleted(qlonglong uid)) +}; +} + +#endif // QTACCOUNTSSERVICE_USERSMODEL_H diff --git a/accounts/usersmodel_p.h b/accounts/usersmodel_p.h new file mode 100644 index 0000000..06e355f --- /dev/null +++ b/accounts/usersmodel_p.h @@ -0,0 +1,65 @@ +/**************************************************************************** + * + * Copyright (C) 2018 Pier Luigi Fiorini + * + * $BEGIN_LICENSE:LGPLv3+$ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * $END_LICENSE$ + ***************************************************************************/ + +#ifndef QTACCOUNTSSERVICE_USERSMODEL_P_H +#define QTACCOUNTSSERVICE_USERSMODEL_P_H + +#include "accountsmanager.h" +#include "useraccount.h" + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt AccountsService API. It exists +// purely as an implementation detail. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +namespace QtAccountsService { + +class UsersModel; + +class UsersModelPrivate +{ + Q_DECLARE_PUBLIC(UsersModel) +public: + UsersModelPrivate(UsersModel *q); + ~UsersModelPrivate(); + + void populate(); + + AccountsManager *manager; + UserAccountList list; + +public Q_SLOTS: + void _q_userAdded(UserAccount *account); + void _q_userDeleted(qlonglong uid); + +protected: + UsersModel *q_ptr; +}; +} + +#endif // QTACCOUNTSSERVICE_USERSMODEL_P_H diff --git a/mpris/CMakeLists.txt b/mpris/CMakeLists.txt new file mode 100644 index 0000000..272f4e9 --- /dev/null +++ b/mpris/CMakeLists.txt @@ -0,0 +1,35 @@ +set(NETWORKMGR_SRCS + mpris.cpp + mpris.h + mpriscontroller_p.h + mpriscontroller.cpp + mpriscontroller.h + mprismanager.cpp + mprismanager.h + mprisplayer.cpp + mprisplayer.h + mprisplayeradaptor.cpp + mprisplayerinterface.cpp + mprisqt.h + mprisrootadaptor.cpp + mprisrootinterface.cpp + qmlplugins.cpp + + qtdbusextended/dbusextended.h + qtdbusextended/dbusextendedabstractinterface.cpp + qtdbusextended/dbusextendedabstractinterface.h + qtdbusextended/dbusextendedpendingcallwatcher_p.h + qtdbusextended/dbusextendedpendingcallwatcher.cpp +) + +add_library(cutefishmpris_qmlplugins SHARED ${NETWORKMGR_SRCS}) + +target_link_libraries (cutefishmpris_qmlplugins + Qt5::Core + Qt5::Quick + Qt5::Gui + Qt5::DBus +) + +install(TARGETS cutefishmpris_qmlplugins DESTINATION ${INSTALL_QMLDIR}/Cutefish/Mpris) +install(FILES qmldir DESTINATION ${INSTALL_QMLDIR}/Cutefish/Mpris) \ No newline at end of file diff --git a/mpris/mpris.cpp b/mpris/mpris.cpp new file mode 100644 index 0000000..81e1394 --- /dev/null +++ b/mpris/mpris.cpp @@ -0,0 +1,112 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "mpris.h" + +#include +#include + +static const char *playbackStatusStrings[] = { "Playing", "Paused", "Stopped" }; +static const char *loopStatusStrings[] = { "None", "Track", "Playlist" }; +static const char *metadataStrings[] = { "mpris:trackid", "mpris:length", "mpris:artUrl", "xesam:album", "xesam:albumArtist", "xesam:artist", "xesam:asText", "xesam:audioBPM", "xesam:autoRating", "xesam:comment", "xesam:composer", "xesam:contentCreated", "xesam:discNumber", "xesam:firstUsed", "xesam:genre", "xesam:lastUsed", "xesam:lyricist", "xesam:title", "xesam:trackNumber", "xesam:url", "xesam:useCount", "xesam:userRating" }; + + +Mpris::Mpris(QObject *parent) + : QObject(parent) +{ +} + +Mpris::~Mpris() +{ +} + +QObject *Mpris::api_factory(QQmlEngine *, QJSEngine *) +{ + return new Mpris; +} + +QString Mpris::metadataToString(Mpris::Metadata metadata) +{ + return enumerationToString(metadata); +} + + +// Private + +template void Mpris::getEnumStringsAndSize(const char ***strings, int *size) +{ + if (is_same::value) { + *strings = loopStatusStrings; + *size = arraySize(loopStatusStrings); + } else if (is_same::value) { + *strings = playbackStatusStrings; + *size = arraySize(playbackStatusStrings); + } else if (is_same::value) { + *strings = metadataStrings; + *size = arraySize(metadataStrings); + } else { + *strings = 0; + *size = 0; + } +} +template void Mpris::getEnumStringsAndSize(const char ***strings, int *size); +template void Mpris::getEnumStringsAndSize(const char ***strings, int *size); +template void Mpris::getEnumStringsAndSize(const char ***strings, int *size); + + +template QString Mpris::enumerationToString(T enumeration) +{ + const char **strings; + int size; + + getEnumStringsAndSize(&strings, &size); + + return enumeration >= 0 && enumeration < size + ? QString(QLatin1String(strings[enumeration])) + : QString(); +} +template QString Mpris::enumerationToString(Mpris::LoopStatus enumeration); +template QString Mpris::enumerationToString(Mpris::PlaybackStatus enumeration); +template QString Mpris::enumerationToString(Mpris::Metadata enumeration); + + +template T Mpris::enumerationFromString(const QString &string) +{ + const char **strings; + int size; + + getEnumStringsAndSize(&strings, &size); + + for (int i = 0; i < size; ++i) { + if (string == QLatin1String(strings[i])) { + return T(i); + } + } + + return T(-1); +} +template Mpris::LoopStatus Mpris::enumerationFromString(const QString &string); +template Mpris::PlaybackStatus Mpris::enumerationFromString(const QString &string); +template Mpris::Metadata Mpris::enumerationFromString(const QString &string); diff --git a/mpris/mpris.h b/mpris/mpris.h new file mode 100644 index 0000000..ce2a472 --- /dev/null +++ b/mpris/mpris.h @@ -0,0 +1,106 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef MPRIS_H +#define MPRIS_H + +#include "mprisqt.h" + +#include +#include + +class QQmlEngine; +class QJSEngine; + +class MPRIS_QT_EXPORT Mpris : public QObject +{ + Q_OBJECT + + Q_ENUMS(PlaybackStatus LoopStatus Metadata) + +public: + + enum PlaybackStatus { + InvalidPlaybackStatus = -1, + Playing, + Paused, + Stopped + }; + + enum LoopStatus { + InvalidLoopStatus = -1, + None, + Track, + Playlist + }; + + enum Metadata { + InvalidMetadata = -1, + TrackId, + Length, + ArtUrl, + Album, + AlbumArtist, + Artist, + AsText, + AudioBPM, + AutoRating, + Comment, + Composer, + ContentCreated, + DiscNumber, + FirstUsed, + Genre, + LastUsed, + Lyricist, + Title, + TrackNumber, + Url, + UseCount, + UserRating + }; + + Mpris(QObject *parent = 0); + ~Mpris(); + + static QObject *api_factory(QQmlEngine *, QJSEngine *); + + Q_INVOKABLE static QString metadataToString(Metadata metadata); + +private: + template static int arraySize(T (&)[N]) { return N; }; + template struct is_same { static const bool value = false; }; + template struct is_same { static const bool value = true; }; + template static void getEnumStringsAndSize(const char ***strings, int *size); + template static QString enumerationToString(T enumeration); + template static T enumerationFromString(const QString &string); + + friend class MprisRootAdaptor; + friend class MprisPlayerAdaptor; + friend class MprisPlayer; + friend class MprisController; +}; + +#endif /* MPRIS_H */ diff --git a/mpris/mpriscontroller.cpp b/mpris/mpriscontroller.cpp new file mode 100644 index 0000000..ed16678 --- /dev/null +++ b/mpris/mpriscontroller.cpp @@ -0,0 +1,691 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "mpriscontroller.h" + +#include "mpriscontroller_p.h" + +#include +#include +#include + +static const QString mprisObjectPath = QStringLiteral("/org/mpris/MediaPlayer2"); + +MprisController::MprisController(const QString &service, const QDBusConnection &connection, QObject *parent) + : QObject(parent) + , m_mprisRootInterface(new MprisRootInterface(service, mprisObjectPath, connection, this)) + , m_mprisPlayerInterface(new MprisPlayerInterface(service, mprisObjectPath, connection, this)) + , m_initedRootInterface(false) + , m_initedPlayerInterface(false) + , m_requestedPosition(false) + , m_canControlReceived(false) +{ + // Mpris Root Interface + connect(m_mprisRootInterface, &MprisRootInterface::asyncGetAllPropertiesFinished, this, &MprisController::onAsyncGetAllRootPropertiesFinished); + connect(m_mprisRootInterface, &MprisRootInterface::canQuitChanged, this, &MprisController::canQuitChanged); + connect(m_mprisRootInterface, &MprisRootInterface::canRaiseChanged, this, &MprisController::canRaiseChanged); + connect(m_mprisRootInterface, &MprisRootInterface::canSetFullscreenChanged, this, &MprisController::canSetFullscreenChanged); + connect(m_mprisRootInterface, &MprisRootInterface::desktopEntryChanged, this, &MprisController::desktopEntryChanged); + connect(m_mprisRootInterface, &MprisRootInterface::fullscreenChanged, this, &MprisController::fullscreenChanged); + connect(m_mprisRootInterface, &MprisRootInterface::hasTrackListChanged, this, &MprisController::hasTrackListChanged); + connect(m_mprisRootInterface, &MprisRootInterface::identityChanged, this, &MprisController::identityChanged); + connect(m_mprisRootInterface, &MprisRootInterface::supportedMimeTypesChanged, this, &MprisController::supportedMimeTypesChanged); + connect(m_mprisRootInterface, &MprisRootInterface::supportedUriSchemesChanged, this, &MprisController::supportedUriSchemesChanged); + m_mprisRootInterface->setUseCache(true); + + // Mpris Player Interface + connect(m_mprisPlayerInterface, &MprisPlayerInterface::asyncGetAllPropertiesFinished, this, &MprisController::onAsyncGetAllPlayerPropertiesFinished); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::canControlChanged, this, &MprisController::onCanControlChanged); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::canGoNextChanged, this, &MprisController::canGoNextChanged); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::canGoPreviousChanged, this, &MprisController::canGoPreviousChanged); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::canPauseChanged, this, &MprisController::canPauseChanged); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::canPlayChanged, this, &MprisController::canPlayChanged); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::canSeekChanged, this, &MprisController::canSeekChanged); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::canSeekChanged, this, &MprisController::canSeekChanged); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::loopStatusChanged, this, &MprisController::loopStatusChanged); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::maximumRateChanged, this, &MprisController::maximumRateChanged); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::metadataChanged, this, &MprisController::metadataChanged); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::minimumRateChanged, this, &MprisController::minimumRateChanged); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::playbackStatusChanged, this, &MprisController::playbackStatusChanged); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::positionChanged, this, &MprisController::onPositionChanged); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::rateChanged, this, &MprisController::rateChanged); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::shuffleChanged, this, &MprisController::shuffleChanged); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::volumeChanged, this, &MprisController::volumeChanged); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::seeked, this, &MprisController::seeked); + m_mprisPlayerInterface->setUseCache(true); + + // This will initialize the properties, if needed + isValid(); +} + +MprisController::~MprisController() +{ +} + +bool MprisController::isValid() const +{ + if (!m_mprisRootInterface->isValid() || !m_mprisPlayerInterface->isValid()) { + m_initedRootInterface = false; + m_initedPlayerInterface = false; + return false; + } + + if (!m_initedRootInterface) { + m_mprisRootInterface->getAllProperties(); + if (m_mprisRootInterface->lastExtendedError().isValid()) { + qWarning() << Q_FUNC_INFO + << "Error" << m_mprisRootInterface->lastExtendedError().name() + << "happened:" << m_mprisRootInterface->lastExtendedError().message(); + } + } + + if (!m_initedPlayerInterface) { + m_mprisPlayerInterface->getAllProperties(); + if (m_mprisPlayerInterface->lastExtendedError().isValid()) { + qWarning() << Q_FUNC_INFO + << "Error" << m_mprisPlayerInterface->lastExtendedError().name() + << "happened:" << m_mprisPlayerInterface->lastExtendedError().message(); + } + } + + return m_initedRootInterface && m_initedPlayerInterface; +} + +// Mpris2 Root Interface +bool MprisController::quit() +{ + if (!canQuit()) { + qDebug() << Q_FUNC_INFO << "The method is not allowed"; + return false; + } + + QDBusPendingReply<> reply = m_mprisRootInterface->Quit(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(onFinishedPendingCall(QDBusPendingCallWatcher*))); + + return true; +} + +bool MprisController::raise() +{ + if (!canRaise()) { + qDebug() << Q_FUNC_INFO << "The method is not allowed"; + return false; + } + + QDBusPendingReply<> reply = m_mprisRootInterface->Raise(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(onFinishedPendingCall(QDBusPendingCallWatcher*))); + + return true; +} + +// Mpris2 Player Interface +bool MprisController::next() +{ + if (!canGoNext()) { + qDebug() << Q_FUNC_INFO << "The method is not allowed"; + return false; + } + + QDBusPendingReply<> reply = m_mprisPlayerInterface->Next(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(onFinishedPendingCall(QDBusPendingCallWatcher*))); + + return true; +} + +bool MprisController::openUri(const QUrl &uri) +{ + if (!canControl()) { + qDebug() << Q_FUNC_INFO << "The method is not allowed"; + return false; + } + + if (!uri.isValid()) { + qDebug() << Q_FUNC_INFO << "The uri is invalid"; + return false; + } + + if (!m_mprisRootInterface->supportedUriSchemes().contains(uri.scheme())) { + qDebug() << Q_FUNC_INFO << "The scheme is not supported"; + return false; + } + + QMimeDatabase db; + QMimeType mime; + if (uri.isLocalFile()) { + mime = db.mimeTypeForFile(uri.toLocalFile()); + } else { + mime = db.mimeTypeForFile(uri.fileName(), QMimeDatabase::MatchExtension); + } + QStringList mimeNames = mime.aliases(); + mimeNames.prepend(mime.name()); + for (int i = 0; i < mimeNames.size(); i++) { + if (m_mprisRootInterface->supportedMimeTypes().contains(mimeNames[i])) { + QDBusPendingReply<> reply = m_mprisPlayerInterface->OpenUri(uri.toString()); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(onFinishedPendingCall(QDBusPendingCallWatcher*))); + return true; + } + } + + qDebug() << Q_FUNC_INFO << "The mime type is not supported"; + return false; +} + +bool MprisController::pause() +{ + if (!canPause()) { + qDebug() << Q_FUNC_INFO << "The method is not allowed"; + return false; + } + + QDBusPendingReply<> reply = m_mprisPlayerInterface->Pause(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(onFinishedPendingCall(QDBusPendingCallWatcher*))); + + return true; +} + +bool MprisController::play() +{ + if (!canPlay()) { + qDebug() << Q_FUNC_INFO << "The method is not allowed"; + return false; + } + + QDBusPendingReply<> reply = m_mprisPlayerInterface->Play(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(onFinishedPendingCall(QDBusPendingCallWatcher*))); + + return true; +} + +bool MprisController::playPause() +{ + if (!canPause()) { + qDebug() << Q_FUNC_INFO << "The method is not allowed"; + return false; + } + + QDBusPendingReply<> reply = m_mprisPlayerInterface->PlayPause(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(onFinishedPendingCall(QDBusPendingCallWatcher*))); + + return true; +} + +bool MprisController::previous() +{ + if (!canGoPrevious()) { + qDebug() << Q_FUNC_INFO << "The method is not allowed"; + return false; + } + + QDBusPendingReply<> reply = m_mprisPlayerInterface->Previous(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(onFinishedPendingCall(QDBusPendingCallWatcher*))); + + return true; +} + +bool MprisController::seek(qlonglong offset) +{ + if (!canSeek()) { + qDebug() << Q_FUNC_INFO << "The method is not allowed"; + return false; + } + + QDBusPendingReply<> reply = m_mprisPlayerInterface->Seek(offset); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(onFinishedPendingCall(QDBusPendingCallWatcher*))); + + return true; +} + +bool MprisController::setPosition(qlonglong position) +{ + if (!canSeek()) { + qDebug() << Q_FUNC_INFO << "The method is not allowed"; + return false; + } + + QVariant trackId = m_mprisPlayerInterface->metadata()[Mpris::metadataToString(Mpris::TrackId)]; + if (!trackId.isValid()) { + qDebug() << Q_FUNC_INFO << "Unknown trackId in which to set the position"; + return false; + } + + return setPosition(trackId.value().path(), position); +} + +bool MprisController::setPosition(const QString &aTrackId, qlonglong position) +{ + if (!canSeek()) { + qDebug() << Q_FUNC_INFO << "The method is not allowed"; + return false; + } + + QDBusObjectPath trackId(aTrackId); + if (trackId.path().isEmpty()) { + qDebug() << Q_FUNC_INFO << "trackId doesn't map to a valid DBus object path"; + return false; + } + + QVariant length = m_mprisPlayerInterface->metadata()[Mpris::metadataToString(Mpris::Length)]; + if (!length.isValid()) { + qlonglong reportedLength = length.toLongLong(); + + if (position < 0 || position > reportedLength) { + qDebug() << Q_FUNC_INFO << "Unknown trackId in which to set the position"; + return false; + } + } + + QDBusPendingReply<> reply = m_mprisPlayerInterface->SetPosition(trackId, position); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(onFinishedPendingCall(QDBusPendingCallWatcher*))); + + return true; +} + +bool MprisController::stop() +{ + if (!canControl()) { + qDebug() << Q_FUNC_INFO << "The method is not allowed"; + return false; + } + + QDBusPendingReply<> reply = m_mprisPlayerInterface->Stop(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + this, SLOT(onFinishedPendingCall(QDBusPendingCallWatcher*))); + + return true; +} + + +// Slots + +QString MprisController::service() const +{ + return m_mprisRootInterface->service(); +} + +// Mpris2 Root Interface +bool MprisController::canQuit() const +{ + if (isValid()) { + return m_mprisRootInterface->canQuit(); + } + + return false; +} + +bool MprisController::canRaise() const +{ + if (isValid()) { + return m_mprisRootInterface->canRaise(); + } + + return false; +} + +bool MprisController::canSetFullscreen() const +{ + if (isValid()) { + return m_mprisRootInterface->canSetFullscreen(); + } + + return false; +} + +QString MprisController::desktopEntry() const +{ + if (isValid()) { + return m_mprisRootInterface->desktopEntry(); + } + + return QString(); +} + +bool MprisController::fullscreen() const +{ + if (isValid()) { + return m_mprisRootInterface->fullscreen(); + } + + return false; +} + +void MprisController::setFullscreen(bool fullscreen) +{ + if (isValid()) { + m_mprisRootInterface->setFullscreen(fullscreen); + } +} + +bool MprisController::hasTrackList() const +{ + if (isValid()) { + return m_mprisRootInterface->hasTrackList(); + } + + return false; +} + +QString MprisController::identity() const +{ + if (isValid()) { + return m_mprisRootInterface->identity(); + } + + return QString(); +} + +QStringList MprisController::supportedUriSchemes() const +{ + if (isValid()) { + return m_mprisRootInterface->supportedUriSchemes(); + } + + return QStringList(); +} + +QStringList MprisController::supportedMimeTypes() const +{ + if (isValid()) { + return m_mprisRootInterface->supportedMimeTypes(); + } + + return QStringList(); +} + +// Mpris2 Player Interface +bool MprisController::canControl() const +{ + if (isValid()) { + return m_mprisPlayerInterface->canControl(); + } + + return false; +} + +bool MprisController::canGoNext() const +{ + if (isValid() && canControl()) { + return m_mprisPlayerInterface->canGoNext(); + } + + return false; +} + +bool MprisController::canGoPrevious() const +{ + if (isValid() && canControl()) { + return m_mprisPlayerInterface->canGoPrevious(); + } + + return false; +} + +bool MprisController::canPause() const +{ + if (isValid() && canControl()) { + return m_mprisPlayerInterface->canPause(); + } + + return false; +} + +bool MprisController::canPlay() const +{ + if (isValid() && canControl()) { + return m_mprisPlayerInterface->canPlay(); + } + + return false; +} + +bool MprisController::canSeek() const +{ + if (isValid() && canControl()) { + return m_mprisPlayerInterface->canSeek(); + } + + return false; +} + +Mpris::LoopStatus MprisController::loopStatus() const +{ + if (isValid()) { + return Mpris::enumerationFromString(m_mprisPlayerInterface->loopStatus()); + } + + return Mpris::None; +} + +void MprisController::setLoopStatus(Mpris::LoopStatus loopStatus) +{ + if (isValid()) { + m_mprisPlayerInterface->setLoopStatus(Mpris::enumerationToString(loopStatus)); + } +} + +double MprisController::maximumRate() const +{ + if (isValid()) { + return m_mprisPlayerInterface->maximumRate(); + } + + return 1; +} + +QVariantMap MprisController::metadata() const +{ + if (isValid()) { + return m_mprisPlayerInterface->metadata(); + } + + return QVariantMap(); +} + +double MprisController::minimumRate() const +{ + if (isValid()) { + return m_mprisPlayerInterface->minimumRate(); + } + + return 1; +} + +Mpris::PlaybackStatus MprisController::playbackStatus() const +{ + if (isValid()) { + return Mpris::enumerationFromString(m_mprisPlayerInterface->playbackStatus()); + } + + return Mpris::Stopped; +} + +qlonglong MprisController::position() const +{ + if (isValid()) { + m_mprisPlayerInterface->setSync(true); + m_mprisPlayerInterface->setUseCache(false); + qlonglong result = m_mprisPlayerInterface->position(); + m_mprisPlayerInterface->setUseCache(true); + m_mprisPlayerInterface->setSync(false); + return result; + } + + return -1; +} + +void MprisController::requestPosition() const +{ + if (m_requestedPosition) { + return; + } + + if (isValid()) { + m_mprisPlayerInterface->setUseCache(false); + m_mprisPlayerInterface->position(); + m_mprisPlayerInterface->setUseCache(true); + if (m_mprisPlayerInterface->lastExtendedError().isValid()) { + qWarning() << Q_FUNC_INFO + << "Failed requesting the current position in the MPRIS2 Player Interface!!!"; + return; + } + m_requestedPosition = true; + } +} + +double MprisController::rate() const +{ + if (isValid()) { + return m_mprisPlayerInterface->rate(); + } + + return 1; +} + +void MprisController::setRate(double rate) +{ + if (isValid()) { + m_mprisPlayerInterface->setRate(rate); + } +} + +bool MprisController::shuffle() const +{ + if (isValid()) { + return m_mprisPlayerInterface->shuffle(); + } + + return false; +} + +void MprisController::setShuffle(bool shuffle) +{ + if (isValid()) { + m_mprisPlayerInterface->setShuffle(shuffle); + } +} + +double MprisController::volume() const +{ + if (isValid()) { + return m_mprisPlayerInterface->volume(); + } + + return 0; +} + +void MprisController::setVolume(double volume) +{ + if (isValid()) { + m_mprisPlayerInterface->setVolume(volume); + } +} + + +// Protected + +void MprisController::onAsyncGetAllRootPropertiesFinished() +{ + if (m_mprisRootInterface->lastExtendedError().isValid()) { + qWarning() << Q_FUNC_INFO + << "Error" << m_mprisRootInterface->lastExtendedError().name() + << "happened:" << m_mprisRootInterface->lastExtendedError().message(); + return; + } + + m_initedRootInterface = true; +} + +void MprisController::onAsyncGetAllPlayerPropertiesFinished() +{ + if (m_mprisPlayerInterface->lastExtendedError().isValid()) { + qWarning() << Q_FUNC_INFO + << "Error" << m_mprisPlayerInterface->lastExtendedError().name() + << "happened:" << m_mprisPlayerInterface->lastExtendedError().message(); + return; + } + + m_initedPlayerInterface = true; +} + +void MprisController::onCanControlChanged() +{ + // On first reception, we are using a "GetAll" so we can skip this + if (m_canControlReceived) { + // I could disconnect and re-connect the signals so I avoid + // double arriving signals but this really shouldn't happen + // ever. + Q_EMIT canGoNextChanged(); + Q_EMIT canGoPreviousChanged(); + Q_EMIT canPauseChanged(); + Q_EMIT canPlayChanged(); + Q_EMIT canSeekChanged(); + qWarning() << Q_FUNC_INFO + << "CanControl is not supposed to change its value!"; + return; + } + + m_canControlReceived = true; +} + +void MprisController::onPositionChanged(qlonglong aPosition) +{ + m_requestedPosition = false; + Q_EMIT positionChanged(aPosition); +} + + +// Private + +void MprisController::onFinishedPendingCall(QDBusPendingCallWatcher *call) +{ + QDBusPendingReply<> reply = *call; + if (reply.isError()) { + qWarning() << Q_FUNC_INFO + << "Error" << reply.error().name() + << "happened:" << reply.error().message(); + } + + call->deleteLater(); +} diff --git a/mpris/mpriscontroller.h b/mpris/mpriscontroller.h new file mode 100644 index 0000000..23d8254 --- /dev/null +++ b/mpris/mpriscontroller.h @@ -0,0 +1,212 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef MPRISCONTROLLER_H +#define MPRISCONTROLLER_H + +#include "mprisqt.h" +#include "mpris.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +class MprisRootInterface; +class MprisPlayerInterface; +class QDBusPendingCallWatcher; +class MPRIS_QT_EXPORT MprisController : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString service READ service) + + // Mpris2 Root Interface + Q_PROPERTY(bool canQuit READ canQuit NOTIFY canQuitChanged) + Q_PROPERTY(bool canRaise READ canRaise NOTIFY canRaiseChanged) + Q_PROPERTY(bool canSetFullscreen READ canSetFullscreen NOTIFY canSetFullscreenChanged) + Q_PROPERTY(QString desktopEntry READ desktopEntry NOTIFY desktopEntryChanged) + Q_PROPERTY(bool fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged) + Q_PROPERTY(bool hasTrackList READ hasTrackList NOTIFY hasTrackListChanged) + Q_PROPERTY(QString identity READ identity NOTIFY identityChanged) + Q_PROPERTY(QStringList supportedUriSchemes READ supportedUriSchemes NOTIFY supportedUriSchemesChanged) + Q_PROPERTY(QStringList supportedMimeTypes READ supportedMimeTypes NOTIFY supportedMimeTypesChanged) + + // Mpris2 Player Interface + Q_PROPERTY(bool canControl READ canControl NOTIFY canControlChanged) + Q_PROPERTY(bool canGoNext READ canGoNext NOTIFY canGoNextChanged) + Q_PROPERTY(bool canGoPrevious READ canGoPrevious NOTIFY canGoPreviousChanged) + Q_PROPERTY(bool canPause READ canPause NOTIFY canPauseChanged) + Q_PROPERTY(bool canPlay READ canPlay NOTIFY canPlayChanged) + Q_PROPERTY(bool canSeek READ canSeek NOTIFY canSeekChanged) + Q_PROPERTY(Mpris::LoopStatus loopStatus READ loopStatus WRITE setLoopStatus NOTIFY loopStatusChanged) + Q_PROPERTY(double maximumRate READ maximumRate NOTIFY maximumRateChanged) + Q_PROPERTY(QVariantMap metadata READ metadata NOTIFY metadataChanged) + Q_PROPERTY(double minimumRate READ minimumRate NOTIFY minimumRateChanged) + Q_PROPERTY(Mpris::PlaybackStatus playbackStatus READ playbackStatus NOTIFY playbackStatusChanged) + Q_PROPERTY(qlonglong position READ position) + Q_PROPERTY(double rate READ rate WRITE setRate NOTIFY rateChanged) + Q_PROPERTY(bool shuffle READ shuffle WRITE setShuffle NOTIFY shuffleChanged) + Q_PROPERTY(double volume READ volume WRITE setVolume NOTIFY volumeChanged) + +public: + + MprisController(const QString &service, const QDBusConnection &connection, QObject *parent = 0); + ~MprisController(); + + bool isValid() const; + + // Mpris2 Root Interface + bool quit(); + bool raise(); + + // Mpris2 Player Interface + bool next(); + bool openUri(const QUrl &uri); + bool pause(); + bool play(); + bool playPause(); + bool previous(); + bool seek(qlonglong offset); + bool setPosition(qlonglong position); + bool setPosition(const QString &aTrackId, qlonglong position); + bool stop(); + +public Q_SLOTS: + + QString service() const; + + // Mpris2 Root Interface + bool canQuit() const; + + bool canRaise() const; + + bool canSetFullscreen() const; + + QString desktopEntry() const; + + bool fullscreen() const; + void setFullscreen(bool fullscreen); + + bool hasTrackList() const; + + QString identity() const; + + QStringList supportedUriSchemes() const; + + QStringList supportedMimeTypes() const; + + // Mpris2 Player Interface + bool canControl() const; + + bool canGoNext() const; + + bool canGoPrevious() const; + + bool canPause() const; + + bool canPlay() const; + + bool canSeek() const; + + Mpris::LoopStatus loopStatus() const; + void setLoopStatus(Mpris::LoopStatus loopStatus); + + double maximumRate() const; + + QVariantMap metadata() const; + + double minimumRate() const; + + Mpris::PlaybackStatus playbackStatus() const; + + qlonglong position() const; + void requestPosition() const; + + double rate() const; + void setRate(double rate); + + bool shuffle() const; + void setShuffle(bool shuffle); + + double volume() const; + void setVolume(double volume); + +Q_SIGNALS: + + // Mpris2 Root Interface + void canQuitChanged(); + void canRaiseChanged(); + void canSetFullscreenChanged(); + void desktopEntryChanged(); + void fullscreenChanged(); + void hasTrackListChanged(); + void identityChanged(); + void supportedUriSchemesChanged(); + void supportedMimeTypesChanged(); + + // Mpris2 Player Interface + void canControlChanged(); + void canGoNextChanged(); + void canGoPreviousChanged(); + void canPauseChanged(); + void canPlayChanged(); + void canSeekChanged(); + void loopStatusChanged(); + void maximumRateChanged(); + void metadataChanged(); + void minimumRateChanged(); + void playbackStatusChanged(); + void positionChanged(qlonglong position); + void rateChanged(); + void shuffleChanged(); + void volumeChanged(); + void seeked(qlonglong position); + +protected Q_SLOTS: + void onAsyncGetAllRootPropertiesFinished(); + void onAsyncGetAllPlayerPropertiesFinished(); + void onCanControlChanged(); + void onPositionChanged(qlonglong aPosition); + void onFinishedPendingCall(QDBusPendingCallWatcher *call); + +private: + MprisRootInterface *m_mprisRootInterface; + MprisPlayerInterface *m_mprisPlayerInterface; + + mutable bool m_initedRootInterface; + mutable bool m_initedPlayerInterface; + mutable bool m_requestedPosition; + bool m_canControlReceived; +}; + +#endif /* MPRISCONTROLLER_H */ diff --git a/mpris/mpriscontroller_p.h b/mpris/mpriscontroller_p.h new file mode 100644 index 0000000..926c04a --- /dev/null +++ b/mpris/mpriscontroller_p.h @@ -0,0 +1,336 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +// +// W A R N I N G +// ------------- +// +// This file is not part of the public API. This header file may +// change from version to version without notice, or even be +// removed. +// +// We mean it. +// +// + + +#ifndef MPRISCONTROLLER_P_H +#define MPRISCONTROLLER_P_H + +#include "qtdbusextended/dbusextendedabstractinterface.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Proxy class for interface org.mpris.MediaPlayer2 + */ +class MprisRootInterface: public DBusExtendedAbstractInterface +{ + Q_OBJECT +public: + static inline const char *staticInterfaceName() + { return "org.mpris.MediaPlayer2"; } + +public: + MprisRootInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = 0); + + ~MprisRootInterface(); + + Q_PROPERTY(bool CanQuit READ canQuit NOTIFY canQuitChanged) + inline bool canQuit() + { return qvariant_cast< bool >(internalPropGet("CanQuit", &m_canQuit)); } + + Q_PROPERTY(bool CanRaise READ canRaise NOTIFY canRaiseChanged) + inline bool canRaise() + { return qvariant_cast< bool >(internalPropGet("CanRaise", &m_canRaise)); } + + Q_PROPERTY(bool CanSetFullscreen READ canSetFullscreen NOTIFY canSetFullscreenChanged) + inline bool canSetFullscreen() + { return qvariant_cast< bool >(internalPropGet("CanSetFullscreen", &m_canSetFullscreen)); } + + Q_PROPERTY(QString DesktopEntry READ desktopEntry NOTIFY desktopEntryChanged) + inline QString desktopEntry() + { return qvariant_cast< QString >(internalPropGet("DesktopEntry", &m_desktopEntry)); } + + Q_PROPERTY(bool Fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged) + inline bool fullscreen() + { return qvariant_cast< bool >(internalPropGet("Fullscreen", &m_fullscreen)); } + inline void setFullscreen(bool value) + { m_fullscreen = value; internalPropSet("Fullscreen", QVariant::fromValue(value), &m_fullscreen); } + + Q_PROPERTY(bool HasTrackList READ hasTrackList NOTIFY hasTrackListChanged) + inline bool hasTrackList() + { return qvariant_cast< bool >(internalPropGet("HasTrackList", &m_hasTrackList)); } + + Q_PROPERTY(QString Identity READ identity NOTIFY identityChanged) + inline QString identity() + { return qvariant_cast< QString >(internalPropGet("Identity", &m_identity)); } + + Q_PROPERTY(QStringList SupportedMimeTypes READ supportedMimeTypes NOTIFY supportedMimeTypesChanged) + inline QStringList supportedMimeTypes() + { return qvariant_cast< QStringList >(internalPropGet("SupportedMimeTypes", &m_supportedMimeTypes)); } + + Q_PROPERTY(QStringList SupportedUriSchemes READ supportedUriSchemes NOTIFY supportedUriSchemesChanged) + inline QStringList supportedUriSchemes() + { return qvariant_cast< QStringList >(internalPropGet("SupportedUriSchemes", &m_supportedUriSchemes)); } + +public Q_SLOTS: // METHODS + inline QDBusPendingReply<> Quit() + { + QList argumentList; + return asyncCallWithArgumentList(QLatin1String("Quit"), argumentList); + } + + inline QDBusPendingReply<> Raise() + { + QList argumentList; + return asyncCallWithArgumentList(QLatin1String("Raise"), argumentList); + } + +Q_SIGNALS: // SIGNALS + void canQuitChanged(bool canQuit); + void canRaiseChanged(bool canRaise); + void canSetFullscreenChanged(bool canSetFullscreen); + void desktopEntryChanged(const QString &desktopEntry); + void fullscreenChanged(bool fullscreen); + void hasTrackListChanged(bool hasTrackList); + void identityChanged(const QString &identity); + void supportedMimeTypesChanged(const QStringList &supportedMimeTypes); + void supportedUriSchemesChanged(const QStringList &supportedUriSchemes); + +private Q_SLOTS: + void onPropertyChanged(const QString &propertyName, const QVariant &value); + +private: + bool m_canQuit; + bool m_canRaise; + bool m_canSetFullscreen; + QString m_desktopEntry; + bool m_fullscreen; + bool m_hasTrackList; + QString m_identity; + QStringList m_supportedUriSchemes; + QStringList m_supportedMimeTypes; +}; + +namespace org { + namespace mpris { + typedef ::MprisRootInterface MediaPlayer2; + } +} + + +/* + * Proxy class for interface org.mpris.MediaPlayer2 + */ +class MprisPlayerInterface: public DBusExtendedAbstractInterface +{ + Q_OBJECT +public: + static inline const char *staticInterfaceName() + { return "org.mpris.MediaPlayer2.Player"; } + +public: + MprisPlayerInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = 0); + + ~MprisPlayerInterface(); + + Q_PROPERTY(bool CanControl READ canControl NOTIFY canControlChanged) + inline bool canControl() + { return qvariant_cast< bool >(internalPropGet("CanControl", &m_canControl)); } + + Q_PROPERTY(bool CanGoNext READ canGoNext NOTIFY canGoNextChanged) + inline bool canGoNext() + { return qvariant_cast< bool >(internalPropGet("CanGoNext", &m_canGoNext)); } + + Q_PROPERTY(bool CanGoPrevious READ canGoPrevious NOTIFY canGoPreviousChanged) + inline bool canGoPrevious() + { return qvariant_cast< bool >(internalPropGet("CanGoPrevious", &m_canGoPrevious)); } + + Q_PROPERTY(bool CanPause READ canPause NOTIFY canPauseChanged) + inline bool canPause() + { return qvariant_cast< bool >(internalPropGet("CanPause", &m_canPause)); } + + Q_PROPERTY(bool CanPlay READ canPlay NOTIFY canPlayChanged) + inline bool canPlay() + { return qvariant_cast< bool >(internalPropGet("CanPlay", &m_canPlay)); } + + Q_PROPERTY(bool CanSeek READ canSeek NOTIFY canSeekChanged) + inline bool canSeek() + { return qvariant_cast< bool >(internalPropGet("CanSeek", &m_canSeek)); } + + Q_PROPERTY(QString LoopStatus READ loopStatus WRITE setLoopStatus NOTIFY loopStatusChanged) + inline QString loopStatus() + { return qvariant_cast< QString >(internalPropGet("LoopStatus", &m_loopStatus)); } + inline void setLoopStatus(const QString &value) + { m_loopStatus = value; internalPropSet("LoopStatus", QVariant::fromValue(value), &m_loopStatus); } + + Q_PROPERTY(double MaximumRate READ maximumRate NOTIFY maximumRateChanged) + inline double maximumRate() + { return qvariant_cast< double >(internalPropGet("MaximumRate", &m_maximumRate)); } + + Q_PROPERTY(QVariantMap Metadata READ metadata NOTIFY metadataChanged) + inline QVariantMap metadata() + { return qvariant_cast< QVariantMap >(internalPropGet("Metadata", &m_metadata)); } + + Q_PROPERTY(double MinimumRate READ minimumRate NOTIFY minimumRateChanged) + inline double minimumRate() + { return qvariant_cast< double >(internalPropGet("MinimumRate", &m_minimumRate)); } + + Q_PROPERTY(QString PlaybackStatus READ playbackStatus NOTIFY playbackStatusChanged) + inline QString playbackStatus() + { return qvariant_cast< QString >(internalPropGet("PlaybackStatus", &m_playbackStatus)); } + + Q_PROPERTY(qlonglong Position READ position NOTIFY positionChanged) + inline qlonglong position() + { return qvariant_cast< qlonglong >(internalPropGet("Position", &m_position)); } + + Q_PROPERTY(double Rate READ rate WRITE setRate NOTIFY rateChanged) + inline double rate() + { return qvariant_cast< double >(internalPropGet("Rate", &m_rate)); } + inline void setRate(double value) + { m_rate = value; internalPropSet("Rate", QVariant::fromValue(value), &m_rate); } + + Q_PROPERTY(bool Shuffle READ shuffle WRITE setShuffle NOTIFY shuffleChanged) + inline bool shuffle() + { return qvariant_cast< bool >(internalPropGet("Shuffle", &m_shuffle)); } + inline void setShuffle(bool value) + { m_shuffle = value; internalPropSet("Shuffle", QVariant::fromValue(value), &m_shuffle); } + + Q_PROPERTY(double Volume READ volume WRITE setVolume NOTIFY volumeChanged) + inline double volume() + { return qvariant_cast< double >(internalPropGet("Volume", &m_volume)); } + inline void setVolume(double value) + { m_volume = value; internalPropSet("Volume", QVariant::fromValue(value), &m_volume); } + +public Q_SLOTS: // METHODS + inline QDBusPendingReply<> Next() + { + QList argumentList; + return asyncCallWithArgumentList(QLatin1String("Next"), argumentList); + } + + inline QDBusPendingReply<> OpenUri(const QString &Uri) + { + QList argumentList; + argumentList << QVariant::fromValue(Uri); + return asyncCallWithArgumentList(QLatin1String("OpenUri"), argumentList); + } + + inline QDBusPendingReply<> Pause() + { + QList argumentList; + return asyncCallWithArgumentList(QLatin1String("Pause"), argumentList); + } + + inline QDBusPendingReply<> Play() + { + QList argumentList; + return asyncCallWithArgumentList(QLatin1String("Play"), argumentList); + } + + inline QDBusPendingReply<> PlayPause() + { + QList argumentList; + return asyncCallWithArgumentList(QLatin1String("PlayPause"), argumentList); + } + + inline QDBusPendingReply<> Previous() + { + QList argumentList; + return asyncCallWithArgumentList(QLatin1String("Previous"), argumentList); + } + + inline QDBusPendingReply<> Seek(qlonglong Offset) + { + QList argumentList; + argumentList << QVariant::fromValue(Offset); + return asyncCallWithArgumentList(QLatin1String("Seek"), argumentList); + } + + inline QDBusPendingReply<> SetPosition(const QDBusObjectPath &TrackId, qlonglong Position) + { + QList argumentList; + argumentList << QVariant::fromValue(TrackId) << QVariant::fromValue(Position); + return asyncCallWithArgumentList(QLatin1String("SetPosition"), argumentList); + } + + inline QDBusPendingReply<> Stop() + { + QList argumentList; + return asyncCallWithArgumentList(QLatin1String("Stop"), argumentList); + } + +Q_SIGNALS: // SIGNALS + void canControlChanged(bool canControl); + void canGoNextChanged(bool canGoNext); + void canGoPreviousChanged(bool canGoPrevious); + void canPauseChanged(bool canPause); + void canPlayChanged(bool canPlay); + void canSeekChanged(bool canSeek); + void loopStatusChanged(const QString &loopStatus); + void maximumRateChanged(double maximumRate); + void metadataChanged(const QVariantMap &metadata); + void minimumRateChanged(double minimumRate); + void playbackStatusChanged(const QString &playbackStatus); + void positionChanged(qlonglong rate); + void rateChanged(double rate); + void shuffleChanged(bool shuffle); + void volumeChanged(double volume); + void seeked(qlonglong Position); + +private Q_SLOTS: + void onPropertyChanged(const QString &propertyName, const QVariant &value); + +private: + bool m_canControl; + bool m_canGoNext; + bool m_canGoPrevious; + bool m_canPause; + bool m_canPlay; + bool m_canSeek; + QString m_loopStatus; + double m_maximumRate; + QVariantMap m_metadata; + double m_minimumRate; + QString m_playbackStatus; + qlonglong m_position; + double m_rate; + bool m_shuffle; + double m_volume; +}; + +namespace MediaPlayer2 { + typedef ::MprisPlayerInterface Player; +} + +#endif /* MPRISROOTINTERFACE_P_H */ diff --git a/mpris/mprismanager.cpp b/mpris/mprismanager.cpp new file mode 100644 index 0000000..c734157 --- /dev/null +++ b/mpris/mprismanager.cpp @@ -0,0 +1,606 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "mprismanager.h" + +#include "mpriscontroller.h" + +#include + +#include +#include + +#include + +static const QString mprisNameSpace = QStringLiteral("org.mpris.MediaPlayer2.*"); +static const QString dBusService = QStringLiteral("org.freedesktop.DBus"); +static const QString dBusObjectPath = QStringLiteral("/org/freedesktop/DBus"); +static const QString dBusInterface = QStringLiteral("org.freedesktop.DBus"); +static const QString dBusNameOwnerChangedSignal = QStringLiteral("NameOwnerChanged"); + +static inline QDBusConnection getDBusConnection() +{ +#ifdef USE_SYSTEM_DBUS + return QDBusConnection::systemBus(); +#else + return QDBusConnection::sessionBus(); +#endif +} + + +MprisManager::MprisManager(QObject *parent) + : QObject(parent) + , m_singleService(false) + , m_playbackStatusMapper(new QSignalMapper(this)) +{ + QDBusConnection connection = getDBusConnection(); + + if (!connection.isConnected()) { + qmlInfo(this) << "Failed attempting to connect to DBus"; + return; + } + + connection.connect(dBusService, dBusObjectPath, dBusInterface, dBusNameOwnerChangedSignal, + QStringList(), QString(), + this, SLOT(onNameOwnerChanged(QString, QString, QString))); + + QStringList serviceNames = connection.interface()->registeredServiceNames(); + QStringList::const_iterator i = serviceNames.constBegin(); + while (i != serviceNames.constEnd()) { + QRegExp rx(mprisNameSpace); + rx.setPatternSyntax(QRegExp::Wildcard); + if (rx.exactMatch(*i)) { + onServiceAppeared(*i); + } + + ++i; + } +} + +MprisManager::~MprisManager() +{ +} + +// Mpris2 Root Interface +bool MprisManager::quit() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->quit(); +} + +bool MprisManager::raise() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->raise(); +} + +// Mpris2 Player Interface +bool MprisManager::next() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->next(); +} + +bool MprisManager::openUri(const QUrl &uri) const +{ + return checkController(Q_FUNC_INFO) && m_currentController->openUri(uri); +} + +bool MprisManager::pause() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->pause(); +} + +bool MprisManager::play() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->play(); +} + +bool MprisManager::playPause() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->playPause(); +} + +bool MprisManager::previous() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->previous(); +} + +bool MprisManager::seek(qlonglong offset) const +{ + return checkController(Q_FUNC_INFO) && m_currentController->seek(offset); +} + +bool MprisManager::setPosition(qlonglong position) const +{ + return checkController(Q_FUNC_INFO) && m_currentController->setPosition(position); +} + +bool MprisManager::setPosition(const QString &trackId, qlonglong position) const +{ + return checkController(Q_FUNC_INFO) && m_currentController->setPosition(trackId, position); +} + +bool MprisManager::stop() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->stop(); +} + + +// Slots + +bool MprisManager::singleService() const +{ + return m_singleService; +} + +void MprisManager::setSingleService(bool single) +{ + if (m_singleService == single) { + return; + } + + m_singleService = single; + Q_EMIT singleServiceChanged(); +} + +QString MprisManager::currentService() const +{ + return m_currentController.isNull() ? QString() : m_currentController->service(); +} + +void MprisManager::setCurrentService(const QString &service) +{ + if (!m_currentController.isNull() && m_currentController->service() == service) { + return; + } + + QRegExp rx(mprisNameSpace); + rx.setPatternSyntax(QRegExp::Wildcard); + if (!rx.exactMatch(service)) { + qmlInfo(this) << service << "is not a proper Mpris2 service"; + return; + } + + QSharedPointer controller = availableController(service); + if (controller.isNull()) { + controller = QSharedPointer(new MprisController(service, getDBusConnection(), this)); + } else { + m_availableControllers.move(m_availableControllers.indexOf(controller), 0); + } + + setCurrentController(controller); +} + +QStringList MprisManager::availableServices() const +{ + QStringList result; + + QList< QSharedPointer >::const_iterator i = m_availableControllers.constBegin(); + while (i != m_availableControllers.constEnd()) { + const QSharedPointer controller = *i; + result.append(controller->service()); + + ++i; + } + + return result; +} + +// Mpris2 Root Interface +bool MprisManager::canQuit() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->canQuit(); +} + +bool MprisManager::canRaise() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->canRaise(); +} + +bool MprisManager::canSetFullscreen() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->canSetFullscreen(); +} + +QString MprisManager::desktopEntry() const +{ + return checkController(Q_FUNC_INFO) ? m_currentController->desktopEntry() : QString(); +} + +bool MprisManager::fullscreen() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->fullscreen(); +} + +void MprisManager::setFullscreen(bool fullscreen) +{ + if (checkController(Q_FUNC_INFO)) { + m_currentController->setFullscreen(fullscreen); + } +} + +bool MprisManager::hasTrackList() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->hasTrackList(); +} + +QString MprisManager::identity() const +{ + return checkController(Q_FUNC_INFO) ? m_currentController->identity() : QString(); +} + +QStringList MprisManager::supportedUriSchemes() const +{ + return checkController(Q_FUNC_INFO) ? m_currentController->supportedUriSchemes() : QStringList(); +} + +QStringList MprisManager::supportedMimeTypes() const +{ + return checkController(Q_FUNC_INFO) ? m_currentController->supportedMimeTypes() : QStringList(); +} + +// Mpris2 Player Interface +bool MprisManager::canControl() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->canControl(); +} + +bool MprisManager::canGoNext() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->canGoNext(); +} + +bool MprisManager::canGoPrevious() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->canGoPrevious(); +} + +bool MprisManager::canPause() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->canPause(); +} + +bool MprisManager::canPlay() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->canPlay(); +} + +bool MprisManager::canSeek() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->canSeek(); +} + +Mpris::LoopStatus MprisManager::loopStatus() const +{ + return checkController(Q_FUNC_INFO) ? m_currentController->loopStatus() : Mpris::None; +} + +void MprisManager::setLoopStatus(Mpris::LoopStatus loopStatus) +{ + if (checkController(Q_FUNC_INFO)) { + m_currentController->setLoopStatus(loopStatus); + } +} + +double MprisManager::maximumRate() const +{ + return checkController(Q_FUNC_INFO) ? m_currentController->maximumRate() : 1; +} + +QVariantMap MprisManager::metadata() const +{ + return checkController(Q_FUNC_INFO) ? m_currentController->metadata() : QVariantMap(); +} + +double MprisManager::minimumRate() const +{ + return checkController(Q_FUNC_INFO) ? m_currentController->minimumRate() : 1; +} + +Mpris::PlaybackStatus MprisManager::playbackStatus() const +{ + return checkController(Q_FUNC_INFO) ? m_currentController->playbackStatus() : Mpris::Stopped; +} + +qlonglong MprisManager::position() const +{ + return checkController(Q_FUNC_INFO) ? m_currentController->position() : 0; +} + +void MprisManager::requestPosition() const +{ + if (checkController(Q_FUNC_INFO)) { + m_currentController->requestPosition(); + } +} + +double MprisManager::rate() const +{ + return checkController(Q_FUNC_INFO) ? m_currentController->rate() : 1; +} + +void MprisManager::setRate(double rate) +{ + if (checkController(Q_FUNC_INFO)) { + m_currentController->setRate(rate); + } +} + +bool MprisManager::shuffle() const +{ + return checkController(Q_FUNC_INFO) && m_currentController->shuffle(); +} + +void MprisManager::setShuffle(bool shuffle) +{ + if (checkController(Q_FUNC_INFO)) { + m_currentController->setShuffle(shuffle); + } +} + +double MprisManager::volume() const +{ + return checkController(Q_FUNC_INFO) ? m_currentController->volume() : 0; +} + +void MprisManager::setVolume(double volume) +{ + if (checkController(Q_FUNC_INFO)) { + m_currentController->setVolume(volume); + } +} + + +// Private + +void MprisManager::onNameOwnerChanged(const QString &service, const QString &oldOwner, const QString &newOwner) +{ + // Unfortunately, QDBus doesn't allow flexible signal watchers. + // Would it allow them, we could pass the filter "arg0namespace='org.mpris.MediaPlayer2'" + // Therefore, we will receive here all the name changes in the + // bus, not just the ones for our name space of interest, and we + // will have to filter on our own :( + + QRegExp rx(mprisNameSpace); + rx.setPatternSyntax(QRegExp::Wildcard); + if (!rx.exactMatch(service)) { + return; + } + + if (oldOwner.isEmpty()) { + onServiceAppeared(service); + return; + } + + if (newOwner.isEmpty()) { + onServiceVanished(service); + return; + } + + // Service changed owner. Nothing to do ... +} + +void MprisManager::onServiceAppeared(const QString &service) +{ + QSharedPointer controller = availableController(service); + if (!controller.isNull()) { + Q_ASSERT(m_availableControllers.contains(controller)); + m_availableControllers.removeOne(controller); + m_otherPlayingControllers.removeOne(controller); + } else { + if (!m_currentController.isNull() && service == m_currentController->service()) { + controller = m_currentController; + } else { + controller = QSharedPointer(new MprisController(service, getDBusConnection(), this)); + } + + connect(controller.data(), SIGNAL(playbackStatusChanged()), m_playbackStatusMapper, SLOT(map())); + m_playbackStatusMapper->setMapping(controller.data(), controller->service()); + connect(m_playbackStatusMapper, SIGNAL(mapped(QString)), this, SLOT(onAvailableControllerPlaybackStatusChanged(QString))); + } + + if (m_currentController.isNull()) { + setCurrentController(controller); + } + + if (controller == m_currentController) { + m_availableControllers.prepend(controller); + Q_EMIT availableServicesChanged(); + return; + } + + if (!m_singleService + && m_currentController->playbackStatus() != Mpris::Playing) { + m_availableControllers.prepend(controller); + setCurrentController(controller); + } else { + m_availableControllers.insert(1, controller); + if (controller->playbackStatus() == Mpris::Playing) { + m_otherPlayingControllers.prepend(controller); + } + } + + Q_EMIT availableServicesChanged(); +} + +void MprisManager::onServiceVanished(const QString &service) +{ + QSharedPointer controller = availableController(service); + if (!controller.isNull()) { + Q_ASSERT(m_availableControllers.contains(controller)); + m_availableControllers.removeOne(controller); + m_otherPlayingControllers.removeOne(controller); + } + + if (!m_currentController.isNull() && service == m_currentController->service()) { + if (m_singleService) { + Q_EMIT availableServicesChanged(); + return; + } + + if (!m_availableControllers.isEmpty()) { + setCurrentController(m_availableControllers[0]); + } else { + setCurrentController(QSharedPointer()); + } + } + + Q_EMIT availableServicesChanged(); +} + +void MprisManager::onAvailableControllerPlaybackStatusChanged(const QString &service) +{ + QSharedPointer controller = availableController(service); + Q_ASSERT(!controller.isNull()); + + if (m_currentController == controller) { + if (m_currentController->playbackStatus() == Mpris::Playing) { + return; + } + + if (!m_otherPlayingControllers.isEmpty()) { + QSharedPointer currentController = m_otherPlayingControllers.takeFirst(); + m_availableControllers.move(m_availableControllers.indexOf(currentController), 0); + setCurrentController(currentController); + } + } else { + if (controller->playbackStatus() != Mpris::Playing) { + m_otherPlayingControllers.removeOne(controller); + return; + } + + if (!m_singleService + && m_currentController->playbackStatus() != Mpris::Playing) { + setCurrentController(controller); + } else { + m_availableControllers.move(m_availableControllers.indexOf(controller), 1); + m_otherPlayingControllers.removeOne(controller); // Just in case, shouldn't be needed + m_otherPlayingControllers.prepend(controller); + } + } +} + +QSharedPointer MprisManager::availableController(const QString &service) +{ + QList< QSharedPointer >::iterator i = m_availableControllers.begin(); + while (i != m_availableControllers.end()) { + QSharedPointer controller = *i; + if (!controller.isNull() && controller->service() == service) { + return controller; + } + + ++i; + } + + return QSharedPointer(); +} + +void MprisManager::setCurrentController(QSharedPointer controller) +{ + if (controller == m_currentController) { + return; + } + + if (!m_currentController.isNull()) { + // Mpris Root Interface + disconnect(m_currentController.data(), &MprisController::canQuitChanged, this, &MprisManager::canQuitChanged); + disconnect(m_currentController.data(), &MprisController::canRaiseChanged, this, &MprisManager::canRaiseChanged); + disconnect(m_currentController.data(), &MprisController::canSetFullscreenChanged, this, &MprisManager::canSetFullscreenChanged); + disconnect(m_currentController.data(), &MprisController::desktopEntryChanged, this, &MprisManager::desktopEntryChanged); + disconnect(m_currentController.data(), &MprisController::fullscreenChanged, this, &MprisManager::fullscreenChanged); + disconnect(m_currentController.data(), &MprisController::hasTrackListChanged, this, &MprisManager::hasTrackListChanged); + disconnect(m_currentController.data(), &MprisController::identityChanged, this, &MprisManager::identityChanged); + disconnect(m_currentController.data(), &MprisController::supportedUriSchemesChanged, this, &MprisManager::supportedUriSchemesChanged); + disconnect(m_currentController.data(), &MprisController::supportedMimeTypesChanged, this, &MprisManager::supportedMimeTypesChanged); + + // Mpris Player Interface + disconnect(m_currentController.data(), &MprisController::canControlChanged, this, &MprisManager::canControlChanged); + disconnect(m_currentController.data(), &MprisController::canGoNextChanged, this, &MprisManager::canGoNextChanged); + disconnect(m_currentController.data(), &MprisController::canGoPreviousChanged, this, &MprisManager::canGoPreviousChanged); + disconnect(m_currentController.data(), &MprisController::canPauseChanged, this, &MprisManager::canPauseChanged); + disconnect(m_currentController.data(), &MprisController::canPlayChanged, this, &MprisManager::canPlayChanged); + disconnect(m_currentController.data(), &MprisController::canSeekChanged, this, &MprisManager::canSeekChanged); + disconnect(m_currentController.data(), &MprisController::loopStatusChanged, this, &MprisManager::loopStatusChanged); + disconnect(m_currentController.data(), &MprisController::maximumRateChanged, this, &MprisManager::maximumRateChanged); + disconnect(m_currentController.data(), &MprisController::metadataChanged, this, &MprisManager::metadataChanged); + disconnect(m_currentController.data(), &MprisController::minimumRateChanged, this, &MprisManager::minimumRateChanged); + disconnect(m_currentController.data(), &MprisController::playbackStatusChanged, this, &MprisManager::playbackStatusChanged); + disconnect(m_currentController.data(), &MprisController::positionChanged, this, &MprisManager::positionChanged); + disconnect(m_currentController.data(), &MprisController::rateChanged, this, &MprisManager::rateChanged); + disconnect(m_currentController.data(), &MprisController::shuffleChanged, this, &MprisManager::shuffleChanged); + disconnect(m_currentController.data(), &MprisController::volumeChanged, this, &MprisManager::volumeChanged); + disconnect(m_currentController.data(), &MprisController::seeked, this, &MprisManager::seeked); + + if (m_currentController->playbackStatus() == Mpris::Playing) { + m_otherPlayingControllers.prepend(m_currentController); + } + } + + m_currentController = controller; + + if (!m_currentController.isNull()) { + // Mpris Root Interface + connect(m_currentController.data(), &MprisController::canQuitChanged, this, &MprisManager::canQuitChanged); + connect(m_currentController.data(), &MprisController::canRaiseChanged, this, &MprisManager::canRaiseChanged); + connect(m_currentController.data(), &MprisController::canSetFullscreenChanged, this, &MprisManager::canSetFullscreenChanged); + connect(m_currentController.data(), &MprisController::desktopEntryChanged, this, &MprisManager::desktopEntryChanged); + connect(m_currentController.data(), &MprisController::fullscreenChanged, this, &MprisManager::fullscreenChanged); + connect(m_currentController.data(), &MprisController::hasTrackListChanged, this, &MprisManager::hasTrackListChanged); + connect(m_currentController.data(), &MprisController::identityChanged, this, &MprisManager::identityChanged); + connect(m_currentController.data(), &MprisController::supportedUriSchemesChanged, this, &MprisManager::supportedUriSchemesChanged); + connect(m_currentController.data(), &MprisController::supportedMimeTypesChanged, this, &MprisManager::supportedMimeTypesChanged); + connect(m_currentController.data(), &MprisController::canControlChanged, this, &MprisManager::canControlChanged); + + // Mpris Player Interface + connect(m_currentController.data(), &MprisController::canGoNextChanged, this, &MprisManager::canGoNextChanged); + connect(m_currentController.data(), &MprisController::canGoPreviousChanged, this, &MprisManager::canGoPreviousChanged); + connect(m_currentController.data(), &MprisController::canPauseChanged, this, &MprisManager::canPauseChanged); + connect(m_currentController.data(), &MprisController::canPlayChanged, this, &MprisManager::canPlayChanged); + connect(m_currentController.data(), &MprisController::canSeekChanged, this, &MprisManager::canSeekChanged); + connect(m_currentController.data(), &MprisController::loopStatusChanged, this, &MprisManager::loopStatusChanged); + connect(m_currentController.data(), &MprisController::maximumRateChanged, this, &MprisManager::maximumRateChanged); + connect(m_currentController.data(), &MprisController::metadataChanged, this, &MprisManager::metadataChanged); + connect(m_currentController.data(), &MprisController::minimumRateChanged, this, &MprisManager::minimumRateChanged); + connect(m_currentController.data(), &MprisController::playbackStatusChanged, this, &MprisManager::playbackStatusChanged); + connect(m_currentController.data(), &MprisController::rateChanged, this, &MprisManager::rateChanged); + connect(m_currentController.data(), &MprisController::shuffleChanged, this, &MprisManager::shuffleChanged); + connect(m_currentController.data(), &MprisController::volumeChanged, this, &MprisManager::volumeChanged); + connect(m_currentController.data(), &MprisController::seeked, this, &MprisManager::seeked); + + if (m_currentController->playbackStatus() == Mpris::Playing) { + m_otherPlayingControllers.removeOne(m_currentController); + } + } + + Q_EMIT currentServiceChanged(); +} + +bool MprisManager::checkController(const char *callerName) const +{ + if (m_currentController.isNull()) { + qWarning() << callerName << "None service available/selected"; + return false; + } + + return true; +} diff --git a/mpris/mprismanager.h b/mpris/mprismanager.h new file mode 100644 index 0000000..fe84fdc --- /dev/null +++ b/mpris/mprismanager.h @@ -0,0 +1,221 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef MPRISMANAGER_H +#define MPRISMANAGER_H + +#include "mprisqt.h" +#include "mpris.h" +#include "mpriscontroller.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +class QSignalMapper; +class MPRIS_QT_EXPORT MprisManager : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool singleService READ singleService WRITE setSingleService NOTIFY singleServiceChanged) + Q_PROPERTY(QString currentService READ currentService WRITE setCurrentService NOTIFY currentServiceChanged) + Q_PROPERTY(QStringList availableServices READ availableServices NOTIFY availableServicesChanged) + + // Mpris2 Root Interface + Q_PROPERTY(bool canQuit READ canQuit NOTIFY canQuitChanged) + Q_PROPERTY(bool canRaise READ canRaise NOTIFY canRaiseChanged) + Q_PROPERTY(bool canSetFullscreen READ canSetFullscreen NOTIFY canSetFullscreenChanged) + Q_PROPERTY(QString desktopEntry READ desktopEntry NOTIFY desktopEntryChanged) + Q_PROPERTY(bool fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged) + Q_PROPERTY(bool hasTrackList READ hasTrackList NOTIFY hasTrackListChanged) + Q_PROPERTY(QString identity READ identity NOTIFY identityChanged) + Q_PROPERTY(QStringList supportedUriSchemes READ supportedUriSchemes NOTIFY supportedUriSchemesChanged) + Q_PROPERTY(QStringList supportedMimeTypes READ supportedMimeTypes NOTIFY supportedMimeTypesChanged) + + // Mpris2 Player Interface + Q_PROPERTY(bool canControl READ canControl NOTIFY canControlChanged) + Q_PROPERTY(bool canGoNext READ canGoNext NOTIFY canGoNextChanged) + Q_PROPERTY(bool canGoPrevious READ canGoPrevious NOTIFY canGoPreviousChanged) + Q_PROPERTY(bool canPause READ canPause NOTIFY canPauseChanged) + Q_PROPERTY(bool canPlay READ canPlay NOTIFY canPlayChanged) + Q_PROPERTY(bool canSeek READ canSeek NOTIFY canSeekChanged) + Q_PROPERTY(Mpris::LoopStatus loopStatus READ loopStatus WRITE setLoopStatus NOTIFY loopStatusChanged) + Q_PROPERTY(double maximumRate READ maximumRate NOTIFY maximumRateChanged) + Q_PROPERTY(QVariantMap metadata READ metadata NOTIFY metadataChanged) + Q_PROPERTY(double minimumRate READ minimumRate NOTIFY minimumRateChanged) + Q_PROPERTY(Mpris::PlaybackStatus playbackStatus READ playbackStatus NOTIFY playbackStatusChanged) + Q_PROPERTY(qlonglong position READ position) + Q_PROPERTY(double rate READ rate WRITE setRate NOTIFY rateChanged) + Q_PROPERTY(bool shuffle READ shuffle WRITE setShuffle NOTIFY shuffleChanged) + Q_PROPERTY(double volume READ volume WRITE setVolume NOTIFY volumeChanged) + +public: + + MprisManager(QObject *parent = 0); + ~MprisManager(); + + // Mpris2 Root Interface + Q_INVOKABLE bool quit() const; + Q_INVOKABLE bool raise() const; + + // Mpris2 Player Interface + Q_INVOKABLE bool next() const; + Q_INVOKABLE bool openUri(const QUrl &uri) const; + Q_INVOKABLE bool pause() const; + Q_INVOKABLE bool play() const; + Q_INVOKABLE bool playPause() const; + Q_INVOKABLE bool previous() const; + Q_INVOKABLE bool seek(qlonglong offset) const; + Q_INVOKABLE bool setPosition(qlonglong position) const; + Q_INVOKABLE bool setPosition(const QString &trackId, qlonglong position) const; + Q_INVOKABLE bool stop() const; + +public Q_SLOTS: + + bool singleService() const; + void setSingleService(bool single); + + QString currentService() const; + void setCurrentService(const QString &service); + + QStringList availableServices() const; + + // Mpris2 Root Interface + bool canQuit() const; + + bool canRaise() const; + + bool canSetFullscreen() const; + + QString desktopEntry() const; + + bool fullscreen() const; + void setFullscreen(bool fullscreen); + + bool hasTrackList() const; + + QString identity() const; + + QStringList supportedUriSchemes() const; + + QStringList supportedMimeTypes() const; + + // Mpris2 Player Interface + bool canControl() const; + + bool canGoNext() const; + + bool canGoPrevious() const; + + bool canPause() const; + + bool canPlay() const; + + bool canSeek() const; + + Mpris::LoopStatus loopStatus() const; + void setLoopStatus(Mpris::LoopStatus loopStatus); + + double maximumRate() const; + + QVariantMap metadata() const; + + double minimumRate() const; + + Mpris::PlaybackStatus playbackStatus() const; + + qlonglong position() const; + void requestPosition() const; + + double rate() const; + void setRate(double rate); + + bool shuffle() const; + void setShuffle(bool shuffle); + + double volume() const; + void setVolume(double volume); + +Q_SIGNALS: + void singleServiceChanged(); + void currentServiceChanged(); + void availableServicesChanged(); + + // Mpris2 Root Interface + void canQuitChanged(); + void canRaiseChanged(); + void canSetFullscreenChanged(); + void desktopEntryChanged(); + void fullscreenChanged(); + void hasTrackListChanged(); + void identityChanged(); + void supportedUriSchemesChanged(); + void supportedMimeTypesChanged(); + + // Mpris2 Player Interface + void canControlChanged(); + void canGoNextChanged(); + void canGoPreviousChanged(); + void canPauseChanged(); + void canPlayChanged(); + void canSeekChanged(); + void loopStatusChanged(); + void maximumRateChanged(); + void metadataChanged(); + void minimumRateChanged(); + void playbackStatusChanged(); + void positionChanged(qlonglong position); + void rateChanged(); + void shuffleChanged(); + void volumeChanged(); + void seeked(qlonglong position); + +private Q_SLOTS: + void onNameOwnerChanged(const QString &service, const QString &oldOwner, const QString& newOwner); + void onServiceAppeared(const QString &service); + void onServiceVanished(const QString &service); + void onAvailableControllerPlaybackStatusChanged(const QString &service); + +private: + QSharedPointer availableController(const QString &service); + void setCurrentController(QSharedPointer controller); + bool checkController(const char *callerName) const; + + bool m_singleService; + QSharedPointer m_currentController; + QList< QSharedPointer > m_availableControllers; + QList< QSharedPointer > m_otherPlayingControllers; + QSignalMapper *m_playbackStatusMapper; +}; + +#endif /* MPRISMANAGER_H */ diff --git a/mpris/mprisplayer.cpp b/mpris/mprisplayer.cpp new file mode 100644 index 0000000..c3dc43c --- /dev/null +++ b/mpris/mprisplayer.cpp @@ -0,0 +1,590 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "mprisplayer.h" + +#include "mprisplayer_p.h" + +#include + +#include +#include +#include +#include +#include + +static const QString serviceNamePrefix = QStringLiteral("org.mpris.MediaPlayer2."); +static const QString mprisObjectPath = QStringLiteral("/org/mpris/MediaPlayer2"); +static const QString dBusPropertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties"); +static const QString dBusPropertiesChangedSignal = QStringLiteral("PropertiesChanged"); + +static inline QDBusConnection getDBusConnection() +{ +#ifdef USE_SYSTEM_DBUS + return QDBusConnection::systemBus(); +#else + return QDBusConnection::sessionBus(); +#endif +} + + +MprisPlayer::MprisPlayer(QObject *parent) + : QObject(parent) + , QDBusContext() + , m_mprisRootAdaptor(new MprisRootAdaptor(this)) + , m_mprisPlayerAdaptor(new MprisPlayerAdaptor(this)) + , m_canQuit(false) + , m_canRaise(false) + , m_canSetFullscreen(false) + , m_fullscreen(false) + , m_hasTrackList(false) + , m_canControl(false) + , m_canGoNext(false) + , m_canGoPrevious(false) + , m_canPause(false) + , m_canPlay(false) + , m_canSeek(false) + , m_loopStatus(Mpris::None) + , m_maximumRate(1) + , m_minimumRate(1) + , m_playbackStatus(Mpris::Stopped) + , m_position(0) + , m_rate(1) + , m_shuffle(false) + , m_volume(0) +{ + QDBusConnection connection = getDBusConnection(); + + if (!connection.isConnected()) { + qmlInfo(this) << "Failed attempting to connect to DBus"; + } else if (!connection.registerObject(mprisObjectPath, this)) { + qmlInfo(this) << "Failed attempting to register object path. Already registered?"; + } +} + +MprisPlayer::~MprisPlayer() +{ + unregisterService(); +} + +QString MprisPlayer::serviceName() const +{ + return m_serviceName; +} + +void MprisPlayer::setServiceName(const QString &serviceName) +{ + if (m_serviceName == serviceName) { + return; + } + + unregisterService(); + m_serviceName = serviceName; + registerService(); + + Q_EMIT serviceNameChanged(); +} + + +// Mpris2 Root Interface +bool MprisPlayer::canQuit() const +{ + return m_canQuit; +} + +void MprisPlayer::setCanQuit(bool canQuit) +{ + if (m_canQuit == canQuit) { + return; + } + + m_canQuit = canQuit; + Q_EMIT canQuitChanged(); +} + +bool MprisPlayer::canRaise() const +{ + return m_canRaise; +} + +void MprisPlayer::setCanRaise(bool canRaise) +{ + if (m_canRaise == canRaise) { + return; + } + + m_canRaise = canRaise; + Q_EMIT canRaiseChanged(); +} + +bool MprisPlayer::canSetFullscreen() const +{ + return m_canSetFullscreen; +} + +void MprisPlayer::setCanSetFullscreen(bool canSetFullscreen) +{ + if (m_canSetFullscreen == canSetFullscreen) { + return; + } + + m_canSetFullscreen = canSetFullscreen; + Q_EMIT canSetFullscreenChanged(); +} + +QString MprisPlayer::desktopEntry() const +{ + return m_desktopEntry; +} + +void MprisPlayer::setDesktopEntry(const QString &desktopEntry) +{ + if (m_desktopEntry == desktopEntry) { + return; + } + + m_desktopEntry = desktopEntry; + Q_EMIT desktopEntryChanged(); +} + +bool MprisPlayer::fullscreen() const +{ + return m_fullscreen; +} + +void MprisPlayer::setFullscreen(bool fullscreen) +{ + if (m_fullscreen == fullscreen) { + return; + } + + m_fullscreen = fullscreen; + Q_EMIT fullscreenChanged(); +} + +bool MprisPlayer::hasTrackList() const +{ + return m_hasTrackList; +} + +void MprisPlayer::setHasTrackList(bool hasTrackList) +{ + if (m_hasTrackList == hasTrackList) { + return; + } + + m_hasTrackList = hasTrackList; + Q_EMIT hasTrackListChanged(); +} + +QString MprisPlayer::identity() const +{ + return m_identity; +} + +void MprisPlayer::setIdentity(const QString &identity) +{ + if (m_identity == identity) { + return; + } + + m_identity = identity; + Q_EMIT identityChanged(); +} + +QStringList MprisPlayer::supportedUriSchemes() const +{ + return m_supportedUriSchemes; +} + +void MprisPlayer::setSupportedUriSchemes(const QStringList &supportedUriSchemes) +{ + if (m_supportedUriSchemes == supportedUriSchemes) { + return; + } + + m_supportedUriSchemes = supportedUriSchemes; + Q_EMIT supportedUriSchemesChanged(); +} + +QStringList MprisPlayer::supportedMimeTypes() const +{ + return m_supportedMimeTypes; +} + +void MprisPlayer::setSupportedMimeTypes(const QStringList &supportedMimeTypes) +{ + if (m_supportedMimeTypes == supportedMimeTypes) { + return; + } + + m_supportedMimeTypes = supportedMimeTypes; + Q_EMIT supportedMimeTypesChanged(); +} + +// Mpris2 Player Interface +bool MprisPlayer::canControl() const +{ + return m_canControl; +} + +void MprisPlayer::setCanControl(bool canControl) +{ + if (m_canControl == canControl) { + return; + } + + m_canControl = canControl; + Q_EMIT canControlChanged(); +} + +bool MprisPlayer::canGoNext() const +{ + return m_canGoNext; +} + +void MprisPlayer::setCanGoNext(bool canGoNext) +{ + if (m_canGoNext == canGoNext) { + return; + } + + m_canGoNext = canGoNext; + Q_EMIT canGoNextChanged(); +} + +bool MprisPlayer::canGoPrevious() const +{ + return m_canGoPrevious; +} + +void MprisPlayer::setCanGoPrevious(bool canGoPrevious) +{ + if (m_canGoPrevious == canGoPrevious) { + return; + } + + m_canGoPrevious = canGoPrevious; + Q_EMIT canGoPreviousChanged(); +} + +bool MprisPlayer::canPause() const +{ + return m_canPause; +} + +void MprisPlayer::setCanPause(bool canPause) +{ + if (m_canPause == canPause) { + return; + } + + m_canPause = canPause; + Q_EMIT canPauseChanged(); +} + +bool MprisPlayer::canPlay() const +{ + return m_canPlay; +} + +void MprisPlayer::setCanPlay(bool canPlay) +{ + if (m_canPlay == canPlay) { + return; + } + + m_canPlay = canPlay; + Q_EMIT canPlayChanged(); +} + +bool MprisPlayer::canSeek() const +{ + return m_canSeek; +} + +void MprisPlayer::setCanSeek(bool canSeek) +{ + if (m_canSeek == canSeek) { + return; + } + + m_canSeek = canSeek; + Q_EMIT canSeekChanged(); +} + +Mpris::LoopStatus MprisPlayer::loopStatus() const +{ + return m_loopStatus; +} + +void MprisPlayer::setLoopStatus(Mpris::LoopStatus loopStatus) +{ + if (m_loopStatus == loopStatus) { + return; + } + + m_loopStatus = loopStatus; + Q_EMIT loopStatusChanged(); +} + +double MprisPlayer::maximumRate() const +{ + return m_maximumRate; +} + +void MprisPlayer::setMaximumRate(double maximumRate) +{ + if (m_maximumRate == maximumRate) { + return; + } + + m_maximumRate = maximumRate; + Q_EMIT maximumRateChanged(); +} + +QVariantMap MprisPlayer::metadata() const +{ + return m_typedMetadata; +} + +void MprisPlayer::setMetadata(const QVariantMap &metadata) +{ + if (m_metadata == metadata) { + return; + } + + m_metadata = metadata; + m_typedMetadata = typeMetadata(metadata); + Q_EMIT metadataChanged(); +} + +double MprisPlayer::minimumRate() const +{ + return m_minimumRate; +} + +void MprisPlayer::setMinimumRate(double minimumRate) +{ + if (m_minimumRate == minimumRate) { + return; + } + + m_minimumRate = minimumRate; + Q_EMIT minimumRateChanged(); +} + +Mpris::PlaybackStatus MprisPlayer::playbackStatus() const +{ + return m_playbackStatus; +} + +void MprisPlayer::setPlaybackStatus(Mpris::PlaybackStatus playbackStatus) +{ + if (m_playbackStatus == playbackStatus) { + return; + } + + m_playbackStatus = playbackStatus; + Q_EMIT playbackStatusChanged(); +} + +qlonglong MprisPlayer::position() const +{ + return m_position; +} + +void MprisPlayer::setPosition(qlonglong position) +{ + if (m_position == position) { + return; + } + + m_position = position; + Q_EMIT positionChanged(); +} + +double MprisPlayer::rate() const +{ + return m_rate; +} + +void MprisPlayer::setRate(double rate) +{ + if (m_rate == rate) { + return; + } + + m_rate = rate; + Q_EMIT rateChanged(); +} + +bool MprisPlayer::shuffle() const +{ + return m_shuffle; +} + +void MprisPlayer::setShuffle(bool shuffle) +{ + if (m_shuffle == shuffle) { + return; + } + + m_shuffle= shuffle; + Q_EMIT shuffleChanged(); +} + +double MprisPlayer::volume() const +{ + return m_volume; +} + +void MprisPlayer::setVolume(double volume) +{ + if (m_volume == volume) { + return; + } + + m_volume = volume; + Q_EMIT volumeChanged(); +} + + +// Private + +QVariantMap MprisPlayer::typeMetadata(const QVariantMap &aMetadata) +{ + QVariantMap metadata; + QVariantMap::const_iterator i = aMetadata.constBegin(); + while (i != aMetadata.constEnd()) { + switch (Mpris::enumerationFromString(i.key())) { + case Mpris::TrackId: + metadata.insert(i.key(), QVariant::fromValue(QDBusObjectPath(i.value().toString()))); + break; + case Mpris::Length: + metadata.insert(i.key(), QVariant::fromValue(i.value().toLongLong())); + break; + case Mpris::ArtUrl: + case Mpris::Url: + metadata.insert(i.key(), QVariant::fromValue(i.value().toUrl().toString())); + break; + case Mpris::Album: + case Mpris::AsText: + case Mpris::Title: + metadata.insert(i.key(), QVariant::fromValue(i.value().toString())); + break; + case Mpris::AlbumArtist: + case Mpris::Artist: + case Mpris::Comment: + case Mpris::Composer: + case Mpris::Genre: + case Mpris::Lyricist: + metadata.insert(i.key(), QVariant::fromValue(i.value().toStringList())); + break; + case Mpris::AudioBPM: + case Mpris::DiscNumber: + case Mpris::TrackNumber: + case Mpris::UseCount: + metadata.insert(i.key(), QVariant::fromValue(i.value().toInt())); + break; + case Mpris::AutoRating: + case Mpris::UserRating: + metadata.insert(i.key(), QVariant::fromValue(i.value().toFloat())); + break; + case Mpris::ContentCreated: + case Mpris::FirstUsed: + case Mpris::LastUsed: + metadata.insert(i.key(), QVariant::fromValue(i.value().toDate().toString(Qt::ISODate))); + break; + case Mpris::InvalidMetadata: + // Passing with the original type and hoping the user used + // a type supported by DBus + metadata.insert(i.key(), i.value()); + break; + default: + // Nothing to do + break; + } + + ++i; + } + + return metadata; +} + +void MprisPlayer::registerService() +{ + if (m_serviceName.isEmpty()) { + qmlInfo(this) << "Failed to register service: empty service name"; + return; + } + + QDBusConnection connection = getDBusConnection(); + + if (!connection.isConnected()) { + qmlInfo(this) << "Failed attempting to connect to DBus"; + return; + } + + if (!connection.registerService(QString(serviceNamePrefix).append(m_serviceName))) { + qmlInfo(this) << "Failed attempting to register service: " << m_serviceName << " Already taken?"; + } + + return; +} + +void MprisPlayer::unregisterService() +{ + if (!m_serviceName.isEmpty()) { + QDBusConnection connection = getDBusConnection(); + connection.unregisterService(QString(serviceNamePrefix).append(m_serviceName)); + } +} + +void MprisPlayer::notifyPropertiesChanged(const QString& interfaceName, const QVariantMap &changedProperties, const QStringList &invalidatedProperties) const +{ + if (m_serviceName.isEmpty()) { + return; + } + + QDBusConnection connection = getDBusConnection(); + + if (!connection.isConnected()) { + qmlInfo(this) << "Failed attempting to connect to DBus"; + return; + } + + QDBusMessage message = QDBusMessage::createSignal(mprisObjectPath, + dBusPropertiesInterface, + dBusPropertiesChangedSignal); + + QList arguments; + arguments << QVariant(interfaceName) << QVariant(changedProperties) << QVariant(invalidatedProperties); + message.setArguments(arguments); + + if (!connection.send(message)) { + qmlInfo(this) << "Failed to send DBus property notification signal"; + } +} diff --git a/mpris/mprisplayer.h b/mpris/mprisplayer.h new file mode 100644 index 0000000..8200eb8 --- /dev/null +++ b/mpris/mprisplayer.h @@ -0,0 +1,250 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef MPRISPLAYER_H +#define MPRISPLAYER_H + +#include "mprisqt.h" +#include "mpris.h" + +#include +#include +#include + + +class MprisRootAdaptor; +class MprisPlayerAdaptor; +class MPRIS_QT_EXPORT MprisPlayer : public QObject, + protected QDBusContext +{ + Q_OBJECT + Q_PROPERTY(QString serviceName READ serviceName WRITE setServiceName NOTIFY serviceNameChanged) + + // Mpris2 Root Interface + Q_PROPERTY(bool canQuit READ canQuit WRITE setCanQuit NOTIFY canQuitChanged) + Q_PROPERTY(bool canRaise READ canRaise WRITE setCanRaise NOTIFY canRaiseChanged) + Q_PROPERTY(bool canSetFullscreen READ canSetFullscreen WRITE setCanSetFullscreen NOTIFY canSetFullscreenChanged) + Q_PROPERTY(QString desktopEntry READ desktopEntry WRITE setDesktopEntry NOTIFY desktopEntryChanged) + Q_PROPERTY(bool fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged) + Q_PROPERTY(bool hasTrackList READ hasTrackList WRITE setHasTrackList NOTIFY hasTrackListChanged) + Q_PROPERTY(QString identity READ identity WRITE setIdentity NOTIFY identityChanged) + Q_PROPERTY(QStringList supportedUriSchemes READ supportedUriSchemes WRITE setSupportedUriSchemes NOTIFY supportedUriSchemesChanged) + Q_PROPERTY(QStringList supportedMimeTypes READ supportedMimeTypes WRITE setSupportedMimeTypes NOTIFY supportedMimeTypesChanged) + + // Mpris2 Player Interface + Q_PROPERTY(bool canControl READ canControl WRITE setCanControl NOTIFY canControlChanged) + Q_PROPERTY(bool canGoNext READ canGoNext WRITE setCanGoNext NOTIFY canGoNextChanged) + Q_PROPERTY(bool canGoPrevious READ canGoPrevious WRITE setCanGoPrevious NOTIFY canGoPreviousChanged) + Q_PROPERTY(bool canPause READ canPause WRITE setCanPause NOTIFY canPauseChanged) + Q_PROPERTY(bool canPlay READ canPlay WRITE setCanPlay NOTIFY canPlayChanged) + Q_PROPERTY(bool canSeek READ canSeek WRITE setCanSeek NOTIFY canSeekChanged) + Q_PROPERTY(Mpris::LoopStatus loopStatus READ loopStatus WRITE setLoopStatus NOTIFY loopStatusChanged) + Q_PROPERTY(double maximumRate READ maximumRate WRITE setMaximumRate NOTIFY maximumRateChanged) + Q_PROPERTY(QVariantMap metadata READ metadata WRITE setMetadata NOTIFY metadataChanged) + Q_PROPERTY(double minimumRate READ minimumRate WRITE setMinimumRate NOTIFY minimumRateChanged) + Q_PROPERTY(Mpris::PlaybackStatus playbackStatus READ playbackStatus WRITE setPlaybackStatus NOTIFY playbackStatusChanged) + Q_PROPERTY(qlonglong position READ position WRITE setPosition NOTIFY positionChanged) + Q_PROPERTY(double rate READ rate WRITE setRate NOTIFY rateChanged) + Q_PROPERTY(bool shuffle READ shuffle WRITE setShuffle NOTIFY shuffleChanged) + Q_PROPERTY(double volume READ volume WRITE setVolume NOTIFY volumeChanged) + +public: + + MprisPlayer(QObject *parent = 0); + ~MprisPlayer(); + +public Q_SLOTS: + + QString serviceName() const; + void setServiceName(const QString &serviceName); + + // Mpris2 Root Interface + bool canQuit() const; + void setCanQuit(bool canQuit); + + bool canRaise() const; + void setCanRaise(bool canRaise); + + bool canSetFullscreen() const; + void setCanSetFullscreen(bool canSetFullscreen); + + QString desktopEntry() const; + void setDesktopEntry(const QString &desktopEntry); + + bool fullscreen() const; + void setFullscreen(bool fullscreen); + + bool hasTrackList() const; + void setHasTrackList(bool hasTrackList); + + QString identity() const; + void setIdentity(const QString &identity); + + QStringList supportedUriSchemes() const; + void setSupportedUriSchemes(const QStringList &supportedUriSchemes); + + QStringList supportedMimeTypes() const; + void setSupportedMimeTypes(const QStringList &supportedMimeTypes); + + // Mpris2 Player Interface + bool canControl() const; + void setCanControl(bool canControl); + + bool canGoNext() const; + void setCanGoNext(bool canGoNext); + + bool canGoPrevious() const; + void setCanGoPrevious(bool canGoPrevious); + + bool canPause() const; + void setCanPause(bool canPause); + + bool canPlay() const; + void setCanPlay(bool canPlay); + + bool canSeek() const; + void setCanSeek(bool canSeek); + + Mpris::LoopStatus loopStatus() const; + void setLoopStatus(Mpris::LoopStatus loopStatus); + + double maximumRate() const; + void setMaximumRate(double maximumRate); + + QVariantMap metadata() const; + void setMetadata(const QVariantMap &metadata); + + double minimumRate() const; + void setMinimumRate(double minimumRate); + + Mpris::PlaybackStatus playbackStatus() const; + void setPlaybackStatus(Mpris::PlaybackStatus playbackStatus); + + qlonglong position() const; + void setPosition(qlonglong position); + + double rate() const; + void setRate(double rate); + + bool shuffle() const; + void setShuffle(bool shuffle); + + double volume() const; + void setVolume(double volume); + +Q_SIGNALS: + void serviceNameChanged(); + + // Mpris2 Root Interface + void canQuitChanged(); + void canRaiseChanged(); + void canSetFullscreenChanged(); + void desktopEntryChanged(); + void fullscreenChanged(); + void hasTrackListChanged(); + void identityChanged(); + void supportedUriSchemesChanged(); + void supportedMimeTypesChanged(); + void fullscreenRequested(bool fullscreen); + void quitRequested(); + void raiseRequested(); + + // Mpris2 Player Interface + void canControlChanged(); + void canGoNextChanged(); + void canGoPreviousChanged(); + void canPauseChanged(); + void canPlayChanged(); + void canSeekChanged(); + void loopStatusChanged(); + void maximumRateChanged(); + void metadataChanged(); + void minimumRateChanged(); + void playbackStatusChanged(); + void positionChanged(); + void rateChanged(); + void shuffleChanged(); + void volumeChanged(); + void loopStatusRequested(Mpris::LoopStatus loopStatus); + void rateRequested(double rate); + void shuffleRequested(bool shuffle); + void volumeRequested(double volume); + void nextRequested(); + void openUriRequested(const QUrl &url); + void pauseRequested(); + void playRequested(); + void playPauseRequested(); + void previousRequested(); + void seekRequested(qlonglong offset); + void seeked(qlonglong position); + void setPositionRequested(const QDBusObjectPath &trackId, qlonglong position); + void stopRequested(); + +private: + static QVariantMap typeMetadata(const QVariantMap &aMetadata); + + void registerService(); + void unregisterService(); + void notifyPropertiesChanged(const QString& interfaceName, const QVariantMap &changedProperties, const QStringList &invalidatedProperties) const; + + MprisRootAdaptor *m_mprisRootAdaptor; + MprisPlayerAdaptor *m_mprisPlayerAdaptor; + + QString m_serviceName; + + // Mpris2 Root Interface + bool m_canQuit; + bool m_canRaise; + bool m_canSetFullscreen; + QString m_desktopEntry; + bool m_fullscreen; + bool m_hasTrackList; + QString m_identity; + QStringList m_supportedUriSchemes; + QStringList m_supportedMimeTypes; + + // Mpris2 Player Interface + bool m_canControl; + bool m_canGoNext; + bool m_canGoPrevious; + bool m_canPause; + bool m_canPlay; + bool m_canSeek; + Mpris::LoopStatus m_loopStatus; + double m_maximumRate; + QVariantMap m_metadata; + QVariantMap m_typedMetadata; + double m_minimumRate; + Mpris::PlaybackStatus m_playbackStatus; + qlonglong m_position; + double m_rate; + bool m_shuffle; + double m_volume; + + friend class MprisRootAdaptor; + friend class MprisPlayerAdaptor; +}; + +#endif /* MPRISPLAYER_H */ diff --git a/mpris/mprisplayer_p.h b/mpris/mprisplayer_p.h new file mode 100644 index 0000000..163be7e --- /dev/null +++ b/mpris/mprisplayer_p.h @@ -0,0 +1,264 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +// +// W A R N I N G +// ------------- +// +// This file is not part of the public API. This header file may +// change from version to version without notice, or even be +// removed. +// +// We mean it. +// +// + + +#ifndef MPRISPLAYER_P_H +#define MPRISPLAYER_P_H + +#include +#include +QT_BEGIN_NAMESPACE +class QByteArray; +template class QList; +template class QMap; +class QString; +class QStringList; +class QVariant; +QT_END_NAMESPACE + + +class MprisPlayer; + +/* + * Adaptor class for interface org.mpris.MediaPlayer2 + */ +class MprisRootAdaptor: public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2") + Q_CLASSINFO("D-Bus Introspection", "" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" + "") +public: + MprisRootAdaptor(MprisPlayer *parent); + virtual ~MprisRootAdaptor(); + +public: // PROPERTIES + Q_PROPERTY(bool CanQuit READ canQuit) + bool canQuit() const; + + Q_PROPERTY(bool CanRaise READ canRaise) + bool canRaise() const; + + Q_PROPERTY(bool CanSetFullscreen READ canSetFullscreen) + bool canSetFullscreen() const; + + Q_PROPERTY(QString DesktopEntry READ desktopEntry) + QString desktopEntry() const; + + Q_PROPERTY(bool Fullscreen READ fullscreen WRITE setFullscreen) + bool fullscreen() const; + void setFullscreen(bool value); + + Q_PROPERTY(bool HasTrackList READ hasTrackList) + bool hasTrackList() const; + + Q_PROPERTY(QString Identity READ identity) + QString identity() const; + + Q_PROPERTY(QStringList SupportedMimeTypes READ supportedMimeTypes) + QStringList supportedMimeTypes() const; + + Q_PROPERTY(QStringList SupportedUriSchemes READ supportedUriSchemes) + QStringList supportedUriSchemes() const; + +public Q_SLOTS: // METHODS + void Quit(); + void Raise(); +Q_SIGNALS: // SIGNALS +private Q_SLOTS: + void onCanQuitChanged() const; + void onCanRaiseChanged() const; + void onCanSetFullscreenChanged() const; + void onDesktopEntryChanged() const; + void onFullscreenChanged() const; + void onHasTrackListChanged() const; + void onIdentityChanged() const; + void onSupportedUriSchemesChanged() const; + void onSupportedMimeTypesChanged() const; +}; + + +/* + * Adaptor class for interface org.mpris.MediaPlayer2.Player + */ +class MprisPlayerAdaptor: public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2.Player") + Q_CLASSINFO("D-Bus Introspection", "" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" + "") + +public: + MprisPlayerAdaptor(MprisPlayer *parent); + virtual ~MprisPlayerAdaptor(); + +public: // PROPERTIES + Q_PROPERTY(bool CanControl READ canControl) + bool canControl() const; + + Q_PROPERTY(bool CanGoNext READ canGoNext) + bool canGoNext() const; + + Q_PROPERTY(bool CanGoPrevious READ canGoPrevious) + bool canGoPrevious() const; + + Q_PROPERTY(bool CanPause READ canPause) + bool canPause() const; + + Q_PROPERTY(bool CanPlay READ canPlay) + bool canPlay() const; + + Q_PROPERTY(bool CanSeek READ canSeek) + bool canSeek() const; + + Q_PROPERTY(QString LoopStatus READ loopStatus WRITE setLoopStatus) + QString loopStatus() const; + void setLoopStatus(const QString &value); + + Q_PROPERTY(double MaximumRate READ maximumRate) + double maximumRate() const; + + Q_PROPERTY(QVariantMap Metadata READ metadata) + QVariantMap metadata() const; + + Q_PROPERTY(double MinimumRate READ minimumRate) + double minimumRate() const; + + Q_PROPERTY(QString PlaybackStatus READ playbackStatus) + QString playbackStatus() const; + + Q_PROPERTY(qlonglong Position READ position) + qlonglong position() const; + + Q_PROPERTY(double Rate READ rate WRITE setRate) + double rate() const; + void setRate(double value); + + Q_PROPERTY(bool Shuffle READ shuffle WRITE setShuffle) + bool shuffle() const; + void setShuffle(bool value); + + Q_PROPERTY(double Volume READ volume WRITE setVolume) + double volume() const; + void setVolume(double value); + +public Q_SLOTS: // METHODS + void Next(); + void OpenUri(const QString &Uri); + void Pause(); + void Play(); + void PlayPause(); + void Previous(); + void Seek(qlonglong Offset); + void SetPosition(const QDBusObjectPath &TrackId, qlonglong Position); + void Stop(); +Q_SIGNALS: // SIGNALS + void Seeked(qlonglong Position); +private Q_SLOTS: + void onCanControlChanged() const; + void onCanGoNextChanged() const; + void onCanGoPreviousChanged() const; + void onCanPauseChanged() const; + void onCanPlayChanged() const; + void onCanSeekChanged() const; + void onLoopStatusChanged() const; + void onMaximumRateChanged() const; + void onMetadataChanged() const; + void onMinimumRateChanged() const; + void onPlaybackStatusChanged() const; + void onRateChanged() const; + void onShuffleChanged() const; + void onVolumeChanged() const; +}; + +#endif /* MPRISPLAYER_P_H */ diff --git a/mpris/mprisplayeradaptor.cpp b/mpris/mprisplayeradaptor.cpp new file mode 100644 index 0000000..086df52 --- /dev/null +++ b/mpris/mprisplayeradaptor.cpp @@ -0,0 +1,644 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "mprisplayer_p.h" + +#include "mprisplayer.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Implementation of adaptor class MprisPlayerAdaptor + */ + +static const QString mprisPlayerInterface = QStringLiteral("org.mpris.MediaPlayer2.Player"); + + +MprisPlayerAdaptor::MprisPlayerAdaptor(MprisPlayer *parent) + : QDBusAbstractAdaptor(parent) +{ + // constructor + setAutoRelaySignals(false); + + connect(parent, SIGNAL(canControlChanged()), this, SLOT(onCanControlChanged())); + connect(parent, SIGNAL(canGoNextChanged()), this, SLOT(onCanGoNextChanged())); + connect(parent, SIGNAL(canGoPreviousChanged()), this, SLOT(onCanGoPreviousChanged())); + connect(parent, SIGNAL(canPauseChanged()), this, SLOT(onCanPauseChanged())); + connect(parent, SIGNAL(canPlayChanged()), this, SLOT(onCanPlayChanged())); + connect(parent, SIGNAL(canSeekChanged()), this, SLOT(onCanSeekChanged())); + connect(parent, SIGNAL(loopStatusChanged()), this, SLOT(onLoopStatusChanged())); + connect(parent, SIGNAL(maximumRateChanged()), this, SLOT(onMaximumRateChanged())); + connect(parent, SIGNAL(metadataChanged()), this, SLOT(onMetadataChanged())); + connect(parent, SIGNAL(minimumRateChanged()), this, SLOT(onMinimumRateChanged())); + connect(parent, SIGNAL(playbackStatusChanged()), this, SLOT(onPlaybackStatusChanged())); + // PositionChanged signal is not forwarded through DBus ... + connect(parent, SIGNAL(rateChanged()), this, SLOT(onRateChanged())); + connect(parent, SIGNAL(shuffleChanged()), this, SLOT(onShuffleChanged())); + connect(parent, SIGNAL(volumeChanged()), this, SLOT(onVolumeChanged())); + connect(parent, SIGNAL(seeked(qlonglong)), this, SIGNAL(Seeked(qlonglong))); +} + +MprisPlayerAdaptor::~MprisPlayerAdaptor() +{ + // destructor +} + +bool MprisPlayerAdaptor::canControl() const +{ + return static_cast(parent())->canControl(); +} + +bool MprisPlayerAdaptor::canGoNext() const +{ + MprisPlayer * const player = static_cast(parent()); + return player->canControl() && player->canGoNext(); +} + +bool MprisPlayerAdaptor::canGoPrevious() const +{ + MprisPlayer * const player = static_cast(parent()); + return player->canControl() && player->canGoPrevious(); +} + +bool MprisPlayerAdaptor::canPause() const +{ + MprisPlayer * const player = static_cast(parent()); + return player->canControl() && player->canPause(); +} + +bool MprisPlayerAdaptor::canPlay() const +{ + MprisPlayer * const player = static_cast(parent()); + return player->canControl() && player->canPlay(); +} + +bool MprisPlayerAdaptor::canSeek() const +{ + MprisPlayer * const player = static_cast(parent()); + return player->canControl() && player->canSeek(); +} + +QString MprisPlayerAdaptor::loopStatus() const +{ + return Mpris::enumerationToString(static_cast(parent())->loopStatus()); +} + +void MprisPlayerAdaptor::setLoopStatus(const QString &value) +{ + MprisPlayer * const player = static_cast(parent()); + if (player->canControl()) { + Q_EMIT player->loopStatusRequested(Mpris::enumerationFromString(value)); + return; + } + + // We cannot send an error reply in a property setter so we just + // complain here + qDebug() << Q_FUNC_INFO << "Requested to modify the loop status but it is not supported."; +} + +double MprisPlayerAdaptor::maximumRate() const +{ + return static_cast(parent())->maximumRate(); +} + +QVariantMap MprisPlayerAdaptor::metadata() const +{ + return static_cast(parent())->metadata(); +} + +double MprisPlayerAdaptor::minimumRate() const +{ + return static_cast(parent())->minimumRate(); +} + +QString MprisPlayerAdaptor::playbackStatus() const +{ + return Mpris::enumerationToString(static_cast(parent())->playbackStatus()); +} + +qlonglong MprisPlayerAdaptor::position() const +{ + return static_cast(parent())->position(); +} + +double MprisPlayerAdaptor::rate() const +{ + return static_cast(parent())->rate(); +} + +void MprisPlayerAdaptor::setRate(double value) +{ + MprisPlayer * const player = static_cast(parent()); + QString errorMessage; + + if (!player->canControl()) { + errorMessage = QStringLiteral("Requested to modify the rate but it is not supported."); + } else if (player->minimumRate() > value) { + errorMessage = QStringLiteral("Requested to modify the rate under its minimum allowed value."); + } else if (player->maximumRate() < value) { + errorMessage = QStringLiteral("Requested to modify the rate over its maximum allowed value."); + } + + if (errorMessage.isEmpty()) { + if (value == 0) { + Q_EMIT player->pauseRequested(); + } else { + Q_EMIT player->rateRequested(value); + } + return; + } + + // We cannot send an error reply in a property setter so we just + // complain here + qDebug() << Q_FUNC_INFO << errorMessage; +} + +bool MprisPlayerAdaptor::shuffle() const +{ + return static_cast(parent())->shuffle(); +} + +void MprisPlayerAdaptor::setShuffle(bool value) +{ + MprisPlayer * const player = static_cast(parent()); + if (player->canControl()) { + Q_EMIT player->shuffleRequested(value); + return; + } + + // We cannot send an error reply in a property setter so we just + // complain here + qDebug() << Q_FUNC_INFO << "Requested to modify the shuffle but it is not supported."; +} + +double MprisPlayerAdaptor::volume() const +{ + return static_cast(parent())->volume(); +} + +void MprisPlayerAdaptor::setVolume(double value) +{ + MprisPlayer * const player = static_cast(parent()); + if (player->canControl()) { + Q_EMIT player->volumeRequested(value < 0 ? 0 : value); + return; + } + + // We cannot send an error reply in a property setter so we just + // complain here + qDebug() << Q_FUNC_INFO << "Requested to modify the volume but it is not supported."; +} + +void MprisPlayerAdaptor::Next() +{ + MprisPlayer * const player = static_cast(parent()); + if (!player->canControl()) { + player->sendErrorReply(QDBusError::NotSupported, QStringLiteral("Wanted to move to next track but it is not supported.")); + return; + } + + if (!player->canGoNext()) { + return; + } + + Q_EMIT player->nextRequested(); +} + +void MprisPlayerAdaptor::OpenUri(const QString &Uri) +{ + MprisPlayer * const player = static_cast(parent()); + if (!player->canControl()) { + player->sendErrorReply(QDBusError::NotSupported, QStringLiteral("Wanted to open an url but it is not supported.")); + return; + } + + QUrl url(Uri, QUrl::StrictMode); + if (!url.isValid()) { + player->sendErrorReply(QDBusError::InvalidArgs, QStringLiteral("Wanted to open an url but the url is invalid.")); + } + + if (!player->supportedUriSchemes().contains(url.scheme())) { + player->sendErrorReply(QDBusError::NotSupported, QStringLiteral("Wanted to open an url but the scheme is not supported.")); + } + + QMimeDatabase db; + QMimeType mime; + if (url.isLocalFile()) { + mime = db.mimeTypeForFile(url.toLocalFile()); + } else { + mime = db.mimeTypeForFile(url.fileName(), QMimeDatabase::MatchExtension); + } + QStringList mimeNames = mime.aliases(); + mimeNames.prepend(mime.name()); + for (int i = 0; i < mimeNames.size(); i++) { + if (player->supportedMimeTypes().contains(mimeNames[i])) { + Q_EMIT player->openUriRequested(url); + return; + } + } + + player->sendErrorReply(QDBusError::NotSupported, QStringLiteral("Wanted to open an url but the mime type is not supported.")); +} + +void MprisPlayerAdaptor::Pause() +{ + MprisPlayer * const player = static_cast(parent()); + if (!player->canControl()) { + player->sendErrorReply(QDBusError::NotSupported, QStringLiteral("Wanted to pause but it is not supported.")); + return; + } + + if (!player->canPause()) { + return; + } + + switch (player->playbackStatus()) { + case Mpris::Playing: + case Mpris::Stopped: + Q_EMIT player->pauseRequested(); + break; + case Mpris::Paused: + default: + // Nothing to do + break; + } +} + +void MprisPlayerAdaptor::Play() +{ + MprisPlayer * const player = static_cast(parent()); + if (!player->canControl()) { + player->sendErrorReply(QDBusError::NotSupported, QStringLiteral("Wanted to play but it is not supported.")); + return; + } + + if (!player->canPlay()) { + return; + } + + switch (player->playbackStatus()) { + case Mpris::Stopped: + case Mpris::Paused: + Q_EMIT player->playRequested(); + break; + case Mpris::Playing: + default: + // Nothing to do + break; + } +} + +void MprisPlayerAdaptor::PlayPause() +{ + MprisPlayer * const player = static_cast(parent()); + if (!player->canControl()) { + player->sendErrorReply(QDBusError::NotSupported, QStringLiteral("Wanted to play or pause but it is not supported.")); + return; + } + + switch (player->playbackStatus()) { + case Mpris::Playing: + if (!player->canPause()) { + player->sendErrorReply(QDBusError::NotSupported, QStringLiteral("Wanted to pause but it is not supported.")); + return; + } + + Q_EMIT player->pauseRequested(); + break; + case Mpris::Stopped: + case Mpris::Paused: + if (!player->canPlay()) { + player->sendErrorReply(QDBusError::NotSupported, QStringLiteral("Wanted to play but it is not supported.")); + return; + } + + Q_EMIT player->playRequested(); + break; + default: + // Nothing to do + break; + } +} + +void MprisPlayerAdaptor::Previous() +{ + MprisPlayer * const player = static_cast(parent()); + if (!player->canControl()) { + player->sendErrorReply(QDBusError::NotSupported, QStringLiteral("Wanted to move to previous track but it is not supported.")); + return; + } + + if (!player->canGoPrevious()) { + return; + } + + Q_EMIT player->previousRequested(); +} + +void MprisPlayerAdaptor::Seek(qlonglong Offset) +{ + MprisPlayer * const player = static_cast(parent()); + if (!player->canControl()) { + player->sendErrorReply(QDBusError::NotSupported, QStringLiteral("Wanted to seek but it is not supported.")); + return; + } + + if (!player->canSeek() || Offset == 0) { + return; + } + + if (Offset < 0) { + Q_EMIT player->seekRequested(Offset); + return; + } + + QVariantMap metadata = player->metadata(); + QVariant length = metadata[Mpris::metadataToString(Mpris::Length)]; + if (length.isValid() && (player->position() + Offset) > length.toLongLong()) { + Q_EMIT player->nextRequested(); + return; + } + + Q_EMIT player->seekRequested(Offset); +} + +void MprisPlayerAdaptor::SetPosition(const QDBusObjectPath &TrackId, qlonglong Position) +{ + MprisPlayer * const player = static_cast(parent()); + if (!player->canControl()) { + player->sendErrorReply(QDBusError::NotSupported, QStringLiteral("Wanted to move to position but it is not supported.")); + return; + } + + if (!player->canSeek()) { + return; + } + + QVariantMap metadata = player->metadata(); + QVariant trackId = metadata[Mpris::metadataToString(Mpris::TrackId)]; + QVariant length = metadata[Mpris::metadataToString(Mpris::Length)]; + if (!trackId.isValid() || !length.isValid()) { + return; + } + + if (trackId.value() != TrackId) { + player->sendErrorReply(QDBusError::InvalidArgs, QStringLiteral("Wanted to move to position but the TrackId is not the current one.")); + return; + } + + if (Position > length.toLongLong()) { + player->sendErrorReply(QDBusError::InvalidArgs, QStringLiteral("Wanted to move to position but the position is off range.")); + return; + } + + Q_EMIT player->setPositionRequested(TrackId, Position); +} + +void MprisPlayerAdaptor::Stop() +{ + MprisPlayer * const player = static_cast(parent()); + if (!player->canControl()) { + player->sendErrorReply(QDBusError::NotSupported, QStringLiteral("Wanted to stop but it is not supported.")); + return; + } + + switch (player->playbackStatus()) { + case Mpris::Playing: + case Mpris::Paused: + Q_EMIT player->stopRequested(); + break; + case Mpris::Stopped: + default: + // Nothing to do + break; + } +} + + +// Private +void MprisPlayerAdaptor::onCanControlChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + // canControlChanged signal is not forwarded through DBus, but + // we can notify about the rest of "Can*" properties + + QVariantMap changedProperties; + if (player->canGoNext()) { + changedProperties[QStringLiteral("CanGoNext")] = QVariant(player->canControl()); + } + if (player->canGoPrevious()) { + changedProperties[QStringLiteral("CanGoPrevious")] = QVariant(player->canControl()); + } + if (player->canPause()) { + changedProperties[QStringLiteral("CanPause")] = QVariant(player->canControl()); + } + if (player->canPlay()) { + changedProperties[QStringLiteral("CanPlay")] = QVariant(player->canControl()); + } + if (player->canSeek()) { + changedProperties[QStringLiteral("CanSeek")] = QVariant(player->canControl()); + } + + if (changedProperties.isEmpty()) { + return; + } + + player->notifyPropertiesChanged(mprisPlayerInterface, changedProperties, QStringList()); +} + +void MprisPlayerAdaptor::onCanGoNextChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + if (!player->canControl()) { + return; + } + + QVariantMap changedProperties; + changedProperties[QStringLiteral("CanGoNext")] = QVariant(player->canGoNext()); + + player->notifyPropertiesChanged(mprisPlayerInterface, changedProperties, QStringList()); +} + +void MprisPlayerAdaptor::onCanGoPreviousChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + if (!player->canControl()) { + return; + } + + QVariantMap changedProperties; + changedProperties[QStringLiteral("CanGoPrevious")] = QVariant(player->canGoPrevious()); + + player->notifyPropertiesChanged(mprisPlayerInterface, changedProperties, QStringList()); +} + +void MprisPlayerAdaptor::onCanPauseChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + if (!player->canControl()) { + return; + } + + QVariantMap changedProperties; + changedProperties[QStringLiteral("CanPause")] = QVariant(player->canPause()); + + player->notifyPropertiesChanged(mprisPlayerInterface, changedProperties, QStringList()); +} + +void MprisPlayerAdaptor::onCanPlayChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + if (!player->canControl()) { + return; + } + + QVariantMap changedProperties; + changedProperties[QStringLiteral("CanPlay")] = QVariant(player->canPlay()); + + player->notifyPropertiesChanged(mprisPlayerInterface, changedProperties, QStringList()); +} + +void MprisPlayerAdaptor::onCanSeekChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + if (!player->canControl()) { + return; + } + + QVariantMap changedProperties; + changedProperties[QStringLiteral("CanSeek")] = QVariant(player->canSeek()); + + player->notifyPropertiesChanged(mprisPlayerInterface, changedProperties, QStringList()); +} + +void MprisPlayerAdaptor::onLoopStatusChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + QVariantMap changedProperties; + changedProperties[QStringLiteral("LoopStatus")] = QVariant(Mpris::enumerationToString(player->loopStatus())); + + player->notifyPropertiesChanged(mprisPlayerInterface, changedProperties, QStringList()); +} + +void MprisPlayerAdaptor::onMaximumRateChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + if (player->maximumRate() < 1) { + qmlInfo(this) << "Maximum rate should be equal or above 1"; + return; + } + + QVariantMap changedProperties; + changedProperties[QStringLiteral("MaximumRate")] = QVariant(player->maximumRate()); + + player->notifyPropertiesChanged(mprisPlayerInterface, changedProperties, QStringList()); +} + +void MprisPlayerAdaptor::onMetadataChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + QVariantMap changedProperties; + changedProperties[QStringLiteral("Metadata")] = QVariant(player->metadata()); + + player->notifyPropertiesChanged(mprisPlayerInterface, changedProperties, QStringList()); +} + +void MprisPlayerAdaptor::onMinimumRateChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + if (player->minimumRate() > 1) { + qmlInfo(this) << "Minimum rate should be equal or less than 1"; + return; + } + + QVariantMap changedProperties; + changedProperties[QStringLiteral("MinimumRate")] = QVariant(player->minimumRate() < 0 ? 0 : player->minimumRate()); + + player->notifyPropertiesChanged(mprisPlayerInterface, changedProperties, QStringList()); +} + +void MprisPlayerAdaptor::onPlaybackStatusChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + QVariantMap changedProperties; + changedProperties[QStringLiteral("PlaybackStatus")] = QVariant(Mpris::enumerationToString(player->playbackStatus())); + + player->notifyPropertiesChanged(mprisPlayerInterface, changedProperties, QStringList()); +} + +void MprisPlayerAdaptor::onRateChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + if (player->rate() <= 0 || player->rate() < player->minimumRate() || player->rate() > player->maximumRate()) { + qmlInfo(this) << "Rate should never be negative or out of the minimum and maximum limits"; + return; + } + + QVariantMap changedProperties; + changedProperties[QStringLiteral("Rate")] = QVariant(player->rate()); + + player->notifyPropertiesChanged(mprisPlayerInterface, changedProperties, QStringList()); +} + +void MprisPlayerAdaptor::onShuffleChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + QVariantMap changedProperties; + changedProperties[QStringLiteral("Shuffle")] = QVariant(player->shuffle()); + + player->notifyPropertiesChanged(mprisPlayerInterface, changedProperties, QStringList()); +} + +void MprisPlayerAdaptor::onVolumeChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + QVariantMap changedProperties; + changedProperties[QStringLiteral("Volume")] = QVariant(player->volume() < 0 ? 0 : player->volume()); + + player->notifyPropertiesChanged(mprisPlayerInterface, changedProperties, QStringList()); +} diff --git a/mpris/mprisplayerinterface.cpp b/mpris/mprisplayerinterface.cpp new file mode 100644 index 0000000..a640f14 --- /dev/null +++ b/mpris/mprisplayerinterface.cpp @@ -0,0 +1,159 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "mpriscontroller_p.h" + +#include "mpris.h" + +#include + + +/* + * Implementation of interface class MprisPlayerInterface + */ + +MprisPlayerInterface::MprisPlayerInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) + : DBusExtendedAbstractInterface(service, path, staticInterfaceName(), connection, parent) + , m_canControl(false) + , m_canGoNext(false) + , m_canGoPrevious(false) + , m_canPause(false) + , m_canPlay(false) + , m_canSeek(false) + , m_loopStatus(Mpris::None) + , m_maximumRate(1) + , m_minimumRate(1) + , m_playbackStatus(Mpris::Stopped) + , m_position(0) + , m_rate(1) + , m_shuffle(false) + , m_volume(0) +{ + connect(this, SIGNAL(propertyChanged(QString, QVariant)), this, SLOT(onPropertyChanged(QString, QVariant))); +} + +MprisPlayerInterface::~MprisPlayerInterface() +{ +} + +void MprisPlayerInterface::onPropertyChanged(const QString &propertyName, const QVariant &value) +{ + if (propertyName == QStringLiteral("CanControl")) { + bool canControl = value.toBool(); + if (m_canControl != canControl) { + m_canControl = canControl; + Q_EMIT canControlChanged(m_canControl); + } + } else if (propertyName == QStringLiteral("CanGoNext")) { + bool canGoNext = value.toBool(); + if (m_canGoNext != canGoNext) { + m_canGoNext = canGoNext; + Q_EMIT canGoNextChanged(m_canGoNext); + } + } else if (propertyName == QStringLiteral("CanGoPrevious")) { + bool canGoPrevious = value.toBool(); + if (m_canGoPrevious != canGoPrevious) { + m_canGoPrevious = canGoPrevious; + Q_EMIT canGoPreviousChanged(m_canGoPrevious); + } + } else if (propertyName == QStringLiteral("CanPause")) { + bool canPause = value.toBool(); + if (m_canPause != canPause) { + m_canPause = canPause; + Q_EMIT canPauseChanged(m_canPause); + } + } else if (propertyName == QStringLiteral("CanPlay")) { + bool canPlay = value.toBool(); + if (m_canPlay != canPlay) { + m_canPlay = canPlay; + Q_EMIT canPlayChanged(m_canPlay); + } + } else if (propertyName == QStringLiteral("CanSeek")) { + bool canSeek = value.toBool(); + if (m_canSeek != canSeek) { + m_canSeek = canSeek; + Q_EMIT canSeekChanged(m_canSeek); + } + } else if (propertyName == QStringLiteral("LoopStatus")) { + QString loopStatus = value.toString(); + if (m_loopStatus != loopStatus) { + m_loopStatus = loopStatus; + Q_EMIT loopStatusChanged(m_loopStatus); + } + } else if (propertyName == QStringLiteral("MaximumRate")) { + bool maximumRate = value.toDouble(); + if (m_maximumRate != maximumRate) { + m_maximumRate = maximumRate; + Q_EMIT maximumRateChanged(m_maximumRate); + } + } else if (propertyName == QStringLiteral("Metadata")) { + QVariantMap metadata = value.toMap(); + if (m_metadata != metadata) { + m_metadata = metadata; + Q_EMIT metadataChanged(m_metadata); + } + } else if (propertyName == QStringLiteral("MinimumRate")) { + double minimumRate = value.toDouble(); + if (m_minimumRate != minimumRate) { + m_minimumRate = minimumRate; + Q_EMIT minimumRateChanged(m_minimumRate); + } + } else if (propertyName == QStringLiteral("PlaybackStatus")) { + QString playbackStatus = value.toString(); + if (m_playbackStatus != playbackStatus) { + m_playbackStatus = playbackStatus; + Q_EMIT playbackStatusChanged(m_playbackStatus); + } + } else if (propertyName == QStringLiteral("Position")) { + qlonglong position = value.toLongLong(); + if (m_position != position) { + m_position = position; + Q_EMIT positionChanged(m_position); + } + } else if (propertyName == QStringLiteral("Rate")) { + double rate = value.toDouble(); + if (m_rate != rate) { + m_rate = rate; + Q_EMIT rateChanged(m_rate); + } + } else if (propertyName == QStringLiteral("Shuffle")) { + bool shuffle = value.toBool(); + if (m_shuffle != shuffle) { + m_shuffle = shuffle; + Q_EMIT shuffleChanged(m_shuffle); + } + } else if (propertyName == QStringLiteral("Volume")) { + double volume = value.toDouble(); + if (m_volume != volume) { + m_volume = volume; + Q_EMIT volumeChanged(m_volume); + } + } else { + qWarning() << Q_FUNC_INFO + << "Received PropertyChanged signal from unknown property: " + << propertyName; + } +} + diff --git a/mpris/mprisqt.h b/mpris/mprisqt.h new file mode 100644 index 0000000..c53627d --- /dev/null +++ b/mpris/mprisqt.h @@ -0,0 +1,35 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef MPRIS_QT_H +#define MPRIS_QT_H + +#if defined(MPRIS_QT_LIBRARY) +# define MPRIS_QT_EXPORT Q_DECL_EXPORT +#else +# define MPRIS_QT_EXPORT Q_DECL_IMPORT +#endif + +#endif /* MPRIS_QT_H */ diff --git a/mpris/mprisrootadaptor.cpp b/mpris/mprisrootadaptor.cpp new file mode 100644 index 0000000..8a6caf8 --- /dev/null +++ b/mpris/mprisrootadaptor.cpp @@ -0,0 +1,244 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "mprisplayer_p.h" + +#include "mprisplayer.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Implementation of adaptor class MprisRootAdaptor + */ + +static const QString mprisRootInterface = QStringLiteral("org.mpris.MediaPlayer2"); + + +MprisRootAdaptor::MprisRootAdaptor(MprisPlayer *parent) + : QDBusAbstractAdaptor(parent) +{ + // constructor + setAutoRelaySignals(false); + + connect(parent, SIGNAL(canQuitChanged()), this, SLOT(onCanQuitChanged())); + connect(parent, SIGNAL(canRaiseChanged()), this, SLOT(onCanRaiseChanged())); + connect(parent, SIGNAL(canSetFullscreenChanged()), this, SLOT(onCanSetFullscreenChanged())); + connect(parent, SIGNAL(desktopEntryChanged()), this, SLOT(onDesktopEntryChanged())); + connect(parent, SIGNAL(fullscreenChanged()), this, SLOT(onFullscreenChanged())); + connect(parent, SIGNAL(hasTrackListChanged()), this, SLOT(onHasTrackListChanged())); + connect(parent, SIGNAL(identityChanged()), this, SLOT(onIdentityChanged())); + connect(parent, SIGNAL(supportedUriSchemesChanged()), this, SLOT(onSupportedUriSchemesChanged())); + connect(parent, SIGNAL(supportedMimeTypesChanged()), this, SLOT(onSupportedMimeTypesChanged())); +} + +MprisRootAdaptor::~MprisRootAdaptor() +{ + // destructor +} + +bool MprisRootAdaptor::canQuit() const +{ + return static_cast(parent())->canQuit(); +} + +bool MprisRootAdaptor::canRaise() const +{ + return static_cast(parent())->canRaise(); +} + +bool MprisRootAdaptor::canSetFullscreen() const +{ + return static_cast(parent())->canSetFullscreen(); +} + +QString MprisRootAdaptor::desktopEntry() const +{ + return static_cast(parent())->desktopEntry(); +} + +bool MprisRootAdaptor::fullscreen() const +{ + return static_cast(parent())->fullscreen(); +} + +void MprisRootAdaptor::setFullscreen(bool value) +{ + MprisPlayer * const player = static_cast(parent()); + if (player->canSetFullscreen()) { + Q_EMIT player->fullscreenRequested(value); + return; + } + + // We cannot send an error reply in a property setter so we just + // complain here + if (value) { + qDebug() << Q_FUNC_INFO << "Requested to fullscreen, but not supported"; + } else { + qDebug() << Q_FUNC_INFO << "Requested to unfullscreen, but not supported"; + } +} + +bool MprisRootAdaptor::hasTrackList() const +{ + return static_cast(parent())->hasTrackList(); +} + +QString MprisRootAdaptor::identity() const +{ + return static_cast(parent())->identity(); +} + +QStringList MprisRootAdaptor::supportedMimeTypes() const +{ + return static_cast(parent())->supportedMimeTypes(); +} + +QStringList MprisRootAdaptor::supportedUriSchemes() const +{ + return static_cast(parent())->supportedUriSchemes(); +} + +void MprisRootAdaptor::Quit() +{ + MprisPlayer * const player = static_cast(parent()); + if (player->canQuit()) { + Q_EMIT player->quitRequested(); + return; + } + + player->sendErrorReply(QDBusError::NotSupported, QStringLiteral("Quit requested but not supported.")); +} + +void MprisRootAdaptor::Raise() +{ + MprisPlayer * const player = static_cast(parent()); + if (player->canRaise()) { + Q_EMIT player->raiseRequested(); + return; + } + + player->sendErrorReply(QDBusError::NotSupported, QStringLiteral("Raise requested but not supported.")); +} + + +// Private +void MprisRootAdaptor::onCanQuitChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + QVariantMap changedProperties; + changedProperties[QStringLiteral("CanQuit")] = QVariant(player->canQuit()); + + player->notifyPropertiesChanged(mprisRootInterface, changedProperties, QStringList()); +} + +void MprisRootAdaptor::onCanRaiseChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + QVariantMap changedProperties; + changedProperties[QStringLiteral("CanRaise")] = QVariant(player->canRaise()); + + player->notifyPropertiesChanged(mprisRootInterface, changedProperties, QStringList()); +} + +void MprisRootAdaptor::onCanSetFullscreenChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + QVariantMap changedProperties; + changedProperties[QStringLiteral("CanSetFullscreen")] = QVariant(player->canSetFullscreen()); + + player->notifyPropertiesChanged(mprisRootInterface, changedProperties, QStringList()); +} + +void MprisRootAdaptor::onDesktopEntryChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + QVariantMap changedProperties; + changedProperties[QStringLiteral("DesktopEntry")] = QVariant(player->desktopEntry()); + + player->notifyPropertiesChanged(mprisRootInterface, changedProperties, QStringList()); +} + +void MprisRootAdaptor::onFullscreenChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + QVariantMap changedProperties; + changedProperties[QStringLiteral("Fullscreen")] = QVariant(player->fullscreen()); + + player->notifyPropertiesChanged(mprisRootInterface, changedProperties, QStringList()); +} + +void MprisRootAdaptor::onHasTrackListChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + QVariantMap changedProperties; + changedProperties[QStringLiteral("HasTrackList")] = QVariant(player->hasTrackList()); + + player->notifyPropertiesChanged(mprisRootInterface, changedProperties, QStringList()); +} + +void MprisRootAdaptor::onIdentityChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + QVariantMap changedProperties; + changedProperties[QStringLiteral("Identity")] = QVariant(player->identity()); + + player->notifyPropertiesChanged(mprisRootInterface, changedProperties, QStringList()); +} + +void MprisRootAdaptor::onSupportedUriSchemesChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + QVariantMap changedProperties; + changedProperties[QStringLiteral("SupportedUriSchemes")] = QVariant(player->supportedUriSchemes()); + + player->notifyPropertiesChanged(mprisRootInterface, changedProperties, QStringList()); +} + +void MprisRootAdaptor::onSupportedMimeTypesChanged() const +{ + MprisPlayer * const player = static_cast(parent()); + + QVariantMap changedProperties; + changedProperties[QStringLiteral("SupportedMimeTypes")] = QVariant(player->supportedMimeTypes()); + + player->notifyPropertiesChanged(mprisRootInterface, changedProperties, QStringList()); +} diff --git a/mpris/mprisrootinterface.cpp b/mpris/mprisrootinterface.cpp new file mode 100644 index 0000000..95eeae0 --- /dev/null +++ b/mpris/mprisrootinterface.cpp @@ -0,0 +1,112 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "mpriscontroller_p.h" + +#include + + +/* + * Implementation of interface class MprisRootInterface + */ + +MprisRootInterface::MprisRootInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) + : DBusExtendedAbstractInterface(service, path, staticInterfaceName(), connection, parent) + , m_canQuit(false) + , m_canRaise(false) + , m_canSetFullscreen(false) + , m_fullscreen(false) + , m_hasTrackList(false) +{ + connect(this, SIGNAL(propertyChanged(QString, QVariant)), this, SLOT(onPropertyChanged(QString, QVariant))); +} + +MprisRootInterface::~MprisRootInterface() +{ +} + +void MprisRootInterface::onPropertyChanged(const QString &propertyName, const QVariant &value) +{ + if (propertyName == QStringLiteral("CanQuit")) { + bool canQuit = value.toBool(); + if (m_canQuit != canQuit) { + m_canQuit = canQuit; + Q_EMIT canQuitChanged(m_canQuit); + } + } else if (propertyName == QStringLiteral("CanRaise")) { + bool canRaise = value.toBool(); + if (m_canRaise != canRaise) { + m_canRaise = canRaise; + Q_EMIT canRaiseChanged(m_canRaise); + } + } else if (propertyName == QStringLiteral("CanSetFullscreen")) { + bool canSetFullscreen = value.toBool(); + if (m_canSetFullscreen != canSetFullscreen) { + m_canSetFullscreen = canSetFullscreen; + Q_EMIT canSetFullscreenChanged(m_canSetFullscreen); + } + } else if (propertyName == QStringLiteral("DesktopEntry")) { + QString desktopEntry = value.toString(); + if (m_desktopEntry != desktopEntry) { + m_desktopEntry = desktopEntry; + Q_EMIT desktopEntryChanged(m_desktopEntry); + } + } else if (propertyName == QStringLiteral("Fullscreen")) { + bool fullscreen = value.toBool(); + if (m_fullscreen != fullscreen) { + m_fullscreen = fullscreen; + Q_EMIT fullscreenChanged(m_fullscreen); + } + } else if (propertyName == QStringLiteral("HasTrackList")) { + bool hasTrackList = value.toBool(); + if (m_hasTrackList != hasTrackList) { + m_hasTrackList = hasTrackList; + Q_EMIT hasTrackListChanged(m_hasTrackList); + } + } else if (propertyName == QStringLiteral("Identity")) { + QString identity= value.toString(); + if (m_identity != identity) { + m_identity = identity; + Q_EMIT identityChanged(m_identity); + } + } else if (propertyName == QStringLiteral("SupportedMimeTypes")) { + QStringList supportedUriSchemes = value.toStringList(); + if (m_supportedUriSchemes != supportedUriSchemes) { + m_supportedUriSchemes = supportedUriSchemes; + Q_EMIT supportedMimeTypesChanged(m_supportedUriSchemes); + } + } else if (propertyName == QStringLiteral("SupportedUriSchemes")) { + QStringList supportedMimeTypes = value.toStringList(); + if (m_supportedMimeTypes != supportedMimeTypes) { + m_supportedMimeTypes = supportedMimeTypes; + Q_EMIT supportedUriSchemesChanged(m_supportedMimeTypes); + } + } else { + qWarning() << Q_FUNC_INFO + << "Received PropertyChanged signal from unknown property: " + << propertyName; + } +} + diff --git a/mpris/org.mpris.MediaPlayer2.Player.xml b/mpris/org.mpris.MediaPlayer2.Player.xml new file mode 100644 index 0000000..9833a9f --- /dev/null +++ b/mpris/org.mpris.MediaPlayer2.Player.xml @@ -0,0 +1,509 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mpris/org.mpris.MediaPlayer2.xml b/mpris/org.mpris.MediaPlayer2.xml new file mode 100644 index 0000000..1bf0b0c --- /dev/null +++ b/mpris/org.mpris.MediaPlayer2.xml @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mpris/qmldir b/mpris/qmldir new file mode 100644 index 0000000..46e6521 --- /dev/null +++ b/mpris/qmldir @@ -0,0 +1,2 @@ +module Cutefish.Mpris +plugin cutefishmpris_qmlplugins \ No newline at end of file diff --git a/mpris/qmlplugins.cpp b/mpris/qmlplugins.cpp new file mode 100644 index 0000000..297ae90 --- /dev/null +++ b/mpris/qmlplugins.cpp @@ -0,0 +1,21 @@ +#include +#include + +#include "mpris.h" +#include "mprisplayer.h" +#include "mprismanager.h" + +class QmlPlugins : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char * uri) override { + qmlRegisterSingletonType(uri, 1, 0, "Mpris", Mpris::api_factory); + qmlRegisterType(uri, 1, 0, "MprisPlayer"); + qmlRegisterType(uri, 1, 0, "MprisManager"); + } +}; + +#include "qmlplugins.moc" \ No newline at end of file diff --git a/mpris/qtdbusextended/dbusextended.h b/mpris/qtdbusextended/dbusextended.h new file mode 100644 index 0000000..6b27aa2 --- /dev/null +++ b/mpris/qtdbusextended/dbusextended.h @@ -0,0 +1,35 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef QT_DBUS_EXTENDED_H +#define QT_DBUS_EXTENDED_H + +#if defined(QT_DBUS_EXTENDED_LIBRARY) +# define QT_DBUS_EXTENDED_EXPORT Q_DECL_EXPORT +#else +# define QT_DBUS_EXTENDED_EXPORT Q_DECL_IMPORT +#endif + +#endif /* QT_DBUS_EXTENDED_H */ diff --git a/mpris/qtdbusextended/dbusextendedabstractinterface.cpp b/mpris/qtdbusextended/dbusextendedabstractinterface.cpp new file mode 100644 index 0000000..591343a --- /dev/null +++ b/mpris/qtdbusextended/dbusextendedabstractinterface.cpp @@ -0,0 +1,439 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "dbusextendedpendingcallwatcher_p.h" + +#include "dbusextendedabstractinterface.h" + +#include +#include +#include +#include +#include + +#include +#include + + +Q_GLOBAL_STATIC_WITH_ARGS(QByteArray, dBusPropertiesInterface, ("org.freedesktop.DBus.Properties")) +Q_GLOBAL_STATIC_WITH_ARGS(QByteArray, dBusPropertiesChangedSignal, ("PropertiesChanged")) +Q_GLOBAL_STATIC_WITH_ARGS(QByteArray, propertyChangedSignature, ("propertyChanged(QString,QVariant)")) +Q_GLOBAL_STATIC_WITH_ARGS(QByteArray, propertyInvalidatedSignature, ("propertyInvalidated(QString)")) + + +DBusExtendedAbstractInterface::DBusExtendedAbstractInterface(const QString &service, const QString &path, const char *interface, const QDBusConnection &connection, QObject *parent) + : QDBusAbstractInterface(service, path, interface, connection, parent) + , m_sync(false) + , m_useCache(false) + , m_getAllPendingCallWatcher(0) + , m_propertiesChangedConnected(false) +{ +} + +DBusExtendedAbstractInterface::~DBusExtendedAbstractInterface() +{ +} + +void DBusExtendedAbstractInterface::getAllProperties() +{ + m_lastExtendedError = QDBusError(); + + if (!isValid()) { + QString errorMessage = QStringLiteral("This Extended DBus interface is not valid yet."); + m_lastExtendedError = QDBusMessage::createError(QDBusError::Failed, errorMessage); + qDebug() << Q_FUNC_INFO << errorMessage; + return; + } + + if (!m_sync && m_getAllPendingCallWatcher) { + // Call already in place, not repeating ... + return; + } + + QDBusMessage msg = QDBusMessage::createMethodCall(service(), path(), *dBusPropertiesInterface(), QStringLiteral("GetAll")); + msg << interface(); + + if (m_sync) { + QDBusMessage reply = connection().call(msg); + + if (reply.type() != QDBusMessage::ReplyMessage) { + m_lastExtendedError = QDBusError(reply); + qWarning() << Q_FUNC_INFO << m_lastExtendedError.message(); + return; + } + + if (reply.signature() != QLatin1String("a{sv}")) { + QString errorMessage = QStringLiteral("Invalid signature \"%1\" in return from call to %2") + .arg(reply.signature(), + QString(*dBusPropertiesInterface())); + qWarning() << Q_FUNC_INFO << errorMessage; + m_lastExtendedError = QDBusError(QDBusError::InvalidSignature, errorMessage); + return; + } + + QVariantMap value = reply.arguments().at(0).toMap(); + onPropertiesChanged(interface(), value, QStringList()); + } else { + QDBusPendingReply async = connection().asyncCall(msg); + m_getAllPendingCallWatcher = new QDBusPendingCallWatcher(async, this); + + connect(m_getAllPendingCallWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(onAsyncGetAllPropertiesFinished(QDBusPendingCallWatcher*))); + return; + } +} + +void DBusExtendedAbstractInterface::connectNotify(const QMetaMethod &signal) +{ + if (signal.methodType() == QMetaMethod::Signal + && (signal.methodSignature() == *propertyChangedSignature() + || signal.methodSignature() == *propertyInvalidatedSignature())) { + if (!m_propertiesChangedConnected) { + QStringList argumentMatch; + argumentMatch << interface(); + connection().connect(service(), path(), *dBusPropertiesInterface(), *dBusPropertiesChangedSignal(), + argumentMatch, QString(), + this, SLOT(onPropertiesChanged(QString, QVariantMap, QStringList))); + + m_propertiesChangedConnected = true; + return; + } + } else { + QDBusAbstractInterface::connectNotify(signal); + } +} + +void DBusExtendedAbstractInterface::disconnectNotify(const QMetaMethod &signal) +{ + if (signal.methodType() == QMetaMethod::Signal + && (signal.methodSignature() == *propertyChangedSignature() + || signal.methodSignature() == *propertyInvalidatedSignature())) { + if (m_propertiesChangedConnected + && 0 == receivers(propertyChangedSignature()->constData()) + && 0 == receivers(propertyInvalidatedSignature()->constData())) { + QStringList argumentMatch; + argumentMatch << interface(); + connection().disconnect(service(), path(), *dBusPropertiesInterface(), *dBusPropertiesChangedSignal(), + argumentMatch, QString(), + this, SLOT(onPropertiesChanged(QString, QVariantMap, QStringList))); + + m_propertiesChangedConnected = false; + return; + } + } else { + QDBusAbstractInterface::disconnectNotify(signal); + } +} + +QVariant DBusExtendedAbstractInterface::internalPropGet(const char *propname, void *propertyPtr) +{ + m_lastExtendedError = QDBusError(); + + if (m_useCache) { + int propertyIndex = metaObject()->indexOfProperty(propname); + QMetaProperty metaProperty = metaObject()->property(propertyIndex); + return QVariant(metaProperty.type(), propertyPtr); + } + + if (m_sync) { + return property(propname); + } else { + if (!isValid()) { + QString errorMessage = QStringLiteral("This Extended DBus interface is not valid yet."); + m_lastExtendedError = QDBusMessage::createError(QDBusError::Failed, errorMessage); + qDebug() << Q_FUNC_INFO << errorMessage; + return QVariant(); + } + + int propertyIndex = metaObject()->indexOfProperty(propname); + + if (-1 == propertyIndex) { + QString errorMessage = QStringLiteral("Got unknown property \"%1\" to read") + .arg(QString::fromLatin1(propname)); + m_lastExtendedError = QDBusMessage::createError(QDBusError::Failed, errorMessage); + qWarning() << Q_FUNC_INFO << errorMessage; + return QVariant(); + } + + QMetaProperty metaProperty = metaObject()->property(propertyIndex); + + if (!metaProperty.isReadable()) { + QString errorMessage = QStringLiteral("Property \"%1\" is NOT readable") + .arg(QString::fromLatin1(propname)); + m_lastExtendedError = QDBusMessage::createError(QDBusError::Failed, errorMessage); + qWarning() << Q_FUNC_INFO << errorMessage; + return QVariant(); + } + + // is this metatype registered? + const char *expectedSignature = ""; + if (int(metaProperty.type()) != QMetaType::QVariant) { + expectedSignature = QDBusMetaType::typeToSignature(metaProperty.userType()); + if (0 == expectedSignature) { + QString errorMessage = + QStringLiteral("Type %1 must be registered with Qt D-Bus " + "before it can be used to read property " + "%2.%3") + .arg(metaProperty.typeName(), + interface(), + propname); + m_lastExtendedError = QDBusMessage::createError(QDBusError::Failed, errorMessage); + qWarning() << Q_FUNC_INFO << errorMessage; + return QVariant(); + } + } + + asyncProperty(propname); + return QVariant(metaProperty.type(), propertyPtr); + } +} + +void DBusExtendedAbstractInterface::internalPropSet(const char *propname, const QVariant &value, void *propertyPtr) +{ + m_lastExtendedError = QDBusError(); + + if (m_sync) { + setProperty(propname, value); + } else { + if (!isValid()) { + QString errorMessage = QStringLiteral("This interface is not yet valid"); + m_lastExtendedError = QDBusMessage::createError(QDBusError::Failed, errorMessage); + qDebug() << Q_FUNC_INFO << errorMessage; + return; + } + + int propertyIndex = metaObject()->indexOfProperty(propname); + + if (-1 == propertyIndex) { + QString errorMessage = QStringLiteral("Got unknown property \"%1\" to write") + .arg(QString::fromLatin1(propname)); + m_lastExtendedError = QDBusMessage::createError(QDBusError::Failed, errorMessage); + qWarning() << Q_FUNC_INFO << errorMessage; + return; + } + + QMetaProperty metaProperty = metaObject()->property(propertyIndex); + + if (!metaProperty.isWritable()) { + QString errorMessage = QStringLiteral("Property \"%1\" is NOT writable") + .arg(QString::fromLatin1(propname)); + m_lastExtendedError = QDBusMessage::createError(QDBusError::Failed, errorMessage); + qWarning() << Q_FUNC_INFO << errorMessage; + return; + } + + asyncSetProperty(propname, QVariant(metaProperty.type(), propertyPtr)); + } +} + +QVariant DBusExtendedAbstractInterface::asyncProperty(const QString &propertyName) +{ + QDBusMessage msg = QDBusMessage::createMethodCall(service(), path(), *dBusPropertiesInterface(), QStringLiteral("Get")); + msg << interface() << propertyName; + QDBusPendingReply async = connection().asyncCall(msg); + DBusExtendedPendingCallWatcher *watcher = new DBusExtendedPendingCallWatcher(async, propertyName, QVariant(), this); + + connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(onAsyncPropertyFinished(QDBusPendingCallWatcher*))); + + return QVariant(); +} + +void DBusExtendedAbstractInterface::asyncSetProperty(const QString &propertyName, const QVariant &value) +{ + QDBusMessage msg = QDBusMessage::createMethodCall(service(), path(), *dBusPropertiesInterface(), QStringLiteral("Set")); + msg << interface() << propertyName << value; + QDBusPendingReply async = connection().asyncCall(msg); + DBusExtendedPendingCallWatcher *watcher = new DBusExtendedPendingCallWatcher(async, propertyName, value, this); + + connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(onAsyncSetPropertyFinished(QDBusPendingCallWatcher*))); +} + +void DBusExtendedAbstractInterface::onAsyncPropertyFinished(DBusExtendedPendingCallWatcher *watcher) +{ + QDBusPendingReply reply = *watcher; + + if (reply.isError()) { + m_lastExtendedError = reply.error(); + } else { + int propertyIndex = metaObject()->indexOfProperty(watcher->asyncProperty().toLatin1().constData()); + QVariant value = demarshall(interface(), + metaObject()->property(propertyIndex), + reply.value(), + &m_lastExtendedError); + + if (m_lastExtendedError.isValid()) { + emit propertyInvalidated(watcher->asyncProperty()); + } else { + emit propertyChanged(watcher->asyncProperty(), value); + } + } + + emit asyncPropertyFinished(watcher->asyncProperty()); + watcher->deleteLater(); +} + +void DBusExtendedAbstractInterface::onAsyncSetPropertyFinished(DBusExtendedPendingCallWatcher *watcher) +{ + QDBusPendingReply reply = *watcher; + + if (reply.isError()) { + m_lastExtendedError = reply.error(); + } else { + m_lastExtendedError = QDBusError(); + } + + emit asyncSetPropertyFinished(watcher->asyncProperty()); + + // Resetting the property to its previous value after sending the + // finished signal + if (reply.isError()) { + m_lastExtendedError = QDBusError(); + emit propertyChanged(watcher->asyncProperty(), watcher->previousValue()); + } + + watcher->deleteLater(); +} + +void DBusExtendedAbstractInterface::onAsyncGetAllPropertiesFinished(QDBusPendingCallWatcher *watcher) +{ + m_getAllPendingCallWatcher = 0; + + QDBusPendingReply reply = *watcher; + + if (reply.isError()) { + m_lastExtendedError = reply.error(); + } else { + m_lastExtendedError = QDBusError(); + } + + emit asyncGetAllPropertiesFinished(); + + if (!reply.isError()) { + onPropertiesChanged(interface(), reply.value(), QStringList()); + } + + watcher->deleteLater(); +} + +void DBusExtendedAbstractInterface::onPropertiesChanged(const QString& interfaceName, + const QVariantMap& changedProperties, + const QStringList& invalidatedProperties) +{ + if (interfaceName == interface()) { + QVariantMap::const_iterator i = changedProperties.constBegin(); + while (i != changedProperties.constEnd()) { + int propertyIndex = metaObject()->indexOfProperty(i.key().toLatin1().constData()); + + if (-1 == propertyIndex) { + qDebug() << Q_FUNC_INFO << "Got unknown changed property" << i.key(); + } else { + QVariant value = demarshall(interface(), metaObject()->property(propertyIndex), i.value(), &m_lastExtendedError); + + if (m_lastExtendedError.isValid()) { + emit propertyInvalidated(i.key()); + } else { + emit propertyChanged(i.key(), value); + } + } + + ++i; + } + + QStringList::const_iterator j = invalidatedProperties.constBegin(); + while (j != invalidatedProperties.constEnd()) { + if (-1 == metaObject()->indexOfProperty(j->toLatin1().constData())) { + qDebug() << Q_FUNC_INFO << "Got unknown invalidated property" << *j; + } else { + m_lastExtendedError = QDBusError(); + emit propertyInvalidated(*j); + } + + ++j; + } + } +} + +QVariant DBusExtendedAbstractInterface::demarshall(const QString &interface, const QMetaProperty &metaProperty, const QVariant &value, QDBusError *error) +{ + Q_ASSERT(metaProperty.isValid()); + Q_ASSERT(error != 0); + + if (value.userType() == metaProperty.userType()) { + // No need demarshalling. Passing back straight away ... + *error = QDBusError(); + return value; + } + + QVariant result = QVariant(metaProperty.userType(), (void*)0); + QString errorMessage; + const char *expectedSignature = QDBusMetaType::typeToSignature(metaProperty.userType()); + + if (value.userType() == qMetaTypeId()) { + // demarshalling a DBus argument ... + QDBusArgument dbusArg = value.value(); + + if (expectedSignature == dbusArg.currentSignature().toLatin1()) { + QDBusMetaType::demarshall(dbusArg, metaProperty.userType(), result.data()); + if (!result.isValid()) { + errorMessage = QStringLiteral("Unexpected failure demarshalling " + "upon PropertiesChanged signal arrival " + "for property `%3.%4' (expected type `%5' (%6))") + .arg(interface, + QString::fromLatin1(metaProperty.name()), + QString::fromLatin1(metaProperty.typeName()), + expectedSignature); + } + } else { + errorMessage = QStringLiteral("Unexpected `user type' (%2) " + "upon PropertiesChanged signal arrival " + "for property `%3.%4' (expected type `%5' (%6))") + .arg(dbusArg.currentSignature(), + interface, + QString::fromLatin1(metaProperty.name()), + QString::fromLatin1(metaProperty.typeName()), + QString::fromLatin1(expectedSignature)); + } + } else { + const char *actualSignature = QDBusMetaType::typeToSignature(value.userType()); + + errorMessage = QStringLiteral("Unexpected `%1' (%2) " + "upon PropertiesChanged signal arrival " + "for property `%3.%4' (expected type `%5' (%6))") + .arg(QString::fromLatin1(value.typeName()), + QString::fromLatin1(actualSignature), + interface, + QString::fromLatin1(metaProperty.name()), + QString::fromLatin1(metaProperty.typeName()), + QString::fromLatin1(expectedSignature)); + } + + if (errorMessage.isEmpty()) { + *error = QDBusError(); + } else { + *error = QDBusMessage::createError(QDBusError::InvalidSignature, errorMessage); + qDebug() << Q_FUNC_INFO << errorMessage; + } + + return result; +} diff --git a/mpris/qtdbusextended/dbusextendedabstractinterface.h b/mpris/qtdbusextended/dbusextendedabstractinterface.h new file mode 100644 index 0000000..1728cbe --- /dev/null +++ b/mpris/qtdbusextended/dbusextendedabstractinterface.h @@ -0,0 +1,94 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef DBUSEXTENDEDABSTRACTINTERFACE_H +#define DBUSEXTENDEDABSTRACTINTERFACE_H + +#include "dbusextended.h" + +#include +#include + +class QDBusPendingCallWatcher; +class DBusExtendedPendingCallWatcher; + +class QT_DBUS_EXTENDED_EXPORT DBusExtendedAbstractInterface: public QDBusAbstractInterface +{ + Q_OBJECT + +public: + virtual ~DBusExtendedAbstractInterface(); + + Q_PROPERTY(bool sync READ sync WRITE setSync) + inline bool sync() const { return m_sync; } + inline void setSync(bool sync) { m_sync = sync; } + + Q_PROPERTY(bool useCache READ useCache WRITE setUseCache) + inline bool useCache() const { return m_useCache; } + inline void setUseCache(bool useCache) { m_useCache = useCache; } + + void getAllProperties(); + inline QDBusError lastExtendedError() const { return m_lastExtendedError; }; + +protected: + DBusExtendedAbstractInterface(const QString &service, + const QString &path, + const char *interface, + const QDBusConnection &connection, + QObject *parent); + + void connectNotify(const QMetaMethod &signal); + void disconnectNotify(const QMetaMethod &signal); + QVariant internalPropGet(const char *propname, void *propertyPtr); + void internalPropSet(const char *propname, const QVariant &value, void *propertyPtr); + +Q_SIGNALS: + void propertyChanged(const QString &propertyName, const QVariant &value); + void propertyInvalidated(const QString &propertyName); + void asyncPropertyFinished(const QString &propertyName); + void asyncSetPropertyFinished(const QString &propertyName); + void asyncGetAllPropertiesFinished(); + +private Q_SLOTS: + void onPropertiesChanged(const QString& interfaceName, + const QVariantMap& changedProperties, + const QStringList& invalidatedProperties); + void onAsyncPropertyFinished(DBusExtendedPendingCallWatcher *watcher); + void onAsyncSetPropertyFinished(DBusExtendedPendingCallWatcher *watcher); + void onAsyncGetAllPropertiesFinished(QDBusPendingCallWatcher *watcher); + +private: + QVariant asyncProperty(const QString &propertyName); + void asyncSetProperty(const QString &propertyName, const QVariant &value); + static QVariant demarshall(const QString &interface, const QMetaProperty &metaProperty, const QVariant &value, QDBusError *error); + + bool m_sync; + bool m_useCache; + QDBusPendingCallWatcher *m_getAllPendingCallWatcher; + QDBusError m_lastExtendedError; + bool m_propertiesChangedConnected; +}; + +#endif /* DBUSEXTENDEDABSTRACTINTERFACE_H */ diff --git a/mpris/qtdbusextended/dbusextendedpendingcallwatcher.cpp b/mpris/qtdbusextended/dbusextendedpendingcallwatcher.cpp new file mode 100644 index 0000000..a8404d9 --- /dev/null +++ b/mpris/qtdbusextended/dbusextendedpendingcallwatcher.cpp @@ -0,0 +1,38 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "dbusextendedpendingcallwatcher_p.h" + + +DBusExtendedPendingCallWatcher::DBusExtendedPendingCallWatcher(const QDBusPendingCall &call, const QString &asyncProperty, const QVariant &previousValue, QObject *parent) + : QDBusPendingCallWatcher(call, parent) + , m_asyncProperty(asyncProperty) + , m_previousValue(previousValue) +{ +} + +DBusExtendedPendingCallWatcher::~DBusExtendedPendingCallWatcher() +{ +} diff --git a/mpris/qtdbusextended/dbusextendedpendingcallwatcher_p.h b/mpris/qtdbusextended/dbusextendedpendingcallwatcher_p.h new file mode 100644 index 0000000..ff7a85d --- /dev/null +++ b/mpris/qtdbusextended/dbusextendedpendingcallwatcher_p.h @@ -0,0 +1,67 @@ +// -*- c++ -*- + +/*! + * + * Copyright (C) 2015 Jolla Ltd. + * + * Contact: Valerio Valerio + * Author: Andres Gomez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +// +// W A R N I N G +// ------------- +// +// This file is not part of the public API. This header file may +// change from version to version without notice, or even be +// removed. +// +// We mean it. +// +// + + +#ifndef DBUSEXTENDEDPENDINGCALLWATCHER_P_H +#define DBUSEXTENDEDPENDINGCALLWATCHER_P_H + +#include +#include + +class DBusExtendedPendingCallWatcher: public QDBusPendingCallWatcher +{ + Q_OBJECT + +public: + explicit DBusExtendedPendingCallWatcher(const QDBusPendingCall &call, + const QString &asyncProperty, + const QVariant &previousValue, + QObject *parent = 0); + ~DBusExtendedPendingCallWatcher(); + + Q_PROPERTY(QString AsyncProperty READ asyncProperty) + inline QString asyncProperty() const { return m_asyncProperty; } + + Q_PROPERTY(QVariant PreviousValue READ previousValue) + inline QVariant previousValue() const { return m_previousValue; } + +private: + QString m_asyncProperty; + QVariant m_previousValue; +}; + +#endif /* DBUSEXTENDEDPENDINGCALLWATCHER_P_H */ diff --git a/networkmanagement/CMakeLists.txt b/networkmanagement/CMakeLists.txt new file mode 100644 index 0000000..2041784 --- /dev/null +++ b/networkmanagement/CMakeLists.txt @@ -0,0 +1,53 @@ +set(NETWORKMGR_SRCS + appletproxymodel.cpp + appletproxymodel.h + + networkitemslist.cpp + networkitemslist.h + + networkmodel.cpp + networkmodel.h + + networkmodelitem.cpp + networkmodelitem.h + + networking.cpp + networking.h + + network.cpp + network.h + + wiressitemsettings.cpp + wiressitemsettings.h + + technologyproxymodel.cpp + technologyproxymodel.h + + uiutils.cpp + uiutils.h + + connectionicon.cpp + connectionicon.h + + qmlplugins.cpp + qmlplugins.h +) + +find_package(KF5NetworkManagerQt REQUIRED) +find_package(KF5ModemManagerQt REQUIRED) + +add_library(cutefishnm_qmlplugins SHARED ${NETWORKMGR_SRCS}) +generate_export_header(cutefishnm_qmlplugins EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/networkmanager_export.h BASE_NAME NetworkManager) + +target_link_libraries (cutefishnm_qmlplugins + Qt5::Core + Qt5::Quick + Qt5::Gui + Qt5::DBus + + KF5::NetworkManagerQt + KF5::ModemManagerQt +) + +install(TARGETS cutefishnm_qmlplugins DESTINATION ${INSTALL_QMLDIR}/Cutefish/NetworkManagement) +install(FILES qmldir DESTINATION ${INSTALL_QMLDIR}/Cutefish/NetworkManagement) \ No newline at end of file diff --git a/networkmanagement/appletproxymodel.cpp b/networkmanagement/appletproxymodel.cpp new file mode 100644 index 0000000..2b89388 --- /dev/null +++ b/networkmanagement/appletproxymodel.cpp @@ -0,0 +1,133 @@ +/* + Copyright 2013-2014 Jan Grulich + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "appletproxymodel.h" +#include "networkmodel.h" +#include "uiutils.h" + +AppletProxyModel::AppletProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + setDynamicSortFilter(true); + setFilterCaseSensitivity(Qt::CaseInsensitive); + sort(0, Qt::DescendingOrder); +} + +AppletProxyModel::~AppletProxyModel() +{ +} + +bool AppletProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); + + // slaves are filtered-out when not searching for a connection (makes the state of search results clear) + const bool isSlave = sourceModel()->data(index, NetworkModel::SlaveRole).toBool(); + if (isSlave && filterRegExp().isEmpty()) { + return false; + } + + const NetworkManager::ConnectionSettings::ConnectionType type = (NetworkManager::ConnectionSettings::ConnectionType) sourceModel()->data(index, NetworkModel::TypeRole).toUInt(); + if (!UiUtils::isConnectionTypeSupported(type)) { + return false; + } + + NetworkModelItem::ItemType itemType = (NetworkModelItem::ItemType)sourceModel()->data(index, NetworkModel::ItemTypeRole).toUInt(); + + if (itemType != NetworkModelItem::AvailableConnection && + itemType != NetworkModelItem::AvailableAccessPoint) { + return false; + } + + if (filterRegExp().isEmpty()) { + return true; + } + + return sourceModel()->data(index, NetworkModel::ItemUniqueNameRole).toString().contains(filterRegExp()); +} + +bool AppletProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + const bool leftAvailable = (NetworkModelItem::ItemType)sourceModel()->data(left, NetworkModel::ItemTypeRole).toUInt() != NetworkModelItem::UnavailableConnection; + const bool leftConnected = sourceModel()->data(left, NetworkModel::ConnectionStateRole).toUInt() == NetworkManager::ActiveConnection::Activated; + const int leftConnectionState = sourceModel()->data(left, NetworkModel::ConnectionStateRole).toUInt(); + const QString leftName = sourceModel()->data(left, NetworkModel::NameRole).toString(); + const UiUtils::SortedConnectionType leftType = UiUtils::connectionTypeToSortedType((NetworkManager::ConnectionSettings::ConnectionType) sourceModel()->data(left, NetworkModel::TypeRole).toUInt()); + const QString leftUuid = sourceModel()->data(left, NetworkModel::UuidRole).toString(); + const int leftSignal = sourceModel()->data(left, NetworkModel::SignalRole).toInt(); + const QDateTime leftDate = sourceModel()->data(left, NetworkModel::TimeStampRole).toDateTime(); + + const bool rightAvailable = (NetworkModelItem::ItemType)sourceModel()->data(right, NetworkModel::ItemTypeRole).toUInt() != NetworkModelItem::UnavailableConnection; + const bool rightConnected = sourceModel()->data(right, NetworkModel::ConnectionStateRole).toUInt() == NetworkManager::ActiveConnection::Activated; + const int rightConnectionState = sourceModel()->data(right, NetworkModel::ConnectionStateRole).toUInt(); + const QString rightName = sourceModel()->data(right, NetworkModel::NameRole).toString(); + const UiUtils::SortedConnectionType rightType = UiUtils::connectionTypeToSortedType((NetworkManager::ConnectionSettings::ConnectionType) sourceModel()->data(right, NetworkModel::TypeRole).toUInt()); + const QString rightUuid = sourceModel()->data(right, NetworkModel::UuidRole).toString(); + const int rightSignal = sourceModel()->data(right, NetworkModel::SignalRole).toInt(); + const QDateTime rightDate = sourceModel()->data(right, NetworkModel::TimeStampRole).toDateTime(); + + if (leftAvailable < rightAvailable) { + return true; + } else if (leftAvailable > rightAvailable) { + return false; + } + + if (leftConnected < rightConnected) { + return true; + } else if (leftConnected > rightConnected) { + return false; + } + + if (leftConnectionState > rightConnectionState) { + return true; + } else if (leftConnectionState < rightConnectionState) { + return false; + } + + if (leftUuid.isEmpty() && !rightUuid.isEmpty()) { + return true; + } else if (!leftUuid.isEmpty() && rightUuid.isEmpty()) { + return false; + } + + if (leftType < rightType) { + return false; + } else if (leftType > rightType) { + return true; + } + + if (leftDate > rightDate) { + return false; + } else if (leftDate < rightDate) { + return true; + } + + if (leftSignal < rightSignal) { + return true; + } else if (leftSignal > rightSignal) { + return false; + } + + if (QString::localeAwareCompare(leftName, rightName) > 0) { + return true; + } else { + return false; + } +} diff --git a/networkmanagement/appletproxymodel.h b/networkmanagement/appletproxymodel.h new file mode 100644 index 0000000..b614c82 --- /dev/null +++ b/networkmanagement/appletproxymodel.h @@ -0,0 +1,43 @@ +/* + Copyright 2013-2014 Jan Grulich + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef APPLETPROXYMODEL_H +#define APPLETPROXYMODEL_H + +#include +#include + +#include "networkmodelitem.h" + +class NETWORKMANAGER_EXPORT AppletProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(QAbstractItemModel * sourceModel READ sourceModel WRITE setSourceModel) + +public: + explicit AppletProxyModel(QObject *parent = nullptr); + ~AppletProxyModel() override; + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; +}; + +#endif // APPLETPROXYMODEL_H diff --git a/networkmanagement/connectionicon.cpp b/networkmanagement/connectionicon.cpp new file mode 100644 index 0000000..74c2b56 --- /dev/null +++ b/networkmanagement/connectionicon.cpp @@ -0,0 +1,657 @@ +/* + Copyright 2013 Jan Grulich + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "connectionicon.h" +#include "uiutils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#if WITH_MODEMMANAGER_SUPPORT +#include +#endif + +ConnectionIcon::ConnectionIcon(QObject* parent) + : QObject(parent) + , m_signal(0) + , m_wirelessNetwork(nullptr) + , m_connecting(false) + , m_limited(false) + , m_vpn(false) +#if WITH_MODEMMANAGER_SUPPORT + , m_modemNetwork(nullptr) +#endif +{ + connect(NetworkManager::notifier(), &NetworkManager::Notifier::primaryConnectionChanged, this, &ConnectionIcon::primaryConnectionChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::activatingConnectionChanged, this, &ConnectionIcon::activatingConnectionChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionAdded, this, &ConnectionIcon::activeConnectionAdded); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::connectivityChanged, this, &ConnectionIcon::connectivityChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::deviceAdded, this, &ConnectionIcon::deviceAdded); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::deviceRemoved, this, &ConnectionIcon::deviceRemoved); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::networkingEnabledChanged, this, &ConnectionIcon::networkingEnabledChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::statusChanged, this, &ConnectionIcon::statusChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::wirelessEnabledChanged, this, &ConnectionIcon::wirelessEnabledChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::wirelessHardwareEnabledChanged, this, &ConnectionIcon::wirelessEnabledChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::wwanEnabledChanged, this, &ConnectionIcon::wwanEnabledChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::wwanHardwareEnabledChanged, this, &ConnectionIcon::wwanEnabledChanged); + + for (const NetworkManager::Device::Ptr &device : NetworkManager::networkInterfaces()) { + if (device->type() == NetworkManager::Device::Ethernet) { + NetworkManager::WiredDevice::Ptr wiredDevice = device.staticCast(); + if (wiredDevice) { + connect(wiredDevice.data(), &NetworkManager::WiredDevice::carrierChanged, this, &ConnectionIcon::carrierChanged); + } + } else if (device->type() == NetworkManager::Device::Wifi) { + NetworkManager::WirelessDevice::Ptr wifiDevice = device.staticCast(); + if (wifiDevice) { + connect(wifiDevice.data(), &NetworkManager::WirelessDevice::availableConnectionAppeared, this, &ConnectionIcon::wirelessNetworkAppeared); + connect(wifiDevice.data(), &NetworkManager::WirelessDevice::networkAppeared, this, &ConnectionIcon::wirelessNetworkAppeared); + } + } + } + + for (const NetworkManager::ActiveConnection::Ptr &activeConnection : NetworkManager::activeConnections()) { + addActiveConnection(activeConnection->path()); + } + setStates(); + + setIcons(); + + QDBusPendingReply pendingReply = NetworkManager::checkConnectivity(); + QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(pendingReply); + connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this] (QDBusPendingCallWatcher *watcher) { + QDBusPendingReply reply = *watcher; + if (reply.isValid()) { + connectivityChanged((NetworkManager::Connectivity)reply.value()); + } + watcher->deleteLater(); + }); +} + +ConnectionIcon::~ConnectionIcon() +{ +} + +bool ConnectionIcon::connecting() const +{ + return m_connecting; +} + +QString ConnectionIcon::connectionIcon() const +{ + if (m_vpn && !m_connectionIcon.contains("available")) { + return m_connectionIcon + "-locked"; + } + + if (m_limited && !m_connectionIcon.contains("available")) { + return m_connectionIcon + "-limited"; + } + + return m_connectionIcon; +} + +QString ConnectionIcon::connectionTooltipIcon() const +{ + return m_connectionTooltipIcon; +} + +QString ConnectionIcon::currentSSID() const +{ + return m_currentSSID; +} + +void ConnectionIcon::activatingConnectionChanged(const QString& connection) +{ + Q_UNUSED(connection); + setIcons(); +} + +void ConnectionIcon::addActiveConnection(const QString &activeConnection) +{ + NetworkManager::ActiveConnection::Ptr active = NetworkManager::findActiveConnection(activeConnection); + + if (active) { + NetworkManager::VpnConnection::Ptr vpnConnection; + connect(active.data(), &NetworkManager::ActiveConnection::destroyed, this, &ConnectionIcon::activeConnectionDestroyed); + if (active->vpn()) { + vpnConnection = active.objectCast(); + connect(vpnConnection.data(), &NetworkManager::VpnConnection::stateChanged, this, &ConnectionIcon::vpnConnectionStateChanged); + } else { + connect(active.data(), &NetworkManager::ActiveConnection::stateChanged, this, &ConnectionIcon::activeConnectionStateChanged, Qt::UniqueConnection); + } + } +} + +void ConnectionIcon::activeConnectionAdded(const QString &activeConnection) +{ + addActiveConnection(activeConnection); + setStates(); +} + +void ConnectionIcon::activeConnectionStateChanged(NetworkManager::ActiveConnection::State state) +{ + Q_UNUSED(state); + setStates(); +} + +void ConnectionIcon::activeConnectionDestroyed() +{ + setStates(); +} + +void ConnectionIcon::carrierChanged(bool carrier) +{ + Q_UNUSED(carrier); + setIcons(); +} + +void ConnectionIcon::connectivityChanged(NetworkManager::Connectivity conn) +{ + const bool needsPortal = conn == NetworkManager::Portal; + if (needsPortal != m_needsPortal) { + m_needsPortal = needsPortal; + Q_EMIT needsPortalChanged(needsPortal); + } + setLimited(conn == NetworkManager::Portal || conn == NetworkManager::Limited); +} + +void ConnectionIcon::deviceAdded(const QString& device) +{ + NetworkManager::Device::Ptr dev = NetworkManager::findNetworkInterface(device); + + if (!dev) { + return; + } + + if (dev->type() == NetworkManager::Device::Ethernet) { + NetworkManager::WiredDevice::Ptr wiredDev = dev.objectCast(); + connect(wiredDev.data(), &NetworkManager::WiredDevice::carrierChanged, this, &ConnectionIcon::carrierChanged); + } +} + +void ConnectionIcon::deviceRemoved(const QString& device) +{ + Q_UNUSED(device); + + if (NetworkManager::status() == NetworkManager::Disconnected) { + setDisconnectedIcon(); + } +} + +#if WITH_MODEMMANAGER_SUPPORT +void ConnectionIcon::modemNetworkRemoved() +{ + m_modemNetwork.clear(); +} + +void ConnectionIcon::modemSignalChanged(const ModemManager::SignalQualityPair &signalQuality) +{ + int diff = m_signal - signalQuality.signal; + + if (diff >= 10 || + diff <= -10) { + m_signal = signalQuality.signal; + + setIconForModem(); + } +} +#endif + +void ConnectionIcon::networkingEnabledChanged(bool enabled) +{ + if (!enabled) { + setConnectionIcon("network-unavailable"); + } +} + +void ConnectionIcon::primaryConnectionChanged(const QString& connection) +{ + if (!connection.isEmpty()) { + setIcons(); + } +} + +void ConnectionIcon::statusChanged(NetworkManager::Status status) +{ + if (status == NetworkManager::Disconnected) { + setDisconnectedIcon(); + } +} + +void ConnectionIcon::vpnConnectionStateChanged(NetworkManager::VpnConnection::State state, NetworkManager::VpnConnection::StateChangeReason reason) +{ + Q_UNUSED(state); + Q_UNUSED(reason); + setStates(); + setIcons(); +} + +void ConnectionIcon::wirelessEnabledChanged(bool enabled) +{ + Q_UNUSED(enabled); + + m_currentSSID = ""; + emit currentSSIDChanged(); + + setIcons(); +} + +void ConnectionIcon::wwanEnabledChanged(bool enabled) +{ + Q_UNUSED(enabled); + setIcons(); + +} + +void ConnectionIcon::wirelessNetworkAppeared(const QString& network) +{ + Q_UNUSED(network); + setIcons(); +} + +void ConnectionIcon::setStates() +{ + bool connecting = false; + bool vpn = false; + for (const NetworkManager::ActiveConnection::Ptr &activeConnection : NetworkManager::activeConnections()) { + NetworkManager::VpnConnection::Ptr vpnConnection; + if (activeConnection->vpn()) { + vpnConnection = activeConnection.objectCast(); + } + + if (!vpnConnection) { + if (activeConnection->state() == NetworkManager::ActiveConnection::Activating && UiUtils::isConnectionTypeSupported(activeConnection->type())) { + connecting = true; + } + if (activeConnection->type() == NetworkManager::ConnectionSettings::ConnectionType::WireGuard) { + vpn = true; + } + } else { + if (vpnConnection->state() == NetworkManager::VpnConnection::Activated) { + vpn = true; + } else if (vpnConnection->state() == NetworkManager::VpnConnection::Prepare || + vpnConnection->state() == NetworkManager::VpnConnection::NeedAuth || + vpnConnection->state() == NetworkManager::VpnConnection::Connecting || + vpnConnection->state() == NetworkManager::VpnConnection::GettingIpConfig) { + connecting = true; + } + } + } + + setVpn(vpn); + setConnecting(connecting); +} + +void ConnectionIcon::setIcons() +{ + m_signal = 0; +#if WITH_MODEMMANAGER_SUPPORT + if (m_modemNetwork) { + disconnect(m_modemNetwork.data(), nullptr, this, nullptr); + m_modemNetwork.clear(); + } +#endif + if (m_wirelessNetwork) { + disconnect(m_wirelessNetwork.data(), nullptr, this, nullptr); + m_wirelessNetwork.clear(); + } + + NetworkManager::ActiveConnection::Ptr connection = NetworkManager::activatingConnection(); + + // Set icon based on the current primary connection if the activating connection is virtual + // since we're not setting icons for virtual connections + if (!connection + || (connection && UiUtils::isConnectionTypeVirtual(connection->type())) + || connection->type() == NetworkManager::ConnectionSettings::WireGuard) { + connection = NetworkManager::primaryConnection(); + } + + /* Fallback: If we still don't have an active connection with default route or the default route goes through a connection + of generic type (some type of VPNs) we need to go through all other active connections and pick the one with + highest probability of being the main one (order is: vpn, wired, wireless, gsm, cdma, bluetooth) */ + if ((!connection && !NetworkManager::activeConnections().isEmpty()) || (connection && connection->type() == NetworkManager::ConnectionSettings::Generic) + || (connection && connection->type() == NetworkManager::ConnectionSettings::Tun)) { + for (const NetworkManager::ActiveConnection::Ptr &activeConnection : NetworkManager::activeConnections()) { + const NetworkManager::ConnectionSettings::ConnectionType type = activeConnection->type(); + if (type == NetworkManager::ConnectionSettings::Bluetooth) { + if (connection && connection->type() <= NetworkManager::ConnectionSettings::Bluetooth) { + connection = activeConnection; + } + } else if (type == NetworkManager::ConnectionSettings::Cdma) { + if (connection && connection->type() <= NetworkManager::ConnectionSettings::Cdma) { + connection = activeConnection; + } + } else if (type == NetworkManager::ConnectionSettings::Gsm) { + if (connection && connection->type() <= NetworkManager::ConnectionSettings::Gsm) { + connection = activeConnection; + } + } else if (type == NetworkManager::ConnectionSettings::Vpn) { + connection = activeConnection; + } else if (type == NetworkManager::ConnectionSettings::WireGuard) { + connection = activeConnection; + } else if (type == NetworkManager::ConnectionSettings::Wired) { + if (connection && (connection->type() != NetworkManager::ConnectionSettings::Vpn + || connection->type() != NetworkManager::ConnectionSettings::WireGuard)) { + connection = activeConnection; + } + } else if (type == NetworkManager::ConnectionSettings::Wireless) { + if (connection && (connection->type() != NetworkManager::ConnectionSettings::Vpn && + (connection->type() != NetworkManager::ConnectionSettings::Wired))) { + connection = activeConnection; + } + } + } + } + + if (connection && !connection->devices().isEmpty()) { + NetworkManager::Device::Ptr device = NetworkManager::findNetworkInterface(connection->devices().first()); + + if (device) { + NetworkManager::Device::Type type = device->type(); + if (type == NetworkManager::Device::Wifi) { + NetworkManager::WirelessDevice::Ptr wifiDevice = device.objectCast(); + if (wifiDevice->mode() == NetworkManager::WirelessDevice::Adhoc) { + setWirelessIconForSignalStrength(100); + } else { + NetworkManager::AccessPoint::Ptr ap = wifiDevice->activeAccessPoint(); + if (ap) { + setWirelessIcon(device, ap->ssid()); + } + } + } else if (type == NetworkManager::Device::Ethernet) { + setConnectionIcon("network-wired-activated"); + setConnectionTooltipIcon("network-wired-activated"); + } else if (type == NetworkManager::Device::Modem) { +#if WITH_MODEMMANAGER_SUPPORT + setModemIcon(device); +#else + setConnectionIcon("network-mobile-0"); + setConnectionTooltipIcon("phone"); +#endif + } else if (type == NetworkManager::Device::Bluetooth) { + NetworkManager::BluetoothDevice::Ptr btDevice = device.objectCast(); + if (btDevice) { + if (btDevice->bluetoothCapabilities().testFlag(NetworkManager::BluetoothDevice::Dun)) { +#if WITH_MODEMMANAGER_SUPPORT + setModemIcon(device); +#else + setConnectionIcon("network-mobile-0"); + setConnectionTooltipIcon("phone"); +#endif + } else { + setConnectionIcon("network-bluetooth-activated"); + setConnectionTooltipIcon("preferences-system-bluetooth"); + } + } + } else if (type == 29) { // TODO change to WireGuard enum value once it is added + // WireGuard is a VPN but is not implemented + // in NetworkManager as a VPN, so we don't want to + // do anything just because it has a device + // associated with it. + } else { + // Ignore other devices (bond/bridge/team etc.) + setDisconnectedIcon(); + } + } + } else { + setDisconnectedIcon(); + } +} + +void ConnectionIcon::setDisconnectedIcon() +{ + // if (Configuration::airplaneModeEnabled()) { + // setConnectionIcon(QStringLiteral("network-flightmode-on")); + // return; + // } + + if (NetworkManager::status() == NetworkManager::Unknown || + NetworkManager::status() == NetworkManager::Asleep) { + setConnectionIcon("network-unavailable"); + return; + } + + bool wired = false; + bool wireless = false; + bool modem = false; + + m_limited = false; + m_vpn = false; + + for (const NetworkManager::Device::Ptr &device : NetworkManager::networkInterfaces()) { + if (device->type() == NetworkManager::Device::Ethernet) { + NetworkManager::WiredDevice::Ptr wiredDev = device.objectCast(); + if (wiredDev->carrier()) { + wired = true; + } + } else if (device->type() == NetworkManager::Device::Wifi && + NetworkManager::isWirelessEnabled() && + NetworkManager::isWirelessHardwareEnabled()) { + NetworkManager::WirelessDevice::Ptr wifiDevice = device.objectCast(); + if (!wifiDevice->accessPoints().isEmpty() || !wifiDevice->availableConnections().isEmpty()) { + wireless = true; + } + } else if (device->type() == NetworkManager::Device::Modem && + NetworkManager::isWwanEnabled() && + NetworkManager::isWwanHardwareEnabled()) { + modem = true; + } + } + + if (wired) { + setConnectionIcon("network-wired-available"); + setConnectionTooltipIcon("network-wired"); + return; + } else if (wireless) { + setConnectionIcon("network-wireless-available"); + setConnectionTooltipIcon("network-wireless-connected-00"); + return; + } else if (modem) { + setConnectionIcon("network-mobile-available"); + setConnectionTooltipIcon("phone"); + return; + } else { + setConnectionIcon("network-unavailable"); + setConnectionTooltipIcon("network-wired"); + } +} + +#if WITH_MODEMMANAGER_SUPPORT +void ConnectionIcon::setModemIcon(const NetworkManager::Device::Ptr & device) +{ + NetworkManager::ModemDevice::Ptr modemDevice = device.objectCast(); + + if (!modemDevice) { + setConnectionIcon("network-mobile-100"); + + return; + } + + ModemManager::ModemDevice::Ptr modem = ModemManager::findModemDevice(device->udi()); + if (modem) { + if (modem->hasInterface(ModemManager::ModemDevice::ModemInterface)) { + m_modemNetwork = modem->interface(ModemManager::ModemDevice::ModemInterface).objectCast(); + } + } + + if (m_modemNetwork) { + connect(m_modemNetwork.data(), &ModemManager::Modem::signalQualityChanged, this, &ConnectionIcon::modemSignalChanged, Qt::UniqueConnection); + connect(m_modemNetwork.data(), &ModemManager::Modem::accessTechnologiesChanged, this, &ConnectionIcon::setIconForModem, Qt::UniqueConnection); + connect(m_modemNetwork.data(), &ModemManager::Modem::destroyed, this, &ConnectionIcon::modemNetworkRemoved); + + m_signal = m_modemNetwork->signalQuality().signal; + setIconForModem(); + } else { + setConnectionIcon("network-mobile-0"); + setConnectionTooltipIcon("phone"); + return; + } +} + +void ConnectionIcon::setIconForModem() +{ + if (!m_signal) { + m_signal = m_modemNetwork->signalQuality().signal; + } + QString strength = "00"; + + if (m_signal == 0) { + strength = '0'; + } else if (m_signal < 20) { + strength = "20"; + } else if (m_signal < 40) { + strength = "40"; + } else if (m_signal < 60) { + strength = "60"; + } else if (m_signal < 80) { + strength = "80"; + } else { + strength = "100"; + } + + QString result; + + switch(m_modemNetwork->accessTechnologies()) { + case MM_MODEM_ACCESS_TECHNOLOGY_GSM: + case MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT: + result = "network-mobile-%1"; + break; + case MM_MODEM_ACCESS_TECHNOLOGY_GPRS: + result = "network-mobile-%1-gprs"; + break; + case MM_MODEM_ACCESS_TECHNOLOGY_EDGE: + result = "network-mobile-%1-edge"; + break; + case MM_MODEM_ACCESS_TECHNOLOGY_UMTS: + result = "network-mobile-%1-umts"; + break; + case MM_MODEM_ACCESS_TECHNOLOGY_HSDPA: + result = "network-mobile-%1-hsdpa"; + break; + case MM_MODEM_ACCESS_TECHNOLOGY_HSUPA: + result = "network-mobile-%1-hsupa"; + break; + case MM_MODEM_ACCESS_TECHNOLOGY_HSPA: + case MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS: + result = "network-mobile-%1-hspa"; + break; + case MM_MODEM_ACCESS_TECHNOLOGY_LTE: + result = "network-mobile-%1-lte"; + break; + default: + result = "network-mobile-%1"; + break; + } + + setConnectionIcon(QString(result).arg(strength)); + setConnectionTooltipIcon("phone"); +} +#endif + +void ConnectionIcon::setWirelessIcon(const NetworkManager::Device::Ptr &device, const QString& ssid) +{ + NetworkManager::WirelessDevice::Ptr wirelessDevice = device.objectCast(); + if (device) { + m_wirelessNetwork = wirelessDevice->findNetwork(ssid); + } else { + m_wirelessNetwork.clear(); + } + + if (m_wirelessNetwork) { + m_currentSSID = ssid; + emit currentSSIDChanged(); + + connect(m_wirelessNetwork.data(), &NetworkManager::WirelessNetwork::signalStrengthChanged, this, &ConnectionIcon::setWirelessIconForSignalStrength, Qt::UniqueConnection); + + setWirelessIconForSignalStrength(m_wirelessNetwork->signalStrength()); + } else { + setDisconnectedIcon(); + + m_currentSSID = ""; + emit currentSSIDChanged(); + } +} + +void ConnectionIcon::setWirelessIconForSignalStrength(int strength) +{ + int iconStrength = 100; + if (strength == 0) { + iconStrength = 0; + setConnectionTooltipIcon("network-wireless-connected-00"); + } else if (strength <= 25) { + iconStrength = 25; + setConnectionTooltipIcon("network-wireless-connected-25"); + } else if (strength <= 50) { + iconStrength = 50; + setConnectionTooltipIcon("network-wireless-connected-50"); + } else if (strength < 75) { + iconStrength = 75; + setConnectionTooltipIcon("network-wireless-connected-75"); + } else if (strength < 100) { + setConnectionTooltipIcon("network-wireless-connected-100"); + } + + QString icon = QString("network-wireless-%1").arg(iconStrength); + + setConnectionIcon(icon); +} + +void ConnectionIcon::setConnecting(bool connecting) +{ + if (connecting != m_connecting) { + m_connecting = connecting; + Q_EMIT connectingChanged(m_connecting); + } +} + +void ConnectionIcon::setConnectionIcon(const QString & icon) +{ + if (icon != m_connectionIcon) { + m_connectionIcon = icon; + Q_EMIT connectionIconChanged(connectionIcon()); + } +} + +void ConnectionIcon::setConnectionTooltipIcon(const QString & icon) +{ + if (icon != m_connectionTooltipIcon) { + m_connectionTooltipIcon = icon; + Q_EMIT connectionTooltipIconChanged(m_connectionTooltipIcon); + } +} + +void ConnectionIcon::setVpn(bool vpn) +{ + if (m_vpn != vpn) { + m_vpn = vpn; + Q_EMIT connectionIconChanged(connectionIcon()); + } +} + +void ConnectionIcon::setLimited(bool limited) +{ + if (m_limited != limited) { + m_limited = limited; + Q_EMIT connectionIconChanged(connectionIcon()); + } +} diff --git a/networkmanagement/connectionicon.h b/networkmanagement/connectionicon.h new file mode 100644 index 0000000..f05ff56 --- /dev/null +++ b/networkmanagement/connectionicon.h @@ -0,0 +1,108 @@ +/* + Copyright 2013 Jan Grulich + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef PLASMA_NM_CONNECTION_ICON_H +#define PLASMA_NM_CONNECTION_ICON_H + +#include +#include +#include +#include +#if WITH_MODEMMANAGER_SUPPORT +#include +#endif + +class ConnectionIcon : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool connecting READ connecting NOTIFY connectingChanged) + Q_PROPERTY(QString connectionIcon READ connectionIcon NOTIFY connectionIconChanged) + Q_PROPERTY(QString connectionTooltipIcon READ connectionTooltipIcon NOTIFY connectionTooltipIconChanged) + Q_PROPERTY(bool needsPortal READ needsPortal NOTIFY needsPortalChanged) + Q_PROPERTY(QString currentSSID READ currentSSID NOTIFY currentSSIDChanged) + +public: + explicit ConnectionIcon(QObject* parent = nullptr); + ~ConnectionIcon() override; + + bool connecting() const; + QString connectionIcon() const; + QString connectionTooltipIcon() const; + + QString currentSSID() const; + + bool needsPortal() const { return m_needsPortal; } + +private Q_SLOTS: + void activatingConnectionChanged(const QString & connection); + void activeConnectionAdded(const QString & activeConnection); + void activeConnectionDestroyed(); + void activeConnectionStateChanged(NetworkManager::ActiveConnection::State state); + void carrierChanged(bool carrier); + void connectivityChanged(NetworkManager::Connectivity connectivity); + void deviceAdded(const QString & device); + void deviceRemoved(const QString & device); + void networkingEnabledChanged(bool enabled); + void primaryConnectionChanged(const QString & connection); +#if WITH_MODEMMANAGER_SUPPORT + void modemNetworkRemoved(); + void modemSignalChanged(const ModemManager::SignalQualityPair &signalQuality); + void setIconForModem(); +#endif + void statusChanged(NetworkManager::Status status); + void setWirelessIconForSignalStrength(int strength); + void vpnConnectionStateChanged(NetworkManager::VpnConnection::State state, NetworkManager::VpnConnection::StateChangeReason reason); + void wirelessEnabledChanged(bool enabled); + void wirelessNetworkAppeared(const QString &network); + void wwanEnabledChanged(bool enabled); +Q_SIGNALS: + void connectingChanged(bool connecting); + void connectionIconChanged(const QString & icon); + void connectionTooltipIconChanged(const QString & icon); + void needsPortalChanged(bool needsPortal); + void currentSSIDChanged(); + +private: + void addActiveConnection(const QString & activeConnection); + void setConnecting(bool connecting); + void setConnectionIcon(const QString & icon); + void setConnectionTooltipIcon(const QString & icon); + void setVpn(bool vpn); + void setLimited(bool limited); + uint m_signal; + NetworkManager::WirelessNetwork::Ptr m_wirelessNetwork; + + QString m_currentSSID; + + bool m_connecting; + bool m_limited; + bool m_vpn; + QString m_connectionIcon; + QString m_connectionTooltipIcon; + bool m_needsPortal = false; + + void setDisconnectedIcon(); + void setIcons(); + void setStates(); + void setWirelessIcon(const NetworkManager::Device::Ptr & device, const QString & ssid); +#if WITH_MODEMMANAGER_SUPPORT + ModemManager::Modem::Ptr m_modemNetwork; + void setModemIcon(const NetworkManager::Device::Ptr & device); +#endif +}; + +#endif // PLASMA_NM_CONNECTION_ICON_H diff --git a/networkmanagement/network.cpp b/networkmanagement/network.cpp new file mode 100644 index 0000000..75014c3 --- /dev/null +++ b/networkmanagement/network.cpp @@ -0,0 +1,322 @@ +#include "network.h" +#include "uiutils.h" +#include + +#include +#include + +Network::Network(QObject *parent) + : QObject(parent) + , m_wirelessNetwork(nullptr) +{ + ::passwd *pw = ::getpwuid(::getuid()); + m_userName = QString::fromLocal8Bit(pw->pw_name); + + connect(NetworkManager::notifier(), &NetworkManager::Notifier::networkingEnabledChanged, this, &Network::enabledChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::wirelessEnabledChanged, this, &Network::onWirelessEnabledChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::wirelessHardwareEnabledChanged, this, &Network::wirelessHardwareEnabledChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::statusChanged, this, &Network::statusChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::activatingConnectionChanged, this, &Network::activatingConnectionChanged); + + for (const NetworkManager::Device::Ptr &device : NetworkManager::networkInterfaces()) { + if (device->type() == NetworkManager::Device::Wifi) { + NetworkManager::WirelessDevice::Ptr wifiDevice = device.staticCast(); + if (wifiDevice) { + connect(wifiDevice.data(), &NetworkManager::WirelessDevice::availableConnectionAppeared, this, &Network::onWirelessNetworkAppeared); + connect(wifiDevice.data(), &NetworkManager::WirelessDevice::networkAppeared, this, &Network::onWirelessNetworkAppeared); + } + } + } + + statusChanged(NetworkManager::status()); + changeActiveConnections(); + updateIcons(); +} + +bool Network::isEnabled() const +{ + return NetworkManager::isNetworkingEnabled(); +} + +void Network::setEnabled(bool enabled) +{ + NetworkManager::setNetworkingEnabled(enabled); +} + +bool Network::isWirelessEnabled() const +{ + return NetworkManager::isWirelessEnabled(); +} + +void Network::setWirelessEnabled(bool enabled) +{ + NetworkManager::setWirelessEnabled(enabled); +} + +bool Network::isWirelessHardwareEnabled() const +{ + return NetworkManager::isWirelessHardwareEnabled(); +} + +QString Network::wirelessConnectionName() const +{ + return m_wirelessConnectionName; +} + +QString Network::wirelessIconName() const +{ + return m_wirelessIconName; +} + +Network::SortedConnectionType Network::connectionTypeToSortedType(NetworkManager::ConnectionSettings::ConnectionType type) +{ + switch (type) { + case NetworkManager::ConnectionSettings::Adsl: + return Network::Adsl; + break; + case NetworkManager::ConnectionSettings::Bluetooth: + return Network::Bluetooth; + break; + case NetworkManager::ConnectionSettings::Cdma: + return Network::Cdma; + break; + case NetworkManager::ConnectionSettings::Gsm: + return Network::Gsm; + break; + case NetworkManager::ConnectionSettings::Infiniband: + return Network::Infiniband; + break; + case NetworkManager::ConnectionSettings::OLPCMesh: + return Network::OLPCMesh; + break; + case NetworkManager::ConnectionSettings::Pppoe: + return Network::Pppoe; + break; + case NetworkManager::ConnectionSettings::Vpn: + return Network::Vpn; + break; + case NetworkManager::ConnectionSettings::Wired: + return Network::Wired; + break; + case NetworkManager::ConnectionSettings::Wireless: + return Network::Wireless; + break; + default: + return Network::Other; + break; + } +} + +void Network::statusChanged(NetworkManager::Status status) +{ + if (status == NetworkManager::ConnectedLinkLocal || + status == NetworkManager::ConnectedSiteOnly || + status == NetworkManager::Connected) { + changeActiveConnections(); + } + + if (status == NetworkManager::Disconnected) { + resetWireless(); + } +} + +void Network::changeActiveConnections() +{ + if (NetworkManager::status() != NetworkManager::Connected && + NetworkManager::status() != NetworkManager::ConnectedLinkLocal && + NetworkManager::status() != NetworkManager::ConnectedSiteOnly) { + + m_wirelessConnectionName = ""; + emit wirelessConnectionNameChanged(); + + return; + } + + QString activeConnections; + QList activeConnectionList = NetworkManager::activeConnections(); + std::sort(activeConnectionList.begin(), activeConnectionList.end(), [](const NetworkManager::ActiveConnection::Ptr &left, const NetworkManager::ActiveConnection::Ptr &right) { + return Network::connectionTypeToSortedType(left->type()) < Network::connectionTypeToSortedType(right->type()); + }); + + for (const NetworkManager::ActiveConnection::Ptr &active : activeConnectionList) { + if (active->devices().isEmpty() && !UiUtils::isConnectionTypeSupported(active->type())) + continue; + + NetworkManager::Device::Ptr device = NetworkManager::findNetworkInterface(active->devices().first()); + NetworkManager::Connection::Ptr connection = active->connection(); + bool connecting = false; + bool connected = false; + + if (active->state() == NetworkManager::ActiveConnection::Activated) + connected = true; + else if (active->state() == NetworkManager::ActiveConnection::Activating) + connecting = true; + + if (device->type() == NetworkManager::Device::Wifi) { + if (m_wirelessConnectionName != connection->name()) { + m_wirelessConnectionName = connection->name(); + emit wirelessConnectionNameChanged(); + + updateIcons(); + } + } + + connect(connection.data(), &NetworkManager::Connection::updated, this, &Network::changeActiveConnections, Qt::UniqueConnection); + } +} + +void Network::activatingConnectionChanged() +{ + updateIcons(); +} + +void Network::defaultChanged() +{ + statusChanged(NetworkManager::status()); +} + +void Network::onWirelessEnabledChanged() +{ + if (!isWirelessEnabled()) { + resetWireless(); + } else { + updateIcons(); + } + + emit wirelessEnabledChanged(); +} + +void Network::onWirelessNetworkAppeared(const QString& network) +{ + Q_UNUSED(network); + + updateIcons(); +} + +void Network::updateIcons() +{ + if (m_wirelessNetwork) { + disconnect(m_wirelessNetwork.data(), nullptr, this, nullptr); + m_wirelessNetwork.clear(); + } + + NetworkManager::ActiveConnection::Ptr connection = NetworkManager::activatingConnection(); + + // Set icon based on the current primary connection if the activating connection is virtual + // since we're not setting icons for virtual connections + if (!connection + || (connection && UiUtils::isConnectionTypeVirtual(connection->type())) + || connection->type() == NetworkManager::ConnectionSettings::WireGuard) { + connection = NetworkManager::primaryConnection(); + } + + /* Fallback: If we still don't have an active connection with default route or the default route goes through a connection + of generic type (some type of VPNs) we need to go through all other active connections and pick the one with + highest probability of being the main one (order is: vpn, wired, wireless, gsm, cdma, bluetooth) */ + if ((!connection && !NetworkManager::activeConnections().isEmpty()) || (connection && connection->type() == NetworkManager::ConnectionSettings::Generic) + || (connection && connection->type() == NetworkManager::ConnectionSettings::Tun)) { + for (const NetworkManager::ActiveConnection::Ptr &activeConnection : NetworkManager::activeConnections()) { + const NetworkManager::ConnectionSettings::ConnectionType type = activeConnection->type(); + if (type == NetworkManager::ConnectionSettings::Bluetooth) { + if (connection && connection->type() <= NetworkManager::ConnectionSettings::Bluetooth) { + connection = activeConnection; + } + } else if (type == NetworkManager::ConnectionSettings::Cdma) { + if (connection && connection->type() <= NetworkManager::ConnectionSettings::Cdma) { + connection = activeConnection; + } + } else if (type == NetworkManager::ConnectionSettings::Gsm) { + if (connection && connection->type() <= NetworkManager::ConnectionSettings::Gsm) { + connection = activeConnection; + } + } else if (type == NetworkManager::ConnectionSettings::Vpn) { + connection = activeConnection; + } else if (type == NetworkManager::ConnectionSettings::WireGuard) { + connection = activeConnection; + } else if (type == NetworkManager::ConnectionSettings::Wired) { + if (connection && (connection->type() != NetworkManager::ConnectionSettings::Vpn + || connection->type() != NetworkManager::ConnectionSettings::WireGuard)) { + connection = activeConnection; + } + } else if (type == NetworkManager::ConnectionSettings::Wireless) { + if (connection && (connection->type() != NetworkManager::ConnectionSettings::Vpn && + (connection->type() != NetworkManager::ConnectionSettings::Wired))) { + connection = activeConnection; + } + } + } + } + + if (connection && !connection->devices().isEmpty()) { + NetworkManager::Device::Ptr device = NetworkManager::findNetworkInterface(connection->devices().first()); + + if (device) { + NetworkManager::Device::Type type = device->type(); + + if (type == NetworkManager::Device::Wifi) { + NetworkManager::WirelessDevice::Ptr wifiDevice = device.objectCast(); + + if (wifiDevice->mode() == NetworkManager::WirelessDevice::Adhoc) { + updateWirelessIconForSignalStrength(100); + } else { + NetworkManager::AccessPoint::Ptr ap = wifiDevice->activeAccessPoint(); + if (ap) { + updateWirelessIcon(device, ap->ssid()); + } + } + } else if (type == NetworkManager::Device::Ethernet) { + + } else { + resetWireless(); + } + } else { + resetWireless(); + } + } +} + +void Network::updateWirelessIcon(const NetworkManager::Device::Ptr &device, const QString& ssid) +{ + NetworkManager::WirelessDevice::Ptr wirelessDevice = device.objectCast(); + + if (device) { + m_wirelessNetwork = wirelessDevice->findNetwork(ssid); + } else { + m_wirelessNetwork.clear(); + } + + if (m_wirelessNetwork) { + connect(m_wirelessNetwork.data(), &NetworkManager::WirelessNetwork::signalStrengthChanged, this, &Network::updateWirelessIconForSignalStrength, Qt::UniqueConnection); + updateWirelessIconForSignalStrength(m_wirelessNetwork->signalStrength()); + } else { + resetWireless(); + } +} + +void Network::updateWirelessIconForSignalStrength(int strength) +{ + int iconStrength = 0; + + if (strength == 0) + iconStrength = 0; + else if (strength <= 25) + iconStrength = 25; + else if (strength <= 50) + iconStrength = 50; + else if (strength <= 75) + iconStrength = 75; + else if (strength <= 100) + iconStrength = 100; + + m_wirelessIconName = QString("network-wireless-connected-%1").arg(iconStrength); + emit wirelessIconNameChanged(); +} + +void Network::resetWireless() +{ + m_wirelessConnectionName = ""; + m_wirelessIconName = ""; + + emit wirelessConnectionNameChanged(); + emit wirelessIconNameChanged(); +} diff --git a/networkmanagement/network.h b/networkmanagement/network.h new file mode 100644 index 0000000..00297a9 --- /dev/null +++ b/networkmanagement/network.h @@ -0,0 +1,78 @@ +#ifndef NETWORK_H +#define NETWORK_H + +#include +#include +#include +#include + +class Network : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool wirelessEnabled READ isWirelessEnabled WRITE setWirelessEnabled NOTIFY wirelessEnabledChanged) + Q_PROPERTY(bool wirelessHardwareEnabled READ isWirelessHardwareEnabled NOTIFY wirelessHardwareEnabledChanged) + Q_PROPERTY(QString wirelessConnectionName READ wirelessConnectionName NOTIFY wirelessConnectionNameChanged) + Q_PROPERTY(QString wirelessIconName READ wirelessIconName NOTIFY wirelessIconNameChanged) + +public: + enum SortedConnectionType { + Wired, + Wireless, + Gsm, + Cdma, + Pppoe, + Adsl, + Infiniband, + OLPCMesh, + Bluetooth, + Vpn, + Other + }; + Q_ENUM(SortedConnectionType) + + explicit Network(QObject *parent = nullptr); + + bool isEnabled() const; + void setEnabled(bool enabled); + + bool isWirelessEnabled() const; + void setWirelessEnabled(bool enabled); + + bool isWirelessHardwareEnabled() const; + + QString wirelessConnectionName() const; + QString wirelessIconName() const; + + static SortedConnectionType connectionTypeToSortedType(NetworkManager::ConnectionSettings::ConnectionType type); + +signals: + void enabledChanged(); + void wirelessEnabledChanged(); + void wirelessHardwareEnabledChanged(); + void wirelessConnectionNameChanged(); + void wirelessIconNameChanged(); + +private slots: + void statusChanged(NetworkManager::Status status); + void changeActiveConnections(); + void activatingConnectionChanged(); + void defaultChanged(); + + void onWirelessEnabledChanged(); + void onWirelessNetworkAppeared(const QString& network); + + void updateIcons(); + void updateWirelessIcon(const NetworkManager::Device::Ptr &device, const QString& ssid); + void updateWirelessIconForSignalStrength(int strength); + + void resetWireless(); + +private: + QString m_userName; + QString m_wirelessConnectionName; + QString m_wirelessIconName; + NetworkManager::WirelessNetwork::Ptr m_wirelessNetwork; +}; + +#endif diff --git a/networkmanagement/networking.cpp b/networkmanagement/networking.cpp new file mode 100644 index 0000000..4829c5e --- /dev/null +++ b/networkmanagement/networking.cpp @@ -0,0 +1,525 @@ +/* + Copyright (C) 2019 Pier Luigi Fiorini + Copyright 2013-2014 Jan Grulich + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include +#include +#include +#include + +#include + +#if WITH_MODEMMANAGER_SUPPORT +# include +# include +# include +#endif + +#include "uiutils.h" +#include "networking.h" +#include "networkmodel.h" + +#include +#include + +#include + +Networking::Networking(QObject *parent) + : QObject(parent) + , m_lastWirelessEnabled(isWirelessEnabled()) + , m_lastMobileEnabled(isMobileEnabled()) +{ + ::passwd *pw = ::getpwuid(::getuid()); + m_userName = QString::fromLocal8Bit(pw->pw_name); + + connect(NetworkManager::notifier(), &NetworkManager::Notifier::networkingEnabledChanged, + this, &Networking::enabledChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::wirelessEnabledChanged, + this, &Networking::wirelessEnabledChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::wirelessHardwareEnabledChanged, + this, &Networking::wirelessHardwareEnabledChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::wwanEnabledChanged, + this, &Networking::mobileEnabledChanged); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::wwanHardwareEnabledChanged, + this, &Networking::mobileHardwareEnabledChanged); + + connect(NetworkManager::notifier(), &NetworkManager::Notifier::statusChanged, + this, &Networking::statusChanged); + doChangeActiveConnections(); + statusChanged(NetworkManager::status()); +} + +bool Networking::isEnabled() const +{ + return NetworkManager::isNetworkingEnabled(); +} + +void Networking::setEnabled(bool enabled) +{ + NetworkManager::setNetworkingEnabled(enabled); +} + +bool Networking::isWirelessEnabled() const +{ + return NetworkManager::isWirelessEnabled(); +} + +void Networking::setWirelessEnabled(bool enabled) +{ + NetworkManager::setWirelessEnabled(enabled); +} + +bool Networking::isWirelessHardwareEnabled() const +{ + return NetworkManager::isWirelessHardwareEnabled(); +} + +bool Networking::isMobileEnabled() const +{ + return NetworkManager::isWwanEnabled(); +} + +void Networking::setMobileEnabled(bool enabled) +{ + NetworkManager::setWwanEnabled(enabled); +} + +bool Networking::isMobileHardwareEnabled() const +{ + return NetworkManager::isWwanHardwareEnabled(); +} + +bool Networking::isAirplaneModeEnabled() const +{ + return !isWirelessEnabled() && !isMobileEnabled(); +} + +void Networking::setAirplaneModeEnabled(bool enabled) +{ + if (isAirplaneModeEnabled() == enabled) + return; + + m_lastWirelessEnabled = isWirelessEnabled(); + m_lastMobileEnabled = isMobileEnabled(); + + if (enabled) { + setWirelessEnabled(false); + setMobileEnabled(false); + } else { + if (m_lastWirelessEnabled) + setWirelessEnabled(true); + if (m_lastMobileEnabled) + setMobileEnabled(true); + } + + Q_EMIT airplaneModeEnabledChanged(); +} + +QString Networking::activeConnections() const +{ + return m_activeConnections; +} + +QString Networking::networkStatus() const +{ + return m_networkStatus; +} + +void Networking::activateConnection(const QString &connectionPath, const QString &device, const QString &specificObject) +{ + NetworkManager::Connection::Ptr connection = NetworkManager::findConnection(connectionPath); + + if (!connection) { + qCWarning(gLcNm, "Unable to activate connection \"%s\"", qPrintable(connectionPath)); + return; + } + + if (connection->settings()->connectionType() == NetworkManager::ConnectionSettings::Vpn) { + NetworkManager::VpnSetting::Ptr vpnSetting = connection->settings()->setting(NetworkManager::Setting::Vpn).staticCast(); + if (vpnSetting) { + qCDebug(gLcNm, "Checking VPN \"%s\" type \"%s\"", qPrintable(connection->name()), qPrintable(vpnSetting->serviceType())); + } + } + +#if 0 +#if WITH_MODEMMANAGER_SUPPORT + if (connection->settings()->connectionType() == NetworkManager::ConnectionSettings::Gsm) { + NetworkManager::ModemDevice::Ptr nmModemDevice = NetworkManager::findNetworkInterface(device).objectCast(); + if (nmModemDevice) { + ModemManager::ModemDevice::Ptr mmModemDevice = ModemManager::findModemDevice(nmModemDevice->udi()); + if (mmModemDevice) { + ModemManager::Modem::Ptr modem = mmModemDevice->interface(ModemManager::ModemDevice::ModemInterface).objectCast(); + NetworkManager::GsmSetting::Ptr gsmSetting = connection->settings()->setting(NetworkManager::Setting::Gsm).staticCast(); + if (gsmSetting && gsmSetting->pinFlags() == NetworkManager::Setting::NotSaved && + modem && modem->unlockRequired() > MM_MODEM_LOCK_NONE) { + QDBusInterface managerIface("org.kde.plasmanetworkmanagement", "/org/kde/plasmanetworkmanagement", "org.kde.plasmanetworkmanagement", QDBusConnection::sessionBus(), this); + managerIface.call("unlockModem", mmModemDevice->uni()); + connect(modem.data(), &ModemManager::Modem::unlockRequiredChanged, this, &Handler::unlockRequiredChanged); + // m_tmpConnectionPath = connectionPath; + // m_tmpDevicePath = device; + // m_tmpSpecificPath = specificObject; + return; + } + } + } + } +#endif +#endif + QDBusPendingReply reply = NetworkManager::activateConnection(connectionPath, device, specificObject); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + watcher->setProperty("connectionName", connection->name()); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { + QDBusPendingReply<> reply = *self; + + if (reply.isError() || !reply.isValid()) { + const QString error = reply.error().message(); + const QString connectionName = self->property("connectionName").toString(); + } + + self->deleteLater(); + }); +} + +void Networking::addAndActivateConnection(const QString &device, const QString &specificObject, const QString &password) +{ + NetworkManager::AccessPoint::Ptr ap; + NetworkManager::WirelessDevice::Ptr wifiDev; + for (const NetworkManager::Device::Ptr &dev : NetworkManager::networkInterfaces()) { + if (dev->type() == NetworkManager::Device::Wifi) { + wifiDev = dev.objectCast(); + ap = wifiDev->findAccessPoint(specificObject); + if (ap) + break; + } + } + + if (!ap) + return; + + NetworkManager::ConnectionSettings::Ptr settings = NetworkManager::ConnectionSettings::Ptr(new NetworkManager::ConnectionSettings(NetworkManager::ConnectionSettings::Wireless)); + settings->setId(ap->ssid()); + settings->setUuid(NetworkManager::ConnectionSettings::createNewUuid()); + settings->setAutoconnect(true); + settings->addToPermissions(m_userName, QString()); + + NetworkManager::WirelessSetting::Ptr wifiSetting = settings->setting(NetworkManager::Setting::Wireless).dynamicCast(); + wifiSetting->setInitialized(true); + wifiSetting = settings->setting(NetworkManager::Setting::Wireless).dynamicCast(); + wifiSetting->setSsid(ap->ssid().toUtf8()); + if (ap->mode() == NetworkManager::AccessPoint::Adhoc) { + wifiSetting->setMode(NetworkManager::WirelessSetting::Adhoc); + } + + NetworkManager::WirelessSecuritySetting::Ptr wifiSecurity = settings->setting(NetworkManager::Setting::WirelessSecurity).dynamicCast(); + NetworkManager::WirelessSecurityType securityType = NetworkManager::findBestWirelessSecurity(wifiDev->wirelessCapabilities(), true, (ap->mode() == NetworkManager::AccessPoint::Adhoc), ap->capabilities(), ap->wpaFlags(), ap->rsnFlags()); + + if (securityType != NetworkManager::NoneSecurity) { + wifiSecurity->setInitialized(true); + wifiSetting->setSecurity("802-11-wireless-security"); + } + + if (securityType == NetworkManager::Leap || + securityType == NetworkManager::DynamicWep || + securityType == NetworkManager::Wpa2Eap || + securityType == NetworkManager::WpaEap) { + if (securityType == NetworkManager::DynamicWep || securityType == NetworkManager::Leap) { + wifiSecurity->setKeyMgmt(NetworkManager::WirelessSecuritySetting::Ieee8021x); + if (securityType == NetworkManager::Leap) + wifiSecurity->setAuthAlg(NetworkManager::WirelessSecuritySetting::Leap); + } else { + wifiSecurity->setKeyMgmt(NetworkManager::WirelessSecuritySetting::WpaEap); + } + // m_tmpConnectionUuid = settings->uuid(); + // m_tmpDevicePath = device; + // m_tmpSpecificPath = specificObject; + + // TODO: Edit connection? + } else { + if (securityType == NetworkManager::StaticWep) { + wifiSecurity->setKeyMgmt(NetworkManager::WirelessSecuritySetting::Wep); + wifiSecurity->setWepKey0(password); +#if 0 + if (KWallet::Wallet::isEnabled()) + wifiSecurity->setWepKeyFlags(NetworkManager::Setting::AgentOwned); +#endif + } else { + if (ap->mode() == NetworkManager::AccessPoint::Adhoc) + wifiSecurity->setKeyMgmt(NetworkManager::WirelessSecuritySetting::WpaNone); + else + wifiSecurity->setKeyMgmt(NetworkManager::WirelessSecuritySetting::WpaPsk); + wifiSecurity->setPsk(password); +#if 0 + if (KWallet::Wallet::isEnabled()) + wifiSecurity->setPskFlags(NetworkManager::Setting::AgentOwned); +#endif + } + + QDBusPendingReply reply = NetworkManager::addAndActivateConnection(settings->toMap(), device, specificObject); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + watcher->setProperty("connectionName", settings->name()); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { + QDBusPendingReply<> reply = *self; + + if (reply.isError() || !reply.isValid()) { + const QString error = reply.error().message(); + const QString connectionName = self->property("connectionName").toString(); + } + + self->deleteLater(); + }); + } + + settings.clear(); +} + +void Networking::deactivateConnection(const QString &connectionName, const QString &device) +{ + NetworkManager::Connection::Ptr connection = NetworkManager::findConnection(connectionName); + + if (!connection) { + qCWarning(gLcNm, "Failed to deactivate connection \"%s\"", qPrintable(connectionName)); + return; + } + + QDBusPendingReply<> reply; + for (const NetworkManager::ActiveConnection::Ptr &active : NetworkManager::activeConnections()) { + if (active->uuid() == connection->uuid() && ((!active->devices().isEmpty() && active->devices().first() == device) || + active->vpn())) { + if (active->vpn()) { + reply = NetworkManager::deactivateConnection(active->path()); + } else { + NetworkManager::Device::Ptr device = NetworkManager::findNetworkInterface(active->devices().first()); + if (device) + reply = device->disconnectInterface(); + } + } + } + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) { + QDBusPendingReply<> reply = *self; + + if (reply.isError() || !reply.isValid()) { + const QString error = reply.error().message(); + const QString connectionName = self->property("connectionName").toString(); + } + + self->deleteLater(); + }); +} + +void Networking::removeConnection(const QString &connectionPath) +{ + NetworkManager::Connection::Ptr con = NetworkManager::findConnection(connectionPath); + + if (!con || con->uuid().isEmpty()) { + qCWarning(gLcNm) << "Not possible to remove connection " << connectionPath; + return; + } + + // Remove slave connections + for (const NetworkManager::Connection::Ptr &connection : NetworkManager::listConnections()) { + NetworkManager::ConnectionSettings::Ptr settings = connection->settings(); + if (settings->master() == con->uuid()) { + connection->remove(); + } + } + + QDBusPendingReply<> reply = con->remove(); + // QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + // watcher->setProperty("action", Networking::RemoveConnection); + // watcher->setProperty("connection", con->name()); + // connect(watcher, &QDBusPendingCallWatcher::finished, this, [=] (QDBusPendingCallWatcher * watcher) { + // }); +} + +Networking::SortedConnectionType Networking::connectionTypeToSortedType(NetworkManager::ConnectionSettings::ConnectionType type) +{ + switch (type) { + case NetworkManager::ConnectionSettings::Adsl: + return Networking::Adsl; + break; + case NetworkManager::ConnectionSettings::Bluetooth: + return Networking::Bluetooth; + break; + case NetworkManager::ConnectionSettings::Cdma: + return Networking::Cdma; + break; + case NetworkManager::ConnectionSettings::Gsm: + return Networking::Gsm; + break; + case NetworkManager::ConnectionSettings::Infiniband: + return Networking::Infiniband; + break; + case NetworkManager::ConnectionSettings::OLPCMesh: + return Networking::OLPCMesh; + break; + case NetworkManager::ConnectionSettings::Pppoe: + return Networking::Pppoe; + break; + case NetworkManager::ConnectionSettings::Vpn: + return Networking::Vpn; + break; + case NetworkManager::ConnectionSettings::Wired: + return Networking::Wired; + break; + case NetworkManager::ConnectionSettings::Wireless: + return Networking::Wireless; + break; + default: + return Networking::Other; + break; + } +} + +void Networking::doChangeActiveConnections() +{ + for (const NetworkManager::ActiveConnection::Ptr &active : NetworkManager::activeConnections()) { + connect(active.data(), &NetworkManager::ActiveConnection::default4Changed, + this, &Networking::defaultChanged, + Qt::UniqueConnection); + connect(active.data(), &NetworkManager::ActiveConnection::default6Changed, + this, &Networking::defaultChanged, + Qt::UniqueConnection); + connect(active.data(), &NetworkManager::ActiveConnection::stateChanged, + this, &Networking::changeActiveConnections); + } + + changeActiveConnections(); +} + +QString Networking::checkUnknownReason() const +{ + // Check if NetworkManager is running. + if (!QDBusConnection::systemBus().interface()->isServiceRegistered(QLatin1String(NM_DBUS_INTERFACE))) + return tr("NetworkManager not running"); + + // Check for compatible NetworkManager version. + if (NetworkManager::compareVersion(0, 9, 8) < 0) + return tr("NetworkManager 0.9.8 required, found %1").arg(NetworkManager::version()); + + return tr("Unknown"); +} + +void Networking::statusChanged(NetworkManager::Status status) +{ + switch (status) { + case NetworkManager::ConnectedLinkLocal: + case NetworkManager::ConnectedSiteOnly: + case NetworkManager::Connected: + m_networkStatus = tr("Connected"); + break; + case NetworkManager::Asleep: + m_networkStatus = tr("Inactive"); + break; + case NetworkManager::Disconnected: + m_networkStatus = tr("Disconnected"); + break; + case NetworkManager::Disconnecting: + m_networkStatus = tr("Disconnecting"); + break; + case NetworkManager::Connecting: + m_networkStatus = tr("Connecting"); + break; + default: + m_networkStatus = checkUnknownReason(); + break; + } + + if (status == NetworkManager::ConnectedLinkLocal || + status == NetworkManager::ConnectedSiteOnly || + status == NetworkManager::Connected) { + changeActiveConnections(); + } else { + m_activeConnections = m_networkStatus; + Q_EMIT activeConnectionsChanged(); + } + + Q_EMIT networkStatusChanged(); +} + +void Networking::changeActiveConnections() +{ + if (NetworkManager::status() != NetworkManager::Connected && + NetworkManager::status() != NetworkManager::ConnectedLinkLocal && + NetworkManager::status() != NetworkManager::ConnectedSiteOnly) + return; + + QString activeConnections; + const QString format = tr("%1: %2"); + + QList activeConnectionList = NetworkManager::activeConnections(); + std::sort(activeConnectionList.begin(), activeConnectionList.end(), [](const NetworkManager::ActiveConnection::Ptr &left, const NetworkManager::ActiveConnection::Ptr &right) { + return Networking::connectionTypeToSortedType(left->type()) < Networking::connectionTypeToSortedType(right->type()); + }); + + for (const NetworkManager::ActiveConnection::Ptr &active : activeConnectionList) { + if (!active->devices().isEmpty() && UiUtils::isConnectionTypeSupported(active->type())) { + NetworkManager::Device::Ptr device = NetworkManager::findNetworkInterface(active->devices().first()); + if (device && device->type() != NetworkManager::Device::Generic && device->type() <= NetworkManager::Device::Team) { + bool connecting = false; + bool connected = false; + QString conType; + QString status; + NetworkManager::VpnConnection::Ptr vpnConnection; + + if (active->vpn()) { + conType = tr("VPN"); + vpnConnection = active.objectCast(); + } else { + conType = UiUtils::interfaceTypeLabel(device->type(), device); + } + + if (vpnConnection && active->vpn()) { + if (vpnConnection->state() >= NetworkManager::VpnConnection::Prepare && + vpnConnection->state() <= NetworkManager::VpnConnection::GettingIpConfig) + connecting = true; + else if (vpnConnection->state() == NetworkManager::VpnConnection::Activated) + connected = true; + } else { + if (active->state() == NetworkManager::ActiveConnection::Activated) + connected = true; + else if (active->state() == NetworkManager::ActiveConnection::Activating) + connecting = true; + } + + NetworkManager::Connection::Ptr connection = active->connection(); + if (connecting) { + status = tr("Connecting to %1").arg(connection->name()); + } else if (connected) { + status = tr("Connected to %1").arg(connection->name()); + } + + if (!activeConnections.isEmpty()) + activeConnections += QLatin1Char('\n'); + activeConnections += format.arg(conType, status); + + connect(connection.data(), &NetworkManager::Connection::updated, + this, &Networking::changeActiveConnections, + Qt::UniqueConnection); + } + } + } + + m_activeConnections = activeConnections; + Q_EMIT activeConnectionsChanged(); +} + +void Networking::defaultChanged() +{ + statusChanged(NetworkManager::status()); +} diff --git a/networkmanagement/networking.h b/networkmanagement/networking.h new file mode 100644 index 0000000..02d47a3 --- /dev/null +++ b/networkmanagement/networking.h @@ -0,0 +1,122 @@ +/* + Copyright (C) 2019 Pier Luigi Fiorini + Copyright 2013-2014 Jan Grulich + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef NETWORKING_H +#define NETWORKING_H + +#include +#include + +#include +#include + +class NETWORKMANAGER_EXPORT Networking : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool wirelessEnabled READ isWirelessEnabled WRITE setWirelessEnabled NOTIFY wirelessEnabledChanged) + Q_PROPERTY(bool wirelessHardwareEnabled READ isWirelessHardwareEnabled NOTIFY wirelessHardwareEnabledChanged) + Q_PROPERTY(bool mobileEnabled READ isMobileEnabled WRITE setMobileEnabled NOTIFY mobileEnabledChanged) + Q_PROPERTY(bool mobileHardwareEnabled READ isMobileHardwareEnabled NOTIFY mobileHardwareEnabledChanged) + Q_PROPERTY(bool airplaneModeEnabled READ isAirplaneModeEnabled WRITE setAirplaneModeEnabled NOTIFY airplaneModeEnabledChanged) + Q_PROPERTY(QString activeConnections READ activeConnections NOTIFY activeConnectionsChanged) + Q_PROPERTY(QString networkStatus READ networkStatus NOTIFY networkStatusChanged) + +public: + enum SortedConnectionType { + Wired, + Wireless, + Gsm, + Cdma, + Pppoe, + Adsl, + Infiniband, + OLPCMesh, + Bluetooth, + Vpn, + Other + }; + Q_ENUM(SortedConnectionType) + + enum HandlerAction { + ActivateConnection, + AddAndActivateConnection, + AddConnection, + DeactivateConnection, + RemoveConnection, + RequestScan, + UpdateConnection, + CreateHotspot, + }; + Q_ENUM(HandlerAction) + + explicit Networking(QObject *parent = nullptr); + + bool isEnabled() const; + void setEnabled(bool enabled); + + bool isWirelessEnabled() const; + void setWirelessEnabled(bool enabled); + + bool isWirelessHardwareEnabled() const; + + bool isMobileEnabled() const; + void setMobileEnabled(bool enabled); + + bool isMobileHardwareEnabled() const; + + bool isAirplaneModeEnabled() const; + void setAirplaneModeEnabled(bool enabled); + + QString activeConnections() const; + QString networkStatus() const; + + Q_INVOKABLE void activateConnection(const QString &connectionPath, const QString &device, const QString &specificObject); + Q_INVOKABLE void addAndActivateConnection(const QString &device, const QString &specificObject, const QString &password = QString()); + Q_INVOKABLE void deactivateConnection(const QString &connectionName, const QString &device); + Q_INVOKABLE void removeConnection(const QString &connectionPath); + + static SortedConnectionType connectionTypeToSortedType(NetworkManager::ConnectionSettings::ConnectionType type); + +Q_SIGNALS: + void enabledChanged(); + void wirelessEnabledChanged(); + void wirelessHardwareEnabledChanged(); + void mobileEnabledChanged(); + void mobileHardwareEnabledChanged(); + void airplaneModeEnabledChanged(); + void activeConnectionsChanged(); + void networkStatusChanged(); + +private: + bool m_lastWirelessEnabled = false; + bool m_lastMobileEnabled = false; + QString m_userName; + QString m_activeConnections; + QString m_networkStatus; + + void doChangeActiveConnections(); + QString checkUnknownReason() const; + +private Q_SLOTS: + void statusChanged(NetworkManager::Status status); + void changeActiveConnections(); + void defaultChanged(); +}; + +#endif // NETWORKING_H diff --git a/networkmanagement/networkitemslist.cpp b/networkmanagement/networkitemslist.cpp new file mode 100644 index 0000000..24df2e0 --- /dev/null +++ b/networkmanagement/networkitemslist.cpp @@ -0,0 +1,175 @@ +/* + Copyright 2013-2018 Jan Grulich + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "networkitemslist.h" +#include "networkmodelitem.h" + +NetworkItemsList::NetworkItemsList(QObject *parent) + : QObject(parent) +{ +} + +NetworkItemsList::~NetworkItemsList() +{ + qDeleteAll(m_items); +} + +bool NetworkItemsList::contains(const NetworkItemsList::FilterType type, const QString ¶meter) const +{ + for (NetworkModelItem *item : m_items) { + switch (type) { + case NetworkItemsList::ActiveConnection: + if (item->activeConnectionPath() == parameter) { + return true; + } + break; + case NetworkItemsList::Connection: + if (item->connectionPath() == parameter) { + return true; + } + break; + case NetworkItemsList::Device: + if (item->devicePath() == parameter) { + return true; + } + break; + case NetworkItemsList::Name: + if (item->name() == parameter) { + return true; + } + break; + case NetworkItemsList::Ssid: + if (item->ssid() == parameter) { + return true; + } + break; + case NetworkItemsList::Uuid: + if (item->uuid() == parameter) { + return true; + } + break; + case NetworkItemsList::Type: + break; + default: break; + } + } + + return false; +} + +int NetworkItemsList::count() const +{ + return m_items.count(); +} + +int NetworkItemsList::indexOf(NetworkModelItem *item) const +{ + return m_items.indexOf(item); +} + +void NetworkItemsList::insertItem(NetworkModelItem *item) +{ + m_items << item; +} + +NetworkModelItem *NetworkItemsList::itemAt(int index) const +{ + return m_items.at(index); +} + +QList< NetworkModelItem*> NetworkItemsList::items() const +{ + return m_items; +} + +void NetworkItemsList::removeItem(NetworkModelItem *item) +{ + m_items.removeAll(item); +} + +QList< NetworkModelItem*> NetworkItemsList::returnItems(const NetworkItemsList::FilterType type, const QString ¶meter, const QString &additionalParameter) const +{ + QList result; + + for (NetworkModelItem *item : m_items) { + switch (type) { + case NetworkItemsList::ActiveConnection: + if (item->activeConnectionPath() == parameter) { + result << item; + } + break; + case NetworkItemsList::Connection: + if (item->connectionPath() == parameter) { + if (additionalParameter.isEmpty()) { + result << item; + } else { + if (item->devicePath() == additionalParameter) { + result << item; + } + } + } + break; + case NetworkItemsList::Device: + if (item->devicePath() == parameter) { + result << item; + } + break; + case NetworkItemsList::Name: + if (item->name() == parameter) { + result << item; + } + break; + case NetworkItemsList::Ssid: + if (item->ssid() == parameter) { + if (additionalParameter.isEmpty()) { + result << item; + } else { + if (item->devicePath() == additionalParameter) { + result << item; + } + } + } + break; + case NetworkItemsList::Uuid: + if (item->uuid() == parameter) { + result << item; + } + break; + case NetworkItemsList::Type: + break; + } + } + + return result; +} + +QList NetworkItemsList::returnItems(const NetworkItemsList::FilterType type, NetworkManager::ConnectionSettings::ConnectionType typeParameter) const +{ + QList result; + + for (NetworkModelItem *item : m_items) { + if (type == NetworkItemsList::Type) { + if (item->type() == typeParameter) { + result << item; + } + } + } + return result; +} diff --git a/networkmanagement/networkitemslist.h b/networkmanagement/networkitemslist.h new file mode 100644 index 0000000..2694905 --- /dev/null +++ b/networkmanagement/networkitemslist.h @@ -0,0 +1,65 @@ +/* + Copyright 2013-2018 Jan Grulich + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef NETWORKITEMSLIST_H +#define NETWORKITEMSLIST_H + +#include + +#include + +#include + +class NetworkModelItem; + +class NETWORKMANAGER_EXPORT NetworkItemsList : public QObject +{ + Q_OBJECT + +public: + enum FilterType { + ActiveConnection, + Connection, + Device, + Name, + Ssid, + Uuid, + Type + }; + + explicit NetworkItemsList(QObject *parent = nullptr); + ~NetworkItemsList() override; + + bool contains(const FilterType type, const QString ¶meter) const; + int count() const; + int indexOf(NetworkModelItem *item) const; + NetworkModelItem *itemAt(int index) const; + QList items() const; + QList returnItems(const FilterType type, const QString ¶meter, const QString &additionalParameter = QString()) const; + QList returnItems(const FilterType type, NetworkManager::ConnectionSettings::ConnectionType typeParameter) const; + + void insertItem(NetworkModelItem *item); + void removeItem(NetworkModelItem *item); + +private: + QList m_items; +}; + +#endif // NETWORKITEMSLIST_H diff --git a/networkmanagement/networkmodel.cpp b/networkmanagement/networkmodel.cpp new file mode 100644 index 0000000..6d5ade3 --- /dev/null +++ b/networkmanagement/networkmodel.cpp @@ -0,0 +1,1116 @@ +/* + Copyright 2013-2018 Jan Grulich + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "networkmodel.h" +#include "networkmodelitem.h" +#include "uiutils.h" + +#if WITH_MODEMMANAGER_SUPPORT +#include +#endif +#include + +Q_LOGGING_CATEGORY(gLcNm, "cutefish.networkmanager", QtInfoMsg) + +NetworkModel::NetworkModel(QObject *parent) + : QAbstractListModel(parent) +{ + initialize(); +} + +NetworkModel::~NetworkModel() +{ +} + +QVariant NetworkModel::data(const QModelIndex &index, int role) const +{ + const int row = index.row(); + + if (row >= 0 && row < m_list.count()) { + NetworkModelItem *item = m_list.itemAt(row); + + switch (role) { + case ConnectionDetailsRole: + return item->details(); + case ConnectionIconRole: + return item->icon(); + case ConnectionPathRole: + return item->connectionPath(); + case ConnectionStateRole: + return item->connectionState(); + case DeviceName: + return item->deviceName(); + case DevicePathRole: + return item->devicePath(); + case DeviceStateRole: + return item->deviceState(); + case DuplicateRole: + return item->duplicate(); + case ItemUniqueNameRole: + if (m_list.returnItems(NetworkItemsList::Name, item->name()).count() > 1) { + return item->originalName(); + } else { + return item->name(); + } + case ItemTypeRole: + return item->itemType(); + case LastUsedRole: + return UiUtils::formatLastUsedDateRelative(item->timestamp()); + case LastUsedDateOnlyRole: + return UiUtils::formatDateRelative(item->timestamp()); + case NameRole: + return item->name(); + case SectionRole: + return item->sectionType(); + case SignalRole: + return item->signal(); + case SlaveRole: + return item->slave(); + case SsidRole: + return item->ssid(); + case SpecificPathRole: + return item->specificPath(); + case SecurityTypeRole: + return item->securityType(); + case SecurityTypeStringRole: + return UiUtils::labelFromWirelessSecurity(item->securityType()); + case TimeStampRole: + return item->timestamp(); + case TypeRole: + return item->type(); + case UniRole: + return item->uni(); + case UuidRole: + return item->uuid(); + case VpnState: + return item->vpnState(); + case VpnType: + return item->vpnType(); + case RxBytesRole: + return item->rxBytes(); + case TxBytesRole: + return item->txBytes(); + default: + break; + } + } + + return QVariant(); +} + +int NetworkModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return parent.isValid() ? 0 : m_list.count(); +} + +QHash NetworkModel::roleNames() const +{ + QHash roles = QAbstractListModel::roleNames(); + roles[ConnectionDetailsRole] = "connectionDetails"; + roles[ConnectionIconRole] = "connectionIcon"; + roles[ConnectionPathRole] = "connectionPath"; + roles[ConnectionStateRole] = "connectionState"; + roles[DeviceName] = "deviceName"; + roles[DevicePathRole] = "devicePath"; + roles[DeviceStateRole] = "deviceState"; + roles[DuplicateRole] = "duplicate"; + roles[ItemUniqueNameRole] = "itemUniqueName"; + roles[ItemTypeRole] = "itemType"; + roles[LastUsedRole] = "lastUsed"; + roles[LastUsedDateOnlyRole] = "lastUsedDateOnly"; + roles[NameRole] = "name"; + roles[SectionRole] = "section"; + roles[SignalRole] = "signal"; + roles[SlaveRole] = "slave"; + roles[SsidRole] = "ssid"; + roles[SpecificPathRole] = "specificPath"; + roles[SecurityTypeRole] = "securityType"; + roles[SecurityTypeStringRole] = "securityTypeString"; + roles[TimeStampRole] = "timeStamp"; + roles[TypeRole] = "type"; + roles[UniRole] = "uni"; + roles[UuidRole] = "uuid"; + roles[VpnState] = "vpnState"; + roles[VpnType] = "vpnType"; + roles[RxBytesRole] = "rxBytes"; + roles[TxBytesRole] = "txBytes"; + + return roles; +} + +void NetworkModel::initialize() +{ + // Initialize existing connections + for (const NetworkManager::Connection::Ptr &connection : NetworkManager::listConnections()) { + addConnection(connection); + } + + // Initialize existing devices + for (const NetworkManager::Device::Ptr &dev : NetworkManager::networkInterfaces()) { + if (!dev->managed()) { + continue; + } + addDevice(dev); + } + + // Initialize existing active connections + for (const NetworkManager::ActiveConnection::Ptr &active : NetworkManager::activeConnections()) { + addActiveConnection(active); + } + + initializeSignals(); +} + +void NetworkModel::initializeSignals() +{ + connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionAdded, this, &NetworkModel::activeConnectionAdded, Qt::UniqueConnection); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionRemoved, this, &NetworkModel::activeConnectionRemoved, Qt::UniqueConnection); + connect(NetworkManager::settingsNotifier(), &NetworkManager::SettingsNotifier::connectionAdded, this, &NetworkModel::connectionAdded, Qt::UniqueConnection); + connect(NetworkManager::settingsNotifier(), &NetworkManager::SettingsNotifier::connectionRemoved, this, &NetworkModel::connectionRemoved, Qt::UniqueConnection); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::deviceAdded, this, &NetworkModel::deviceAdded, Qt::UniqueConnection); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::deviceRemoved, this, &NetworkModel::deviceRemoved, Qt::UniqueConnection); + connect(NetworkManager::notifier(), &NetworkManager::Notifier::statusChanged, this, &NetworkModel::statusChanged, Qt::UniqueConnection); +} + +void NetworkModel::initializeSignals(const NetworkManager::ActiveConnection::Ptr &activeConnection) +{ + if (activeConnection->vpn()) { + NetworkManager::VpnConnection::Ptr vpnConnection = activeConnection.objectCast(); + if (vpnConnection) { + connect(vpnConnection.data(), &NetworkManager::VpnConnection::stateChanged, this, &NetworkModel::activeVpnConnectionStateChanged, Qt::UniqueConnection); + } + } else { + connect(activeConnection.data(), &NetworkManager::ActiveConnection::stateChanged, this, &NetworkModel::activeConnectionStateChanged, Qt::UniqueConnection); + } +} + +void NetworkModel::initializeSignals(const NetworkManager::Connection::Ptr &connection) +{ + connect(connection.data(), &NetworkManager::Connection::updated, this, &NetworkModel::connectionUpdated, Qt::UniqueConnection); +} + +void NetworkModel::initializeSignals(const NetworkManager::Device::Ptr &device) +{ + connect(device.data(), &NetworkManager::Device::availableConnectionAppeared, this, &NetworkModel::availableConnectionAppeared, Qt::UniqueConnection); + connect(device.data(), &NetworkManager::Device::availableConnectionDisappeared, this, &NetworkModel::availableConnectionDisappeared, Qt::UniqueConnection); + connect(device.data(), &NetworkManager::Device::ipV4ConfigChanged, this, &NetworkModel::ipConfigChanged, Qt::UniqueConnection); + connect(device.data(), &NetworkManager::Device::ipV6ConfigChanged, this, &NetworkModel::ipConfigChanged, Qt::UniqueConnection); + connect(device.data(), &NetworkManager::Device::ipInterfaceChanged, this, &NetworkModel::ipInterfaceChanged); + connect(device.data(), &NetworkManager::Device::stateChanged, this, &NetworkModel::deviceStateChanged, Qt::UniqueConnection); + + auto deviceStatistics = device->deviceStatistics(); + connect(deviceStatistics.data(), &NetworkManager::DeviceStatistics::rxBytesChanged, this, [this, device](qulonglong rxBytes) { + for (auto *item : m_list.returnItems(NetworkItemsList::Device, device->uni())) { + item->setRxBytes(rxBytes); + updateItem(item); + } + }); + connect(deviceStatistics.data(), &NetworkManager::DeviceStatistics::txBytesChanged, this, [this, device](qulonglong txBytes) { + for (auto *item : m_list.returnItems(NetworkItemsList::Device, device->uni())) { + item->setTxBytes(txBytes); + updateItem(item); + } + }); + + if (device->type() == NetworkManager::Device::Wifi) { + NetworkManager::WirelessDevice::Ptr wifiDev = device.objectCast(); + connect(wifiDev.data(), &NetworkManager::WirelessDevice::networkAppeared, this, &NetworkModel::wirelessNetworkAppeared, Qt::UniqueConnection); + connect(wifiDev.data(), &NetworkManager::WirelessDevice::networkDisappeared, this, &NetworkModel::wirelessNetworkDisappeared, Qt::UniqueConnection); + + } + +#if WITH_MODEMMANAGER_SUPPORT + else if (device->type() == NetworkManager::Device::Modem) { + ModemManager::ModemDevice::Ptr modem = ModemManager::findModemDevice(device->udi()); + if (modem) { + if (modem->hasInterface(ModemManager::ModemDevice::ModemInterface)) { + ModemManager::Modem::Ptr modemNetwork = modem->interface(ModemManager::ModemDevice::ModemInterface).objectCast(); + if (modemNetwork) { + connect(modemNetwork.data(), &ModemManager::Modem::signalQualityChanged, this, &NetworkModel::gsmNetworkSignalQualityChanged, Qt::UniqueConnection); + connect(modemNetwork.data(), &ModemManager::Modem::accessTechnologiesChanged, this, &NetworkModel::gsmNetworkAccessTechnologiesChanged, Qt::UniqueConnection); + connect(modemNetwork.data(), &ModemManager::Modem::currentModesChanged, this, &NetworkModel::gsmNetworkCurrentModesChanged, Qt::UniqueConnection); + } + } + } + } +#endif +} + +void NetworkModel::initializeSignals(const NetworkManager::WirelessNetwork::Ptr &network) +{ + connect(network.data(), &NetworkManager::WirelessNetwork::signalStrengthChanged, this, &NetworkModel::wirelessNetworkSignalChanged, Qt::UniqueConnection); + connect(network.data(), &NetworkManager::WirelessNetwork::referenceAccessPointChanged, this, &NetworkModel::wirelessNetworkReferenceApChanged, Qt::UniqueConnection); +} + +void NetworkModel::addActiveConnection(const NetworkManager::ActiveConnection::Ptr &activeConnection) +{ + initializeSignals(activeConnection); + + NetworkManager::Device::Ptr device; + NetworkManager::Connection::Ptr connection = activeConnection->connection(); + + // Not necessary to have device for VPN connections + if (activeConnection && !activeConnection->vpn() && !activeConnection->devices().isEmpty()) { + device = NetworkManager::findNetworkInterface(activeConnection->devices().first()); + } + + // Check whether we have a base connection + if (!m_list.contains(NetworkItemsList::Uuid, connection->uuid())) { + // Active connection appeared before a base connection, so we have to add its base connection first + addConnection(connection); + } + + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::NetworkItemsList::Uuid, connection->uuid())) { + if (((device && device->uni() == item->devicePath()) || item->devicePath().isEmpty()) || item->type() == NetworkManager::ConnectionSettings::Vpn) { + item->setActiveConnectionPath(activeConnection->path()); + item->setConnectionState(activeConnection->state()); + if (activeConnection->vpn()) { + NetworkManager::VpnConnection::Ptr vpnConnection = activeConnection.objectCast(); + NetworkManager::VpnConnection::State state = vpnConnection->state(); + if (state == NetworkManager::VpnConnection::Prepare || + state == NetworkManager::VpnConnection::NeedAuth || + state == NetworkManager::VpnConnection::Connecting || + state == NetworkManager::VpnConnection::GettingIpConfig) { + item->setConnectionState(NetworkManager::ActiveConnection::Activating); + } else if (state == NetworkManager::VpnConnection::Activated) { + item->setConnectionState(NetworkManager::ActiveConnection::Activated); + } else { + item->setConnectionState(NetworkManager::ActiveConnection::Deactivated); + } + item->setVpnState(state); + } + item->invalidateDetails(); + qCDebug(gLcNm) << "Item " << item->name() << ": active connection state changed to " << item->connectionState(); + + if (device && device->uni() == item->devicePath()) { + auto deviceStatistics = device->deviceStatistics(); + item->setRxBytes(deviceStatistics->rxBytes()); + item->setTxBytes(deviceStatistics->txBytes()); + } + } + updateItem(item); + } +} + +void NetworkModel::addAvailableConnection(const QString &connection, const NetworkManager::Device::Ptr &device) +{ + if (!device) { + return; + } + + checkAndCreateDuplicate(connection, device->uni()); + + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Connection, connection)) { + // The item is already associated with another device + if (!device || !item->devicePath().isEmpty()) { + continue; + } + + if (device->ipInterfaceName().isEmpty()) { + item->setDeviceName(device->interfaceName()); + } else { + item->setDeviceName(device->ipInterfaceName()); + } + + item->setDevicePath(device->uni()); + item->setDeviceState(device->state()); + qCDebug(gLcNm) << "Item " << item->name() << ": device changed to " << item->devicePath(); +#if WITH_MODEMMANAGER_SUPPORT + if (device->type() == NetworkManager::Device::Modem) { + ModemManager::ModemDevice::Ptr modemDevice = ModemManager::findModemDevice(device->udi()); + if (modemDevice) { + ModemManager::Modem::Ptr modemInterface = modemDevice->interface(ModemManager::ModemDevice::ModemInterface).objectCast(); + if (modemInterface) { + item->setSignal(modemInterface->signalQuality().signal); + qCDebug(gLcNm) << "Item " << item->name() << ": signal changed to " << item->signal(); + } + } + } +#endif + if (item->type() == NetworkManager::ConnectionSettings::Wireless && item->mode() == NetworkManager::WirelessSetting::Infrastructure) { + // Find an accesspoint which could be removed, because it will be merged with a connection + for (NetworkModelItem *secondItem : m_list.returnItems(NetworkItemsList::Ssid, item->ssid())) { + if (secondItem->itemType() == NetworkModelItem::AvailableAccessPoint && secondItem->devicePath() == item->devicePath()) { + const int row = m_list.indexOf(secondItem); + qCDebug(gLcNm) << "Access point " << secondItem->name() << ": merged to " << item->name() << " connection"; + if (row >= 0) { + beginRemoveRows(QModelIndex(), row, row); + m_list.removeItem(secondItem); + secondItem->deleteLater(); + endRemoveRows(); + } + break; + } + } + + NetworkManager::WirelessDevice::Ptr wifiDevice = device.objectCast(); + if (wifiDevice) { + NetworkManager::WirelessNetwork::Ptr wifiNetwork = wifiDevice->findNetwork(item->ssid()); + if (wifiNetwork) { + updateFromWirelessNetwork(item, wifiNetwork, wifiDevice); + } + } + } + + updateItem(item); + break; + } +} + +void NetworkModel::addConnection(const NetworkManager::Connection::Ptr &connection) +{ + // Can't add a connection without name or uuid + if (connection->name().isEmpty() || connection->uuid().isEmpty()) { + return; + } + + initializeSignals(connection); + + NetworkManager::ConnectionSettings::Ptr settings = connection->settings(); + NetworkManager::VpnSetting::Ptr vpnSetting; + NetworkManager::WirelessSetting::Ptr wirelessSetting; + + if (settings->connectionType() == NetworkManager::ConnectionSettings::Vpn) { + vpnSetting = settings->setting(NetworkManager::Setting::Vpn).dynamicCast(); + } else if (settings->connectionType() == NetworkManager::ConnectionSettings::Wireless) { + wirelessSetting = settings->setting(NetworkManager::Setting::Wireless).dynamicCast(); + } + + // Check whether the connection is already in the model to avoid duplicates, but this shouldn't happen + if (m_list.contains(NetworkItemsList::Connection, connection->path())) { + return; + } + + NetworkModelItem *item = new NetworkModelItem(); + item->setConnectionPath(connection->path()); + item->setName(settings->id()); + item->setTimestamp(settings->timestamp()); + item->setType(settings->connectionType()); + item->setUuid(settings->uuid()); + item->setSlave(settings->isSlave()); + + if (item->type() == NetworkManager::ConnectionSettings::Vpn) { + item->setVpnType(vpnSetting->serviceType().section('.', -1)); + } else if (item->type() == NetworkManager::ConnectionSettings::Wireless) { + item->setMode(wirelessSetting->mode()); + item->setSecurityType(NetworkManager::securityTypeFromConnectionSetting(settings)); + item->setSsid(QString::fromUtf8(wirelessSetting->ssid())); + } + + item->invalidateDetails(); + + const int index = m_list.count(); + beginInsertRows(QModelIndex(), index, index); + m_list.insertItem(item); + endInsertRows(); + qCDebug(gLcNm) << "New connection " << item->name() << " added"; +} + +void NetworkModel::addDevice(const NetworkManager::Device::Ptr &device) +{ + initializeSignals(device); + + if (device->type() == NetworkManager::Device::Wifi) { + NetworkManager::WirelessDevice::Ptr wifiDev = device.objectCast(); + for (const NetworkManager::WirelessNetwork::Ptr &wifiNetwork : wifiDev->networks()) { + addWirelessNetwork(wifiNetwork, wifiDev); + } + } + + for (const NetworkManager::Connection::Ptr &connection : device->availableConnections()) { + addAvailableConnection(connection->path(), device); + } +} + +void NetworkModel::addWirelessNetwork(const NetworkManager::WirelessNetwork::Ptr &network, const NetworkManager::WirelessDevice::Ptr &device) +{ + initializeSignals(network); + +#if 0 + Avoid duplicating entries in the model + if (!Configuration::hotspotConnectionPath().isEmpty()) { + NetworkManager::ActiveConnection::Ptr activeConnection = NetworkManager::findActiveConnection(Configuration::hotspotConnectionPath()); + + // If we are trying to add an AP which is the one created by our hotspot, then we can skip this and don't add it twice + if (activeConnection && activeConnection->specificObject() == network->referenceAccessPoint()->uni()) { + return; + } + } +#endif + + // BUG: 386342 + // When creating a new hidden wireless network and attempting to connect to it, NM then later reports that AccessPoint appeared, but + // it doesn't know its SSID from some reason, this also makes Wireless device to advertise a new available connection, which we later + // attempt to merge with an AP, based on its SSID, but it doesn't find any, because we have AP with empty SSID. After this we get another + // AccessPoint appeared signal, this time we know SSID, but we don't attempt any merging, because it's usually the other way around, thus + // we need to attempt to merge it here with a connection we guess it's related to this new AP + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Type, NetworkManager::ConnectionSettings::Wireless)) { + if (item->itemType() != NetworkModelItem::AvailableConnection) + continue; + + NetworkManager::ConnectionSettings::Ptr connectionSettings = NetworkManager::findConnection(item->connectionPath())->settings(); + if (connectionSettings && connectionSettings->connectionType() == NetworkManager::ConnectionSettings::Wireless) { + NetworkManager::WirelessSetting::Ptr wirelessSetting = connectionSettings->setting(NetworkManager::Setting::Wireless).dynamicCast(); + if (QString::fromUtf8(wirelessSetting->ssid()) == network->ssid()) { + const QString bssid = NetworkManager::macAddressAsString(wirelessSetting->bssid()); + const QString restrictedHw = NetworkManager::macAddressAsString(wirelessSetting->macAddress()); + if ((bssid.isEmpty() || bssid == network->referenceAccessPoint()->hardwareAddress()) && + (restrictedHw.isEmpty() || restrictedHw == device->hardwareAddress())) { + updateFromWirelessNetwork(item, network, device); + return; + } + } + } + } + + NetworkManager::WirelessSetting::NetworkMode mode = NetworkManager::WirelessSetting::Infrastructure; + NetworkManager::WirelessSecurityType securityType = NetworkManager::UnknownSecurity; + + NetworkManager::AccessPoint::Ptr ap = network->referenceAccessPoint(); + if (ap && (ap->capabilities().testFlag(NetworkManager::AccessPoint::Privacy) || ap->wpaFlags() || ap->rsnFlags())) { + securityType = NetworkManager::findBestWirelessSecurity(device->wirelessCapabilities(), true, (device->mode() == NetworkManager::WirelessDevice::Adhoc), + ap->capabilities(), ap->wpaFlags(), ap->rsnFlags()); + if (network->referenceAccessPoint()->mode() == NetworkManager::AccessPoint::Infra) { + mode = NetworkManager::WirelessSetting::Infrastructure; + } else if (network->referenceAccessPoint()->mode() == NetworkManager::AccessPoint::Adhoc) { + mode = NetworkManager::WirelessSetting::Adhoc; + } else if (network->referenceAccessPoint()->mode() == NetworkManager::AccessPoint::ApMode) { + mode = NetworkManager::WirelessSetting::Ap; + } + } + + NetworkModelItem *item = new NetworkModelItem(); + if (device->ipInterfaceName().isEmpty()) { + item->setDeviceName(device->interfaceName()); + } else { + item->setDeviceName(device->ipInterfaceName()); + } + item->setDevicePath(device->uni()); + item->setMode(mode); + item->setName(network->ssid()); + item->setSignal(network->signalStrength()); + item->setSpecificPath(network->referenceAccessPoint()->uni()); + item->setSsid(network->ssid()); + item->setType(NetworkManager::ConnectionSettings::Wireless); + item->setSecurityType(securityType); + item->invalidateDetails(); + + const int index = m_list.count(); + beginInsertRows(QModelIndex(), index, index); + m_list.insertItem(item); + endInsertRows(); + qCDebug(gLcNm) << "New wireless network " << item->name() << " added"; +} + +void NetworkModel::checkAndCreateDuplicate(const QString &connection, const QString &deviceUni) +{ + bool createDuplicate = false; + NetworkModelItem *originalItem = nullptr; + + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Connection, connection)) { + if (!item->duplicate()) { + originalItem = item; + } + + if (!item->duplicate() && item->itemType() == NetworkModelItem::AvailableConnection && (item->devicePath() != deviceUni && !item->devicePath().isEmpty())) { + createDuplicate = true; + } + } + + if (createDuplicate) { + NetworkModelItem *duplicatedItem = new NetworkModelItem(originalItem); + duplicatedItem->invalidateDetails(); + + const int index = m_list.count(); + beginInsertRows(QModelIndex(), index, index); + m_list.insertItem(duplicatedItem); + endInsertRows(); + } +} + +void NetworkModel::onItemUpdated() +{ + NetworkModelItem *item = static_cast(sender()); + if (item) { + updateItem(item); + } +} + +void NetworkModel::setDeviceStatisticsRefreshRateMs(const QString &devicePath, uint refreshRate) +{ + NetworkManager::Device::Ptr device = NetworkManager::findNetworkInterface(devicePath); + + if (device) { + device->deviceStatistics()->setRefreshRateMs(refreshRate); + } +} + +void NetworkModel::updateItem(NetworkModelItem*item) +{ + const int row = m_list.indexOf(item); + + if (row >= 0) { + item->invalidateDetails(); + QModelIndex index = createIndex(row, 0); + Q_EMIT dataChanged(index, index, item->changedRoles()); + item->clearChangedRoles(); + } +} + +void NetworkModel::accessPointSignalStrengthChanged(int signal) +{ + NetworkManager::AccessPoint *apPtr = qobject_cast(sender()); + if (!apPtr) { + return; + } + + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Ssid, apPtr->ssid())) { + if (item->specificPath() == apPtr->uni()) { + item->setSignal(signal); + updateItem(item); + qCDebug(gLcNm) << "AccessPoint " << item->name() << ": signal changed to " << item->signal(); + } + } +} + +void NetworkModel::activeConnectionAdded(const QString &activeConnection) +{ + NetworkManager::ActiveConnection::Ptr activeCon = NetworkManager::findActiveConnection(activeConnection); + + if (activeCon) { + addActiveConnection(activeCon); + } +} + +void NetworkModel::activeConnectionRemoved(const QString &activeConnection) +{ + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::ActiveConnection, activeConnection)) { + item->setActiveConnectionPath(QString()); + item->setConnectionState(NetworkManager::ActiveConnection::Deactivated); + item->setVpnState(NetworkManager::VpnConnection::Disconnected); + updateItem(item); + qCDebug(gLcNm) << "Item " << item->name() << ": active connection removed"; + } +} + +void NetworkModel::activeConnectionStateChanged(NetworkManager::ActiveConnection::State state) +{ + NetworkManager::ActiveConnection *activePtr = qobject_cast(sender()); + + if (!activePtr) { + return; + } + + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::ActiveConnection, activePtr->path())) { + item->setConnectionState(state); + updateItem(item); + qCDebug(gLcNm) << "Item " << item->name() << ": active connection changed to " << item->connectionState(); + } +} + +void NetworkModel::activeVpnConnectionStateChanged(NetworkManager::VpnConnection::State state, NetworkManager::VpnConnection::StateChangeReason reason) +{ + Q_UNUSED(reason) + NetworkManager::ActiveConnection *activePtr = qobject_cast(sender()); + + if (!activePtr) { + return; + } + + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::ActiveConnection, activePtr->path())) { + if (state == NetworkManager::VpnConnection::Prepare || + state == NetworkManager::VpnConnection::NeedAuth || + state == NetworkManager::VpnConnection::Connecting || + state == NetworkManager::VpnConnection::GettingIpConfig) { + item->setConnectionState(NetworkManager::ActiveConnection::Activating); + } else if (state == NetworkManager::VpnConnection::Activated) { + item->setConnectionState(NetworkManager::ActiveConnection::Activated); + } else { + item->setConnectionState(NetworkManager::ActiveConnection::Deactivated); + } + item->setVpnState(state); + updateItem(item); + qCDebug(gLcNm) << "Item " << item->name() << ": active connection changed to " << item->connectionState(); + } +} + +void NetworkModel::availableConnectionAppeared(const QString &connection) +{ + NetworkManager::Device::Ptr device = NetworkManager::findNetworkInterface(qobject_cast(sender())->uni()); + if (!device) { + return; + } + + addAvailableConnection(connection, device); +} + +void NetworkModel::availableConnectionDisappeared(const QString &connection) +{ + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Connection, connection)) { + bool available = false; + const QString devicePath = item->devicePath(); + const QString specificPath = item->specificPath(); + + // We have to check whether the connection is still available, because it might be + // presented in the model for more devices and we don't want to remove it for all of them. + + // Check whether the device is still available + NetworkManager::Device::Ptr device = NetworkManager::findNetworkInterface(devicePath); + if (device) { + // Check whether the connection is still listed as available + for (const NetworkManager::Connection::Ptr &connection : device->availableConnections()) { + if (connection->path() == item->connectionPath()) { + available = true; + break; + } + } + } + + if (!available) { + item->setDeviceName(QString()); + item->setDevicePath(QString()); + item->setDeviceState(NetworkManager::Device::UnknownState); + item->setSignal(0); + item->setSpecificPath(QString()); + qCDebug(gLcNm) << "Item " << item->name() << " removed as available connection"; + // Check whether the connection is still available as an access point, this happens + // when we change its properties, like ssid, bssid, security etc. + if (item->type() == NetworkManager::ConnectionSettings::Wireless && !specificPath.isEmpty()) { + if (device && device->type() == NetworkManager::Device::Wifi) { + NetworkManager::WirelessDevice::Ptr wifiDevice = device.objectCast(); + if (wifiDevice) { + NetworkManager::AccessPoint::Ptr ap = wifiDevice->findAccessPoint(specificPath); + if (ap) { + NetworkManager::WirelessNetwork::Ptr network = wifiDevice->findNetwork(ap->ssid()); + if (network) { + addWirelessNetwork(network, wifiDevice); + } + } + } + } + } + + if (item->duplicate()) { + const int row = m_list.indexOf(item); + if (row >= 0) { + qCDebug(gLcNm) << "Duplicate item " << item->name() << " removed completely"; + beginRemoveRows(QModelIndex(), row, row); + m_list.removeItem(item); + item->deleteLater(); + endRemoveRows(); + } + } else { + updateItem(item); + } + } + available = false; + } +} + +void NetworkModel::connectionAdded(const QString &connection) +{ + NetworkManager::Connection::Ptr newConnection = NetworkManager::findConnection(connection); + if (newConnection) { + addConnection(newConnection); + } +} + +void NetworkModel::connectionRemoved(const QString &connection) +{ + bool remove = false; + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Connection, connection)) { + // When the item type is wireless, we can remove only the connection and leave it as an available access point + if (item->type() == NetworkManager::ConnectionSettings::Wireless && !item->devicePath().isEmpty()) { + for (NetworkModelItem *secondItem : m_list.items()) { + // Remove it entirely when there is another connection with the same configuration and for the same device + // or it's a shared connection + if ((item->mode() != NetworkManager::WirelessSetting::Infrastructure) || + (item->connectionPath() != secondItem->connectionPath() && + item->devicePath() == secondItem->devicePath() && + item->mode() == secondItem->mode() && + item->securityType() == secondItem->securityType() && + item->ssid() == secondItem->ssid())) { + remove = true; + break; + } + } + + if (!remove) { + item->setConnectionPath(QString()); + item->setName(item->ssid()); + item->setSlave(false); + item->setTimestamp(QDateTime()); + item->setUuid(QString()); + updateItem(item); + qCDebug(gLcNm) << "Item " << item->name() << ": connection removed"; + } + } else { + remove = true; + } + + if (remove) { + const int row = m_list.indexOf(item); + if (row >= 0) { + qCDebug(gLcNm) << "Item " << item->name() << " removed completely"; + beginRemoveRows(QModelIndex(), row, row); + m_list.removeItem(item); + item->deleteLater(); + endRemoveRows(); + } + } + remove = false; + } +} + +void NetworkModel::connectionUpdated() +{ + NetworkManager::Connection *connectionPtr = qobject_cast(sender()); + if (!connectionPtr) { + return; + } + + NetworkManager::ConnectionSettings::Ptr settings = connectionPtr->settings(); + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Connection, connectionPtr->path())) { + item->setConnectionPath(connectionPtr->path()); + item->setName(settings->id()); + item->setTimestamp(settings->timestamp()); + item->setType(settings->connectionType()); + item->setUuid(settings->uuid()); + + if (item->type() == NetworkManager::ConnectionSettings::Wireless) { + NetworkManager::WirelessSetting::Ptr wirelessSetting; + wirelessSetting = settings->setting(NetworkManager::Setting::Wireless).dynamicCast(); + item->setMode(wirelessSetting->mode()); + item->setSecurityType(NetworkManager::securityTypeFromConnectionSetting(settings)); + item->setSsid(QString::fromUtf8(wirelessSetting->ssid())); + // TODO check whether BSSID has changed and update the wireless info + } + + updateItem(item); + qCDebug(gLcNm) << "Item " << item->name() << ": connection updated"; + } +} + +void NetworkModel::deviceAdded(const QString &device) +{ + NetworkManager::Device::Ptr dev = NetworkManager::findNetworkInterface(device); + if (dev) { + addDevice(dev); + } +} + +void NetworkModel::deviceRemoved(const QString &device) +{ + // Make all items unavailable + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Device, device)) { + availableConnectionDisappeared(item->connectionPath()); + } +} + +void NetworkModel::deviceStateChanged(NetworkManager::Device::State state, NetworkManager::Device::State oldState, NetworkManager::Device::StateChangeReason reason) +{ + Q_UNUSED(oldState); + Q_UNUSED(reason); + + NetworkManager::Device::Ptr device = NetworkManager::findNetworkInterface(qobject_cast(sender())->uni()); + + if (!device) { + return; + } + + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Device, device->uni())) { + item->setDeviceState(state); + updateItem(item); +// qCDebug(gLcNm) << "Item " << item->name() << ": device state changed to " << item->deviceState(); + } +} + +#if WITH_MODEMMANAGER_SUPPORT +void NetworkModel::gsmNetworkAccessTechnologiesChanged(QFlags accessTechnologies) +{ + Q_UNUSED(accessTechnologies); + ModemManager::Modem *gsmNetwork = qobject_cast(sender()); + if (!gsmNetwork) + return; + + for (const NetworkManager::Device::Ptr &dev : NetworkManager::networkInterfaces()) { + if (dev->type() != NetworkManager::Device::Modem) { + continue; + } + + ModemManager::ModemDevice::Ptr modem = ModemManager::findModemDevice(dev->udi()); + if (!modem) { + continue; + } + + if (!modem->hasInterface(ModemManager::ModemDevice::ModemInterface)) { + continue; + } + + ModemManager::Modem::Ptr modemNetwork = modem->interface(ModemManager::ModemDevice::ModemInterface).objectCast(); + if (!modemNetwork || modemNetwork->device() != gsmNetwork->device()) { + continue; + } + + // TODO store access technology internally? + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Device, dev->uni())) { + updateItem(item); + } + } +} + +void NetworkModel::gsmNetworkCurrentModesChanged() +{ + ModemManager::Modem *gsmNetwork = qobject_cast(sender()); + if (!gsmNetwork) { + return; + } + + for (const NetworkManager::Device::Ptr &dev : NetworkManager::networkInterfaces()) { + if (dev->type() != NetworkManager::Device::Modem) + continue; + + ModemManager::ModemDevice::Ptr modem = ModemManager::findModemDevice(dev->udi()); + if (!modem) { + continue; + } + + if (!modem->hasInterface(ModemManager::ModemDevice::ModemInterface)) { + continue; + } + + ModemManager::Modem::Ptr modemNetwork = modem->interface(ModemManager::ModemDevice::ModemInterface).objectCast(); + if (!modemNetwork || modemNetwork->device() != gsmNetwork->device()) { + continue; + } + + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Device, dev->uni())) { + updateItem(item); + } + } +} + +void NetworkModel::gsmNetworkSignalQualityChanged(const ModemManager::SignalQualityPair &signalQuality) +{ + ModemManager::Modem *gsmNetwork = qobject_cast(sender()); + if (!gsmNetwork) { + return; + } + + for (const NetworkManager::Device::Ptr &dev : NetworkManager::networkInterfaces()) { + if (dev->type() != NetworkManager::Device::Modem) { + continue; + } + + ModemManager::ModemDevice::Ptr modem = ModemManager::findModemDevice(dev->udi()); + if (!modem) { + continue; + } + + if (!modem->hasInterface(ModemManager::ModemDevice::ModemInterface)) { + continue; + } + + ModemManager::Modem::Ptr modemNetwork = modem->interface(ModemManager::ModemDevice::ModemInterface).objectCast(); + if (!modemNetwork || modemNetwork->device() != gsmNetwork->device()) { + continue; + } + + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Device, dev->uni())) { + item->setSignal(signalQuality.signal); + updateItem(item); + } + } +} + +#endif + +void NetworkModel::ipConfigChanged() +{ + NetworkManager::Device::Ptr device = NetworkManager::findNetworkInterface(qobject_cast(sender())->uni()); + + if (!device) { + return; + } + + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Device, device->uni())) { + updateItem(item); +// qCDebug(gLcNm) << "Item " << item->name() << ": device ipconfig changed"; + } +} + +void NetworkModel::ipInterfaceChanged() +{ + NetworkManager::Device *device = qobject_cast(sender()); + if (!device) { + return; + } + + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Device, device->uni())) { + if (device->ipInterfaceName().isEmpty()) { + item->setDeviceName(device->interfaceName()); + } else { + item->setDeviceName(device->ipInterfaceName()); + } + } +} + +void NetworkModel::statusChanged(NetworkManager::Status status) +{ + Q_UNUSED(status); + + qCDebug(gLcNm) << "NetworkManager state changed to " << status; + // This has probably effect only for VPN connections + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Type, NetworkManager::ConnectionSettings::Vpn)) { + updateItem(item); + } +} + +void NetworkModel::wirelessNetworkAppeared(const QString &ssid) +{ + NetworkManager::Device::Ptr device = NetworkManager::findNetworkInterface(qobject_cast(sender())->uni()); + if (device && device->type() == NetworkManager::Device::Wifi) { + NetworkManager::WirelessDevice::Ptr wirelessDevice = device.objectCast(); + NetworkManager::WirelessNetwork::Ptr network = wirelessDevice->findNetwork(ssid); + addWirelessNetwork(network, wirelessDevice); + } +} + +void NetworkModel::wirelessNetworkDisappeared(const QString &ssid) +{ + NetworkManager::Device::Ptr device = NetworkManager::findNetworkInterface(qobject_cast(sender())->uni()); + if (!device) { + return; + } + + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Ssid, ssid, device->uni())) { + // Remove the entire item, because it's only AP or it's a duplicated available connection + if (item->itemType() == NetworkModelItem::AvailableAccessPoint || item->duplicate()) { + const int row = m_list.indexOf(item); + if (row >= 0) { + qCDebug(gLcNm) << "Wireless network " << item->name() << " removed completely"; + beginRemoveRows(QModelIndex(), row, row); + m_list.removeItem(item); + item->deleteLater(); + endRemoveRows(); + } + // Remove only AP and device from the item and leave it as an unavailable connection + } else { + if (item->mode() == NetworkManager::WirelessSetting::Infrastructure) { + item->setDeviceName(QString()); + item->setDevicePath(QString()); + item->setSpecificPath(QString()); + } + item->setSignal(0); + updateItem(item); + qCDebug(gLcNm) << "Item " << item->name() << ": wireless network removed"; + } + } +} + +void NetworkModel::wirelessNetworkReferenceApChanged(const QString &accessPoint) +{ + NetworkManager::WirelessNetwork *networkPtr = qobject_cast(sender()); + + if (!networkPtr) { + return; + } + + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Ssid, networkPtr->ssid(), networkPtr->device())) { + NetworkManager::Connection::Ptr connection = NetworkManager::findConnection(item->connectionPath()); + if (!connection) { + continue; + } + + NetworkManager::WirelessSetting::Ptr wirelessSetting = connection->settings()->setting(NetworkManager::Setting::Wireless).staticCast(); + if (!wirelessSetting) { + continue; + } + + if (wirelessSetting->bssid().isEmpty()) { + item->setSpecificPath(accessPoint); + updateItem(item); + } + } +} + +void NetworkModel::wirelessNetworkSignalChanged(int signal) +{ + NetworkManager::WirelessNetwork *networkPtr = qobject_cast(sender()); + if (!networkPtr) { + return; + } + + for (NetworkModelItem *item : m_list.returnItems(NetworkItemsList::Ssid, networkPtr->ssid(), networkPtr->device())) { + if (item->specificPath() == networkPtr->referenceAccessPoint()->uni()) { + item->setSignal(signal); + updateItem(item); +// qCDebug(gLcNm) << "Wireless network " << item->name() << ": signal changed to " << item->signal(); + } + } +} + +NetworkManager::WirelessSecurityType NetworkModel::alternativeWirelessSecurity(const NetworkManager::WirelessSecurityType type) +{ + if (type == NetworkManager::WpaPsk) { + return NetworkManager::Wpa2Psk; + } else if (type == NetworkManager::WpaEap) { + return NetworkManager::Wpa2Eap; + } else if (type == NetworkManager::Wpa2Psk) { + return NetworkManager::WpaPsk; + } else if (type == NetworkManager::Wpa2Eap) { + return NetworkManager::WpaEap; + } + return type; +} + +void NetworkModel::updateFromWirelessNetwork(NetworkModelItem *item, const NetworkManager::WirelessNetwork::Ptr &network, const NetworkManager::WirelessDevice::Ptr &device) +{ + NetworkManager::WirelessSecurityType securityType = NetworkManager::UnknownSecurity; + NetworkManager::AccessPoint::Ptr ap = network->referenceAccessPoint(); + if (ap && ap->capabilities().testFlag(NetworkManager::AccessPoint::Privacy)) { + securityType = NetworkManager::findBestWirelessSecurity(device->wirelessCapabilities(), true, (device->mode() == NetworkManager::WirelessDevice::Adhoc), + ap->capabilities(), ap->wpaFlags(), ap->rsnFlags()); + } + + // Check whether the connection is associated with some concrete AP + NetworkManager::Connection::Ptr connection = NetworkManager::findConnection(item->connectionPath()); + if (connection) { + NetworkManager::WirelessSetting::Ptr wirelessSetting = connection->settings()->setting(NetworkManager::Setting::Wireless).staticCast(); + if (wirelessSetting) { + if (!wirelessSetting->bssid().isEmpty()) { + for (const NetworkManager::AccessPoint::Ptr ap : network->accessPoints()) { + if (ap->hardwareAddress() == NetworkManager::macAddressAsString(wirelessSetting->bssid())) { + item->setSignal(ap->signalStrength()); + item->setSpecificPath(ap->uni()); + // We need to watch this AP for signal changes + connect(ap.data(), &NetworkManager::AccessPoint::signalStrengthChanged, this, &NetworkModel::accessPointSignalStrengthChanged, Qt::UniqueConnection); + } + } + } else { + item->setSignal(network->signalStrength()); + item->setSpecificPath(network->referenceAccessPoint()->uni()); + } + } + } + item->setSecurityType(securityType); + updateItem(item); +} diff --git a/networkmanagement/networkmodel.h b/networkmanagement/networkmodel.h new file mode 100644 index 0000000..2a4b364 --- /dev/null +++ b/networkmanagement/networkmodel.h @@ -0,0 +1,180 @@ +/* + Copyright 2013-2018 Jan Grulich + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef NETWORKMODEL_H +#define NETWORKMODEL_H + +#include + +#include +#include + +#include "networkitemslist.h" + +#include +#include +#include +#include + +#if WITH_MODEMMANAGER_SUPPORT +#include +#endif + +Q_DECLARE_LOGGING_CATEGORY(gLcNm) + +class NETWORKMANAGER_EXPORT NetworkModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit NetworkModel(QObject *parent = nullptr); + ~NetworkModel() override; + + enum ItemRole { + ConnectionDetailsRole = Qt::UserRole + 1, + ConnectionIconRole, + ConnectionPathRole, + ConnectionStateRole, + DeviceName, + DevicePathRole, + DeviceStateRole, + DuplicateRole, + ItemUniqueNameRole, + ItemTypeRole, + LastUsedRole, + LastUsedDateOnlyRole, + NameRole, + SecurityTypeRole, + SecurityTypeStringRole, + SectionRole, + SignalRole, + SlaveRole, + SsidRole, + SpecificPathRole, + TimeStampRole, + TypeRole, + UniRole, + UuidRole, + VpnState, + VpnType, + RxBytesRole, + TxBytesRole + }; + Q_ENUMS(ItemRole) + + enum ConnectionStatus { + UnknownState = 0, + Activating, + Activated, + Deactivating, + Deactivated + }; + Q_ENUMS(ConnectionStatus) + + enum ConnectionType { + UnknownConnectionType = 0, + Adsl, + Bluetooth, + Bond, + Bridge, + Cdma, + Gsm, + Infiniband, + OLPCMesh, + Pppoe, + Vlan, + Vpn, + Wimax, + Wired, + Wireless + }; + Q_ENUMS(ConnectionType) + + enum SecurityType { + UnknownSecurity = -1, + NoneSecurity = 0, + StaticWep, + DynamicWep, + Leap, + WpaPsk, + WpaEap, + Wpa2Psk, + Wpa2Eap, + SAE + }; + Q_ENUMS(SecurityType) + + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + +public Q_SLOTS: + void onItemUpdated(); + void setDeviceStatisticsRefreshRateMs(const QString &devicePath, uint refreshRate); + +private Q_SLOTS: + void accessPointSignalStrengthChanged(int signal); + void activeConnectionAdded(const QString &activeConnection); + void activeConnectionRemoved(const QString &activeConnection); + void activeConnectionStateChanged(NetworkManager::ActiveConnection::State state); + void activeVpnConnectionStateChanged(NetworkManager::VpnConnection::State state,NetworkManager::VpnConnection::StateChangeReason reason); + void availableConnectionAppeared(const QString &connection); + void availableConnectionDisappeared(const QString &connection); + void connectionAdded(const QString &connection); + void connectionRemoved(const QString &connection); + void connectionUpdated(); + void deviceAdded(const QString &device); + void deviceRemoved(const QString &device); + void deviceStateChanged(NetworkManager::Device::State state, NetworkManager::Device::State oldState, NetworkManager::Device::StateChangeReason reason); +#if WITH_MODEMMANAGER_SUPPORT + void gsmNetworkAccessTechnologiesChanged(QFlags accessTechnologies); + void gsmNetworkCurrentModesChanged(); + void gsmNetworkSignalQualityChanged(const ModemManager::SignalQualityPair &signalQuality); +#endif + void ipConfigChanged(); + void ipInterfaceChanged(); + void statusChanged(NetworkManager::Status status); + void wirelessNetworkAppeared(const QString &ssid); + void wirelessNetworkDisappeared(const QString &ssid); + void wirelessNetworkSignalChanged(int signal); + void wirelessNetworkReferenceApChanged(const QString &accessPoint); + + void initialize(); +private: + NetworkItemsList m_list; + + void addActiveConnection(const NetworkManager::ActiveConnection::Ptr &activeConnection); + void addAvailableConnection(const QString &connection, const NetworkManager::Device::Ptr &device); + void addConnection(const NetworkManager::Connection::Ptr &connection); + void addDevice(const NetworkManager::Device::Ptr &device); + void addWirelessNetwork(const NetworkManager::WirelessNetwork::Ptr &network, const NetworkManager::WirelessDevice::Ptr &device); + void checkAndCreateDuplicate(const QString &connection, const QString &deviceUni); + void initializeSignals(); + void initializeSignals(const NetworkManager::ActiveConnection::Ptr &activeConnection); + void initializeSignals(const NetworkManager::Connection::Ptr &connection); + void initializeSignals(const NetworkManager::Device::Ptr &device); + void initializeSignals(const NetworkManager::WirelessNetwork::Ptr &network); + void updateItem(NetworkModelItem *item); + void updateFromWirelessNetwork(NetworkModelItem *item, const NetworkManager::WirelessNetwork::Ptr &network, const NetworkManager::WirelessDevice::Ptr &device); + + NetworkManager::WirelessSecurityType alternativeWirelessSecurity(const NetworkManager::WirelessSecurityType type); +}; + +#endif // NETWORKMODEL_H diff --git a/networkmanagement/networkmodelitem.cpp b/networkmanagement/networkmodelitem.cpp new file mode 100644 index 0000000..f154b00 --- /dev/null +++ b/networkmanagement/networkmodelitem.cpp @@ -0,0 +1,686 @@ +/* + Copyright 2013-2018 Jan Grulich + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#include "networkmodelitem.h" +#include "uiutils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if WITH_MODEMMANAGER_SUPPORT +#include +#include +#include +#include +#include +#endif + +NetworkModelItem::NetworkModelItem(QObject *parent) + : QObject(parent) + , m_connectionState(NetworkManager::ActiveConnection::Deactivated) + , m_deviceState(NetworkManager::Device::UnknownState) + , m_detailsValid(false) + , m_duplicate(false) + , m_mode(NetworkManager::WirelessSetting::Infrastructure) + , m_securityType(NetworkManager::NoneSecurity) + , m_signal(0) + , m_slave(false) + , m_type(NetworkManager::ConnectionSettings::Unknown) + , m_vpnState(NetworkManager::VpnConnection::Unknown) + , m_rxBytes(0) + , m_txBytes(0) +{ +} + +NetworkModelItem::NetworkModelItem(const NetworkModelItem *item, QObject *parent) + : QObject(parent) + , m_connectionPath(item->connectionPath()) + , m_connectionState(NetworkManager::ActiveConnection::Deactivated) + , m_detailsValid(false) + , m_duplicate(true) + , m_mode(item->mode()) + , m_name(item->name()) + , m_securityType(item->securityType()) + , m_slave(item->slave()) + , m_ssid(item->ssid()) + , m_timestamp(item->timestamp()) + , m_type(item->type()) + , m_uuid(item->uuid()) + , m_vpnState(NetworkManager::VpnConnection::Unknown) + , m_rxBytes(0) + , m_txBytes(0) +{ +} + +NetworkModelItem::~NetworkModelItem() +{ +} + +QString NetworkModelItem::activeConnectionPath() const +{ + return m_activeConnectionPath; +} + +void NetworkModelItem::setActiveConnectionPath(const QString &path) +{ + m_activeConnectionPath = path; +} + +QString NetworkModelItem::connectionPath() const +{ + return m_connectionPath; +} + +void NetworkModelItem::setConnectionPath(const QString &path) +{ + if (m_connectionPath != path) { + m_connectionPath = path; + m_changedRoles << NetworkModel::ConnectionPathRole << NetworkModel::UniRole; + } +} + +NetworkManager::ActiveConnection::State NetworkModelItem::connectionState() const +{ + return m_connectionState; +} + +void NetworkModelItem::setConnectionState(NetworkManager::ActiveConnection::State state) +{ + if (m_connectionState != state) { + m_connectionState = state; + m_changedRoles << NetworkModel::ConnectionStateRole << NetworkModel::SectionRole; + refreshIcon(); + } +} + +QStringList NetworkModelItem::details() const +{ + if (!m_detailsValid) { + updateDetails(); + } + return m_details; +} + +QString NetworkModelItem::devicePath() const +{ + return m_devicePath; +} + +QString NetworkModelItem::deviceName() const +{ + return m_deviceName; +} + +void NetworkModelItem::setDeviceName(const QString &name) +{ + if (m_deviceName != name) { + m_deviceName = name; + m_changedRoles << NetworkModel::DeviceName; + } +} + +void NetworkModelItem::setDevicePath(const QString &path) +{ + if (m_devicePath != path) { + m_devicePath = path; + m_changedRoles << NetworkModel::DevicePathRole << NetworkModel::ItemTypeRole << NetworkModel::UniRole; + } +} + +QString NetworkModelItem::deviceState() const +{ + return UiUtils::connectionStateToString(m_deviceState); +} + +void NetworkModelItem::setDeviceState(const NetworkManager::Device::State state) +{ + if (m_deviceState != state) { + m_deviceState = state; + m_changedRoles << NetworkModel::DeviceStateRole; + } +} + +bool NetworkModelItem::duplicate() const +{ + return m_duplicate; +} + +void NetworkModelItem::setIcon(const QString& icon) +{ + if (icon != m_icon) { + m_icon = icon; + m_changedRoles << NetworkModel::ConnectionIconRole; + } +} + +void NetworkModelItem::refreshIcon() +{ + setIcon(computeIcon()); +} + +QString NetworkModelItem::computeIcon() const +{ + switch (m_type) { + case NetworkManager::ConnectionSettings::Adsl: + return QStringLiteral("network-mobile-100"); + break; + case NetworkManager::ConnectionSettings::Bluetooth: + if (m_connectionState == NetworkManager::ActiveConnection::Activated) { + return QStringLiteral("network-bluetooth-activated"); + } else { + return QStringLiteral("network-bluetooth"); + } + break; + case NetworkManager::ConnectionSettings::Bond: + break; + case NetworkManager::ConnectionSettings::Bridge: + break; + case NetworkManager::ConnectionSettings::Cdma: + case NetworkManager::ConnectionSettings::Gsm: + if (m_signal == 0 ) { + return QStringLiteral("network-mobile-0"); + } else if (m_signal < 20) { + return QStringLiteral("network-mobile-20"); + } else if (m_signal < 40) { + return QStringLiteral("network-mobile-40"); + } else if (m_signal < 60) { + return QStringLiteral("network-mobile-60"); + } else if (m_signal < 80) { + return QStringLiteral("network-mobile-80"); + } else { + return QStringLiteral("network-mobile-100"); + } + break; + case NetworkManager::ConnectionSettings::Infiniband: + break; + case NetworkManager::ConnectionSettings::OLPCMesh: + break; + case NetworkManager::ConnectionSettings::Pppoe: + return QStringLiteral("network-mobile-100"); + break; + case NetworkManager::ConnectionSettings::Vlan: + break; + case NetworkManager::ConnectionSettings::Vpn: + case NetworkManager::ConnectionSettings::WireGuard: + return QStringLiteral("network-vpn"); + break; + case NetworkManager::ConnectionSettings::Wired: + if (m_connectionState == NetworkManager::ActiveConnection::Activated) { + return QStringLiteral("network-wired-activated"); + } else { + return QStringLiteral("network-wired"); + } + break; + case NetworkManager::ConnectionSettings::Wireless: + if (m_signal == 0 ) { + if (m_mode == NetworkManager::WirelessSetting::Adhoc || m_mode == NetworkManager::WirelessSetting::Ap) { + return (m_securityType <= NetworkManager::NoneSecurity) ? QStringLiteral("network-wireless-connected-100") : QStringLiteral("network-wireless-connected-100"); + } + return (m_securityType <= NetworkManager::NoneSecurity) ? QStringLiteral("network-wireless-connected-100") : QStringLiteral("network-wireless-connected-100"); + } else if (m_signal <= 25) { + return QStringLiteral("network-wireless-connected-25"); + } else if (m_signal <= 50) { + return QStringLiteral("network-wireless-connected-50"); + } else if (m_signal <= 75) { + return QStringLiteral("network-wireless-connected-75"); + } else { + return QStringLiteral("network-wireless-connected-100"); + } + + break; + default: + break; + } + + if (m_connectionState == NetworkManager::ActiveConnection::Activated) { + return QStringLiteral("network-wired-activated"); + } else { + return QStringLiteral("network-wired"); + } +} + +NetworkModelItem::ItemType NetworkModelItem::itemType() const +{ + if (!m_devicePath.isEmpty() || + m_type == NetworkManager::ConnectionSettings::Bond || + m_type == NetworkManager::ConnectionSettings::Bridge || + m_type == NetworkManager::ConnectionSettings::Vlan || + m_type == NetworkManager::ConnectionSettings::Team || + ((NetworkManager::status() == NetworkManager::Connected || + NetworkManager::status() == NetworkManager::ConnectedLinkLocal || + NetworkManager::status() == NetworkManager::ConnectedSiteOnly) && (m_type == NetworkManager::ConnectionSettings::Vpn || m_type == NetworkManager::ConnectionSettings::WireGuard))) { + if (m_connectionPath.isEmpty() && m_type == NetworkManager::ConnectionSettings::Wireless) { + return NetworkModelItem::AvailableAccessPoint; + } else { + return NetworkModelItem::AvailableConnection; + } + } + return NetworkModelItem::UnavailableConnection; +} + +NetworkManager::WirelessSetting::NetworkMode NetworkModelItem::mode() const +{ + return m_mode; +} + +void NetworkModelItem::setMode(const NetworkManager::WirelessSetting::NetworkMode mode) +{ + if (m_mode != mode) { + m_mode = mode; + refreshIcon(); + } +} + +QString NetworkModelItem::name() const +{ + return m_name; +} + +void NetworkModelItem::setName(const QString &name) +{ + if (m_name != name) { + m_name = name; + m_changedRoles << NetworkModel::ItemUniqueNameRole << NetworkModel::NameRole; + } +} + +QString NetworkModelItem::originalName() const +{ + if (m_deviceName.isEmpty()) { + return m_name; + } + + return QLatin1String("%1 (%2)").arg(m_name).arg(m_deviceName); + + // return m_name % QLatin1String(" (") % m_deviceName % ')'; +} + +QString NetworkModelItem::sectionType() const +{ + if (m_connectionState == NetworkManager::ActiveConnection::Deactivated) { + return "Available connections"; + } else { + return QString(); + } +} + +NetworkManager::WirelessSecurityType NetworkModelItem::securityType() const +{ + return m_securityType; +} + +void NetworkModelItem::setSecurityType(NetworkManager::WirelessSecurityType type) +{ + if (m_securityType != type) { + m_securityType = type; + m_changedRoles << NetworkModel::SecurityTypeStringRole << NetworkModel::SecurityTypeRole; + refreshIcon(); + } +} + +int NetworkModelItem::signal() const +{ + return m_signal; +} + +void NetworkModelItem::setSignal(int signal) +{ + if (m_signal != signal) { + m_signal = signal; + m_changedRoles << NetworkModel::SignalRole; + refreshIcon(); + } +} + +bool NetworkModelItem::slave() const +{ + return m_slave; +} + +void NetworkModelItem::setSlave(bool slave) +{ + if (m_slave != slave) { + m_slave = slave; + m_changedRoles << NetworkModel::SlaveRole; + } +} + +QString NetworkModelItem::specificPath() const +{ + return m_specificPath; +} + +void NetworkModelItem::setSpecificPath(const QString &path) +{ + if (m_specificPath != path) { + m_specificPath = path; + m_changedRoles << NetworkModel::SpecificPathRole; + } +} + +QString NetworkModelItem::ssid() const +{ + return m_ssid; +} + +void NetworkModelItem::setSsid(const QString &ssid) +{ + if (m_ssid != ssid) { + m_ssid = ssid; + m_changedRoles << NetworkModel::SsidRole << NetworkModel::UniRole; + } +} + +NetworkManager::ConnectionSettings::ConnectionType NetworkModelItem::type() const +{ + return m_type; +} + +QDateTime NetworkModelItem::timestamp() const +{ + return m_timestamp; +} + +void NetworkModelItem::setTimestamp(const QDateTime &date) +{ + if (m_timestamp != date) { + m_timestamp = date; + m_changedRoles << NetworkModel::TimeStampRole; + } +} + +void NetworkModelItem::setType(NetworkManager::ConnectionSettings::ConnectionType type) +{ + if (m_type != type) { + m_type = type; + m_changedRoles << NetworkModel::TypeRole << NetworkModel::ItemTypeRole << NetworkModel::UniRole; + + refreshIcon(); + } +} + +QString NetworkModelItem::uni() const +{ + if (m_type == NetworkManager::ConnectionSettings::Wireless && m_uuid.isEmpty()) { + return m_ssid + '%' + m_devicePath; + } else { + return m_connectionPath + '%' + m_devicePath; + } +} + +QString NetworkModelItem::uuid() const +{ + return m_uuid; +} + +void NetworkModelItem::setUuid(const QString &uuid) +{ + if (m_uuid != uuid) { + m_uuid = uuid; + m_changedRoles << NetworkModel::UuidRole; + } +} + +QString NetworkModelItem::vpnState() const +{ + return UiUtils::vpnConnectionStateToString(m_vpnState); +} + +void NetworkModelItem::setVpnState(NetworkManager::VpnConnection::State state) +{ + if (m_vpnState != state) { + m_vpnState = state; + m_changedRoles << NetworkModel::VpnState; + } +} + +QString NetworkModelItem::vpnType() const +{ + return m_vpnType; +} + +void NetworkModelItem::setVpnType(const QString &type) +{ + if (m_vpnType != type) { + m_vpnType = type; + m_changedRoles << NetworkModel::VpnType; + } +} + +qulonglong NetworkModelItem::rxBytes() const +{ + return m_rxBytes; +} + +void NetworkModelItem::setRxBytes(qulonglong bytes) +{ + if (m_rxBytes != bytes) { + m_rxBytes = bytes; + m_changedRoles << NetworkModel::RxBytesRole; + } +} + +qulonglong NetworkModelItem::txBytes() const +{ + return m_txBytes; +} + +void NetworkModelItem::setTxBytes(qulonglong bytes) +{ + if (m_txBytes != bytes) { + m_txBytes = bytes; + m_changedRoles << NetworkModel::TxBytesRole; + } +} + +bool NetworkModelItem::operator==(const NetworkModelItem *item) const +{ + if (!item->uuid().isEmpty() && !uuid().isEmpty()) { + if (item->devicePath() == devicePath() && item->uuid() == uuid()) { + return true; + } + } else if (item->type() == NetworkManager::ConnectionSettings::Wireless && type() == NetworkManager::ConnectionSettings::Wireless) { + if (item->ssid() == ssid() && item->devicePath() == devicePath()) { + return true; + } + } + + return false; +} + +void NetworkModelItem::invalidateDetails() +{ + m_detailsValid = false; + m_changedRoles << NetworkModel::ConnectionDetailsRole; +} + +void NetworkModelItem::updateDetails() const +{ + m_detailsValid = true; + m_details.clear(); + + if (itemType() == NetworkModelItem::UnavailableConnection) { + return; + } + + NetworkManager::Device::Ptr device = NetworkManager::findNetworkInterface(m_devicePath); + + // Get IPv[46]Address and related nameservers + IPv4 default gateway + if (device && device->ipV4Config().isValid() && m_connectionState == NetworkManager::ActiveConnection::Activated) { + if (!device->ipV4Config().addresses().isEmpty()) { + QHostAddress addr = device->ipV4Config().addresses().first().ip(); + if (!addr.isNull()) { + m_details << tr("IPv4 Address") << addr.toString(); + } + } + if (!device->ipV4Config().gateway().isEmpty()) { + QString addr = device->ipV4Config().gateway(); + if (!addr.isNull()) { + m_details << tr("IPv4 Default Gateway") << addr; + } + } + if (!device->ipV4Config().nameservers().isEmpty()) { + QHostAddress addr = device->ipV4Config().nameservers().first(); + if (!addr.isNull()) { + m_details << tr("IPv4 Nameserver") << addr.toString(); + } + } + } + + if (device && device->ipV6Config().isValid() && m_connectionState == NetworkManager::ActiveConnection::Activated) { + if (!device->ipV6Config().addresses().isEmpty()) { + QHostAddress addr = device->ipV6Config().addresses().first().ip(); + if (!addr.isNull()) { + m_details << tr("IPv6 Address") << addr.toString(); + } + } + if (!device->ipV6Config().nameservers().isEmpty()) { + QHostAddress addr = device->ipV6Config().nameservers().first(); + if (!addr.isNull()) { + m_details << tr("IPv6 Nameserver") << addr.toString(); + } + } + } + if (m_type == NetworkManager::ConnectionSettings::Wired) { + NetworkManager::WiredDevice::Ptr wiredDevice = device.objectCast(); + if (wiredDevice) { + if (m_connectionState == NetworkManager::ActiveConnection::Activated) { + m_details << tr("Connection speed") << UiUtils::connectionSpeed(wiredDevice->bitRate()); + } + m_details << tr("MAC Address") << wiredDevice->permanentHardwareAddress(); + } + } else if (m_type == NetworkManager::ConnectionSettings::Wireless) { + NetworkManager::WirelessDevice::Ptr wirelessDevice = device.objectCast(); + m_details << tr("Access point (SSID)") << m_ssid; + if (m_mode == NetworkManager::WirelessSetting::Infrastructure) { + m_details << tr("Signal strength") << QStringLiteral("%1%").arg(m_signal); + } + m_details << tr("Security type") << UiUtils::labelFromWirelessSecurity(m_securityType); + if (wirelessDevice) { + if (m_connectionState == NetworkManager::ActiveConnection::Activated) { + m_details << tr("Connection speed") << UiUtils::connectionSpeed(wirelessDevice->bitRate()); + } + m_details << tr("MAC Address") << wirelessDevice->permanentHardwareAddress(); + } + } else if (m_type == NetworkManager::ConnectionSettings::Gsm || m_type == NetworkManager::ConnectionSettings::Cdma) { +#if WITH_MODEMMANAGER_SUPPORT + NetworkManager::ModemDevice::Ptr modemDevice = device.objectCast(); + if (modemDevice) { + ModemManager::ModemDevice::Ptr modem = ModemManager::findModemDevice(modemDevice->udi()); + if (modem) { + ModemManager::Modem::Ptr modemNetwork = modem->interface(ModemManager::ModemDevice::ModemInterface).objectCast(); + + if (m_type == NetworkManager::ConnectionSettings::Gsm) { + ModemManager::Modem3gpp::Ptr gsmNet = modem->interface(ModemManager::ModemDevice::GsmInterface).objectCast(); + if (gsmNet) { + m_details << tr("Operator") << gsmNet->operatorName(); + } + } else { + ModemManager::ModemCdma::Ptr cdmaNet = modem->interface(ModemManager::ModemDevice::CdmaInterface).objectCast(); + m_details << tr("Network ID") << QString("%1").arg(cdmaNet->nid()); + } + + if (modemNetwork) { + m_details << tr("Signal Quality") << QString("%1%").arg(modemNetwork->signalQuality().signal); + m_details << tr("Access Technology") << UiUtils::convertAccessTechnologyToString(modemNetwork->accessTechnologies()); + } + } + } +#endif + } else if (m_type == NetworkManager::ConnectionSettings::Vpn) { + m_details << tr("VPN plugin") << m_vpnType; + + if (m_connectionState == NetworkManager::ActiveConnection::Activated) { + NetworkManager::ActiveConnection::Ptr active = NetworkManager::findActiveConnection(m_activeConnectionPath); + NetworkManager::VpnConnection::Ptr vpnConnection; + + if (active) { + vpnConnection = NetworkManager::VpnConnection::Ptr(new NetworkManager::VpnConnection(active->path()), &QObject::deleteLater); + } + + if (vpnConnection && !vpnConnection->banner().isEmpty()) { + m_details << tr("Banner") << vpnConnection->banner().simplified(); + } + } + } else if (m_type == NetworkManager::ConnectionSettings::Bluetooth) { + NetworkManager::BluetoothDevice::Ptr bluetoothDevice = device.objectCast(); + if (bluetoothDevice) { + m_details << tr("Name") << bluetoothDevice->name(); + if (bluetoothDevice->bluetoothCapabilities() == NetworkManager::BluetoothDevice::Pan) { + m_details << tr("Capabilities") << QStringLiteral("PAN"); + } else if (bluetoothDevice->bluetoothCapabilities() == NetworkManager::BluetoothDevice::Dun) { + m_details << tr("Capabilities") << QStringLiteral("DUN"); + } + m_details << tr("MAC Address") << bluetoothDevice->hardwareAddress(); + + } + } else if (m_type == NetworkManager::ConnectionSettings::Infiniband) { + NetworkManager::InfinibandDevice::Ptr infinibandDevice = device.objectCast(); + m_details << tr("Type") << tr("Infiniband"); + if (infinibandDevice) { + m_details << tr("MAC Address") << infinibandDevice->hwAddress(); + } + } else if (m_type == NetworkManager::ConnectionSettings::Bond) { + NetworkManager::BondDevice::Ptr bondDevice = device.objectCast(); + m_details << tr("Type") << tr("Bond"); + if (bondDevice) { + m_details << tr("MAC Address") << bondDevice->hwAddress(); + } + } else if (m_type == NetworkManager::ConnectionSettings::Bridge) { + NetworkManager::BridgeDevice::Ptr bridgeDevice = device.objectCast(); + m_details << tr("Type") << tr("Bridge"); + if (bridgeDevice) { + m_details << tr("MAC Address") << bridgeDevice->hwAddress(); + } + } else if (m_type == NetworkManager::ConnectionSettings::Vlan) { + NetworkManager::VlanDevice::Ptr vlanDevice = device.objectCast(); + m_details << tr("Type") << tr("Vlan"); + if (vlanDevice) { + m_details << tr("Vlan ID") << QString("%1").arg(vlanDevice->vlanId()); + m_details << tr("MAC Address") << vlanDevice->hwAddress(); + } + } else if (m_type == NetworkManager::ConnectionSettings::Adsl) { + m_details << tr("Type") << tr("Adsl"); + } + else if (m_type == NetworkManager::ConnectionSettings::Team) { + NetworkManager::TeamDevice::Ptr teamDevice = device.objectCast(); + m_details << tr("Type") << tr("Team"); + if (teamDevice) { + m_details << tr("MAC Address") << teamDevice->hwAddress(); + } + } + + if (device && m_connectionState == NetworkManager::ActiveConnection::Activated) { + m_details << tr("Device") << device->interfaceName(); + } +} diff --git a/networkmanagement/networkmodelitem.h b/networkmanagement/networkmodelitem.h new file mode 100644 index 0000000..d9431aa --- /dev/null +++ b/networkmanagement/networkmodelitem.h @@ -0,0 +1,160 @@ +/* + Copyright 2013-2018 Jan Grulich + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), which shall + act as a proxy defined in Section 6 of version 3 of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef NETWORKMODELITEM_H +#define NETWORKMODELITEM_H + +#include + +#include +#include +#include +#include +#include + +#include "networkmodel.h" + +class NETWORKMANAGER_EXPORT NetworkModelItem : public QObject +{ + Q_OBJECT + +public: + enum ItemType { UnavailableConnection, AvailableConnection, AvailableAccessPoint }; + + explicit NetworkModelItem(QObject *parent = nullptr); + explicit NetworkModelItem(const NetworkModelItem *item, QObject *parent = nullptr); + ~NetworkModelItem() override; + + QString activeConnectionPath() const; + void setActiveConnectionPath(const QString &path); + + QString connectionPath() const; + void setConnectionPath(const QString &path); + + NetworkManager::ActiveConnection::State connectionState() const; + void setConnectionState(NetworkManager::ActiveConnection::State state); + + QStringList details() const; + + QString deviceName() const; + void setDeviceName(const QString &name); + + QString devicePath() const; + void setDevicePath(const QString &path); + + QString deviceState() const; + void setDeviceState(const NetworkManager::Device::State state); + + bool duplicate() const; + + void setIcon(const QString &icon); + QString icon() const { return m_icon; } + + ItemType itemType() const; + + NetworkManager::WirelessSetting::NetworkMode mode() const; + void setMode(const NetworkManager::WirelessSetting::NetworkMode mode); + + QString name() const; + void setName(const QString &name); + + QString originalName() const; + + QString sectionType() const; + + NetworkManager::WirelessSecurityType securityType() const; + void setSecurityType(NetworkManager::WirelessSecurityType type); + + int signal() const; + void setSignal(int signal); + + bool slave() const; + void setSlave(bool slave); + + QString specificPath() const; + void setSpecificPath(const QString &path); + + QString ssid() const; + void setSsid(const QString &ssid); + + QDateTime timestamp() const; + void setTimestamp(const QDateTime &date); + + NetworkManager::ConnectionSettings::ConnectionType type() const; + void setType(NetworkManager::ConnectionSettings::ConnectionType type); + + QString uni() const; + + QString uuid() const; + void setUuid(const QString &uuid); + + QString vpnState() const; + void setVpnState(NetworkManager::VpnConnection::State state); + + QString vpnType() const; + void setVpnType(const QString &type); + + qulonglong rxBytes() const; + void setRxBytes(qulonglong bytes); + + qulonglong txBytes() const; + void setTxBytes(qulonglong bytes); + + bool operator==(const NetworkModelItem *item) const; + + QVector changedRoles() const { return m_changedRoles; } + void clearChangedRoles() { m_changedRoles.clear(); } + +public Q_SLOTS: + void invalidateDetails(); + +private: + QString computeIcon() const; + void refreshIcon(); + void updateDetails() const; + + QString m_activeConnectionPath; + QString m_connectionPath; + NetworkManager::ActiveConnection::State m_connectionState; + QString m_devicePath; + QString m_deviceName; + NetworkManager::Device::State m_deviceState; + mutable QStringList m_details; + mutable bool m_detailsValid; + bool m_duplicate; + NetworkManager::WirelessSetting::NetworkMode m_mode; + QString m_name; + NetworkManager::WirelessSecurityType m_securityType; + int m_signal; + bool m_slave; + QString m_specificPath; + QString m_ssid; + QDateTime m_timestamp; + NetworkManager::ConnectionSettings::ConnectionType m_type; + QString m_uuid; + QString m_vpnType; + NetworkManager::VpnConnection::State m_vpnState; + qulonglong m_rxBytes; + qulonglong m_txBytes; + QString m_icon; + QVector m_changedRoles; +}; + +#endif // NETWORKMODELITEM_H diff --git a/networkmanagement/qmldir b/networkmanagement/qmldir new file mode 100644 index 0000000..706fddd --- /dev/null +++ b/networkmanagement/qmldir @@ -0,0 +1,3 @@ +module Cutefish.NetworkManagement + +plugin cutefishnm_qmlplugins \ No newline at end of file diff --git a/networkmanagement/qmlplugins.cpp b/networkmanagement/qmlplugins.cpp new file mode 100644 index 0000000..df2c124 --- /dev/null +++ b/networkmanagement/qmlplugins.cpp @@ -0,0 +1,24 @@ +#include "qmlplugins.h" +#include "networking.h" +#include "networkmodel.h" +#include "networkmodelitem.h" +#include "appletproxymodel.h" +#include "technologyproxymodel.h" +#include "wiressitemsettings.h" +#include "connectionicon.h" +#include "network.h" + +#include + +void QmlPlugins::registerTypes(const char* uri) +{ + qmlRegisterUncreatableType(uri, 1, 0, "NetworkModelItem", + QLatin1String("Cannot instantiate NetworkModelItem")); + qmlRegisterType(uri, 1, 0, "AppletProxyModel"); + qmlRegisterType(uri, 1, 0, "Networking"); + qmlRegisterType(uri, 1, 0, "NetworkModel"); + qmlRegisterType(uri, 1, 0, "TechnologyProxyModel"); + qmlRegisterType(uri, 1, 0, "WirelessItemSettings"); + qmlRegisterType(uri, 1, 0, "ConnectionIcon"); + qmlRegisterType(uri, 1, 0, "Network"); +} \ No newline at end of file diff --git a/networkmanagement/qmlplugins.h b/networkmanagement/qmlplugins.h new file mode 100644 index 0000000..2c63956 --- /dev/null +++ b/networkmanagement/qmlplugins.h @@ -0,0 +1,15 @@ +#ifndef QMLPLUGINS_H +#define QMLPLUGINS_H + +#include + +class QmlPlugins : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char * uri) override; +}; + +#endif // QMLPLUGINS_H diff --git a/networkmanagement/technologyproxymodel.cpp b/networkmanagement/technologyproxymodel.cpp new file mode 100644 index 0000000..e98bab7 --- /dev/null +++ b/networkmanagement/technologyproxymodel.cpp @@ -0,0 +1,144 @@ + +/**************************************************************************** + * + * Copyright (C) 2017 Pier Luigi Fiorini + * + * $BEGIN_LICENSE:LGPLv3+$ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * $END_LICENSE$ + ***************************************************************************/ + +#include "networkmodel.h" +#include "networkmodelitem.h" +#include "technologyproxymodel.h" + +static NetworkManager::ConnectionSettings::ConnectionType convertType(TechnologyProxyModel::Type type) +{ + switch (type) { + case TechnologyProxyModel::UnknownType: + return NetworkManager::ConnectionSettings::ConnectionType::Unknown; + case TechnologyProxyModel::AdslType: + return NetworkManager::ConnectionSettings::ConnectionType::Adsl; + case TechnologyProxyModel::BluetoothType: + return NetworkManager::ConnectionSettings::ConnectionType::Bluetooth; + case TechnologyProxyModel::CdmaType: + return NetworkManager::ConnectionSettings::ConnectionType::Cdma; + case TechnologyProxyModel::GsmType: + return NetworkManager::ConnectionSettings::ConnectionType::Gsm; + case TechnologyProxyModel::OLPCMeshType: + return NetworkManager::ConnectionSettings::ConnectionType::OLPCMesh; + case TechnologyProxyModel::PppoeType: + return NetworkManager::ConnectionSettings::ConnectionType::Pppoe; + case TechnologyProxyModel::VpnType: + return NetworkManager::ConnectionSettings::ConnectionType::Vpn; + case TechnologyProxyModel::WimaxType: + return NetworkManager::ConnectionSettings::ConnectionType::Wimax; + case TechnologyProxyModel::WiredType: + return NetworkManager::ConnectionSettings::ConnectionType::Wired; + case TechnologyProxyModel::WirelessType: + return NetworkManager::ConnectionSettings::ConnectionType::Wireless; + default: + break; + } + + return NetworkManager::ConnectionSettings::ConnectionType::Unknown; +} + +TechnologyProxyModel::TechnologyProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + setDynamicSortFilter(true); + setSortCaseSensitivity(Qt::CaseInsensitive); + setSortLocaleAware(true); + sort(0, Qt::DescendingOrder); +} + +TechnologyProxyModel::Type TechnologyProxyModel::type() const +{ + return m_type; +} + +void TechnologyProxyModel::setType(Type type) +{ + if (m_type == type) + return; + m_type = type; + Q_EMIT typeChanged(); + + if (type == UnknownType) + setFilterRole(0); + else + setFilterRole(NetworkModel::TypeRole); +} + +bool TechnologyProxyModel::showInactiveConnections() const +{ + return m_showInactiveConnections; +} + +void TechnologyProxyModel::setShowInactiveConnections(bool value) +{ + if (m_showInactiveConnections == value) + return; + + m_showInactiveConnections = value; + Q_EMIT showInactiveConnectionsChanged(); +} + +bool TechnologyProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); + + // Filter out slaves and duplicates + const bool isSlave = sourceModel()->data(index, NetworkModel::SlaveRole).toBool(); + const bool isDuplicate = sourceModel()->data(index, NetworkModel::DuplicateRole).toBool(); + if (isSlave || isDuplicate) + return false; + + // Connection and item type + const NetworkManager::ConnectionSettings::ConnectionType type = + static_cast(sourceModel()->data(index, NetworkModel::TypeRole).toUInt()); + const NetworkModelItem::ItemType itemType = + static_cast(sourceModel()->data(index, NetworkModel::ItemTypeRole).toUInt()); + + // Filter-out certain connection types we are not interested in + if (m_type != UnknownType) { + if (type != convertType(m_type)) + return false; + } + + // Filter-out access points + if (itemType != NetworkModelItem::AvailableConnection && + itemType != NetworkModelItem::AvailableAccessPoint) + return false; + + // Filter by state + const NetworkManager::ActiveConnection::State state = + static_cast(sourceModel()->data(index, NetworkModel::ConnectionStateRole).toUInt()); + if (!m_showInactiveConnections && state == NetworkManager::ActiveConnection::Deactivated) + return false; + + // Filter on connection name + const QString pattern = filterRegExp().pattern(); + if (!pattern.isEmpty()) { + QString data = sourceModel()->data(index, Qt::DisplayRole).toString(); + if (data.isEmpty()) + data = sourceModel()->data(index, NetworkModel::NameRole).toString(); + return data.contains(pattern, Qt::CaseInsensitive); + } + + return true; +} diff --git a/networkmanagement/technologyproxymodel.h b/networkmanagement/technologyproxymodel.h new file mode 100644 index 0000000..26abcbe --- /dev/null +++ b/networkmanagement/technologyproxymodel.h @@ -0,0 +1,75 @@ +/**************************************************************************** + * + * Copyright (C) 2017 Pier Luigi Fiorini + * + * $BEGIN_LICENSE:LGPLv3+$ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * $END_LICENSE$ + ***************************************************************************/ + +#ifndef TECHNOLOGYPROXYMODEL_H +#define TECHNOLOGYPROXYMODEL_H + +#include + +#include + +#include + +class NETWORKMANAGER_EXPORT TechnologyProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(Type type READ type WRITE setType NOTIFY typeChanged) + Q_PROPERTY(bool showInactiveConnections READ showInactiveConnections WRITE setShowInactiveConnections NOTIFY showInactiveConnectionsChanged) + Q_PROPERTY(QAbstractItemModel *sourceModel READ sourceModel WRITE setSourceModel) + +public: + enum Type { + UnknownType = 0, + AdslType, + BluetoothType, + CdmaType, + GsmType, + OLPCMeshType, + PppoeType, + VpnType, + WimaxType, + WiredType, + WirelessType + }; + Q_ENUM(Type) + + TechnologyProxyModel(QObject *parent = nullptr); + + Type type() const; + void setType(Type type); + + bool showInactiveConnections() const; + void setShowInactiveConnections(bool value); + +Q_SIGNALS: + void typeChanged(); + void showInactiveConnectionsChanged(); + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + +private: + Type m_type = UnknownType; + bool m_showInactiveConnections = false; +}; + +#endif //TECHNOLOGYPROXYMODEL_H diff --git a/networkmanagement/uiutils.cpp b/networkmanagement/uiutils.cpp new file mode 100644 index 0000000..b744617 --- /dev/null +++ b/networkmanagement/uiutils.cpp @@ -0,0 +1,803 @@ +/* + Copyright 2008-2010 Sebastian Kügler + Copyright 2013-2014 Jan Grulich + Copyright 2015-2016 Pier Luigi Fiorini + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License or (at your option) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 of the license. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include + +#if WITH_MODEMMANAGER_SUPPORT +#include +#include +#include +#include +#include +#endif + +#include +#include +#include + +#include "networkmodel.h" +#include "uiutils.h" + +using namespace NetworkManager; + +UiUtils::SortedConnectionType UiUtils::connectionTypeToSortedType(NetworkManager::ConnectionSettings::ConnectionType type) +{ + switch (type) { + case NetworkManager::ConnectionSettings::Unknown: + return UiUtils::Unknown; + break; + case NetworkManager::ConnectionSettings::Adsl: + return UiUtils::Adsl; + break; + case NetworkManager::ConnectionSettings::Bluetooth: + return UiUtils::Bluetooth; + break; + case NetworkManager::ConnectionSettings::Bond: + return UiUtils::Bond; + break; + case NetworkManager::ConnectionSettings::Bridge: + return UiUtils::Bridge; + break; + case NetworkManager::ConnectionSettings::Cdma: + return UiUtils::Cdma; + break; + case NetworkManager::ConnectionSettings::Gsm: + return UiUtils::Gsm; + break; + case NetworkManager::ConnectionSettings::Infiniband: + return UiUtils::Infiniband; + break; + case NetworkManager::ConnectionSettings::OLPCMesh: + return UiUtils::OLPCMesh; + break; + case NetworkManager::ConnectionSettings::Pppoe: + return UiUtils::Pppoe; + break; + case NetworkManager::ConnectionSettings::Team: + return UiUtils::Team; + break; + case NetworkManager::ConnectionSettings::Vlan: + return UiUtils::Vlan; + break; + case NetworkManager::ConnectionSettings::Vpn: + return UiUtils::Vpn; + break; + case NetworkManager::ConnectionSettings::Wired: + return UiUtils::Wired; + break; + case NetworkManager::ConnectionSettings::Wireless: + return UiUtils::Wireless; + break; + default: + return UiUtils::Unknown; + break; + } +} + +bool UiUtils::isConnectionTypeSupported(NetworkManager::ConnectionSettings::ConnectionType type) +{ + if (type == NetworkManager::ConnectionSettings::Generic || type == NetworkManager::ConnectionSettings::Tun) { + return false; + } + + // TODO: Implement this setting + //bool manageVirtualConnections = Configuration::manageVirtualConnections(); + bool manageVirtualConnections = false; + + if (type == NetworkManager::ConnectionSettings::Bond || + type == NetworkManager::ConnectionSettings::Bridge || + type == NetworkManager::ConnectionSettings::Infiniband || + type == NetworkManager::ConnectionSettings::Team || + type == NetworkManager::ConnectionSettings::Vlan) { + return manageVirtualConnections; + } + + return true; +} + +bool UiUtils::isConnectionTypeVirtual(NetworkManager::ConnectionSettings::ConnectionType type) +{ + if (type == NetworkManager::ConnectionSettings::Bond || + type == NetworkManager::ConnectionSettings::Bridge || + type == NetworkManager::ConnectionSettings::Infiniband || + type == NetworkManager::ConnectionSettings::Team || + type == NetworkManager::ConnectionSettings::Vlan) { + return true; + } + + return false; +} + +QString UiUtils::interfaceTypeLabel(const NetworkManager::Device::Type type, const NetworkManager::Device::Ptr iface) +{ + QString deviceText; + switch (type) { + case NetworkManager::Device::Wifi: + /*: title of the interface widget in nm's popup */ + deviceText = QObject::tr("Wi-Fi"); + break; + case NetworkManager::Device::Bluetooth: + /*: title of the interface widget in nm's popup */ + deviceText = QObject::tr("Bluetooth"); + break; + case NetworkManager::Device::Wimax: + /*: title of the interface widget in nm's popup */ + deviceText = QObject::tr("WiMAX"); + break; + case NetworkManager::Device::InfiniBand: + /*: title of the interface widget in nm's popup */ + deviceText = QObject::tr("Infiniband"); + break; + case NetworkManager::Device::Adsl: + /*: title of the interface widget in nm's popup */ + deviceText = QObject::tr("ADSL"); + break; + case NetworkManager::Device::Bond: + /*: title of the interface widget in nm's popup */ + deviceText = QObject::tr("Virtual (bond)"); + break; + case NetworkManager::Device::Bridge: + /*: title of the interface widget in nm's popup */ + deviceText = QObject::tr("Virtual (bridge)"); + break; + case NetworkManager::Device::Vlan: + /*: title of the interface widget in nm's popup */ + deviceText = QObject::tr("Virtual (vlan)"); + break; +#if NM_CHECK_VERSION(0, 9, 10) + case NetworkManager::Device::Team: + /*: title of the interface widget in nm's popup */ + deviceText = QObject::tr("Virtual (team)"); + break; +#endif + case NetworkManager::Device::Modem: { + const NetworkManager::ModemDevice::Ptr nmModemIface = iface.objectCast(); + if (nmModemIface) { + switch(modemSubType(nmModemIface->currentCapabilities())) { + case NetworkManager::ModemDevice::Pots: + /*: title of the interface widget in nm's popup */ + deviceText = QObject::tr("Serial Modem"); + break; + case NetworkManager::ModemDevice::GsmUmts: + case NetworkManager::ModemDevice::CdmaEvdo: + case NetworkManager::ModemDevice::Lte: + /*: title of the interface widget in nm's popup */ + deviceText = QObject::tr("Mobile Broadband"); + break; + case NetworkManager::ModemDevice::NoCapability: + qCWarning(gLcNm) << "Unhandled modem sub type: NetworkManager::ModemDevice::NoCapability"; + break; + default: + break; + } + } + } + break; + case NetworkManager::Device::Ethernet: + default: + /*: title of the interface widget in nm's popup */ + deviceText = QObject::tr("Wired Ethernet"); + break; + } + return deviceText; +} + +QString UiUtils::iconAndTitleForConnectionSettingsType(NetworkManager::ConnectionSettings::ConnectionType type, QString &title) +{ + QString text; + QString icon = QLatin1String("action/settings_ethernet"); + switch (type) { + case ConnectionSettings::Adsl: + text = QObject::tr("ADSL"); + icon = QLatin1String("device/network_cell"); + break; + case ConnectionSettings::Pppoe: + text = QObject::tr("DSL"); + icon = QLatin1String("device/network_cell"); + break; + case ConnectionSettings::Bluetooth: + text = QObject::tr("Bluetooth"); + icon = QLatin1String("device/bluetooth"); + break; + case ConnectionSettings::Bond: + text = QObject::tr("Bond"); + break; + case ConnectionSettings::Bridge: + text = QObject::tr("Bridge"); + break; + case ConnectionSettings::Gsm: + case ConnectionSettings::Cdma: + text = QObject::tr("Mobile broadband"); + icon = QLatin1String("device/network_cell"); + break; + case ConnectionSettings::Infiniband: + text = QObject::tr("Infiniband"); + break; + case ConnectionSettings::OLPCMesh: + text = QObject::tr("Olpc mesh"); + break; + case ConnectionSettings::Vlan: + text = QObject::tr("VLAN"); + break; + case ConnectionSettings::Vpn: + text = QObject::tr("VPN"); + icon = QLatin1String("communication/vpn_key"); + break; + case ConnectionSettings::Wimax: + text = QObject::tr("WiMAX"); + icon = QLatin1String("device/network_wifi"); + break; + case ConnectionSettings::Wired: + text = QObject::tr("Wired Ethernet"); + break; + case ConnectionSettings::Wireless: + text = QObject::tr("Wi-Fi"); + icon = QLatin1String("device/network_wifi"); + break; +#if NM_CHECK_VERSION(0, 9, 10) + case ConnectionSettings::Team: + text = QObject::tr("Team"); + break; +#endif + default: + text = QObject::tr("Unknown connection type"); + break; + } + title = text; + return icon; +} + +QString UiUtils::prettyInterfaceName(NetworkManager::Device::Type type, const QString &interfaceName) +{ + QString ret; + switch (type) { + case NetworkManager::Device::Wifi: + ret = QObject::tr("Wireless Interface (%1)").arg(interfaceName); + break; + case NetworkManager::Device::Ethernet: + ret = QObject::tr("Wired Interface (%1)").arg(interfaceName); + break; + case NetworkManager::Device::Bluetooth: + ret = QObject::tr("Bluetooth (%1)").arg(interfaceName); + break; + case NetworkManager::Device::Modem: + ret = QObject::tr("Modem (%1)").arg(interfaceName); + break; + case NetworkManager::Device::Adsl: + ret = QObject::tr("ADSL (%1)").arg(interfaceName); + break; + case NetworkManager::Device::Vlan: + ret = QObject::tr("VLan (%1)").arg(interfaceName); + break; + case NetworkManager::Device::Bridge: + ret = QObject::tr("Bridge (%1)").arg(interfaceName); + break; + default: + ret = interfaceName; + } + return ret; +} + +QString UiUtils::connectionStateToString(NetworkManager::Device::State state, const QString &connectionName) +{ + QString stateString; + switch (state) { + case NetworkManager::Device::UnknownState: + /*: description of unknown network interface state */ + stateString = QObject::tr("Unknown"); + break; + case NetworkManager::Device::Unmanaged: + /*: description of unmanaged network interface state */ + stateString = QObject::tr("Unmanaged"); + break; + case NetworkManager::Device::Unavailable: + /*: description of unavailable network interface state */ + stateString = QObject::tr("Unavailable"); + break; + case NetworkManager::Device::Disconnected: + /*: description of unconnected network interface state */ + stateString = QObject::tr("Not connected"); + break; + case NetworkManager::Device::Preparing: + /*: description of preparing to connect network interface state */ + stateString = QObject::tr("Preparing to connect"); + break; + case NetworkManager::Device::ConfiguringHardware: + /*: description of configuring hardware network interface state */ + stateString = QObject::tr("Configuring interface"); + break; + case NetworkManager::Device::NeedAuth: + /*: description of waiting for authentication network interface state */ + stateString = QObject::tr("Waiting for authorization"); + break; + case NetworkManager::Device::ConfiguringIp: + /*: network interface doing dhcp request in most cases */ + stateString = QObject::tr("Setting network address"); + break; + case NetworkManager::Device::CheckingIp: + /*: is other action required to fully connect? captive portals, etc. */ + stateString = QObject::tr("Checking further connectivity"); + break; + case NetworkManager::Device::WaitingForSecondaries: + /*: a secondary connection (e.g. VPN) has to be activated first to continue */ + stateString = QObject::tr("Waiting for a secondary connection"); + break; + case NetworkManager::Device::Activated: + if (connectionName.isEmpty()) { + /*: network interface connected state label */ + stateString = QObject::tr("Connected"); + } else { + /*: network interface connected state label */ + stateString = QObject::tr("Connected to %1").arg(connectionName); + } + break; + case NetworkManager::Device::Deactivating: + /*: network interface disconnecting state label */ + stateString = QObject::tr("Deactivating connection"); + break; + case NetworkManager::Device::Failed: + /*: description of unknown network interface state */ + stateString = QObject::tr("Connection Failed"); + break; + default: + /*: interface state */ + stateString = QObject::tr("Error: Invalid state"); + } + return stateString; +} + +QString UiUtils::vpnConnectionStateToString(VpnConnection::State state) +{ + QString stateString; + switch (state) { + case VpnConnection::Unknown: + /*: The state of the VPN connection is unknown */ + stateString = QObject::tr("Unknown"); + break; + case VpnConnection::Prepare: + /*: The VPN connection is preparing to connect */ + stateString = QObject::tr("Preparing to connect"); + break; + case VpnConnection::NeedAuth: + /*: The VPN connection needs authorization credentials */ + stateString = QObject::tr("Needs authorization"); + break; + case VpnConnection::Connecting: + /*: The VPN connection is being established */ + stateString = QObject::tr("Connecting"); + break; + case VpnConnection::GettingIpConfig: + /*: The VPN connection is getting an IP address */ + stateString = QObject::tr("Setting network address"); + break; + case VpnConnection::Activated: + /*: The VPN connection is active */ + stateString = QObject::tr("Activated"); + break; + case VpnConnection::Failed: + /*: The VPN connection failed */ + stateString = QObject::tr("Failed"); + break; + case VpnConnection::Disconnected: + /*: The VPN connection is disconnected */ + stateString = QObject::tr("Failed"); + break; + default: + /*: interface state */ + stateString = QObject::tr("interface state", "Error: Invalid state"); + } + return stateString; +} + +QString UiUtils::operationModeToString(NetworkManager::WirelessDevice::OperationMode mode) +{ + QString modeString; + switch (mode) { + case NetworkManager::WirelessDevice::WirelessDevice::Unknown: + /*: wireless network operation mode */ + modeString = QObject::tr("Unknown"); + break; + case NetworkManager::WirelessDevice::Adhoc: + /*: wireless network operation mode */ + modeString = QObject::tr("Adhoc"); + break; + case NetworkManager::WirelessDevice::WirelessDevice::Infra: + /*: wireless network operation mode */ + modeString = QObject::tr("Infrastructure"); + break; + case NetworkManager::WirelessDevice::WirelessDevice::ApMode: + /*: wireless network operation mode */ + modeString = QObject::tr("Access point"); + break; + default: + modeString = QObject::tr("INCORRECT MODE FIX ME"); + } + return modeString; +} + +QStringList UiUtils::wpaFlagsToStringList(NetworkManager::AccessPoint::WpaFlags flags) +{ + /* for testing purposes + flags = NetworkManager::AccessPoint::PairWep40 + | NetworkManager::AccessPoint::PairWep104 + | NetworkManager::AccessPoint::PairTkip + | NetworkManager::AccessPoint::PairCcmp + | NetworkManager::AccessPoint::GroupWep40 + | NetworkManager::AccessPoint::GroupWep104 + | NetworkManager::AccessPoint::GroupTkip + | NetworkManager::AccessPoint::GroupCcmp + | NetworkManager::AccessPoint::KeyMgmtPsk + | NetworkManager::AccessPoint::KeyMgmt8021x; */ + + QStringList flagList; + + if (flags.testFlag(NetworkManager::AccessPoint::PairWep40)) + /*: wireless network cipher */ + flagList.append(QObject::tr("Pairwise WEP40")); + if (flags.testFlag(NetworkManager::AccessPoint::PairWep104)) + /*: wireless network cipher */ + flagList.append(QObject::tr("Pairwise WEP104")); + if (flags.testFlag(NetworkManager::AccessPoint::PairTkip)) + /*: wireless network cipher */ + flagList.append(QObject::tr("Pairwise TKIP")); + if (flags.testFlag(NetworkManager::AccessPoint::PairCcmp)) + /*: wireless network cipher */ + flagList.append(QObject::tr("Pairwise CCMP")); + if (flags.testFlag(NetworkManager::AccessPoint::GroupWep40)) + /*: wireless network cipher */ + flagList.append(QObject::tr("Group WEP40")); + if (flags.testFlag(NetworkManager::AccessPoint::GroupWep104)) + /*: wireless network cipher */ + flagList.append(QObject::tr("Group WEP104")); + if (flags.testFlag(NetworkManager::AccessPoint::GroupTkip)) + /*: wireless network cipher */ + flagList.append(QObject::tr("Group TKIP")); + if (flags.testFlag(NetworkManager::AccessPoint::GroupCcmp)) + /*: wireless network cipher */ + flagList.append(QObject::tr("Group CCMP")); + if (flags.testFlag(NetworkManager::AccessPoint::KeyMgmtPsk)) + /*: wireless network cipher */ + flagList.append(QObject::tr("PSK")); + if (flags.testFlag(NetworkManager::AccessPoint::KeyMgmt8021x)) + /*: wireless network cipher */ + flagList.append(QObject::tr("802.1x")); + + return flagList; +} + + +QString UiUtils::connectionSpeed(double bitrate) +{ + QString out; + if (bitrate < 1000) { + /*: connection speed */ + out = QObject::tr("%1 Bit/s").arg(bitrate); + } else if (bitrate < 1000000) { + /*: connection speed */ + out = QObject::tr("%1 MBit/s").arg(bitrate/1000); + } else { + /*: connection speed */ + out = QObject::tr("%1 GBit/s").arg(bitrate/1000000); + } + return out; +} + +QString UiUtils::wirelessBandToString(NetworkManager::WirelessSetting::FrequencyBand band) +{ + switch (band) { + case NetworkManager::WirelessSetting::Automatic: + return QLatin1String("automatic"); + break; + case NetworkManager::WirelessSetting::A: + return QLatin1String("a"); + break; + case NetworkManager::WirelessSetting::Bg: + return QLatin1String("b/g"); + break; + default: + break; + } + + return QString(); +} + +#if WITH_MODEMMANAGER_SUPPORT +QString UiUtils::convertAllowedModeToString(ModemManager::Modem::ModemModes modes) +{ + if (modes.testFlag(MM_MODEM_MODE_4G)) { + /*: Gsm modes (2G/3G/any) */ + return QObject::tr("LTE"); + } else if (modes.testFlag(MM_MODEM_MODE_3G)) { + /*: Gsm modes (2G/3G/any) */ + return QObject::tr("UMTS/HSxPA"); + } else if (modes.testFlag(MM_MODEM_MODE_2G)) { + /*: Gsm modes (2G/3G/any) */ + return QObject::tr("GPRS/EDGE"); + } else if (modes.testFlag(MM_MODEM_MODE_CS)) { + /*: Gsm modes (2G/3G/any) */ + return QObject::tr("GSM"); + } else if (modes.testFlag(MM_MODEM_MODE_ANY)) { + /*: Gsm modes (2G/3G/any) */ + return QObject::tr("Any"); + } + + /*: Gsm modes (2G/3G/any) */ + return QObject::tr("Any"); +} + +QString UiUtils::convertAccessTechnologyToString(ModemManager::Modem::AccessTechnologies tech) +{ + if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_LTE)) { + /*: Cellular access technology */ + return QObject::tr("LTE"); + } else if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_EVDOB)) { + /*: Cellular access technology */ + return QObject::tr("CDMA2000 EVDO revision B"); + } else if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_EVDOA)) { + /*: Cellular access technology */ + return QObject::tr("CDMA2000 EVDO revision A"); + } else if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_EVDO0)) { + /*: Cellular access technology */ + return QObject::tr("CDMA2000 EVDO revision 0"); + } else if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_1XRTT)) { + /*: Cellular access technology */ + return QObject::tr("CDMA2000 1xRTT"); + } else if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS)) { + /*: Cellular access technology */ + return QObject::tr("HSPA+"); + } else if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_HSPA)) { + /*: Cellular access technology */ + return QObject::tr("HSPA"); + } else if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_HSUPA)) { + /*: Cellular access technology */ + return QObject::tr("HSUPA"); + } else if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_HSDPA)) { + /*: Cellular access technology */ + return QObject::tr("HSDPA"); + } else if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_UMTS)) { + /*: Cellular access technology */ + return QObject::tr("UMTS"); + } else if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_EDGE)) { + /*: Cellular access technology */ + return QObject::tr("EDGE"); + } else if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_GPRS)) { + /*: Cellular access technology */ + return QObject::tr("GPRS"); + } else if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT)) { + /*: Cellular access technology */ + return QObject::tr("Compact GSM"); + } else if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_GSM)) { + /*: Cellular access technology */ + return QObject::tr("GSM"); + } else if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_POTS)) { + /*: Analog wireline modem */ + return QObject::tr("Analog"); + } else if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN)) { + /*: Unknown cellular access technology */ + return QObject::tr("Unknown"); + } else if (tech.testFlag(MM_MODEM_ACCESS_TECHNOLOGY_ANY)) { + /*: Any cellular access technology */ + return QObject::tr("Any"); + } + + /*: Unknown cellular access technology */ + return QObject::tr("Unknown"); +} + +QString UiUtils::convertLockReasonToString(MMModemLock reason) +{ + switch (reason) { + case MM_MODEM_LOCK_NONE: + /*: possible SIM lock reason */ + return QObject::tr("Modem is unlocked."); + case MM_MODEM_LOCK_SIM_PIN: + /*: possible SIM lock reason */ + return QObject::tr("SIM requires the PIN code."); + case MM_MODEM_LOCK_SIM_PIN2: + /*: possible SIM lock reason */ + return QObject::tr("SIM requires the PIN2 code."); + case MM_MODEM_LOCK_SIM_PUK: + /*: possible SIM lock reason */ + return QObject::tr("SIM requires the PUK code."); + case MM_MODEM_LOCK_SIM_PUK2: + /*: possible SIM lock reason */ + return QObject::tr("SIM requires the PUK2 code."); + case MM_MODEM_LOCK_PH_SP_PIN: + /*: possible SIM lock reason */ + return QObject::tr("Modem requires the service provider PIN code."); + case MM_MODEM_LOCK_PH_SP_PUK: + /*: possible SIM lock reason */ + return QObject::tr("Modem requires the service provider PUK code."); + case MM_MODEM_LOCK_PH_NET_PIN: + /*: possible SIM lock reason */ + return QObject::tr("Modem requires the network PIN code."); + case MM_MODEM_LOCK_PH_NET_PUK: + /*: possible SIM lock reason */ + return QObject::tr("Modem requires the network PUK code."); + case MM_MODEM_LOCK_PH_SIM_PIN: + /*: possible SIM lock reason */ + return QObject::tr("Modem requires the PIN code."); + case MM_MODEM_LOCK_PH_CORP_PIN: + /*: possible SIM lock reason */ + return QObject::tr("Modem requires the corporate PIN code."); + case MM_MODEM_LOCK_PH_CORP_PUK: + /*: possible SIM lock reason */ + return QObject::tr("Modem requires the corporate PUK code."); + case MM_MODEM_LOCK_PH_FSIM_PIN: + /*: possible SIM lock reason */ + return QObject::tr("Modem requires the PH-FSIM PIN code."); + case MM_MODEM_LOCK_PH_FSIM_PUK: + /*: possible SIM lock reason */ + return QObject::tr("Modem requires the PH-FSIM PUK code."); + case MM_MODEM_LOCK_PH_NETSUB_PIN: + /*: possible SIM lock reason */ + return QObject::tr("Modem requires the network subset PIN code."); + case MM_MODEM_LOCK_PH_NETSUB_PUK: + /*: possible SIM lock reason */ + return QObject::tr("Modem requires the network subset PUK code."); + case MM_MODEM_LOCK_UNKNOWN: + default: + /*: possible SIM lock reason */ + return QObject::tr("Lock reason unknown."); + } +} +#endif + +#if !NM_CHECK_VERSION(1, 2, 0) +QString UiUtils::convertNspTypeToString(WimaxNsp::NetworkType type) +{ + switch (type) { + /*: unknown Wimax NSP type */ + case WimaxNsp::Unknown: return QObject::tr("Unknown"); + case WimaxNsp::Home: return QObject::tr("Home"); + case WimaxNsp::Partner: return QObject::tr("Partner"); + case WimaxNsp::RoamingPartner: return QObject::tr("Roaming partner"); + default: break; + } + + /*: Unknown */ + return QObject::tr("Unknown Wimax NSP type"); +} +#endif + +NetworkManager::ModemDevice::Capability UiUtils::modemSubType(NetworkManager::ModemDevice::Capabilities modemCaps) +{ + if (modemCaps & NetworkManager::ModemDevice::Lte) { + return NetworkManager::ModemDevice::Lte; + } else if (modemCaps & NetworkManager::ModemDevice::CdmaEvdo) { + return NetworkManager::ModemDevice::CdmaEvdo; + } else if (modemCaps & NetworkManager::ModemDevice::GsmUmts) { + return NetworkManager::ModemDevice::GsmUmts; + } else if (modemCaps & NetworkManager::ModemDevice::Pots) { + return NetworkManager::ModemDevice::Pots; + } + return NetworkManager::ModemDevice::NoCapability; +} + +QString UiUtils::labelFromWirelessSecurity(NetworkManager::WirelessSecurityType type) +{ + QString tip; + switch (type) { + case NetworkManager::NoneSecurity: + /*: @label no security */ + tip = QObject::tr("Insecure"); + break; + case NetworkManager::StaticWep: + /*: @label WEP security */ + tip = QObject::tr("WEP"); + break; + case NetworkManager::Leap: + /*: @label LEAP security */ + tip = QObject::tr("LEAP"); + break; + case NetworkManager::DynamicWep: + /*: @label Dynamic WEP security */ + tip = QObject::tr("Dynamic WEP"); + break; + case NetworkManager::WpaPsk: + /*: @label WPA-PSK security */ + tip = QObject::tr("WPA-PSK"); + break; + case NetworkManager::WpaEap: + /*: @label WPA-EAP security */ + tip = QObject::tr("WPA-EAP"); + break; + case NetworkManager::Wpa2Psk: + /*: @label WPA2-PSK security */ + tip = QObject::tr("WPA2-PSK"); + break; + case NetworkManager::Wpa2Eap: + /*: @label WPA2-EAP security */ + tip = QObject::tr("WPA2-EAP"); + break; + default: + /*: @label unknown security */ + // tip = QObject::tr("Unknown security type"); + tip = QObject::tr("Unknown"); + break; + } + return tip; +} + +QString UiUtils::formatDateRelative(const QDateTime & lastUsed) +{ + QString lastUsedText; + if (lastUsed.isValid()) { + const QDateTime now = QDateTime::currentDateTime(); + if (lastUsed.daysTo(now) == 0 ) { + const int secondsAgo = lastUsed.secsTo(now); + if (secondsAgo < (60 * 60 )) { + const int minutesAgo = secondsAgo / 60; + /*: Label for last used time for a network connection used in the last hour, as the number of minutes since usage */ + lastUsedText = QObject::tr("%n minute(s) ago", "", minutesAgo); + } else { + const int hoursAgo = secondsAgo / (60 * 60); + /*: Label for last used time for a network connection used in the last day, as the number of hours since usage */ + lastUsedText = QObject::tr("%n hour(s) ago", "", hoursAgo); + } + } else if (lastUsed.daysTo(now) == 1) { + /*: Label for last used time for a network connection used the previous day */ + lastUsedText = QObject::tr("Yesterday"); + } else { + lastUsedText = QLocale().toString(lastUsed.date(), QLocale::ShortFormat); + } + } else { + /*: Label for last used time for a network connection that has never been used */ + lastUsedText = QObject::tr("Never"); + } + return lastUsedText; +} + +QString UiUtils::formatLastUsedDateRelative(const QDateTime & lastUsed) +{ + QString lastUsedText; + if (lastUsed.isValid()) { + const QDateTime now = QDateTime::currentDateTime(); + if (lastUsed.daysTo(now) == 0 ) { + const int secondsAgo = lastUsed.secsTo(now); + if (secondsAgo < (60 * 60 )) { + const int minutesAgo = secondsAgo / 60; + /*: Label for last used time for a network connection used in the last hour, as the number of minutes since usage */ + lastUsedText = QObject::tr("Last used %n minute(s) ago", "", minutesAgo); + } else { + const int hoursAgo = secondsAgo / (60 * 60); + /*: Label for last used time for a network connection used in the last day, as the number of hours since usage */ + lastUsedText = QObject::tr("Last used %n hour(s) ago", "", hoursAgo); + } + } else if (lastUsed.daysTo(now) == 1) { + /*: Label for last used time for a network connection used the previous day */ + lastUsedText = QObject::tr("Last used yesterday"); + } else { + lastUsedText = QObject::tr("Last used on %1").arg(QLocale().toString(lastUsed.date(), QLocale::ShortFormat)); + } + } else { + /*: Label for last used time for a network connection that has never been used */ + lastUsedText = QObject::tr("Never used"); + } + return lastUsedText; +} diff --git a/networkmanagement/uiutils.h b/networkmanagement/uiutils.h new file mode 100644 index 0000000..c6e1a82 --- /dev/null +++ b/networkmanagement/uiutils.h @@ -0,0 +1,145 @@ +/* + Copyright 2008-2010 Sebastian Kügler + Copyright 2013-2014 Jan Grulich + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License or (at your option) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 of the license. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef UIUTILS_H +#define UIUTILS_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if WITH_MODEMMANAGER_SUPPORT +#include +#include +#endif + +class NETWORKMANAGER_EXPORT UiUtils +{ +public: + enum SortedConnectionType { + Wired, + Wireless, + Gsm, + Cdma, + Pppoe, + Adsl, + Infiniband, + OLPCMesh, + Bluetooth, + Vpn, + Vlan, + Bridge, + Bond, + Team, + Unknown + }; + + /* + * @return sorted connection type used to prioritize specific connection types + */ + static SortedConnectionType connectionTypeToSortedType(NetworkManager::ConnectionSettings::ConnectionType type); + + /* + * @return whether given connection type is supported + * Currently ignored connection types: Bond, Bridge, Generic, Infiniband, Team, Vlan, Tun + */ + static bool isConnectionTypeSupported(NetworkManager::ConnectionSettings::ConnectionType type); + + /** + * @return true if the connection is virtual. + * @param type Type of the network connection + */ + static bool isConnectionTypeVirtual(NetworkManager::ConnectionSettings::ConnectionType type); + + /** + * @return a human-readable description for the network interface type for use as label + * @param type the type of the network interface + */ + static QString interfaceTypeLabel(const NetworkManager::Device::Type type, const NetworkManager::Device::Ptr iface); + + /** + * @return a human-readable name for a given network interface according to the configured + * naming style + * @param type type of the network interface + * @param interfaceName name of the network interface (eg eth0) + */ + static QString prettyInterfaceName(NetworkManager::Device::Type type, const QString &interfaceName); + + /** + * @return a human-readable description of the connection state of a given network interface + * @param state The connection state + */ + static QString connectionStateToString(NetworkManager::Device::State state, const QString &connectionName = QString()); + + static QString vpnConnectionStateToString(NetworkManager::VpnConnection::State state); + + static QString iconAndTitleForConnectionSettingsType(NetworkManager::ConnectionSettings::ConnectionType type, + QString &title); + /** + * @return a human-readable description of operation mode. + * @param mode the operation mode + */ + static QString operationModeToString(NetworkManager::WirelessDevice::OperationMode mode); + + /** + * @return string list with a human-readable description of wpa flags. + * @param flags the wpa flags + */ + static QStringList wpaFlagsToStringList(NetworkManager::AccessPoint::WpaFlags flags); + + /** + * @return localized string showing a human-readable connection speed. 1000 is used as base. + * @param bitrate bitrate of the connection per second + */ + static QString connectionSpeed(double bitrate); + + /** + * @param band The band of a wireless network. The value corresponds to the type enum in Knm::WirelessSetting::EnumBand + * @return A string representation + */ + static QString wirelessBandToString(NetworkManager::WirelessSetting::FrequencyBand band); + +#if WITH_MODEMMANAGER_SUPPORT + static QString convertAllowedModeToString(ModemManager::Modem::ModemModes mode); + static QString convertAccessTechnologyToString(ModemManager::Modem::AccessTechnologies tech); + static QString convertLockReasonToString(MMModemLock reason); +#endif + static NetworkManager::ModemDevice::Capability modemSubType(NetworkManager::ModemDevice::Capabilities modemCaps); +#if !NM_CHECK_VERSION(1, 2, 0) + static QString convertNspTypeToString(NetworkManager::WimaxNsp::NetworkType type); +#endif + static QString labelFromWirelessSecurity(NetworkManager::WirelessSecurityType type); + + static QString formatDateRelative(const QDateTime & lastUsed); + static QString formatLastUsedDateRelative(const QDateTime & lastUsed); +}; +#endif // UIUTILS_H diff --git a/networkmanagement/wiressitemsettings.cpp b/networkmanagement/wiressitemsettings.cpp new file mode 100644 index 0000000..ada0b64 --- /dev/null +++ b/networkmanagement/wiressitemsettings.cpp @@ -0,0 +1,131 @@ +#include +#include +#include + +#include "wiressitemsettings.h" + +#include +#include +#include + +WirelessItemSettings::WirelessItemSettings(QObject *parent) + : QObject(parent) +{ +} + +QString WirelessItemSettings::path() const +{ + return m_path; +} + +void WirelessItemSettings::setPath(const QString &path) +{ + if (m_path == path) + return; + + m_path = path; + + m_connection = NetworkManager::findConnection(m_path); + if (m_connection) { + m_settings = m_connection->settings(); + Q_EMIT nameChanged(); + Q_EMIT autoConnectChanged(); + Q_EMIT availableToOtherUsersChanged(); + Q_EMIT settingsAvailable(); + } else { + m_settings.reset(); + } + + Q_EMIT pathChanged(); +} + +QString WirelessItemSettings::name() const +{ + if (m_settings) + return m_settings->id(); + else + qWarning("NetworkManager settings are invalid"); + + return QString(); +} + +void WirelessItemSettings::setName(const QString &name) +{ + if (m_settings) { + if (this->name() == name) + return; + m_settings->setId(name); + m_connection->update(m_settings->toMap()); + Q_EMIT nameChanged(); + } else { + qWarning("NetworkManager settings are invalid"); + } +} + +bool WirelessItemSettings::autoConnect() const +{ + if (m_settings) + return m_settings->autoconnect(); + else + qWarning("NetworkManager settings are invalid"); + return false; +} + +void WirelessItemSettings::setAutoConnect(bool value) +{ + if (m_settings) { + if (this->autoConnect() == value) + return; + m_settings->setAutoconnect(value); + m_connection->update(m_settings->toMap()); + Q_EMIT autoConnectChanged(); + } else { + qWarning("NetworkManager settings are invalid"); + } +} + +bool WirelessItemSettings::availableToOtherUsers() const +{ + if (m_settings) + return m_settings->permissions().isEmpty(); + else + qWarning("NetworkManager settings are invalid"); + return false; +} + +void WirelessItemSettings::setAvailableToOtherUsers(bool value) +{ + if (m_settings) { + bool availableToOtherUsers = m_settings->permissions().isEmpty(); + if (availableToOtherUsers == value) + return; + + if (value) + m_settings->setPermissions(QHash()); + else + m_settings->addToPermissions(getCurrentUserName(), QString()); + m_connection->update(m_settings->toMap()); + Q_EMIT availableToOtherUsersChanged(); + } else { + qWarning("NetworkManager settings are invalid"); + } +} + +void WirelessItemSettings::updateSettings(const QString &type, const QVariantMap &map) +{ + if (!m_settings) + return; + + NMVariantMapMap settings = m_settings->toMap(); + settings.insert(type, map); + m_connection->update(settings); +} + +QString WirelessItemSettings::getCurrentUserName() const +{ + uid_t uid = ::geteuid(); + struct passwd *pw = ::getpwuid(uid); + if (pw) + return QString::fromLocal8Bit(pw->pw_name); + return QString(); +} diff --git a/networkmanagement/wiressitemsettings.h b/networkmanagement/wiressitemsettings.h new file mode 100644 index 0000000..42e6956 --- /dev/null +++ b/networkmanagement/wiressitemsettings.h @@ -0,0 +1,52 @@ +#ifndef WIRELESSITEMSETTINGS_H +#define WIRELESSITEMSETTINGS_H + +#include + +#include + +#include + +class NETWORKMANAGER_EXPORT WirelessItemSettings : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(bool autoConnect READ autoConnect WRITE setAutoConnect NOTIFY autoConnectChanged) + Q_PROPERTY(bool availableToOtherUsers READ availableToOtherUsers WRITE setAvailableToOtherUsers NOTIFY availableToOtherUsersChanged) + +public: + WirelessItemSettings(QObject *parent = nullptr); + + QString path() const; + void setPath(const QString &path); + + QString name() const; + void setName(const QString &name); + + bool autoConnect() const; + void setAutoConnect(bool value); + + bool availableToOtherUsers() const; + void setAvailableToOtherUsers(bool value); + +Q_SIGNALS: + void pathChanged(); + void settingsAvailable(); + void nameChanged(); + void autoConnectChanged(); + void availableToOtherUsersChanged(); + +protected: + NetworkManager::Connection::Ptr m_connection; + NetworkManager::ConnectionSettings::Ptr m_settings; + + void updateSettings(const QString &type, const QVariantMap &map); + +private: + QString m_path; + + QString getCurrentUserName() const; +}; + +#endif // WIRELESSITEMSETTINGS_H diff --git a/screen/CMakeLists.txt b/screen/CMakeLists.txt new file mode 100644 index 0000000..fee3c21 --- /dev/null +++ b/screen/CMakeLists.txt @@ -0,0 +1,31 @@ +set(SCREEN_SRCS + common/control.cpp + common/globals.cpp + common/orientation_sensor.cpp + common/utils.cpp + confighandler.cpp + outputmodel.cpp + plugin.cpp + screen.cpp +) + +find_package(KF5Screen REQUIRED) +find_package(KF5KIO REQUIRED) +find_package(Qt5 REQUIRED COMPONENTS Test Sensors) + +add_library(cutefishscreen_qmlplugins SHARED ${SCREEN_SRCS}) + +target_link_libraries (cutefishscreen_qmlplugins + Qt5::Core + Qt5::Quick + Qt5::Gui + Qt5::DBus + Qt5::Sensors + Qt5::Test + + KF5::Screen + KF5::KIOCore +) + +install(TARGETS cutefishscreen_qmlplugins DESTINATION ${INSTALL_QMLDIR}/Cutefish/Screen) +install(FILES qmldir DESTINATION ${INSTALL_QMLDIR}/Cutefish/Screen) \ No newline at end of file diff --git a/screen/common/control.cpp b/screen/common/control.cpp new file mode 100644 index 0000000..8275e4c --- /dev/null +++ b/screen/common/control.cpp @@ -0,0 +1,645 @@ +/******************************************************************** +Copyright 2019 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "control.h" +#include "globals.h" + +#include +#include +#include +#include +#include + +#include +#include + +QString Control::s_dirName = QStringLiteral("control/"); + +Control::Control(QObject *parent) + : QObject(parent) +{ +} + +void Control::activateWatcher() +{ + if (m_watcher) { + return; + } + m_watcher = new QFileSystemWatcher({filePath()}, this); + connect(m_watcher, &QFileSystemWatcher::fileChanged, this, [this]() { + readFile(); + Q_EMIT changed(); + }); +} + +QFileSystemWatcher* Control::watcher() const +{ + return m_watcher; +} + +bool Control::writeFile() +{ + const QString path = filePath(); + const auto infoMap = constInfo(); + + if (infoMap.isEmpty()) { + // Nothing to write. Default control. Remove file if it exists. + QFile::remove(path); + return true; + } + if (!QDir().mkpath(dirPath())) { + // TODO: error message + return false; + } + + // write updated data to file + QFile file(path); + if (!file.open(QIODevice::WriteOnly)) { + // TODO: logging category? +// qCWarning(KSCREEN_COMMON) << "Failed to open config control file for writing! " << file.errorString(); + return false; + } + file.write(QJsonDocument::fromVariant(infoMap).toJson()); +// qCDebug(KSCREEN_COMMON) << "Control saved on: " << file.fileName(); + return true; +} + +QString Control::dirPath() const +{ + return Globals::dirPath() % s_dirName; +} + +void Control::readFile() +{ + QFile file(filePath()); + if (file.open(QIODevice::ReadOnly)) { + // This might not be reached, bus this is ok. The control file will + // eventually be created on first write later on. + QJsonDocument parser; + m_info = parser.fromJson(file.readAll()).toVariant().toMap(); + } +} + +QString Control::filePathFromHash(const QString &hash) const +{ + return dirPath() % hash; +} + +QVariantMap& Control::info() +{ + return m_info; +} + +const QVariantMap& Control::constInfo() const +{ + return m_info; +} + +Control::OutputRetention Control::convertVariantToOutputRetention(QVariant variant) +{ + if (variant.canConvert()) { + const auto retention = variant.toInt(); + if (retention == (int)OutputRetention::Global) { + return OutputRetention::Global; + } + if (retention == (int)OutputRetention::Individual) { + return OutputRetention::Individual; + } + } + return OutputRetention::Undefined; +} + +ControlConfig::ControlConfig(KScreen::ConfigPtr config, QObject *parent) + : Control(parent) + , m_config(config) +{ +// qDebug() << "Looking for control file:" << config->connectedOutputsHash(); + readFile(); + + // TODO: use a file watcher in case of changes to the control file while + // object exists? + + // As global outputs are indexed by a hash of their edid, which is not unique, + // to be able to tell apart multiple identical outputs, these need special treatment + QStringList allIds; + const auto outputs = config->outputs(); + allIds.reserve(outputs.count()); + for (const KScreen::OutputPtr &output : outputs) { + const auto outputId = output->hashMd5(); + if (allIds.contains(outputId) && !m_duplicateOutputIds.contains(outputId)) { + m_duplicateOutputIds << outputId; + } + allIds << outputId; + } + + for (auto output : outputs) { + m_outputsControls << new ControlOutput(output, this); + } + + // TODO: this is same in Output::readInOutputs of the daemon. Combine? + + // TODO: connect to outputs added/removed signals and reevaluate duplicate ids + // in case of such a change while object exists? +} + +void ControlConfig::activateWatcher() +{ + if (watcher()) { + // Watcher was already activated. + return; + } + for (auto *output : m_outputsControls) { + output->activateWatcher(); + connect(output, &ControlOutput::changed, this, &ControlConfig::changed); + } +} + +QString ControlConfig::dirPath() const +{ + return Control::dirPath() % QStringLiteral("configs/"); +} + +QString ControlConfig::filePath() const +{ + if (!m_config) { + return QString(); + } + return filePathFromHash(m_config->connectedOutputsHash()); +} + +bool ControlConfig::writeFile() +{ + bool success = true; + for (auto *outputControl : m_outputsControls) { + if (getOutputRetention(outputControl->id(), outputControl->name()) + == OutputRetention::Individual) { + continue; + } + success &= outputControl->writeFile(); + } + return success && Control::writeFile(); +} + +bool ControlConfig::infoIsOutput(const QVariantMap &info, const QString &outputId, const QString &outputName) const +{ + const QString outputIdInfo = info[QStringLiteral("id")].toString(); + if (outputIdInfo.isEmpty()) { + return false; + } + if (outputId != outputIdInfo) { + return false; + } + + if (!outputName.isEmpty() && m_duplicateOutputIds.contains(outputId)) { + // We may have identical outputs connected, these will have the same id in the config + // in order to find the right one, also check the output's name (usually the connector) + const auto metadata = info[QStringLiteral("metadata")].toMap(); + const auto outputNameInfo = metadata[QStringLiteral("name")].toString(); + if (outputName != outputNameInfo) { + // was a duplicate id, but info not for this output + return false; + } + } + return true; +} + +Control::OutputRetention ControlConfig::getOutputRetention(const KScreen::OutputPtr &output) const +{ + return getOutputRetention(output->hashMd5(), output->name()); +} + +Control::OutputRetention ControlConfig::getOutputRetention(const QString &outputId, const QString &outputName) const +{ + const QVariantList outputsInfo = getOutputs(); + for (const auto &variantInfo : outputsInfo) { + const QVariantMap info = variantInfo.toMap(); + if (!infoIsOutput(info, outputId, outputName)) { + continue; + } + return convertVariantToOutputRetention(info[QStringLiteral("retention")]); + } + // info for output not found + return OutputRetention::Undefined; +} + +static QVariantMap metadata(const QString &outputName) +{ + QVariantMap metadata; + metadata[QStringLiteral("name")] = outputName; + return metadata; +} + +QVariantMap createOutputInfo(const QString &outputId, const QString &outputName) +{ + QVariantMap outputInfo; + outputInfo[QStringLiteral("id")] = outputId; + outputInfo[QStringLiteral("metadata")] = metadata(outputName); + return outputInfo; +} + +void ControlConfig::setOutputRetention(const KScreen::OutputPtr &output, OutputRetention value) +{ + setOutputRetention(output->hashMd5(), output->name(), value); +} + +void ControlConfig::setOutputRetention(const QString &outputId, const QString &outputName, OutputRetention value) +{ + QList::iterator it; + QVariantList outputsInfo = getOutputs(); + + for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) { + QVariantMap outputInfo = (*it).toMap(); + if (!infoIsOutput(outputInfo, outputId, outputName)) { + continue; + } + outputInfo[QStringLiteral("retention")] = (int)value; + *it = outputInfo; + setOutputs(outputsInfo); + return; + } + // no entry yet, create one + auto outputInfo = createOutputInfo(outputId, outputName); + outputInfo[QStringLiteral("retention")] = (int)value; + + outputsInfo << outputInfo; + setOutputs(outputsInfo); +} + +qreal ControlConfig::getScale(const KScreen::OutputPtr &output) const +{ + return getScale(output->hashMd5(), output->name()); +} + +qreal ControlConfig::getScale(const QString &outputId, const QString &outputName) const +{ + const auto retention = getOutputRetention(outputId, outputName); + if (retention == OutputRetention::Individual) { + const QVariantList outputsInfo = getOutputs(); + for (const auto &variantInfo : outputsInfo) { + const QVariantMap info = variantInfo.toMap(); + if (!infoIsOutput(info, outputId, outputName)) { + continue; + } + const auto val = info[QStringLiteral("scale")]; + return val.canConvert() ? val.toReal() : -1; + } + } + // Retention is global or info for output not in config control file. + if (auto *outputControl = getOutputControl(outputId, outputName)) { + return outputControl->getScale(); + } + + // Info for output not found. + return -1; + } + +void ControlConfig::setScale(const KScreen::OutputPtr &output, qreal value) +{ + setScale(output->hashMd5(), output->name(), value); +} + +// TODO: combine methods (templated functions) +void ControlConfig::setScale(const QString &outputId, const QString &outputName, qreal value) +{ + QList::iterator it; + QVariantList outputsInfo = getOutputs(); + + auto setOutputScale = [&outputId, &outputName, value, this]() { + if (auto *control = getOutputControl(outputId, outputName)) { + control->setScale(value); + } + + }; + + for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) { + QVariantMap outputInfo = (*it).toMap(); + if (!infoIsOutput(outputInfo, outputId, outputName)) { + continue; + } + outputInfo[QStringLiteral("scale")] = value; + *it = outputInfo; + setOutputs(outputsInfo); + setOutputScale(); + return; + } + // no entry yet, create one + auto outputInfo = createOutputInfo(outputId, outputName); + outputInfo[QStringLiteral("scale")] = value; + + outputsInfo << outputInfo; + setOutputs(outputsInfo); + setOutputScale(); +} + +bool ControlConfig::getAutoRotate(const KScreen::OutputPtr &output) const +{ + return getAutoRotate(output->hashMd5(), output->name()); +} + +bool ControlConfig::getAutoRotate(const QString &outputId, const QString &outputName) const +{ + const auto retention = getOutputRetention(outputId, outputName); + if (retention == OutputRetention::Individual) { + const QVariantList outputsInfo = getOutputs(); + for (const auto &variantInfo : outputsInfo) { + const QVariantMap info = variantInfo.toMap(); + if (!infoIsOutput(info, outputId, outputName)) { + continue; + } + const auto val = info[QStringLiteral("autorotate")]; + return !val.canConvert() || val.toBool(); + } + } + // Retention is global or info for output not in config control file. + if (auto *outputControl = getOutputControl(outputId, outputName)) { + return outputControl->getAutoRotate(); + } + + // Info for output not found. + return true; +} + +void ControlConfig::setAutoRotate(const KScreen::OutputPtr &output, bool value) +{ + setAutoRotate(output->hashMd5(), output->name(), value); +} + +// TODO: combine methods (templated functions) +void ControlConfig::setAutoRotate(const QString &outputId, const QString &outputName, bool value) +{ + QList::iterator it; + QVariantList outputsInfo = getOutputs(); + + auto setOutputAutoRotate = [&outputId, &outputName, value, this]() { + if (auto *control = getOutputControl(outputId, outputName)) { + control->setAutoRotate(value); + } + + }; + + for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) { + QVariantMap outputInfo = (*it).toMap(); + if (!infoIsOutput(outputInfo, outputId, outputName)) { + continue; + } + outputInfo[QStringLiteral("autorotate")] = value; + *it = outputInfo; + setOutputs(outputsInfo); + setOutputAutoRotate(); + return; + } + // no entry yet, create one + auto outputInfo = createOutputInfo(outputId, outputName); + outputInfo[QStringLiteral("autorotate")] = value; + + outputsInfo << outputInfo; + setOutputs(outputsInfo); + setOutputAutoRotate(); +} + +bool ControlConfig::getAutoRotateOnlyInTabletMode(const KScreen::OutputPtr &output) const +{ + return getAutoRotateOnlyInTabletMode(output->hashMd5(), output->name()); +} + +bool ControlConfig::getAutoRotateOnlyInTabletMode(const QString &outputId, + const QString &outputName) const +{ + const auto retention = getOutputRetention(outputId, outputName); + if (retention == OutputRetention::Individual) { + const QVariantList outputsInfo = getOutputs(); + for (const auto &variantInfo : outputsInfo) { + const QVariantMap info = variantInfo.toMap(); + if (!infoIsOutput(info, outputId, outputName)) { + continue; + } + const auto val = info[QStringLiteral("autorotate-tablet-only")]; + return !val.canConvert() || val.toBool(); + } + } + // Retention is global or info for output not in config control file. + if (auto *outputControl = getOutputControl(outputId, outputName)) { + return outputControl->getAutoRotateOnlyInTabletMode(); + } + + // Info for output not found. + return true; +} + +void ControlConfig::setAutoRotateOnlyInTabletMode(const KScreen::OutputPtr &output, bool value) +{ + setAutoRotateOnlyInTabletMode(output->hashMd5(), output->name(), value); +} + +// TODO: combine methods (templated functions) +void ControlConfig::setAutoRotateOnlyInTabletMode(const QString &outputId, + const QString &outputName, bool value) +{ + QList::iterator it; + QVariantList outputsInfo = getOutputs(); + + auto setOutputAutoRotateOnlyInTabletMode = [&outputId, &outputName, value, this]() { + if (auto *control = getOutputControl(outputId, outputName)) { + control->setAutoRotateOnlyInTabletMode(value); + } + }; + + for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) { + QVariantMap outputInfo = (*it).toMap(); + if (!infoIsOutput(outputInfo, outputId, outputName)) { + continue; + } + outputInfo[QStringLiteral("autorotate-tablet-only")] = value; + *it = outputInfo; + setOutputs(outputsInfo); + setOutputAutoRotateOnlyInTabletMode(); + return; + } + // no entry yet, create one + auto outputInfo = createOutputInfo(outputId, outputName); + outputInfo[QStringLiteral("autorotate-tablet-only")] = value; + + outputsInfo << outputInfo; + setOutputs(outputsInfo); + setOutputAutoRotateOnlyInTabletMode(); +} + + +KScreen::OutputPtr ControlConfig::getReplicationSource(const KScreen::OutputPtr &output) const +{ + return getReplicationSource(output->hashMd5(), output->name()); +} + +KScreen::OutputPtr ControlConfig::getReplicationSource(const QString &outputId, + const QString &outputName) const +{ + const QVariantList outputsInfo = getOutputs(); + for (const auto &variantInfo : outputsInfo) { + const QVariantMap info = variantInfo.toMap(); + if (!infoIsOutput(info, outputId, outputName)) { + continue; + } + const QString sourceHash = info[QStringLiteral("replicate-hash")].toString(); + const QString sourceName = info[QStringLiteral("replicate-name")].toString(); + + if (sourceHash.isEmpty() && sourceName.isEmpty()) { + // Common case when the replication source has been unset. + return nullptr; + } + + for (const auto &output : m_config->outputs()) { + if (output->hashMd5() == sourceHash && output->name() == sourceName) { + return output; + } + } + // No match. + return nullptr; + } + // Info for output not found. + return nullptr; +} + +void ControlConfig::setReplicationSource(const KScreen::OutputPtr &output, + const KScreen::OutputPtr &source) +{ + setReplicationSource(output->hashMd5(), output->name(), source); +} + +void ControlConfig::setReplicationSource(const QString &outputId, const QString &outputName, + const KScreen::OutputPtr &source) +{ + QList::iterator it; + QVariantList outputsInfo = getOutputs(); + const QString sourceHash = source ? source->hashMd5() : QStringLiteral(""); + const QString sourceName = source ? source->name() : QStringLiteral(""); + + for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) { + QVariantMap outputInfo = (*it).toMap(); + if (!infoIsOutput(outputInfo, outputId, outputName)) { + continue; + } + outputInfo[QStringLiteral("replicate-hash")] = sourceHash; + outputInfo[QStringLiteral("replicate-name")] = sourceName; + *it = outputInfo; + setOutputs(outputsInfo); + // TODO: shall we set this information also as new global value (like with auto-rotate)? + return; + } + // no entry yet, create one + auto outputInfo = createOutputInfo(outputId, outputName); + outputInfo[QStringLiteral("replicate-hash")] = sourceHash; + outputInfo[QStringLiteral("replicate-name")] = sourceName; + + outputsInfo << outputInfo; + setOutputs(outputsInfo); + // TODO: shall we set this information also as new global value (like with auto-rotate)? +} + +QVariantList ControlConfig::getOutputs() const +{ + return constInfo()[QStringLiteral("outputs")].toList(); +} + +void ControlConfig::setOutputs(QVariantList outputsInfo) +{ + auto &infoMap = info(); + infoMap[QStringLiteral("outputs")] = outputsInfo; +} +ControlOutput* ControlConfig::getOutputControl(const QString &outputId, + const QString &outputName) const +{ + for (auto *control : m_outputsControls) { + if (control->id() == outputId && control->name() == outputName) { + return control; + } + } + return nullptr; +} + +ControlOutput::ControlOutput(KScreen::OutputPtr output, QObject *parent) + : Control(parent) + , m_output(output) +{ + readFile(); +} + +QString ControlOutput::id() const +{ + return m_output->hashMd5(); +} + +QString ControlOutput::name() const +{ + return m_output->name(); +} + +QString ControlOutput::dirPath() const +{ + return Control::dirPath() % QStringLiteral("outputs/"); +} + +QString ControlOutput::filePath() const +{ + if (!m_output) { + return QString(); + } + return filePathFromHash(m_output->hashMd5()); +} + +qreal ControlOutput::getScale() const +{ + const auto val = constInfo()[QStringLiteral("scale")]; + return val.canConvert() ? val.toReal() : -1; +} + +void ControlOutput::setScale(qreal value) +{ + auto &infoMap = info(); + if (infoMap.isEmpty()) { + infoMap = createOutputInfo(m_output->hashMd5(), m_output->name()); + } + infoMap[QStringLiteral("scale")] = value; +} + +bool ControlOutput::getAutoRotate() const +{ + const auto val = constInfo()[QStringLiteral("autorotate")]; + return !val.canConvert() || val.toBool(); +} + +void ControlOutput::setAutoRotate(bool value) +{ + auto &infoMap = info(); + if (infoMap.isEmpty()) { + infoMap = createOutputInfo(m_output->hashMd5(), m_output->name()); + } + infoMap[QStringLiteral("autorotate")] = value; +} + +bool ControlOutput::getAutoRotateOnlyInTabletMode() const +{ + const auto val = constInfo()[QStringLiteral("autorotate-tablet-only")]; + return !val.canConvert() || val.toBool(); +} + +void ControlOutput::setAutoRotateOnlyInTabletMode(bool value) +{ + auto &infoMap = info(); + if (infoMap.isEmpty()) { + infoMap = createOutputInfo(m_output->hashMd5(), m_output->name()); + } + infoMap[QStringLiteral("autorotate-tablet-only")] = value; +} diff --git a/screen/common/control.h b/screen/common/control.h new file mode 100644 index 0000000..5cea1ec --- /dev/null +++ b/screen/common/control.h @@ -0,0 +1,147 @@ +/******************************************************************** +Copyright 2019 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef COMMON_CONTROL_H +#define COMMON_CONTROL_H + +#include + +#include +#include +#include + +class QFileSystemWatcher; + +class Control : public QObject +{ + Q_OBJECT +public: + enum class OutputRetention { + Undefined = -1, + Global = 0, + Individual = 1, + }; + Q_ENUM(OutputRetention) + + explicit Control(QObject *parent = nullptr); + + + ~Control() override = default; + + virtual bool writeFile(); + virtual void activateWatcher(); + +Q_SIGNALS: + void changed(); + +protected: + virtual QString dirPath() const; + virtual QString filePath() const = 0; + QString filePathFromHash(const QString &hash) const; + void readFile(); + QVariantMap& info(); + const QVariantMap& constInfo() const; + QFileSystemWatcher* watcher() const; + + static OutputRetention convertVariantToOutputRetention(QVariant variant); + +private: + static QString s_dirName; + QVariantMap m_info; + QFileSystemWatcher *m_watcher = nullptr; +}; + +class ControlOutput; + +class ControlConfig : public Control +{ + Q_OBJECT +public: + explicit ControlConfig(KScreen::ConfigPtr config, QObject *parent = nullptr); + + OutputRetention getOutputRetention(const KScreen::OutputPtr &output) const; + OutputRetention getOutputRetention(const QString &outputId, const QString &outputName) const; + void setOutputRetention(const KScreen::OutputPtr &output, OutputRetention value); + void setOutputRetention(const QString &outputId, const QString &outputName, OutputRetention value); + + qreal getScale(const KScreen::OutputPtr &output) const; + qreal getScale(const QString &outputId, const QString &outputName) const; + void setScale(const KScreen::OutputPtr &output, qreal value); + void setScale(const QString &outputId, const QString &outputName, qreal value); + + bool getAutoRotate(const KScreen::OutputPtr &output) const; + bool getAutoRotate(const QString &outputId, const QString &outputName) const; + void setAutoRotate(const KScreen::OutputPtr &output, bool value); + void setAutoRotate(const QString &outputId, const QString &outputName, bool value); + + bool getAutoRotateOnlyInTabletMode(const KScreen::OutputPtr &output) const; + bool getAutoRotateOnlyInTabletMode(const QString &outputId, const QString &outputName) const; + void setAutoRotateOnlyInTabletMode(const KScreen::OutputPtr &output, bool value); + void setAutoRotateOnlyInTabletMode(const QString &outputId, const QString &outputName, + bool value); + + KScreen::OutputPtr getReplicationSource(const KScreen::OutputPtr &output) const; + KScreen::OutputPtr getReplicationSource(const QString &outputId, + const QString &outputName) const; + void setReplicationSource(const KScreen::OutputPtr &output, const KScreen::OutputPtr &source); + void setReplicationSource(const QString &outputId, const QString &outputName, + const KScreen::OutputPtr &source); + + QString dirPath() const override; + QString filePath() const override; + + bool writeFile() override; + void activateWatcher() override; + +private: + QVariantList getOutputs() const; + void setOutputs(QVariantList outputsInfo); + bool infoIsOutput(const QVariantMap &info, const QString &outputId, const QString &outputName) const; + ControlOutput* getOutputControl(const QString &outputId, const QString &outputName) const; + + KScreen::ConfigPtr m_config; + QStringList m_duplicateOutputIds; + QVector m_outputsControls; +}; + +class ControlOutput : public Control +{ + Q_OBJECT +public: + explicit ControlOutput(KScreen::OutputPtr output, QObject *parent = nullptr); + + QString id() const; + QString name() const; + + // TODO: scale auto value + + qreal getScale() const; + void setScale(qreal value); + + bool getAutoRotate() const; + void setAutoRotate(bool value); + + bool getAutoRotateOnlyInTabletMode() const; + void setAutoRotateOnlyInTabletMode(bool value); + + QString dirPath() const override; + QString filePath() const override; + +private: + KScreen::OutputPtr m_output; +}; + +#endif diff --git a/screen/common/globals.cpp b/screen/common/globals.cpp new file mode 100644 index 0000000..6a3675f --- /dev/null +++ b/screen/common/globals.cpp @@ -0,0 +1,40 @@ +/******************************************************************** +Copyright 2018 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "globals.h" + +#include +#include + +namespace Globals +{ + +QString s_dirPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % + QStringLiteral("/kscreen/"); + +QString dirPath() { + return s_dirPath; +} + +void setDirPath(const QString &path) +{ + s_dirPath = path; + if (!s_dirPath.endsWith(QLatin1Char('/'))) { + s_dirPath += QLatin1Char('/'); + } +} + +} diff --git a/screen/common/globals.h b/screen/common/globals.h new file mode 100644 index 0000000..142598e --- /dev/null +++ b/screen/common/globals.h @@ -0,0 +1,28 @@ +/******************************************************************** +Copyright 2018 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef COMMON_GLOBALS_H +#define COMMON_GLOBALS_H + +#include + +namespace Globals +{ + void setDirPath(const QString &path); + QString dirPath(); +} + +#endif diff --git a/screen/common/orientation_sensor.cpp b/screen/common/orientation_sensor.cpp new file mode 100644 index 0000000..2615529 --- /dev/null +++ b/screen/common/orientation_sensor.cpp @@ -0,0 +1,78 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "orientation_sensor.h" + +#include + +OrientationSensor::OrientationSensor(QObject *parent) + : QObject(parent) + , m_sensor(new QOrientationSensor(this)) +{ + connect(m_sensor, &QOrientationSensor::activeChanged, this, &OrientationSensor::refresh); +} + +OrientationSensor::~OrientationSensor() = default; + +void OrientationSensor::updateState() +{ + const auto orientation = m_sensor->reading()->orientation(); + if (m_value != orientation) { + m_value = orientation; + Q_EMIT valueChanged(orientation); + } +} + +void OrientationSensor::refresh() +{ + if (m_sensor->isActive()) { + if (m_enabled) { + updateState(); + } + Q_EMIT availableChanged(true); + } else { + Q_EMIT availableChanged(false); + } +} + +QOrientationReading::Orientation OrientationSensor::value() const +{ + return m_value; +} + +bool OrientationSensor::available() const +{ + return m_sensor->connectToBackend(); +} + +bool OrientationSensor::enabled() const +{ + return m_sensor->isActive(); +} + +void OrientationSensor::setEnabled(bool enable) +{ + if (m_enabled == enable) { + return; + } + m_enabled = enable; + + if (enable) { + connect(m_sensor, &QOrientationSensor::readingChanged, this, &OrientationSensor::updateState); + m_sensor->start(); + } else { + disconnect(m_sensor, &QOrientationSensor::readingChanged, this, &OrientationSensor::updateState); + m_value = QOrientationReading::Undefined; + } + Q_EMIT enabledChanged(enable); +} \ No newline at end of file diff --git a/screen/common/orientation_sensor.h b/screen/common/orientation_sensor.h new file mode 100644 index 0000000..7ffdecf --- /dev/null +++ b/screen/common/orientation_sensor.h @@ -0,0 +1,44 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#pragma once + +#include +#include + +class OrientationSensor final : public QObject +{ + Q_OBJECT +public: + explicit OrientationSensor(QObject *parent = nullptr); + ~OrientationSensor() override final; + + QOrientationReading::Orientation value() const; + bool available() const; + bool enabled() const; + + void setEnabled(bool enable); + +Q_SIGNALS: + void valueChanged(QOrientationReading::Orientation orientation); + void availableChanged(bool available); + void enabledChanged(bool enabled); + +private: + void refresh(); + void updateState(); + + QOrientationSensor *m_sensor; + QOrientationReading::Orientation m_value = QOrientationReading::Undefined; + bool m_enabled = false; +}; \ No newline at end of file diff --git a/screen/common/utils.cpp b/screen/common/utils.cpp new file mode 100644 index 0000000..182c59a --- /dev/null +++ b/screen/common/utils.cpp @@ -0,0 +1,59 @@ +/* + * Copyright 2013 Daniel Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "utils.h" + +#include +#include + +QString Utils::outputName(const KScreen::OutputPtr& output) +{ + return outputName(output.data()); +} + +QString Utils::outputName(const KScreen::Output *output) +{ + if (output->type() == KScreen::Output::Panel) { + return QObject::tr("Laptop Screen"); + } + + if (output->edid()) { + // The name will be "VendorName ModelName (ConnectorName)", + // but some components may be empty. + QString name; + if (!(output->edid()->vendor().isEmpty())) { + name = output->edid()->vendor() + QLatin1Char(' '); + } + if (!output->edid()->name().isEmpty()) { + name += output->edid()->name() + QLatin1Char(' '); + } + if (!name.trimmed().isEmpty()) { + return name + QLatin1Char('(') + output->name() + QLatin1Char(')'); + } + } + return output->name(); +} + +QString Utils::sizeToString(const QSize &size) +{ + return QStringLiteral("%1x%2").arg(size.width()).arg(size.height()); +} + diff --git a/screen/common/utils.h b/screen/common/utils.h new file mode 100644 index 0000000..f59c832 --- /dev/null +++ b/screen/common/utils.h @@ -0,0 +1,40 @@ +/* + * Copyright 2013 Daniel Vrátil + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef KSCREEN_KCM_UTILS_H +#define KSCREEN_KCM_UTILS_H + +#include +#include + +#include +#include + +namespace Utils +{ + + QString outputName(const KScreen::Output *output); + QString outputName(const KScreen::OutputPtr &output); + + QString sizeToString(const QSize &size); +} + +#endif diff --git a/screen/confighandler.cpp b/screen/confighandler.cpp new file mode 100644 index 0000000..2a10648 --- /dev/null +++ b/screen/confighandler.cpp @@ -0,0 +1,339 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ + +#include "confighandler.h" + +#include "outputmodel.h" + +#include +#include +#include + +#include + +using namespace KScreen; + +ConfigHandler::ConfigHandler(QObject *parent) + : QObject(parent) +{ +} + +void ConfigHandler::setConfig(KScreen::ConfigPtr config) +{ + m_config = config; + m_initialConfig = m_config->clone(); + m_initialControl.reset(new ControlConfig(m_initialConfig)); + + KScreen::ConfigMonitor::instance()->addConfig(m_config); + m_control.reset(new ControlConfig(config)); + + m_outputs = new OutputModel(this); + connect(m_outputs, &OutputModel::positionChanged, + this, &ConfigHandler::checkScreenNormalization); + connect(m_outputs, &OutputModel::sizeChanged, + this, &ConfigHandler::checkScreenNormalization); + + for (const KScreen::OutputPtr &output : config->outputs()) { + initOutput(output); + } + m_lastNormalizedScreenSize = screenSize(); + + // TODO: put this into m_initialControl + m_initialRetention = getRetention(); + Q_EMIT retentionChanged(); + + connect(m_outputs, &OutputModel::changed, + this, [this]() { + checkNeedsSave(); + Q_EMIT changed(); + }); + connect(m_config.data(), &KScreen::Config::outputAdded, + this, [this]() { Q_EMIT outputConnect(true); }); + connect(m_config.data(), &KScreen::Config::outputRemoved, + this, [this]() { Q_EMIT outputConnect(false); }); + connect(m_config.data(), &KScreen::Config::primaryOutputChanged, + this, &ConfigHandler::primaryOutputChanged); + + Q_EMIT outputModelChanged(); +} + +void ConfigHandler::resetScale(const KScreen::OutputPtr &output) +{ + // Load scale control (either not set, same or windowing system does not transmit scale). + const qreal scale = m_control->getScale(output); + if (scale > 0) { + output->setScale(scale); + for (auto initialOutput : m_initialConfig->outputs()) { + if (initialOutput->id() == output->id()) { + initialOutput->setScale(scale); + break; + } + } + } +} + +void ConfigHandler::initOutput(const KScreen::OutputPtr &output) +{ + if (output->isConnected()) { + resetScale(output); + m_outputs->add(output); + } + connect(output.data(), &KScreen::Output::isConnectedChanged, + this, [this, output]() { + Q_EMIT outputConnect(output->isConnected()); + }); +} + +void ConfigHandler::updateInitialData() +{ + m_initialRetention = getRetention(); + connect(new GetConfigOperation(), &GetConfigOperation::finished, + this, [this](ConfigOperation *op) { + if (op->hasError()) { + return; + } + m_initialConfig = qobject_cast(op)->config(); + for (auto output : m_config->outputs()) { + resetScale(output); + } + m_initialControl.reset(new ControlConfig(m_initialConfig)); + checkNeedsSave(); + }); +} + +void ConfigHandler::checkNeedsSave() +{ + if (m_config->supportedFeatures() & + KScreen::Config::Feature::PrimaryDisplay) { + if (m_config->primaryOutput() && m_initialConfig->primaryOutput()) { + if (m_config->primaryOutput()->hashMd5() != + m_initialConfig->primaryOutput()->hashMd5() ) { + Q_EMIT needsSaveChecked(true); + return; + } + } else if ((bool)m_config->primaryOutput() != (bool)m_initialConfig->primaryOutput()) { + Q_EMIT needsSaveChecked(true); + return; + } + } + + if (m_initialRetention != getRetention()) { + Q_EMIT needsSaveChecked(true); + return; + } + + for (const auto &output : m_config->connectedOutputs()) { + const QString hash = output->hashMd5(); + for (const auto &initialOutput : m_initialConfig->outputs()) { + if (hash != initialOutput->hashMd5()) { + continue; + } + bool needsSave = false; + if (output->isEnabled() != initialOutput->isEnabled()) { + needsSave = true; + } + if (output->isEnabled()) { + needsSave |= output->currentModeId() != + initialOutput->currentModeId() + || output->pos() != initialOutput->pos() + || output->scale() != initialOutput->scale() + || output->rotation() != initialOutput->rotation() + || output->replicationSource() != initialOutput->replicationSource() + || autoRotate(output) != m_initialControl->getAutoRotate(output) + || autoRotateOnlyInTabletMode(output) + != m_initialControl->getAutoRotateOnlyInTabletMode(output); + } + if (needsSave) { + Q_EMIT needsSaveChecked(true); + return; + } + break; + } + } + Q_EMIT needsSaveChecked(false); +} + +QSize ConfigHandler::screenSize() const +{ + int width = 0, height = 0; + QSize size; + + for (const auto &output : m_config->connectedOutputs()) { + if (!output->isPositionable()) { + continue; + } + const int outputRight = output->geometry().right(); + const int outputBottom = output->geometry().bottom(); + + if (outputRight > width) { + width = outputRight; + } + if (outputBottom > height) { + height = outputBottom; + } + } + if (width > 0 && height > 0) { + size = QSize(width, height); + } else { + size = QSize(); + } + return size; +} + +QSize ConfigHandler::normalizeScreen() +{ + if (!m_config) { + return QSize(); + } + bool changed = m_outputs->normalizePositions(); + + const auto currentScreenSize = screenSize(); + changed |= m_lastNormalizedScreenSize != currentScreenSize; + m_lastNormalizedScreenSize = currentScreenSize; + + Q_EMIT screenNormalizationUpdate(true); + return currentScreenSize; +} + +void ConfigHandler::checkScreenNormalization() +{ + const bool normalized = !m_config || + (m_lastNormalizedScreenSize == screenSize() && + m_outputs->positionsNormalized()); + + Q_EMIT screenNormalizationUpdate(normalized); +} + +void ConfigHandler::primaryOutputSelected(int index) +{ + Q_UNUSED(index) + // TODO +} + +void ConfigHandler::primaryOutputChanged(const KScreen::OutputPtr &output) +{ + Q_UNUSED(output) + +} + +Control::OutputRetention ConfigHandler::getRetention() const +{ + using Retention = Control::OutputRetention; + + auto ret = Retention::Undefined; + if (!m_control) { + return ret; + } + const auto outputs = m_config->connectedOutputs(); + if (outputs.isEmpty()) { + return ret; + } + ret = m_control->getOutputRetention(outputs.first()); + + for (const auto &output : outputs) { + const auto outputRet = m_control->getOutputRetention(output); + if (ret != outputRet ) { + // Control file with different retention values per output. + return Retention::Undefined; + } + } + + if (ret == Retention::Undefined) { + // If all outputs have undefined retention, + // this should be displayed as global retention. + return Retention::Global; + } + return ret; +} + +int ConfigHandler::retention() const +{ + return static_cast(getRetention()); +} + +void ConfigHandler::setRetention(int retention) +{ + using Retention = Control::OutputRetention; + + if (!m_control) { + return; + } + if (retention != static_cast(Retention::Global) && + retention != static_cast(Retention::Individual)) { + // We only allow setting to global or individual retention. + return; + } + if (retention == ConfigHandler::retention()) { + return; + } + auto ret = static_cast(retention); + for (const auto &output : m_config->connectedOutputs()) { + m_control->setOutputRetention(output, ret); + } + checkNeedsSave(); + Q_EMIT retentionChanged(); + Q_EMIT changed(); +} + +qreal ConfigHandler::scale(const KScreen::OutputPtr &output) const +{ + return m_control->getScale(output); +} + +void ConfigHandler::setScale(KScreen::OutputPtr &output, qreal scale) +{ + m_control->setScale(output, scale); +} + +KScreen::OutputPtr ConfigHandler::replicationSource(const KScreen::OutputPtr &output) const +{ + return m_control->getReplicationSource(output); +} + +void ConfigHandler::setReplicationSource(KScreen::OutputPtr &output, + const KScreen::OutputPtr &source) +{ + m_control->setReplicationSource(output, source); +} + +bool ConfigHandler::autoRotate(const KScreen::OutputPtr &output) const +{ + return m_control->getAutoRotate(output); +} + +void ConfigHandler::setAutoRotate(KScreen::OutputPtr &output, bool autoRotate) +{ + m_control->setAutoRotate(output, autoRotate); +} + +bool ConfigHandler::autoRotateOnlyInTabletMode(const KScreen::OutputPtr &output) const +{ + return m_control->getAutoRotateOnlyInTabletMode(output); +} + +void ConfigHandler::setAutoRotateOnlyInTabletMode(KScreen::OutputPtr &output, bool value) +{ + m_control->setAutoRotateOnlyInTabletMode(output, value); +} + +void ConfigHandler::writeControl() +{ + if (!m_control) { + return; + } + m_control->writeFile(); +} diff --git a/screen/confighandler.h b/screen/confighandler.h new file mode 100644 index 0000000..834ddcd --- /dev/null +++ b/screen/confighandler.h @@ -0,0 +1,95 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#pragma once + +#include "./common/control.h" + +#include + +#include + +class OutputModel; + +class ConfigHandler : public QObject +{ + Q_OBJECT +public: + explicit ConfigHandler (QObject *parent = nullptr); + ~ConfigHandler() override = default; + + void setConfig(KScreen::ConfigPtr config); + void updateInitialData(); + + OutputModel* outputModel() const { + return m_outputs; + } + + QSize normalizeScreen(); + + KScreen::ConfigPtr config() const { + return m_config; + } + + KScreen::ConfigPtr initialConfig() const { + return m_initialConfig; + } + + int retention() const; + void setRetention(int retention); + + qreal scale(const KScreen::OutputPtr &output) const; + void setScale(KScreen::OutputPtr &output, qreal scale); + + KScreen::OutputPtr replicationSource(const KScreen::OutputPtr &output) const; + void setReplicationSource(KScreen::OutputPtr &output, const KScreen::OutputPtr &source); + + bool autoRotate(const KScreen::OutputPtr &output) const; + void setAutoRotate(KScreen::OutputPtr &output, bool autoRotate); + bool autoRotateOnlyInTabletMode(const KScreen::OutputPtr &output) const; + void setAutoRotateOnlyInTabletMode(KScreen::OutputPtr &output, bool value); + + void writeControl(); + + void checkNeedsSave(); + +Q_SIGNALS: + void outputModelChanged(); + void changed(); + void screenNormalizationUpdate(bool normalized); + void needsSaveChecked(bool need); + void retentionChanged(); + void outputConnect(bool connected); + +private: + void checkScreenNormalization(); + QSize screenSize() const; + Control::OutputRetention getRetention() const; + void primaryOutputSelected(int index); + void primaryOutputChanged(const KScreen::OutputPtr &output); + void initOutput(const KScreen::OutputPtr &output); + void resetScale(const KScreen::OutputPtr &output); + + KScreen::ConfigPtr m_config = nullptr; + KScreen::ConfigPtr m_initialConfig; + OutputModel *m_outputs = nullptr; + + std::unique_ptr m_control; + std::unique_ptr m_initialControl; + Control::OutputRetention m_initialRetention = Control::OutputRetention:: + Undefined; + QSize m_lastNormalizedScreenSize; +}; diff --git a/screen/outputmodel.cpp b/screen/outputmodel.cpp new file mode 100644 index 0000000..488c078 --- /dev/null +++ b/screen/outputmodel.cpp @@ -0,0 +1,984 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "outputmodel.h" + +#include "./common/utils.h" + +#include "confighandler.h" + +#include + +OutputModel::OutputModel(ConfigHandler *configHandler) + : QAbstractListModel(configHandler) + , m_config(configHandler) +{ + connect(this, &OutputModel::dataChanged, this, &OutputModel::changed); +} + +int OutputModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_outputs.count(); +} + +QVariant OutputModel::data(const QModelIndex &index, int role) const +{ + if (index.row() < 0 || index.row() >= m_outputs.count()) { + return QVariant(); + } + + const KScreen::OutputPtr &output = m_outputs[index.row()].ptr; + switch (role) { + case Qt::DisplayRole: + return Utils::outputName(output); + case EnabledRole: + return output->isEnabled(); + case InternalRole: + return output->type() == KScreen::Output::Type::Panel; + case PrimaryRole: + return output->isPrimary(); + case SizeRole: + return output->geometry().size(); + case PositionRole: + return m_outputs[index.row()].pos; + case NormalizedPositionRole: + return output->geometry().topLeft(); + case AutoRotateRole: + return m_config->autoRotate(output); + case AutoRotateOnlyInTabletModeRole: + return m_config->autoRotateOnlyInTabletMode(output); + case RotationRole: + return output->rotation(); + case ScaleRole: + return output->scale(); + case ResolutionIndexRole: + return resolutionIndex(output); + case ResolutionsRole: + return resolutionsStrings(output); + case RefreshRateIndexRole: + return refreshRateIndex(output); + case ReplicationSourceModelRole: + return replicationSourceModel(output); + case ReplicationSourceIndexRole: + return replicationSourceIndex(index.row()); + case ReplicasModelRole: + return replicasModel(output); + case RefreshRatesRole: + QVariantList ret; + for (const auto rate : refreshRates(output)) { + ret << QString("%1 Hz").arg(int(rate + 0.5)); + } + return ret; + } + return QVariant(); +} + +bool OutputModel::setData(const QModelIndex &index, + const QVariant &value, int role) +{ + if (index.row() < 0 || index.row() >= m_outputs.count()) { + return false; + } + + Output &output = m_outputs[index.row()]; + switch (role) { + case PositionRole: + if (value.canConvert()) { + QPoint val = value.toPoint(); + if (output.pos == val) { + return false; + } + + snap(output, val); + m_outputs[index.row()].pos = val; + updatePositions(); + Q_EMIT positionChanged(); + Q_EMIT dataChanged(index, index, {role}); + return true; + } + break; + case EnabledRole: + if (value.canConvert()) { + return setEnabled(index.row(), value.toBool()); + } + break; + case PrimaryRole: + if (value.canConvert()) { + bool primary = value.toBool(); + if (output.ptr->isPrimary() == primary) { + return false; + } + m_config->config()->setPrimaryOutput(output.ptr); + Q_EMIT dataChanged(index, index, {role}); + return true; + } + break; + case ResolutionIndexRole: + if (value.canConvert()) { + return setResolution(index.row(), value.toInt()); + } + break; + case RefreshRateIndexRole: + if (value.canConvert()) { + return setRefreshRate(index.row(), value.toInt()); + } + break; + case AutoRotateRole: + if (value.canConvert()) { + return setAutoRotate(index.row(), value.value()); + } + break; + case AutoRotateOnlyInTabletModeRole: + if (value.canConvert()) { + return setAutoRotateOnlyInTabletMode(index.row(), value.value()); + } + break; + case RotationRole: + if (value.canConvert()) { + return setRotation(index.row(), + value.value()); + } + break; + case ReplicationSourceIndexRole: + if (value.canConvert()) { + return setReplicationSourceIndex(index.row(), value.toInt() - 1); + } + break; + case ScaleRole: + bool ok; + const qreal scale = value.toReal(&ok); + if (ok && !qFuzzyCompare(output.ptr->scale(), scale)) { + output.ptr->setScale(scale); + m_config->setScale(output.ptr, scale); + Q_EMIT sizeChanged(); + Q_EMIT dataChanged(index, index, {role, SizeRole}); + return true; + } + break; + } + return false; +} + +QHash OutputModel::roleNames() const { + QHash roles = QAbstractItemModel::roleNames(); + roles[EnabledRole] = "enabled"; + roles[InternalRole] = "internal"; + roles[PrimaryRole] = "primary"; + roles[SizeRole] = "size"; + roles[PositionRole] = "position"; + roles[NormalizedPositionRole] = "normalizedPosition"; + roles[AutoRotateRole] = "autoRotate"; + roles[AutoRotateOnlyInTabletModeRole] = "autoRotateOnlyInTabletMode"; + roles[RotationRole] = "rotation"; + roles[ScaleRole] = "scale"; + roles[ResolutionIndexRole] = "resolutionIndex"; + roles[ResolutionsRole] = "resolutions"; + roles[RefreshRateIndexRole] = "refreshRateIndex"; + roles[RefreshRatesRole] = "refreshRates"; + roles[ReplicationSourceModelRole] = "replicationSourceModel"; + roles[ReplicationSourceIndexRole] = "replicationSourceIndex"; + roles[ReplicasModelRole] = "replicasModel"; + return roles; +} + +void OutputModel::add(const KScreen::OutputPtr &output) +{ + const int insertPos = m_outputs.count(); + Q_EMIT beginInsertRows(QModelIndex(), insertPos, insertPos); + + int i = 0; + while (i < m_outputs.size()) { + const QPoint pos = m_outputs[i].ptr->pos(); + if (output->pos().x() < pos.x()) { + break; + } + if (output->pos().x() == pos.x() && + output->pos().y() < pos.y()) { + break; + } + i++; + } + // Set the initial non-normalized position to be the normalized + // position plus the current delta. + QPoint pos = output->pos(); + if (!m_outputs.isEmpty()) { + const QPoint delta = m_outputs[0].pos - m_outputs[0].ptr->pos(); + pos = output->pos() + delta; + } + m_outputs.insert(i, Output(output, pos)); + + connect(output.data(), &KScreen::Output::isPrimaryChanged, + this, [this, output](){ + roleChanged(output->id(), PrimaryRole); + }); + Q_EMIT endInsertRows(); + + // Update replications. + for (int j = 0; j < m_outputs.size(); j++) { + if (i == j) { + continue; + } + QModelIndex index = createIndex(j, 0); + // Calling this directly ignores possible optimization when the + // refresh rate hasn't changed in fact. But that's ok. + Q_EMIT dataChanged(index, index, {ReplicationSourceModelRole, + ReplicationSourceIndexRole}); + } +} + +void OutputModel::remove(int outputId) +{ + auto it = std::find_if(m_outputs.begin(), m_outputs.end(), + [outputId](const Output &output) { + return output.ptr->id() == outputId; + }); + if (it != m_outputs.end()) { + const int index = it - m_outputs.begin(); + Q_EMIT beginRemoveRows(QModelIndex(), index, index); + m_outputs.erase(it); + Q_EMIT endRemoveRows(); + } +} + +void OutputModel::resetPosition(const Output &output) +{ + if (output.posReset.x() < 0) { + // KCM was closed in between. + for (const Output &out : m_outputs) { + if (out.ptr->id() == output.ptr->id()) { + continue; + } + if (out.ptr->geometry().right() > output.ptr->pos().x()) { + output.ptr->setPos(out.ptr->geometry().topRight()); + } + } + } else { + output.ptr->setPos(/*output.ptr->pos() - */output.posReset); + } +} + +bool OutputModel::setEnabled(int outputIndex, bool enable) +{ + Output &output = m_outputs[outputIndex]; + + if (output.ptr->isEnabled() == enable) { + return false; + } + + output.ptr->setEnabled(enable); + + if (enable) { + resetPosition(output); + + setResolution(outputIndex, resolutionIndex(output.ptr)); + reposition(); + } else { + output.posReset = output.ptr->pos(); + } + + QModelIndex index = createIndex(outputIndex, 0); + Q_EMIT dataChanged(index, index, {EnabledRole}); + return true; +} + +inline +bool refreshRateCompare(float rate1, float rate2) +{ + return qAbs(rate1 - rate2) < 0.5; +} + +bool OutputModel::setResolution(int outputIndex, int resIndex) +{ + const Output &output = m_outputs[outputIndex]; + const auto resolutionList = resolutions(output.ptr); + if (resIndex < 0 || resIndex >= resolutionList.size()) { + return false; + } + const QSize size = resolutionList[resIndex]; + + const float oldRate = output.ptr->currentMode() ? output.ptr->currentMode()->refreshRate() : + -1; + const auto modes = output.ptr->modes(); + + auto modeIt = std::find_if(modes.begin(), modes.end(), + [size, oldRate](const KScreen::ModePtr &mode) { + // TODO: we don't want to compare against old refresh rate if + // refresh rate selection is auto. + return mode->size() == size && + refreshRateCompare(mode->refreshRate(), oldRate); + }); + + if (modeIt == modes.end()) { + // New resolution does not support previous refresh rate. + // Get the highest one instead. + float bestRefreshRate = 0; + auto it = modes.begin(); + while (it != modes.end()) { + if ((*it)->size() == size && (*it)->refreshRate() > bestRefreshRate) { + modeIt = it; + } + it++; + } + } + Q_ASSERT(modeIt != modes.end()); + + const auto id = (*modeIt)->id(); + if (output.ptr->currentModeId() == id) { + return false; + } + output.ptr->setCurrentModeId(id); + + QModelIndex index = createIndex(outputIndex, 0); + // Calling this directly ignores possible optimization when the + // refresh rate hasn't changed in fact. But that's ok. + Q_EMIT dataChanged(index, index, {ResolutionIndexRole, + SizeRole, + RefreshRateIndexRole}); + Q_EMIT sizeChanged(); + return true; +} + +bool OutputModel::setRefreshRate(int outputIndex, int refIndex) +{ + const Output &output = m_outputs[outputIndex]; + const auto rates = refreshRates(output.ptr); + if (refIndex < 0 || refIndex >= rates.size()) { + return false; + } + const float refreshRate = rates[refIndex]; + + const auto modes = output.ptr->modes(); + const auto oldMode = output.ptr->currentMode(); + + auto modeIt = std::find_if(modes.begin(), modes.end(), + [oldMode, refreshRate](const KScreen::ModePtr &mode) { + // TODO: we don't want to compare against old refresh rate if + // refresh rate selection is auto. + return mode->size() == oldMode->size() && + refreshRateCompare(mode->refreshRate(), refreshRate); + }); + Q_ASSERT(modeIt != modes.end()); + + if (refreshRateCompare(oldMode->refreshRate(), (*modeIt)->refreshRate())) { + // no change + return false; + } + output.ptr->setCurrentModeId((*modeIt)->id()); + QModelIndex index = createIndex(outputIndex, 0); + Q_EMIT dataChanged(index, index, {RefreshRateIndexRole}); + return true; +} + +bool OutputModel::setAutoRotate(int outputIndex, bool value) +{ + Output &output = m_outputs[outputIndex]; + + if (m_config->autoRotate(output.ptr) == value) { + return false; + } + m_config->setAutoRotate(output.ptr, value); + + QModelIndex index = createIndex(outputIndex, 0); + Q_EMIT dataChanged(index, index, {AutoRotateRole}); + return true; +} + +bool OutputModel::setAutoRotateOnlyInTabletMode(int outputIndex, bool value) +{ + Output &output = m_outputs[outputIndex]; + + if (m_config->autoRotateOnlyInTabletMode(output.ptr) == value) { + return false; + } + m_config->setAutoRotateOnlyInTabletMode(output.ptr, value); + + QModelIndex index = createIndex(outputIndex, 0); + Q_EMIT dataChanged(index, index, {AutoRotateOnlyInTabletModeRole}); + return true; +} + +bool OutputModel::setRotation(int outputIndex, KScreen::Output::Rotation rotation) +{ + const Output &output = m_outputs[outputIndex]; + + if (rotation != KScreen::Output::None + && rotation != KScreen::Output::Left + && rotation != KScreen::Output::Inverted + && rotation != KScreen::Output::Right) { + return false; + } + if (output.ptr->rotation() == rotation) { + return false; + } + output.ptr->setRotation(rotation); + + QModelIndex index = createIndex(outputIndex, 0); + Q_EMIT dataChanged(index, index, {RotationRole, SizeRole}); + Q_EMIT sizeChanged(); + return true; + +} + +int OutputModel::resolutionIndex(const KScreen::OutputPtr &output) const +{ + const QSize currentResolution = output->enforcedModeSize(); + + if (!currentResolution.isValid()) { + return 0; + } + + const auto sizes = resolutions(output); + + const auto it = std::find_if(sizes.begin(), + sizes.end(), + [currentResolution](const QSize &size) { + return size == currentResolution; + }); + if (it == sizes.end()) { + return -1; + } + return it - sizes.begin(); +} + +int OutputModel::refreshRateIndex(const KScreen::OutputPtr &output) const +{ + if (!output->currentMode()) { + return 0; + } + const auto rates = refreshRates(output); + const float currentRate = output->currentMode()->refreshRate(); + + const auto it = std::find_if(rates.begin(), + rates.end(), + [currentRate](float rate) { + return refreshRateCompare(rate, currentRate); + }); + if (it == rates.end()) { + return 0; + } + return it - rates.begin(); +} + +static int greatestCommonDivisor(int a, int b) { + if (b == 0) { + return a; + } + return greatestCommonDivisor(b, a % b); +} + +QVariantList OutputModel::resolutionsStrings(const KScreen::OutputPtr &output) const +{ + QVariantList ret; + for (const QSize &size : resolutions(output)) { + int divisor = greatestCommonDivisor(size.width(), size.height()); + + // Prefer "16:10" over "8:5" + if (size.height() / divisor == 5) { + divisor /= 2; + } + + const QString text; + + // const QString text = i18nc("Width x height (aspect ratio)", "%1x%2 (%3:%4)", + // // Explicitly not have it add thousand-separators. + // QString::number(size.width()), + // QString::number(size.height()), + // size.width() / divisor, + // size.height() / divisor); + + ret << text; + } + return ret; +} + +QVector OutputModel::resolutions(const KScreen::OutputPtr &output) const +{ + QVector hits; + + for (const auto &mode : output->modes()) { + const QSize size = mode->size(); + if (!hits.contains(size)) { + hits << size; + } + } + std::sort(hits.begin(), hits.end(), [](const QSize &a, const QSize &b) { + if (a.width() > b.width()) { + return true; + } + if (a.width() == b.width() && a.height() > b.height()) { + return true; + } + return false; + }); + return hits; +} + +QVector OutputModel::refreshRates(const KScreen::OutputPtr + &output) const +{ + QVector hits; + + QSize baseSize; + if (output->currentMode()) { + baseSize = output->currentMode()->size(); + } else if (output->preferredMode()) { + baseSize = output->preferredMode()->size(); + } + if (!baseSize.isValid()) { + return hits; + } + + for (const auto &mode : output->modes()) { + if (mode->size() != baseSize) { + continue; + } + const float rate = mode->refreshRate(); + if (std::find_if(hits.begin(), hits.end(), + [rate](float r) { + return refreshRateCompare(r, rate); + }) != hits.end()) { + continue; + } + hits << rate; + } + return hits; +} + +int OutputModel::replicationSourceId(const Output &output) const +{ + const KScreen::OutputPtr source = m_config->replicationSource(output.ptr); + if (!source) { + return 0; + } + return source->id(); +} + +QStringList OutputModel::replicationSourceModel(const KScreen::OutputPtr &output) const +{ + QStringList ret = { QObject::tr("None") }; + + for (const auto &out : m_outputs) { + if (out.ptr->id() != output->id()) { + const int outSourceId = replicationSourceId(out); + if (outSourceId == output->id()) { + // 'output' is already source for replication, can't be replica itself + return { QObject::tr("Replicated by other output") }; + } + if (outSourceId) { + // This 'out' is a replica. Can't be a replication source. + continue; + } + ret.append(Utils::outputName(out.ptr)); + } + } + return ret; +} + +bool OutputModel::setReplicationSourceIndex(int outputIndex, int sourceIndex) +{ + if (outputIndex <= sourceIndex) { + sourceIndex++; + } + if (sourceIndex >= m_outputs.count()) { + return false; + } + + Output &output = m_outputs[outputIndex]; + const int oldSourceId = replicationSourceId(output); + + if (sourceIndex < 0) { + if (oldSourceId == 0) { + // no change + return false; + } + m_config->setReplicationSource(output.ptr, nullptr); + output.ptr->setLogicalSize(QSizeF()); + resetPosition(output); + } else { + const auto source = m_outputs[sourceIndex].ptr; + if (oldSourceId == source->id()) { + // no change + return false; + } + m_config->setReplicationSource(output.ptr, source); + output.posReset = output.ptr->pos(); + output.ptr->setPos(source->pos()); + output.ptr->setLogicalSize(source->logicalSize()); + } + + reposition(); + + QModelIndex index = createIndex(outputIndex, 0); + Q_EMIT dataChanged(index, index, {ReplicationSourceIndexRole}); + + if (oldSourceId != 0) { + auto it = std::find_if(m_outputs.begin(), m_outputs.end(), + [oldSourceId](const Output &out) { + return out.ptr->id() == oldSourceId; + }); + if (it != m_outputs.end()) { + QModelIndex index = createIndex(it - m_outputs.begin(), 0); + Q_EMIT dataChanged(index, index, {ReplicationSourceModelRole, ReplicasModelRole}); + } + } + if (sourceIndex >= 0) { + QModelIndex index = createIndex(sourceIndex, 0); + Q_EMIT dataChanged(index, index, {ReplicationSourceModelRole, ReplicasModelRole}); + } + return true; +} + +int OutputModel::replicationSourceIndex(int outputIndex) const +{ + const int sourceId = replicationSourceId(m_outputs[outputIndex]); + if (!sourceId) { + return 0; + } + for (int i = 0; i < m_outputs.size(); i++) { + const Output &output = m_outputs[i]; + if (output.ptr->id() == sourceId) { + return i + (outputIndex > i ? 1 : 0); + } + } + return 0; +} + +QVariantList OutputModel::replicasModel(const KScreen::OutputPtr &output) const +{ + QVariantList ret; + for (int i = 0; i < m_outputs.size(); i++) { + const Output &out = m_outputs[i]; + if (out.ptr->id() != output->id()) { + if (replicationSourceId(out) == output->id()) { + ret << i; + } + } + } + return ret; +} + +void OutputModel::roleChanged(int outputId, OutputRoles role) +{ + for (int i = 0; i < m_outputs.size(); i++) { + Output &output = m_outputs[i]; + if (output.ptr->id() == outputId) { + QModelIndex index = createIndex(i, 0); + Q_EMIT dataChanged(index, index, {role}); + return; + } + } +} + +bool OutputModel::positionable(const Output &output) const +{ + return output.ptr->isPositionable(); +} + +void OutputModel::reposition() +{ + int x = 0; + int y = 0; + + // Find first valid output. + for (const auto &out : m_outputs) { + if (positionable(out)) { + x = out.ptr->pos().x(); + y = out.ptr->pos().y(); + break; + } + } + + for (int i = 0; i < m_outputs.size(); i++) { + if (!positionable(m_outputs[i])) { + continue; + } + const QPoint &cmp = m_outputs[i].ptr->pos(); + + if (cmp.x() < x) { + x = cmp.x(); + } + if (cmp.y() < y) { + y = cmp.y(); + } + } + + if (x == 0 && y == 0) { + return; + } + + for (int i = 0; i < m_outputs.size(); i++) { + auto &out = m_outputs[i]; + out.ptr->setPos(out.ptr->pos() - QPoint(x, y)); + QModelIndex index = createIndex(i, 0); + Q_EMIT dataChanged(index, index, {NormalizedPositionRole}); + } + m_config->normalizeScreen(); +} + +QPoint OutputModel::originDelta() const +{ + int x = 0; + int y = 0; + + // Find first valid output. + for (const auto &out : m_outputs) { + if (positionable(out)) { + x = out.pos.x(); + y = out.pos.y(); + break; + } + } + + for (int i = 1; i < m_outputs.size(); i++) { + if (!positionable(m_outputs[i])) { + continue; + } + const QPoint &cmp = m_outputs[i].pos; + + if (cmp.x() < x) { + x = cmp.x(); + } + if (cmp.y() < y) { + y = cmp.y(); + } + } + return QPoint(x, y); +} + +void OutputModel::updatePositions() +{ + const QPoint delta = originDelta(); + for (int i = 0; i < m_outputs.size(); i++) { + const auto &out = m_outputs[i]; + if (!positionable(out)) { + continue; + } + const QPoint set = out.pos - delta; + if (out.ptr->pos() != set) { + out.ptr->setPos(set); + QModelIndex index = createIndex(i, 0); + Q_EMIT dataChanged(index, index, {NormalizedPositionRole}); + } + } + updateOrder(); +} + +void OutputModel::updateOrder() +{ + auto order = m_outputs; + std::sort(order.begin(), order.end(), [](const Output &a, const Output &b) { + const int xDiff = b.ptr->pos().x() - a.ptr->pos().x(); + const int yDiff = b.ptr->pos().y() - a.ptr->pos().y(); + if (xDiff > 0) { + return true; + } + if (xDiff == 0 && yDiff > 0) { + return true; + } + return false; + }); + + for (int i = 0; i < order.size(); i++) { + for (int j = 0; j < m_outputs.size(); j++) { + if (order[i].ptr->id() != m_outputs[j].ptr->id()) { + continue; + } + if (i != j) { + beginMoveRows(QModelIndex(), j, j, QModelIndex(), i); + m_outputs.remove(j); + m_outputs.insert(i, order[i]); + endMoveRows(); + } + break; + } + } + + // TODO: Could this be optimized by only outputs updating where replica indices changed? + for (int i = 0; i < m_outputs.size(); i++) { + QModelIndex index = createIndex(i, 0); + Q_EMIT dataChanged(index, index, { ReplicasModelRole }); + } +} + +bool OutputModel::normalizePositions() +{ + bool changed = false; + for (int i = 0; i < m_outputs.size(); i++) { + auto &output = m_outputs[i]; + if (output.pos == output.ptr->pos()) { + continue; + } + if (!positionable(output)) { + continue; + } + changed = true; + auto index = createIndex(i, 0); + output.pos = output.ptr->pos(); + Q_EMIT dataChanged(index, index, {PositionRole}); + } + return changed; +} + +bool OutputModel::positionsNormalized() const +{ + // There might be slight deviations because of snapping. + return originDelta().manhattanLength() < 5; +} + +const int s_snapArea = 80; + +bool isVerticalClose(const QRect &rect1, const QRect &rect2) +{ + if (rect2.top() - rect1.bottom() > s_snapArea ) { + return false; + } + if (rect1.top() - rect2.bottom() > s_snapArea ) { + return false; + } + return true; +} + +bool snapToRight(const QRect &target, + const QSize &size, + QPoint &dest) +{ + if (qAbs(target.right() - dest.x()) < s_snapArea) { + // In snap zone for left to right snap. + dest.setX(target.right() + 1); + return true; + } + if (qAbs(target.right() - (dest.x() + size.width())) < s_snapArea) { + // In snap zone for right to right snap. + dest.setX(target.right() - size.width()); + return true; + } + return false; +} + +bool snapToLeft(const QRect &target, + const QSize &size, + QPoint &dest) +{ + if (qAbs(target.left() - dest.x()) < s_snapArea) { + // In snap zone for left to left snap. + dest.setX(target.left()); + return true; + } + if (qAbs(target.left() - (dest.x() + size.width())) < s_snapArea) { + // In snap zone for right to left snap. + dest.setX(target.left() - size.width()); + return true; + } + return false; +} + +bool snapToMiddle(const QRect &target, + const QSize &size, + QPoint &dest) +{ + const int outputMid = dest.y() + size.height() / 2; + const int targetMid = target.top() + target.height() / 2; + if (qAbs(targetMid - outputMid) < s_snapArea) { + // In snap zone for middle to middle snap. + dest.setY(targetMid - size.height() / 2); + return true; + } + return false; +} + +bool snapToTop(const QRect &target, + const QSize &size, + QPoint &dest) +{ + if (qAbs(target.top() - dest.y()) < s_snapArea) { + // In snap zone for bottom to top snap. + dest.setY(target.top()); + return true; + } + if (qAbs(target.top() - (dest.y() + size.height())) < s_snapArea) { + // In snap zone for top to top snap. + dest.setY(target.top() - size.height()); + return true; + } + return false; +} + +bool snapToBottom(const QRect &target, + const QSize &size, + QPoint &dest) +{ + if (qAbs(target.bottom() - dest.y()) < s_snapArea) { + // In snap zone for top to bottom snap. + dest.setY(target.bottom() + 1); + return true; + } + if (qAbs(target.bottom() - (dest.y() + size.height())) < s_snapArea) { + // In snap zone for bottom to bottom snap. + dest.setY(target.bottom() - size.height() + 1); + return true; + } + return false; +} + +bool snapVertical(const QRect &target, + const QSize &size, + QPoint &dest) +{ + if (snapToMiddle(target, size, dest)) { + return true; + } + if (snapToBottom(target, size, dest)) { + return true; + } + if (snapToTop(target, size, dest)) { + return true; + } + return false; +} + +void OutputModel::snap(const Output &output, QPoint &dest) +{ + const QSize size = output.ptr->geometry().size(); + for (const Output &out : m_outputs) { + if (out.ptr->id() == output.ptr->id()) { + // Can not snap to itself. + continue; + } + if (!positionable(out)) { + continue; + } + + const QRect target(out.pos, out.ptr->geometry().size()); + + if (!isVerticalClose(target, QRect(dest, size))) { + continue; + } + + // try snap left to right first + if (snapToRight(target, size, dest)) { + snapVertical(target, size, dest); + continue; + } + if (snapToLeft(target, size, dest)) { + snapVertical(target, size, dest); + continue; + } + if (snapVertical(target, size, dest)) { + continue; + } + } +} diff --git a/screen/outputmodel.h b/screen/outputmodel.h new file mode 100644 index 0000000..76fded3 --- /dev/null +++ b/screen/outputmodel.h @@ -0,0 +1,150 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#pragma once + +#include +#include + +#include +#include + +class ConfigHandler; + +class OutputModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum OutputRoles { + EnabledRole = Qt::UserRole + 1, + InternalRole, + PrimaryRole, + SizeRole, + /** Position in the graphical view relative to some arbitrary but fixed origin. */ + PositionRole, + /** Position for backend relative to most northwest display corner. */ + NormalizedPositionRole, + AutoRotateRole, + AutoRotateOnlyInTabletModeRole, + RotationRole, + ScaleRole, + ResolutionIndexRole, + ResolutionsRole, + RefreshRateIndexRole, + RefreshRatesRole, + ReplicationSourceModelRole, + ReplicationSourceIndexRole, + ReplicasModelRole + }; + + explicit OutputModel(ConfigHandler *configHandler); + ~OutputModel() override = default; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, + int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, + const QVariant &value, + int role = Qt::EditRole) override; + + void add(const KScreen::OutputPtr &output); + void remove(int outputId); + + /** + * Resets the origin for calculation of positions to the most northwest display corner + * while keeping the normalized positions untouched. + * + * @return true if some (unnormalized) output position changed on this call, otherwise false. + */ + bool normalizePositions(); + bool positionsNormalized() const; + +Q_SIGNALS: + void positionChanged(); + void sizeChanged(); + void changed(); + +protected: + QHash roleNames() const override; + +private: + struct Output { + Output() {} + Output(const Output &output) + : ptr(output.ptr) + , pos(output.pos) + {} + Output(Output &&) noexcept = default; + Output(KScreen::OutputPtr _ptr, const QPoint &_pos) + : ptr(_ptr) + , pos(_pos) + {} + Output &operator=(const Output &output) { + ptr = output.ptr; + pos = output.pos; + posReset = QPoint(-1, -1); + return *this; + } + Output &operator=(Output &&) noexcept = default; + + KScreen::OutputPtr ptr; + QPoint pos; + QPoint posReset = QPoint(-1, -1); + }; + + void roleChanged(int outputId, OutputRoles role); + + void resetPosition(const Output &output); + void reposition(); + void updatePositions(); + void updateOrder(); + QPoint originDelta() const; + + /** + * @brief Snaps moved output to others + * @param output the moved output + * @param dest the desired destination to be adjusted by snapping + */ + void snap(const Output &output, QPoint &dest); + + bool setEnabled(int outputIndex, bool enable); + + bool setResolution(int outputIndex, int resIndex); + bool setRefreshRate(int outputIndex, int refIndex); + bool setRotation(int outputIndex, KScreen::Output::Rotation rotation); + bool setAutoRotate(int outputIndex, bool value); + bool setAutoRotateOnlyInTabletMode(int outputIndex, bool value); + + int resolutionIndex(const KScreen::OutputPtr &output) const; + int refreshRateIndex(const KScreen::OutputPtr &output) const; + QVariantList resolutionsStrings(const KScreen::OutputPtr &output) const; + QVector resolutions(const KScreen::OutputPtr &output) const; + QVector refreshRates(const KScreen::OutputPtr &output) const; + + bool positionable(const Output &output) const; + + QStringList replicationSourceModel(const KScreen::OutputPtr &output) const; + bool setReplicationSourceIndex(int outputIndex, int sourceIndex); + int replicationSourceIndex(int outputIndex) const; + int replicationSourceId(const Output &output) const; + + QVariantList replicasModel(const KScreen::OutputPtr &output) const; + + QVector m_outputs; + + ConfigHandler *m_config; +}; diff --git a/screen/plugin.cpp b/screen/plugin.cpp new file mode 100644 index 0000000..80345a3 --- /dev/null +++ b/screen/plugin.cpp @@ -0,0 +1,20 @@ +#include +#include + +#include "outputmodel.h" +#include "screen.h" + +class QmlPlugins : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char * uri) override { + // qmlRegisterType(uri, 1, 0, "Screen"); + qmlRegisterType(uri, 1, 0, "Screen"); + qmlRegisterType(uri, 1, 0, "Output"); + } +}; + +#include "plugin.moc" \ No newline at end of file diff --git a/screen/qmldir b/screen/qmldir new file mode 100644 index 0000000..3ba928b --- /dev/null +++ b/screen/qmldir @@ -0,0 +1,3 @@ +module Cutefish.Screen + +plugin cutefishscreen_qmlplugins \ No newline at end of file diff --git a/screen/screen.cpp b/screen/screen.cpp new file mode 100644 index 0000000..26badc9 --- /dev/null +++ b/screen/screen.cpp @@ -0,0 +1,52 @@ +#include "screen.h" +#include "outputmodel.h" + +#include +#include + +Screen::Screen(QObject *parent) + : QObject(parent) +{ + qmlRegisterType(); + load(); +} + +void Screen::load() +{ + // Don't pull away the outputModel under QML's feet + // signal its disappearance first before deleting and replacing it. + // We take the m_config pointer so outputModel() will return null, + // gracefully cleaning up the QML side and only then we will delete it. + auto *oldConfig = m_config.release(); + if (oldConfig) { + emit outputModelChanged(); + delete oldConfig; + } + + m_config.reset(new ConfigHandler(this)); + connect(m_config.get(), &ConfigHandler::outputModelChanged, this, &Screen::outputModelChanged); + + connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, this, &Screen::configReady); +} + +OutputModel *Screen::outputModel() const +{ + if (!m_config) { + return nullptr; + } + + return m_config->outputModel(); +} + +void Screen::configReady(KScreen::ConfigOperation *op) +{ + if (op->hasError()) { + m_config.reset(); + return; + } + + KScreen::ConfigPtr config = qobject_cast(op)->config(); + const bool autoRotationSupported = config->supportedFeatures() & (KScreen::Config::Feature::AutoRotation | KScreen::Config::Feature::TabletMode); + + m_config->setConfig(config); +} \ No newline at end of file diff --git a/screen/screen.h b/screen/screen.h new file mode 100644 index 0000000..d419424 --- /dev/null +++ b/screen/screen.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +#include "confighandler.h" +#include "outputmodel.h" + +class ConfigHandler; +class OutputModel; +class Screen : public QObject +{ + Q_OBJECT + Q_PROPERTY(OutputModel *outputModel READ outputModel NOTIFY outputModelChanged) + +public: + explicit Screen(QObject *parent = nullptr); + + OutputModel *outputModel() const; + + void load(); + +private: + void configReady(KScreen::ConfigOperation *op); + +Q_SIGNALS: + void outputModelChanged(); + +private: + std::unique_ptr m_config; +}; \ No newline at end of file