Import Upstream version 5.24.4

This commit is contained in:
Lu zhiping 2022-06-29 21:02:24 +08:00
commit 749f68970d
146 changed files with 18794 additions and 0 deletions

2
.git-blame-ignore-revs Normal file
View File

@ -0,0 +1,2 @@
# clang-format
0ae03aa6e4b2afd6f1348ff16502a6257f3580e4

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
.kdev4
*.kdev4
/build*
.clang-format
cmake-build-debug*
.idea
/compile_commands.json
.clangd
.cache

6
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,6 @@
# SPDX-FileCopyrightText: None
# SPDX-License-Identifier: CC0-1.0
include:
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml

9
.kde-ci.yml Normal file
View File

@ -0,0 +1,9 @@
# SPDX-FileCopyrightText: None
# SPDX-License-Identifier: CC0-1.0
Dependencies:
- 'on': ['@all']
'require':
'frameworks/extra-cmake-modules': '@latest'
'frameworks/kwayland': '@latest'
'plasma/kwayland-server': '@same'

1
.mailmap Normal file
View File

@ -0,0 +1 @@
Sebastian Kügler <sebas@kde.org>

119
CMakeLists.txt Normal file
View File

@ -0,0 +1,119 @@
cmake_minimum_required(VERSION 3.16)
project(libkscreen)
set(PROJECT_VERSION "5.24.4")
set(QT_MIN_VERSION "5.15.0")
set(KF5_MIN_VERSION "5.86")
set(KDE_COMPILERSETTINGS_LEVEL "5.82")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH})
include(KDEInstallDirs)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(KDECMakeSettings)
include(ECMSetupVersion)
include(ECMMarkAsTest)
include(ECMGenerateHeaders)
include(ECMQtDeclareLoggingCategory)
include(ECMAddQch)
include(FeatureSummary)
include(CMakePackageConfigHelpers)
include(GenerateExportHeader)
include(KDEClangFormat)
include(KDEGitCommitHooks)
find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Core DBus Gui Test X11Extras)
find_package(QtWaylandScanner REQUIRED)
find_package(WaylandScanner)
# Wayland backend
find_package(KF5Wayland ${KF5_MIN_VERSION} CONFIG REQUIRED)
add_feature_info("KF5Wayland" KF5Wayland_FOUND "Required for building libkscreen's KWayland backend")
find_package(PlasmaWaylandProtocols 1.6.0 CONFIG)
set_package_properties(PlasmaWaylandProtocols PROPERTIES TYPE REQUIRED)
# Wayland backend
find_package(KF5Wayland ${KF5_MIN_VERSION} CONFIG REQUIRED)
add_feature_info("KF5Wayland" KF5Wayland_FOUND "Required for building libkscreen's KWayland backend")
find_package(Wayland 1.15 COMPONENTS Client)
set_package_properties(Wayland PROPERTIES
TYPE REQUIRED
)
# xrandr backend
find_package(XCB COMPONENTS XCB RANDR)
set_package_properties(XCB PROPERTIES
TYPE OPTIONAL
PURPOSE "Required for building XRandR backends"
)
option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF)
add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)")
ecm_setup_version(${PROJECT_VERSION} VARIABLE_PREFIX KSCREEN
VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kscreen_version.h"
PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5ScreenConfigVersion.cmake"
SOVERSION 7)
add_subdirectory(src)
add_subdirectory(backends)
if(BUILD_TESTING)
add_subdirectory(autotests)
add_subdirectory(tests)
endif()
set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/KF5Screen")
# add clang-format target for all our real source files
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
if (BUILD_QCH)
ecm_install_qch_export(
TARGETS KF5Screen_QCH
FILE KF5ScreenQchTargets.cmake
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5ScreenQchTargets.cmake\")")
endif()
configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KF5ScreenConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/KF5ScreenConfig.cmake"
INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/KF5ScreenConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/KF5ScreenConfigVersion.cmake"
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
install(EXPORT
KF5ScreenTargets
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
FILE KF5ScreenTargets.cmake
NAMESPACE KF5::
COMPONENT Devel
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/kscreen_version.h"
DESTINATION "${KF5_INCLUDE_INSTALL_DIR}"
COMPONENT Devel
)
install(FILES libkscreen.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR})
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)

106
CMakePresets.json Normal file
View File

@ -0,0 +1,106 @@
{
"version": 2,
"configurePresets": [
{
"name": "dev",
"displayName": "Build as debug",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "dev-disable-deprecated",
"displayName": "Build as without deprecated methods",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-disable-deprecated",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"CMAKE_CXX_FLAGS_INIT": "-DQT_DISABLE_DEPRECATED_BEFORE=0x060000 -DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x060000"
}
},
{
"name": "asan",
"displayName": "Build with Asan support.",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-asan",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"ECM_ENABLE_SANITIZERS" : "'address;undefined'",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "unity",
"displayName": "Build with CMake unity support.",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-unity",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_UNITY_BUILD": "ON",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "release",
"displayName": "Build as release mode.",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "profile",
"displayName": "profile",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-profile",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "clazy",
"displayName": "clazy",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build-clazy",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
},
"environment": {
"CXX": "clazy",
"CCACHE_DISABLE": "ON"
}
}
],
"buildPresets": [
{
"name": "dev",
"configurePreset": "dev"
},
{
"name": "asan",
"configurePreset": "asan"
},
{
"name": "dev-disable-deprecated",
"configurePreset": "dev-disable-deprecated"
},
{
"name": "unity",
"configurePreset": "unity"
},
{
"name": "clazy",
"configurePreset": "clazy",
"environment": {
"CLAZY_CHECKS" : "level0,level1,detaching-member,ifndef-define-typo,isempty-vs-count,qrequiredresult-candidates,reserve-candidates,signal-with-return-value,unneeded-cast,function-args-by-ref,function-args-by-value,returning-void-expression,no-ctor-missing-parent-argument,isempty-vs-count,qhash-with-char-pointer-key,raw-environment-function,qproperty-type-mismatch,old-style-connect,qstring-allocations,container-inside-loop,heap-allocated-small-trivial-type,inefficient-qlist,qstring-varargs,level2,detaching-member,heap-allocated-small-trivial-type,isempty-vs-count,qstring-varargs,qvariant-template-instantiation,raw-environment-function,reserve-candidates,signal-with-return-value,thread-with-slots,no-ctor-missing-parent-argument,no-missing-typeinfo",
"CCACHE_DISABLE" : "ON"
}
}
]
}

41
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,41 @@
# Contributing to libkscreen
- [Code of Conduct](#code-of-conduct)
- [Submission Guideline](#submission-guideline)
- [Commit Message Guideline](#commit-message-guideline)
- [Contact](#contact)
## Code of Conduct
The [KDE Community Code of Conduct][kde-coc] is applied.
You can reach out to the [Commmunity Working Group][community-working-group] if you have questions about the Code of Conduct or if you want to get help on solving an issue with another contributor or maintainer.
## Issues
Please reports issues and suggestions using [KDE's bugzilla][bugzilla]
## Submission Guideline
The project follows the [Frameworks Coding Style][frameworks-style].
All non-trivial patches need to go through [code review][gitlab-reviews].
Commits are applied on top of master and cherry-picked to release branches if appropriate. Use `-x` when cherry-picking. Larger changes should be split up into smaller logical commits.
libkscreen is released as part of Plasma. See the [Plasma schedule][plasma-schedule] for information on the release schedule.
## Commit Message Guideline
Please follow the [KDE Commit Policy][commit-policy]. In particular please make sure to use appropriate [commit keywords][commit-policy-keywords].
## Contact
Real-time communication about the project happens on the IRC channel `#plasma` on irc.libera.chat and the bridged Matrix room `#plasma:kde.org`.
Emails about the project can be sent to the [plasma-devel][plasma-devel] mailing list.
[kde-coc]: https://kde.org/code-of-conduct
[community-working-group]: https://ev.kde.org/workinggroups/cwg.php
[frameworks-style]: https://community.kde.org/Policies/Frameworks_Coding_Style
[gitlab-reviews]: https://invent.kde.org/plasma/kscreen
[plasma-schedule]: https://community.kde.org/Schedules/Plasma_5
[commit-policy]: https://community.kde.org/Policies/Commit_Policy
[bugzilla]: https://bugs.kde.org/describecomponents.cgi?product=KScreen
[commit-policy-keywords]: https://community.kde.org/Policies/Commit_Policy#Special_keywords_in_GIT_and_SVN_log_messages
[plasma-devel]: https://mail.kde.org/mailman/listinfo/plasma-devel

8
KF5ScreenConfig.cmake.in Normal file
View File

@ -0,0 +1,8 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
find_dependency(Qt5Core @QT_MIN_VERSION@)
find_dependency(Qt5Gui @QT_MIN_VERSION@)
include("${CMAKE_CURRENT_LIST_DIR}/KF5ScreenTargets.cmake")
@PACKAGE_INCLUDE_QCHTARGETS@

311
LICENSES/GPL-2.0-only.txt Normal file
View File

@ -0,0 +1,311 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
Everyone is permitted to copy and distribute verbatim copies of this license
document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your freedom to share
and change it. By contrast, the GNU General Public License is intended to
guarantee your freedom to share and change free software--to make sure the
software is free for all its users. This General Public License applies to
most of the Free Software Foundation's software and to any other program whose
authors commit to using it. (Some other Free Software Foundation software
is covered by the GNU Lesser General Public License instead.) You can apply
it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our
General Public Licenses are designed to make sure that you have the freedom
to distribute copies of free software (and charge for this service if you
wish), that you receive source code or can get it if you want it, that you
can change the software or use pieces of it in new free programs; and that
you know you can do these things.
To protect your rights, we need to make restrictions that forbid anyone to
deny you these rights or to ask you to surrender the rights. These restrictions
translate to certain responsibilities for you if you distribute copies of
the software, or if you modify it.
For example, if you distribute copies of such a program, whether gratis or
for a fee, you must give the recipients all the rights that you have. You
must make sure that they, too, receive or can get the source code. And you
must show them these terms so they know their rights.
We protect your rights with two steps: (1) copyright the software, and (2)
offer you this license which gives you legal permission to copy, distribute
and/or modify the software.
Also, for each author's protection and ours, we want to make certain that
everyone understands that there is no warranty for this free software. If
the software is modified by someone else and passed on, we want its recipients
to know that what they have is not the original, so that any problems introduced
by others will not reflect on the original authors' reputations.
Finally, any free program is threatened constantly by software patents. We
wish to avoid the danger that redistributors of a free program will individually
obtain patent licenses, in effect making the program proprietary. To prevent
this, we have made it clear that any patent must be licensed for everyone's
free use or not licensed at all.
The precise terms and conditions for copying, distribution and modification
follow.
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains a notice
placed by the copyright holder saying it may be distributed under the terms
of this General Public License. The "Program", below, refers to any such program
or work, and a "work based on the Program" means either the Program or any
derivative work under copyright law: that is to say, a work containing the
Program or a portion of it, either verbatim or with modifications and/or translated
into another language. (Hereinafter, translation is included without limitation
in the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not covered
by this License; they are outside its scope. The act of running the Program
is not restricted, and the output from the Program is covered only if its
contents constitute a work based on the Program (independent of having been
made by running the Program). Whether that is true depends on what the Program
does.
1. You may copy and distribute verbatim copies of the Program's source code
as you receive it, in any medium, provided that you conspicuously and appropriately
publish on each copy an appropriate copyright notice and disclaimer of warranty;
keep intact all the notices that refer to this License and to the absence
of any warranty; and give any other recipients of the Program a copy of this
License along with the Program.
You may charge a fee for the physical act of transferring a copy, and you
may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion of it,
thus forming a work based on the Program, and copy and distribute such modifications
or work under the terms of Section 1 above, provided that you also meet all
of these conditions:
a) You must cause the modified files to carry prominent notices stating that
you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in whole or
in part contains or is derived from the Program or any part thereof, to be
licensed as a whole at no charge to all third parties under the terms of this
License.
c) If the modified program normally reads commands interactively when run,
you must cause it, when started running for such interactive use in the most
ordinary way, to print or display an announcement including an appropriate
copyright notice and a notice that there is no warranty (or else, saying that
you provide a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this License.
(Exception: if the Program itself is interactive but does not normally print
such an announcement, your work based on the Program is not required to print
an announcement.)
These requirements apply to the modified work as a whole. If identifiable
sections of that work are not derived from the Program, and can be reasonably
considered independent and separate works in themselves, then this License,
and its terms, do not apply to those sections when you distribute them as
separate works. But when you distribute the same sections as part of a whole
which is a work based on the Program, the distribution of the whole must be
on the terms of this License, whose permissions for other licensees extend
to the entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest your
rights to work written entirely by you; rather, the intent is to exercise
the right to control the distribution of derivative or collective works based
on the Program.
In addition, mere aggregation of another work not based on the Program with
the Program (or with a work based on the Program) on a volume of a storage
or distribution medium does not bring the other work under the scope of this
License.
3. You may copy and distribute the Program (or a work based on it, under Section
2) in object code or executable form under the terms of Sections 1 and 2 above
provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable source code,
which must be distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three years, to give
any third party, for a charge no more than your cost of physically performing
source distribution, a complete machine-readable copy of the corresponding
source code, to be distributed under the terms of Sections 1 and 2 above on
a medium customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer to distribute
corresponding source code. (This alternative is allowed only for noncommercial
distribution and only if you received the program in object code or executable
form with such an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for making
modifications to it. For an executable work, complete source code means all
the source code for all modules it contains, plus any associated interface
definition files, plus the scripts used to control compilation and installation
of the executable. However, as a special exception, the source code distributed
need not include anything that is normally distributed (in either source or
binary form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component itself
accompanies the executable.
If distribution of executable or object code is made by offering access to
copy from a designated place, then offering equivalent access to copy the
source code from the same place counts as distribution of the source code,
even though third parties are not compelled to copy the source along with
the object code.
4. You may not copy, modify, sublicense, or distribute the Program except
as expressly provided under this License. Any attempt otherwise to copy, modify,
sublicense or distribute the Program is void, and will automatically terminate
your rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses terminated
so long as such parties remain in full compliance.
5. You are not required to accept this License, since you have not signed
it. However, nothing else grants you permission to modify or distribute the
Program or its derivative works. These actions are prohibited by law if you
do not accept this License. Therefore, by modifying or distributing the Program
(or any work based on the Program), you indicate your acceptance of this License
to do so, and all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the Program),
the recipient automatically receives a license from the original licensor
to copy, distribute or modify the Program subject to these terms and conditions.
You may not impose any further restrictions on the recipients' exercise of
the rights granted herein. You are not responsible for enforcing compliance
by third parties to this License.
7. If, as a consequence of a court judgment or allegation of patent infringement
or for any other reason (not limited to patent issues), conditions are imposed
on you (whether by court order, agreement or otherwise) that contradict the
conditions of this License, they do not excuse you from the conditions of
this License. If you cannot distribute so as to satisfy simultaneously your
obligations under this License and any other pertinent obligations, then as
a consequence you may not distribute the Program at all. For example, if a
patent license would not permit royalty-free redistribution of the Program
by all those who receive copies directly or indirectly through you, then the
only way you could satisfy both it and this License would be to refrain entirely
from distribution of the Program.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply and
the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any patents
or other property right claims or to contest validity of any such claims;
this section has the sole purpose of protecting the integrity of the free
software distribution system, which is implemented by public license practices.
Many people have made generous contributions to the wide range of software
distributed through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing to
distribute software through any other system and a licensee cannot impose
that choice.
This section is intended to make thoroughly clear what is believed to be a
consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in certain
countries either by patents or by copyrighted interfaces, the original copyright
holder who places the Program under this License may add an explicit geographical
distribution limitation excluding those countries, so that distribution is
permitted only in or among countries not thus excluded. In such case, this
License incorporates the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions of
the General Public License from time to time. Such new versions will be similar
in spirit to the present version, but may differ in detail to address new
problems or concerns.
Each version is given a distinguishing version number. If the Program specifies
a version number of this License which applies to it and "any later version",
you have the option of following the terms and conditions either of that version
or of any later version published by the Free Software Foundation. If the
Program does not specify a version number of this License, you may choose
any version ever published by the Free Software Foundation.
10. If you wish to incorporate parts of the Program into other free programs
whose distribution conditions are different, write to the author to ask for
permission. For software which is copyrighted by the Free Software Foundation,
write to the Free Software Foundation; we sometimes make exceptions for this.
Our decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing and reuse
of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES
OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH
HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible
use to the public, the best way to achieve this is to make it free software
which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach
them to the start of each source file to most effectively convey the exclusion
of warranty; and each file should have at least the "copyright" line and a
pointer to where the full notice is found.
one line to give the program's name and an idea of what it does. Copyright
(C) yyyy name of author
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
Street, Fifth Floor, Boston, MA 02110-1301, USA. Also add information on how
to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this when
it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author Gnomovision comes
with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software,
and you are welcome to redistribute it under certain conditions; type `show
c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may be
called something other than `show w' and `show c'; they could even be mouse-clicks
or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your school,
if any, to sign a "copyright disclaimer" for the program, if necessary. Here
is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision'
(which makes passes at compilers) written by James Hacker.
signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice

View File

@ -0,0 +1,311 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
Everyone is permitted to copy and distribute verbatim copies of this license
document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your freedom to share
and change it. By contrast, the GNU General Public License is intended to
guarantee your freedom to share and change free software--to make sure the
software is free for all its users. This General Public License applies to
most of the Free Software Foundation's software and to any other program whose
authors commit to using it. (Some other Free Software Foundation software
is covered by the GNU Lesser General Public License instead.) You can apply
it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our
General Public Licenses are designed to make sure that you have the freedom
to distribute copies of free software (and charge for this service if you
wish), that you receive source code or can get it if you want it, that you
can change the software or use pieces of it in new free programs; and that
you know you can do these things.
To protect your rights, we need to make restrictions that forbid anyone to
deny you these rights or to ask you to surrender the rights. These restrictions
translate to certain responsibilities for you if you distribute copies of
the software, or if you modify it.
For example, if you distribute copies of such a program, whether gratis or
for a fee, you must give the recipients all the rights that you have. You
must make sure that they, too, receive or can get the source code. And you
must show them these terms so they know their rights.
We protect your rights with two steps: (1) copyright the software, and (2)
offer you this license which gives you legal permission to copy, distribute
and/or modify the software.
Also, for each author's protection and ours, we want to make certain that
everyone understands that there is no warranty for this free software. If
the software is modified by someone else and passed on, we want its recipients
to know that what they have is not the original, so that any problems introduced
by others will not reflect on the original authors' reputations.
Finally, any free program is threatened constantly by software patents. We
wish to avoid the danger that redistributors of a free program will individually
obtain patent licenses, in effect making the program proprietary. To prevent
this, we have made it clear that any patent must be licensed for everyone's
free use or not licensed at all.
The precise terms and conditions for copying, distribution and modification
follow.
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains a notice
placed by the copyright holder saying it may be distributed under the terms
of this General Public License. The "Program", below, refers to any such program
or work, and a "work based on the Program" means either the Program or any
derivative work under copyright law: that is to say, a work containing the
Program or a portion of it, either verbatim or with modifications and/or translated
into another language. (Hereinafter, translation is included without limitation
in the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not covered
by this License; they are outside its scope. The act of running the Program
is not restricted, and the output from the Program is covered only if its
contents constitute a work based on the Program (independent of having been
made by running the Program). Whether that is true depends on what the Program
does.
1. You may copy and distribute verbatim copies of the Program's source code
as you receive it, in any medium, provided that you conspicuously and appropriately
publish on each copy an appropriate copyright notice and disclaimer of warranty;
keep intact all the notices that refer to this License and to the absence
of any warranty; and give any other recipients of the Program a copy of this
License along with the Program.
You may charge a fee for the physical act of transferring a copy, and you
may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion of it,
thus forming a work based on the Program, and copy and distribute such modifications
or work under the terms of Section 1 above, provided that you also meet all
of these conditions:
a) You must cause the modified files to carry prominent notices stating that
you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in whole or
in part contains or is derived from the Program or any part thereof, to be
licensed as a whole at no charge to all third parties under the terms of this
License.
c) If the modified program normally reads commands interactively when run,
you must cause it, when started running for such interactive use in the most
ordinary way, to print or display an announcement including an appropriate
copyright notice and a notice that there is no warranty (or else, saying that
you provide a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this License.
(Exception: if the Program itself is interactive but does not normally print
such an announcement, your work based on the Program is not required to print
an announcement.)
These requirements apply to the modified work as a whole. If identifiable
sections of that work are not derived from the Program, and can be reasonably
considered independent and separate works in themselves, then this License,
and its terms, do not apply to those sections when you distribute them as
separate works. But when you distribute the same sections as part of a whole
which is a work based on the Program, the distribution of the whole must be
on the terms of this License, whose permissions for other licensees extend
to the entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest your
rights to work written entirely by you; rather, the intent is to exercise
the right to control the distribution of derivative or collective works based
on the Program.
In addition, mere aggregation of another work not based on the Program with
the Program (or with a work based on the Program) on a volume of a storage
or distribution medium does not bring the other work under the scope of this
License.
3. You may copy and distribute the Program (or a work based on it, under Section
2) in object code or executable form under the terms of Sections 1 and 2 above
provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable source code,
which must be distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three years, to give
any third party, for a charge no more than your cost of physically performing
source distribution, a complete machine-readable copy of the corresponding
source code, to be distributed under the terms of Sections 1 and 2 above on
a medium customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer to distribute
corresponding source code. (This alternative is allowed only for noncommercial
distribution and only if you received the program in object code or executable
form with such an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for making
modifications to it. For an executable work, complete source code means all
the source code for all modules it contains, plus any associated interface
definition files, plus the scripts used to control compilation and installation
of the executable. However, as a special exception, the source code distributed
need not include anything that is normally distributed (in either source or
binary form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component itself
accompanies the executable.
If distribution of executable or object code is made by offering access to
copy from a designated place, then offering equivalent access to copy the
source code from the same place counts as distribution of the source code,
even though third parties are not compelled to copy the source along with
the object code.
4. You may not copy, modify, sublicense, or distribute the Program except
as expressly provided under this License. Any attempt otherwise to copy, modify,
sublicense or distribute the Program is void, and will automatically terminate
your rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses terminated
so long as such parties remain in full compliance.
5. You are not required to accept this License, since you have not signed
it. However, nothing else grants you permission to modify or distribute the
Program or its derivative works. These actions are prohibited by law if you
do not accept this License. Therefore, by modifying or distributing the Program
(or any work based on the Program), you indicate your acceptance of this License
to do so, and all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the Program),
the recipient automatically receives a license from the original licensor
to copy, distribute or modify the Program subject to these terms and conditions.
You may not impose any further restrictions on the recipients' exercise of
the rights granted herein. You are not responsible for enforcing compliance
by third parties to this License.
7. If, as a consequence of a court judgment or allegation of patent infringement
or for any other reason (not limited to patent issues), conditions are imposed
on you (whether by court order, agreement or otherwise) that contradict the
conditions of this License, they do not excuse you from the conditions of
this License. If you cannot distribute so as to satisfy simultaneously your
obligations under this License and any other pertinent obligations, then as
a consequence you may not distribute the Program at all. For example, if a
patent license would not permit royalty-free redistribution of the Program
by all those who receive copies directly or indirectly through you, then the
only way you could satisfy both it and this License would be to refrain entirely
from distribution of the Program.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply and
the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any patents
or other property right claims or to contest validity of any such claims;
this section has the sole purpose of protecting the integrity of the free
software distribution system, which is implemented by public license practices.
Many people have made generous contributions to the wide range of software
distributed through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing to
distribute software through any other system and a licensee cannot impose
that choice.
This section is intended to make thoroughly clear what is believed to be a
consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in certain
countries either by patents or by copyrighted interfaces, the original copyright
holder who places the Program under this License may add an explicit geographical
distribution limitation excluding those countries, so that distribution is
permitted only in or among countries not thus excluded. In such case, this
License incorporates the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions of
the General Public License from time to time. Such new versions will be similar
in spirit to the present version, but may differ in detail to address new
problems or concerns.
Each version is given a distinguishing version number. If the Program specifies
a version number of this License which applies to it and "any later version",
you have the option of following the terms and conditions either of that version
or of any later version published by the Free Software Foundation. If the
Program does not specify a version number of this License, you may choose
any version ever published by the Free Software Foundation.
10. If you wish to incorporate parts of the Program into other free programs
whose distribution conditions are different, write to the author to ask for
permission. For software which is copyrighted by the Free Software Foundation,
write to the Free Software Foundation; we sometimes make exceptions for this.
Our decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing and reuse
of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES
OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH
HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible
use to the public, the best way to achieve this is to make it free software
which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach
them to the start of each source file to most effectively convey the exclusion
of warranty; and each file should have at least the "copyright" line and a
pointer to where the full notice is found.
one line to give the program's name and an idea of what it does. Copyright
(C) yyyy name of author
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
Street, Fifth Floor, Boston, MA 02110-1301, USA. Also add information on how
to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this when
it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author Gnomovision comes
with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software,
and you are welcome to redistribute it under certain conditions; type `show
c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may be
called something other than `show w' and `show c'; they could even be mouse-clicks
or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your school,
if any, to sign a "copyright disclaimer" for the program, if necessary. Here
is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision'
(which makes passes at compilers) written by James Hacker.
signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice

604
LICENSES/GPL-3.0-only.txt Normal file
View File

@ -0,0 +1,604 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright © 2007 Free Software Foundation, Inc. <http://fsf.org/>
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.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 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 <http://www.gnu.org/licenses/>.
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:
<program> Copyright (C) <year> <name of author>
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 <http://www.gnu.org/licenses/>.
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 <http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@ -0,0 +1,462 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies of this license
document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts as
the successor of the GNU Library Public License, version 2, hence the version
number 2.1.]
Preamble
The licenses for most software are designed to take away your freedom to share
and change it. By contrast, the GNU General Public Licenses are intended to
guarantee your freedom to share and change free software--to make sure the
software is free for all its users.
This license, the Lesser General Public License, applies to some specially
designated software packages--typically libraries--of the Free Software Foundation
and other authors who decide to use it. You can use it too, but we suggest
you first think carefully about whether this license or the ordinary General
Public License is the better strategy to use in any particular case, based
on the explanations below.
When we speak of free software, we are referring to freedom of use, not price.
Our General Public Licenses are designed to make sure that you have the freedom
to distribute copies of free software (and charge for this service if you
wish); that you receive source code or can get it if you want it; that you
can change the software and use pieces of it in new free programs; and that
you are informed that you can do these things.
To protect your rights, we need to make restrictions that forbid distributors
to deny you these rights or to ask you to surrender these rights. These restrictions
translate to certain responsibilities for you if you distribute copies of
the library or if you modify it.
For example, if you distribute copies of the library, whether gratis or for
a fee, you must give the recipients all the rights that we gave you. You must
make sure that they, too, receive or can get the source code. If you link
other code with the library, you must provide complete object files to the
recipients, so that they can relink them with the library after making changes
to the library and recompiling it. And you must show them these terms so they
know their rights.
We protect your rights with a two-step method: (1) we copyright the library,
and (2) we offer you this license, which gives you legal permission to copy,
distribute and/or modify the library.
To protect each distributor, we want to make it very clear that there is no
warranty for the free library. Also, if the library is modified by someone
else and passed on, the recipients should know that what they have is not
the original version, so that the original author's reputation will not be
affected by problems that might be introduced by others.
Finally, software patents pose a constant threat to the existence of any free
program. We wish to make sure that a company cannot effectively restrict the
users of a free program by obtaining a restrictive license from a patent holder.
Therefore, we insist that any patent license obtained for a version of the
library must be consistent with the full freedom of use specified in this
license.
Most GNU software, including some libraries, is covered by the ordinary GNU
General Public License. This license, the GNU Lesser General Public License,
applies to certain designated libraries, and is quite different from the ordinary
General Public License. We use this license for certain libraries in order
to permit linking those libraries into non-free programs.
When a program is linked with a library, whether statically or using a shared
library, the combination of the two is legally speaking a combined work, a
derivative of the original library. The ordinary General Public License therefore
permits such linking only if the entire combination fits its criteria of freedom.
The Lesser General Public License permits more lax criteria for linking other
code with the library.
We call this license the "Lesser" General Public License because it does Less
to protect the user's freedom than the ordinary General Public License. It
also provides other free software developers Less of an advantage over competing
non-free programs. These disadvantages are the reason we use the ordinary
General Public License for many libraries. However, the Lesser license provides
advantages in certain special circumstances.
For example, on rare occasions, there may be a special need to encourage the
widest possible use of a certain library, so that it becomes a de-facto standard.
To achieve this, non-free programs must be allowed to use the library. A more
frequent case is that a free library does the same job as widely used non-free
libraries. In this case, there is little to gain by limiting the free library
to free software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free programs
enables a greater number of people to use a large body of free software. For
example, permission to use the GNU C Library in non-free programs enables
many more people to use the whole GNU operating system, as well as its variant,
the GNU/Linux operating system.
Although the Lesser General Public License is Less protective of the users'
freedom, it does ensure that the user of a program that is linked with the
Library has the freedom and the wherewithal to run that program using a modified
version of the Library.
The precise terms and conditions for copying, distribution and modification
follow. Pay close attention to the difference between a "work based on the
library" and a "work that uses the library". The former contains code derived
from the library, whereas the latter must be combined with the library in
order to run.
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other program
which contains a notice placed by the copyright holder or other authorized
party saying it may be distributed under the terms of this Lesser General
Public License (also called "this License"). Each licensee is addressed as
"you".
A "library" means a collection of software functions and/or data prepared
so as to be conveniently linked with application programs (which use some
of those functions and data) to form executables.
The "Library", below, refers to any such software library or work which has
been distributed under these terms. A "work based on the Library" means either
the Library or any derivative work under copyright law: that is to say, a
work containing the Library or a portion of it, either verbatim or with modifications
and/or translated straightforwardly into another language. (Hereinafter, translation
is included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for making modifications
to it. For a library, complete source code means all the source code for all
modules it contains, plus any associated interface definition files, plus
the scripts used to control compilation and installation of the library.
Activities other than copying, distribution and modification are not covered
by this License; they are outside its scope. The act of running a program
using the Library is not restricted, and output from such a program is covered
only if its contents constitute a work based on the Library (independent of
the use of the Library in a tool for writing it). Whether that is true depends
on what the Library does and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's complete source
code as you receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice and disclaimer
of warranty; keep intact all the notices that refer to this License and to
the absence of any warranty; and distribute a copy of this License along with
the Library.
You may charge a fee for the physical act of transferring a copy, and you
may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Library or any portion of it,
thus forming a work based on the Library, and copy and distribute such modifications
or work under the terms of Section 1 above, provided that you also meet all
of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices stating that
you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no charge to all
third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a table of
data to be supplied by an application program that uses the facility, other
than as an argument passed when the facility is invoked, then you must make
a good faith effort to ensure that, in the event an application does not supply
such function or table, the facility still operates, and performs whatever
part of its purpose remains meaningful.
(For example, a function in a library to compute square roots has a purpose
that is entirely well-defined independent of the application. Therefore, Subsection
2d requires that any application-supplied function or table used by this function
must be optional: if the application does not supply it, the square root function
must still compute square roots.)
These requirements apply to the modified work as a whole. If identifiable
sections of that work are not derived from the Library, and can be reasonably
considered independent and separate works in themselves, then this License,
and its terms, do not apply to those sections when you distribute them as
separate works. But when you distribute the same sections as part of a whole
which is a work based on the Library, the distribution of the whole must be
on the terms of this License, whose permissions for other licensees extend
to the entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest your
rights to work written entirely by you; rather, the intent is to exercise
the right to control the distribution of derivative or collective works based
on the Library.
In addition, mere aggregation of another work not based on the Library with
the Library (or with a work based on the Library) on a volume of a storage
or distribution medium does not bring the other work under the scope of this
License.
3. You may opt to apply the terms of the ordinary GNU General Public License
instead of this License to a given copy of the Library. To do this, you must
alter all the notices that refer to this License, so that they refer to the
ordinary GNU General Public License, version 2, instead of to this License.
(If a newer version than version 2 of the ordinary GNU General Public License
has appeared, then you can specify that version instead if you wish.) Do not
make any other change in these notices.
Once this change is made in a given copy, it is irreversible for that copy,
so the ordinary GNU General Public License applies to all subsequent copies
and derivative works made from that copy.
This option is useful when you wish to copy part of the code of the Library
into a program that is not a library.
4. You may copy and distribute the Library (or a portion or derivative of
it, under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you accompany it with the complete corresponding
machine-readable source code, which must be distributed under the terms of
Sections 1 and 2 above on a medium customarily used for software interchange.
If distribution of object code is made by offering access to copy from a designated
place, then offering equivalent access to copy the source code from the same
place satisfies the requirement to distribute the source code, even though
third parties are not compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the Library, but
is designed to work with the Library by being compiled or linked with it,
is called a "work that uses the Library". Such a work, in isolation, is not
a derivative work of the Library, and therefore falls outside the scope of
this License.
However, linking a "work that uses the Library" with the Library creates an
executable that is a derivative of the Library (because it contains portions
of the Library), rather than a "work that uses the library". The executable
is therefore covered by this License. Section 6 states terms for distribution
of such executables.
When a "work that uses the Library" uses material from a header file that
is part of the Library, the object code for the work may be a derivative work
of the Library even though the source code is not. Whether this is true is
especially significant if the work can be linked without the Library, or if
the work is itself a library. The threshold for this to be true is not precisely
defined by law.
If such an object file uses only numerical parameters, data structure layouts
and accessors, and small macros and small inline functions (ten lines or less
in length), then the use of the object file is unrestricted, regardless of
whether it is legally a derivative work. (Executables containing this object
code plus portions of the Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may distribute
the object code for the work under the terms of Section 6. Any executables
containing that work also fall under Section 6, whether or not they are linked
directly with the Library itself.
6. As an exception to the Sections above, you may also combine or link a "work
that uses the Library" with the Library to produce a work containing portions
of the Library, and distribute that work under terms of your choice, provided
that the terms permit modification of the work for the customer's own use
and reverse engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the Library
is used in it and that the Library and its use are covered by this License.
You must supply a copy of this License. If the work during execution displays
copyright notices, you must include the copyright notice for the Library among
them, as well as a reference directing the user to the copy of this License.
Also, you must do one of these things:
a) Accompany the work with the complete corresponding machine-readable source
code for the Library including whatever changes were used in the work (which
must be distributed under Sections 1 and 2 above); and, if the work is an
executable linked with the Library, with the complete machine-readable "work
that uses the Library", as object code and/or source code, so that the user
can modify the Library and then relink to produce a modified executable containing
the modified Library. (It is understood that the user who changes the contents
of definitions files in the Library will not necessarily be able to recompile
the application to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the Library. A
suitable mechanism is one that (1) uses at run time a copy of the library
already present on the user's computer system, rather than copying library
functions into the executable, and (2) will operate properly with a modified
version of the library, if the user installs one, as long as the modified
version is interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at least three years,
to give the same user the materials specified in Subsection 6a, above, for
a charge no more than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy from a designated
place, offer equivalent access to copy the above specified materials from
the same place.
e) Verify that the user has already received a copy of these materials or
that you have already sent this user a copy.
For an executable, the required form of the "work that uses the Library" must
include any data and utility programs needed for reproducing the executable
from it. However, as a special exception, the materials to be distributed
need not include anything that is normally distributed (in either source or
binary form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component itself
accompanies the executable.
It may happen that this requirement contradicts the license restrictions of
other proprietary libraries that do not normally accompany the operating system.
Such a contradiction means you cannot use both them and the Library together
in an executable that you distribute.
7. You may place library facilities that are a work based on the Library side-by-side
in a single library together with other library facilities not covered by
this License, and distribute such a combined library, provided that the separate
distribution of the work based on the Library and of the other library facilities
is otherwise permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work based on the
Library, uncombined with any other library facilities. This must be distributed
under the terms of the Sections above.
b) Give prominent notice with the combined library of the fact that part of
it is a work based on the Library, and explaining where to find the accompanying
uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute the Library
except as expressly provided under this License. Any attempt otherwise to
copy, modify, sublicense, link with, or distribute the Library is void, and
will automatically terminate your rights under this License. However, parties
who have received copies, or rights, from you under this License will not
have their licenses terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not signed
it. However, nothing else grants you permission to modify or distribute the
Library or its derivative works. These actions are prohibited by law if you
do not accept this License. Therefore, by modifying or distributing the Library
(or any work based on the Library), you indicate your acceptance of this License
to do so, and all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the Library),
the recipient automatically receives a license from the original licensor
to copy, distribute, link with or modify the Library subject to these terms
and conditions. You may not impose any further restrictions on the recipients'
exercise of the rights granted herein. You are not responsible for enforcing
compliance by third parties with this License.
11. If, as a consequence of a court judgment or allegation of patent infringement
or for any other reason (not limited to patent issues), conditions are imposed
on you (whether by court order, agreement or otherwise) that contradict the
conditions of this License, they do not excuse you from the conditions of
this License. If you cannot distribute so as to satisfy simultaneously your
obligations under this License and any other pertinent obligations, then as
a consequence you may not distribute the Library at all. For example, if a
patent license would not permit royalty-free redistribution of the Library
by all those who receive copies directly or indirectly through you, then the
only way you could satisfy both it and this License would be to refrain entirely
from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any patents
or other property right claims or to contest validity of any such claims;
this section has the sole purpose of protecting the integrity of the free
software distribution system which is implemented by public license practices.
Many people have made generous contributions to the wide range of software
distributed through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing to
distribute software through any other system and a licensee cannot impose
that choice.
This section is intended to make thoroughly clear what is believed to be a
consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in certain
countries either by patents or by copyrighted interfaces, the original copyright
holder who places the Library under this License may add an explicit geographical
distribution limitation excluding those countries, so that distribution is
permitted only in or among countries not thus excluded. In such case, this
License incorporates the limitation as if written in the body of this License.
13. The Free Software Foundation may publish revised and/or new versions of
the Lesser General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to address
new problems or concerns.
Each version is given a distinguishing version number. If the Library specifies
a version number of this License which applies to it and "any later version",
you have the option of following the terms and conditions either of that version
or of any later version published by the Free Software Foundation. If the
Library does not specify a license version number, you may choose any version
ever published by the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free programs
whose distribution conditions are incompatible with these, write to the author
to ask for permission. For software which is copyrighted by the Free Software
Foundation, write to the Free Software Foundation; we sometimes make exceptions
for this. Our decision will be guided by the two goals of preserving the free
status of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY
"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES
OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH
HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest possible
use to the public, we recommend making it free software that everyone can
redistribute and change. You can do so by permitting redistribution under
these terms (or, alternatively, under the terms of the ordinary General Public
License).
To apply these terms, attach the following notices to the library. It is safest
to attach them to the start of each source file to most effectively convey
the exclusion of warranty; and each file should have at least the "copyright"
line and a pointer to where the full notice is found.
one line to give the library's name and an idea of what it does.
Copyright (C) year name of author
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free
Software Foundation; either version 2.1 of the License, or (at your option)
any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
You should have received a copy of the GNU Lesser General Public License along
with this library; if not, write to the Free Software Foundation, Inc., 51
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information
on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your school,
if any, to sign a "copyright disclaimer" for the library, if necessary. Here
is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in
the library `Frob' (a library for tweaking knobs) written
by James Random Hacker.
signature of Ty Coon, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

View File

@ -0,0 +1,12 @@
This library 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) at any later version that is
accepted by the membership of KDE e.V. (or its successor
approved by the membership of KDE e.V.), which shall act as a
proxy as defined in Section 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.

14
README.md Normal file
View File

@ -0,0 +1,14 @@
# libkscreen
libkscreen is the screen management library for KDE Plasma Workspaces. Its primary consumer is the KDE screen management application KScreen.
libkscreen is part of [Plasma releases][plasma-releases].
## End user
Since this is a development library end users should instead look for support directly for the apps using this library. Please contact the support channels of your Linux distribution first. In case you find a bug in KScreen or if the bug is traced back to libkscreen, you can report it at the KDE [bug tracker][bug-tracker] (first look for duplicates).
## Contributing
See the `CONTRIBUTING.md` file.
[plasma-releases]: https://community.kde.org/Schedules/Plasma_5
[bug-tracker]: https://bugs.kde.org/describecomponents.cgi?product=KScreen

49
autotests/CMakeLists.txt Normal file
View File

@ -0,0 +1,49 @@
add_definitions(-DTEST_DATA="${CMAKE_CURRENT_SOURCE_DIR}/configs/")
include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/tests/kwayland/)
macro(KSCREEN_ADD_TEST)
foreach(_testname ${ARGN})
set(test_SRCS ${_testname}.cpp ${KSCREEN_WAYLAND_SRCS})
qt_add_dbus_interface(test_SRCS ${CMAKE_SOURCE_DIR}/interfaces/org.kde.KScreen.FakeBackend.xml fakebackendinterface)
add_executable(${_testname} ${test_SRCS})
target_link_libraries(${_testname} Qt::Core Qt::Gui Qt::Test Qt::DBus KF5::Screen ${KSCREEN_WAYLAND_LIBS})
add_test(NAME kscreen-${_testname}
COMMAND dbus-launch $<TARGET_FILE:${_testname}>
)
ecm_mark_as_test(${_testname})
endforeach(_testname)
endmacro(KSCREEN_ADD_TEST)
kscreen_add_test(testscreenconfig)
kscreen_add_test(testqscreenbackend)
kscreen_add_test(testconfigserializer)
kscreen_add_test(testconfigmonitor)
kscreen_add_test(testinprocess)
kscreen_add_test(testbackendloader)
kscreen_add_test(testlog)
kscreen_add_test(testmodelistchange)
kscreen_add_test(testedid)
set(KSCREEN_WAYLAND_LIBS
KF5::WaylandServer KF5::WaylandClient
)
# For WaylandConfigReader and TestServer
set(KSCREEN_WAYLAND_SRCS
${CMAKE_SOURCE_DIR}/tests/kwayland/waylandconfigreader.cpp
${CMAKE_SOURCE_DIR}/tests/kwayland/waylandtestserver.cpp
)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../backends/kwayland)
kscreen_add_test(testkwaylandbackend)
kscreen_add_test(testkwaylandconfig)
kscreen_add_test(testkwaylanddpms)
set(KSCREEN_WAYLAND_LIBS "")
set(KSCREEN_WAYLAND_SRCS "")
if (ENABLE_XRANDR_TESTS)
kscreen_add_test(textxrandr)
endif()

View File

@ -0,0 +1,673 @@
{
"outputs": [
{
"clones": [
],
"connected": true,
"currentModeId": "73",
"enabled": false,
"manufacturer": "Butterfly Visuals",
"model": "smaragd",
"icon": "",
"id": 66,
"modes": [
{
"id": "73",
"name": "1920x1080",
"refreshRate": 60.06005859375,
"size": {
"height": 1080,
"width": 1920
}
},
{
"id": "74",
"name": "1400x1050",
"refreshRate": 59.975616455078125,
"size": {
"height": 1050,
"width": 1400
}
},
{
"id": "75",
"name": "1280x1024",
"refreshRate": 60.019741058349609,
"size": {
"height": 1024,
"width": 1280
}
},
{
"id": "76",
"name": "1280x960",
"refreshRate": 60,
"size": {
"height": 960,
"width": 1280
}
},
{
"id": "77",
"name": "1024x768",
"refreshRate": 60.003841400146484,
"size": {
"height": 768,
"width": 1024
}
},
{
"id": "78",
"name": "800x600",
"refreshRate": 60.316539764404297,
"size": {
"height": 600,
"width": 800
}
},
{
"id": "79",
"name": "800x600",
"refreshRate": 56.25,
"size": {
"height": 600,
"width": 800
}
},
{
"id": "80",
"name": "640x480",
"refreshRate": 59.940475463867188,
"size": {
"height": 480,
"width": 640
}
}
],
"name": "eDP1",
"pos": {
"x": 0,
"y": 0
},
"preferredModes": [
"73"
],
"primary": false,
"rotation": 1,
"sizeMM": {
"height": 193,
"width": 344
},
"type": 7
},
{
"manufacturer": "Octopus Graphics",
"model": "rubyled",
"clones": [
],
"connected": true,
"currentModeId": "869",
"enabled": true,
"icon": "",
"id": 67,
"modes": [
{
"id": "75",
"name": "1280x1024",
"refreshRate": 60.019741058349609,
"size": {
"height": 1024,
"width": 1280
}
},
{
"id": "76",
"name": "1280x960",
"refreshRate": 60,
"size": {
"height": 960,
"width": 1280
}
},
{
"id": "77",
"name": "1024x768",
"refreshRate": 60.003841400146484,
"size": {
"height": 768,
"width": 1024
}
},
{
"id": "78",
"name": "800x600",
"refreshRate": 60.316539764404297,
"size": {
"height": 600,
"width": 800
}
},
{
"id": "79",
"name": "800x600",
"refreshRate": 56.25,
"size": {
"height": 600,
"width": 800
}
},
{
"id": "854",
"name": "1280x1024",
"refreshRate": 75.024673461914062,
"size": {
"height": 1024,
"width": 1280
}
},
{
"id": "855",
"name": "1440x900",
"refreshRate": 59.887443542480469,
"size": {
"height": 900,
"width": 1440
}
},
{
"id": "856",
"name": "1280x800",
"refreshRate": 59.810325622558594,
"size": {
"height": 800,
"width": 1280
}
},
{
"id": "857",
"name": "1152x864",
"refreshRate": 75,
"size": {
"height": 864,
"width": 1152
}
},
{
"id": "859",
"name": "1024x768",
"refreshRate": 75.076217651367188,
"size": {
"height": 768,
"width": 1024
}
},
{
"id": "860",
"name": "1024x768",
"refreshRate": 70.069358825683594,
"size": {
"height": 768,
"width": 1024
}
},
{
"id": "861",
"name": "832x624",
"refreshRate": 74.55126953125,
"size": {
"height": 624,
"width": 832
}
},
{
"id": "862",
"name": "800x600",
"refreshRate": 72.187568664550781,
"size": {
"height": 600,
"width": 800
}
},
{
"id": "863",
"name": "800x600",
"refreshRate": 75,
"size": {
"height": 600,
"width": 800
}
},
{
"id": "864",
"name": "640x480",
"refreshRate": 75,
"size": {
"height": 480,
"width": 640
}
},
{
"id": "865",
"name": "640x480",
"refreshRate": 72.808799743652344,
"size": {
"height": 480,
"width": 640
}
},
{
"id": "866",
"name": "640x480",
"refreshRate": 66.666664123535156,
"size": {
"height": 480,
"width": 640
}
},
{
"id": "867",
"name": "640x480",
"refreshRate": 60,
"size": {
"height": 480,
"width": 640
}
},
{
"id": "868",
"name": "720x400",
"refreshRate": 70.0816650390625,
"size": {
"height": 400,
"width": 720
}
},
{
"id": "869",
"name": "1680x1050",
"refreshRate": 59.883251190185547,
"size": {
"height": 1050,
"width": 1680
}
},
{
"id": "870",
"name": "1440x900",
"refreshRate": 74.984428405761719,
"size": {
"height": 900,
"width": 1440
}
},
{
"id": "871",
"name": "1280x800",
"refreshRate": 74.93414306640625,
"size": {
"height": 800,
"width": 1280
}
}
],
"name": "VGA1",
"pos": {
"x": 1920,
"y": 0
},
"preferredModes": [
"869"
],
"primary": false,
"rotation": 1,
"sizeMM": {
"height": 296,
"width": 474
},
"type": 0
},
{
"manufacturer": "Gänseei",
"model": "Dotterfleck",
"clones": [
],
"connected": false,
"currentModeId": "",
"enabled": false,
"icon": "",
"id": 68,
"modes": [
],
"name": "DP1",
"pos": {
"x": 0,
"y": 0
},
"preferredModes": [
],
"primary": false,
"rotation": 1,
"sizeMM": {
"height": 300,
"width": 200
},
"type": 0
},
{
"manufacturer": "Anarchy Electronics",
"model": "Che",
"clones": [
],
"connected": false,
"currentModeId": "",
"enabled": false,
"icon": "",
"id": 69,
"modes": [
],
"name": "HDMI1",
"pos": {
"x": 0,
"y": 0
},
"preferredModes": [
],
"primary": false,
"rotation": 1,
"sizeMM": {
"height": 300,
"width": 200
},
"type": 0
},
{
"manufacturer": "Bonsai",
"model": "marina",
"clones": [
71
],
"connected": true,
"currentModeId": "850",
"enabled": true,
"icon": "",
"id": 70,
"modes": [
{
"id": "75",
"name": "1280x1024",
"refreshRate": 60.019741058349609,
"size": {
"height": 1024,
"width": 1280
}
},
{
"id": "76",
"name": "1280x960",
"refreshRate": 60,
"size": {
"height": 960,
"width": 1280
}
},
{
"id": "77",
"name": "1024x768",
"refreshRate": 60.003841400146484,
"size": {
"height": 768,
"width": 1024
}
},
{
"id": "78",
"name": "800x600",
"refreshRate": 60.316539764404297,
"size": {
"height": 600,
"width": 800
}
},
{
"id": "79",
"name": "800x600",
"refreshRate": 56.25,
"size": {
"height": 600,
"width": 800
}
},
{
"id": "850",
"name": "1920x1080",
"refreshRate": 60,
"size": {
"height": 1080,
"width": 1920
}
},
{
"id": "851",
"name": "1680x1050",
"refreshRate": 59.954250335693359,
"size": {
"height": 1050,
"width": 1680
}
},
{
"id": "852",
"name": "1400x1050",
"refreshRate": 59.978443145751953,
"size": {
"height": 1050,
"width": 1400
}
},
{
"id": "853",
"name": "1600x900",
"refreshRate": 59.982475280761719,
"size": {
"height": 900,
"width": 1600
}
},
{
"id": "854",
"name": "1280x1024",
"refreshRate": 75.024673461914062,
"size": {
"height": 1024,
"width": 1280
}
},
{
"id": "855",
"name": "1440x900",
"refreshRate": 59.887443542480469,
"size": {
"height": 900,
"width": 1440
}
},
{
"id": "856",
"name": "1280x800",
"refreshRate": 59.810325622558594,
"size": {
"height": 800,
"width": 1280
}
},
{
"id": "857",
"name": "1152x864",
"refreshRate": 75,
"size": {
"height": 864,
"width": 1152
}
},
{
"id": "858",
"name": "1280x720",
"refreshRate": 59.967262268066406,
"size": {
"height": 720,
"width": 1280
}
},
{
"id": "859",
"name": "1024x768",
"refreshRate": 75.076217651367188,
"size": {
"height": 768,
"width": 1024
}
},
{
"id": "860",
"name": "1024x768",
"refreshRate": 70.069358825683594,
"size": {
"height": 768,
"width": 1024
}
},
{
"id": "861",
"name": "832x624",
"refreshRate": 74.55126953125,
"size": {
"height": 624,
"width": 832
}
},
{
"id": "862",
"name": "800x600",
"refreshRate": 72.187568664550781,
"size": {
"height": 600,
"width": 800
}
},
{
"id": "863",
"name": "800x600",
"refreshRate": 75,
"size": {
"height": 600,
"width": 800
}
},
{
"id": "864",
"name": "640x480",
"refreshRate": 75,
"size": {
"height": 480,
"width": 640
}
},
{
"id": "865",
"name": "640x480",
"refreshRate": 72.808799743652344,
"size": {
"height": 480,
"width": 640
}
},
{
"id": "866",
"name": "640x480",
"refreshRate": 66.666664123535156,
"size": {
"height": 480,
"width": 640
}
},
{
"id": "867",
"name": "640x480",
"refreshRate": 60,
"size": {
"height": 480,
"width": 640
}
},
{
"id": "868",
"name": "720x400",
"refreshRate": 70.0816650390625,
"size": {
"height": 400,
"width": 720
}
}
],
"name": "DP2",
"pos": {
"x": 0,
"y": 0
},
"preferredModes": [
"850"
],
"primary": true,
"rotation": 1,
"sizeMM": {
"height": 287,
"width": 510
},
"type": 0
},
{
"manufacturer": "Tuna Pictures",
"model": "52-1337LED",
"clones": [
],
"connected": false,
"currentModeId": "",
"enabled": false,
"icon": "",
"id": 71,
"modes": [
],
"name": "HDMI2",
"pos": {
"x": 0,
"y": 0
},
"preferredModes": [
],
"primary": false,
"rotation": 1,
"sizeMM": {
"height": 400,
"width": 230
},
"type": 0,
"edid" : "AP///////wAQrBbwTExLQQ4WAQOANCB46h7Frk80sSYOUFSlSwCBgKlA0QBxTwEBAQEBAQEBKDyAoHCwI0AwIDYABkQhAAAaAAAA/wBGNTI1TTI0NUFLTEwKAAAA/ABERUxMIFUyNDEwCiAgAAAA/QA4TB5REQAKICAgICAgAToCAynxUJAFBAMCBxYBHxITFCAVEQYjCQcHZwMMABAAOC2DAQAA4wUDAQI6gBhxOC1AWCxFAAZEIQAAHgEdgBhxHBYgWCwlAAZEIQAAngEdAHJR0B4gbihVAAZEIQAAHowK0Iog4C0QED6WAAZEIQAAGAAAAAAAAAAAAAAAAAAAPg=="
}
],
"screen": {
"currentSize": {
"height": 1080,
"width": 3600
},
"id": 24,
"maxActiveOutputsCount": 3,
"maxSize": {
"height": 8192,
"width": 8192
},
"minSize": {
"height": 200,
"width": 320
}
}
}

View File

@ -0,0 +1,124 @@
{
"screen" :
{
"id" : 1,
"maxSize" : {
"width" : 8192,
"height" : 8192
},
"minSize" : {
"width" : 320,
"height" : 200
},
"currentSize" : {
"width" : 1024,
"height" : 768
},
"maxActiveOutputsCount": 2
},
"outputs" :
[
{
"id" : 1,
"name" : "LVDS1",
"type" : "LVDS",
"modes" :
[
{
"id" : 3,
"name" : "1280x800",
"refreshRate" : 59.9,
"size" : {
"width" : 1280,
"height" : 800
}
},
{
"id" : 2,
"name" : "1024x768",
"refreshRate" : 59.9,
"size" : {
"width" : 1024,
"height" : 768
}
},
{
"id" : 1,
"name" : "800x600",
"refreshRate" : 60,
"size" : {
"width" : 800,
"height" : 600
}
}
],
"pos" : {
"x" : 0,
"y" : 0
},
"clones" : [2],
"currentModeId" : 2,
"preferredModes" : [2],
"rotation" : 1,
"connected" : true,
"enabled" : true,
"primary" : true,
"edid" : "AP///////wBMLcMFMzJGRQkUAQMOMx14Ku6Ro1RMmSYPUFQjCACBAIFAgYCVAKlAswABAQEBAjqAGHE4LUBYLEUA/h8RAAAeAAAA/QA4PB5REQAKICAgICAgAAAA/ABTeW5jTWFzdGVyCiAgAAAA/wBIOU1aMzAyMTk2CiAgAC4="
},
{
"id" : 2,
"name" : "HDMI1",
"type" : "HDMI",
"modes" :
[
{
"id" : 4,
"name" : "1920x1080",
"refreshRate" : 60,
"size" : {
"width" : 1920,
"height" : 1080
}
},
{
"id" : 3,
"name" : "1600x1200",
"refreshRate" : 60,
"size" : {
"width" : 1600,
"height" : 1200
}
},
{
"id" : 2,
"name" : "1024x768",
"refreshRate" : 59.9,
"size" : {
"width" : 1024,
"height" : 768
}
},
{
"id" : 1,
"name" : "800x600",
"refreshRate" : 59.9,
"size" : {
"width" : 800,
"height" : 600
}
}
],
"pos" : {
"x" : 1280,
"y" : 0
},
"currentModeId" : 2,
"preferredModes" : [2],
"rotation" : 1,
"connected" : true,
"enabled" : true,
"primary" : false,
"edid" : "AP///////wAQrBbwTExLQQ4WAQOANCB46h7Frk80sSYOUFSlSwCBgKlA0QBxTwEBAQEBAQEBKDyAoHCwI0AwIDYABkQhAAAaAAAA/wBGNTI1TTI0NUFLTEwKAAAA/ABERUxMIFUyNDEwCiAgAAAA/QA4TB5REQAKICAgICAgAToCAynxUJAFBAMCBxYBHxITFCAVEQYjCQcHZwMMABAAOC2DAQAA4wUDAQI6gBhxOC1AWCxFAAZEIQAAHgEdgBhxHBYgWCwlAAZEIQAAngEdAHJR0B4gbihVAAZEIQAAHowK0Iog4C0QED6WAAZEIQAAGAAAAAAAAAAAAAAAAAAAPg=="
}
]
}

View File

@ -0,0 +1,132 @@
{
"screen" :
{
"id" : 1,
"maxSize" : {
"width" : 8192,
"height" : 8192
},
"minSize" : {
"width" : 320,
"height" : 200
},
"currentSize" : {
"width" : 3200,
"height" : 1880
},
"maxActiveOutputsCount": 2
},
"outputs" :
[
{
"id" : 1,
"name" : "LVDS1",
"type" : "LVDS",
"modes" :
[
{
"id" : 3,
"name" : "1280x800",
"refreshRate" : 59.9,
"size" : {
"width" : 1280,
"height" : 800
}
},
{
"id" : 2,
"name" : "1024x768",
"refreshRate" : 59.9,
"size" : {
"width" : 1024,
"height" : 768
}
},
{
"id" : 1,
"name" : "800x600",
"refreshRate" : 60,
"size" : {
"width" : 800,
"height" : 600
}
}
],
"pos" : {
"x" : 0,
"y" : 0
},
"size" : {
"width" : 1280,
"height" : 800
},
"currentModeId" : 3,
"preferredModes" : [2],
"rotation" : 1,
"connected" : true,
"enabled" : true,
"primary" : true,
"edid" : "AP///////wBMLcMFMzJGRQkUAQMOMx14Ku6Ro1RMmSYPUFQjCACBAIFAgYCVAKlAswABAQEBAjqAGHE4LUBYLEUA/h8RAAAeAAAA/QA4PB5REQAKICAgICAgAAAA/ABTeW5jTWFzdGVyCiAgAAAA/wBIOU1aMzAyMTk2CiAgAC4="
},
{
"id" : 2,
"name" : "HDMI1",
"type" : "HDMI",
"modes" :
[
{
"id" : 4,
"name" : "1920x1080",
"refreshRate" : 60,
"size" : {
"width" : 1920,
"height" : 1080
}
},
{
"id" : 3,
"name" : "1600x1200",
"refreshRate" : 60,
"size" : {
"width" : 1600,
"height" : 1200
}
},
{
"id" : 2,
"name" : "1024x768",
"refreshRate" : 59.9,
"size" : {
"width" : 1024,
"height" : 768
}
},
{
"id" : 1,
"name" : "800x600",
"refreshRate" : 59.9,
"size" : {
"width" : 800,
"height" : 600
}
}
],
"pos" : {
"x" : 1280,
"y" : 0
},
"size" : {
"width" : 1920,
"height" : 1080
},
"scale" : 1.4,
"currentModeId" : 4,
"preferredModes" : [4],
"rotation" : 1,
"connected" : true,
"enabled" : true,
"primary" : false,
"edid" : "AP///////wAQrBbwTExLQQ4WAQOANCB46h7Frk80sSYOUFSlSwCBgKlA0QBxTwEBAQEBAQEBKDyAoHCwI0AwIDYABkQhAAAaAAAA/wBGNTI1TTI0NUFLTEwKAAAA/ABERUxMIFUyNDEwCiAgAAAA/QA4TB5REQAKICAgICAgAToCAynxUJAFBAMCBxYBHxITFCAVEQYjCQcHZwMMABAAOC2DAQAA4wUDAQI6gBhxOC1AWCxFAAZEIQAAHgEdgBhxHBYgWCwlAAZEIQAAngEdAHJR0B4gbihVAAZEIQAAHowK0Iog4C0QED6WAAZEIQAAGAAAAAAAAAAAAAAAAAAAPg=="
}
]
}

View File

@ -0,0 +1,68 @@
{
"screen" :
{
"id" : 1,
"maxSize" : {
"width" : 8192,
"height" : 8192
},
"minSize" : {
"width" : 320,
"height" : 200
},
"currentSize" : {
"width" : 1280,
"height" : 800
},
"maxActiveOutputsCount": 2
},
"outputs" :
[
{
"id" : 1,
"name" : "LVDS1",
"type" : "LVDS",
"modes" :
[
{
"id" : 3,
"name" : "1280x800",
"refreshRate" : 59.9,
"size" : {
"width" : 1280,
"height" : 800
}
},
{
"id" : 2,
"name" : "1024x768",
"refreshRate" : 59.9,
"size" : {
"width" : 1024,
"height" : 768
}
},
{
"id" : 1,
"name" : "800x600",
"refreshRate" : 60,
"size" : {
"width" : 800,
"height" : 600
}
}
],
"pos" : {
"x" : 0,
"y" : 0
},
"currentModeId" : 3,
"preferredModes" : [],
"rotation" : 1,
"connected" : true,
"enabled" : true,
"primary" : true,
"edid" : "AP///////wBMLcMFMzJGRQkUAQMOMx14Ku6Ro1RMmSYPUFQjCACBAIFAgYCVAKlAswABAQEBAjqAGHE4LUBYLEUA/h8RAAAeAAAA/QA4PB5REQAKICAgICAgAAAA/ABTeW5jTWFzdGVyCiAgAAAA/wBIOU1aMzAyMTk2CiAgAC4="
}
]
}

View File

@ -0,0 +1,72 @@
{
"screen" :
{
"id" : 1,
"maxSize" : {
"width" : 8192,
"height" : 8192
},
"minSize" : {
"width" : 320,
"height" : 200
},
"currentSize" : {
"width" : 1280,
"height" : 800
},
"maxActiveOutputsCount": 2
},
"outputs" :
[
{
"id" : 1,
"name" : "LVDS1",
"type" : "LVDS",
"modes" :
[
{
"id" : 3,
"name" : "1280x800",
"refreshRate" : 59.9,
"size" : {
"width" : 1280,
"height" : 800
}
},
{
"id" : 2,
"name" : "1024x768",
"refreshRate" : 59.9,
"size" : {
"width" : 1024,
"height" : 768
}
},
{
"id" : 1,
"name" : "800x600",
"refreshRate" : 60,
"size" : {
"width" : 800,
"height" : 600
}
}
],
"pos" : {
"x" : 0,
"y" : 0
},
"size" : {
"width" : 1280,
"height" : 800
},
"currentModeId" : 3,
"preferredModes" : [3],
"rotation" : 1,
"connected" : true,
"enabled" : true,
"primary" : true,
"edid" : "AP///////wBMLcMFMzJGRQkUAQMOMx14Ku6Ro1RMmSYPUFQjCACBAIFAgYCVAKlAswABAQEBAjqAGHE4LUBYLEUA/h8RAAAeAAAA/QA4PB5REQAKICAgICAgAAAA/ABTeW5jTWFzdGVyCiAgAAAA/wBIOU1aMzAyMTk2CiAgAC4="
}
]
}

View File

@ -0,0 +1,76 @@
{
"screen" :
{
"id" : 1,
"maxSize" : {
"width" : 8192,
"height" : 8192
},
"minSize" : {
"width" : 320,
"height" : 200
},
"currentSize" : {
"width" : 1280,
"height" : 800
},
"maxActiveOutputsCount": 2
},
"outputs" :
[
{
"id" : 2,
"name" : "LVDS1",
"type" : "LVDS",
"modes" :
[
{
"id" : 3,
"name" : "128000x80000",
"refreshRate" : 59.9,
"size" : {
"width" : 128000,
"height" : 80000
}
},
{
"id" : 42,
"name" : "1280x800",
"refreshRate" : 59.9,
"size" : {
"width" : 1280,
"height" : 800
}
},
{
"id" : 2,
"name" : "1024x768",
"refreshRate" : 59.9,
"size" : {
"width" : 1024,
"height" : 768
}
},
{
"id" : 1,
"name" : "800x600",
"refreshRate" : 60,
"size" : {
"width" : 800,
"height" : 600
}
}
],
"pos" : {
"x" : 0,
"y" : 0
},
"preferredModes" : [42],
"rotation" : 1,
"connected" : true,
"enabled" : true,
"primary" : true,
"edid" : "AP///////wBMLcMFMzJGRQkUAQMOMx14Ku6Ro1RMmSYPUFQjCACBAIFAgYCVAKlAswABAQEBAjqAGHE4LUBYLEUA/h8RAAAeAAAA/QA4PB5REQAKICAgICAgAAAA/ABTeW5jTWFzdGVyCiAgAAAA/wBIOU1aMzAyMTk2CiAgAC4="
}
]
}

View File

@ -0,0 +1,78 @@
{
"screen" :
{
"id" : 1,
"maxSize" : {
"width" : 8192,
"height" : 8192
},
"minSize" : {
"width" : 320,
"height" : 200
},
"currentSize" : {
"width" : 3200,
"height" : 1880
},
"maxActiveOutputsCount": 1
},
"outputs" :
[
{
"id" : 1,
"name" : "LVDS1",
"type" : "LVDS",
"modes" :
[
{
"id" : 3,
"name" : "1280x800",
"refreshRate" : 59.9,
"size" : {
"width" : 1280,
"height" : 800
}
}
],
"pos" : {
"x" : 0,
"y" : 0
},
"currentModeId" : 3,
"preferredModes" : [3],
"rotation" : 1,
"connected" : true,
"enabled" : true,
"primary" : true,
"edid" : "AP///////wBMLcMFMzJGRQkUAQMOMx14Ku6Ro1RMmSYPUFQjCACBAIFAgYCVAKlAswABAQEBAjqAGHE4LUBYLEUA/h8RAAAeAAAA/QA4PB5REQAKICAgICAgAAAA/ABTeW5jTWFzdGVyCiAgAAAA/wBIOU1aMzAyMTk2CiAgAC4="
},
{
"id" : 2,
"name" : "HDMI1",
"type" : "HDMI",
"modes" :
[
{
"id" : 4,
"name" : "1920x1080",
"refreshRate" : 60,
"size" : {
"width" : 1920,
"height" : 1080
}
}
],
"pos" : {
"x" : 1280,
"y" : 0
},
"currentModeId" : 4,
"preferredModes" : [4],
"rotation" : 1,
"connected" : true,
"enabled" : true,
"primary" : false,
"edid" : "AP///////wAQrBbwTExLQQ4WAQOANCB46h7Frk80sSYOUFSlSwCBgKlA0QBxTwEBAQEBAQEBKDyAoHCwI0AwIDYABkQhAAAaAAAA/wBGNTI1TTI0NUFLTEwKAAAA/ABERUxMIFUyNDEwCiAgAAAA/QA4TB5REQAKICAgICAgAToCAynxUJAFBAMCBxYBHxITFCAVEQYjCQcHZwMMABAAOC2DAQAA4wUDAQI6gBhxOC1AWCxFAAZEIQAAHgEdgBhxHBYgWCwlAAZEIQAAngEdAHJR0B4gbihVAAZEIQAAHowK0Iog4C0QED6WAAZEIQAAGAAAAAAAAAAAAAAAAAAAPg=="
}
]
}

View File

@ -0,0 +1,99 @@
/*
* SPDX-FileCopyrightText: 2016 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <QCoreApplication>
#include <QObject>
#include <QSignalSpy>
#include <QtTest>
#include "../src/backendmanager_p.h"
Q_LOGGING_CATEGORY(KSCREEN, "kscreen")
using namespace KScreen;
class TestBackendLoader : public QObject
{
Q_OBJECT
public:
explicit TestBackendLoader(QObject *parent = nullptr);
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void testPreferredBackend();
void testEnv();
void testEnv_data();
void testFallback();
};
TestBackendLoader::TestBackendLoader(QObject *parent)
: QObject(parent)
{
qputenv("KSCREEN_LOGGING", "false");
qputenv("KSCREEN_BACKEND_INPROCESS", QByteArray());
qputenv("KSCREEN_BACKEND", QByteArray());
}
void TestBackendLoader::initTestCase()
{
}
void TestBackendLoader::cleanupTestCase()
{
// set to original value
qputenv("KSCREEN_BACKEND", QByteArray());
}
void TestBackendLoader::testPreferredBackend()
{
auto backends = BackendManager::instance()->listBackends();
QVERIFY(!backends.isEmpty());
auto preferred = BackendManager::instance()->preferredBackend();
QVERIFY(preferred.exists());
auto fake = BackendManager::instance()->preferredBackend(QStringLiteral("Fake"));
QVERIFY(fake.fileName().startsWith(QLatin1String("KSC_Fake")));
}
void TestBackendLoader::testEnv_data()
{
QTest::addColumn<QString>("var");
QTest::addColumn<QString>("backend");
// clang-format off
QTest::newRow("all lower") << "kwayland" << "KSC_KWayland";
QTest::newRow("camel case") << "KWayland" << "KSC_KWayland";
QTest::newRow("all upper") << "KWAYLAND" << "KSC_KWayland";
QTest::newRow("mixed") << "kwAYlaND" << "KSC_KWayland";
QTest::newRow("xrandr 1.1") << "xrandr11" << "KSC_XRandR11";
QTest::newRow("qscreen") << "qscreen" << "KSC_QScreen";
QTest::newRow("mixed") << "fake" << "KSC_Fake";
// clang-format on
}
void TestBackendLoader::testEnv()
{
// We want to be pretty liberal, so this should work
QFETCH(QString, var);
QFETCH(QString, backend);
qputenv("KSCREEN_BACKEND", var.toLocal8Bit());
auto preferred = BackendManager::instance()->preferredBackend();
QVERIFY(preferred.fileName().startsWith(backend));
}
void TestBackendLoader::testFallback()
{
qputenv("KSCREEN_BACKEND", "nonsense");
auto preferred = BackendManager::instance()->preferredBackend();
QVERIFY(preferred.fileName().startsWith(QLatin1String("KSC_QScreen")));
}
QTEST_GUILESS_MAIN(TestBackendLoader)
#include "testbackendloader.moc"

View File

@ -0,0 +1,97 @@
/*
* SPDX-FileCopyrightText: 2014 Daniel Vratil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#include <QObject>
#include <QSignalSpy>
#include <QtTest>
#include "../src/backendmanager_p.h"
#include "../src/config.h"
#include "../src/configmonitor.h"
#include "../src/configoperation.h"
#include "../src/getconfigoperation.h"
#include "../src/output.h"
#include "../src/setconfigoperation.h"
#include <QSignalSpy>
#include "fakebackendinterface.h"
class TestConfigMonitor : public QObject
{
Q_OBJECT
public:
TestConfigMonitor()
{
}
KScreen::ConfigPtr getConfig()
{
auto op = new KScreen::GetConfigOperation();
if (!op->exec()) {
qWarning("Failed to retrieve backend: %s", qPrintable(op->errorString()));
return KScreen::ConfigPtr();
}
return op->config();
}
private Q_SLOTS:
void initTestCase()
{
qputenv("KSCREEN_LOGGING", "false");
qputenv("KSCREEN_BACKEND", "Fake");
// This particular test is only useful for out of process operation, so enforce that
qputenv("KSCREEN_BACKEND_INPROCESS", "0");
KScreen::BackendManager::instance()->shutdownBackend();
}
void cleanupTestCase()
{
KScreen::BackendManager::instance()->shutdownBackend();
}
void testChangeNotifyInProcess()
{
qputenv("KSCREEN_BACKEND_INPROCESS", "1");
KScreen::BackendManager::instance()->shutdownBackend();
KScreen::BackendManager::instance()->setMethod(KScreen::BackendManager::InProcess);
// json file for the fake backend
qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "singleoutput.json");
// Prepare monitor
KScreen::ConfigMonitor *monitor = KScreen::ConfigMonitor::instance();
QSignalSpy spy(monitor, SIGNAL(configurationChanged()));
// Get config and monitor it for changes
KScreen::ConfigPtr config = getConfig();
monitor->addConfig(config);
QSignalSpy enabledSpy(config->outputs().first().data(), SIGNAL(isEnabledChanged()));
auto output = config->outputs().first();
output->setEnabled(false);
auto setop = new KScreen::SetConfigOperation(config);
QVERIFY(!setop->hasError());
setop->exec();
QTRY_VERIFY(!spy.isEmpty());
QCOMPARE(spy.size(), 1);
QCOMPARE(enabledSpy.size(), 1);
QCOMPARE(config->output(1)->isEnabled(), false);
output->setEnabled(false);
auto setop2 = new KScreen::SetConfigOperation(config);
QVERIFY(!setop2->hasError());
setop2->exec();
QTRY_VERIFY(!spy.isEmpty());
QCOMPARE(spy.size(), 2);
}
};
QTEST_MAIN(TestConfigMonitor)
#include "testconfigmonitor.moc"

View File

@ -0,0 +1,178 @@
/*
* SPDX-FileCopyrightText: 2014 Daniel Vratil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#include <QObject>
#include <QtTest>
#include "../src/configserializer_p.h"
#include "../src/mode.h"
#include "../src/output.h"
#include "../src/screen.h"
#include "../src/types.h"
class TestConfigSerializer : public QObject
{
Q_OBJECT
public:
TestConfigSerializer()
{
}
private Q_SLOTS:
void testSerializePoint()
{
const QPoint point(42, 24);
const QJsonObject obj = KScreen::ConfigSerializer::serializePoint(point);
QVERIFY(!obj.isEmpty());
QCOMPARE(obj[QLatin1String("x")].toInt(), point.x());
QCOMPARE(obj[QLatin1String("y")].toInt(), point.y());
}
void testSerializeSize()
{
const QSize size(800, 600);
const QJsonObject obj = KScreen::ConfigSerializer::serializeSize(size);
QVERIFY(!obj.isEmpty());
QCOMPARE(obj[QLatin1String("width")].toInt(), size.width());
QCOMPARE(obj[QLatin1String("height")].toInt(), size.height());
}
void testSerializeList()
{
QStringList stringList;
stringList << QStringLiteral("Item 1") << QStringLiteral("Item 2") << QStringLiteral("Item 3") << QStringLiteral("Item 4");
QJsonArray arr = KScreen::ConfigSerializer::serializeList<QString>(stringList);
QCOMPARE(arr.size(), stringList.size());
for (int i = 0; i < arr.size(); ++i) {
QCOMPARE(arr.at(i).toString(), stringList.at(i));
}
QList<int> intList;
intList << 4 << 3 << 2 << 1;
arr = KScreen::ConfigSerializer::serializeList<int>(intList);
QCOMPARE(arr.size(), intList.size());
for (int i = 0; i < arr.size(); ++i) {
QCOMPARE(arr.at(i).toInt(), intList[i]);
}
}
void testSerializeScreen()
{
KScreen::ScreenPtr screen(new KScreen::Screen);
screen->setId(12);
screen->setMinSize(QSize(360, 360));
screen->setMaxSize(QSize(8192, 8192));
screen->setCurrentSize(QSize(3600, 1280));
screen->setMaxActiveOutputsCount(3);
const QJsonObject obj = KScreen::ConfigSerializer::serializeScreen(screen);
QVERIFY(!obj.isEmpty());
QCOMPARE(obj[QLatin1String("id")].toInt(), screen->id());
QCOMPARE(obj[QLatin1String("maxActiveOutputsCount")].toInt(), screen->maxActiveOutputsCount());
const QJsonObject minSize = obj[QLatin1String("minSize")].toObject();
QCOMPARE(minSize[QLatin1String("width")].toInt(), screen->minSize().width());
QCOMPARE(minSize[QLatin1String("height")].toInt(), screen->minSize().height());
const QJsonObject maxSize = obj[QLatin1String("maxSize")].toObject();
QCOMPARE(maxSize[QLatin1String("width")].toInt(), screen->maxSize().width());
QCOMPARE(maxSize[QLatin1String("height")].toInt(), screen->maxSize().height());
const QJsonObject currSize = obj[QLatin1String("currentSize")].toObject();
QCOMPARE(currSize[QLatin1String("width")].toInt(), screen->currentSize().width());
QCOMPARE(currSize[QLatin1String("height")].toInt(), screen->currentSize().height());
}
void testSerializeMode()
{
KScreen::ModePtr mode(new KScreen::Mode);
mode->setId(QStringLiteral("755"));
mode->setName(QStringLiteral("1280x1024"));
mode->setRefreshRate(50.666);
mode->setSize(QSize(1280, 1024));
const QJsonObject obj = KScreen::ConfigSerializer::serializeMode(mode);
QVERIFY(!obj.isEmpty());
QCOMPARE(obj[QLatin1String("id")].toString(), mode->id());
QCOMPARE(obj[QLatin1String("name")].toString(), mode->name());
QCOMPARE((float)obj[QLatin1String("refreshRate")].toDouble(), mode->refreshRate());
const QJsonObject size = obj[QLatin1String("size")].toObject();
QCOMPARE(size[QLatin1String("width")].toInt(), mode->size().width());
QCOMPARE(size[QLatin1String("height")].toInt(), mode->size().height());
}
void testSerializeOutput()
{
KScreen::ModeList modes;
KScreen::ModePtr mode(new KScreen::Mode);
mode->setId(QStringLiteral("1"));
mode->setName(QStringLiteral("800x600"));
mode->setSize(QSize(800, 600));
mode->setRefreshRate(50.4);
modes.insert(mode->id(), mode);
KScreen::OutputPtr output(new KScreen::Output);
output->setId(60);
output->setName(QStringLiteral("LVDS-0"));
output->setType(KScreen::Output::Panel);
output->setIcon(QString());
output->setModes(modes);
output->setPos(QPoint(1280, 0));
output->setSize(mode->size());
output->setRotation(KScreen::Output::None);
output->setCurrentModeId(QStringLiteral("1"));
output->setPreferredModes(QStringList() << QStringLiteral("1"));
output->setConnected(true);
output->setEnabled(true);
output->setPrimary(true);
output->setClones(QList<int>() << 50 << 60);
output->setSizeMm(QSize(310, 250));
const QJsonObject obj = KScreen::ConfigSerializer::serializeOutput(output);
QVERIFY(!obj.isEmpty());
QCOMPARE(obj[QLatin1String("id")].toInt(), output->id());
QCOMPARE(obj[QLatin1String("name")].toString(), output->name());
QCOMPARE(static_cast<KScreen::Output::Type>(obj[QLatin1String("type")].toInt()), output->type());
QCOMPARE(obj[QLatin1String("icon")].toString(), output->icon());
const QJsonArray arr = obj[QLatin1String("modes")].toArray();
QCOMPARE(arr.size(), output->modes().count());
QJsonObject pos = obj[QLatin1String("pos")].toObject();
QCOMPARE(pos[QLatin1String("x")].toInt(), output->pos().x());
QCOMPARE(pos[QLatin1String("y")].toInt(), output->pos().y());
const QJsonObject size = obj[QLatin1String("size")].toObject();
QCOMPARE(size[QLatin1String("width")].toInt(), output->size().width());
QCOMPARE(size[QLatin1String("height")].toInt(), output->size().height());
QCOMPARE(static_cast<KScreen::Output::Rotation>(obj[QLatin1String("rotation")].toInt()), output->rotation());
QCOMPARE(obj[QLatin1String("currentModeId")].toString(), output->currentModeId());
QCOMPARE(obj[QLatin1String("connected")].toBool(), output->isConnected());
QCOMPARE(obj[QLatin1String("enabled")].toBool(), output->isEnabled());
QCOMPARE(obj[QLatin1String("primary")].toBool(), output->isPrimary());
const QJsonArray clones = obj[QLatin1String("clones")].toArray();
QCOMPARE(clones.size(), output->clones().count());
for (int i = 0; i < clones.size(); ++i) {
QCOMPARE(clones[i].toInt(), output->clones()[i]);
}
const QJsonObject sizeMm = obj[QLatin1String("sizeMM")].toObject();
QCOMPARE(sizeMm[QLatin1String("width")].toInt(), output->sizeMm().width());
QCOMPARE(sizeMm[QLatin1String("height")].toInt(), output->sizeMm().height());
}
};
QTEST_MAIN(TestConfigSerializer)
#include "testconfigserializer.moc"

164
autotests/testedid.cpp Normal file
View File

@ -0,0 +1,164 @@
/*
* SPDX-FileCopyrightText: 2018 Frederik Gladhorn <gladhorn@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <QCoreApplication>
#include <QObject>
#include <QtTest>
#include "../src/edid.h"
using namespace KScreen;
class TestEdid : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testInvalid();
void testEdidParser_data();
void testEdidParser();
};
void TestEdid::testInvalid()
{
QScopedPointer<Edid> e(new Edid());
QCOMPARE(e->isValid(), false);
QCOMPARE(e->name(), QString());
QScopedPointer<Edid> e2(e->clone());
QCOMPARE(e2->isValid(), false);
QCOMPARE(e2->name(), QString());
QScopedPointer<Edid> e3(new Edid("some random data"));
QCOMPARE(e3->isValid(), false);
}
void TestEdid::testEdidParser_data()
{
// The raw edid data
QTest::addColumn<QByteArray>("raw_edid");
QTest::addColumn<QString>("deviceId");
QTest::addColumn<QString>("name");
QTest::addColumn<QString>("pnpId");
// List of potential vendor names, this depends on the availablility
// of pnp.ids, otherwise it will be a three letter abbreviation.
QTest::addColumn<QStringList>("vendor");
QTest::addColumn<QString>("serial");
QTest::addColumn<QString>("eisaId");
QTest::addColumn<QString>("hash");
QTest::addColumn<uint>("width");
QTest::addColumn<uint>("height");
QTest::addColumn<qreal>("gamma");
QTest::addColumn<QQuaternion>("red");
QTest::addColumn<QQuaternion>("green");
QTest::addColumn<QQuaternion>("blue");
QTest::addColumn<QQuaternion>("white");
// clang-format off
QTest::addRow("cor")
<< QByteArray::fromBase64("AP///////wAN8iw0AAAAABwVAQOAHRB4CoPVlFdSjCccUFQAAAABAQEBAQEBAQEBAQEBAQEBEhtWWlAAGTAwIDYAJaQQAAAYEhtWWlAAGTAwIDYAJaQQAAAYAAAA/gBBVU8KICAgICAgICAgAAAA/gBCMTMzWFcwMyBWNCAKAIc=")
<< QStringLiteral("xrandr-unknown")
<< QStringLiteral("")
<< QStringLiteral("COR")
<< QStringList({QStringLiteral("COR"), QStringLiteral("Corollary Inc")})
<< QStringLiteral("")
<< QStringLiteral("B133XW03 V4")
<< QStringLiteral("82266089b3f9da3a8c48de1ec81b09e1")
<< 29U << 16U << 2.2
<< QQuaternion(1, QVector3D(0.580078, 0.339844, 0))
<< QQuaternion(1, QVector3D(0.320313, 0.549805, 0))
<< QQuaternion(1, QVector3D(0.155273, 0.110352, 0))
<< QQuaternion(1, QVector3D(0.313477, 0.329102, 0));
QTest::addRow("dell")
<< QByteArray::fromBase64("AP///////wAQrBbwTExLQQ4WAQOANCB46h7Frk80sSYOUFSlSwCBgKlA0QBxTwEBAQEBAQEBKDyAoHCwI0AwIDYABkQhAAAaAAAA/wBGNTI1TTI0NUFLTEwKAAAA/ABERUxMIFUyNDEwCiAgAAAA/QA4TB5REQAKICAgICAgAToCAynxUJAFBAMCBxYBHxITFCAVEQYjCQcHZwMMABAAOC2DAQAA4wUDAQI6gBhxOC1AWCxFAAZEIQAAHgEdgBhxHBYgWCwlAAZEIQAAngEdAHJR0B4gbihVAAZEIQAAHowK0Iog4C0QED6WAAZEIQAAGAAAAAAAAAAAAAAAAAAAPg==")
<< QStringLiteral("xrandr-DELL U2410-F525M245AKLL")
<< QStringLiteral("DELL U2410")
<< QStringLiteral("DEL")
<< QStringList({QStringLiteral("DEL"), QStringLiteral("Dell Inc.")})
<< QStringLiteral("F525M245AKLL")
<< QStringLiteral("")
<< QStringLiteral("be55eeb5fcc1e775f321c1ae3aa02ef0")
<< 52U << 32U << 2.2
<< QQuaternion(1, QVector3D(0.679688, 0.308594, 0))
<< QQuaternion(1, QVector3D(0.206055, 0.693359, 0))
<< QQuaternion(1, QVector3D(0.151367, 0.0546875, 0))
<< QQuaternion(1, QVector3D(0.313477, 0.329102, 0));
QTest::addRow("samsung") << QByteArray::fromBase64("AP///////wBMLcMFMzJGRQkUAQMOMx14Ku6Ro1RMmSYPUFQjCACBAIFAgYCVAKlAswABAQEBAjqAGHE4LUBYLEUA/h8RAAAeAAAA/QA4PB5REQAKICAgICAgAAAA/ABTeW5jTWFzdGVyCiAgAAAA/wBIOU1aMzAyMTk2CiAgAC4=")
<< QStringLiteral("xrandr-SyncMaster-H9MZ302196")
<< QStringLiteral("SyncMaster")
<< QStringLiteral("SAM")
<< QStringList({QStringLiteral("SAM"), QStringLiteral("Samsung Electric Company")})
<< QStringLiteral("H9MZ302196")
<< QStringLiteral("")
<< QStringLiteral("9384061b2b87ad193f841e07d60e9e1a")
<< 51U << 29U << 2.2
<< QQuaternion(1, QVector3D(0.639648, 0.328125, 0))
<< QQuaternion(1, QVector3D(0.299805, 0.599609, 0))
<< QQuaternion(1, QVector3D(0.150391, 0.0595703, 0))
<< QQuaternion(1, QVector3D(0.3125, 0.329102, 0));
QTest::newRow("sharp")
<< QByteArray::fromBase64("AP///////wBNEEoUAAAAAB4ZAQSlHRF4Dt5Qo1RMmSYPUFQAAAABAQEBAQEBAQEBAQEBAQEBzZGAoMAINHAwIDUAJqUQAAAYpHSAoMAINHAwIDUAJqUQAAAYAAAA/gBSWE40OYFMUTEzM1oxAAAAAAACQQMoABIAAAsBCiAgAMw=")
<< QStringLiteral("xrandr-unknown")
<< QString() // unsure why, this screen reports no name
<< QStringLiteral("SHP")
<< QStringList({QStringLiteral("SHP"), QStringLiteral("Sharp Corporation")})
<< QStringLiteral("")
<< QStringLiteral("RXN49-LQ133Z1")
<< QStringLiteral("3627c3534e4c82871967b57237bf5b83")
<< 29U << 17U << 2.2
<< QQuaternion(1, QVector3D(0.639648, 0.328125, 0))
<< QQuaternion(1, QVector3D(0.299805, 0.599609, 0))
<< QQuaternion(1, QVector3D(0.149414, 0.0595703, 0))
<< QQuaternion(1, QVector3D(0.3125, 0.328125, 0));
// clang-format on
}
void TestEdid::testEdidParser()
{
QFETCH(QByteArray, raw_edid);
QFETCH(QString, deviceId);
QFETCH(QString, name);
QFETCH(QString, pnpId);
QFETCH(QStringList, vendor);
QFETCH(QString, serial);
QFETCH(QString, eisaId);
QFETCH(QString, hash);
QFETCH(uint, width);
QFETCH(uint, height);
QFETCH(qreal, gamma);
QFETCH(QQuaternion, red);
QFETCH(QQuaternion, green);
QFETCH(QQuaternion, blue);
QFETCH(QQuaternion, white);
QScopedPointer<Edid> e(new Edid(raw_edid));
QCOMPARE(e->isValid(), true);
// FIXME: we hard-code all deviceIds as xrandr-something, that makes no sense
QCOMPARE(e->deviceId(), deviceId);
QCOMPARE(e->name(), name);
QCOMPARE(e->pnpId(), pnpId);
// FIXME: needs to return at least the short ID
// QVERIFY2(vendor.contains(e->vendor()), qPrintable(QString::fromLatin1("%1 not in list").arg(e->vendor())));
QCOMPARE(e->serial(), serial);
QCOMPARE(e->eisaId(), eisaId);
QCOMPARE(e->hash(), hash);
QCOMPARE(e->width(), width);
QCOMPARE(e->height(), height);
QCOMPARE(e->gamma(), gamma);
QVERIFY(qFuzzyCompare(e->red(), red));
QVERIFY(qFuzzyCompare(e->green(), green));
QVERIFY(qFuzzyCompare(e->blue(), blue));
QVERIFY(qFuzzyCompare(e->white(), white));
}
QTEST_GUILESS_MAIN(TestEdid)
#include "testedid.moc"

329
autotests/testinprocess.cpp Normal file
View File

@ -0,0 +1,329 @@
/*
* SPDX-FileCopyrightText: 2015 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <QCoreApplication>
#include <QDBusConnectionInterface>
#include <QObject>
#include <QSignalSpy>
#include <QtTest>
#include "../src/backendmanager_p.h"
#include "../src/config.h"
#include "../src/configmonitor.h"
#include "../src/edid.h"
#include "../src/getconfigoperation.h"
#include "../src/mode.h"
#include "../src/output.h"
#include "../src/setconfigoperation.h"
Q_LOGGING_CATEGORY(KSCREEN, "kscreen")
using namespace KScreen;
class TestInProcess : public QObject
{
Q_OBJECT
public:
explicit TestInProcess(QObject *parent = nullptr);
private Q_SLOTS:
void initTestCase();
void init();
void cleanup();
void loadConfig();
void testCreateJob();
void testModeSwitching();
void testBackendCaching();
void testConfigApply();
void testConfigMonitor();
private:
ConfigPtr m_config;
bool m_backendServiceInstalled = false;
};
TestInProcess::TestInProcess(QObject *parent)
: QObject(parent)
, m_config(nullptr)
{
}
void TestInProcess::initTestCase()
{
m_backendServiceInstalled = true;
const QString kscreenServiceName = QStringLiteral("org.kde.KScreen");
QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface();
if (!bus->isServiceRegistered(kscreenServiceName)) {
auto reply = bus->startService(kscreenServiceName);
if (!reply.isValid()) {
qDebug() << "D-Bus service org.kde.KScreen could not be started, skipping out-of-process tests";
m_backendServiceInstalled = false;
}
}
}
void TestInProcess::init()
{
qputenv("KSCREEN_LOGGING", "false");
// Make sure we do everything in-process
qputenv("KSCREEN_BACKEND_INPROCESS", "1");
// Use Fake backend with one of the json configs
qputenv("KSCREEN_BACKEND", "Fake");
qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "multipleoutput.json");
KScreen::BackendManager::instance()->shutdownBackend();
}
void TestInProcess::cleanup()
{
KScreen::BackendManager::instance()->shutdownBackend();
}
void TestInProcess::loadConfig()
{
qputenv("KSCREEN_BACKEND_INPROCESS", "1");
BackendManager::instance()->setMethod(BackendManager::InProcess);
auto *op = new GetConfigOperation();
QVERIFY(op->exec());
m_config = op->config();
QVERIFY(m_config);
QVERIFY(m_config->isValid());
}
void TestInProcess::testModeSwitching()
{
KScreen::BackendManager::instance()->shutdownBackend();
BackendManager::instance()->setMethod(BackendManager::InProcess);
// Load QScreen backend in-process
qDebug() << "TT qscreen in-process";
qputenv("KSCREEN_BACKEND", "QScreen");
auto op = new GetConfigOperation();
QVERIFY(op->exec());
auto oc = op->config();
QVERIFY(oc != nullptr);
QVERIFY(oc->isValid());
qDebug() << "TT fake in-process";
// Load the Fake backend in-process
qputenv("KSCREEN_BACKEND", "Fake");
auto ip = new GetConfigOperation();
QVERIFY(ip->exec());
auto ic = ip->config();
QVERIFY(ic != nullptr);
QVERIFY(ic->isValid());
QVERIFY(ic->outputs().count());
KScreen::ConfigPtr xc(nullptr);
if (m_backendServiceInstalled) {
qDebug() << "TT xrandr out-of-process";
// Load the xrandr backend out-of-process
qputenv("KSCREEN_BACKEND", "QScreen");
qputenv("KSCREEN_BACKEND_INPROCESS", "0");
BackendManager::instance()->setMethod(BackendManager::OutOfProcess);
auto xp = new GetConfigOperation();
QCOMPARE(BackendManager::instance()->method(), BackendManager::OutOfProcess);
QVERIFY(xp->exec());
xc = xp->config();
QVERIFY(xc != nullptr);
QVERIFY(xc->isValid());
QVERIFY(xc->outputs().count());
}
qDebug() << "TT fake in-process";
qputenv("KSCREEN_BACKEND_INPROCESS", "1");
BackendManager::instance()->setMethod(BackendManager::InProcess);
// Load the Fake backend in-process
qputenv("KSCREEN_BACKEND", "Fake");
auto fp = new GetConfigOperation();
QCOMPARE(BackendManager::instance()->method(), BackendManager::InProcess);
QVERIFY(fp->exec());
auto fc = fp->config();
QVERIFY(fc != nullptr);
QVERIFY(fc->isValid());
QVERIFY(fc->outputs().count());
QVERIFY(oc->isValid());
QVERIFY(ic->isValid());
if (xc) {
QVERIFY(xc->isValid());
}
QVERIFY(fc->isValid());
}
void TestInProcess::testBackendCaching()
{
KScreen::BackendManager::instance()->shutdownBackend();
qputenv("KSCREEN_BACKEND", "Fake");
QElapsedTimer t;
BackendManager::instance()->setMethod(BackendManager::InProcess);
QCOMPARE(BackendManager::instance()->method(), BackendManager::InProcess);
int t_cold;
int t_warm;
{
t.start();
auto cp = new GetConfigOperation();
cp->exec();
auto cc = cp->config();
t_cold = t.nsecsElapsed();
QVERIFY(cc != nullptr);
QVERIFY(cc->isValid());
QVERIFY(cc->outputs().count());
}
{
// KScreen::BackendManager::instance()->shutdownBackend();
QCOMPARE(BackendManager::instance()->method(), BackendManager::InProcess);
t.start();
auto cp = new GetConfigOperation();
cp->exec();
auto cc = cp->config();
t_warm = t.nsecsElapsed();
QVERIFY(cc != nullptr);
QVERIFY(cc->isValid());
QVERIFY(cc->outputs().count());
}
{
auto cp = new GetConfigOperation();
QCOMPARE(BackendManager::instance()->method(), BackendManager::InProcess);
cp->exec();
auto cc = cp->config();
QVERIFY(cc != nullptr);
QVERIFY(cc->isValid());
QVERIFY(cc->outputs().count());
}
// Check if all our configs are still valid after the backend is gone
KScreen::BackendManager::instance()->shutdownBackend();
if (m_backendServiceInstalled) {
// qputenv("KSCREEN_BACKEND", "QScreen");
qputenv("KSCREEN_BACKEND_INPROCESS", "0");
BackendManager::instance()->setMethod(BackendManager::OutOfProcess);
QCOMPARE(BackendManager::instance()->method(), BackendManager::OutOfProcess);
int t_x_cold;
{
t.start();
auto xp = new GetConfigOperation();
xp->exec();
t_x_cold = t.nsecsElapsed();
auto xc = xp->config();
QVERIFY(xc != nullptr);
}
t.start();
auto xp = new GetConfigOperation();
xp->exec();
int t_x_warm = t.nsecsElapsed();
auto xc = xp->config();
QVERIFY(xc != nullptr);
// Make sure in-process is faster
QVERIFY(t_cold > t_warm);
QVERIFY(t_x_cold > t_x_warm);
QVERIFY(t_x_cold > t_cold);
return;
qDebug() << "ip speedup for cached access:" << (qreal)((qreal)t_cold / (qreal)t_warm);
qDebug() << "oop speedup for cached access:" << (qreal)((qreal)t_x_cold / (qreal)t_x_warm);
qDebug() << "out-of vs. in-process speedup:" << (qreal)((qreal)t_x_warm / (qreal)t_warm);
qDebug() << "cold oop: " << ((qreal)t_x_cold / 1000000);
qDebug() << "cached oop: " << ((qreal)t_x_warm / 1000000);
qDebug() << "cold in process: " << ((qreal)t_cold / 1000000);
qDebug() << "cached in process: " << ((qreal)t_warm / 1000000);
}
}
void TestInProcess::testCreateJob()
{
KScreen::BackendManager::instance()->shutdownBackend();
{
BackendManager::instance()->setMethod(BackendManager::InProcess);
auto op = new GetConfigOperation();
auto _op = qobject_cast<GetConfigOperation *>(op);
QVERIFY(_op != nullptr);
QCOMPARE(BackendManager::instance()->method(), BackendManager::InProcess);
QVERIFY(op->exec());
auto cc = op->config();
QVERIFY(cc != nullptr);
QVERIFY(cc->isValid());
}
if (m_backendServiceInstalled) {
BackendManager::instance()->setMethod(BackendManager::OutOfProcess);
auto op = new GetConfigOperation();
auto _op = qobject_cast<GetConfigOperation *>(op);
QVERIFY(_op != nullptr);
QCOMPARE(BackendManager::instance()->method(), BackendManager::OutOfProcess);
QVERIFY(op->exec());
auto cc = op->config();
QVERIFY(cc != nullptr);
QVERIFY(cc->isValid());
}
KScreen::BackendManager::instance()->shutdownBackend();
BackendManager::instance()->setMethod(BackendManager::InProcess);
}
void TestInProcess::testConfigApply()
{
qputenv("KSCREEN_BACKEND", "Fake");
KScreen::BackendManager::instance()->shutdownBackend();
BackendManager::instance()->setMethod(BackendManager::InProcess);
auto op = new GetConfigOperation();
op->exec();
auto config = op->config();
// qDebug() << "op:" << config->outputs().count();
auto output = config->outputs().first();
// qDebug() << "res:" << output->geometry();
// qDebug() << "modes:" << output->modes();
auto m0 = output->modes().first();
// qDebug() << "m0:" << m0->id() << m0;
output->setCurrentModeId(m0->id());
QVERIFY(Config::canBeApplied(config));
// expected to fail, SetConfigOperation is out-of-process only
auto setop = new SetConfigOperation(config);
QVERIFY(!setop->hasError());
QVERIFY(setop->exec());
QVERIFY(!setop->hasError());
}
void TestInProcess::testConfigMonitor()
{
qputenv("KSCREEN_BACKEND", "Fake");
KScreen::BackendManager::instance()->shutdownBackend();
BackendManager::instance()->setMethod(BackendManager::InProcess);
auto op = new GetConfigOperation();
op->exec();
auto config = op->config();
// qDebug() << "op:" << config->outputs().count();
auto output = config->outputs().first();
// qDebug() << "res:" << output->geometry();
// qDebug() << "modes:" << output->modes();
auto m0 = output->modes().first();
// qDebug() << "m0:" << m0->id() << m0;
output->setCurrentModeId(m0->id());
QVERIFY(Config::canBeApplied(config));
QSignalSpy monitorSpy(ConfigMonitor::instance(), &ConfigMonitor::configurationChanged);
qDebug() << "MOnitorspy connencted.";
ConfigMonitor::instance()->addConfig(config);
auto setop = new SetConfigOperation(config);
QVERIFY(!setop->hasError());
// do not cal setop->exec(), this must not block as the signalspy already blocks
QVERIFY(monitorSpy.wait(500));
}
QTEST_GUILESS_MAIN(TestInProcess)
#include "testinprocess.moc"

View File

@ -0,0 +1,306 @@
/*
* SPDX-FileCopyrightText: 2014-2015 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <QCoreApplication>
#include <QCryptographicHash>
#include <QObject>
#include <QtTest>
#include "../src/backendmanager_p.h"
#include "../src/config.h"
#include "../src/configmonitor.h"
#include "../src/edid.h"
#include "../src/getconfigoperation.h"
#include "../src/mode.h"
#include "../src/output.h"
#include "../src/setconfigoperation.h"
// KWayland
#include <KWayland/Server/display.h>
#include <KWayland/Server/outputdevice_interface.h>
#include "waylandtestserver.h"
Q_LOGGING_CATEGORY(KSCREEN_WAYLAND, "kscreen.kwayland")
using namespace KScreen;
class testWaylandBackend : public QObject
{
Q_OBJECT
public:
explicit testWaylandBackend(QObject *parent = nullptr);
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void loadConfig();
void verifyConfig();
void verifyOutputs();
void verifyModes();
void verifyScreen();
void verifyIds();
void verifyFeatures();
void simpleWrite();
void addOutput();
void removeOutput();
void testEdid();
private:
ConfigPtr m_config;
WaylandTestServer *m_server;
KWayland::Server::OutputDeviceInterface *m_serverOutputDevice;
};
testWaylandBackend::testWaylandBackend(QObject *parent)
: QObject(parent)
, m_config(nullptr)
{
qputenv("KSCREEN_LOGGING", "false");
m_server = new WaylandTestServer(this);
m_server->setConfig(QLatin1String(TEST_DATA) + QLatin1String("multipleoutput.json"));
}
void testWaylandBackend::initTestCase()
{
qputenv("KSCREEN_BACKEND", "kwayland");
KScreen::BackendManager::instance()->shutdownBackend();
// This is how KWayland will pick up the right socket,
// and thus connect to our internal test server.
setenv("WAYLAND_DISPLAY", s_socketName.toLocal8Bit().constData(), 1);
m_server->start();
GetConfigOperation *op = new GetConfigOperation();
op->exec();
m_config = op->config();
}
void testWaylandBackend::loadConfig()
{
GetConfigOperation *op = new GetConfigOperation();
op->exec();
m_config = op->config();
QVERIFY(m_config->isValid());
qCDebug(KSCREEN_WAYLAND) << "ops" << m_config->outputs();
}
void testWaylandBackend::verifyConfig()
{
QVERIFY(m_config != nullptr);
if (!m_config) {
QSKIP("Wayland backend invalid", SkipAll);
}
}
void testWaylandBackend::verifyScreen()
{
ScreenPtr screen = m_config->screen();
QVERIFY(screen->minSize().width() <= screen->maxSize().width());
QVERIFY(screen->minSize().height() <= screen->maxSize().height());
QVERIFY(screen->minSize().width() <= screen->currentSize().width());
QVERIFY(screen->minSize().height() <= screen->currentSize().height());
QVERIFY(screen->maxSize().width() >= screen->currentSize().width());
QVERIFY(screen->maxSize().height() >= screen->currentSize().height());
QVERIFY(m_config->screen()->maxActiveOutputsCount() > 0);
}
void testWaylandBackend::verifyOutputs()
{
bool primaryFound = false;
for (const KScreen::OutputPtr op : m_config->outputs()) {
if (op->isPrimary()) {
primaryFound = true;
}
}
// qCDebug(KSCREEN_WAYLAND) << "Primary found? " << primaryFound << m_config->outputs();
QVERIFY(primaryFound);
QVERIFY(m_config->outputs().count());
QCOMPARE(m_server->outputCount(), m_config->outputs().count());
KScreen::OutputPtr primary = m_config->primaryOutput();
QVERIFY(primary->isEnabled());
QVERIFY(primary->isConnected());
QList<int> ids;
for (const auto &output : m_config->outputs()) {
QVERIFY(!output->name().isEmpty());
QVERIFY(output->id() > -1);
QVERIFY(output->isConnected());
QVERIFY(output->geometry() != QRectF(1, 1, 1, 1));
QVERIFY(output->geometry() != QRectF());
QVERIFY(output->sizeMm() != QSize());
QVERIFY(output->edid() != nullptr);
QVERIFY(output->preferredModes().size() == 1);
QCOMPARE(output->rotation(), Output::None);
QVERIFY(!ids.contains(output->id()));
ids << output->id();
}
}
void testWaylandBackend::verifyModes()
{
KScreen::OutputPtr primary = m_config->primaryOutput();
QVERIFY(primary);
QVERIFY(primary->modes().count() > 0);
for (const auto &output : m_config->outputs()) {
for (const auto &mode : output->modes()) {
QVERIFY(!mode->name().isEmpty());
QVERIFY(mode->refreshRate() > 0);
QVERIFY(mode->size().isValid());
}
}
}
void testWaylandBackend::verifyIds()
{
QList<quint32> ids;
for (const auto &output : m_config->outputs()) {
QVERIFY(ids.contains(output->id()) == false);
QVERIFY(output->id() > 0);
ids << output->id();
}
}
void testWaylandBackend::simpleWrite()
{
KScreen::BackendManager::instance()->shutdownBackend();
GetConfigOperation *op = new GetConfigOperation();
op->exec();
m_config = op->config();
auto output = m_config->output(3);
QVERIFY(output);
auto n_mode = QStringLiteral("800x600@60");
auto o_mode = output->currentModeId();
output->setCurrentModeId(n_mode);
auto setop = new SetConfigOperation(m_config);
QVERIFY(setop->exec());
}
void testWaylandBackend::cleanupTestCase()
{
m_config->deleteLater();
KScreen::BackendManager::instance()->shutdownBackend();
}
void testWaylandBackend::addOutput()
{
KScreen::BackendManager::instance()->shutdownBackend();
GetConfigOperation *op = new GetConfigOperation();
op->exec();
auto config = op->config();
QCOMPARE(config->outputs().count(), 2);
KScreen::ConfigMonitor *monitor = KScreen::ConfigMonitor::instance();
monitor->addConfig(config);
QSignalSpy configSpy(monitor, &KScreen::ConfigMonitor::configurationChanged);
// Now add an outputdevice on the server side
m_serverOutputDevice = m_server->display()->createOutputDevice(this);
m_serverOutputDevice->setUuid("1337");
OutputDeviceInterface::Mode m0;
m0.id = 0;
m0.size = QSize(800, 600);
m0.flags = OutputDeviceInterface::ModeFlags(OutputDeviceInterface::ModeFlag::Preferred);
m_serverOutputDevice->addMode(m0);
OutputDeviceInterface::Mode m1;
m1.id = 1;
m1.size = QSize(1024, 768);
m_serverOutputDevice->addMode(m1);
OutputDeviceInterface::Mode m2;
m2.id = 2;
m2.size = QSize(1280, 1024);
m2.refreshRate = 90000;
m_serverOutputDevice->addMode(m2);
m_serverOutputDevice->setCurrentMode(1);
m_serverOutputDevice->create();
QVERIFY(configSpy.wait());
// QTRY_VERIFY(configSpy.count());
GetConfigOperation *op2 = new GetConfigOperation();
op2->exec();
auto newconfig = op2->config();
QCOMPARE(newconfig->outputs().count(), 3);
}
void testWaylandBackend::removeOutput()
{
KScreen::BackendManager::instance()->shutdownBackend();
GetConfigOperation *op = new GetConfigOperation();
op->exec();
auto config = op->config();
QCOMPARE(config->outputs().count(), 3);
KScreen::ConfigMonitor *monitor = KScreen::ConfigMonitor::instance();
monitor->addConfig(config);
QSignalSpy configSpy(monitor, &KScreen::ConfigMonitor::configurationChanged);
delete m_serverOutputDevice;
QVERIFY(configSpy.wait());
GetConfigOperation *op2 = new GetConfigOperation();
op2->exec();
auto newconfig = op2->config();
QCOMPARE(newconfig->outputs().count(), 2);
}
void testWaylandBackend::testEdid()
{
m_server->showOutputs();
QByteArray data = QByteArray::fromBase64(
"AP///////wAQrBbwTExLQQ4WAQOANCB46h7Frk80sSYOUFSlSwCBgKlA0QBxTwEBAQEBAQEBKDyAoHCwI0AwIDYABkQhAAAaAAAA/wBGNTI1TTI0NUFLTEwKAAAA/ABERUxMIFUyNDEwCiAgAAAA/"
"QA4TB5REQAKICAgICAgAToCAynxUJAFBAMCBxYBHxITFCAVEQYjCQcHZwMMABAAOC2DAQAA4wUDAQI6gBhxOC1AWCxFAAZEIQAAHgEdgBhxHBYgWCwlAAZEIQAAngEdAHJR0B4gbihVAAZEIQAAHow"
"K0Iog4C0QED6WAAZEIQAAGAAAAAAAAAAAAAAAAAAAPg==");
QScopedPointer<Edid> edid(new Edid(data));
QVERIFY(edid->isValid());
GetConfigOperation *op = new GetConfigOperation();
op->exec();
auto config = op->config();
QVERIFY(config->outputs().count() > 0);
auto o = config->outputs().last();
qCDebug(KSCREEN_WAYLAND) << "Edid: " << o->edid()->isValid();
QVERIFY(o->edid()->isValid());
QCOMPARE(o->edid()->deviceId(), edid->deviceId());
QCOMPARE(o->edid()->name(), edid->name());
QCOMPARE(o->edid()->vendor(), edid->vendor());
QCOMPARE(o->edid()->eisaId(), edid->eisaId());
QCOMPARE(o->edid()->serial(), edid->serial());
QCOMPARE(o->edid()->hash(), edid->hash());
QCOMPARE(o->edid()->width(), edid->width());
QCOMPARE(o->edid()->height(), edid->height());
QCOMPARE(o->edid()->gamma(), edid->gamma());
QCOMPARE(o->edid()->red(), edid->red());
QCOMPARE(o->edid()->green(), edid->green());
QCOMPARE(o->edid()->blue(), edid->blue());
QCOMPARE(o->edid()->white(), edid->white());
}
void testWaylandBackend::verifyFeatures()
{
GetConfigOperation *op = new GetConfigOperation();
op->exec();
auto config = op->config();
QVERIFY(!config->supportedFeatures().testFlag(Config::Feature::None));
QVERIFY(config->supportedFeatures().testFlag(Config::Feature::Writable));
QVERIFY(!config->supportedFeatures().testFlag(Config::Feature::PrimaryDisplay));
}
QTEST_GUILESS_MAIN(testWaylandBackend)
#include "testkwaylandbackend.moc"

View File

@ -0,0 +1,309 @@
/*
* SPDX-FileCopyrightText: 2015 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <QCoreApplication>
#include <QObject>
#include <QSignalSpy>
#include <QtTest>
#include "backendmanager_p.h"
#include "config.h"
#include "configmonitor.h"
#include "edid.h"
#include "getconfigoperation.h"
#include "mode.h"
#include "output.h"
#include "setconfigoperation.h"
#include "waylandtestserver.h"
Q_LOGGING_CATEGORY(KSCREEN_WAYLAND, "kscreen.kwayland")
using namespace KScreen;
class TestKWaylandConfig : public QObject
{
Q_OBJECT
public:
explicit TestKWaylandConfig(QObject *parent = nullptr);
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void changeConfig();
void testPositionChange();
void testRotationChange();
void testRotationChange_data();
void testScaleChange();
void testModeChange();
void testApplyOnPending();
private:
WaylandTestServer *m_server;
};
TestKWaylandConfig::TestKWaylandConfig(QObject *parent)
: QObject(parent)
, m_server(nullptr)
{
qputenv("KSCREEN_LOGGING", "false");
}
void TestKWaylandConfig::initTestCase()
{
setenv("KSCREEN_BACKEND", "kwayland", 1);
KScreen::BackendManager::instance()->shutdownBackend();
// This is how KWayland will pick up the right socket,
// and thus connect to our internal test server.
setenv("WAYLAND_DISPLAY", s_socketName.toLocal8Bit().constData(), 1);
m_server = new WaylandTestServer(this);
m_server->start();
}
void TestKWaylandConfig::cleanupTestCase()
{
qDebug() << "Shutting down";
KScreen::BackendManager::instance()->shutdownBackend();
delete m_server;
}
void TestKWaylandConfig::changeConfig()
{
auto op = new GetConfigOperation();
QVERIFY(op->exec());
auto config = op->config();
QVERIFY(config);
// Prepare monitor & spy
KScreen::ConfigMonitor *monitor = KScreen::ConfigMonitor::instance();
monitor->addConfig(config);
QSignalSpy configSpy(monitor, &KScreen::ConfigMonitor::configurationChanged);
// The first output is currently disabled, let's try to enable it
auto output = config->outputs().first();
QVERIFY(output->isEnabled() == false);
output->setEnabled(true);
output->setCurrentModeId(QStringLiteral("76"));
auto output2 = config->outputs()[2]; // is this id stable enough?
output2->setPos(QPoint(4000, 1080));
output2->setRotation(KScreen::Output::Left);
QSignalSpy serverSpy(m_server, &WaylandTestServer::configChanged);
auto sop = new SetConfigOperation(config, this);
sop->exec(); // fire and forget...
QVERIFY(configSpy.wait());
// check if the server changed
QCOMPARE(serverSpy.count(), 1);
QCOMPARE(configSpy.count(), 1);
monitor->removeConfig(config);
m_server->showOutputs();
}
void TestKWaylandConfig::testPositionChange()
{
auto op = new GetConfigOperation();
QVERIFY(op->exec());
auto config = op->config();
QVERIFY(config);
// Prepare monitor & spy
KScreen::ConfigMonitor *monitor = KScreen::ConfigMonitor::instance();
monitor->addConfig(config);
QSignalSpy configSpy(monitor, &KScreen::ConfigMonitor::configurationChanged);
auto output = config->outputs()[2]; // is this id stable enough?
auto new_pos = QPoint(3840, 1080);
output->setPos(new_pos);
QSignalSpy serverSpy(m_server, &WaylandTestServer::configChanged);
auto sop = new SetConfigOperation(config, this);
sop->exec(); // fire and forget...
QVERIFY(configSpy.wait());
// check if the server changed
QCOMPARE(serverSpy.count(), 1);
QCOMPARE(configSpy.count(), 1);
}
void TestKWaylandConfig::testRotationChange_data()
{
QTest::addColumn<KScreen::Output::Rotation>("rotation");
QTest::newRow("left") << KScreen::Output::Left;
QTest::newRow("inverted") << KScreen::Output::Inverted;
QTest::newRow("right") << KScreen::Output::Right;
QTest::newRow("none") << KScreen::Output::None;
}
void TestKWaylandConfig::testRotationChange()
{
QFETCH(KScreen::Output::Rotation, rotation);
auto op = new GetConfigOperation();
QVERIFY(op->exec());
auto config = op->config();
QVERIFY(config);
// Prepare monitor & spy
KScreen::ConfigMonitor *monitor = KScreen::ConfigMonitor::instance();
monitor->addConfig(config);
QSignalSpy configSpy(monitor, &KScreen::ConfigMonitor::configurationChanged);
auto output = config->outputs().first(); // is this id stable enough?
output->setRotation(rotation);
QSignalSpy serverSpy(m_server, &WaylandTestServer::configChanged);
auto sop = new SetConfigOperation(config, this);
sop->exec(); // fire and forget...
QVERIFY(configSpy.wait());
// check if the server changed
QCOMPARE(serverSpy.count(), 1);
QCOMPARE(configSpy.count(), 1);
// Get a new config, then compare the output with the expected new value
auto newop = new GetConfigOperation();
QVERIFY(newop->exec());
auto newconfig = newop->config();
QVERIFY(newconfig);
auto newoutput = newconfig->outputs().first();
QCOMPARE(newoutput->rotation(), rotation);
}
void TestKWaylandConfig::testScaleChange()
{
auto op = new GetConfigOperation();
QVERIFY(op->exec());
auto config = op->config();
QVERIFY(config);
auto op2 = new GetConfigOperation();
QVERIFY(op2->exec());
auto config2 = op2->config();
QVERIFY(config2);
// Prepare monitor & spy
KScreen::ConfigMonitor *monitor = KScreen::ConfigMonitor::instance();
monitor->addConfig(config);
monitor->addConfig(config2);
QSignalSpy configSpy(monitor, &KScreen::ConfigMonitor::configurationChanged);
QSignalSpy configSpy2(monitor, &KScreen::ConfigMonitor::configurationChanged);
auto output2 = config2->outputs()[2]; // is this id stable enough?
QSignalSpy outputSpy(output2.data(), &KScreen::Output::scaleChanged);
QCOMPARE(output2->scale(), 1.0);
auto output = config->outputs()[2]; // is this id stable enough?
output->setScale(2);
QSignalSpy serverSpy(m_server, &WaylandTestServer::configChanged);
auto sop = new SetConfigOperation(config, this);
sop->exec(); // fire and forget...
QVERIFY(configSpy.wait());
// check if the server changed
QCOMPARE(serverSpy.count(), 1);
QCOMPARE(configSpy.count(), 1);
QCOMPARE(outputSpy.count(), 1);
QCOMPARE(configSpy2.count(), 1);
QCOMPARE(output2->scale(), 2.0);
}
void TestKWaylandConfig::testModeChange()
{
auto op = new GetConfigOperation();
QVERIFY(op->exec());
auto config = op->config();
QVERIFY(config);
KScreen::ConfigMonitor *monitor = KScreen::ConfigMonitor::instance();
monitor->addConfig(config);
QSignalSpy configSpy(monitor, &KScreen::ConfigMonitor::configurationChanged);
auto output = config->outputs()[1]; // is this id stable enough?
QString new_mode = QStringLiteral("74");
output->setCurrentModeId(new_mode);
QSignalSpy serverSpy(m_server, &WaylandTestServer::configChanged);
auto sop = new SetConfigOperation(config, this);
sop->exec();
QVERIFY(configSpy.wait());
// check if the server changed
QCOMPARE(serverSpy.count(), 1);
QCOMPARE(configSpy.count(), 1);
}
void TestKWaylandConfig::testApplyOnPending()
{
auto op = new GetConfigOperation();
QVERIFY(op->exec());
auto config = op->config();
QVERIFY(config);
auto op2 = new GetConfigOperation();
QVERIFY(op2->exec());
auto config2 = op2->config();
QVERIFY(config2);
KScreen::ConfigMonitor *monitor = KScreen::ConfigMonitor::instance();
monitor->addConfig(config);
QSignalSpy configSpy(monitor, &KScreen::ConfigMonitor::configurationChanged);
auto output = config->outputs()[1]; // is this id stable enough?
QCOMPARE(output->scale(), 1.0);
output->setScale(2);
QSignalSpy serverSpy(m_server, &WaylandTestServer::configChanged);
QSignalSpy serverReceivedSpy(m_server, &WaylandTestServer::configReceived);
m_server->suspendChanges(true);
new SetConfigOperation(config, this);
/* Apply next config */
auto output2 = config2->outputs()[2]; // is this id stable enough?
QCOMPARE(output2->scale(), 2.0);
output2->setScale(3);
new SetConfigOperation(config2, this);
QVERIFY(serverReceivedSpy.wait());
QCOMPARE(serverReceivedSpy.count(), 1);
m_server->suspendChanges(false);
QVERIFY(configSpy.wait());
// check if the server changed
QCOMPARE(serverSpy.count(), 1);
QCOMPARE(configSpy.count(), 1);
QCOMPARE(output->scale(), 2.0);
QCOMPARE(output2->scale(), 3.0);
QVERIFY(configSpy.wait());
// check if the server changed
QCOMPARE(serverSpy.count(), 2);
QCOMPARE(configSpy.count(), 2);
QCOMPARE(output2->scale(), 3.0);
}
QTEST_GUILESS_MAIN(TestKWaylandConfig)
#include "testkwaylandconfig.moc"

View File

@ -0,0 +1,110 @@
/*
* SPDX-FileCopyrightText: 2016 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <QCoreApplication>
#include <QObject>
#include <QSignalSpy>
#include <QtTest>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/dpms.h>
#include <KWayland/Client/registry.h>
#include "waylandtestserver.h"
static const QString s_socketName = QStringLiteral("libkscreen-test-wayland-backend-0");
// static const QString s_socketName = QStringLiteral("wayland-0");
Q_LOGGING_CATEGORY(KSCREEN, "kscreen")
using namespace KWayland::Client;
class TestDpmsClient : public QObject
{
Q_OBJECT
public:
explicit TestDpmsClient(QObject *parent = nullptr);
Q_SIGNALS:
void dpmsAnnounced();
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void testDpmsConnect();
private:
ConnectionThread *m_connection;
QThread *m_thread;
Registry *m_registry;
KScreen::WaylandTestServer *m_server;
};
TestDpmsClient::TestDpmsClient(QObject *parent)
: QObject(parent)
, m_server(nullptr)
{
setenv("WAYLAND_DISPLAY", s_socketName.toLocal8Bit().constData(), true);
m_server = new KScreen::WaylandTestServer(this);
m_server->start();
}
void TestDpmsClient::initTestCase()
{
// setup connection
m_connection = new KWayland::Client::ConnectionThread;
m_connection->setSocketName(s_socketName);
QSignalSpy connectedSpy(m_connection, SIGNAL(connected()));
m_connection->setSocketName(s_socketName);
m_thread = new QThread(this);
m_connection->moveToThread(m_thread);
m_thread->start();
m_connection->initConnection();
QVERIFY(connectedSpy.wait());
QSignalSpy dpmsSpy(this, &TestDpmsClient::dpmsAnnounced);
m_connection->initConnection();
QVERIFY(connectedSpy.wait(100));
m_registry = new KWayland::Client::Registry;
m_registry->create(m_connection);
QObject::connect(m_registry, &Registry::interfacesAnnounced, this, [this] {
const bool hasDpms = m_registry->hasInterface(Registry::Interface::Dpms);
if (hasDpms) {
qDebug() << QStringLiteral("Compositor provides a DpmsManager");
} else {
qDebug() << QStringLiteral("Compositor does not provid a DpmsManager");
}
Q_EMIT this->dpmsAnnounced();
});
m_registry->setup();
QVERIFY(dpmsSpy.wait(100));
}
void TestDpmsClient::cleanupTestCase()
{
m_thread->exit();
m_thread->wait();
delete m_registry;
delete m_thread;
delete m_connection;
}
void TestDpmsClient::testDpmsConnect()
{
QVERIFY(m_registry->isValid());
}
QTEST_GUILESS_MAIN(TestDpmsClient)
#include "testkwaylanddpms.moc"

127
autotests/testlog.cpp Normal file
View File

@ -0,0 +1,127 @@
/*
* SPDX-FileCopyrightText: 2016 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <QLoggingCategory>
#include <QObject>
#include <QtTest>
#include "../src/log.h"
Q_DECLARE_LOGGING_CATEGORY(KSCREEN_TESTLOG)
Q_LOGGING_CATEGORY(KSCREEN_TESTLOG, "kscreen.testlog")
using namespace KScreen;
auto KSCREEN_LOGGING = "KSCREEN_LOGGING";
class TestLog : public QObject
{
Q_OBJECT
private Q_SLOTS:
void init();
void initTestCase();
void cleanupTestCase();
void testContext();
void testEnabled();
void testLog();
private:
QString m_defaultLogFile;
};
void TestLog::init()
{
QStandardPaths::setTestModeEnabled(true);
m_defaultLogFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kscreen/kscreen.log");
}
void TestLog::initTestCase()
{
qputenv(KSCREEN_LOGGING, QByteArray("true"));
}
void TestLog::cleanupTestCase()
{
qunsetenv(KSCREEN_LOGGING);
}
void TestLog::testContext()
{
auto log = Log::instance();
QString ctx = QStringLiteral("context text");
QVERIFY(log != nullptr);
log->setContext(ctx);
QCOMPARE(log->context(), ctx);
delete log;
}
void TestLog::testEnabled()
{
qputenv(KSCREEN_LOGGING, QByteArray("faLSe"));
auto log = Log::instance();
QCOMPARE(log->enabled(), false);
QCOMPARE(log->logFile(), QString());
delete log;
qunsetenv(KSCREEN_LOGGING);
log = Log::instance();
QCOMPARE(log->enabled(), false);
QCOMPARE(log->logFile(), QString());
delete log;
qputenv(KSCREEN_LOGGING, QByteArray("truE"));
log = Log::instance();
QCOMPARE(log->enabled(), true);
QCOMPARE(log->logFile(), m_defaultLogFile);
delete log;
}
void TestLog::testLog()
{
auto log = Log::instance();
Q_UNUSED(log);
QFile lf(m_defaultLogFile);
lf.remove();
QVERIFY(!lf.exists());
QString logmsg = QStringLiteral("This is a log message. ♥");
Log::log(logmsg);
QVERIFY(lf.exists());
QVERIFY(lf.remove());
qCDebug(KSCREEN_TESTLOG) << "qCDebug message from testlog";
QVERIFY(lf.exists());
QVERIFY(lf.remove());
delete Log::instance();
// Make sure on log file gets written when disabled
qputenv(KSCREEN_LOGGING, "false");
qCDebug(KSCREEN_TESTLOG) << logmsg;
QCOMPARE(Log::instance()->enabled(), false);
QVERIFY(!lf.exists());
Log::log(logmsg);
QVERIFY(!lf.exists());
// Make sure we don't crash on cleanup
delete Log::instance();
delete Log::instance();
}
QTEST_MAIN(TestLog)
#include "testlog.moc"

View File

@ -0,0 +1,163 @@
/*
* SPDX-FileCopyrightText: 2016 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <QObject>
#include <QtTest>
#include "../src/backendmanager_p.h"
#include "../src/config.h"
#include "../src/configmonitor.h"
#include "../src/getconfigoperation.h"
#include "../src/mode.h"
#include "../src/output.h"
#include "../src/setconfigoperation.h"
using namespace KScreen;
class TestModeListChange : public QObject
{
Q_OBJECT
private:
KScreen::ConfigPtr getConfig();
KScreen::ModeList createModeList();
bool compareModeList(KScreen::ModeList before, KScreen::ModeList &after);
QSize s0 = QSize(1920, 1080);
QSize s1 = QSize(1600, 1200);
QSize s2 = QSize(1280, 1024);
QSize s3 = QSize(800, 600);
QSize snew = QSize(777, 888);
QString idnew = QStringLiteral("666");
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void modeListChange();
};
ConfigPtr TestModeListChange::getConfig()
{
qputenv("KSCREEN_BACKEND_INPROCESS", "1");
auto *op = new GetConfigOperation();
if (!op->exec()) {
qWarning("ConfigOperation error: %s", qPrintable(op->errorString()));
BackendManager::instance()->shutdownBackend();
return ConfigPtr();
}
BackendManager::instance()->shutdownBackend();
return op->config();
}
KScreen::ModeList TestModeListChange::createModeList()
{
KScreen::ModeList newmodes;
{
QString _id = QString::number(11);
KScreen::ModePtr kscreenMode(new KScreen::Mode);
kscreenMode->setId(_id);
kscreenMode->setName(_id);
kscreenMode->setSize(s0);
kscreenMode->setRefreshRate(60);
newmodes.insert(_id, kscreenMode);
}
{
QString _id = QString::number(22);
KScreen::ModePtr kscreenMode(new KScreen::Mode);
kscreenMode->setId(_id);
kscreenMode->setName(_id);
kscreenMode->setSize(s1);
kscreenMode->setRefreshRate(60);
newmodes.insert(_id, kscreenMode);
}
{
QString _id = QString::number(33);
KScreen::ModePtr kscreenMode(new KScreen::Mode);
kscreenMode->setId(_id);
kscreenMode->setName(_id);
kscreenMode->setSize(s2);
kscreenMode->setRefreshRate(60);
newmodes.insert(_id, kscreenMode);
}
return newmodes;
}
void TestModeListChange::initTestCase()
{
qputenv("KSCREEN_LOGGING", "false");
qputenv("KSCREEN_BACKEND", "Fake");
}
void TestModeListChange::cleanupTestCase()
{
BackendManager::instance()->shutdownBackend();
}
void TestModeListChange::modeListChange()
{
// json file for the fake backend
qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "singleoutput.json");
const ConfigPtr config = getConfig();
QVERIFY(!config.isNull());
auto output = config->outputs().first();
QVERIFY(!output.isNull());
auto modelist = output->modes();
auto mode = modelist.first();
mode->setId(QStringLiteral("44"));
mode->setSize(QSize(880, 440));
output->setModes(modelist);
QCOMPARE(output->modes().first()->id(), QStringLiteral("44"));
QCOMPARE(output->modes().first()->size(), QSize(880, 440));
QVERIFY(!modelist.isEmpty());
ConfigMonitor::instance()->addConfig(config);
QSignalSpy outputChangedSpy(output.data(), &Output::outputChanged);
QVERIFY(outputChangedSpy.isValid());
QSignalSpy modesChangedSpy(output.data(), &Output::modesChanged);
QVERIFY(modesChangedSpy.isValid());
auto before = createModeList();
output->setModes(before);
QCOMPARE(modesChangedSpy.count(), 1);
output->setModes(before);
QCOMPARE(modesChangedSpy.count(), 1);
output->setModes(before);
QCOMPARE(modesChangedSpy.count(), 1);
QCOMPARE(output->modes().first()->size(), s0);
QCOMPARE(output->modes().first()->id(), QStringLiteral("11"));
auto after = createModeList();
auto firstmode = after.first();
QVERIFY(!firstmode.isNull());
QCOMPARE(firstmode->size(), s0);
QCOMPARE(firstmode->id(), QStringLiteral("11"));
firstmode->setSize(snew);
firstmode->setId(idnew);
output->setModes(after);
QCOMPARE(modesChangedSpy.count(), 2);
QString _id = QString::number(11);
KScreen::ModePtr kscreenMode(new KScreen::Mode);
kscreenMode->setId(_id);
kscreenMode->setName(_id);
kscreenMode->setSize(s0);
kscreenMode->setRefreshRate(60);
before.insert(_id, kscreenMode);
output->setModes(before);
QCOMPARE(modesChangedSpy.count(), 3);
QCOMPARE(outputChangedSpy.count(), modesChangedSpy.count());
}
QTEST_MAIN(TestModeListChange)
#include "testmodelistchange.moc"

View File

@ -0,0 +1,211 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
* SPDX-FileCopyrightText: 2012 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <QElapsedTimer>
#include <QObject>
#include <QtTest>
#include "../src/backendmanager_p.h"
#include "../src/config.h"
#include "../src/edid.h"
#include "../src/getconfigoperation.h"
#include "../src/mode.h"
#include "../src/output.h"
Q_LOGGING_CATEGORY(KSCREEN_QSCREEN, "kscreen.qscreen")
using namespace KScreen;
class testQScreenBackend : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void verifyConfig();
void verifyScreen();
void verifyOutputs();
void verifyModes();
void verifyFeatures();
void commonUsagePattern();
void cleanupTestCase();
private:
QProcess m_process;
ConfigPtr m_config;
QString m_backend;
};
void testQScreenBackend::initTestCase()
{
qputenv("KSCREEN_LOGGING", "false");
qputenv("KSCREEN_BACKEND", "qscreen");
qputenv("KSCREEN_BACKEND_INPROCESS", "1");
KScreen::BackendManager::instance()->shutdownBackend();
m_backend = QString::fromLocal8Bit(qgetenv("KSCREEN_BACKEND"));
QElapsedTimer t;
t.start();
auto *op = new GetConfigOperation();
op->exec();
m_config = op->config();
const int n = t.nsecsElapsed();
qDebug() << "Test took: " << n << "ns";
}
void testQScreenBackend::verifyConfig()
{
QVERIFY(!m_config.isNull());
if (!m_config) {
QSKIP("QScreenbackend invalid", SkipAll);
}
}
void testQScreenBackend::verifyScreen()
{
ScreenPtr screen = m_config->screen();
QVERIFY(screen->minSize().width() <= screen->maxSize().width());
QVERIFY(screen->minSize().height() <= screen->maxSize().height());
QVERIFY(screen->minSize().width() <= screen->currentSize().width());
QVERIFY(screen->minSize().height() <= screen->currentSize().height());
QVERIFY(screen->maxSize().width() >= screen->currentSize().width());
QVERIFY(screen->maxSize().height() >= screen->currentSize().height());
QVERIFY(m_config->screen()->maxActiveOutputsCount() > 0);
}
void testQScreenBackend::verifyOutputs()
{
bool primaryFound = false;
for (const KScreen::OutputPtr &op : m_config->outputs()) {
if (op->isPrimary()) {
primaryFound = true;
}
}
qDebug() << "Primary found? " << primaryFound;
QVERIFY(primaryFound);
if (m_backend == QLatin1String("screen")) {
QCOMPARE(m_config->outputs().count(), QGuiApplication::screens().count());
}
const KScreen::OutputPtr primary = m_config->primaryOutput();
QVERIFY(primary->isEnabled());
QVERIFY(primary->isConnected());
// qDebug() << "Primary geometry? " << primary->geometry();
// qDebug() << " prim modes: " << primary->modes();
QList<int> ids;
for (const KScreen::OutputPtr &output : m_config->outputs()) {
qDebug() << " _____________________ Output: " << output;
qDebug() << " output name: " << output->name();
qDebug() << " output modes: " << output->modes().count() << output->modes();
qDebug() << " output enabled: " << output->isEnabled();
qDebug() << " output connect: " << output->isConnected();
qDebug() << " output sizeMm : " << output->sizeMm();
QVERIFY(!output->name().isEmpty());
QVERIFY(output->id() > -1);
QVERIFY(output->isConnected());
QVERIFY(output->isEnabled());
QVERIFY(output->geometry() != QRectF(1, 1, 1, 1));
QVERIFY(output->geometry() != QRectF());
// Pass, but leave a note, when the x server doesn't report physical size
if (!output->sizeMm().isValid()) {
QEXPECT_FAIL("", "The X server doesn't return a sensible physical output size", Continue);
QVERIFY(output->sizeMm() != QSize());
}
QVERIFY(output->edid() != nullptr);
QCOMPARE(output->rotation(), Output::None);
QVERIFY(!ids.contains(output->id()));
ids << output->id();
}
}
void testQScreenBackend::verifyModes()
{
const KScreen::OutputPtr primary = m_config->primaryOutput();
QVERIFY(primary);
QVERIFY(primary->modes().count() > 0);
for (const KScreen::OutputPtr &output : m_config->outputs()) {
for (const KScreen::ModePtr &mode : output->modes()) {
qDebug() << " Mode : " << mode->name();
QVERIFY(!mode->name().isEmpty());
QVERIFY(mode->refreshRate() > 0);
QVERIFY(mode->size() != QSize());
}
}
}
void testQScreenBackend::commonUsagePattern()
{
auto *op = new GetConfigOperation();
op->exec();
const KScreen::OutputList outputs = op->config()->outputs();
QVariantList outputList;
for (const KScreen::OutputPtr &output : outputs) {
if (!output->isConnected()) {
continue;
}
QVariantMap info;
info[QStringLiteral("id")] = output->id();
info[QStringLiteral("primary")] = output->isPrimary();
info[QStringLiteral("enabled")] = output->isEnabled();
info[QStringLiteral("rotation")] = output->rotation();
QVariantMap pos;
pos[QStringLiteral("x")] = output->pos().x();
pos[QStringLiteral("y")] = output->pos().y();
info[QStringLiteral("pos")] = pos;
if (output->isEnabled()) {
const KScreen::ModePtr mode = output->currentMode();
if (!mode) {
// qWarning() << "CurrentMode is null" << output->name();
return;
}
QVariantMap modeInfo;
modeInfo[QStringLiteral("refresh")] = mode->refreshRate();
QVariantMap modeSize;
modeSize[QStringLiteral("width")] = mode->size().width();
modeSize[QStringLiteral("height")] = mode->size().height();
modeInfo[QStringLiteral("size")] = modeSize;
info[QStringLiteral("mode")] = modeInfo;
}
outputList.append(info);
}
}
void testQScreenBackend::cleanupTestCase()
{
KScreen::BackendManager::instance()->shutdownBackend();
qApp->exit(0);
}
void testQScreenBackend::verifyFeatures()
{
GetConfigOperation *op = new GetConfigOperation();
op->exec();
auto config = op->config();
QVERIFY(config->supportedFeatures().testFlag(Config::Feature::None));
QVERIFY(!config->supportedFeatures().testFlag(Config::Feature::Writable));
QVERIFY(!config->supportedFeatures().testFlag(Config::Feature::PrimaryDisplay));
}
QTEST_MAIN(testQScreenBackend)
#include "testqscreenbackend.moc"

View File

@ -0,0 +1,309 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <QObject>
#include <QtTest>
#include "../src/backendmanager_p.h"
#include "../src/config.h"
#include "../src/getconfigoperation.h"
#include "../src/mode.h"
#include "../src/output.h"
#include "../src/screen.h"
#include "../src/setconfigoperation.h"
using namespace KScreen;
class testScreenConfig : public QObject
{
Q_OBJECT
private:
KScreen::ConfigPtr getConfig();
private Q_SLOTS:
void initTestCase();
void singleOutput();
void singleOutputWithoutPreferred();
void multiOutput();
void clonesOutput();
void configCanBeApplied();
void supportedFeatures();
void testInvalidMode();
void cleanupTestCase();
void testOutputPositionNormalization();
};
ConfigPtr testScreenConfig::getConfig()
{
qputenv("KSCREEN_BACKEND_INPROCESS", "1");
auto *op = new GetConfigOperation();
if (!op->exec()) {
qWarning("ConfigOperation error: %s", qPrintable(op->errorString()));
BackendManager::instance()->shutdownBackend();
return ConfigPtr();
}
BackendManager::instance()->shutdownBackend();
return op->config();
}
void testScreenConfig::initTestCase()
{
qputenv("KSCREEN_LOGGING", "false");
qputenv("KSCREEN_BACKEND", "Fake");
}
void testScreenConfig::cleanupTestCase()
{
BackendManager::instance()->shutdownBackend();
}
void testScreenConfig::singleOutput()
{
// json file for the fake backend
qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "singleoutput.json");
// QVERIFY2(kscreen, KScreen::errorString().toLatin1());
// QVERIFY2(!kscreen->backend().isEmpty(), "No backend loaded");
const ConfigPtr config = getConfig();
QVERIFY(!config.isNull());
const ScreenPtr screen = config->screen();
QVERIFY(!screen.isNull());
QCOMPARE(screen->minSize(), QSize(320, 200));
QCOMPARE(screen->maxSize(), QSize(8192, 8192));
QCOMPARE(screen->currentSize(), QSize(1280, 800));
QCOMPARE(config->outputs().count(), 1);
const OutputPtr output = config->outputs().take(1);
QVERIFY(!output.isNull());
QCOMPARE(output->name(), QLatin1String("LVDS1"));
QCOMPARE(output->type(), Output::Panel);
QCOMPARE(output->modes().count(), 3);
QCOMPARE(output->pos(), QPoint(0, 0));
QCOMPARE(output->geometry(), QRect(0, 0, 1280, 800));
QCOMPARE(output->currentModeId(), QLatin1String("3"));
QCOMPARE(output->preferredModeId(), QLatin1String("3"));
QCOMPARE(output->rotation(), Output::None);
QCOMPARE(output->scale(), 1.0);
QCOMPARE(output->isConnected(), true);
QCOMPARE(output->isEnabled(), true);
QCOMPARE(output->isPrimary(), true);
// QCOMPARE(output->isEmbedded(), true);
QVERIFY2(output->clones().isEmpty(), "In singleOutput is impossible to have clones");
const ModePtr mode = output->currentMode();
QCOMPARE(mode->size(), QSize(1280, 800));
QCOMPARE(mode->refreshRate(), (float)59.9);
}
void testScreenConfig::singleOutputWithoutPreferred()
{
qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "singleOutputWithoutPreferred.json");
const ConfigPtr config = getConfig();
QVERIFY(!config.isNull());
const OutputPtr output = config->outputs().take(1);
QVERIFY(!output.isNull());
QVERIFY(output->preferredModes().isEmpty());
QCOMPARE(output->preferredModeId(), QLatin1String("3"));
}
void testScreenConfig::multiOutput()
{
qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "multipleoutput.json");
const ConfigPtr config = getConfig();
QVERIFY(!config.isNull());
const ScreenPtr screen = config->screen();
QVERIFY(!screen.isNull());
QCOMPARE(screen->minSize(), QSize(320, 200));
QCOMPARE(screen->maxSize(), QSize(8192, 8192));
QCOMPARE(screen->currentSize(), QSize(3200, 1880));
QCOMPARE(config->outputs().count(), 2);
const OutputPtr output = config->outputs().take(2);
QVERIFY(!output.isNull());
QCOMPARE(output->name(), QStringLiteral("HDMI1"));
QCOMPARE(output->type(), Output::HDMI);
QCOMPARE(output->modes().count(), 4);
QCOMPARE(output->pos(), QPoint(1280, 0));
QCOMPARE(output->geometry(), QRect(1280, 0, 1920 / 1.4, 1080 / 1.4));
QCOMPARE(output->currentModeId(), QLatin1String("4"));
QCOMPARE(output->preferredModeId(), QLatin1String("4"));
QCOMPARE(output->rotation(), Output::None);
QCOMPARE(output->scale(), 1.4);
QCOMPARE(output->isConnected(), true);
QCOMPARE(output->isEnabled(), true);
QCOMPARE(output->isPrimary(), false);
QVERIFY2(output->clones().isEmpty(), "This simulates extended output, no clones");
const ModePtr mode = output->currentMode();
QVERIFY(!mode.isNull());
QCOMPARE(mode->size(), QSize(1920, 1080));
QCOMPARE(mode->refreshRate(), (float)60.0);
}
void testScreenConfig::clonesOutput()
{
qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "multipleclone.json");
const ConfigPtr config = getConfig();
QVERIFY(!config.isNull());
const ScreenPtr screen = config->screen();
QVERIFY(!screen.isNull());
QCOMPARE(screen->minSize(), QSize(320, 200));
QCOMPARE(screen->maxSize(), QSize(8192, 8192));
QCOMPARE(screen->currentSize(), QSize(1024, 768));
const OutputPtr one = config->outputs()[1];
const OutputPtr two = config->outputs()[2];
QCOMPARE(one->currentMode()->size(), two->currentMode()->size());
QCOMPARE(one->clones().count(), 1);
QCOMPARE(one->clones().first(), two->id());
QVERIFY2(two->clones().isEmpty(), "Output two should have no clones");
}
void testScreenConfig::configCanBeApplied()
{
qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "singleoutputBroken.json");
const ConfigPtr brokenConfig = getConfig();
qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "singleoutput.json");
const ConfigPtr currentConfig = getConfig();
QVERIFY(!currentConfig.isNull());
const OutputPtr primaryBroken = brokenConfig->outputs()[2];
QVERIFY(!primaryBroken.isNull());
const OutputPtr currentPrimary = currentConfig->outputs()[1];
QVERIFY(!currentPrimary.isNull());
QVERIFY(!Config::canBeApplied(brokenConfig));
primaryBroken->setId(currentPrimary->id());
QVERIFY(!Config::canBeApplied(brokenConfig));
primaryBroken->setConnected(currentPrimary->isConnected());
QVERIFY(!Config::canBeApplied(brokenConfig));
primaryBroken->setCurrentModeId(QStringLiteral("42"));
QVERIFY(!Config::canBeApplied(brokenConfig));
primaryBroken->setCurrentModeId(currentPrimary->currentModeId());
QVERIFY(!Config::canBeApplied(brokenConfig));
qDebug() << "brokenConfig.modes" << primaryBroken->mode(QStringLiteral("3"));
primaryBroken->mode(QStringLiteral("3"))->setSize(QSize(1280, 800));
qDebug() << "brokenConfig.modes" << primaryBroken->mode(QStringLiteral("3"));
QVERIFY(Config::canBeApplied(brokenConfig));
qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "tooManyOutputs.json");
const ConfigPtr brokenConfig2 = getConfig();
QVERIFY(!brokenConfig2.isNull());
int enabledOutputsCount = 0;
for (const OutputPtr &output : brokenConfig2->outputs()) {
if (output->isEnabled()) {
++enabledOutputsCount;
}
}
QVERIFY(brokenConfig2->screen()->maxActiveOutputsCount() < enabledOutputsCount);
QVERIFY(!Config::canBeApplied(brokenConfig2));
const ConfigPtr nulllConfig;
QVERIFY(!Config::canBeApplied(nulllConfig));
}
void testScreenConfig::supportedFeatures()
{
ConfigPtr config = getConfig();
QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::None));
QVERIFY(!config->supportedFeatures().testFlag(KScreen::Config::Feature::Writable));
QVERIFY(!config->supportedFeatures().testFlag(KScreen::Config::Feature::PrimaryDisplay));
QVERIFY(!config->supportedFeatures().testFlag(KScreen::Config::Feature::PerOutputScaling));
config->setSupportedFeatures(KScreen::Config::Feature::Writable | KScreen::Config::Feature::PrimaryDisplay);
QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::Writable));
QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::PrimaryDisplay));
config->setSupportedFeatures(KScreen::Config::Feature::None);
QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::None));
config->setSupportedFeatures(KScreen::Config::Feature::PerOutputScaling | KScreen::Config::Feature::Writable);
QVERIFY(!config->supportedFeatures().testFlag(KScreen::Config::Feature::None));
QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::Writable));
QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::PerOutputScaling));
config->setSupportedFeatures(KScreen::Config::Feature::PerOutputScaling | KScreen::Config::Feature::Writable | KScreen::Config::Feature::PrimaryDisplay);
QVERIFY(!config->supportedFeatures().testFlag(KScreen::Config::Feature::None));
QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::Writable));
QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::PrimaryDisplay));
QVERIFY(config->supportedFeatures().testFlag(KScreen::Config::Feature::PerOutputScaling));
}
void testScreenConfig::testInvalidMode()
{
ModeList modes;
ModePtr invalidMode = modes.value(QStringLiteral("99"));
QVERIFY(invalidMode.isNull());
auto output = new KScreen::Output();
auto currentMode = output->currentMode();
QVERIFY(currentMode.isNull());
QVERIFY(!currentMode);
delete output;
}
void testScreenConfig::testOutputPositionNormalization()
{
qputenv("KSCREEN_BACKEND_ARGS", "TEST_DATA=" TEST_DATA "multipleoutput.json");
const ConfigPtr config = getConfig();
QVERIFY(!config.isNull());
auto left = config->outputs().first();
auto right = config->outputs().last();
QVERIFY(!left.isNull());
QVERIFY(!right.isNull());
left->setPos(QPoint(-5000, 700));
right->setPos(QPoint(-3720, 666));
QCOMPARE(left->pos(), QPoint(-5000, 700));
QCOMPARE(right->pos(), QPoint(-3720, 666));
// start a set operation to fix up the positions
{
auto setop = new SetConfigOperation(config);
setop->exec();
}
QCOMPARE(left->pos(), QPoint(0, 34));
QCOMPARE(right->pos(), QPoint(1280, 0));
// make sure it doesn't touch a valid config
{
auto setop = new SetConfigOperation(config);
setop->exec();
}
QCOMPARE(left->pos(), QPoint(0, 34));
QCOMPARE(right->pos(), QPoint(1280, 0));
// positions of single outputs should be at 0, 0
left->setEnabled(false);
{
auto setop = new SetConfigOperation(config);
setop->exec();
}
QCOMPARE(right->pos(), QPoint());
}
QTEST_MAIN(testScreenConfig)
#include "testscreenconfig.moc"

65
autotests/testxrandr.cpp Normal file
View File

@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#define QT_GUI_LIB
#include <QObject>
#include <QtTest>
#include "../src/config.h"
#include "../src/getconfigoperation.h"
#include "../src/mode.h"
#include "../src/output.h"
using namespace KScreen;
class testXRandR : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void singleOutput();
private:
QProcess m_process;
};
void testXRandR::initTestCase()
{
}
void testXRandR::singleOutput()
{
qputenv("KSCREEN_BACKEND", "XRandR");
GetConfigOperation *op = new GetConfigOperation();
QVERIFY(op->exec());
const ConfigPtr config = op->config();
if (!config) {
QSKIP("XRandR X extension is not available", SkipAll);
}
QCOMPARE(config->outputs().count(), 1);
const OutputPtr output = config->outputs().take(327);
QCOMPARE(output->name(), QString("default"));
QCOMPARE(output->type(), Output::Unknown);
QCOMPARE(output->modes().count(), 15);
QCOMPARE(output->pos(), QPoint(0, 0));
QCOMPARE(output->currentModeId(), QLatin1String("338"));
QCOMPARE(output->rotation(), Output::None);
QCOMPARE(output->isConnected(), true);
QCOMPARE(output->isEnabled(), true);
QCOMPARE(output->isPrimary(), false);
QVERIFY2(output->clones().isEmpty(), "In singleOutput is impossible to have clones");
}
QTEST_MAIN(testXRandR)
#include "testxrandr.moc"

11
backends/CMakeLists.txt Normal file
View File

@ -0,0 +1,11 @@
add_subdirectory(fake)
add_subdirectory(qscreen)
add_subdirectory(kwayland)
if(${XCB_RANDR_FOUND})
message(STATUS "Will build xrandr backend.")
add_subdirectory(xrandr)
add_subdirectory(xrandr1.1)
else()
message(STATUS "Not building xrandr backend, no XCB_RANDR_FOUND set.")
endif()

View File

@ -0,0 +1,29 @@
include_directories(${CMAKE_SOURCE_DIR}/src
${CMAKE_BUILD_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
set(fake_SRCS
fake.cpp
parser.cpp
fake.h
parser.h
)
qt_add_dbus_adaptor(fake_SRCS ${CMAKE_SOURCE_DIR}/interfaces/org.kde.KScreen.FakeBackend.xml
fake.h Fake
)
add_library(KSC_Fake MODULE ${fake_SRCS})
set_target_properties(KSC_Fake PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kscreen")
set_target_properties(KSC_Fake PROPERTIES PREFIX "")
target_link_libraries(KSC_Fake
Qt::Core
Qt::DBus
KF5::Screen
)
install(TARGETS KSC_Fake DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kscreen/)

189
backends/fake/fake.cpp Normal file
View File

@ -0,0 +1,189 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "fake.h"
#include "parser.h"
#include "config.h"
#include "edid.h"
#include <output.h>
#include <stdlib.h>
#include <QFile>
#include <QTimer>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDBusConnection>
#include "fakebackendadaptor.h"
using namespace KScreen;
Q_LOGGING_CATEGORY(KSCREEN_FAKE, "kscreen.fake")
Fake::Fake()
: KScreen::AbstractBackend()
{
QLoggingCategory::setFilterRules(QStringLiteral("kscreen.fake.debug = true"));
if (qgetenv("KSCREEN_BACKEND_INPROCESS") != QByteArray("1")) {
QTimer::singleShot(0, this, &Fake::delayedInit);
}
}
void Fake::init(const QVariantMap &arguments)
{
if (!mConfig.isNull()) {
mConfig.clear();
}
mConfigFile = arguments[QStringLiteral("TEST_DATA")].toString();
qCDebug(KSCREEN_FAKE) << "Fake profile file:" << mConfigFile;
}
void Fake::delayedInit()
{
new FakeBackendAdaptor(this);
QDBusConnection::sessionBus().registerObject(QStringLiteral("/fake"), this);
}
Fake::~Fake()
{
}
QString Fake::name() const
{
return QStringLiteral("Fake");
}
QString Fake::serviceName() const
{
return QStringLiteral("org.kde.KScreen.Backend.Fake");
}
ConfigPtr Fake::config() const
{
if (mConfig.isNull()) {
mConfig = Parser::fromJson(mConfigFile);
}
return mConfig;
}
void Fake::setConfig(const ConfigPtr &config)
{
qCDebug(KSCREEN_FAKE) << "set config" << config->outputs();
mConfig = config->clone();
Q_EMIT configChanged(mConfig);
}
bool Fake::isValid() const
{
return true;
}
QByteArray Fake::edid(int outputId) const
{
Q_UNUSED(outputId);
QFile file(mConfigFile);
file.open(QIODevice::ReadOnly);
const QJsonDocument jsonDoc = QJsonDocument::fromJson(file.readAll());
const QJsonObject json = jsonDoc.object();
const QJsonArray outputs = json[QStringLiteral("outputs")].toArray();
for (const QJsonValue &value : outputs) {
const QVariantMap output = value.toObject().toVariantMap();
if (output[QStringLiteral("id")].toInt() != outputId) {
continue;
}
return QByteArray::fromBase64(output[QStringLiteral("edid")].toByteArray());
}
return QByteArray();
}
void Fake::setConnected(int outputId, bool connected)
{
KScreen::OutputPtr output = config()->output(outputId);
if (output->isConnected() == connected) {
return;
}
output->setConnected(connected);
qCDebug(KSCREEN_FAKE) << "emitting configChanged in Fake";
Q_EMIT configChanged(mConfig);
}
void Fake::setEnabled(int outputId, bool enabled)
{
KScreen::OutputPtr output = config()->output(outputId);
if (output->isEnabled() == enabled) {
return;
}
output->setEnabled(enabled);
Q_EMIT configChanged(mConfig);
}
void Fake::setPrimary(int outputId, bool primary)
{
KScreen::OutputPtr output = config()->output(outputId);
if (output->isPrimary() == primary) {
return;
}
for (KScreen::OutputPtr output : config()->outputs()) {
if (output->id() == outputId) {
output->setPrimary(primary);
} else {
output->setPrimary(false);
}
}
Q_EMIT configChanged(mConfig);
}
void Fake::setCurrentModeId(int outputId, const QString &modeId)
{
KScreen::OutputPtr output = config()->output(outputId);
if (output->currentModeId() == modeId) {
return;
}
output->setCurrentModeId(modeId);
Q_EMIT configChanged(mConfig);
}
void Fake::setRotation(int outputId, int rotation)
{
KScreen::OutputPtr output = config()->output(outputId);
const KScreen::Output::Rotation rot = static_cast<KScreen::Output::Rotation>(rotation);
if (output->rotation() == rot) {
return;
}
output->setRotation(rot);
Q_EMIT configChanged(mConfig);
}
void Fake::addOutput(int outputId, const QString &name)
{
KScreen::OutputPtr output(new KScreen::Output);
output->setId(outputId);
output->setName(name);
mConfig->addOutput(output);
Q_EMIT configChanged(mConfig);
}
void Fake::removeOutput(int outputId)
{
mConfig->removeOutput(outputId);
Q_EMIT configChanged(mConfig);
}

49
backends/fake/fake.h Normal file
View File

@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef FAKE_BACKEND_H
#define FAKE_BACKEND_H
#include "abstractbackend.h"
#include <QLoggingCategory>
#include <QObject>
class Fake : public KScreen::AbstractBackend
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.kf5.kscreen.backends.fake")
public:
explicit Fake();
~Fake() override;
void init(const QVariantMap &arguments) override;
QString name() const override;
QString serviceName() const override;
KScreen::ConfigPtr config() const override;
void setConfig(const KScreen::ConfigPtr &config) override;
QByteArray edid(int outputId) const override;
bool isValid() const override;
void setConnected(int outputId, bool connected);
void setEnabled(int outputId, bool enabled);
void setPrimary(int outputId, bool primary);
void setCurrentModeId(int outputId, const QString &modeId);
void setRotation(int outputId, int rotation);
void addOutput(int outputId, const QString &name);
void removeOutput(int outputId);
private Q_SLOTS:
void delayedInit();
private:
QString mConfigFile;
mutable KScreen::ConfigPtr mConfig;
};
Q_DECLARE_LOGGING_CATEGORY(KSCREEN_FAKE)
#endif // FAKE_BACKEND_H

246
backends/fake/parser.cpp Normal file
View File

@ -0,0 +1,246 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "parser.h"
#include "fake.h"
#include "config.h"
#include "output.h"
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLoggingCategory>
#include <QMetaObject>
#include <QMetaProperty>
using namespace KScreen;
ConfigPtr Parser::fromJson(const QByteArray &data)
{
ConfigPtr config(new Config);
const QJsonObject json = QJsonDocument::fromJson(data).object();
ScreenPtr screen = Parser::screenFromJson(json[QStringLiteral("screen")].toObject().toVariantMap());
config->setScreen(screen);
const QVariantList outputs = json[QStringLiteral("outputs")].toArray().toVariantList();
if (outputs.isEmpty()) {
return config;
}
OutputList outputList;
for (const QVariant &value : outputs) {
const OutputPtr output = Parser::outputFromJson(value.toMap());
outputList.insert(output->id(), output);
}
config->setOutputs(outputList);
return config;
}
ConfigPtr Parser::fromJson(const QString &path)
{
QFile file(path);
if (!file.open(QIODevice::ReadOnly)) {
qWarning() << file.errorString();
qWarning() << "File: " << path;
return ConfigPtr();
}
return Parser::fromJson(file.readAll());
}
ScreenPtr Parser::screenFromJson(const QVariantMap &data)
{
ScreenPtr screen(new Screen);
screen->setId(data[QStringLiteral("id")].toInt());
screen->setMinSize(Parser::sizeFromJson(data[QStringLiteral("minSize")].toMap()));
screen->setMaxSize(Parser::sizeFromJson(data[QStringLiteral("maxSize")].toMap()));
screen->setCurrentSize(Parser::sizeFromJson(data[QStringLiteral("currentSize")].toMap()));
screen->setMaxActiveOutputsCount(data[QStringLiteral("maxActiveOutputsCount")].toInt());
return screen;
}
void Parser::qvariant2qobject(const QVariantMap &variant, QObject *object)
{
const QMetaObject *metaObject = object->metaObject();
for (QVariantMap::const_iterator iter = variant.begin(); iter != variant.end(); ++iter) {
const int propertyIndex = metaObject->indexOfProperty(qPrintable(iter.key()));
if (propertyIndex == -1) {
// qWarning() << "Skipping non-existent property" << iter.key();
continue;
}
const QMetaProperty metaProperty = metaObject->property(propertyIndex);
if (!metaProperty.isWritable()) {
// qWarning() << "Skipping read-only property" << iter.key();
continue;
}
const QVariant property = object->property(iter.key().toLatin1().constData());
Q_ASSERT(property.isValid());
if (property.isValid()) {
QVariant value = iter.value();
if (value.canConvert(property.type())) {
value.convert(property.type());
object->setProperty(iter.key().toLatin1().constData(), value);
} else if (QLatin1String("QVariant") == QLatin1String(property.typeName())) {
object->setProperty(iter.key().toLatin1().constData(), value);
}
}
}
}
OutputPtr Parser::outputFromJson(QMap<QString, QVariant> map)
{
OutputPtr output(new Output);
output->setId(map[QStringLiteral("id")].toInt());
QStringList preferredModes;
const QVariantList prefModes = map[QStringLiteral("preferredModes")].toList();
for (const QVariant &mode : prefModes) {
preferredModes.append(mode.toString());
}
output->setPreferredModes(preferredModes);
map.remove(QStringLiteral("preferredModes"));
ModeList modelist;
const QVariantList modes = map[QStringLiteral("modes")].toList();
for (const QVariant &modeValue : modes) {
const ModePtr mode = Parser::modeFromJson(modeValue);
modelist.insert(mode->id(), mode);
}
output->setModes(modelist);
map.remove(QStringLiteral("modes"));
if (map.contains(QStringLiteral("clones"))) {
QList<int> clones;
for (const QVariant &id : map[QStringLiteral("clones")].toList()) {
clones.append(id.toInt());
}
output->setClones(clones);
map.remove(QStringLiteral("clones"));
}
const QByteArray type = map[QStringLiteral("type")].toByteArray().toUpper();
if (type.contains("LVDS") || type.contains("EDP") || type.contains("IDP") || type.contains("7")) {
output->setType(Output::Panel);
} else if (type.contains("VGA")) {
output->setType(Output::VGA);
} else if (type.contains("DVI")) {
output->setType(Output::DVI);
} else if (type.contains("DVI-I")) {
output->setType(Output::DVII);
} else if (type.contains("DVI-A")) {
output->setType(Output::DVIA);
} else if (type.contains("DVI-D")) {
output->setType(Output::DVID);
} else if (type.contains("HDMI") || type.contains("6")) {
output->setType(Output::HDMI);
} else if (type.contains("Panel")) {
output->setType(Output::Panel);
} else if (type.contains("TV")) {
output->setType(Output::TV);
} else if (type.contains("TV-Composite")) {
output->setType(Output::TVComposite);
} else if (type.contains("TV-SVideo")) {
output->setType(Output::TVSVideo);
} else if (type.contains("TV-Component")) {
output->setType(Output::TVComponent);
} else if (type.contains("TV-SCART")) {
output->setType(Output::TVSCART);
} else if (type.contains("TV-C4")) {
output->setType(Output::TVC4);
} else if (type.contains("DisplayPort") || type.contains("14")) {
output->setType(Output::DisplayPort);
} else if (type.contains("Unknown")) {
output->setType(Output::Unknown);
} else {
qCWarning(KSCREEN_FAKE) << "Output Type not translated:" << type;
}
map.remove(QStringLiteral("type"));
if (map.contains(QStringLiteral("pos"))) {
output->setPos(Parser::pointFromJson(map[QStringLiteral("pos")].toMap()));
map.remove(QStringLiteral("pos"));
}
if (map.contains(QStringLiteral("size"))) {
output->setSize(Parser::sizeFromJson(map[QStringLiteral("size")].toMap()));
map.remove(QStringLiteral("size"));
}
auto scale = QStringLiteral("scale");
if (map.contains(scale)) {
qDebug() << "Scale found:" << map[scale].toReal();
output->setScale(map[scale].toReal());
map.remove(scale);
}
// Remove some extra properties that we do not want or need special treatment
map.remove(QStringLiteral("edid"));
Parser::qvariant2qobject(map, output.data());
return output;
}
ModePtr Parser::modeFromJson(const QVariant &data)
{
const QVariantMap map = data.toMap();
ModePtr mode(new Mode);
Parser::qvariant2qobject(map, mode.data());
mode->setSize(Parser::sizeFromJson(map[QStringLiteral("size")].toMap()));
return mode;
}
QSize Parser::sizeFromJson(const QVariant &data)
{
const QVariantMap map = data.toMap();
QSize size;
size.setWidth(map[QStringLiteral("width")].toInt());
size.setHeight(map[QStringLiteral("height")].toInt());
return size;
}
QPoint Parser::pointFromJson(const QVariant &data)
{
const QVariantMap map = data.toMap();
QPoint point;
point.setX(map[QStringLiteral("x")].toInt());
point.setY(map[QStringLiteral("y")].toInt());
return point;
}
QRect Parser::rectFromJson(const QVariant &data)
{
QRect rect;
rect.setSize(Parser::sizeFromJson(data));
rect.setBottomLeft(Parser::pointFromJson(data));
return rect;
}
bool Parser::validate(const QByteArray &data)
{
Q_UNUSED(data);
return true;
}
bool Parser::validate(const QString &data)
{
Q_UNUSED(data);
return true;
}

37
backends/fake/parser.h Normal file
View File

@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef PARSER_H
#define PARSER_H
#include <QByteArray>
#include <QPoint>
#include <QRect>
#include <QSize>
#include <QString>
#include <QVariant>
#include "types.h"
class Parser
{
public:
static KScreen::ConfigPtr fromJson(const QByteArray &data);
static KScreen::ConfigPtr fromJson(const QString &path);
static bool validate(const QByteArray &data);
static bool validate(const QString &data);
private:
static void qvariant2qobject(const QVariantMap &variant, QObject *object);
static KScreen::ScreenPtr screenFromJson(const QMap<QString, QVariant> &data);
static KScreen::OutputPtr outputFromJson(QMap<QString, QVariant> data /* sic */);
static KScreen::ModePtr modeFromJson(const QVariant &data);
static QSize sizeFromJson(const QVariant &data);
static QRect rectFromJson(const QVariant &data);
static QPoint pointFromJson(const QVariant &data);
};
#endif // PARSER_H

View File

@ -0,0 +1,46 @@
set(wayland_SRCS
waylandbackend.cpp
waylandconfig.cpp
waylandoutputdevice.cpp
waylandoutputmanagement.cpp
waylandoutputdevicemode.cpp
waylandscreen.cpp
../utils.cpp
)
qt_add_dbus_interface(wayland_SRCS org.kde.KWin.TabletModeManager.xml tabletmodemanager_interface)
ecm_qt_declare_logging_category(wayland_SRCS
HEADER kscreen_kwayland_logging.h
IDENTIFIER KSCREEN_WAYLAND
CATEGORY_NAME kscreen.kwayland
)
ecm_add_qtwayland_client_protocol(wayland_SRCS
PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-output-device-v2.xml
BASENAME kde-output-device-v2
)
ecm_add_qtwayland_client_protocol(wayland_SRCS
PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-output-management-v2.xml
BASENAME kde-output-management-v2
)
ecm_add_qtwayland_client_protocol(wayland_SRCS
PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-primary-output-v1.xml
BASENAME kde-primary-output-v1
)
add_library(KSC_KWayland MODULE ${wayland_SRCS})
set_target_properties(KSC_KWayland PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kscreen")
set_target_properties(KSC_KWayland PROPERTIES PREFIX "")
target_link_libraries(KSC_KWayland Qt::Core
Qt::DBus
Qt::Gui
KF5::Screen
KF5::WaylandClient
Wayland::Client
)
install(TARGETS KSC_KWayland DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kscreen/)

View File

@ -0,0 +1,49 @@
# Design of libkscreen's Wayland backend
This backend uses KWayland's OutputManagement protocol for listing and
configuring devices. This is described here.
## Listing outputs
KScreen's outputs are created from KWayland::Client::OutputDevice objects,
they copy the data into kscreen's Outputs, and update these objects. A list
of outputs is requested from the client Registry object.
## Configuring outputs
The backend asks the global OutputManagement interface for an OutputConfiguration
object, then sets the changes per outputdevice on this object, and asks the
compositor to apply() this configuration.
For this to work, the compositor should support the Wayland org_kde_kwin_outputdevice
and org_kde_kwin_outputmanagement protocols, for example through
KWayland::Server classes OutputDevice, OutputManagmenent and OuputConfiguration.
## General working
WaylandBackend creates a global static internal config, available through
WaylandBackend::internalConfig(). WaylandConfig binds to the wl_registry
callbacks and catches org_kde_kwin_outputdevice creation and destruction.
It passes org_kde_kwin_outputdevice creation and removal on to
WB::internalConfig() to handle its internal data representation as WaylandOutput.
WaylandOutput binds to org_kde_kwin_outputdevice's callback, and gets notified
of geometry and modes, including changes. WaylandOutput administrates the
internal representation of these objects, and invokes the global notifier,
which then runs the pointers it holds through the updateK* methods in
Wayland{Screen,Output,...}.
KScreen:{Screen,Output,Edid,Mode} objects are created from the internal
representation as requested (usually triggered by the creation of a
KScreen::Config object through KScreen::Config::current()). As with other
backends, the objects which are handed out to the lib's user are expected
to be deleted by the user, the backend only takes ownership of its internal
data representation objects.
## Note about scope of output ids
The ids of the outputdevices are internal to the wayland backend. The id is
generated in the wayland backend, and does not match kwin's output ids. Do
not try to read kwin's config from here.
<sebas@kde.org>

View File

@ -0,0 +1,13 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.KWin.TabletModeManager">
<property name="tabletModeAvailable" type="b" access="read"/>
<property name="tabletMode" type="b" access="read"/>
<signal name="tabletModeAvailableChanged">
<arg name="tabletModeAvailable" type="b" direction="out"/>
</signal>
<signal name="tabletModeChanged">
<arg name="tabletMode" type="b" direction="out"/>
</signal>
</interface>
</node>

View File

@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
* SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
* SPDX-FileCopyrightText: 2014-2015 Sebastian Kügler <sebas@kde.org>
* SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "waylandbackend.h"
#include "waylandconfig.h"
#include "waylandoutputdevice.h"
#include "kscreen_kwayland_logging.h"
#include <configmonitor.h>
#include <mode.h>
#include <QSettings>
#include <QStandardPaths>
using namespace KScreen;
WaylandBackend::WaylandBackend()
: KScreen::AbstractBackend()
, m_internalConfig(new WaylandConfig(this))
{
qCDebug(KSCREEN_WAYLAND) << "Loading Wayland backend.";
connect(m_internalConfig, &WaylandConfig::configChanged, this, [this]() {
Q_EMIT configChanged(m_internalConfig->currentConfig());
});
}
QString WaylandBackend::name() const
{
return QStringLiteral("kwayland");
}
QString WaylandBackend::serviceName() const
{
return QStringLiteral("org.kde.KScreen.Backend.KWayland");
}
ConfigPtr WaylandBackend::config() const
{
// Note: This should ONLY be called from GetConfigOperation!
return m_internalConfig->currentConfig();
}
void WaylandBackend::setConfig(const KScreen::ConfigPtr &newconfig)
{
if (!newconfig) {
return;
}
// wait for KWin reply
QEventLoop loop;
connect(m_internalConfig, &WaylandConfig::configChanged, &loop, &QEventLoop::quit);
m_internalConfig->applyConfig(newconfig);
loop.exec();
}
QByteArray WaylandBackend::edid(int outputId) const
{
WaylandOutputDevice *output = m_internalConfig->outputMap().value(outputId);
if (!output) {
return QByteArray();
}
return output->edid();
}
bool WaylandBackend::isValid() const
{
return m_internalConfig->isReady();
}

View File

@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
* SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
* SPDX-FileCopyrightText: 2014-2015 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#pragma once
#include "abstractbackend.h"
#include <QLoggingCategory>
namespace KScreen
{
class WaylandConfig;
class WaylandBackend : public KScreen::AbstractBackend
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.kf5.kscreen.backends.kwayland")
public:
explicit WaylandBackend();
~WaylandBackend() override = default;
QString name() const override;
QString serviceName() const override;
KScreen::ConfigPtr config() const override;
void setConfig(const KScreen::ConfigPtr &config) override;
bool isValid() const override;
QByteArray edid(int outputId) const override;
private:
WaylandConfig *m_internalConfig;
};
}
Q_DECLARE_LOGGING_CATEGORY(KSCREEN_WAYLAND)

View File

@ -0,0 +1,342 @@
/*
* SPDX-FileCopyrightText: 2014-2015 Sebastian Kügler <sebas@kde.org>
* SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
* SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "waylandconfig.h"
#include "kscreen_kwayland_logging.h"
#include "waylandbackend.h"
#include "waylandoutputdevice.h"
#include "waylandoutputmanagement.h"
#include "waylandscreen.h"
#include "output.h"
#include "tabletmodemanager_interface.h"
#include <QThread>
#include <configmonitor.h>
#include <mode.h>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/event_queue.h>
#include <KWayland/Client/registry.h>
#include <QTimer>
using namespace KScreen;
WaylandConfig::WaylandConfig(QObject *parent)
: QObject(parent)
, m_outputManagement(nullptr)
, m_registryInitialized(false)
, m_blockSignals(true)
, m_kscreenConfig(new Config)
, m_kscreenPendingConfig(nullptr)
, m_screen(new WaylandScreen(this))
, m_tabletModeAvailable(false)
, m_tabletModeEngaged(false)
{
initKWinTabletMode();
connect(this, &WaylandConfig::initialized, &m_syncLoop, &QEventLoop::quit);
QTimer::singleShot(3000, this, [this] {
if (m_syncLoop.isRunning()) {
qCWarning(KSCREEN_WAYLAND) << "Connection to Wayland server timed out.";
m_syncLoop.quit();
}
});
initConnection();
m_syncLoop.exec();
}
WaylandConfig::~WaylandConfig()
{
m_syncLoop.quit();
}
void WaylandConfig::initKWinTabletMode()
{
auto *interface =
new OrgKdeKWinTabletModeManagerInterface(QStringLiteral("org.kde.KWin"), QStringLiteral("/org/kde/KWin"), QDBusConnection::sessionBus(), this);
if (!interface->isValid()) {
m_tabletModeAvailable = false;
m_tabletModeEngaged = false;
return;
}
m_tabletModeAvailable = interface->tabletModeAvailable();
m_tabletModeEngaged = interface->tabletMode();
connect(interface, &OrgKdeKWinTabletModeManagerInterface::tabletModeChanged, this, [this](bool tabletMode) {
if (m_tabletModeEngaged == tabletMode) {
return;
}
m_tabletModeEngaged = tabletMode;
if (!m_blockSignals && m_initializingOutputs.empty()) {
Q_EMIT configChanged();
}
});
connect(interface, &OrgKdeKWinTabletModeManagerInterface::tabletModeAvailableChanged, this, [this](bool available) {
if (m_tabletModeAvailable == available) {
return;
}
m_tabletModeAvailable = available;
if (!m_blockSignals && m_initializingOutputs.empty()) {
Q_EMIT configChanged();
}
});
}
void WaylandConfig::initConnection()
{
m_connection = KWayland::Client::ConnectionThread::fromApplication(this);
setupRegistry();
}
void WaylandConfig::blockSignals()
{
Q_ASSERT(m_blockSignals == false);
m_blockSignals = true;
}
void WaylandConfig::unblockSignals()
{
Q_ASSERT(m_blockSignals == true);
m_blockSignals = false;
}
void WaylandConfig::setupRegistry()
{
if (!m_connection) {
return;
}
m_registry = new KWayland::Client::Registry(this);
connect(m_registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this](const QByteArray &interface, quint32 name, quint32 version) {
if (interface == WaylandOutputDevice::interface()->name) {
addOutput(name, std::min(2u, version));
}
if (interface == WaylandOutputManagement::interface()->name) {
m_outputManagement = new WaylandOutputManagement(m_registry->registry(), name, std::min(2u, version));
}
if (interface == WaylandPrimaryOutput::interface()->name) {
m_primaryOutput.reset(new WaylandPrimaryOutput(m_registry->registry(), name, std::min(1u, version)));
connect(m_primaryOutput.data(), &WaylandPrimaryOutput::primaryOutputChanged, this, [this](const QString &name) {
if (m_primaryOutputName == name) {
return;
}
m_primaryOutputName = name;
for (auto output : qAsConst(m_outputMap)) {
output->setPrimary(output->outputName() == name);
}
if (!m_blockSignals) {
Q_EMIT configChanged();
}
});
}
});
connect(m_registry, &KWayland::Client::Registry::interfacesAnnounced, this, [this] {
m_registryInitialized = true;
unblockSignals();
checkInitialized();
});
m_registry->create(m_connection);
m_registry->setup();
}
int s_outputId = 0;
void WaylandConfig::addOutput(quint32 name, quint32 version)
{
qCDebug(KSCREEN_WAYLAND) << "adding output" << name;
auto device = new WaylandOutputDevice(++s_outputId);
m_initializingOutputs << device;
connect(m_registry, &KWayland::Client::Registry::interfaceRemoved, this, [name, device, this](const quint32 &interfaceName) {
if (name == interfaceName) {
removeOutput(device);
}
});
QMetaObject::Connection *const connection = new QMetaObject::Connection;
*connection = connect(device, &WaylandOutputDevice::done, this, [this, connection, device]() {
QObject::disconnect(*connection);
delete connection;
device->setPrimary(m_primaryOutputName == device->outputName());
m_initializingOutputs.removeOne(device);
m_outputMap.insert(device->id(), device);
checkInitialized();
if (!m_blockSignals && m_initializingOutputs.isEmpty()) {
m_screen->setOutputs(m_outputMap.values());
Q_EMIT configChanged();
}
connect(device, &WaylandOutputDevice::done, this, [this]() {
// output got update must update current config
if (!m_blockSignals) {
Q_EMIT configChanged();
}
});
});
device->init(*m_registry, name, version);
}
void WaylandConfig::removeOutput(WaylandOutputDevice *output)
{
qCDebug(KSCREEN_WAYLAND) << "removing output" << output->name();
if (m_initializingOutputs.removeOne(output)) {
// output was not yet fully initialized, just remove here and return
delete output;
return;
}
// remove the output from output mapping
const auto removedOutput = m_outputMap.take(output->id());
Q_ASSERT(removedOutput == output);
Q_UNUSED(removedOutput);
m_screen->setOutputs(m_outputMap.values());
delete output;
if (!m_blockSignals) {
Q_EMIT configChanged();
}
}
bool WaylandConfig::isReady() const
{
// clang-format off
return !m_blockSignals
&& m_registryInitialized
&& m_initializingOutputs.isEmpty()
&& m_outputMap.count() > 0
&& m_outputManagement != nullptr;
// clang-format on
}
void WaylandConfig::checkInitialized()
{
if (!m_initialized && isReady()) {
m_initialized = true;
m_screen->setOutputs(m_outputMap.values());
Q_EMIT initialized();
}
}
KScreen::ConfigPtr WaylandConfig::currentConfig()
{
m_kscreenConfig->setScreen(m_screen->toKScreenScreen(m_kscreenConfig));
const auto features = Config::Feature::Writable | Config::Feature::PerOutputScaling | Config::Feature::AutoRotation | Config::Feature::TabletMode
| Config::Feature::PrimaryDisplay | Config::Feature::SynchronousOutputChanges;
m_kscreenConfig->setSupportedFeatures(features);
m_kscreenConfig->setValid(m_connection->display());
KScreen::ScreenPtr screen = m_kscreenConfig->screen();
m_screen->updateKScreenScreen(screen);
// Removing removed outputs
const KScreen::OutputList outputs = m_kscreenConfig->outputs();
for (const auto &output : outputs) {
if (!m_outputMap.contains(output->id())) {
m_kscreenConfig->removeOutput(output->id());
}
}
// Add KScreen::Outputs that aren't in the list yet
KScreen::OutputList kscreenOutputs = m_kscreenConfig->outputs();
for (const auto &output : m_outputMap) {
KScreen::OutputPtr &kscreenOutput = kscreenOutputs[output->id()];
if (!kscreenOutput) {
kscreenOutput = output->toKScreenOutput();
} else {
output->updateKScreenOutput(kscreenOutput);
}
}
m_kscreenConfig->setOutputs(kscreenOutputs);
m_kscreenConfig->setTabletModeAvailable(m_tabletModeAvailable);
m_kscreenConfig->setTabletModeEngaged(m_tabletModeEngaged);
return m_kscreenConfig;
}
QMap<int, WaylandOutputDevice *> WaylandConfig::outputMap() const
{
return m_outputMap;
}
void WaylandConfig::tryPendingConfig()
{
if (!m_kscreenPendingConfig) {
return;
}
applyConfig(m_kscreenPendingConfig);
m_kscreenPendingConfig = nullptr;
}
WaylandOutputDevice *WaylandConfig::findOutputDevice(struct ::kde_output_device_v2 *outputdevice) const
{
for (WaylandOutputDevice *device : m_outputMap) {
if (device->object() == outputdevice) {
return device;
}
}
return nullptr;
}
void WaylandConfig::applyConfig(const KScreen::ConfigPtr &newConfig)
{
using namespace KWayland::Client;
// Create a new configuration object
auto wlConfig = m_outputManagement->createConfiguration();
bool changed = false;
if (m_blockSignals) {
// Last apply still pending, remember new changes and apply afterwards
m_kscreenPendingConfig = newConfig;
return;
}
for (const auto &output : newConfig->outputs()) {
changed |= m_outputMap[output->id()]->setWlConfig(wlConfig, output);
}
if (!changed) {
return;
}
// We now block changes in order to compress events while the compositor is doing its thing
// once it's done or failed, we'll trigger configChanged() only once, and not per individual
// property change.
connect(wlConfig, &WaylandOutputConfiguration::applied, this, [this, wlConfig] {
wlConfig->deleteLater();
unblockSignals();
Q_EMIT configChanged();
tryPendingConfig();
});
connect(wlConfig, &WaylandOutputConfiguration::failed, this, [this, wlConfig] {
wlConfig->deleteLater();
unblockSignals();
Q_EMIT configChanged();
tryPendingConfig();
});
// Now block signals and ask the compositor to apply the changes.
blockSignals();
wlConfig->apply();
}

View File

@ -0,0 +1,113 @@
/*
* SPDX-FileCopyrightText: 2014-2015 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#pragma once
#include "abstractbackend.h"
#include "config.h"
#include <QDir>
#include <QEventLoop>
#include <QLoggingCategory>
#include <QScreen>
#include <QSize>
#include <QSocketNotifier>
struct kde_output_device_v2;
namespace KWayland
{
namespace Client
{
class ConnectionThread;
class EventQueue;
class Registry;
class OutputManagement;
}
}
namespace KScreen
{
class Output;
class WaylandPrimaryOutput;
class WaylandOutputDevice;
class WaylandScreen;
class WaylandOutputManagement;
/**
* @class WaylandConfig
*
* This class holds the basic skeleton of the configuration and takes care of
* fetching the information from the Wayland server and synchronizing the
* configuration out to the "clients" that receive the config from the backend.
* We initialize a wayland connection, using a threaded event queue when
* querying the wayland server for data.
* Initially, the creation of a WaylandConfig blocks until all data has been
* received, signalled by the initialized() signal. This means that the
* wayland client has received information about all interfaces, and that all
* outputs are completely initialized. From then on, we properly notifyUpdate().
*/
class WaylandConfig : public QObject
{
Q_OBJECT
public:
explicit WaylandConfig(QObject *parent = nullptr);
~WaylandConfig() override;
KScreen::ConfigPtr currentConfig();
QMap<int, WaylandOutputDevice *> outputMap() const;
void applyConfig(const KScreen::ConfigPtr &newConfig);
WaylandOutputDevice *findOutputDevice(struct ::kde_output_device_v2 *outputdevice) const;
bool isReady() const;
Q_SIGNALS:
void configChanged();
void initialized();
private:
void setupRegistry();
void checkInitialized();
void disconnected();
void initKWinTabletMode();
void initConnection();
void addOutput(quint32 name, quint32 version);
void removeOutput(WaylandOutputDevice *output);
void blockSignals();
void unblockSignals();
void tryPendingConfig();
KWayland::Client::ConnectionThread *m_connection;
KWayland::Client::Registry *m_registry;
WaylandOutputManagement *m_outputManagement = nullptr;
QScopedPointer<WaylandPrimaryOutput> m_primaryOutput;
// KWayland names as keys
QMap<int, WaylandOutputDevice *> m_outputMap;
QString m_primaryOutputName;
// KWayland names
QList<WaylandOutputDevice *> m_initializingOutputs;
int m_lastOutputId = -1;
bool m_registryInitialized;
bool m_blockSignals;
QEventLoop m_syncLoop;
KScreen::ConfigPtr m_kscreenConfig;
KScreen::ConfigPtr m_kscreenPendingConfig;
WaylandScreen *m_screen;
bool m_tabletModeAvailable;
bool m_tabletModeEngaged;
bool m_initialized = false;
};
}

View File

@ -0,0 +1,423 @@
/*
* SPDX-FileCopyrightText: 2014-2015 Sebastian Kügler <sebas@kde.org>
* SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "waylandoutputdevice.h"
#include "../utils.h"
#include "waylandoutputmanagement.h"
#include "kscreen_kwayland_logging.h"
#include <wayland-server-protocol.h>
using namespace KScreen;
WaylandOutputDevice::WaylandOutputDevice(int id)
: QObject()
, kde_output_device_v2()
, m_id(id)
{
}
WaylandOutputDevice::~WaylandOutputDevice()
{
qDeleteAll(m_modes);
kde_output_device_v2_destroy(object());
}
void WaylandOutputDevice::kde_output_device_v2_geometry(int32_t x,
int32_t y,
int32_t physical_width,
int32_t physical_height,
int32_t subpixel,
const QString &make,
const QString &model,
int32_t transform)
{
m_pos = QPoint(x, y);
m_physicalSize = QSize(physical_width, physical_height);
m_subpixel = subpixel;
m_manufacturer = make;
m_model = model;
m_transform = transform;
}
void WaylandOutputDevice::kde_output_device_v2_current_mode(struct ::kde_output_device_mode_v2 *mode)
{
auto m = WaylandOutputDeviceMode::get(mode);
if (*m == *m_mode) {
// unchanged
return;
}
m_mode = m;
}
void WaylandOutputDevice::kde_output_device_v2_mode(struct ::kde_output_device_mode_v2 *mode)
{
WaylandOutputDeviceMode *m = new WaylandOutputDeviceMode(mode);
// last mode sent is the current one
m_mode = m;
m_modes.append(m);
connect(m, &WaylandOutputDeviceMode::removed, this, [this, m]() {
m_modes.removeOne(m);
if (m_mode == m) {
if (!m_modes.isEmpty()) {
m_mode = m_modes.first();
} else {
// was last mode
qFatal("KWaylandBackend: no output modes available anymore, this seems like a compositor bug");
}
}
delete m;
});
}
OutputPtr WaylandOutputDevice::toKScreenOutput()
{
OutputPtr output(new Output());
output->setId(m_id);
updateKScreenOutput(output);
return output;
}
Output::Rotation toKScreenRotation(int32_t transform)
{
switch (transform) {
case WL_OUTPUT_TRANSFORM_NORMAL:
return Output::None;
case WL_OUTPUT_TRANSFORM_90:
return Output::Left;
case WL_OUTPUT_TRANSFORM_180:
return Output::Inverted;
case WL_OUTPUT_TRANSFORM_270:
return Output::Right;
case WL_OUTPUT_TRANSFORM_FLIPPED:
qCWarning(KSCREEN_WAYLAND) << "flipped transform is unsupported by kscreen";
return Output::None;
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
qCWarning(KSCREEN_WAYLAND) << "flipped-90 transform is unsupported by kscreen";
return Output::Left;
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
qCWarning(KSCREEN_WAYLAND) << "flipped-180 transform is unsupported by kscreen";
return Output::Inverted;
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
qCWarning(KSCREEN_WAYLAND) << "flipped-270 transform is unsupported by kscreen";
return Output::Right;
default:
Q_UNREACHABLE();
}
}
wl_output_transform toKWaylandTransform(const Output::Rotation rotation)
{
switch (rotation) {
case Output::None:
return WL_OUTPUT_TRANSFORM_NORMAL;
case Output::Left:
return WL_OUTPUT_TRANSFORM_90;
case Output::Inverted:
return WL_OUTPUT_TRANSFORM_180;
case Output::Right:
return WL_OUTPUT_TRANSFORM_270;
default:
Q_UNREACHABLE();
}
}
void KScreen::WaylandOutputDevice::updateKScreenModes(OutputPtr &output)
{
ModeList modeList;
QStringList preferredModeIds;
QString currentModeId = QStringLiteral("-1");
int modeId = 0;
for (const WaylandOutputDeviceMode *wlMode : qAsConst(m_modes)) {
ModePtr mode(new Mode());
const QString modeIdStr = QString::number(modeId);
// KWayland gives the refresh rate as int in mHz
mode->setId(modeIdStr);
mode->setRefreshRate(wlMode->refreshRate() / 1000.0);
mode->setSize(wlMode->size());
mode->setName(modeName(wlMode));
if (m_mode == wlMode) {
currentModeId = modeIdStr;
}
if (wlMode->preferred()) {
preferredModeIds << modeIdStr;
}
// Add to the modelist which gets set on the output
modeList[modeIdStr] = mode;
modeId++;
}
output->setCurrentModeId(currentModeId);
output->setPreferredModes(preferredModeIds);
output->setModes(modeList);
}
void WaylandOutputDevice::updateKScreenOutput(OutputPtr &output)
{
// Initialize primary output
output->setId(m_id);
output->setEnabled(enabled());
output->setConnected(true);
output->setPrimary(m_isPrimary);
output->setName(name());
output->setSizeMm(m_physicalSize);
output->setPos(m_pos);
output->setRotation(toKScreenRotation(m_transform));
if (!output->edid()) {
output->setEdid(m_edid);
}
QSize currentSize = m_mode->size();
output->setSize(output->isHorizontal() ? currentSize : currentSize.transposed());
output->setScale(m_factor);
output->setType(Utils::guessOutputType(m_outputName, m_outputName));
output->setCapabilities(static_cast<Output::Capabilities>(static_cast<uint32_t>(m_flags)));
output->setOverscan(m_overscan);
output->setVrrPolicy(static_cast<Output::VrrPolicy>(m_vrr_policy));
output->setRgbRange(static_cast<Output::RgbRange>(m_rgbRange));
updateKScreenModes(output);
}
QString WaylandOutputDevice::modeId() const
{
return QString::number(m_modes.indexOf(m_mode));
}
WaylandOutputDeviceMode *WaylandOutputDevice::deviceModeFromId(const int modeId) const
{
return m_modes.at(modeId);
}
bool WaylandOutputDevice::setWlConfig(WaylandOutputConfiguration *wlConfig, const KScreen::OutputPtr &output)
{
bool changed = false;
// enabled?
if (enabled() != output->isEnabled()) {
changed = true;
wlConfig->enable(object(), output->isEnabled());
}
// position
if (globalPosition() != output->pos()) {
changed = true;
wlConfig->position(object(), output->pos().x(), output->pos().y());
}
// scale
if (!qFuzzyCompare(scale(), output->scale())) {
changed = true;
wlConfig->scale(object(), wl_fixed_from_double(output->scale()));
}
// rotation
if (toKScreenRotation(m_transform) != output->rotation()) {
changed = true;
wlConfig->transform(object(), toKWaylandTransform(output->rotation()));
}
// mode
const ModePtr mode = output->currentMode();
if (mode->size() != pixelSize() || mode->refreshRate() != refreshRate()) {
bool toIntOk;
int modeId = mode->id().toInt(&toIntOk);
Q_ASSERT(toIntOk);
changed = true;
wlConfig->mode(object(), deviceModeFromId(modeId)->object());
}
// overscan
if ((output->capabilities() & Output::Capability::Overscan) && overscan() != output->overscan()) {
wlConfig->overscan(object(), output->overscan());
changed = true;
}
// vrr
if ((output->capabilities() & Output::Capability::Vrr) && vrrPolicy() != static_cast<uint32_t>(output->vrrPolicy())) {
wlConfig->set_vrr_policy(object(), static_cast<uint32_t>(output->vrrPolicy()));
changed = true;
}
if ((output->capabilities() & Output::Capability::RgbRange) && rgbRange() != static_cast<uint32_t>(output->rgbRange())) {
wlConfig->set_rgb_range(object(), static_cast<uint32_t>(output->rgbRange()));
changed = true;
}
if (output->isPrimary()) {
wlConfig->set_primary_output(object());
changed = true;
}
return changed;
}
QString WaylandOutputDevice::modeName(const WaylandOutputDeviceMode *m) const
{
return QString::number(m->size().width()) + QLatin1Char('x') + QString::number(m->size().height()) + QLatin1Char('@')
+ QString::number(qRound(m->refreshRate() / 1000.0));
}
QString WaylandOutputDevice::name() const
{
return QStringLiteral("%1 %2").arg(m_manufacturer, m_model);
}
QString WaylandOutputDevice::outputName() const
{
return m_outputName;
}
QDebug operator<<(QDebug dbg, const WaylandOutputDevice *output)
{
dbg << "WaylandOutput(Id:" << output->id() << ", Name:" << QString(output->manufacturer() + QLatin1Char(' ') + output->model()) << ")";
return dbg;
}
bool WaylandOutputDevice::isPrimary() const
{
return m_isPrimary;
}
void WaylandOutputDevice::setPrimary(bool primary)
{
m_isPrimary = primary;
}
void WaylandOutputDevice::kde_output_device_v2_done()
{
Q_EMIT done();
}
void WaylandOutputDevice::kde_output_device_v2_scale(wl_fixed_t factor)
{
m_factor = wl_fixed_to_double(factor);
}
void WaylandOutputDevice::kde_output_device_v2_edid(const QString &edid)
{
m_edid = QByteArray::fromBase64(edid.toUtf8());
}
void WaylandOutputDevice::kde_output_device_v2_enabled(int32_t enabled)
{
m_enabled = enabled;
}
void WaylandOutputDevice::kde_output_device_v2_uuid(const QString &uuid)
{
m_uuid = uuid;
}
void WaylandOutputDevice::kde_output_device_v2_serial_number(const QString &serialNumber)
{
m_serialNumber = serialNumber;
}
void WaylandOutputDevice::kde_output_device_v2_eisa_id(const QString &eisaId)
{
m_eisaId = eisaId;
}
void WaylandOutputDevice::kde_output_device_v2_capabilities(uint32_t flags)
{
m_flags = flags;
}
void WaylandOutputDevice::kde_output_device_v2_overscan(uint32_t overscan)
{
m_overscan = overscan;
}
void WaylandOutputDevice::kde_output_device_v2_vrr_policy(uint32_t vrr_policy)
{
m_vrr_policy = vrr_policy;
}
void WaylandOutputDevice::kde_output_device_v2_rgb_range(uint32_t rgb_range)
{
m_rgbRange = rgb_range;
}
void WaylandOutputDevice::kde_output_device_v2_name(const QString &outputName)
{
m_outputName = outputName;
}
QByteArray WaylandOutputDevice::edid() const
{
return m_edid;
}
bool WaylandOutputDevice::enabled() const
{
return m_enabled;
}
int WaylandOutputDevice::id() const
{
return m_id;
}
qreal WaylandOutputDevice::scale() const
{
return m_factor;
}
QString WaylandOutputDevice::manufacturer() const
{
return m_manufacturer;
}
QString WaylandOutputDevice::model() const
{
return m_model;
}
QPoint WaylandOutputDevice::globalPosition() const
{
return m_pos;
}
QSize WaylandOutputDevice::pixelSize() const
{
return m_mode->size();
}
int WaylandOutputDevice::refreshRate() const
{
return m_mode->refreshRate();
}
uint32_t WaylandOutputDevice::vrrPolicy() const
{
return m_vrr_policy;
}
uint32_t WaylandOutputDevice::overscan() const
{
return m_overscan;
}
uint32_t WaylandOutputDevice::capabilities() const
{
return m_flags;
}
uint32_t WaylandOutputDevice::rgbRange() const
{
return m_rgbRange;
}

View File

@ -0,0 +1,120 @@
/*
* SPDX-FileCopyrightText: 2014-2015 Sebastian Kügler <sebas@kde.org>
* SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef WAYLANDOUTPUTDEVICE_H
#define WAYLANDOUTPUTDEVICE_H
#include "output.h"
#include "waylandoutputdevicemode.h"
#include "qwayland-kde-output-device-v2.h"
#include <QPoint>
#include <QSize>
namespace KScreen
{
class WaylandOutputConfiguration;
class WaylandOutputDevice : public QObject, public QtWayland::kde_output_device_v2
{
Q_OBJECT
public:
WaylandOutputDevice(int id);
~WaylandOutputDevice();
QByteArray edid() const;
bool enabled() const;
int id() const;
QString name() const;
QString outputName() const;
QString model() const;
QString manufacturer() const;
qreal scale() const;
QPoint globalPosition() const;
QSize pixelSize() const;
int refreshRate() const;
uint32_t vrrPolicy() const;
uint32_t overscan() const;
uint32_t capabilities() const;
uint32_t rgbRange() const;
OutputPtr toKScreenOutput();
void updateKScreenOutput(OutputPtr &output);
void updateKScreenModes(OutputPtr &output);
bool isPrimary() const;
void setPrimary(bool primary);
bool setWlConfig(WaylandOutputConfiguration *wlConfig, const KScreen::OutputPtr &output);
QString modeId() const;
QString uuid() const
{
return m_uuid;
}
Q_SIGNALS:
void done();
protected:
void kde_output_device_v2_geometry(int32_t x,
int32_t y,
int32_t physical_width,
int32_t physical_height,
int32_t subpixel,
const QString &make,
const QString &model,
int32_t transform) override;
void kde_output_device_v2_current_mode(struct ::kde_output_device_mode_v2 *mode) override;
void kde_output_device_v2_mode(struct ::kde_output_device_mode_v2 *mode) override;
void kde_output_device_v2_done() override;
void kde_output_device_v2_scale(wl_fixed_t factor) override;
void kde_output_device_v2_edid(const QString &raw) override;
void kde_output_device_v2_enabled(int32_t enabled) override;
void kde_output_device_v2_uuid(const QString &uuid) override;
void kde_output_device_v2_serial_number(const QString &serialNumber) override;
void kde_output_device_v2_eisa_id(const QString &eisaId) override;
void kde_output_device_v2_capabilities(uint32_t flags) override;
void kde_output_device_v2_overscan(uint32_t overscan) override;
void kde_output_device_v2_vrr_policy(uint32_t vrr_policy) override;
void kde_output_device_v2_rgb_range(uint32_t rgb_range) override;
void kde_output_device_v2_name(const QString &name) override;
private:
QString modeName(const WaylandOutputDeviceMode *m) const;
WaylandOutputDeviceMode *deviceModeFromId(const int modeId) const;
WaylandOutputDeviceMode *m_mode;
QList<WaylandOutputDeviceMode *> m_modes;
int m_id;
QPoint m_pos;
QSize m_physicalSize;
int32_t m_subpixel;
QString m_manufacturer;
QString m_model;
int32_t m_transform;
qreal m_factor;
QByteArray m_edid;
int32_t m_enabled;
QString m_uuid;
QString m_serialNumber;
QString m_outputName;
QString m_eisaId;
uint32_t m_flags;
uint32_t m_overscan;
uint32_t m_vrr_policy;
uint32_t m_rgbRange;
bool m_isPrimary = false;
};
}
KSCREEN_EXPORT QDebug operator<<(QDebug dbg, const KScreen::WaylandOutputDevice *output);
#endif // WAYLANDOUTPUTDEVICE_H

View File

@ -0,0 +1,64 @@
/*
* SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "waylandoutputdevicemode.h"
using namespace KScreen;
WaylandOutputDeviceMode::WaylandOutputDeviceMode(struct ::kde_output_device_mode_v2 *object)
: QtWayland::kde_output_device_mode_v2(object)
{
}
WaylandOutputDeviceMode::~WaylandOutputDeviceMode()
{
kde_output_device_mode_v2_destroy(object());
}
void WaylandOutputDeviceMode::kde_output_device_mode_v2_size(int32_t width, int32_t height)
{
m_size = QSize(width, height);
}
void WaylandOutputDeviceMode::kde_output_device_mode_v2_refresh(int32_t refresh)
{
m_refreshRate = refresh;
}
void WaylandOutputDeviceMode::kde_output_device_mode_v2_preferred()
{
m_preferred = true;
}
void WaylandOutputDeviceMode::kde_output_device_mode_v2_removed()
{
Q_EMIT removed();
}
int WaylandOutputDeviceMode::refreshRate() const
{
return m_refreshRate;
}
QSize WaylandOutputDeviceMode::size() const
{
return m_size;
}
bool WaylandOutputDeviceMode::preferred() const
{
return m_preferred;
}
bool WaylandOutputDeviceMode::operator==(const WaylandOutputDeviceMode &other)
{
return m_size == other.m_size && m_refreshRate == other.m_refreshRate && m_preferred == other.m_preferred;
}
WaylandOutputDeviceMode *WaylandOutputDeviceMode::get(struct ::kde_output_device_mode_v2 *object)
{
auto mode = QtWayland::kde_output_device_mode_v2::fromObject(object);
return static_cast<WaylandOutputDeviceMode *>(mode);
}

View File

@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef WAYLANDOUTPUTDEVICEMODE_H
#define WAYLANDOUTPUTDEVICEMODE_H
#include "qwayland-kde-output-device-v2.h"
#include <QObject>
#include <QSize>
namespace KScreen
{
class WaylandOutputDeviceMode : public QObject, public QtWayland::kde_output_device_mode_v2
{
Q_OBJECT
public:
WaylandOutputDeviceMode(struct ::kde_output_device_mode_v2 *object);
~WaylandOutputDeviceMode() override;
int refreshRate() const;
QSize size() const;
bool preferred() const;
bool operator==(const WaylandOutputDeviceMode &other);
static WaylandOutputDeviceMode *get(struct ::kde_output_device_mode_v2 *object);
Q_SIGNALS:
void removed();
protected:
void kde_output_device_mode_v2_size(int32_t width, int32_t height) override;
void kde_output_device_mode_v2_refresh(int32_t refresh) override;
void kde_output_device_mode_v2_preferred() override;
void kde_output_device_mode_v2_removed() override;
private:
int m_refreshRate = 60000;
QSize m_size;
bool m_preferred = false;
};
}
#endif // WAYLANDOUTPUTDEVICEMODE_H

View File

@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2014-2015 Sebastian Kügler <sebas@kde.org>
* SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "waylandoutputmanagement.h"
#include "waylandconfig.h"
#include <QDebug>
namespace KScreen
{
WaylandOutputManagement::WaylandOutputManagement(struct ::wl_registry *registry, int id, int version)
: QObject()
, QtWayland::kde_output_management_v2(registry, id, version)
{
}
WaylandOutputConfiguration *WaylandOutputManagement::createConfiguration()
{
return new WaylandOutputConfiguration(create_configuration());
}
WaylandOutputConfiguration::WaylandOutputConfiguration(struct ::kde_output_configuration_v2 *object)
: QObject()
, QtWayland::kde_output_configuration_v2()
{
init(object);
}
void WaylandOutputConfiguration::kde_output_configuration_v2_applied()
{
Q_EMIT applied();
}
void WaylandOutputConfiguration::kde_output_configuration_v2_failed()
{
Q_EMIT failed();
}
WaylandPrimaryOutput::WaylandPrimaryOutput(struct ::wl_registry *registry, int id, int version)
: QObject()
, QtWayland::kde_primary_output_v1(registry, id, version)
{
}
void WaylandPrimaryOutput::kde_primary_output_v1_primary_output(const QString &outputName)
{
Q_EMIT primaryOutputChanged(outputName);
}
}

View File

@ -0,0 +1,59 @@
/*
* SPDX-FileCopyrightText: 2014-2015 Sebastian Kügler <sebas@kde.org>
* SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef WAYLANDOUTPUTMANAGEMENT_H
#define WAYLANDOUTPUTMANAGEMENT_H
#include "qwayland-kde-output-management-v2.h"
#include "qwayland-kde-primary-output-v1.h"
#include <QObject>
#include <QSize>
namespace KScreen
{
class WaylandConfig;
class WaylandOutputDevice;
class WaylandOutputConfiguration : public QObject, public QtWayland::kde_output_configuration_v2
{
Q_OBJECT
public:
WaylandOutputConfiguration(struct ::kde_output_configuration_v2 *object);
Q_SIGNALS:
void applied();
void failed();
protected:
void kde_output_configuration_v2_applied() override;
void kde_output_configuration_v2_failed() override;
};
class WaylandOutputManagement : public QObject, public QtWayland::kde_output_management_v2
{
Q_OBJECT
public:
WaylandOutputManagement(struct ::wl_registry *registry, int id, int version);
WaylandOutputConfiguration *createConfiguration();
};
class WaylandPrimaryOutput : public QObject, public QtWayland::kde_primary_output_v1
{
Q_OBJECT
public:
WaylandPrimaryOutput(struct ::wl_registry *registry, int id, int version);
Q_SIGNALS:
void primaryOutputChanged(const QString &outputName);
protected:
void kde_primary_output_v1_primary_output(const QString &outputName) override;
};
}
#endif // WAYLANDOUTPUTMANAGEMENT_H

View File

@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: 2014-2015 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "waylandscreen.h"
#include "waylandconfig.h"
#include "waylandoutputdevice.h"
#include <mode.h>
using namespace KScreen;
WaylandScreen::WaylandScreen(WaylandConfig *config)
: QObject(config)
, m_outputCount(0)
{
}
ScreenPtr WaylandScreen::toKScreenScreen(KScreen::ConfigPtr &parent) const
{
Q_UNUSED(parent);
KScreen::ScreenPtr kscreenScreen(new KScreen::Screen);
updateKScreenScreen(kscreenScreen);
return kscreenScreen;
}
void WaylandScreen::setOutputs(const QList<WaylandOutputDevice *> &outputs)
{
m_outputCount = outputs.count();
QRect r;
for (const auto *out : outputs) {
if (out->enabled()) {
r |= QRect(out->globalPosition(), out->pixelSize() / out->scale());
}
}
m_size = r.size();
}
void WaylandScreen::updateKScreenScreen(KScreen::ScreenPtr &screen) const
{
screen->setMinSize(QSize(0, 0));
// 64000^2 should be enough for everyone.
screen->setMaxSize(QSize(64000, 64000));
screen->setCurrentSize(m_size);
screen->setMaxActiveOutputsCount(m_outputCount);
}

View File

@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: 2014-2015 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#pragma once
#include "abstractbackend.h"
#include "config.h"
#include "screen.h"
#include <QObject>
#include <QSize>
namespace KScreen
{
class WaylandConfig;
class WaylandOutputDevice;
class WaylandScreen : public QObject
{
Q_OBJECT
public:
explicit WaylandScreen(WaylandConfig *config);
~WaylandScreen() override = default;
KScreen::ScreenPtr toKScreenScreen(KScreen::ConfigPtr &parent) const;
void updateKScreenScreen(KScreen::ScreenPtr &screen) const;
void setOutputs(const QList<WaylandOutputDevice *> &outputs);
private:
QSize m_size;
int m_outputCount;
};
}

View File

@ -0,0 +1,26 @@
include_directories(${CMAKE_SOURCE_DIR}/src
${CMAKE_BUILD_DIR}
)
add_library(KSC_QScreen MODULE)
target_sources(KSC_QScreen PRIVATE
qscreenbackend.cpp
qscreenconfig.cpp
qscreenscreen.cpp
qscreenoutput.cpp
qscreenbackend.h
qscreenconfig.h
qscreenscreen.h
qscreenoutput.h
)
set_target_properties(KSC_QScreen PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kscreen")
set_target_properties(KSC_QScreen PROPERTIES PREFIX "")
target_link_libraries(KSC_QScreen Qt::Core
Qt::Gui
Qt::X11Extras
KF5::Screen
)
install(TARGETS KSC_QScreen DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kscreen/)

View File

@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
* SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
* SPDX-FileCopyrightText: 2014 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "qscreenbackend.h"
#include "qscreenconfig.h"
#include "qscreenoutput.h"
using namespace KScreen;
Q_LOGGING_CATEGORY(KSCREEN_QSCREEN, "kscreen.qscreen")
QScreenConfig *QScreenBackend::s_internalConfig = nullptr;
QScreenBackend::QScreenBackend()
: KScreen::AbstractBackend()
, m_isValid(true)
{
if (s_internalConfig == nullptr) {
s_internalConfig = new QScreenConfig();
connect(s_internalConfig, &QScreenConfig::configChanged, this, &QScreenBackend::configChanged);
}
}
QScreenBackend::~QScreenBackend()
{
}
QString QScreenBackend::name() const
{
return QStringLiteral("QScreen");
}
QString QScreenBackend::serviceName() const
{
return QStringLiteral("org.kde.KScreen.Backend.QScreen");
}
ConfigPtr QScreenBackend::config() const
{
return s_internalConfig->toKScreenConfig();
}
void QScreenBackend::setConfig(const ConfigPtr &config)
{
if (!config) {
return;
}
qWarning() << "The QScreen backend for libkscreen is read-only,";
qWarning() << "setting a configuration is not supported.";
qWarning() << "You can force another backend using the KSCREEN_BACKEND env var.";
}
bool QScreenBackend::isValid() const
{
return m_isValid;
}

View File

@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
* SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
* SPDX-FileCopyrightText: 2014 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef QSCREEN_BACKEND_H
#define QSCREEN_BACKEND_H
#include "abstractbackend.h"
#include <QLoggingCategory>
namespace KScreen
{
class QScreenConfig;
class QScreenBackend : public KScreen::AbstractBackend
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.kf5.kscreen.backends.qscreen")
public:
explicit QScreenBackend();
~QScreenBackend() override;
QString name() const override;
QString serviceName() const override;
KScreen::ConfigPtr config() const override;
void setConfig(const KScreen::ConfigPtr &config) override;
bool isValid() const override;
private:
bool m_isValid;
static KScreen::QScreenConfig *s_internalConfig;
};
} // namespace
Q_DECLARE_LOGGING_CATEGORY(KSCREEN_QSCREEN)
#endif // QSCREEN_BACKEND_H

View File

@ -0,0 +1,119 @@
/*
* SPDX-FileCopyrightText: 2014 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "qscreenconfig.h"
#include "qscreenbackend.h"
#include "qscreenoutput.h"
#include "qscreenscreen.h"
#include <mode.h>
#include <QGuiApplication>
#include <QRect>
#include <QScreen>
using namespace KScreen;
QScreenConfig::QScreenConfig(QObject *parent)
: QObject(parent)
, m_screen(new QScreenScreen(this))
, m_blockSignals(true)
{
for (const QScreen *qscreen : QGuiApplication::screens()) {
screenAdded(qscreen);
}
m_blockSignals = false;
connect(qApp, &QGuiApplication::screenAdded, this, &QScreenConfig::screenAdded);
connect(qApp, &QGuiApplication::screenRemoved, this, &QScreenConfig::screenRemoved);
}
QScreenConfig::~QScreenConfig()
{
qDeleteAll(m_outputMap);
}
ConfigPtr QScreenConfig::toKScreenConfig() const
{
ConfigPtr config(new Config);
config->setScreen(m_screen->toKScreenScreen());
updateKScreenConfig(config);
return config;
}
int QScreenConfig::outputId(const QScreen *qscreen)
{
QList<int> ids;
for (auto output : qAsConst(m_outputMap)) {
if (qscreen == output->qscreen()) {
return output->id();
}
}
m_lastOutputId++;
return m_lastOutputId;
}
void QScreenConfig::screenAdded(const QScreen *qscreen)
{
qCDebug(KSCREEN_QSCREEN) << "Screen added" << qscreen << qscreen->name();
QScreenOutput *qscreenoutput = new QScreenOutput(qscreen, this);
qscreenoutput->setId(outputId(qscreen));
m_outputMap.insert(qscreenoutput->id(), qscreenoutput);
if (!m_blockSignals) {
Q_EMIT configChanged(toKScreenConfig());
}
}
void QScreenConfig::screenRemoved(QScreen *qscreen)
{
qCDebug(KSCREEN_QSCREEN) << "Screen removed" << qscreen << QGuiApplication::screens().count();
// Find output matching the QScreen object and remove it
int removedOutputId = -1;
for (auto output : m_outputMap) {
if (output->qscreen() == qscreen) {
removedOutputId = output->id();
m_outputMap.remove(removedOutputId);
delete output;
}
}
Q_EMIT configChanged(toKScreenConfig());
}
void QScreenConfig::updateKScreenConfig(ConfigPtr &config) const
{
KScreen::ScreenPtr screen = config->screen();
m_screen->updateKScreenScreen(screen);
config->setScreen(screen);
// Removing removed outputs
KScreen::OutputList outputs = config->outputs();
for (const KScreen::OutputPtr &output : outputs) {
if (!m_outputMap.contains(output->id())) {
config->removeOutput(output->id());
}
}
// Add KScreen::Outputs that aren't in the list yet, handle primaryOutput
KScreen::OutputList kscreenOutputs = config->outputs();
for (QScreenOutput *output : m_outputMap) {
KScreen::OutputPtr kscreenOutput = kscreenOutputs[output->id()];
if (!kscreenOutput) {
kscreenOutput = output->toKScreenOutput();
kscreenOutputs.insert(kscreenOutput->id(), kscreenOutput);
}
output->updateKScreenOutput(kscreenOutput);
if (QGuiApplication::primaryScreen() == output->qscreen()) {
config->setPrimaryOutput(kscreenOutput);
}
}
config->setOutputs(kscreenOutputs);
}
QMap<int, QScreenOutput *> QScreenConfig::outputMap() const
{
return m_outputMap;
}

View File

@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2014 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef QSCREEN_CONFIG_H
#define QSCREEN_CONFIG_H
#include "config.h"
#include <QScreen>
namespace KScreen
{
class Output;
class QScreenOutput;
class QScreenScreen;
class QScreenConfig : public QObject
{
Q_OBJECT
public:
explicit QScreenConfig(QObject *parent = nullptr);
~QScreenConfig() override;
KScreen::ConfigPtr toKScreenConfig() const;
void updateKScreenConfig(KScreen::ConfigPtr &config) const;
QMap<int, QScreenOutput *> outputMap() const;
int outputId(const QScreen *qscreen);
private Q_SLOTS:
void screenAdded(const QScreen *qscreen);
void screenRemoved(QScreen *qscreen);
Q_SIGNALS:
void configChanged(const KScreen::ConfigPtr &config);
private:
QMap<int, QScreenOutput *> m_outputMap;
QScreenScreen *m_screen;
int m_lastOutputId = -1;
bool m_blockSignals;
};
} // namespace
#endif // QSCREEN_CONFIG_H

View File

@ -0,0 +1,99 @@
/*
* SPDX-FileCopyrightText: 2014 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "qscreenoutput.h"
#include "qscreenbackend.h"
#include <edid.h>
#include <mode.h>
#include <QGuiApplication>
#include <QScreen>
using namespace KScreen;
QScreenOutput::QScreenOutput(const QScreen *qscreen, QObject *parent)
: QObject(parent)
, m_qscreen(qscreen)
, m_id(-1)
{
}
QScreenOutput::~QScreenOutput()
{
}
int QScreenOutput::id() const
{
return m_id;
}
void QScreenOutput::setId(const int newId)
{
m_id = newId;
}
const QScreen *QScreenOutput::qscreen() const
{
return m_qscreen;
}
OutputPtr QScreenOutput::toKScreenOutput() const
{
OutputPtr output(new Output);
output->setId(m_id);
output->setName(m_qscreen->name());
updateKScreenOutput(output);
return output;
}
void QScreenOutput::updateKScreenOutput(OutputPtr &output) const
{
// Initialize primary output
output->setEnabled(true);
output->setConnected(true);
output->setPrimary(QGuiApplication::primaryScreen() == m_qscreen);
// Rotation - translate QScreen::primaryOrientation() to Output::rotation()
if (m_qscreen->primaryOrientation() == Qt::PortraitOrientation) {
// 90 degrees
output->setRotation(Output::Right);
} else if (m_qscreen->primaryOrientation() == Qt::InvertedLandscapeOrientation) {
// 180 degrees
output->setRotation(Output::Inverted);
} else if (m_qscreen->primaryOrientation() == Qt::InvertedPortraitOrientation) {
// 270 degrees
output->setRotation(Output::Left);
}
// Physical size, geometry, etc.
QSize mm;
qreal physicalWidth;
physicalWidth = m_qscreen->size().width() / (m_qscreen->physicalDotsPerInchX() / 25.4);
mm.setWidth(qRound(physicalWidth));
qreal physicalHeight;
physicalHeight = m_qscreen->size().height() / (m_qscreen->physicalDotsPerInchY() / 25.4);
mm.setHeight(qRound(physicalHeight));
output->setSizeMm(mm);
output->setPos(m_qscreen->availableGeometry().topLeft());
output->setSize(m_qscreen->availableGeometry().size());
// Modes: we create a single default mode and go with that
ModePtr mode(new Mode);
const QString modeid = QStringLiteral("defaultmode");
mode->setId(modeid);
mode->setRefreshRate(m_qscreen->refreshRate());
mode->setSize(m_qscreen->size());
const QString modename = QString::number(m_qscreen->size().width()) + QLatin1String("x") + QString::number(m_qscreen->size().height()) + QLatin1String("@")
+ QString::number(m_qscreen->refreshRate());
mode->setName(modename);
ModeList modes;
modes[modeid] = mode;
output->setModes(modes);
output->setCurrentModeId(modeid);
}

View File

@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2014 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef QSCREEN_OUTPUT_H
#define QSCREEN_OUTPUT_H
#include "qscreenconfig.h"
#include "config.h"
#include "edid.h"
#include "output.h"
#include <QScreen>
namespace KScreen
{
class QScreenOutput : public QObject
{
Q_OBJECT
public:
explicit QScreenOutput(const QScreen *qscreen, QObject *parent = nullptr);
~QScreenOutput() override;
KScreen::OutputPtr toKScreenOutput() const;
void updateKScreenOutput(KScreen::OutputPtr &output) const;
int id() const;
void setId(const int newId);
const QScreen *qscreen() const;
private:
const QScreen *m_qscreen;
int m_id;
};
} // namespace
#endif

View File

@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2014 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "qscreenbackend.h"
#include "qscreenoutput.h"
#include "qscreenscreen.h"
#include <configmonitor.h>
#include <mode.h>
#include <QGuiApplication>
#include <QScreen>
using namespace KScreen;
QScreenScreen::QScreenScreen(QScreenConfig *config)
: QObject(config)
{
}
QScreenScreen::~QScreenScreen()
{
}
ScreenPtr QScreenScreen::toKScreenScreen() const
{
KScreen::ScreenPtr kscreenScreen(new KScreen::Screen);
updateKScreenScreen(kscreenScreen);
return kscreenScreen;
}
void QScreenScreen::updateKScreenScreen(ScreenPtr &screen) const
{
if (!screen) {
return;
}
auto primary = QGuiApplication::primaryScreen();
if (primary) {
QSize _s = primary->availableVirtualGeometry().size();
screen->setCurrentSize(_s);
screen->setId(1);
screen->setMaxSize(_s);
screen->setMinSize(_s);
screen->setCurrentSize(_s);
screen->setMaxActiveOutputsCount(QGuiApplication::screens().count());
}
}

View File

@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2014 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef QSCREEN_SCREEN_H
#define QSCREEN_SCREEN_H
#include "config.h"
#include "screen.h"
#include <QLoggingCategory>
#include <QScreen>
#include <QSize>
namespace KScreen
{
class Output;
class QScreenScreen : public QObject
{
Q_OBJECT
public:
explicit QScreenScreen(QScreenConfig *config);
~QScreenScreen() override;
KScreen::ScreenPtr toKScreenScreen() const;
void updateKScreenScreen(KScreen::ScreenPtr &screen) const;
};
} // namespace
#endif // QSCREEN_SCREEN_H

54
backends/utils.cpp Normal file
View File

@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2018 Daniel Vrátil <dvratil@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "utils.h"
#include <QVector>
KScreen::Output::Type Utils::guessOutputType(const QString &type, const QString &name)
{
static const auto embedded = {QLatin1String("LVDS"), QLatin1String("IDP"), QLatin1String("EDP"), QLatin1String("LCD"), QLatin1String("DSI")};
for (const QLatin1String &pre : embedded) {
if (name.startsWith(pre, Qt::CaseInsensitive)) {
return KScreen::Output::Panel;
}
}
if (type.contains(QLatin1String("VGA"))) {
return KScreen::Output::VGA;
} else if (type.contains(QLatin1String("DVI"))) {
return KScreen::Output::DVI;
} else if (type.contains(QLatin1String("DVI-I"))) {
return KScreen::Output::DVII;
} else if (type.contains(QLatin1String("DVI-A"))) {
return KScreen::Output::DVIA;
} else if (type.contains(QLatin1String("DVI-D"))) {
return KScreen::Output::DVID;
} else if (type.contains(QLatin1String("HDMI"))) {
return KScreen::Output::HDMI;
} else if (type.contains(QLatin1String("Panel"))) {
return KScreen::Output::Panel;
} else if (type.contains(QLatin1String("TV-Composite"))) {
return KScreen::Output::TVComposite;
} else if (type.contains(QLatin1String("TV-SVideo"))) {
return KScreen::Output::TVSVideo;
} else if (type.contains(QLatin1String("TV-Component"))) {
return KScreen::Output::TVComponent;
} else if (type.contains(QLatin1String("TV-SCART"))) {
return KScreen::Output::TVSCART;
} else if (type.contains(QLatin1String("TV-C4"))) {
return KScreen::Output::TVC4;
} else if (type.contains(QLatin1String("TV"))) {
return KScreen::Output::TV;
} else if (type.contains(QLatin1String("DisplayPort")) || type.startsWith(QLatin1String("DP"))) {
return KScreen::Output::DisplayPort;
} else if (type.contains(QLatin1String("unknown"))) {
return KScreen::Output::Unknown;
} else {
return KScreen::Output::Unknown;
}
}

18
backends/utils.h Normal file
View File

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2018 Daniel Vrátil <dvratil@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef KSCREEN_BACKEND_UTILS_H_
#define KSCREEN_BACKEND_UTILS_H_
#include "output.h"
namespace Utils
{
KScreen::Output::Type guessOutputType(const QString &type, const QString &name);
}
#endif

View File

@ -0,0 +1,193 @@
/*
* SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "xcbeventlistener.h"
#include <QGuiApplication>
#include <QX11Info>
#include <QRect>
Q_LOGGING_CATEGORY(KSCREEN_XCB_HELPER, "kscreen.xcb.helper")
XCBEventListener::XCBEventListener()
: m_isRandrPresent(false)
, m_randrBase(0)
, m_randrErrorBase(0)
, m_majorOpcode(0)
, m_versionMajor(0)
, m_versionMinor(0)
, m_window(0)
{
xcb_connection_t *c = QX11Info::connection();
xcb_prefetch_extension_data(c, &xcb_randr_id);
auto cookie = xcb_randr_query_version(c, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION);
const auto *queryExtension = xcb_get_extension_data(c, &xcb_randr_id);
if (!queryExtension) {
qCDebug(KSCREEN_XCB_HELPER) << "Fail to query for xrandr extension";
return;
}
if (!queryExtension->present) {
qCDebug(KSCREEN_XCB_HELPER) << "XRandR extension is not present at all";
return;
}
m_isRandrPresent = queryExtension->present;
m_randrBase = queryExtension->first_event;
m_randrErrorBase = queryExtension->first_error;
m_majorOpcode = queryExtension->major_opcode;
xcb_generic_error_t *error = nullptr;
auto *versionReply = xcb_randr_query_version_reply(c, cookie, &error);
Q_ASSERT_X(versionReply, "xrandrxcbhelper", "Query to fetch xrandr version failed");
if (error) {
qFatal("Error while querying for xrandr version: %d", error->error_code);
}
m_versionMajor = versionReply->major_version;
m_versionMinor = versionReply->minor_version;
free(versionReply);
qCDebug(KSCREEN_XCB_HELPER).nospace() << "Detected XRandR " << m_versionMajor << "." << m_versionMinor;
qCDebug(KSCREEN_XCB_HELPER) << "Event Base: " << m_randrBase;
qCDebug(KSCREEN_XCB_HELPER) << "Event Error: " << m_randrErrorBase;
uint32_t rWindow = QX11Info::appRootWindow();
m_window = xcb_generate_id(c);
xcb_create_window(c, XCB_COPY_FROM_PARENT, m_window, rWindow, 0, 0, 1, 1, 0, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT, 0, nullptr);
xcb_randr_select_input(c,
m_window,
XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE | XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE | XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE
| XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
qApp->installNativeEventFilter(this);
}
XCBEventListener::~XCBEventListener()
{
if (m_window && QX11Info::connection()) {
xcb_destroy_window(QX11Info::connection(), m_window);
}
}
QString XCBEventListener::rotationToString(xcb_randr_rotation_t rotation)
{
switch (rotation) {
case XCB_RANDR_ROTATION_ROTATE_0:
return QStringLiteral("Rotate_0");
case XCB_RANDR_ROTATION_ROTATE_90:
return QStringLiteral("Rotate_90");
case XCB_RANDR_ROTATION_ROTATE_180:
return QStringLiteral("Rotate_180");
case XCB_RANDR_ROTATION_ROTATE_270:
return QStringLiteral("Rotate_270");
case XCB_RANDR_ROTATION_REFLECT_X:
return QStringLiteral("Reflect_X");
case XCB_RANDR_ROTATION_REFLECT_Y:
return QStringLiteral("Reflect_Y");
}
return QStringLiteral("invalid value (%1)").arg(rotation);
}
QString XCBEventListener::connectionToString(xcb_randr_connection_t connection)
{
switch (connection) {
case XCB_RANDR_CONNECTION_CONNECTED:
return QStringLiteral("Connected");
case XCB_RANDR_CONNECTION_DISCONNECTED:
return QStringLiteral("Disconnected");
case XCB_RANDR_CONNECTION_UNKNOWN:
return QStringLiteral("UnknownConnection");
}
return QStringLiteral("invalid value (%1)").arg(connection);
}
bool XCBEventListener::nativeEventFilter(const QByteArray &eventType, void *message, long int *result)
{
Q_UNUSED(result);
if (eventType != "xcb_generic_event_t") {
return false;
}
auto *e = static_cast<xcb_generic_event_t *>(message);
const uint8_t xEventType = e->response_type & ~0x80;
// If this event is not xcb_randr_notify, we don't want it
if (xEventType == m_randrBase + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
handleScreenChange(e);
}
if (xEventType == m_randrBase + XCB_RANDR_NOTIFY) {
handleXRandRNotify(e);
}
return false;
}
void XCBEventListener::handleScreenChange(xcb_generic_event_t *e)
{
auto *e2 = reinterpret_cast<xcb_randr_screen_change_notify_event_t *>(e);
// Only accept notifications for our window
if (e2->request_window != m_window) {
return;
}
qCDebug(KSCREEN_XCB_HELPER) << "RRScreenChangeNotify";
qCDebug(KSCREEN_XCB_HELPER) << "\tTimestamp: " << e2->timestamp;
qCDebug(KSCREEN_XCB_HELPER) << "\tConfig_timestamp: " << e2->config_timestamp;
qCDebug(KSCREEN_XCB_HELPER) << "\tWindow:" << e2->request_window;
qCDebug(KSCREEN_XCB_HELPER) << "\tRoot:" << e2->root;
qCDebug(KSCREEN_XCB_HELPER) << "\tRotation: " << rotationToString((xcb_randr_rotation_t)e2->rotation);
qCDebug(KSCREEN_XCB_HELPER) << "\tSize ID:" << e2->sizeID;
qCDebug(KSCREEN_XCB_HELPER) << "\tSize: " << e2->width << e2->height;
qCDebug(KSCREEN_XCB_HELPER) << "\tSizeMM: " << e2->mwidth << e2->mheight;
Q_EMIT screenChanged((xcb_randr_rotation_t)e2->rotation, QSize(e2->width, e2->height), QSize(e2->mwidth, e2->mheight));
Q_EMIT outputsChanged();
}
void XCBEventListener::handleXRandRNotify(xcb_generic_event_t *e)
{
auto *randrEvent = reinterpret_cast<xcb_randr_notify_event_t *>(e);
if (randrEvent->subCode == XCB_RANDR_NOTIFY_CRTC_CHANGE) {
xcb_randr_crtc_change_t crtc = randrEvent->u.cc;
qCDebug(KSCREEN_XCB_HELPER) << "RRNotify_CrtcChange";
qCDebug(KSCREEN_XCB_HELPER) << "\tTimestamp: " << crtc.timestamp;
qCDebug(KSCREEN_XCB_HELPER) << "\tCRTC: " << crtc.crtc;
qCDebug(KSCREEN_XCB_HELPER) << "\tMode: " << crtc.mode;
qCDebug(KSCREEN_XCB_HELPER) << "\tRotation: " << rotationToString((xcb_randr_rotation_t)crtc.rotation);
qCDebug(KSCREEN_XCB_HELPER) << "\tGeometry: " << crtc.x << crtc.y << crtc.width << crtc.height;
Q_EMIT crtcChanged(crtc.crtc, crtc.mode, (xcb_randr_rotation_t)crtc.rotation, QRect(crtc.x, crtc.y, crtc.width, crtc.height), crtc.timestamp);
} else if (randrEvent->subCode == XCB_RANDR_NOTIFY_OUTPUT_CHANGE) {
xcb_randr_output_change_t output = randrEvent->u.oc;
qCDebug(KSCREEN_XCB_HELPER) << "RRNotify_OutputChange";
qCDebug(KSCREEN_XCB_HELPER) << "\tTimestamp: " << output.timestamp;
qCDebug(KSCREEN_XCB_HELPER) << "\tOutput: " << output.output;
qCDebug(KSCREEN_XCB_HELPER) << "\tCRTC: " << output.crtc;
qCDebug(KSCREEN_XCB_HELPER) << "\tMode: " << output.mode;
qCDebug(KSCREEN_XCB_HELPER) << "\tRotation: " << rotationToString((xcb_randr_rotation_t)output.rotation);
qCDebug(KSCREEN_XCB_HELPER) << "\tConnection: " << connectionToString((xcb_randr_connection_t)output.connection);
qCDebug(KSCREEN_XCB_HELPER) << "\tSubpixel Order: " << output.subpixel_order;
Q_EMIT outputChanged(output.output, output.crtc, output.mode, (xcb_randr_connection_t)output.connection);
} else if (randrEvent->subCode == XCB_RANDR_NOTIFY_OUTPUT_PROPERTY) {
xcb_randr_output_property_t property = randrEvent->u.op;
XCB::ScopedPointer<xcb_get_atom_name_reply_t> reply(
xcb_get_atom_name_reply(QX11Info::connection(), xcb_get_atom_name(QX11Info::connection(), property.atom), nullptr));
qCDebug(KSCREEN_XCB_HELPER) << "RRNotify_OutputProperty (ignored)";
qCDebug(KSCREEN_XCB_HELPER) << "\tTimestamp: " << property.timestamp;
qCDebug(KSCREEN_XCB_HELPER) << "\tOutput: " << property.output;
qCDebug(KSCREEN_XCB_HELPER) << "\tProperty: " << xcb_get_atom_name_name(reply.data());
qCDebug(KSCREEN_XCB_HELPER) << "\tState (newValue, Deleted): " << property.status;
}
}

View File

@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef XRANDRX11HELPER_H
#define XRANDRX11HELPER_H
#include <QAbstractNativeEventFilter>
#include <QLoggingCategory>
#include <QObject>
#include <QRect>
#include "xcbwrapper.h"
class XCBEventListener : public QObject, public QAbstractNativeEventFilter
{
Q_OBJECT
public:
XCBEventListener();
~XCBEventListener() override;
bool nativeEventFilter(const QByteArray &eventType, void *message, long int *result) override;
Q_SIGNALS:
/* Emitted when only XRandR 1.1 or older is available */
void screenChanged(xcb_randr_rotation_t rotation, const QSize &sizePx, const QSize &sizeMm);
void outputsChanged();
/* Emitted only when XRandR 1.2 or newer is available */
void crtcChanged(xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_rotation_t rotation, const QRect &geom, xcb_timestamp_t timestamp);
void outputChanged(xcb_randr_output_t output, xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_connection_t connection);
void outputPropertyChanged(xcb_randr_output_t output);
private:
QString rotationToString(xcb_randr_rotation_t rotation);
QString connectionToString(xcb_randr_connection_t connection);
void handleScreenChange(xcb_generic_event_t *e);
void handleXRandRNotify(xcb_generic_event_t *e);
protected:
bool m_isRandrPresent;
bool m_event11;
uint8_t m_randrBase;
uint8_t m_randrErrorBase;
uint8_t m_majorOpcode;
uint32_t m_versionMajor;
uint32_t m_versionMinor;
uint32_t m_window;
};
Q_DECLARE_LOGGING_CATEGORY(KSCREEN_XCB_HELPER)
#endif // XRANDRX11HELPER_H

53
backends/xcbwrapper.cpp Normal file
View File

@ -0,0 +1,53 @@
/*
K Win - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2012, 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2015 Daniel Vrátil <dvratil@redhat.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "xcbwrapper.h"
static xcb_connection_t *sXRandR11XCBConnection = nullptr;
xcb_connection_t *XCB::connection()
{
// Use our own connection to make sure that we won't mess up Qt's connection
// if something goes wrong on our side.
if (sXRandR11XCBConnection == nullptr) {
sXRandR11XCBConnection = xcb_connect(nullptr, nullptr);
}
return sXRandR11XCBConnection;
}
void XCB::closeConnection()
{
if (sXRandR11XCBConnection) {
xcb_disconnect(sXRandR11XCBConnection);
sXRandR11XCBConnection = nullptr;
}
}
xcb_screen_t *XCB::screenOfDisplay(xcb_connection_t *c, int screen)
{
for (auto iter = xcb_setup_roots_iterator(xcb_get_setup(c)); iter.rem; --screen, xcb_screen_next(&iter)) {
if (screen == 0) {
return iter.data;
}
}
return nullptr;
}
XCB::GrabServer::GrabServer()
{
xcb_grab_server(connection());
}
XCB::GrabServer::~GrabServer()
{
xcb_ungrab_server(connection());
xcb_flush(connection());
}

209
backends/xcbwrapper.h Normal file
View File

@ -0,0 +1,209 @@
/*
K Win - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2012, 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2015 Daniel Vrátil <dvratil@redhat.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef XCB_WRAPPER_H
#define XCB_WRAPPER_H
#include <functional>
#include <type_traits>
#include <QScopedPointer>
#include <QX11Info>
#include <xcb/randr.h>
#include <xcb/xcb.h>
namespace XCB
{
template<typename T> using ScopedPointer = QScopedPointer<T, QScopedPointerPodDeleter>;
xcb_connection_t *connection();
void closeConnection();
xcb_screen_t *screenOfDisplay(xcb_connection_t *c, int screen);
struct GrabServer {
GrabServer();
~GrabServer();
};
template<typename Reply, typename Cookie, typename ReplyFunc, ReplyFunc replyFunc, typename RequestFunc, RequestFunc requestFunc, typename... RequestFuncArgs>
class Wrapper
{
public:
Wrapper()
: m_retrieved(false)
, m_window(XCB_WINDOW_NONE)
, m_reply(nullptr)
{
m_cookie.sequence = 0;
}
explicit Wrapper(const RequestFuncArgs &... args)
: m_retrieved(false)
, m_cookie(requestFunc(connection(), args...))
, m_window(requestWindow<RequestFuncArgs...>(args...))
, m_reply(nullptr)
{
}
explicit Wrapper(const Wrapper &other)
: m_retrieved(other.m_retrieved)
, m_cookie(other.m_cookie)
, m_window(other.m_window)
, m_reply(nullptr)
{
takeFromOther(const_cast<Wrapper &>(other));
}
virtual ~Wrapper()
{
cleanup();
}
inline Wrapper &operator=(const Wrapper &other)
{
if (this != &other) {
// if we had managed a reply, free it
cleanup();
// copy members
m_retrieved = other.m_retrieved;
m_cookie = other.m_cookie;
m_window = other.m_window;
m_reply = other.m_reply;
// take over the responsibility for the reply pointer
takeFromOther(const_cast<Wrapper &>(other));
}
return *this;
}
inline operator const Reply *() const
{
getReply();
return m_reply;
}
inline const Reply *operator->() const
{
getReply();
return m_reply;
}
inline bool isNull() const
{
getReply();
return m_reply == nullptr;
}
inline operator bool() const
{
return !isNull();
}
inline const Reply *data() const
{
getReply();
return m_reply;
}
inline xcb_window_t window() const
{
return m_window;
}
inline bool isRetrieved() const
{
return m_retrieved;
}
/**
* Returns the value of the reply pointer referenced by this object. The reply pointer of
* this object will be reset to null. Calling any method which requires the reply to be valid
* will crash.
*
* Callers of this function take ownership of the pointer.
**/
inline Reply *take()
{
getReply();
Reply *ret = m_reply;
m_reply = nullptr;
m_window = XCB_WINDOW_NONE;
return ret;
}
protected:
void getReply() const
{
if (m_retrieved || !m_cookie.sequence) {
return;
}
m_reply = replyFunc(connection(), m_cookie, nullptr);
m_retrieved = true;
}
private:
inline void cleanup()
{
if (!m_retrieved && m_cookie.sequence) {
xcb_discard_reply(connection(), m_cookie.sequence);
} else if (m_reply) {
free(m_reply);
}
}
inline void takeFromOther(Wrapper &other)
{
if (m_retrieved) {
m_reply = other.take();
} else {
// ensure that other object doesn't try to get the reply or discards it in the dtor
other.m_retrieved = true;
other.m_window = XCB_WINDOW_NONE;
}
}
template<typename... Args> constexpr xcb_window_t requestWindow(const Args &... args) const
{
return std::is_same<typename std::tuple_element<0, std::tuple<Args...>>::type, xcb_window_t>::value ? std::get<0>(std::tuple<Args...>(args...))
: static_cast<xcb_window_t>(XCB_WINDOW_NONE);
}
mutable bool m_retrieved;
Cookie m_cookie;
xcb_window_t m_window;
mutable Reply *m_reply;
};
#define XCB_DECLARE_TYPE(name, xcb_request, ...) \
typedef Wrapper<xcb_request##_reply_t, \
xcb_request##_cookie_t, \
decltype(&xcb_request##_reply), \
xcb_request##_reply, \
decltype(&xcb_request), \
xcb_request, \
##__VA_ARGS__> \
name
XCB_DECLARE_TYPE(ScreenInfo, xcb_randr_get_screen_info, xcb_window_t);
XCB_DECLARE_TYPE(ScreenSize, xcb_randr_get_screen_size_range, xcb_window_t);
XCB_DECLARE_TYPE(PrimaryOutput, xcb_randr_get_output_primary, xcb_window_t);
XCB_DECLARE_TYPE(InternAtom, xcb_intern_atom, uint8_t, uint16_t, const char *);
XCB_DECLARE_TYPE(OutputInfo, xcb_randr_get_output_info, xcb_randr_output_t, xcb_timestamp_t);
XCB_DECLARE_TYPE(CRTCInfo, xcb_randr_get_crtc_info, xcb_randr_crtc_t, xcb_timestamp_t);
XCB_DECLARE_TYPE(AtomName, xcb_get_atom_name, xcb_atom_t);
}
#endif

View File

@ -0,0 +1,28 @@
include_directories(${CMAKE_SOURCE_DIR}/src
${CMAKE_BUILD_DIR}
)
add_library(KSC_XRandR MODULE)
target_sources(KSC_XRandR PRIVATE
xrandr.cpp
xrandrconfig.cpp
xrandrcrtc.cpp
xrandroutput.cpp
xrandrmode.cpp
xrandrscreen.cpp
../xcbwrapper.cpp
../xcbeventlistener.cpp
../utils.cpp
)
set_target_properties(KSC_XRandR PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kscreen")
set_target_properties(KSC_XRandR PROPERTIES PREFIX "")
target_link_libraries(KSC_XRandR Qt::Core
Qt::Gui
Qt::X11Extras
${XCB_LIBRARIES}
KF5::Screen
)
install(TARGETS KSC_XRandR DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kscreen/)

295
backends/xrandr/xrandr.cpp Normal file
View File

@ -0,0 +1,295 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
* SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "xrandr.h"
#include "../xcbeventlistener.h"
#include "../xcbwrapper.h"
#include "xrandrconfig.h"
#include "xrandrscreen.h"
#include "config.h"
#include "edid.h"
#include "output.h"
#include <QRect>
#include <QTime>
#include <QTimer>
#include <QX11Info>
xcb_screen_t *XRandR::s_screen = nullptr;
xcb_window_t XRandR::s_rootWindow = 0;
XRandRConfig *XRandR::s_internalConfig = nullptr;
int XRandR::s_randrBase = 0;
int XRandR::s_randrError = 0;
bool XRandR::s_monitorInitialized = false;
bool XRandR::s_has_1_3 = false;
bool XRandR::s_xorgCacheInitialized = false;
using namespace KScreen;
Q_LOGGING_CATEGORY(KSCREEN_XRANDR, "kscreen.xrandr")
XRandR::XRandR()
: KScreen::AbstractBackend()
, m_x11Helper(nullptr)
, m_isValid(false)
, m_configChangeCompressor(nullptr)
{
qRegisterMetaType<xcb_randr_output_t>("xcb_randr_output_t");
qRegisterMetaType<xcb_randr_crtc_t>("xcb_randr_crtc_t");
qRegisterMetaType<xcb_randr_mode_t>("xcb_randr_mode_t");
qRegisterMetaType<xcb_randr_connection_t>("xcb_randr_connection_t");
qRegisterMetaType<xcb_randr_rotation_t>("xcb_randr_rotation_t");
qRegisterMetaType<xcb_timestamp_t>("xcb_timestamp_t");
// Use our own connection to make sure that we won't mess up Qt's connection
// if something goes wrong on our side.
xcb_generic_error_t *error = nullptr;
xcb_randr_query_version_reply_t *version;
XCB::connection();
version = xcb_randr_query_version_reply(XCB::connection(), //
xcb_randr_query_version(XCB::connection(), XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION),
&error);
if (!version || error) {
XCB::closeConnection();
free(error);
return;
}
if ((version->major_version > 1) || ((version->major_version == 1) && (version->minor_version >= 2))) {
m_isValid = true;
} else {
XCB::closeConnection();
qCWarning(KSCREEN_XRANDR) << "XRandR extension not available or unsupported version";
return;
}
if (s_screen == nullptr) {
s_screen = XCB::screenOfDisplay(XCB::connection(), QX11Info::appScreen());
s_rootWindow = s_screen->root;
xcb_prefetch_extension_data(XCB::connection(), &xcb_randr_id);
auto reply = xcb_get_extension_data(XCB::connection(), &xcb_randr_id);
s_randrBase = reply->first_event;
s_randrError = reply->first_error;
}
XRandR::s_has_1_3 = (version->major_version > 1 || (version->major_version == 1 && version->minor_version >= 3));
if (s_internalConfig == nullptr) {
s_internalConfig = new XRandRConfig();
}
if (!s_monitorInitialized) {
m_x11Helper = new XCBEventListener();
connect(m_x11Helper, &XCBEventListener::outputChanged, this, &XRandR::outputChanged, Qt::QueuedConnection);
connect(m_x11Helper, &XCBEventListener::crtcChanged, this, &XRandR::crtcChanged, Qt::QueuedConnection);
connect(m_x11Helper, &XCBEventListener::screenChanged, this, &XRandR::screenChanged, Qt::QueuedConnection);
m_configChangeCompressor = new QTimer(this);
m_configChangeCompressor->setSingleShot(true);
m_configChangeCompressor->setInterval(500);
connect(m_configChangeCompressor, &QTimer::timeout, [&]() {
qCDebug(KSCREEN_XRANDR) << "Emitting configChanged()";
Q_EMIT configChanged(config());
});
s_monitorInitialized = true;
}
}
XRandR::~XRandR()
{
delete m_x11Helper;
}
QString XRandR::name() const
{
return QStringLiteral("XRandR");
}
QString XRandR::serviceName() const
{
return QStringLiteral("org.kde.KScreen.Backend.XRandR");
}
void XRandR::outputChanged(xcb_randr_output_t output, xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_connection_t connection)
{
m_configChangeCompressor->start();
XRandROutput *xOutput = s_internalConfig->output(output);
if (!xOutput) {
s_internalConfig->addNewOutput(output);
return;
}
// check if this output disappeared
if (crtc == XCB_NONE && mode == XCB_NONE && connection == XCB_RANDR_CONNECTION_DISCONNECTED) {
XCB::OutputInfo info(output, XCB_TIME_CURRENT_TIME);
if (info.isNull()) {
s_internalConfig->removeOutput(output);
qCDebug(KSCREEN_XRANDR) << "Output" << output << " removed";
return;
}
// info is valid: the output is still there
}
XCB::PrimaryOutput primary(XRandR::rootWindow());
xOutput->update(crtc, mode, connection, (primary->output == output));
qCDebug(KSCREEN_XRANDR) << "Output" << xOutput->id() << ": connected =" << xOutput->isConnected() << ", enabled =" << xOutput->isEnabled();
}
void XRandR::crtcChanged(xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_rotation_t rotation, const QRect &geom, xcb_timestamp_t timestamp)
{
XRandRCrtc *xCrtc = s_internalConfig->crtc(crtc);
if (!xCrtc) {
s_internalConfig->addNewCrtc(crtc);
} else {
xCrtc->update(mode, rotation, geom);
}
xCrtc->updateConfigTimestamp(timestamp);
m_configChangeCompressor->start();
}
void XRandR::screenChanged(xcb_randr_rotation_t rotation, const QSize &sizePx, const QSize &sizeMm)
{
Q_UNUSED(sizeMm);
QSize newSizePx = sizePx;
if (rotation == XCB_RANDR_ROTATION_ROTATE_90 || rotation == XCB_RANDR_ROTATION_ROTATE_270) {
newSizePx.transpose();
}
XRandRScreen *xScreen = s_internalConfig->screen();
Q_ASSERT(xScreen);
xScreen->update(newSizePx);
m_configChangeCompressor->start();
}
ConfigPtr XRandR::config() const
{
return s_internalConfig->toKScreenConfig();
}
void XRandR::setConfig(const ConfigPtr &config)
{
if (!config) {
return;
}
qCDebug(KSCREEN_XRANDR) << "XRandR::setConfig";
s_internalConfig->applyKScreenConfig(config);
qCDebug(KSCREEN_XRANDR) << "XRandR::setConfig done!";
}
QByteArray XRandR::edid(int outputId) const
{
const XRandROutput *output = s_internalConfig->output(outputId);
if (!output) {
return QByteArray();
}
return output->edid();
}
bool XRandR::isValid() const
{
return m_isValid;
}
quint8 *XRandR::getXProperty(xcb_randr_output_t output, xcb_atom_t atom, size_t &len)
{
quint8 *result;
auto cookie = xcb_randr_get_output_property(XCB::connection(), output, atom, XCB_ATOM_ANY, 0, 100, false, false);
auto reply = xcb_randr_get_output_property_reply(XCB::connection(), cookie, nullptr);
if (reply->type == XCB_ATOM_INTEGER && reply->format == 8) {
result = new quint8[reply->num_items];
memcpy(result, xcb_randr_get_output_property_data(reply), reply->num_items);
len = reply->num_items;
} else {
result = nullptr;
}
free(reply);
return result;
}
QByteArray XRandR::outputEdid(xcb_randr_output_t outputId)
{
size_t len = 0;
quint8 *result;
auto edid_atom = XCB::InternAtom(false, 4, "EDID")->atom;
result = XRandR::getXProperty(outputId, edid_atom, len);
if (result == nullptr) {
auto edid_atom = XCB::InternAtom(false, 9, "EDID_DATA")->atom;
result = XRandR::getXProperty(outputId, edid_atom, len);
}
if (result == nullptr) {
auto edid_atom = XCB::InternAtom(false, 25, "XFree86_DDC_EDID1_RAWDATA")->atom;
result = XRandR::getXProperty(outputId, edid_atom, len);
}
QByteArray edid;
if (result != nullptr) {
if (len % 128 == 0) {
edid = QByteArray(reinterpret_cast<const char *>(result), len);
}
delete[] result;
}
return edid;
}
bool XRandR::hasProperty(xcb_randr_output_t output, const QByteArray &name)
{
xcb_generic_error_t *error = nullptr;
auto atom = XCB::InternAtom(false, name.length(), name.constData())->atom;
auto cookie = xcb_randr_get_output_property(XCB::connection(), output, atom, XCB_ATOM_ANY, 0, 1, false, false);
auto prop_reply = xcb_randr_get_output_property_reply(XCB::connection(), cookie, &error);
const bool ret = prop_reply->num_items == 1;
free(prop_reply);
return ret;
}
xcb_randr_get_screen_resources_reply_t *XRandR::screenResources()
{
if (XRandR::s_has_1_3) {
if (XRandR::s_xorgCacheInitialized) {
// HACK: This abuses the fact that xcb_randr_get_screen_resources_reply_t
// and xcb_randr_get_screen_resources_current_reply_t are the same
return reinterpret_cast<xcb_randr_get_screen_resources_reply_t *>(
xcb_randr_get_screen_resources_current_reply(XCB::connection(),
xcb_randr_get_screen_resources_current(XCB::connection(), XRandR::rootWindow()),
nullptr));
} else {
/* XRRGetScreenResourcesCurrent is faster then XRRGetScreenResources
* because it returns cached values. However the cached values are not
* available until someone calls XRRGetScreenResources first. In case
* we happen to be the first ones, we need to fill the cache first. */
XRandR::s_xorgCacheInitialized = true;
}
}
return xcb_randr_get_screen_resources_reply(XCB::connection(), xcb_randr_get_screen_resources(XCB::connection(), XRandR::rootWindow()), nullptr);
}
xcb_window_t XRandR::rootWindow()
{
return s_rootWindow;
}
xcb_screen_t *XRandR::screen()
{
return s_screen;
}

68
backends/xrandr/xrandr.h Normal file
View File

@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
* SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#pragma once
#include "abstractbackend.h"
#include <QLoggingCategory>
#include <QSize>
#include "../xcbwrapper.h"
class QRect;
class QTimer;
class XCBEventListener;
class XRandRConfig;
class XRandR : public KScreen::AbstractBackend
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.kf5.kscreen.backends.xrandr")
public:
explicit XRandR();
~XRandR() override;
QString name() const override;
QString serviceName() const override;
KScreen::ConfigPtr config() const override;
void setConfig(const KScreen::ConfigPtr &config) override;
bool isValid() const override;
QByteArray edid(int outputId) const override;
static QByteArray outputEdid(xcb_randr_output_t outputId);
static xcb_randr_get_screen_resources_reply_t *screenResources();
static xcb_screen_t *screen();
static xcb_window_t rootWindow();
static bool hasProperty(xcb_randr_output_t outputId, const QByteArray &name);
private:
void outputChanged(xcb_randr_output_t output, xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_connection_t connection);
void crtcChanged(xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_rotation_t rotation, const QRect &geom, xcb_timestamp_t timestamp);
void screenChanged(xcb_randr_rotation_t rotation, const QSize &sizePx, const QSize &sizeMm);
static quint8 *getXProperty(xcb_randr_output_t output, xcb_atom_t atom, size_t &len);
static xcb_screen_t *s_screen;
static xcb_window_t s_rootWindow;
static XRandRConfig *s_internalConfig;
static int s_randrBase;
static int s_randrError;
static bool s_monitorInitialized;
static bool s_has_1_3;
static bool s_xorgCacheInitialized;
XCBEventListener *m_x11Helper;
bool m_isValid;
QTimer *m_configChangeCompressor;
};
Q_DECLARE_LOGGING_CATEGORY(KSCREEN_XRANDR)

View File

@ -0,0 +1,615 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
* SPDX-FileCopyrightText: 2012-2015 Daniel Vrátil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "xrandrconfig.h"
#include "config.h"
#include "edid.h"
#include "output.h"
#include "xrandr.h"
#include "xrandrcrtc.h"
#include "xrandrmode.h"
#include "xrandroutput.h"
#include "xrandrscreen.h"
#include "../xcbwrapper.h"
#include <QRect>
#include <QScopedPointer>
#include <QX11Info>
using namespace KScreen;
XRandRConfig::XRandRConfig()
: QObject()
, m_screen(nullptr)
{
m_screen = new XRandRScreen(this);
XCB::ScopedPointer<xcb_randr_get_screen_resources_reply_t> resources(XRandR::screenResources());
xcb_randr_crtc_t *crtcs = xcb_randr_get_screen_resources_crtcs(resources.data());
const int crtcsCount = xcb_randr_get_screen_resources_crtcs_length(resources.data());
for (int i = 0; i < crtcsCount; ++i) {
addNewCrtc(crtcs[i]);
}
xcb_randr_output_t *outputs = xcb_randr_get_screen_resources_outputs(resources.data());
const int outputsCount = xcb_randr_get_screen_resources_outputs_length(resources.data());
for (int i = 0; i < outputsCount; ++i) {
addNewOutput(outputs[i]);
}
}
XRandRConfig::~XRandRConfig()
{
qDeleteAll(m_outputs);
qDeleteAll(m_crtcs);
delete m_screen;
}
XRandROutput::Map XRandRConfig::outputs() const
{
return m_outputs;
}
XRandROutput *XRandRConfig::output(xcb_randr_output_t output) const
{
return m_outputs[output];
}
XRandRCrtc::Map XRandRConfig::crtcs() const
{
return m_crtcs;
}
XRandRCrtc *XRandRConfig::crtc(xcb_randr_crtc_t crtc) const
{
return m_crtcs[crtc];
}
XRandRScreen *XRandRConfig::screen() const
{
return m_screen;
}
void XRandRConfig::addNewOutput(xcb_randr_output_t id)
{
XRandROutput *xOutput = new XRandROutput(id, this);
m_outputs.insert(id, xOutput);
}
void XRandRConfig::addNewCrtc(xcb_randr_crtc_t crtc)
{
m_crtcs.insert(crtc, new XRandRCrtc(crtc, this));
}
void XRandRConfig::removeOutput(xcb_randr_output_t id)
{
delete m_outputs.take(id);
}
KScreen::ConfigPtr XRandRConfig::toKScreenConfig() const
{
KScreen::ConfigPtr config(new KScreen::Config);
const Config::Features features = Config::Feature::Writable | Config::Feature::PrimaryDisplay | Config::Feature::OutputReplication;
config->setSupportedFeatures(features);
KScreen::OutputList kscreenOutputs;
for (auto iter = m_outputs.constBegin(); iter != m_outputs.constEnd(); ++iter) {
KScreen::OutputPtr kscreenOutput = (*iter)->toKScreenOutput();
kscreenOutputs.insert(kscreenOutput->id(), kscreenOutput);
}
config->setOutputs(kscreenOutputs);
config->setScreen(m_screen->toKScreenScreen());
return config;
}
void XRandRConfig::applyKScreenConfig(const KScreen::ConfigPtr &config)
{
const KScreen::OutputList kscreenOutputs = config->outputs();
const QSize newScreenSize = screenSize(config);
const QSize currentScreenSize = m_screen->currentSize();
// When the current screen configuration is bigger than the new size (like
// when rotating an output), the XSetScreenSize can fail or apply the smaller
// size only partially, because we apply the size (we have to) before the
// output changes. To prevent all kinds of weird screen sizes from happening,
// we initially set such screen size, that it can take the current as well
// as the new configuration, then we apply the output changes, and finally then
// (if necessary) we reduce the screen size to fix the new configuration precisely.
const QSize intermediateScreenSize =
QSize(qMax(newScreenSize.width(), currentScreenSize.width()), qMax(newScreenSize.height(), currentScreenSize.height()));
int neededCrtcs = 0;
xcb_randr_output_t primaryOutput = 0;
xcb_randr_output_t oldPrimaryOutput = 0;
for (const XRandROutput *xrandrOutput : m_outputs) {
if (xrandrOutput->isPrimary()) {
oldPrimaryOutput = xrandrOutput->id();
break;
}
}
KScreen::OutputList toDisable, toEnable, toChange;
for (const KScreen::OutputPtr &kscreenOutput : kscreenOutputs) {
xcb_randr_output_t outputId = kscreenOutput->id();
XRandROutput *currentOutput = output(outputId);
// Only set the output as primary if it is enabled.
if (kscreenOutput->isPrimary() && kscreenOutput->isEnabled()) {
primaryOutput = outputId;
}
const bool currentEnabled = currentOutput->isEnabled();
if (!kscreenOutput->isEnabled() && currentEnabled) {
toDisable.insert(outputId, kscreenOutput);
continue;
} else if (kscreenOutput->isEnabled() && !currentEnabled) {
toEnable.insert(outputId, kscreenOutput);
++neededCrtcs;
continue;
} else if (!kscreenOutput->isEnabled() && !currentEnabled) {
continue;
}
++neededCrtcs;
if (kscreenOutput->currentModeId() != currentOutput->currentModeId()) {
if (!toChange.contains(outputId)) {
toChange.insert(outputId, kscreenOutput);
}
}
if (kscreenOutput->pos() != currentOutput->position()) {
if (!toChange.contains(outputId)) {
toChange.insert(outputId, kscreenOutput);
}
}
if (kscreenOutput->rotation() != currentOutput->rotation()) {
if (!toChange.contains(outputId)) {
toChange.insert(outputId, kscreenOutput);
}
}
if (kscreenOutput->explicitLogicalSize() != currentOutput->logicalSize()) {
if (!toChange.contains(outputId)) {
toChange.insert(outputId, kscreenOutput);
}
}
XRandRMode *currentMode = currentOutput->modes().value(kscreenOutput->currentModeId().toInt());
// For some reason, in some environments currentMode is null
// which doesn't make sense because it is the *current* mode...
// Since we haven't been able to figure out the reason why
// this happens, we are adding this debug code to try to
// figure out how this happened.
if (!currentMode) {
qWarning() << "Current mode is null:"
<< "ModeId:" << currentOutput->currentModeId() << "Mode: " << currentOutput->currentMode() << "Output: " << currentOutput->id();
printConfig(config);
printInternalCond();
continue;
}
// When the output would not fit into new screen size, we need to disable and reposition it.
const QRect geom = kscreenOutput->geometry();
if (geom.right() > newScreenSize.width() || geom.bottom() > newScreenSize.height()) {
if (!toDisable.contains(outputId)) {
qCDebug(KSCREEN_XRANDR) << "The new output would not fit into screen - new geometry: " << geom << ", new screen size:" << newScreenSize;
toDisable.insert(outputId, kscreenOutput);
}
}
}
const KScreen::ScreenPtr kscreenScreen = config->screen();
if (newScreenSize.width() > kscreenScreen->maxSize().width() || newScreenSize.height() > kscreenScreen->maxSize().height()) {
qCDebug(KSCREEN_XRANDR) << "The new screen size is too big - requested: " << newScreenSize << ", maximum: " << kscreenScreen->maxSize();
return;
}
qCDebug(KSCREEN_XRANDR) << "Needed CRTCs: " << neededCrtcs;
XCB::ScopedPointer<xcb_randr_get_screen_resources_reply_t> screenResources(XRandR::screenResources());
if (neededCrtcs > screenResources->num_crtcs) {
qCDebug(KSCREEN_XRANDR) << "We need more CRTCs than we have available - requested: " << neededCrtcs << ", available: " << screenResources->num_crtcs;
return;
}
qCDebug(KSCREEN_XRANDR) << "Actions to perform:"
<< "\n"
<< "\tPrimary Output:" << (primaryOutput != oldPrimaryOutput);
if (primaryOutput != oldPrimaryOutput) {
qCDebug(KSCREEN_XRANDR) << "\t\tOld:" << oldPrimaryOutput << "\n"
<< "\t\tNew:" << primaryOutput;
}
qCDebug(KSCREEN_XRANDR) << "\tChange Screen Size:" << (newScreenSize != currentScreenSize);
if (newScreenSize != currentScreenSize) {
qCDebug(KSCREEN_XRANDR) << "\t\tOld:" << currentScreenSize << "\n"
<< "\t\tIntermediate:" << intermediateScreenSize << "\n"
<< "\t\tNew:" << newScreenSize;
}
qCDebug(KSCREEN_XRANDR) << "\tDisable outputs:" << !toDisable.isEmpty();
if (!toDisable.isEmpty()) {
qCDebug(KSCREEN_XRANDR) << "\t\t" << toDisable.keys();
}
qCDebug(KSCREEN_XRANDR) << "\tChange outputs:" << !toChange.isEmpty();
if (!toChange.isEmpty()) {
qCDebug(KSCREEN_XRANDR) << "\t\t" << toChange.keys();
}
qCDebug(KSCREEN_XRANDR) << "\tEnable outputs:" << !toEnable.isEmpty();
if (!toEnable.isEmpty()) {
qCDebug(KSCREEN_XRANDR) << "\t\t" << toEnable.keys();
}
// Grab the server so that no-one else can do changes to XRandR and to block
// change notifications until we are done
XCB::GrabServer grabber;
// If there is nothing to do, not even bother
if (oldPrimaryOutput == primaryOutput && toDisable.isEmpty() && toEnable.isEmpty() && toChange.isEmpty()) {
if (newScreenSize != currentScreenSize) {
setScreenSize(newScreenSize);
}
return;
}
for (const KScreen::OutputPtr &output : toDisable) {
disableOutput(output);
}
if (intermediateScreenSize != currentScreenSize) {
setScreenSize(intermediateScreenSize);
}
bool forceScreenSizeUpdate = false;
for (const KScreen::OutputPtr &output : toChange) {
if (!changeOutput(output)) {
/* If we disabled the output before changing it and XRandR failed
* to re-enable it, then update screen size too */
if (toDisable.contains(output->id())) {
output->setEnabled(false);
qCDebug(KSCREEN_XRANDR) << "Output failed to change: " << output->name();
forceScreenSizeUpdate = true;
}
}
}
for (const KScreen::OutputPtr &output : toEnable) {
if (!enableOutput(output)) {
qCDebug(KSCREEN_XRANDR) << "Output failed to be Enabled: " << output->name();
forceScreenSizeUpdate = true;
}
}
if (oldPrimaryOutput != primaryOutput) {
setPrimaryOutput(primaryOutput);
}
if (forceScreenSizeUpdate || intermediateScreenSize != newScreenSize) {
QSize newSize = newScreenSize;
if (forceScreenSizeUpdate) {
newSize = screenSize(config);
qCDebug(KSCREEN_XRANDR) << "Forced to change screen size: " << newSize;
}
setScreenSize(newSize);
}
}
void XRandRConfig::printConfig(const ConfigPtr &config) const
{
qCDebug(KSCREEN_XRANDR) << "KScreen version:" /*<< LIBKSCREEN_VERSION*/;
if (!config) {
qCDebug(KSCREEN_XRANDR) << "Config is invalid";
return;
}
if (!config->screen()) {
qCDebug(KSCREEN_XRANDR) << "No screen in the configuration, broken backend";
return;
}
qCDebug(KSCREEN_XRANDR) << "Screen:"
<< "\n"
<< "\tmaxSize:" << config->screen()->maxSize() << "\n"
<< "\tminSize:" << config->screen()->minSize() << "\n"
<< "\tcurrentSize:" << config->screen()->currentSize();
const OutputList outputs = config->outputs();
for (const OutputPtr &output : outputs) {
qCDebug(KSCREEN_XRANDR) << "\n-----------------------------------------------------\n"
<< "\n"
<< "Id: " << output->id() << "\n"
<< "Name: " << output->name() << "\n"
<< "Type: " << output->type() << "\n"
<< "Connected: " << output->isConnected();
if (!output->isConnected()) {
continue;
}
qCDebug(KSCREEN_XRANDR) << "Enabled: " << output->isEnabled() << "\n"
<< "Primary: " << output->isPrimary() << "\n"
<< "Rotation: " << output->rotation() << "\n"
<< "Pos: " << output->pos() << "\n"
<< "MMSize: " << output->sizeMm();
if (output->currentMode()) {
qCDebug(KSCREEN_XRANDR) << "Size: " << output->currentMode()->size();
}
qCDebug(KSCREEN_XRANDR) << "Clones: " << (output->clones().isEmpty() ? QStringLiteral("None") : QString::number(output->clones().count())) << "\n"
<< "Mode: " << output->currentModeId() << "\n"
<< "Preferred Mode: " << output->preferredModeId() << "\n"
<< "Preferred modes: " << output->preferredModes() << "\n"
<< "Modes: ";
ModeList modes = output->modes();
for (const ModePtr &mode : modes) {
qCDebug(KSCREEN_XRANDR) << "\t" << mode->id() << " " << mode->name() << " " << mode->size() << " " << mode->refreshRate();
}
Edid *edid = output->edid();
qCDebug(KSCREEN_XRANDR) << "EDID Info: ";
if (edid && edid->isValid()) {
qCDebug(KSCREEN_XRANDR) << "\tDevice ID: " << edid->deviceId() << "\n"
<< "\tName: " << edid->name() << "\n"
<< "\tVendor: " << edid->vendor() << "\n"
<< "\tSerial: " << edid->serial() << "\n"
<< "\tEISA ID: " << edid->eisaId() << "\n"
<< "\tHash: " << edid->hash() << "\n"
<< "\tWidth: " << edid->width() << "\n"
<< "\tHeight: " << edid->height() << "\n"
<< "\tGamma: " << edid->gamma() << "\n"
<< "\tRed: " << edid->red() << "\n"
<< "\tGreen: " << edid->green() << "\n"
<< "\tBlue: " << edid->blue() << "\n"
<< "\tWhite: " << edid->white();
} else {
qCDebug(KSCREEN_XRANDR) << "\tUnavailable";
}
}
}
void XRandRConfig::printInternalCond() const
{
qCDebug(KSCREEN_XRANDR) << "Internal config in xrandr";
for (const XRandROutput *output : m_outputs) {
qCDebug(KSCREEN_XRANDR) << "Id: " << output->id() << "\n"
<< "Current Mode: " << output->currentMode() << "\n"
<< "Current mode id: " << output->currentModeId() << "\n"
<< "Connected: " << output->isConnected() << "\n"
<< "Enabled: " << output->isEnabled() << "\n"
<< "Primary: " << output->isPrimary();
if (!output->isEnabled()) {
continue;
}
XRandRMode::Map modes = output->modes();
for (const XRandRMode *mode : modes) {
qCDebug(KSCREEN_XRANDR) << "\t" << mode->id() << "\n"
<< "\t" << mode->name() << "\n"
<< "\t" << mode->size() << mode->refreshRate();
}
}
}
QSize XRandRConfig::screenSize(const KScreen::ConfigPtr &config) const
{
QRect rect;
for (const KScreen::OutputPtr &output : config->outputs()) {
if (!output->isConnected() || !output->isEnabled()) {
continue;
}
const ModePtr currentMode = output->currentMode();
if (!currentMode) {
qCDebug(KSCREEN_XRANDR) << "Output: " << output->name() << " has no current Mode!";
continue;
}
const QRect outputGeom = output->geometry();
rect = rect.united(outputGeom);
}
const QSize size = QSize(rect.width(), rect.height());
qCDebug(KSCREEN_XRANDR) << "Requested screen size is" << size;
return size;
}
bool XRandRConfig::setScreenSize(const QSize &size) const
{
const double dpi = 25.4 * XRandR::screen()->height_in_pixels / XRandR::screen()->height_in_millimeters;
const int widthMM = (25.4 * size.width()) / dpi;
const int heightMM = (25.4 * size.height()) / dpi;
qCDebug(KSCREEN_XRANDR) << "RRSetScreenSize"
<< "\n"
<< "\tDPI:" << dpi << "\n"
<< "\tSize:" << size << "\n"
<< "\tSizeMM:" << QSize(widthMM, heightMM);
xcb_randr_set_screen_size(XCB::connection(), XRandR::rootWindow(), size.width(), size.height(), widthMM, heightMM);
m_screen->update(size);
return true;
}
void XRandRConfig::setPrimaryOutput(xcb_randr_output_t outputId) const
{
qCDebug(KSCREEN_XRANDR) << "RRSetOutputPrimary"
<< "\n"
<< "\tNew primary:" << outputId;
xcb_randr_set_output_primary(XCB::connection(), XRandR::rootWindow(), outputId);
for (XRandROutput *output : m_outputs) {
output->setIsPrimary(output->id() == outputId);
}
}
bool XRandRConfig::disableOutput(const OutputPtr &kscreenOutput) const
{
XRandROutput *xOutput = output(kscreenOutput->id());
Q_ASSERT(xOutput);
Q_ASSERT(xOutput->crtc());
if (!xOutput->crtc()) {
qCWarning(KSCREEN_XRANDR) << "Attempting to disable output without CRTC, wth?";
return false;
}
const xcb_randr_crtc_t crtc = xOutput->crtc()->crtc();
qCDebug(KSCREEN_XRANDR) << "RRSetCrtcConfig (disable output)"
<< "\n"
<< "\tCRTC:" << crtc;
auto cookie = xcb_randr_set_crtc_config(XCB::connection(), //
crtc,
XCB_CURRENT_TIME,
XCB_CURRENT_TIME,
0,
0,
XCB_NONE,
XCB_RANDR_ROTATION_ROTATE_0,
0,
nullptr);
XCB::ScopedPointer<xcb_randr_set_crtc_config_reply_t> reply(xcb_randr_set_crtc_config_reply(XCB::connection(), cookie, nullptr));
if (!reply) {
qCDebug(KSCREEN_XRANDR) << "\tResult: unknown (error)";
return false;
}
qCDebug(KSCREEN_XRANDR) << "\tResult:" << reply->status;
// Update the cached output now, otherwise we get RRNotify_CrtcChange notification
// for an outdated output, which can lead to a crash.
if (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS) {
xOutput->update(XCB_NONE,
XCB_NONE,
xOutput->isConnected() ? XCB_RANDR_CONNECTION_CONNECTED : XCB_RANDR_CONNECTION_DISCONNECTED,
kscreenOutput->isPrimary());
if (xOutput->crtc())
xOutput->crtc()->updateTimestamp(reply->timestamp);
}
return (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS);
}
bool XRandRConfig::enableOutput(const OutputPtr &kscreenOutput) const
{
XRandRCrtc *freeCrtc = nullptr;
qCDebug(KSCREEN_XRANDR) << m_crtcs;
for (XRandRCrtc *crtc : m_crtcs) {
crtc->update();
qCDebug(KSCREEN_XRANDR) << "Testing CRTC" << crtc->crtc() << "\n"
<< "\tFree:" << crtc->isFree() << "\n"
<< "\tMode:" << crtc->mode() << "\n"
<< "\tPossible outputs:" << crtc->possibleOutputs() << "\n"
<< "\tConnected outputs:" << crtc->outputs() << "\n"
<< "\tGeometry:" << crtc->geometry();
if (crtc->isFree() && crtc->possibleOutputs().contains(kscreenOutput->id())) {
freeCrtc = crtc;
break;
}
}
if (!freeCrtc) {
qCWarning(KSCREEN_XRANDR) << "Failed to get free CRTC for output" << kscreenOutput->id();
return false;
}
XRandROutput *xOutput = output(kscreenOutput->id());
const int modeId = kscreenOutput->currentMode() ? kscreenOutput->currentModeId().toInt() : kscreenOutput->preferredModeId().toInt();
xOutput->updateLogicalSize(kscreenOutput, freeCrtc);
qCDebug(KSCREEN_XRANDR) << "RRSetCrtcConfig (enable output)"
<< "\n"
<< "\tOutput:" << kscreenOutput->id() << "(" << kscreenOutput->name() << ")"
<< "\n"
<< "\tNew CRTC:" << freeCrtc->crtc() << "\n"
<< "\tPos:" << kscreenOutput->pos() << "\n"
<< "\tMode:" << kscreenOutput->currentMode() << "Preferred:" << kscreenOutput->preferredModeId() << "\n"
<< "\tRotation:" << kscreenOutput->rotation();
if (!sendConfig(kscreenOutput, freeCrtc)) {
return false;
}
xOutput->update(freeCrtc->crtc(), modeId, XCB_RANDR_CONNECTION_CONNECTED, kscreenOutput->isPrimary());
return true;
}
bool XRandRConfig::changeOutput(const KScreen::OutputPtr &kscreenOutput) const
{
XRandROutput *xOutput = output(kscreenOutput->id());
Q_ASSERT(xOutput);
if (!xOutput->crtc()) {
qCDebug(KSCREEN_XRANDR) << "Output" << kscreenOutput->id() << "has no CRTC, falling back to enableOutput()";
return enableOutput(kscreenOutput);
}
int modeId = kscreenOutput->currentMode() ? kscreenOutput->currentModeId().toInt() : kscreenOutput->preferredModeId().toInt();
xOutput->updateLogicalSize(kscreenOutput);
qCDebug(KSCREEN_XRANDR) << "RRSetCrtcConfig (change output)"
<< "\n"
<< "\tOutput:" << kscreenOutput->id() << "(" << kscreenOutput->name() << ")"
<< "\n"
<< "\tCRTC:" << xOutput->crtc()->crtc() << "\n"
<< "\tPos:" << kscreenOutput->pos() << "\n"
<< "\tMode:" << kscreenOutput->currentMode() << "Preferred:" << kscreenOutput->preferredModeId() << "\n"
<< "\tRotation:" << kscreenOutput->rotation();
if (!sendConfig(kscreenOutput, xOutput->crtc())) {
return false;
}
xOutput->update(xOutput->crtc()->crtc(), modeId, XCB_RANDR_CONNECTION_CONNECTED, kscreenOutput->isPrimary());
return true;
}
bool XRandRConfig::sendConfig(const KScreen::OutputPtr &kscreenOutput, XRandRCrtc *crtc) const
{
xcb_randr_output_t outputs[1]{static_cast<xcb_randr_output_t>(kscreenOutput->id())};
const int modeId = kscreenOutput->currentMode() ? kscreenOutput->currentModeId().toInt() : kscreenOutput->preferredModeId().toInt();
auto cookie = xcb_randr_set_crtc_config(XCB::connection(),
crtc->crtc(),
XCB_CURRENT_TIME,
XCB_CURRENT_TIME,
kscreenOutput->pos().rx(),
kscreenOutput->pos().ry(),
modeId,
kscreenOutput->rotation(),
1,
outputs);
XCB::ScopedPointer<xcb_randr_set_crtc_config_reply_t> reply(xcb_randr_set_crtc_config_reply(XCB::connection(), cookie, nullptr));
if (!reply) {
qCDebug(KSCREEN_XRANDR) << "\tResult: unknown (error)";
return false;
}
crtc->updateTimestamp(reply->timestamp);
qCDebug(KSCREEN_XRANDR) << "\tResult: " << reply->status << " timestamp: " << reply->timestamp;
return (reply->status == XCB_RANDR_SET_CONFIG_SUCCESS);
}

View File

@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#pragma once
#include <QObject>
#include "xrandr.h"
#include "xrandrcrtc.h"
#include "xrandroutput.h"
class XRandRScreen;
namespace KScreen
{
class Config;
}
class XRandRConfig : public QObject
{
Q_OBJECT
public:
XRandRConfig();
~XRandRConfig() override;
XRandROutput::Map outputs() const;
XRandROutput *output(xcb_randr_output_t output) const;
XRandRCrtc::Map crtcs() const;
XRandRCrtc *crtc(xcb_randr_crtc_t crtc) const;
XRandRScreen *screen() const;
void addNewOutput(xcb_randr_output_t id);
void addNewCrtc(xcb_randr_crtc_t crtc);
void removeOutput(xcb_randr_output_t id);
KScreen::ConfigPtr toKScreenConfig() const;
void applyKScreenConfig(const KScreen::ConfigPtr &config);
private:
QSize screenSize(const KScreen::ConfigPtr &config) const;
bool setScreenSize(const QSize &size) const;
void setPrimaryOutput(xcb_randr_output_t outputId) const;
bool disableOutput(const KScreen::OutputPtr &output) const;
bool enableOutput(const KScreen::OutputPtr &output) const;
bool changeOutput(const KScreen::OutputPtr &output) const;
bool sendConfig(const KScreen::OutputPtr &kscreenOutput, XRandRCrtc *crtc) const;
/**
* We need to print stuff to discover the damn bug
* where currentMode is null
*/
void printConfig(const KScreen::ConfigPtr &config) const;
void printInternalCond() const;
XRandROutput::Map m_outputs;
XRandRCrtc::Map m_crtcs;
XRandRScreen *m_screen;
};

View File

@ -0,0 +1,136 @@
/*
* SPDX-FileCopyrightText: 2015 Daniel Vrátil <dvratil@redhat.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*
*/
#include "xrandrcrtc.h"
#include "xrandr.h"
#include "xrandrconfig.h"
#include "../xcbwrapper.h"
XRandRCrtc::XRandRCrtc(xcb_randr_crtc_t crtc, XRandRConfig *config)
: QObject(config)
, m_crtc(crtc)
, m_mode(0)
, m_rotation(XCB_RANDR_ROTATION_ROTATE_0)
, m_timestamp(XCB_CURRENT_TIME)
, m_configTimestamp(XCB_CURRENT_TIME)
{
update();
}
xcb_randr_crtc_t XRandRCrtc::crtc() const
{
return m_crtc;
}
xcb_randr_mode_t XRandRCrtc::mode() const
{
return m_mode;
}
QRect XRandRCrtc::geometry() const
{
return m_geometry;
}
xcb_randr_rotation_t XRandRCrtc::rotation() const
{
return m_rotation;
}
QVector<xcb_randr_output_t> XRandRCrtc::possibleOutputs()
{
return m_possibleOutputs;
}
QVector<xcb_randr_output_t> XRandRCrtc::outputs() const
{
return m_outputs;
}
bool XRandRCrtc::connectOutput(xcb_randr_output_t output)
{
update();
qCDebug(KSCREEN_XRANDR) << "Connected output" << output << "to CRTC" << m_crtc;
if (!m_possibleOutputs.contains(output)) {
qCDebug(KSCREEN_XRANDR) << "Output" << output << "is not an allowed output for CRTC" << m_crtc;
return false;
}
if (!m_outputs.contains(output)) {
m_outputs.append(output);
}
return true;
}
void XRandRCrtc::disconectOutput(xcb_randr_output_t output)
{
update();
qCDebug(KSCREEN_XRANDR) << "Disconnected output" << output << "from CRTC" << m_crtc;
const int index = m_outputs.indexOf(output);
if (index > -1) {
m_outputs.remove(index);
}
}
bool XRandRCrtc::isFree() const
{
return m_outputs.isEmpty();
}
void XRandRCrtc::update()
{
XCB::CRTCInfo crtcInfo(m_crtc, XCB_TIME_CURRENT_TIME);
m_mode = crtcInfo->mode;
m_geometry = QRect(crtcInfo->x, crtcInfo->y, crtcInfo->width, crtcInfo->height);
m_rotation = (xcb_randr_rotation_t)crtcInfo->rotation;
m_possibleOutputs.clear();
m_possibleOutputs.reserve(crtcInfo->num_possible_outputs);
xcb_randr_output_t *possible = xcb_randr_get_crtc_info_possible(crtcInfo);
for (int i = 0; i < crtcInfo->num_possible_outputs; ++i) {
m_possibleOutputs.append(possible[i]);
}
m_outputs.clear();
xcb_randr_output_t *outputs = xcb_randr_get_crtc_info_outputs(crtcInfo);
for (int i = 0; i < crtcInfo->num_outputs; ++i) {
m_outputs.append(outputs[i]);
}
}
void XRandRCrtc::update(xcb_randr_mode_t mode, xcb_randr_rotation_t rotation, const QRect &geom)
{
m_mode = mode;
m_geometry = geom;
m_rotation = rotation;
}
void XRandRCrtc::updateTimestamp(const xcb_timestamp_t tmstamp)
{
if (tmstamp > m_timestamp) {
qCDebug(KSCREEN_XRANDR) << "XRandRCrtc " << m_crtc << " m_timestamp update " << m_timestamp << " => " << tmstamp;
m_timestamp = tmstamp;
}
}
void XRandRCrtc::updateConfigTimestamp(const xcb_timestamp_t tmstamp)
{
if (tmstamp > m_configTimestamp) {
qCDebug(KSCREEN_XRANDR) << "XRandRCrtc " << m_crtc << " m_configTimestamp update" << m_configTimestamp << " => " << tmstamp;
m_configTimestamp = tmstamp;
}
}
bool XRandRCrtc::isChangedFromOutside() const
{
return m_configTimestamp > m_timestamp;
}

View File

@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2015 Daniel Vrátil <dvratil@redhat.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*
*/
#pragma once
#include <QMap>
#include <QObject>
#include <QRect>
#include <QVector>
#include <xcb/randr.h>
class XRandRConfig;
class XRandRCrtc : public QObject
{
Q_OBJECT
public:
typedef QMap<xcb_randr_crtc_t, XRandRCrtc *> Map;
XRandRCrtc(xcb_randr_crtc_t crtc, XRandRConfig *config);
xcb_randr_crtc_t crtc() const;
xcb_randr_mode_t mode() const;
QRect geometry() const;
xcb_randr_rotation_t rotation() const;
QVector<xcb_randr_output_t> possibleOutputs();
QVector<xcb_randr_output_t> outputs() const;
bool connectOutput(xcb_randr_output_t output);
void disconectOutput(xcb_randr_output_t output);
bool isFree() const;
void update();
void update(xcb_randr_crtc_t mode, xcb_randr_rotation_t rotation, const QRect &geom);
void updateTimestamp(const xcb_timestamp_t tmstamp);
void updateConfigTimestamp(const xcb_timestamp_t tmstamp);
bool isChangedFromOutside() const;
private:
xcb_randr_crtc_t m_crtc;
xcb_randr_mode_t m_mode;
QRect m_geometry;
xcb_randr_rotation_t m_rotation;
QVector<xcb_randr_output_t> m_possibleOutputs;
QVector<xcb_randr_output_t> m_outputs;
xcb_timestamp_t m_timestamp;
xcb_timestamp_t m_configTimestamp;
};

View File

@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "xrandrmode.h"
#include "mode.h"
#include "output.h"
#include "xrandroutput.h"
XRandRMode::XRandRMode(const xcb_randr_mode_info_t &modeInfo, XRandROutput *output)
: QObject(output)
{
m_id = modeInfo.id;
// FIXME XCB
// m_name = QString::fromUtf8(modeInfo->name);
m_size = QSize(modeInfo.width, modeInfo.height);
double vTotal = modeInfo.vtotal;
if (modeInfo.mode_flags & XCB_RANDR_MODE_FLAG_DOUBLE_SCAN) {
/* doublescan doubles the number of lines */
vTotal *= 2;
}
if (modeInfo.mode_flags & XCB_RANDR_MODE_FLAG_INTERLACE) {
/* interlace splits the frame into two fields */
/* the field rate is what is typically reported by monitors */
vTotal /= 2;
}
m_refreshRate = (float)modeInfo.dot_clock / ((float)modeInfo.htotal * vTotal);
}
XRandRMode::~XRandRMode()
{
}
KScreen::ModePtr XRandRMode::toKScreenMode()
{
KScreen::ModePtr kscreenMode(new KScreen::Mode);
kscreenMode->setId(QString::number(m_id));
kscreenMode->setName(m_name);
kscreenMode->setSize(m_size);
kscreenMode->setRefreshRate(m_refreshRate);
return kscreenMode;
}
xcb_randr_mode_t XRandRMode::id() const
{
return m_id;
}
QSize XRandRMode::size() const
{
return m_size;
}
float XRandRMode::refreshRate() const
{
return m_refreshRate;
}
QString XRandRMode::name() const
{
return m_name;
}

View File

@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#pragma once
#include <QMap>
#include <QObject>
#include <QSize>
#include <QVariant>
#include "../xcbwrapper.h"
#include "types.h"
class XRandROutput;
namespace KScreen
{
class Output;
class Mode;
}
class XRandRMode : public QObject
{
Q_OBJECT
public:
typedef QMap<xcb_randr_mode_t, XRandRMode *> Map;
explicit XRandRMode(const xcb_randr_mode_info_t &modeInfo, XRandROutput *output);
~XRandRMode() override;
KScreen::ModePtr toKScreenMode();
xcb_randr_mode_t id() const;
QSize size() const;
float refreshRate() const;
QString name() const;
private:
xcb_randr_mode_t m_id;
QString m_name;
QSize m_size;
float m_refreshRate;
};
Q_DECLARE_METATYPE(XRandRMode::Map)

View File

@ -0,0 +1,438 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
* SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "xrandroutput.h"
#include "../utils.h"
#include "config.h"
#include "xrandr.h"
#include "xrandrconfig.h"
#include "xrandrmode.h"
#include <xcb/render.h>
Q_DECLARE_METATYPE(QList<int>)
#define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t)((d)*65536))
#define FIXED_TO_DOUBLE(f) ((double)((f) / 65536.0))
xcb_render_fixed_t fOne = DOUBLE_TO_FIXED(1);
xcb_render_fixed_t fZero = DOUBLE_TO_FIXED(0);
XRandROutput::XRandROutput(xcb_randr_output_t id, XRandRConfig *config)
: QObject(config)
, m_config(config)
, m_id(id)
, m_primary(false)
, m_type(KScreen::Output::Unknown)
, m_crtc(nullptr)
{
init();
}
XRandROutput::~XRandROutput()
{
}
xcb_randr_output_t XRandROutput::id() const
{
return m_id;
}
bool XRandROutput::isConnected() const
{
return m_connected == XCB_RANDR_CONNECTION_CONNECTED;
}
bool XRandROutput::isEnabled() const
{
return m_crtc != nullptr && m_crtc->mode() != XCB_NONE;
}
bool XRandROutput::isPrimary() const
{
return m_primary;
}
QPoint XRandROutput::position() const
{
return m_crtc ? m_crtc->geometry().topLeft() : QPoint();
}
QSize XRandROutput::size() const
{
return m_crtc ? m_crtc->geometry().size() : QSize();
}
XRandRMode::Map XRandROutput::modes() const
{
return m_modes;
}
QString XRandROutput::currentModeId() const
{
return m_crtc ? QString::number(m_crtc->mode()) : QString();
}
XRandRMode *XRandROutput::currentMode() const
{
if (!m_crtc) {
return nullptr;
}
unsigned int modeId = m_crtc->mode();
if (!m_modes.contains(modeId)) {
return nullptr;
}
return m_modes[modeId];
}
KScreen::Output::Rotation XRandROutput::rotation() const
{
return static_cast<KScreen::Output::Rotation>(m_crtc ? m_crtc->rotation() : XCB_RANDR_ROTATION_ROTATE_0);
}
bool XRandROutput::isHorizontal() const
{
const auto rot = rotation();
return rot == KScreen::Output::Rotation::None || rot == KScreen::Output::Rotation::Inverted;
}
QByteArray XRandROutput::edid() const
{
if (m_edid.isNull()) {
m_edid = XRandR::outputEdid(m_id);
}
return m_edid;
}
XRandRCrtc *XRandROutput::crtc() const
{
return m_crtc;
}
void XRandROutput::update()
{
init();
}
void XRandROutput::update(xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_connection_t conn, bool primary)
{
qCDebug(KSCREEN_XRANDR) << "XRandROutput" << m_id << "update"
<< "\n"
<< "\tm_connected:" << m_connected << "\n"
<< "\tm_crtc" << m_crtc << "\n"
<< "\tCRTC:" << crtc << "\n"
<< "\tMODE:" << mode << "\n"
<< "\tConnection:" << conn << "\n"
<< "\tPrimary:" << primary;
// Connected or disconnected
if (isConnected() != (conn == XCB_RANDR_CONNECTION_CONNECTED)) {
if (conn == XCB_RANDR_CONNECTION_CONNECTED) {
// New monitor has been connected, refresh everything
init();
} else {
// Disconnected
m_connected = conn;
m_clones.clear();
m_heightMm = 0;
m_widthMm = 0;
m_type = KScreen::Output::Unknown;
qDeleteAll(m_modes);
m_modes.clear();
m_preferredModes.clear();
m_edid.clear();
}
} else if (conn == XCB_RANDR_CONNECTION_CONNECTED) {
// the output changed in some way, let's update the internal
// list of modes, as it may have changed
XCB::OutputInfo outputInfo(m_id, XCB_TIME_CURRENT_TIME);
if (outputInfo) {
updateModes(outputInfo);
}
m_hotplugModeUpdate = XRandR::hasProperty(m_id, "hotplug_mode_update");
}
// A monitor has been enabled or disabled
// We don't use isEnabled(), because it checks for crtc && crtc->mode(), however
// crtc->mode may already be unset due to xcb_randr_crtc_tChangeNotify coming before
// xcb_randr_output_tChangeNotify and reseting the CRTC mode
if ((m_crtc == nullptr) != (crtc == XCB_NONE)) {
if (crtc == XCB_NONE && mode == XCB_NONE) {
// Monitor has been disabled
m_crtc->disconectOutput(m_id);
m_crtc = nullptr;
} else {
m_crtc = m_config->crtc(crtc);
m_crtc->connectOutput(m_id);
}
}
// Primary has changed
m_primary = primary;
}
void XRandROutput::setIsPrimary(bool primary)
{
m_primary = primary;
}
void XRandROutput::init()
{
XCB::OutputInfo outputInfo(m_id, XCB_TIME_CURRENT_TIME);
Q_ASSERT(outputInfo);
if (!outputInfo) {
return;
}
XCB::PrimaryOutput primary(XRandR::rootWindow());
m_name = QString::fromUtf8((const char *)xcb_randr_get_output_info_name(outputInfo.data()), outputInfo->name_len);
m_type = fetchOutputType(m_id, m_name);
m_icon = QString();
m_connected = (xcb_randr_connection_t)outputInfo->connection;
m_primary = (primary->output == m_id);
xcb_randr_output_t *clones = xcb_randr_get_output_info_clones(outputInfo.data());
for (int i = 0; i < outputInfo->num_clones; ++i) {
m_clones.append(clones[i]);
}
m_widthMm = outputInfo->mm_width;
m_heightMm = outputInfo->mm_height;
m_crtc = m_config->crtc(outputInfo->crtc);
if (m_crtc) {
m_crtc->connectOutput(m_id);
}
m_hotplugModeUpdate = XRandR::hasProperty(m_id, "hotplug_mode_update");
updateModes(outputInfo);
}
void XRandROutput::updateModes(const XCB::OutputInfo &outputInfo)
{
/* Init modes */
XCB::ScopedPointer<xcb_randr_get_screen_resources_reply_t> screenResources(XRandR::screenResources());
Q_ASSERT(screenResources);
if (!screenResources) {
return;
}
xcb_randr_mode_info_t *modes = xcb_randr_get_screen_resources_modes(screenResources.data());
xcb_randr_mode_t *outputModes = xcb_randr_get_output_info_modes(outputInfo.data());
m_preferredModes.clear();
qDeleteAll(m_modes);
m_modes.clear();
for (int i = 0; i < outputInfo->num_modes; ++i) {
/* Resources->modes contains all possible modes, we are only interested
* in those listed in outputInfo->modes. */
for (int j = 0; j < screenResources->num_modes; ++j) {
if (modes[j].id != outputModes[i]) {
continue;
}
XRandRMode *mode = new XRandRMode(modes[j], this);
m_modes.insert(mode->id(), mode);
if (i < outputInfo->num_preferred) {
m_preferredModes.append(QString::number(mode->id()));
}
break;
}
}
}
KScreen::Output::Type XRandROutput::fetchOutputType(xcb_randr_output_t outputId, const QString &name)
{
QString type = QString::fromUtf8(typeFromProperty(outputId));
if (type.isEmpty()) {
type = name;
}
return Utils::guessOutputType(type, name);
}
QByteArray XRandROutput::typeFromProperty(xcb_randr_output_t outputId)
{
QByteArray type;
XCB::InternAtom atomType(true, 13, "ConnectorType");
if (!atomType) {
return type;
}
auto cookie = xcb_randr_get_output_property(XCB::connection(), outputId, atomType->atom, XCB_ATOM_ANY, 0, 100, false, false);
XCB::ScopedPointer<xcb_randr_get_output_property_reply_t> reply(xcb_randr_get_output_property_reply(XCB::connection(), cookie, nullptr));
if (!reply) {
return type;
}
if (!(reply->type == XCB_ATOM_ATOM && reply->format == 32 && reply->num_items == 1)) {
return type;
}
const uint8_t *prop = xcb_randr_get_output_property_data(reply.data());
XCB::AtomName atomName(*reinterpret_cast<const xcb_atom_t *>(prop));
if (!atomName) {
return type;
}
char *connectorType = xcb_get_atom_name_name(atomName);
if (!connectorType) {
return type;
}
type = connectorType;
return type;
}
bool isScaling(const xcb_render_transform_t &tr)
{
return tr.matrix11 != fZero && tr.matrix12 == fZero && tr.matrix13 == fZero && tr.matrix21 == fZero && tr.matrix22 != fZero && tr.matrix23 == fZero
&& tr.matrix31 == fZero && tr.matrix32 == fZero && tr.matrix33 == fOne;
}
xcb_render_transform_t zeroTransform()
{
return {DOUBLE_TO_FIXED(0),
DOUBLE_TO_FIXED(0),
DOUBLE_TO_FIXED(0),
DOUBLE_TO_FIXED(0),
DOUBLE_TO_FIXED(0),
DOUBLE_TO_FIXED(0),
DOUBLE_TO_FIXED(0),
DOUBLE_TO_FIXED(0),
DOUBLE_TO_FIXED(0)};
}
xcb_render_transform_t unityTransform()
{
return {DOUBLE_TO_FIXED(1),
DOUBLE_TO_FIXED(0),
DOUBLE_TO_FIXED(0),
DOUBLE_TO_FIXED(0),
DOUBLE_TO_FIXED(1),
DOUBLE_TO_FIXED(0),
DOUBLE_TO_FIXED(0),
DOUBLE_TO_FIXED(0),
DOUBLE_TO_FIXED(1)};
}
xcb_render_transform_t XRandROutput::currentTransform() const
{
auto cookie = xcb_randr_get_crtc_transform(XCB::connection(), m_crtc->crtc());
xcb_generic_error_t *error = nullptr;
auto *reply = xcb_randr_get_crtc_transform_reply(XCB::connection(), cookie, &error);
if (error) {
return zeroTransform();
}
const xcb_render_transform_t transform = reply->pending_transform;
free(reply);
return transform;
}
QSizeF XRandROutput::logicalSize() const
{
const QSize modeSize = size();
if (!modeSize.isValid()) {
return QSize();
}
const xcb_render_transform_t transform = currentTransform();
const qreal width = FIXED_TO_DOUBLE(transform.matrix11) * modeSize.width();
const qreal height = FIXED_TO_DOUBLE(transform.matrix22) * modeSize.height();
return QSizeF(width, height);
}
void XRandROutput::updateLogicalSize(const KScreen::OutputPtr &output, XRandRCrtc *crtc)
{
if (!crtc) {
// TODO: This is a workaround for now when updateLogicalSize is called on enabling the
// output. At this point m_crtc is not yet set. Change the order in the future so
// that the additional argument is not necessary anymore.
crtc = m_crtc;
}
const QSizeF logicalSize = output->explicitLogicalSize();
xcb_render_transform_t transform = unityTransform();
KScreen::ModePtr mode = output->currentMode() ? output->currentMode() : output->preferredMode();
if (mode && logicalSize.isValid()) {
QSize modeSize = mode->size();
if (!output->isHorizontal()) {
modeSize.transpose();
}
const qreal widthFactor = logicalSize.width() / (qreal)modeSize.width();
const qreal heightFactor = logicalSize.height() / (qreal)modeSize.height();
transform.matrix11 = DOUBLE_TO_FIXED(widthFactor);
transform.matrix22 = DOUBLE_TO_FIXED(heightFactor);
}
QByteArray filterName(isScaling(transform) ? "bilinear" : "nearest");
auto cookie = xcb_randr_set_crtc_transform_checked(XCB::connection(), crtc->crtc(), transform, filterName.size(), filterName.data(), 0, nullptr);
xcb_generic_error_t *error = xcb_request_check(XCB::connection(), cookie);
if (error) {
qCDebug(KSCREEN_XRANDR) << "Error on logical size transformation!";
free(error);
}
}
KScreen::OutputPtr XRandROutput::toKScreenOutput() const
{
KScreen::OutputPtr kscreenOutput(new KScreen::Output);
const bool signalsBlocked = kscreenOutput->signalsBlocked();
kscreenOutput->blockSignals(true);
kscreenOutput->setId(m_id);
kscreenOutput->setType(m_type);
kscreenOutput->setSizeMm(QSize(m_widthMm, m_heightMm));
kscreenOutput->setName(m_name);
kscreenOutput->setIcon(m_icon);
// See https://bugzilla.redhat.com/show_bug.cgi?id=1290586
// QXL will be creating a new mode we need to jump to every time the display is resized
kscreenOutput->setFollowPreferredMode(m_hotplugModeUpdate && m_crtc && m_crtc->isChangedFromOutside());
kscreenOutput->setConnected(isConnected());
if (isConnected()) {
KScreen::ModeList kscreenModes;
for (auto iter = m_modes.constBegin(), end = m_modes.constEnd(); iter != end; ++iter) {
XRandRMode *mode = iter.value();
kscreenModes.insert(QString::number(iter.key()), mode->toKScreenMode());
}
kscreenOutput->setModes(kscreenModes);
kscreenOutput->setPreferredModes(m_preferredModes);
kscreenOutput->setPrimary(m_primary);
kscreenOutput->setClones([](const QList<xcb_randr_output_t> &clones) {
QList<int> kclones;
kclones.reserve(clones.size());
for (xcb_randr_output_t o : clones) {
kclones.append(static_cast<int>(o));
}
return kclones;
}(m_clones));
kscreenOutput->setEnabled(isEnabled());
if (isEnabled()) {
kscreenOutput->setSize(size());
kscreenOutput->setPos(position());
kscreenOutput->setRotation(rotation());
kscreenOutput->setCurrentModeId(currentModeId());
}
// TODO: set logical size?
}
kscreenOutput->blockSignals(signalsBlocked);
return kscreenOutput;
}

View File

@ -0,0 +1,98 @@
/*
* SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#pragma once
#include "output.h"
#include "../xcbwrapper.h"
#include "xrandrmode.h"
#include <QMap>
#include <QObject>
#include <QVariant>
class XRandRConfig;
class XRandRCrtc;
namespace KScreen
{
class Config;
class Output;
}
class XRandROutput : public QObject
{
Q_OBJECT
public:
typedef QMap<xcb_randr_output_t, XRandROutput *> Map;
explicit XRandROutput(xcb_randr_output_t id, XRandRConfig *config);
~XRandROutput() override;
void disabled();
void disconnected();
void update();
void update(xcb_randr_crtc_t crtc, xcb_randr_mode_t mode, xcb_randr_connection_t conn, bool primary);
void setIsPrimary(bool primary);
xcb_randr_output_t id() const;
bool isEnabled() const;
bool isConnected() const;
bool isPrimary() const;
QPoint position() const;
QSize size() const;
QSizeF logicalSize() const;
QString currentModeId() const;
XRandRMode::Map modes() const;
XRandRMode *currentMode() const;
KScreen::Output::Rotation rotation() const;
bool isHorizontal() const;
QByteArray edid() const;
XRandRCrtc *crtc() const;
KScreen::OutputPtr toKScreenOutput() const;
void updateLogicalSize(const KScreen::OutputPtr &output, XRandRCrtc *crtc = nullptr);
private:
void init();
void updateModes(const XCB::OutputInfo &outputInfo);
static KScreen::Output::Type fetchOutputType(xcb_randr_output_t outputId, const QString &name);
static QByteArray typeFromProperty(xcb_randr_output_t outputId);
xcb_render_transform_t currentTransform() const;
XRandRConfig *m_config;
xcb_randr_output_t m_id;
QString m_name;
QString m_icon;
mutable QByteArray m_edid;
xcb_randr_connection_t m_connected;
bool m_primary;
KScreen::Output::Type m_type;
XRandRMode::Map m_modes;
QStringList m_preferredModes;
QList<xcb_randr_output_t> m_clones;
unsigned int m_widthMm;
unsigned int m_heightMm;
bool m_hotplugModeUpdate = false;
XRandRCrtc *m_crtc;
};
Q_DECLARE_METATYPE(XRandROutput::Map)

View File

@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
* SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "xrandrscreen.h"
#include "../xcbwrapper.h"
#include "config.h"
#include "screen.h"
#include "xrandrconfig.h"
#include <QX11Info>
XRandRScreen::XRandRScreen(XRandRConfig *config)
: QObject(config)
{
XCB::ScreenSize size(XRandR::rootWindow());
m_maxSize = QSize(size->max_width, size->max_height);
m_minSize = QSize(size->min_width, size->min_height);
update();
}
XRandRScreen::~XRandRScreen()
{
}
void XRandRScreen::update()
{
xcb_screen_t *screen = XCB::screenOfDisplay(XCB::connection(), QX11Info::appScreen());
m_currentSize = QSize(screen->width_in_pixels, screen->height_in_pixels);
}
void XRandRScreen::update(const QSize &size)
{
m_currentSize = size;
}
QSize XRandRScreen::currentSize()
{
return m_currentSize;
}
KScreen::ScreenPtr XRandRScreen::toKScreenScreen() const
{
KScreen::ScreenPtr kscreenScreen(new KScreen::Screen);
kscreenScreen->setId(m_id);
kscreenScreen->setMaxSize(m_maxSize);
kscreenScreen->setMinSize(m_minSize);
kscreenScreen->setCurrentSize(m_currentSize);
XCB::ScopedPointer<xcb_randr_get_screen_resources_reply_t> screenResources(XRandR::screenResources());
kscreenScreen->setMaxActiveOutputsCount(screenResources->num_crtcs);
return kscreenScreen;
}
void XRandRScreen::updateKScreenScreen(KScreen::ScreenPtr &screen) const
{
screen->setCurrentSize(m_currentSize);
}

View File

@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: 2012, 2013 Daniel Vrátil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#pragma once
#include "types.h"
#include <QObject>
#include <QSize>
class XRandRConfig;
namespace KScreen
{
class Screen;
class Config;
}
class XRandRScreen : public QObject
{
Q_OBJECT
public:
explicit XRandRScreen(XRandRConfig *config = nullptr);
~XRandRScreen() override;
KScreen::ScreenPtr toKScreenScreen() const;
void updateKScreenScreen(KScreen::ScreenPtr &screen) const;
void update();
void update(const QSize &size);
QSize currentSize();
private:
int m_id;
QSize m_minSize;
QSize m_maxSize;
QSize m_currentSize;
};

View File

@ -0,0 +1,25 @@
find_package(XCB REQUIRED COMPONENTS XCB RANDR)
include_directories(${CMAKE_SOURCE_DIR}/src
${CMAKE_BUILD_DIR}
${QT_INCLUDES}
)
add_library(KSC_XRandR11 MODULE)
target_sources(KSC_XRandR11 PRIVATE
xrandr11.cpp
../xcbeventlistener.cpp
../xcbwrapper.cpp
)
set_target_properties(KSC_XRandR11 PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/kf5/kscreen")
set_target_properties(KSC_XRandR11 PROPERTIES PREFIX "")
target_link_libraries(KSC_XRandR11 Qt::Core
Qt::Gui
Qt::X11Extras
${XCB_LIBRARIES}
KF5::Screen
)
install(TARGETS KSC_XRandR11 DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kscreen/)

View File

@ -0,0 +1,179 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "xrandr11.h"
#include "../xcbeventlistener.h"
#include "config.h"
#include "edid.h"
#include "output.h"
#include <xcb/randr.h>
#include <xcb/xcb.h>
#include <QDebug>
#include <QString>
Q_LOGGING_CATEGORY(KSCREEN_XRANDR11, "kscreen.xrandr11")
XRandR11::XRandR11()
: KScreen::AbstractBackend()
, m_valid(false)
, m_x11Helper(nullptr)
, m_currentConfig(new KScreen::Config)
, m_currentTimestamp(0)
{
xcb_generic_error_t *error = nullptr;
xcb_randr_query_version_reply_t *version;
version = xcb_randr_query_version_reply(XCB::connection(), //
xcb_randr_query_version(XCB::connection(), XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION),
&error);
if (!version || error) {
free(error);
XCB::closeConnection();
qCDebug(KSCREEN_XRANDR11) << "Can't get XRandR version";
return;
}
if (version->major_version != 1 || version->minor_version != 1) {
XCB::closeConnection();
qCDebug(KSCREEN_XRANDR11) << "This backend is only for XRandR 1.1, your version is: " << version->major_version << "." << version->minor_version;
return;
}
m_x11Helper = new XCBEventListener();
connect(m_x11Helper, &XCBEventListener::outputsChanged, this, &XRandR11::updateConfig);
m_valid = true;
}
XRandR11::~XRandR11()
{
XCB::closeConnection();
delete m_x11Helper;
}
QString XRandR11::name() const
{
return QStringLiteral("XRandR 1.1");
}
QString XRandR11::serviceName() const
{
return QStringLiteral("org.kde.KScreen.Backend.XRandR11");
}
bool XRandR11::isValid() const
{
return m_valid;
}
KScreen::ConfigPtr XRandR11::config() const
{
KScreen::ConfigPtr config(new KScreen::Config);
auto features = KScreen::Config::Feature::Writable | KScreen::Config::Feature::PrimaryDisplay;
config->setSupportedFeatures(features);
const int screenId = QX11Info::appScreen();
xcb_screen_t *xcbScreen = XCB::screenOfDisplay(XCB::connection(), screenId);
const XCB::ScreenInfo info(xcbScreen->root);
const XCB::ScreenSize size(xcbScreen->root);
if (info->config_timestamp == m_currentTimestamp) {
return m_currentConfig;
}
KScreen::ScreenPtr screen(new KScreen::Screen);
screen->setId(screenId);
screen->setCurrentSize(QSize(xcbScreen->width_in_pixels, xcbScreen->height_in_pixels));
if (size) { // RRGetScreenSize may file on VNC/RDP connections
screen->setMaxSize(QSize(size->max_width, size->max_height));
screen->setMinSize(QSize(size->min_width, size->min_height));
} else {
screen->setMaxSize(screen->currentSize());
screen->setMinSize(screen->currentSize());
}
screen->setMaxActiveOutputsCount(1);
config->setScreen(screen);
KScreen::OutputList outputs;
KScreen::OutputPtr output(new KScreen::Output);
output->setId(1);
output->setConnected(true);
output->setEnabled(true);
output->setName(QStringLiteral("Default"));
output->setPos(QPoint(0, 0));
output->setPrimary(true);
output->setRotation((KScreen::Output::Rotation)info->rotation);
output->setSizeMm(QSize(xcbScreen->width_in_millimeters, xcbScreen->height_in_millimeters));
outputs.insert(1, output);
config->setOutputs(outputs);
KScreen::ModePtr mode;
KScreen::ModeList modes;
auto iter = xcb_randr_get_screen_info_rates_iterator(info);
xcb_randr_screen_size_t *sizes = xcb_randr_get_screen_info_sizes(info);
for (int x = 0; x < info->nSizes; x++) {
const xcb_randr_screen_size_t size = sizes[x];
const uint16_t *rates = xcb_randr_refresh_rates_rates(iter.data);
const int nrates = xcb_randr_refresh_rates_rates_length(iter.data);
for (int j = 0; j < nrates; j++) {
float rate = rates[j];
mode = KScreen::ModePtr(new KScreen::Mode);
mode->setId(QStringLiteral("%1-%2").arg(x).arg(j));
mode->setSize(QSize(size.width, size.height));
mode->setRefreshRate(rate);
mode->setName(QStringLiteral("%1x%2").arg(size.width).arg(size.height));
if (x == info->sizeID && rate == info->rate) {
output->setCurrentModeId(mode->id());
output->setSize(mode->size());
}
modes.insert(mode->id(), mode);
}
xcb_randr_refresh_rates_next(&iter);
}
output->setModes(modes);
return config;
}
void XRandR11::setConfig(const KScreen::ConfigPtr &config)
{
const KScreen::OutputPtr output = config->outputs().take(1);
const KScreen::ModePtr mode = output->currentMode();
const int screenId = QX11Info::appScreen();
xcb_screen_t *xcbScreen = XCB::screenOfDisplay(XCB::connection(), screenId);
const XCB::ScreenInfo info(xcbScreen->root);
xcb_generic_error_t *err;
const int sizeId = mode->id().split(QLatin1Char('-')).first().toInt();
auto cookie = xcb_randr_set_screen_config(XCB::connection(),
xcbScreen->root,
XCB_CURRENT_TIME,
info->config_timestamp,
sizeId,
(short)output->rotation(),
mode->refreshRate());
XCB::ScopedPointer<xcb_randr_set_screen_config_reply_t> reply(xcb_randr_set_screen_config_reply(XCB::connection(), cookie, &err));
if (err) {
free(err);
}
}
void XRandR11::updateConfig()
{
m_currentConfig = config();
Q_EMIT configChanged(m_currentConfig);
}

View File

@ -0,0 +1,45 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef XRANDR11_BACKEND_H
#define XRANDR11_BACKEND_H
#include "../xcbwrapper.h"
#include "abstractbackend.h"
#include <QLoggingCategory>
#include <QObject>
class XCBEventListener;
class XRandR11 : public KScreen::AbstractBackend
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.kf5.kscreen.backends.xrandr11")
public:
explicit XRandR11();
~XRandR11() override;
QString name() const override;
QString serviceName() const override;
KScreen::ConfigPtr config() const override;
void setConfig(const KScreen::ConfigPtr &config) override;
bool isValid() const override;
private Q_SLOTS:
void updateConfig();
private:
bool m_valid;
XCBEventListener *m_x11Helper;
KScreen::ConfigPtr m_currentConfig;
xcb_timestamp_t m_currentTimestamp;
};
Q_DECLARE_LOGGING_CATEGORY(KSCREEN_XRANDR11)
#endif // FAKE_BACKEND_H

View File

@ -0,0 +1,25 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.kscreen.Backend">
<method name="getConfig">
<arg type="a{sv}" direction="out" />
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap" />
</method>
<method name="setConfig">
<arg type="a{sv}" direction="in" />
<arg type="a{sv}" direction="out" />
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap" />
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap" />
</method>
<signal name="configChanged">
<arg type="a{sv}" direction="out" />
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap" />
</signal>
<method name="getEdid">
<arg type="i" direction="in" />
<arg type="ay" direction="out" />
</method>
</interface>
</node>

View File

@ -0,0 +1,32 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.kscreen.FakeBackend">
<method name="setConnected">
<arg type="i" name="outputId" direction="in" />
<arg type="b" name="connected" direction="in" />
</method>
<method name="setEnabled">
<arg type="i" name="outputId" direction="in" />
<arg type="b" name="enabled" direction="in" />
</method>
<method name="setPrimary">
<arg type="i" name="outputId" direction="in" />
<arg type="b" name="primary" direction="in" />
</method>
<method name="setCurrentModeId">
<arg type="i" name="outputId" direction="in" />
<arg type="s" name="currentModeId" direction="in" />
</method>
<method name="setRotation">
<arg type="i" name="outputId" direction="in" />
<arg type="i" name="rotation" direction="in" />
</method>
<method name="addOutput">
<arg type="i" name="outputId" direction="in" />
<arg type="s" name="name" direction="in" />
</method>
<method name="removeOutput">
<arg type="i" name="outputId" direction="in" />
</method>
</interface>
</node>

View File

@ -0,0 +1,16 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.KScreen">
<method name="backend">
<arg type="s" direction="out" />
</method>
<method name="requestBackend">
<arg type="s" direction="in" />
<arg type="a{sv}" direction="in" />
<arg type="b" direction="out" />
<annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
</method>
<method name="quit" />
</interface>
</node>

11
libkscreen.categories Normal file
View File

@ -0,0 +1,11 @@
org.kde.kscreen kscreen IDENTIFIER [KSCREEN]
org.kde.kscreen.edid kscreen (edid) IDENTIFIER [KSCREEN_EDID]
org.kde.kscreen.backendLauncher kscreen (backendlauncher) IDENTIFIER [KSCREEN_BACKEND_LAUNCHER]
kscreen.xcb.helper libkscreen (xcb helper) IDENTIFIER [KSCREEN_XCB_HELPER]
kscreen.qscreen libkscreen (qscreen backend) IDENTIFIER [KSCREEN_QSCREEN]
kscreen.kwayland libkscreen (kwayland backend) IDENTIFIER [KSCREEN_WAYLAND]
kscreen.fake libkscreen (fake backend) IDENTIFIER [KSCREEN_FAKE]
kscreen.kwayland.testserver libkscreen (kwayland test server) IDENTIFIER [KSCREEN_WAYLAND_TESTSERVER]
kscreen.doctor libkscreen (kscreen-doctor utility) IDENTIFIER [KSCREEN_DOCTOR]
kscreen.xrandr libkscreen (xrandr backend) IDENTIFIER [KSCREEN_XRANDR]
kscreen.xrandr11 libkscreen (xrandr1.1 backend) IDENTIFIER [KSCREEN_XRANDR11]

112
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,112 @@
add_subdirectory(backendlauncher)
add_subdirectory(doctor)
set(libkscreen_SRCS
abstractbackend.cpp
backendmanager.cpp
config.cpp
configoperation.cpp
getconfigoperation.cpp
setconfigoperation.cpp
configmonitor.cpp
configserializer.cpp
screen.cpp
output.cpp
edid.cpp
mode.cpp
log.cpp
)
qt_add_dbus_interface(libkscreen_SRCS ${CMAKE_SOURCE_DIR}/interfaces/org.kde.KScreen.Backend.xml backendinterface)
ecm_qt_declare_logging_category(libkscreen_SRCS
HEADER kscreen_debug.h
IDENTIFIER KSCREEN
CATEGORY_NAME org.kde.kscreen
)
ecm_qt_declare_logging_category(libkscreen_SRCS
HEADER kscreen_debug_edid.h
IDENTIFIER KSCREEN_EDID
CATEGORY_NAME org.kde.kscreen.edid
)
add_library(KF5Screen SHARED ${libkscreen_SRCS})
generate_export_header(KF5Screen BASE_NAME KScreen)
target_link_libraries(KF5Screen
PUBLIC
Qt::Core
Qt::Gui
PRIVATE
Qt::DBus
Qt::X11Extras
)
set_target_properties(KF5Screen PROPERTIES
VERSION "${KSCREEN_VERSION}"
SOVERSION "${KSCREEN_SOVERSION}"
EXPORT_NAME Screen
)
target_include_directories(KF5Screen PUBLIC "$<INSTALL_INTERFACE:${KF5_INCLUDE_INSTALL_DIR}/KScreen>")
install(TARGETS KF5Screen EXPORT KF5ScreenTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
add_library(KF5::Screen ALIAS KF5Screen)
ecm_generate_headers(KScreen_HEADERS
HEADER_NAMES
Log
Mode
Output
EDID
Screen
Config
ConfigMonitor
ConfigOperation
GetConfigOperation
SetConfigOperation
Types
PREFIX KScreen
REQUIRED_HEADERS KScreen_REQ_HEADERS
)
install(FILES ${KScreen_HEADERS}
DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KScreen/KScreen
COMPONENT Devel)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kscreen_export.h
backendmanager_p.h # needed for unit-tests in KScreen
${KScreen_REQ_HEADERS}
DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KScreen/kscreen)
if(NOT WIN32)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/kscreen2.pc.in ${CMAKE_CURRENT_BINARY_DIR}/kscreen2.pc @ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kscreen2.pc DESTINATION ${KDE_INSTALL_LIBDIR}/pkgconfig)
endif(NOT WIN32)
include(ECMGeneratePriFile)
ecm_generate_pri_file(BASE_NAME KScreen LIB_NAME KF5Screen DEPS "core" FILENAME_VAR PRI_FILENAME)
install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
if(BUILD_QCH)
ecm_add_qch(
KF5Screen_QCH
NAME KScreen
BASE_NAME KF5Screen
VERSION ${PROJECT_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${KScreen_REQ_HEADERS}
# MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt5Core_QCH
Qt5Gui_QCH
BLANK_MACROS
KSCREEN_EXPORT
KSCREEN_DEPRECATED
KSCREEN_DEPRECATED_EXPORT
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()

18
src/abstractbackend.cpp Normal file
View File

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "abstractbackend.h"
void KScreen::AbstractBackend::init(const QVariantMap &arguments)
{
Q_UNUSED(arguments);
}
QByteArray KScreen::AbstractBackend::edid(int outputId) const
{
Q_UNUSED(outputId);
return QByteArray();
}

104
src/abstractbackend.h Normal file
View File

@ -0,0 +1,104 @@
/*
* SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
* SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef ABSTRACT_BACKEND_H
#define ABSTRACT_BACKEND_H
#include "kscreen_export.h"
#include "types.h"
#include <QObject>
#include <QString>
namespace KScreen
{
class Config;
/**
* Abstract class for backends.
*/
class KSCREEN_EXPORT AbstractBackend : public QObject
{
Q_OBJECT
public:
~AbstractBackend() override
{
}
/**
* This is where the backend should perform all initialization. This method
* is always called right after the backend is created.
*
* Default implementation does nothing.
*
* @p arguments Optional arguments passed by caller. Used mostly for unit-testing.
*/
virtual void init(const QVariantMap &arguments);
/**
* Returns a user-friendly name of the backend.
*/
virtual QString name() const = 0;
/**
* Returns the name of the DBus service that should be used for this backend.
*
* Each backend must have an unique service name (usually something like
* org.kde.KScreen.Backend.%backendName%) to allow multiple different backends
* running concurrently.
*/
virtual QString serviceName() const = 0;
/**
* Returns a new Config object, holding Screen, Output objects, etc.
*
* @return Config object for the system.
*/
virtual KScreen::ConfigPtr config() const = 0;
/**
* Apply a config object to the system.
*
* @param config Configuration to apply
*/
virtual void setConfig(const KScreen::ConfigPtr &config) = 0;
/**
* Returns whether the backend is in valid state.
*
* Backends should use this to tell BackendLauncher whether they are capable
* of operating on the current platform.
*/
virtual bool isValid() const = 0;
/**
* Returns encoded EDID data for given output
*
* Default implementation does nothing and returns null QByteArray. Backends
* that don't support EDID don't have to reimplement this method.
*
* @param outputd ID of output to return EDID data for
*/
virtual QByteArray edid(int outputId) const;
Q_SIGNALS:
/**
* Emitted when backend detects a change in configuration
*
* It's OK to emit this signal for every single change. The emissions are aggregated
* in the backend launcher, so that the backend does not spam DBus and client
* applications.
*
* @param config New configuration
*/
void configChanged(const KScreen::ConfigPtr &config);
};
} // namespace KScreen
#endif // ABSTRACT_BACKEND_H

View File

@ -0,0 +1,44 @@
set(backendlauncher_SRCS
main.cpp
backendloader.cpp
backenddbuswrapper.cpp
)
ecm_qt_declare_logging_category(backendlauncher_SRCS
HEADER kscreen_backendLauncher_debug.h
IDENTIFIER KSCREEN_BACKEND_LAUNCHER
CATEGORY_NAME org.kde.kscreen.backendLauncher
)
qt_add_dbus_adaptor(backendlauncher_SRCS ${CMAKE_SOURCE_DIR}/interfaces/org.kde.KScreen.Backend.xml
backenddbuswrapper.h BackendDBusWrapper backendadaptor BackendAdaptor)
qt_add_dbus_adaptor(backendlauncher_SRCS ${CMAKE_SOURCE_DIR}/interfaces/org.kde.KScreen.xml
backendloader.h BackendLoader backendloaderadaptor BackendLoaderAdaptor)
add_executable(kscreen_backend_launcher ${backendlauncher_SRCS})
target_link_libraries(kscreen_backend_launcher
KF5Screen
Qt::Core
Qt::Gui
Qt::X11Extras
Qt::DBus
)
install(TARGETS kscreen_backend_launcher
DESTINATION ${CMAKE_INSTALL_FULL_LIBEXECDIR_KF5}
)
configure_file(org.kde.kscreen.service.cmake
${CMAKE_CURRENT_BINARY_DIR}/org.kde.kscreen.service @ONLY
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kscreen.service
DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR}
)
configure_file(plasma-kscreen.service.cmake
${CMAKE_CURRENT_BINARY_DIR}/plasma-kscreen.service @ONLY
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plasma-kscreen.service
DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR}
)

View File

@ -0,0 +1,116 @@
/*
* SPDX-FileCopyrightText: 2014 Daniel Vratil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#include "backenddbuswrapper.h"
#include "backendadaptor.h"
#include "backendloader.h"
#include "kscreen_backendLauncher_debug.h"
#include "abstractbackend.h"
#include "config.h"
#include "configserializer_p.h"
#include <QDBusConnection>
#include <QDBusError>
BackendDBusWrapper::BackendDBusWrapper(KScreen::AbstractBackend *backend)
: QObject()
, mBackend(backend)
{
connect(mBackend, &KScreen::AbstractBackend::configChanged, this, &BackendDBusWrapper::backendConfigChanged);
mChangeCollector.setSingleShot(true);
mChangeCollector.setInterval(200); // wait for 200 msecs without any change
// before actually emitting configChanged
connect(&mChangeCollector, &QTimer::timeout, this, &BackendDBusWrapper::doEmitConfigChanged);
}
BackendDBusWrapper::~BackendDBusWrapper()
{
}
bool BackendDBusWrapper::init()
{
QDBusConnection dbus = QDBusConnection::sessionBus();
new BackendAdaptor(this);
if (!dbus.registerObject(QStringLiteral("/backend"), this, QDBusConnection::ExportAdaptors)) {
qCWarning(KSCREEN_BACKEND_LAUNCHER) << "Failed to export backend to DBus: another launcher already running?";
qCWarning(KSCREEN_BACKEND_LAUNCHER) << dbus.lastError().message();
return false;
}
return true;
}
QVariantMap BackendDBusWrapper::getConfig() const
{
const KScreen::ConfigPtr config = mBackend->config();
Q_ASSERT(!config.isNull());
if (!config) {
qCWarning(KSCREEN_BACKEND_LAUNCHER) << "Backend provided an empty config!";
return QVariantMap();
}
const QJsonObject obj = KScreen::ConfigSerializer::serializeConfig(mBackend->config());
Q_ASSERT(!obj.isEmpty());
return obj.toVariantMap();
}
QVariantMap BackendDBusWrapper::setConfig(const QVariantMap &configMap)
{
if (configMap.isEmpty()) {
qCWarning(KSCREEN_BACKEND_LAUNCHER) << "Received an empty config map";
return QVariantMap();
}
const KScreen::ConfigPtr config = KScreen::ConfigSerializer::deserializeConfig(configMap);
mBackend->setConfig(config);
mCurrentConfig = mBackend->config();
QMetaObject::invokeMethod(this, "doEmitConfigChanged", Qt::QueuedConnection);
// TODO: setConfig should return adjusted config that was actually applied
const QJsonObject obj = KScreen::ConfigSerializer::serializeConfig(mCurrentConfig);
Q_ASSERT(!obj.isEmpty());
return obj.toVariantMap();
}
QByteArray BackendDBusWrapper::getEdid(int output) const
{
const QByteArray edidData = mBackend->edid(output);
if (edidData.isEmpty()) {
return QByteArray();
}
return edidData;
}
void BackendDBusWrapper::backendConfigChanged(const KScreen::ConfigPtr &config)
{
Q_ASSERT(!config.isNull());
if (!config) {
qCWarning(KSCREEN_BACKEND_LAUNCHER) << "Backend provided an empty config!";
return;
}
mCurrentConfig = config;
mChangeCollector.start();
}
void BackendDBusWrapper::doEmitConfigChanged()
{
Q_ASSERT(!mCurrentConfig.isNull());
if (mCurrentConfig.isNull()) {
return;
}
const QJsonObject obj = KScreen::ConfigSerializer::serializeConfig(mCurrentConfig);
Q_EMIT configChanged(obj.toVariantMap());
mCurrentConfig.clear();
mChangeCollector.stop();
}

View File

@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2014 Daniel Vratil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#ifndef BACKENDDBUSWRAPPER_H
#define BACKENDDBUSWRAPPER_H
#include <QObject>
#include <QTimer>
#include "types.h"
namespace KScreen
{
class AbstractBackend;
}
class BackendDBusWrapper : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.KScreen.Backend")
public:
explicit BackendDBusWrapper(KScreen::AbstractBackend *backend);
~BackendDBusWrapper() override;
bool init();
QVariantMap getConfig() const;
QVariantMap setConfig(const QVariantMap &config);
QByteArray getEdid(int output) const;
inline KScreen::AbstractBackend *backend() const
{
return mBackend;
}
Q_SIGNALS:
void configChanged(const QVariantMap &config);
private Q_SLOTS:
void backendConfigChanged(const KScreen::ConfigPtr &config);
void doEmitConfigChanged();
private:
KScreen::AbstractBackend *mBackend = nullptr;
QTimer mChangeCollector;
KScreen::ConfigPtr mCurrentConfig;
};
#endif // BACKENDDBUSWRAPPER_H

View File

@ -0,0 +1,115 @@
/*
* SPDX-FileCopyrightText: 2014 Daniel Vratil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#include "backendloader.h"
#include "abstractbackend.h"
#include "backenddbuswrapper.h"
#include "backendloaderadaptor.h"
#include "backendmanager_p.h"
#include "kscreen_backendLauncher_debug.h"
#include <QCoreApplication>
#include <QDBusConnectionInterface>
#include <QDir>
#include <QPluginLoader>
#include <QX11Info>
#include <memory>
#include <QDBusConnection>
#include <QDBusInterface>
void pluginDeleter(QPluginLoader *p)
{
if (p) {
qCDebug(KSCREEN_BACKEND_LAUNCHER) << "Unloading" << p->fileName();
p->unload();
delete p;
}
}
BackendLoader::BackendLoader()
: QObject()
, QDBusContext()
, mLoader(nullptr)
, mBackend(nullptr)
{
}
BackendLoader::~BackendLoader()
{
delete mBackend;
pluginDeleter(mLoader);
qCDebug(KSCREEN_BACKEND_LAUNCHER) << "Backend loader destroyed";
}
bool BackendLoader::init()
{
QDBusConnection dbus = QDBusConnection::sessionBus();
new BackendLoaderAdaptor(this);
if (!dbus.registerObject(QStringLiteral("/"), this, QDBusConnection::ExportAdaptors)) {
qCWarning(KSCREEN_BACKEND_LAUNCHER) << "Failed to export backend to DBus: another launcher already running?";
qCWarning(KSCREEN_BACKEND_LAUNCHER) << dbus.lastError().message();
return false;
}
return true;
}
QString BackendLoader::backend() const
{
if (mBackend) {
return mBackend->backend()->name();
}
return QString();
}
bool BackendLoader::requestBackend(const QString &backendName, const QVariantMap &arguments)
{
if (mBackend) {
// If an backend is already loaded, but it's not the same as the one
// requested, then it's an error
if (!backendName.isEmpty() && mBackend->backend()->name() != backendName) {
sendErrorReply(QDBusError::Failed, QStringLiteral("Another backend is already active"));
return false;
} else {
// If caller requested the same one as already loaded, or did not
// request a specific backend, hapilly reuse the existing one
return true;
}
}
KScreen::AbstractBackend *backend = loadBackend(backendName, arguments);
if (!backend) {
return false;
}
mBackend = new BackendDBusWrapper(backend);
if (!mBackend->init()) {
delete mBackend;
mBackend = nullptr;
pluginDeleter(mLoader);
mLoader = nullptr;
return false;
}
return true;
}
KScreen::AbstractBackend *BackendLoader::loadBackend(const QString &name, const QVariantMap &arguments)
{
if (mLoader == nullptr) {
std::unique_ptr<QPluginLoader, void (*)(QPluginLoader *)> loader(new QPluginLoader(), pluginDeleter);
mLoader = loader.release();
}
return KScreen::BackendManager::loadBackendPlugin(mLoader, name, arguments);
}
void BackendLoader::quit()
{
qApp->quit();
}

View File

@ -0,0 +1,45 @@
/*
* SPDX-FileCopyrightText: 2014 Daniel Vratil <dvratil@redhat.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
*/
#ifndef BACKENDLAUNCHER_H
#define BACKENDLAUNCHER_H
#include <QDBusContext>
#include <QObject>
namespace KScreen
{
class AbstractBackend;
}
class QPluginLoader;
class BackendDBusWrapper;
class BackendLoader : public QObject, protected QDBusContext
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.KScreen")
public:
explicit BackendLoader();
~BackendLoader() override;
bool init();
Q_INVOKABLE QString backend() const;
Q_INVOKABLE bool requestBackend(const QString &name, const QVariantMap &arguments);
Q_INVOKABLE void quit();
private:
KScreen::AbstractBackend *loadBackend(const QString &name, const QVariantMap &arguments);
private:
QPluginLoader *mLoader = nullptr;
BackendDBusWrapper *mBackend = nullptr;
};
#endif // BACKENDLAUNCHER_H

Some files were not shown because too many files have changed in this diff Show More