Import Upstream version 5.24.4
This commit is contained in:
commit
749f68970d
|
@ -0,0 +1,2 @@
|
|||
# clang-format
|
||||
0ae03aa6e4b2afd6f1348ff16502a6257f3580e4
|
|
@ -0,0 +1,10 @@
|
|||
.kdev4
|
||||
*.kdev4
|
||||
/build*
|
||||
.clang-format
|
||||
cmake-build-debug*
|
||||
.idea
|
||||
/compile_commands.json
|
||||
.clangd
|
||||
.cache
|
||||
|
|
@ -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
|
|
@ -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'
|
|
@ -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)
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
|
@ -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@
|
|
@ -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
|
|
@ -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
|
|
@ -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>.
|
|
@ -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!
|
|
@ -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.
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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=="
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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=="
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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="
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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="
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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="
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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=="
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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"
|
|
@ -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()
|
|
@ -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/)
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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/)
|
|
@ -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>
|
||||
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
|
@ -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)
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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/)
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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());
|
||||
}
|
|
@ -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
|
|
@ -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/)
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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/)
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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]
|
|
@ -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()
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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}
|
||||
)
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue